├── .env.example ├── .eslintrc.json ├── app ├── favicon.ico └── (preview) │ ├── uncut-sans.woff2 │ ├── twitter-image.png │ ├── opengraph-image.png │ ├── layout.tsx │ ├── api │ └── chat │ │ └── route.ts │ ├── globals.css │ └── page.tsx ├── public ├── tv.png ├── iphone.png └── watch.png ├── next.config.mjs ├── postcss.config.mjs ├── .gitignore ├── LICENSE.txt ├── tsconfig.json ├── components ├── 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 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vercel-labs/ai-sdk-preview-roundtrips/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /public/tv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vercel-labs/ai-sdk-preview-roundtrips/HEAD/public/tv.png -------------------------------------------------------------------------------- /public/iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vercel-labs/ai-sdk-preview-roundtrips/HEAD/public/iphone.png -------------------------------------------------------------------------------- /public/watch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vercel-labs/ai-sdk-preview-roundtrips/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-roundtrips/HEAD/app/(preview)/uncut-sans.woff2 -------------------------------------------------------------------------------- /app/(preview)/twitter-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vercel-labs/ai-sdk-preview-roundtrips/HEAD/app/(preview)/twitter-image.png -------------------------------------------------------------------------------- /app/(preview)/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vercel-labs/ai-sdk-preview-roundtrips/HEAD/app/(preview)/opengraph-image.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2024 Vercel, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /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: "Automatic Multiple Tool Steps Preview", 8 | description: "Automatically handle multiple tool steps using the AI SDK", 9 | }; 10 | 11 | export default function RootLayout({ 12 | children, 13 | }: Readonly<{ 14 | children: React.ReactNode; 15 | }>) { 16 | return ( 17 | 18 | 19 | 20 | {children} 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /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/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, endRef]; 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.3.1", 14 | "@vercel/kv": "^2.0.0", 15 | "ai": "^4.0.21", 16 | "d3-scale": "^4.0.2", 17 | "date-fns": "^3.6.0", 18 | "framer-motion": "^11.3.19", 19 | "next": "14.2.5", 20 | "react": "^18", 21 | "react-dom": "^18", 22 | "react-use": "^17.5.1", 23 | "sonner": "^1.5.0", 24 | "streamdown": "^1.6.7", 25 | "tailwindcss-animate": "^1.0.7", 26 | "zod": "^3.23.8" 27 | }, 28 | "devDependencies": { 29 | "@types/d3-scale": "^4.0.8", 30 | "@types/node": "^20", 31 | "@types/react": "^18", 32 | "@types/react-dom": "^18", 33 | "eslint": "^8", 34 | "eslint-config-next": "14.2.5", 35 | "postcss": "^8", 36 | "tailwindcss": "^3.4.1", 37 | "typescript": "^5" 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 | -------------------------------------------------------------------------------- /app/(preview)/api/chat/route.ts: -------------------------------------------------------------------------------- 1 | import { getOrders, getTrackingInformation, ORDERS } from "@/components/data"; 2 | import { openai } from "@ai-sdk/openai"; 3 | import { streamText } from "ai"; 4 | import { z } from "zod"; 5 | 6 | export async function POST(request: Request) { 7 | const { messages } = await request.json(); 8 | 9 | const stream = streamText({ 10 | model: openai("gpt-4o"), 11 | system: `\ 12 | - you are a friendly package tracking assistant 13 | - your responses are concise 14 | - you do not ever use lists, tables, or bullet points; instead, you provide a single response 15 | `, 16 | messages, 17 | maxSteps: 5, 18 | tools: { 19 | listOrders: { 20 | description: "list all orders", 21 | parameters: z.object({}), 22 | execute: async function ({}) { 23 | const orders = getOrders(); 24 | return orders; 25 | }, 26 | }, 27 | viewTrackingInformation: { 28 | description: "view tracking information for a specific order", 29 | parameters: z.object({ 30 | orderId: z.string(), 31 | }), 32 | execute: async function ({ orderId }) { 33 | const trackingInformation = getTrackingInformation({ orderId }); 34 | await new Promise((resolve) => setTimeout(resolve, 500)); 35 | return trackingInformation; 36 | }, 37 | }, 38 | }, 39 | }); 40 | 41 | return stream.toDataStreamResponse(); 42 | } 43 | -------------------------------------------------------------------------------- /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 | # Automatic Multiple Tool Steps Preview 2 | 3 | This example demonstrates how to use the [Vercel AI SDK](https://sdk.vercel.ai/docs) with [Next.js](https://nextjs.org/) and the `streamText` function to automatically handle multiple tool steps. 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-roundtrips&env=OPENAI_API_KEY&envDescription=API%20keys%20needed%20for%20application&envLink=platform.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-roundtrips ai-sdk-preview-roundtrips-example 15 | ``` 16 | 17 | ```bash 18 | yarn create next-app --example https://github.com/vercel-labs/ai-sdk-preview-roundtrips ai-sdk-preview-roundtrips-example 19 | ``` 20 | 21 | ```bash 22 | pnpm create next-app --example https://github.com/vercel-labs/ai-sdk-preview-roundtrips ai-sdk-preview-roundtrips-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 Vercel AI SDK or Next.js take a look at the following resources: 37 | 38 | - [Vercel 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 | -------------------------------------------------------------------------------- /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 { ToolInvocation } from "ai"; 9 | import { Orders } from "./orders"; 10 | import { Tracker } from "./tracker"; 11 | 12 | export const TextStreamMessage = ({ 13 | content, 14 | }: { 15 | content: StreamableValue; 16 | }) => { 17 | const [text] = useStreamableValue(content); 18 | 19 | return ( 20 | 25 |
26 | 27 |
28 | 29 |
30 |
31 | {text} 32 |
33 |
34 |
35 | ); 36 | }; 37 | 38 | export const Message = ({ 39 | role, 40 | content, 41 | toolInvocations, 42 | }: { 43 | role: string; 44 | content: string | ReactNode; 45 | toolInvocations: Array | undefined; 46 | }) => { 47 | return ( 48 | 53 |
54 | {role === "assistant" ? : } 55 |
56 | 57 |
58 | {content && ( 59 |
60 | {content as string} 61 |
62 | )} 63 | 64 | {toolInvocations && ( 65 |
66 | {toolInvocations.map((toolInvocation) => { 67 | const { toolName, toolCallId, state } = toolInvocation; 68 | 69 | if (state === "result") { 70 | const { result } = toolInvocation; 71 | 72 | return ( 73 |
74 | {toolName === "listOrders" ? ( 75 | 76 | ) : toolName === "viewTrackingInformation" ? ( 77 |
78 | 79 |
80 | ) : null} 81 |
82 | ); 83 | } 84 | })} 85 |
86 | )} 87 |
88 |
89 | ); 90 | }; 91 | -------------------------------------------------------------------------------- /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 { 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: "Where is", 21 | label: "my watch?", 22 | action: "where is my watch?", 23 | }, 24 | { 25 | title: "What orders", 26 | label: "have shipped?", 27 | action: "what orders have shipped?", 28 | }, 29 | ]; 30 | 31 | return ( 32 |
33 |
34 |
38 | {messages.length === 0 && ( 39 | 40 |
41 |

42 | 43 | + 44 | 45 |

46 |

47 | The maxSteps parameter of streamText function allows you to 48 | automatically handle multiple tool calls in sequence using the 49 | AI SDK in your application. 50 |

51 |

52 | {" "} 53 | Learn more about{" "} 54 | 59 | Multiple Tool Steps{" "} 60 | 61 | from the Vercel AI SDK. 62 |

63 |
64 |
65 | )} 66 | 67 | {messages.map((message) => ( 68 | 74 | ))} 75 |
76 |
77 | 78 |
79 | {messages.length === 0 && 80 | suggestedActions.map((suggestedAction, index) => ( 81 | 1 ? "hidden sm:block" : "block"} 87 | > 88 | 102 | 103 | ))} 104 |
105 | 106 |
110 | { 116 | setInput(event.target.value); 117 | }} 118 | /> 119 |
120 |
121 |
122 | ); 123 | } 124 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------