├── .env.example ├── .eslintrc.json ├── public ├── tv.png ├── iphone.png └── watch.png ├── app ├── favicon.ico └── (preview) │ ├── uncut-sans.woff2 │ ├── layout.tsx │ ├── globals.css │ ├── api │ └── chat │ │ └── route.ts │ └── page.tsx ├── next.config.mjs ├── postcss.config.mjs ├── lib └── schema.ts ├── .gitignore ├── tsconfig.json ├── components ├── reasoning-step.tsx ├── use-scroll-to-bottom.ts ├── data.ts ├── orders.tsx ├── message.tsx ├── tracker.tsx └── icons.tsx ├── package.json ├── README.md └── tailwind.config.ts /.env.example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY=sk-**** -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /public/tv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vercel-labs/ai-sdk-preview-multi-steps/HEAD/public/tv.png -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vercel-labs/ai-sdk-preview-multi-steps/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /public/iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vercel-labs/ai-sdk-preview-multi-steps/HEAD/public/iphone.png -------------------------------------------------------------------------------- /public/watch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vercel-labs/ai-sdk-preview-multi-steps/HEAD/public/watch.png -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /app/(preview)/uncut-sans.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vercel-labs/ai-sdk-preview-multi-steps/HEAD/app/(preview)/uncut-sans.woff2 -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /lib/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const reasoningStepSchema = z.object({ 4 | title: z.string().describe("The title of the reasoning step"), 5 | content: z.string().describe("The content of the reasoning step."), 6 | nextStep: z 7 | .enum(["continue", "finalAnswer"]) 8 | .describe( 9 | "Whether to continue with another step or provide the final answer", 10 | ), 11 | }); 12 | 13 | export type ReasoningStep = z.infer; 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /app/(preview)/layout.tsx: -------------------------------------------------------------------------------- 1 | import "./globals.css"; 2 | import { Metadata } from "next"; 3 | import { Toaster } from "sonner"; 4 | 5 | export const metadata: Metadata = { 6 | metadataBase: new URL("https://ai-sdk-preview-roundtrips.vercel.app"), 7 | title: "Multi-Step Reasoning with the AI SDK", 8 | description: 9 | "Reasoning with multi-step generations and the AI SDK", 10 | }; 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: Readonly<{ 15 | children: React.ReactNode; 16 | }>) { 17 | return ( 18 | 19 | 20 | 21 | {children} 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /components/reasoning-step.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { type ReasoningStep as TReasoningStep } from "@/lib/schema"; 4 | import { motion } from "framer-motion"; 5 | 6 | export const ReasoningStep = ({ step }: { step: TReasoningStep }) => { 7 | return ( 8 | 13 |

14 | reasoning step 15 |

16 |

{step.title}

17 |

{step.content}

18 |
19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /components/use-scroll-to-bottom.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, RefObject } from "react"; 2 | 3 | export function useScrollToBottom(): [ 4 | RefObject, 5 | RefObject, 6 | ] { 7 | const containerRef = useRef(null); 8 | const endRef = useRef(null); 9 | 10 | useEffect(() => { 11 | const container = containerRef.current; 12 | const end = endRef.current; 13 | 14 | if (container && end) { 15 | const observer = new MutationObserver(() => { 16 | end.scrollIntoView({ behavior: "smooth" }); 17 | }); 18 | 19 | observer.observe(container, { 20 | childList: true, 21 | subtree: true, 22 | }); 23 | 24 | return () => observer.disconnect(); 25 | } 26 | }, []); 27 | 28 | return [containerRef as RefObject, endRef as RefObject]; 29 | } 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ai-sdk-preview-roundtrips", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@ai-sdk/openai": "^1.0.10", 13 | "@vercel/analytics": "^1.4.1", 14 | "@vercel/kv": "^3.0.0", 15 | "ai": "^4.0.21", 16 | "d3-scale": "^4.0.2", 17 | "date-fns": "^4.1.0", 18 | "framer-motion": "^11.15.0", 19 | "next": "^15.1.9", 20 | "react": "^19.0.0", 21 | "react-dom": "^19.0.0", 22 | "react-use": "^17.6.0", 23 | "sonner": "^1.7.1", 24 | "streamdown": "^1.6.7", 25 | "tailwindcss-animate": "^1.0.7", 26 | "zod": "^3.24.1" 27 | }, 28 | "devDependencies": { 29 | "@types/d3-scale": "^4.0.8", 30 | "@types/node": "^22.10.2", 31 | "@types/react": "^19.0.2", 32 | "@types/react-dom": "^19.0.2", 33 | "eslint": "^9.17.0", 34 | "eslint-config-next": "15.1.2", 35 | "postcss": "^8.4.49", 36 | "tailwindcss": "^3.4.17", 37 | "typescript": "^5.7.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /components/data.ts: -------------------------------------------------------------------------------- 1 | export interface Order { 2 | id: string; 3 | name: string; 4 | orderedAt: string; 5 | image: string; 6 | } 7 | 8 | export const ORDERS: Order[] = [ 9 | { 10 | id: "412093", 11 | name: "Apple Watch Ultra 2", 12 | orderedAt: "2024-08-26", 13 | image: "watch.png", 14 | }, 15 | { 16 | id: "539182", 17 | name: "Apple TV", 18 | orderedAt: "2024-08-25", 19 | image: "tv.png", 20 | }, 21 | { 22 | id: "281958", 23 | name: "Apple iPhone 14 Pro", 24 | orderedAt: "2024-08-24", 25 | image: "iphone.png", 26 | }, 27 | ]; 28 | 29 | export interface TrackingInformation { 30 | orderId: string; 31 | progress: "Shipped" | "Out for Delivery" | "Delivered"; 32 | description: string; 33 | } 34 | 35 | export const TRACKING_INFORMATION = [ 36 | { 37 | orderId: "412093", 38 | progress: "Shipped", 39 | description: "Last Updated Today 4:31 PM", 40 | }, 41 | { 42 | orderId: "281958", 43 | progress: "Out for Delivery", 44 | description: "ETA Today 5:45 PM", 45 | }, 46 | { 47 | orderId: "539182", 48 | progress: "Delivered", 49 | description: "Front Porch Today 3:16 PM", 50 | }, 51 | ]; 52 | 53 | export const getOrders = () => { 54 | return ORDERS; 55 | }; 56 | 57 | export const getTrackingInformation = ({ orderId }: { orderId: string }) => { 58 | return TRACKING_INFORMATION.find((info) => info.orderId === orderId); 59 | }; 60 | -------------------------------------------------------------------------------- /components/orders.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { motion } from "framer-motion"; 4 | import { InvoiceIcon } from "./icons"; 5 | import Image from "next/image"; 6 | import { format } from "date-fns"; 7 | 8 | export const Orders = ({ orders }: { orders: any[] }) => { 9 | return ( 10 |
11 | {orders.map((order, index) => ( 12 | 19 |
20 | {order.name} 27 |
28 | 29 |
30 |
31 |
{order.name}
32 |
33 | Ordered {format(new Date(order.orderedAt), "dd LLL, yyyy")} 34 |
35 |
36 | 37 |
38 |
39 |
40 | 41 |
42 |
43 | {order.id} 44 |
45 |
46 |
47 |
48 |
49 | ))} 50 |
51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multi-Steps Preview 2 | 3 | This example demonstrates how to use the [AI SDK](https://sdk.vercel.ai/docs) with [Next.js](https://nextjs.org/) and the `streamText` function to automatically handle multi-step generations. 4 | 5 | ## Deploy your own 6 | 7 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel-labs%2Fai-sdk-preview-steps-reasoning&env=OPENAI_API_KEY&envDescription=OpenAI%20API%20Key%20Needed&envLink=https%3A%2F%2Fplatform.openai.com) 8 | 9 | ## How to use 10 | 11 | Run [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example: 12 | 13 | ```bash 14 | npx create-next-app --example https://github.com/vercel-labs/ai-sdk-preview-steps-reasoning ai-sdk-preview-steps-reasoning-example 15 | ``` 16 | 17 | ```bash 18 | yarn create next-app --example https://github.com/vercel-labs/ai-sdk-preview-steps-reasoning ai-sdk-preview-steps-reasoning-example 19 | ``` 20 | 21 | ```bash 22 | pnpm create next-app --example https://github.com/vercel-labs/ai-sdk-preview-steps-reasoning ai-sdk-preview-steps-reasoning-example 23 | ``` 24 | 25 | To run the example locally you need to: 26 | 27 | 1. Sign up for accounts with the AI providers you want to use (e.g., OpenAI, Anthropic). 28 | 2. Obtain API keys for each provider. 29 | 3. Set the required environment variables as shown in the `.env.example` file, but in a new file called `.env`. 30 | 4. `npm install` to install the required dependencies. 31 | 5. `npm run dev` to launch the development server. 32 | 33 | 34 | ## Learn More 35 | 36 | To learn more about the AI SDK or Next.js take a look at the following resources: 37 | 38 | - [AI SDK docs](https://sdk.vercel.ai/docs) 39 | - [Vercel AI Playground](https://play.vercel.ai) 40 | - [Next.js Documentation](https://nextjs.org/docs) 41 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | import { fontFamily } from "tailwindcss/defaultTheme"; 3 | import animate from "tailwindcss-animate"; 4 | 5 | const config: Config = { 6 | content: [ 7 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 8 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 9 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 10 | "./node_modules/streamdown/dist/*.js", 11 | ], 12 | theme: { 13 | container: { 14 | center: true, 15 | padding: "2rem", 16 | screens: { 17 | "2xl": "1400px", 18 | }, 19 | }, 20 | extend: { 21 | colors: { 22 | border: "hsl(var(--border))", 23 | input: "hsl(var(--input))", 24 | ring: "hsl(var(--ring))", 25 | background: "hsl(var(--background))", 26 | foreground: "hsl(var(--foreground))", 27 | primary: { 28 | DEFAULT: "hsl(var(--primary))", 29 | foreground: "hsl(var(--primary-foreground))", 30 | }, 31 | secondary: { 32 | DEFAULT: "hsl(var(--secondary))", 33 | foreground: "hsl(var(--secondary-foreground))", 34 | }, 35 | destructive: { 36 | DEFAULT: "hsl(var(--destructive))", 37 | foreground: "hsl(var(--destructive-foreground))", 38 | }, 39 | muted: { 40 | DEFAULT: "hsl(var(--muted))", 41 | foreground: "hsl(var(--muted-foreground))", 42 | }, 43 | accent: { 44 | DEFAULT: "hsl(var(--accent))", 45 | foreground: "hsl(var(--accent-foreground))", 46 | }, 47 | popover: { 48 | DEFAULT: "hsl(var(--popover))", 49 | foreground: "hsl(var(--popover-foreground))", 50 | }, 51 | card: { 52 | DEFAULT: "hsl(var(--card))", 53 | foreground: "hsl(var(--card-foreground))", 54 | }, 55 | }, 56 | borderRadius: { 57 | lg: `var(--radius)`, 58 | md: `calc(var(--radius) - 2px)`, 59 | sm: "calc(var(--radius) - 4px)", 60 | }, 61 | fontFamily: { 62 | sans: ["var(--font-sans)", ...fontFamily.sans], 63 | }, 64 | keyframes: { 65 | "accordion-down": { 66 | from: { height: "0" }, 67 | to: { height: "var(--radix-accordion-content-height)" }, 68 | }, 69 | "accordion-up": { 70 | from: { height: "var(--radix-accordion-content-height)" }, 71 | to: { height: "0" }, 72 | }, 73 | }, 74 | animation: { 75 | "accordion-down": "accordion-down 0.2s ease-out", 76 | "accordion-up": "accordion-up 0.2s ease-out", 77 | }, 78 | backgroundImage: { 79 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 80 | "gradient-conic": 81 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", 82 | }, 83 | }, 84 | }, 85 | plugins: [animate], 86 | }; 87 | export default config; 88 | -------------------------------------------------------------------------------- /app/(preview)/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 0 0% 3.9%; 9 | 10 | --card: 0 0% 100%; 11 | --card-foreground: 0 0% 3.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 0 0% 3.9%; 15 | 16 | --primary: 0 0% 9%; 17 | --primary-foreground: 0 0% 98%; 18 | 19 | --secondary: 0 0% 96.1%; 20 | --secondary-foreground: 0 0% 9%; 21 | 22 | --muted: 0 0% 96.1%; 23 | --muted-foreground: 0 0% 45.1%; 24 | 25 | --accent: 0 0% 96.1%; 26 | --accent-foreground: 0 0% 9%; 27 | 28 | --destructive: 0 84.2% 60.2%; 29 | --destructive-foreground: 0 0% 98%; 30 | 31 | --border: 0 0% 89.8%; 32 | --input: 0 0% 89.8%; 33 | --ring: 0 0% 3.9%; 34 | 35 | --radius: 0.5rem; 36 | } 37 | 38 | .dark { 39 | --background: 0 0% 3.9%; 40 | --foreground: 0 0% 98%; 41 | 42 | --card: 0 0% 3.9%; 43 | --card-foreground: 0 0% 98%; 44 | 45 | --popover: 0 0% 3.9%; 46 | --popover-foreground: 0 0% 98%; 47 | 48 | --primary: 0 0% 98%; 49 | --primary-foreground: 0 0% 9%; 50 | 51 | --secondary: 0 0% 14.9%; 52 | --secondary-foreground: 0 0% 98%; 53 | 54 | --muted: 0 0% 14.9%; 55 | --muted-foreground: 0 0% 63.9%; 56 | 57 | --accent: 0 0% 14.9%; 58 | --accent-foreground: 0 0% 98%; 59 | 60 | --destructive: 0 62.8% 30.6%; 61 | --destructive-foreground: 0 0% 98%; 62 | 63 | --border: 0 0% 14.9%; 64 | --input: 0 0% 14.9%; 65 | --ring: 0 0% 83.1%; 66 | } 67 | } 68 | 69 | @layer base { 70 | * { 71 | @apply border-border; 72 | } 73 | body { 74 | @apply bg-background text-foreground; 75 | } 76 | } 77 | 78 | :root { 79 | --foreground-rgb: 0, 0, 0; 80 | --background-start-rgb: 214, 219, 220; 81 | --background-end-rgb: 255, 255, 255; 82 | } 83 | 84 | @font-face { 85 | font-family: "uncut sans"; 86 | src: url("./uncut-sans.woff2") format("woff2"); 87 | } 88 | 89 | * { 90 | font-family: "uncut sans", sans-serif; 91 | } 92 | 93 | @media (prefers-color-scheme: dark) { 94 | :root { 95 | --foreground-rgb: 255, 255, 255; 96 | --background-start-rgb: 0, 0, 0; 97 | --background-end-rgb: 0, 0, 0; 98 | } 99 | } 100 | 101 | body { 102 | color: rgb(var(--foreground-rgb)); 103 | background: linear-gradient( 104 | to bottom, 105 | transparent, 106 | rgb(var(--background-end-rgb)) 107 | ) 108 | rgb(var(--background-start-rgb)); 109 | } 110 | 111 | @layer utilities { 112 | .text-balance { 113 | text-wrap: balance; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/(preview)/api/chat/route.ts: -------------------------------------------------------------------------------- 1 | import { openai } from "@ai-sdk/openai"; 2 | import { streamText } from "ai"; 3 | import { z } from "zod"; 4 | 5 | export async function POST(request: Request) { 6 | const { messages } = await request.json(); 7 | 8 | const systemMessage = `You are an expert AI assistant that explains your reasoning step by step. 9 | You approach every question scientifically. 10 | For each step, provide a title that describes what you're doing in that step, along with the content. Decide if you need another step or if you're ready to give the final answer. 11 | 12 | Follow these guidelines exactly: 13 | - Answer every question mathematically where possible. 14 | - USE AS MANY REASONING STEPS AS POSSIBLE. AT LEAST 4. 15 | - BE AWARE OF YOUR LIMITATIONS AS AN LLM AND WHAT YOU CAN AND CANNOT DO. 16 | - IN YOUR REASONING, INCLUDE EXPLORATION OF ALTERNATIVE ANSWERS. 17 | - CONSIDER YOU MAY BE WRONG, AND IF YOU ARE WRONG IN YOUR REASONING, WHERE IT WOULD BE. 18 | - FULLY TEST ALL OTHER POSSIBILITIES. 19 | - YOU CAN BE WRONG. 20 | - WHEN YOU SAY YOU ARE RE-EXAMINING, ACTUALLY RE-EXAMINE, AND USE ANOTHER APPROACH TO DO SO. 21 | - DO NOT JUST SAY YOU ARE RE-EXAMINING. 22 | - USE AT LEAST 4 METHODS TO DERIVE THE ANSWER. USE BEST PRACTICES. 23 | - TRY AND DISPROVE YOUR ANSWER. Slow down. 24 | - Explain why you are right and why you are wrong. 25 | - Have at least one step where you explain things slowly (breaking things onto different lines). 26 | - USE FIRST PRINCIPLES AND MENTAL MODELS (like thinking through the question backwards). 27 | - If you need to count letters, separate each letter by one dash on either side and identify it by the iterator. 28 | - When checking your work, do it from the perspective of Albert Einstein, who is looking for mistakes. 29 | 30 | NOTE, YOUR FIRST ANSWER MIGHT BE WRONG. Check your work twice. 31 | 32 | Use the addReasoningStep function for each step of your reasoning. 33 | `; 34 | 35 | const result = streamText({ 36 | model: openai("gpt-4o-mini"), 37 | system: systemMessage, 38 | messages, 39 | maxSteps: 10, 40 | experimental_toolCallStreaming: true, 41 | tools: { 42 | addAReasoningStep: { 43 | description: "Add a step to the reasoning process.", 44 | parameters: z.object({ 45 | title: z.string().describe("The title of the reasoning step"), 46 | content: z 47 | .string() 48 | .describe( 49 | "The content of the reasoning step. WRITE OUT ALL OF YOUR WORK. Where relevant, prove things mathematically.", 50 | ), 51 | nextStep: z 52 | .enum(["continue", "finalAnswer"]) 53 | .describe( 54 | "Whether to continue with another step or provide the final answer", 55 | ), 56 | }), 57 | execute: async (params) => params, 58 | }, 59 | }, 60 | }); 61 | 62 | return result.toDataStreamResponse(); 63 | } 64 | -------------------------------------------------------------------------------- /components/message.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { motion } from "framer-motion"; 4 | import { BotIcon, UserIcon } from "./icons"; 5 | import { ReactNode } from "react"; 6 | import { StreamableValue, useStreamableValue } from "ai/rsc"; 7 | import { Streamdown } from "streamdown"; 8 | import { Message as TMessage, ToolInvocation } from "ai"; 9 | import { ReasoningStep } from "./reasoning-step"; 10 | 11 | export const TextStreamMessage = ({ 12 | content, 13 | }: { 14 | content: StreamableValue; 15 | }) => { 16 | const [text] = useStreamableValue(content); 17 | 18 | return ( 19 | 24 |
25 | 26 |
27 | 28 |
29 |
30 | {text} 31 |
32 |
33 |
34 | ); 35 | }; 36 | 37 | export const Message = ({ 38 | role, 39 | content, 40 | toolInvocations, 41 | reasoningMessages, 42 | }: { 43 | role: string; 44 | content: string | ReactNode; 45 | toolInvocations: Array | undefined; 46 | reasoningMessages: Array; 47 | }) => { 48 | const usingTool = toolInvocations ?? false; 49 | const loading = content === "" && toolInvocations === undefined; 50 | return ( 51 | 56 | {usingTool ? null : ( 57 |
58 | {role === "assistant" ? : } 59 |
60 | )} 61 | 62 |
63 | {reasoningMessages.length > 0 ? ( 64 |
65 | {reasoningMessages.map((message, i) => { 66 | const { content, toolInvocations } = message; 67 | if ( 68 | toolInvocations && 69 | toolInvocations.length > 0 && 70 | toolInvocations[0] 71 | ) { 72 | return ( 73 | // @ts-ignore 74 | 75 | ); 76 | } 77 | })} 78 |
79 | ) : null} 80 | {content && ( 81 |
82 | {content as string} 83 |
84 | )} 85 | 86 | {toolInvocations && ( 87 |
88 | {toolInvocations.map((toolInvocation) => { 89 | const { toolName, toolCallId, state } = toolInvocation; 90 | 91 | if (state === "result") { 92 | const { result } = toolInvocation; 93 | 94 | return ( 95 |
96 | {toolName === "addAReasoningStep" ? ( 97 | 98 | ) : null} 99 |
100 | ); 101 | } 102 | })} 103 |
104 | )} 105 |
106 |
107 | ); 108 | }; 109 | -------------------------------------------------------------------------------- /app/(preview)/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useRef } from "react"; 4 | import { Message } from "@/components/message"; 5 | import { useScrollToBottom } from "@/components/use-scroll-to-bottom"; 6 | import { motion } from "framer-motion"; 7 | import { GitIcon, MasonryIcon, VercelIcon } from "@/components/icons"; 8 | import Link from "next/link"; 9 | import { useChat } from "ai/react"; 10 | 11 | export default function Home() { 12 | const { messages, handleSubmit, input, setInput, append } = useChat(); 13 | 14 | const inputRef = useRef(null); 15 | const [messagesContainerRef, messagesEndRef] = 16 | useScrollToBottom(); 17 | 18 | const suggestedActions = [ 19 | { 20 | title: "How many 'r's", 21 | label: "are in the word strawberry?", 22 | action: "How many 'r's are in the word strawberry?", 23 | }, 24 | ]; 25 | 26 | return ( 27 |
28 |
29 |
33 | {messages.length === 0 && ( 34 | 35 |
36 |

37 | 38 | + 39 | 40 |

41 |

42 | Multi-step generations with gpt-4o-mini ( 43 | 48 | OpenAI 49 | 50 | ) and the{" "} 51 | 56 | AI SDK 57 | 58 |

59 |
60 |
61 | )} 62 | 63 | {messages.map((message, i) => { 64 | return ( 65 | 72 | ); 73 | })} 74 |
75 |
76 | 77 |
78 | {messages.length === 0 && 79 | suggestedActions.map((suggestedAction, index) => ( 80 | 1 ? "hidden sm:block" : "block"} 86 | > 87 | 101 | 102 | ))} 103 |
104 | 105 |
109 | { 115 | setInput(event.target.value); 116 | }} 117 | /> 118 |
119 |
120 |
121 | ); 122 | } 123 | -------------------------------------------------------------------------------- /components/tracker.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { TrackingInformation } from "./data"; 4 | import { BoxIcon, GPSIcon, HomeIcon, InvoiceIcon } from "./icons"; 5 | import { motion } from "framer-motion"; 6 | 7 | const getColorFromProgress = ({ 8 | progress, 9 | type, 10 | }: { 11 | progress: string; 12 | type: "foreground" | "text"; 13 | }) => { 14 | switch (progress) { 15 | case "Shipped": 16 | return type === "foreground" ? "#a1a1aa" : "#fafafa"; 17 | case "Out for Delivery": 18 | return type === "foreground" ? "#3b82f6" : "#eff6ff"; 19 | case "Delivered": 20 | return type === "foreground" ? "#10b981" : "#f0fdf4"; 21 | default: 22 | return type === "foreground" ? "#f4f4f5" : "#71717a"; 23 | } 24 | }; 25 | 26 | export const Tracker = ({ 27 | trackingInformation, 28 | }: { 29 | trackingInformation: TrackingInformation; 30 | }) => { 31 | const { progress } = trackingInformation; 32 | 33 | return ( 34 |
35 | 41 |
42 |
Tracking Order
43 | 44 |
{trackingInformation.orderId}
45 |
46 |
47 | 48 | 54 | 69 | 70 | 71 |
72 | 91 |
92 | 93 | 114 | 115 | 116 | 117 | 118 | 119 |
120 | 137 |
138 | 160 | 161 | 162 |
163 | 164 | 170 |
{progress}
171 |
172 | {trackingInformation.description} 173 |
174 |
175 |
176 | ); 177 | }; 178 | -------------------------------------------------------------------------------- /components/icons.tsx: -------------------------------------------------------------------------------- 1 | export const BotIcon = () => { 2 | return ( 3 | 10 | 16 | 17 | ); 18 | }; 19 | 20 | export const UserIcon = () => { 21 | return ( 22 | 30 | 36 | 37 | ); 38 | }; 39 | 40 | export const AttachmentIcon = () => { 41 | return ( 42 | 49 | 55 | 56 | ); 57 | }; 58 | 59 | export const VercelIcon = ({ size = 17 }) => { 60 | return ( 61 | 68 | 74 | 75 | ); 76 | }; 77 | 78 | export const MasonryIcon = () => { 79 | return ( 80 | 87 | 93 | 94 | ); 95 | }; 96 | 97 | export const GitIcon = () => { 98 | return ( 99 | 106 | 107 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | ); 121 | }; 122 | 123 | export const BoxIcon = ({ size = 16 }: { size: number }) => { 124 | return ( 125 | 132 | 138 | 139 | ); 140 | }; 141 | 142 | export const HomeIcon = ({ size = 16 }: { size: number }) => { 143 | return ( 144 | 151 | 157 | 158 | ); 159 | }; 160 | 161 | export const GPSIcon = ({ size = 16 }: { size: number }) => { 162 | return ( 163 | 170 | 178 | 179 | ); 180 | }; 181 | 182 | export const InvoiceIcon = ({ size = 16 }: { size: number }) => { 183 | return ( 184 | 191 | 197 | 198 | ); 199 | }; 200 | --------------------------------------------------------------------------------