Compare commits

...

7 Commits

Author SHA1 Message Date
Alex Yang 733c0e3057 fix: interrupt 2024-11-13 11:01:28 -08:00
Alex Yang 628be9eb66 Merge remote-tracking branch 'origin/main' into himself65/20241113/warn 2024-11-13 01:18:19 -08:00
Alex Yang 1c103921ae changeset 2024-11-13 01:09:30 -08:00
Alex Yang f4cf37b643 feat: recoverable data with error handling 2024-11-13 01:08:34 -08:00
Alex Yang 463bf8b630 fix: code 2024-11-13 00:48:03 -08:00
Alex Yang 236828a331 Merge remote-tracking branch 'origin/main' into himself65/20241113/warn 2024-11-13 00:47:03 -08:00
Alex Yang b86208b703 docs: update 2024-11-13 00:27:05 -08:00
10 changed files with 1773 additions and 127 deletions
+10
View File
@@ -6,6 +6,16 @@ const withMDX = createMDX();
const config = {
reactStrictMode: true,
transpilePackages: ["monaco-editor"],
images: {
remotePatterns: [
{
protocol: "https",
hostname: "mermaid.ink",
port: "",
pathname: "/**",
},
],
},
webpack: (config, { isServer }) => {
if (Array.isArray(config.target) && config.target.includes("web")) {
config.target = ["web", "es2020"];
+3
View File
@@ -24,6 +24,8 @@
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-icons": "^1.3.1",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slider": "^1.2.1",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.3",
@@ -52,6 +54,7 @@
"react-text-transition": "^3.1.0",
"react-use-measure": "^2.1.1",
"rehype-katex": "^7.0.1",
"rehype-mermaid": "^3.0.0",
"remark-math": "^6.0.0",
"rimraf": "^6.0.1",
"shiki": "^1.22.2",
+213
View File
@@ -0,0 +1,213 @@
"use client";
import type { ShippingInfo } from "@/components/demo/checkout/shpping";
import {
StartEvent,
StopEvent,
Workflow,
WorkflowContext,
WorkflowEvent,
} from "@llamaindex/workflow";
import { ReactNode, useCallback, useState } from "react";
import { Button } from "../ui/button";
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from "../ui/card";
import { Separator } from "../ui/separator";
type Context = {
userId: string;
items: { name: string; price: number; quantity: number }[];
// utility functions with frontend
update: (ui: ReactNode) => void;
interrupt: () => void;
};
type UserInfo = {
id: string;
name: string;
email: string;
shippingAddress: ShippingInfo;
};
const getUser = async (userId: string): Promise<UserInfo | null> => {
await new Promise((resolve) => setTimeout(resolve, 1000));
const user = localStorage.getItem(`workflow-demo-user:${userId}`);
if (user !== null) {
return JSON.parse(user) as UserInfo;
} else {
return null;
}
};
const checkoutWorkflow = new Workflow<Context, void, void>({});
// 1. User click checkout button
// 2. Check if user has correct information
// - If not, show shipping information form
// 3. Proceed to payment
// 4. Confirm the order
// 5. Send confirmation email
class ShowShippingInformationFormEvent extends WorkflowEvent<void> {}
class ProceedToPaymentEvent extends WorkflowEvent<void> {}
class ConfirmOrderEvent extends WorkflowEvent<void> {}
class SendConfirmationEmailEvent extends WorkflowEvent<void> {}
checkoutWorkflow.addStep(
{
inputs: [StartEvent],
outputs: [ProceedToPaymentEvent, ShowShippingInformationFormEvent],
},
async ({ data }, _) => {
const user = await getUser(data.userId);
if (user === null) {
return new ShowShippingInformationFormEvent();
}
// todo
return new ProceedToPaymentEvent();
},
);
checkoutWorkflow.addStep(
{
inputs: [ShowShippingInformationFormEvent],
outputs: [ProceedToPaymentEvent],
},
async ({ data }) => {
const user = await getUser(data.userId);
if (user === null) {
data.update("TODO");
data.interrupt();
}
return new ProceedToPaymentEvent();
},
);
checkoutWorkflow.addStep(
{
inputs: [ProceedToPaymentEvent],
outputs: [ConfirmOrderEvent],
},
async () => {
return new ConfirmOrderEvent();
},
);
checkoutWorkflow.addStep(
{
inputs: [ConfirmOrderEvent],
outputs: [SendConfirmationEmailEvent],
},
async () => {
return new SendConfirmationEmailEvent();
},
);
checkoutWorkflow.addStep(
{
inputs: [SendConfirmationEmailEvent],
outputs: [StopEvent<void>],
},
async () => {
return new StopEvent(void 0);
},
);
async function runContext(context: WorkflowContext<void, void, Context>) {
try {
await context;
} catch (e) {
if (e instanceof Error && e.message === "Interrupted") {
const snapshot = context.snapshot();
// for each step, save the snapshot into the local storage
localStorage.setItem(
"workflow-snapshot",
new TextDecoder().decode(snapshot),
);
} else {
throw e;
}
}
}
export const CheckoutDemo = () => {
const items = [
{ name: "T-Shirt", price: 19.99, quantity: 2 },
{ name: "Jeans", price: 49.99, quantity: 1 },
{ name: "Sneakers", price: 79.99, quantity: 1 },
];
const [ui, setUI] = useState<ReactNode>(null);
const [contextData, setContextData] = useState<Context>({
userId: "1",
items: items,
update: (ui) => setUI(ui),
interrupt: () => {
throw new Error("Interrupted");
},
});
const subtotal = items.reduce(
(acc, item) => acc + item.price * item.quantity,
0,
);
const tax = subtotal * 0.1; // Assuming 10% tax
const total = subtotal + tax;
const onClickStartCheckout = useCallback(() => {
const context = checkoutWorkflow.run().with(contextData);
runContext(context);
}, []);
return (
<div>
{ui}
<Card className="w-full max-w-md mx-auto">
<CardHeader>
<CardTitle>Your Order</CardTitle>
</CardHeader>
<CardContent>
<ul className="space-y-2">
{items.map((item, index) => (
<li key={index} className="flex justify-between items-center">
<span>
{item.name} x{item.quantity}
</span>
<span>${(item.price * item.quantity).toFixed(2)}</span>
</li>
))}
</ul>
<Separator className="my-4" />
<div className="space-y-2">
<div className="flex justify-between">
<span>Subtotal</span>
<span>${subtotal.toFixed(2)}</span>
</div>
<div className="flex justify-between">
<span>Tax</span>
<span>${tax.toFixed(2)}</span>
</div>
<div className="flex justify-between font-bold">
<span>Total</span>
<span>${total.toFixed(2)}</span>
</div>
</div>
</CardContent>
<CardFooter>
<Button className="w-full" onClick={onClickStartCheckout}>
Proceed to Checkout
</Button>
</CardFooter>
</Card>
</div>
);
};
@@ -0,0 +1,126 @@
"use client";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useState } from "react";
export function ShippingForm({
onSubmit,
}: {
onSubmit: (data: ShippingInfo) => void;
}) {
const [formData, setFormData] = useState<ShippingInfo>({
fullName: "",
address: "",
city: "",
state: "",
zipCode: "",
country: "",
});
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
};
const handleCountryChange = (value: string) => {
setFormData((prev) => ({ ...prev, country: value }));
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit(formData);
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<Label htmlFor="fullName">Full Name</Label>
<Input
id="fullName"
name="fullName"
value={formData.fullName}
onChange={handleChange}
required
/>
</div>
<div>
<Label htmlFor="address">Address</Label>
<Input
id="address"
name="address"
value={formData.address}
onChange={handleChange}
required
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="city">City</Label>
<Input
id="city"
name="city"
value={formData.city}
onChange={handleChange}
required
/>
</div>
<div>
<Label htmlFor="state">State</Label>
<Input
id="state"
name="state"
value={formData.state}
onChange={handleChange}
required
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="zipCode">ZIP Code</Label>
<Input
id="zipCode"
name="zipCode"
value={formData.zipCode}
onChange={handleChange}
required
/>
</div>
<div>
<Label htmlFor="country">Country</Label>
<Select onValueChange={handleCountryChange} value={formData.country}>
<SelectTrigger id="country">
<SelectValue placeholder="Select country" />
</SelectTrigger>
<SelectContent>
<SelectItem value="us">United States</SelectItem>
<SelectItem value="ca">Canada</SelectItem>
<SelectItem value="uk">United Kingdom</SelectItem>
{/* Add more countries as needed */}
</SelectContent>
</Select>
</div>
</div>
<Button type="submit" className="w-full">
Save Shipping Information
</Button>
</form>
);
}
export interface ShippingInfo {
fullName: string;
address: string;
city: string;
state: string;
zipCode: string;
country: string;
}
+83
View File
@@ -0,0 +1,83 @@
import * as React from "react";
import { cn } from "@/lib/utils";
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-xl border bg-card text-card-foreground shadow",
className,
)}
{...props}
/>
));
Card.displayName = "Card";
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
));
CardHeader.displayName = "CardHeader";
const CardTitle = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("font-semibold leading-none tracking-tight", className)}
{...props}
/>
));
CardTitle.displayName = "CardTitle";
const CardDescription = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
));
CardDescription.displayName = "CardDescription";
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
));
CardContent.displayName = "CardContent";
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
));
CardFooter.displayName = "CardFooter";
export {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
};
+162
View File
@@ -0,0 +1,162 @@
"use client";
import { cn } from "@/lib/utils";
import {
CheckIcon,
ChevronDownIcon,
ChevronUpIcon,
} from "@radix-ui/react-icons";
import * as SelectPrimitive from "@radix-ui/react-select";
import * as React from "react";
const Select = SelectPrimitive.Root;
const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value;
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className,
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className,
)}
{...props}
>
<ChevronUpIcon className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
));
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className,
)}
{...props}
>
<ChevronDownIcon className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
));
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName;
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className,
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
));
SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
{...props}
/>
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className,
)}
{...props}
>
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
));
SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
};
+31
View File
@@ -0,0 +1,31 @@
"use client";
import * as SeparatorPrimitive from "@radix-ui/react-separator";
import * as React from "react";
import { cn } from "@/lib/utils";
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
ref,
) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className,
)}
{...props}
/>
),
);
Separator.displayName = SeparatorPrimitive.Root.displayName;
export { Separator };
@@ -0,0 +1,19 @@
---
title: Event Step Handler
description: Learn how to design a good event handler for your workflow.
---
import { CheckoutDemo } from '../../../../../components/demo/checkout'
Let's start an checkout workflow.
<div className='grid grid-cols-2 gap-4'>
<CheckoutDemo/>
<div
className='border p-4'
style={{
borderColor: 'hsl(var(--border))'
}}
>
[![](https://mermaid.ink/img/pako:eNptkd9ugyAUxl-FcN2-gEu2zD9t7dKtmcuSDXtB5KikCgaPWxrruw_RtmkyruB8Pz7O4etppgVQjxaGNyX5CB9SRex6ZglygyQoITvqDg9kuXwkPvuU8EsCqxwmznf1gEUKwZCklE0jVUFilWtTc5RazWDgwJAlUEGGN3IHWGoxQ6GDotltz081KPzHLHLcir3Dz9jPmxFgZmnlpHUft1OZBNoY--LTMOnrUT9_QXsmG7aveAZ31yf5VZ-JPxU2zi9mFzOVy_teYgds7WBK3OkkqrmsZmrrqBc7mbj9aarogtZgeSlsBP2IphRLqCGlnt0Kbo4pTdVgOd6hTk4qox6aDhbU6K4oqZfzqrWnrhEcIZTc5lhfkIarb62vRxAStdlNebvYhz_ob6Ka?type=png)](https://mermaid.live/edit#pako:eNptkd9ugyAUxl-FcN2-gEu2zD9t7dKtmcuSDXtB5KikCgaPWxrruw_RtmkyruB8Pz7O4etppgVQjxaGNyX5CB9SRex6ZglygyQoITvqDg9kuXwkPvuU8EsCqxwmznf1gEUKwZCklE0jVUFilWtTc5RazWDgwJAlUEGGN3IHWGoxQ6GDotltz081KPzHLHLcir3Dz9jPmxFgZmnlpHUft1OZBNoY--LTMOnrUT9_QXsmG7aveAZ31yf5VZ-JPxU2zi9mFzOVy_teYgds7WBK3OkkqrmsZmrrqBc7mbj9aarogtZgeSlsBP2IphRLqCGlnt0Kbo4pTdVgOd6hTk4qox6aDhbU6K4oqZfzqrWnrhEcIZTc5lhfkIarb62vRxAStdlNebvYhz_ob6Ka)
</div>
</div>
@@ -2,5 +2,12 @@
"title": "Workflow",
"description": "See how to use @llamaindex/workflow",
"defaultOpen": false,
"pages": ["index", "different-inputs-outputs", "streaming"]
"pages": [
"index",
"different-inputs-outputs",
"streaming",
"---Advanced concept---",
"handler",
"..."
]
}
+1118 -126
View File
File diff suppressed because it is too large Load Diff