├── .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 |
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 | [](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 | {
90 | append({
91 | role: "user",
92 | content: suggestedAction.action,
93 | });
94 | }}
95 | className="w-full text-left border border-zinc-200 dark:border-zinc-800 text-zinc-800 dark:text-zinc-300 rounded-lg p-2 text-sm hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors flex flex-col"
96 | >
97 | {suggestedAction.title}
98 |
99 | {suggestedAction.label}
100 |
101 |
102 |
103 | ))}
104 |
105 |
106 |
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 |
--------------------------------------------------------------------------------