最新资讯
reddit.com OpenAI 称 Astra 仅满足 10% 安全任务标准 dev.to FreeToken:在自有 GPU 上运行前沿 MoE 模型及其内存需求 dev.to 当 AI Agent 说“完成”时,究竟完成了什么? dev.to 部署 Underdog Bark 3.0 Pro:逐字转写、自定义后端与分块设置 dev.to 为艰难对话构建 AI:五个 Agent、一条安全正则与一位盲评监督者 techcrunch.com Palo Alto Networks 洽谈以 5 亿美元收购 AI Agent 安全初创公司 Clutch theinformation.com 黄仁勋被要求就涉华间谍指控向国会作简报 www.infoq.com Edera 推出 Object Capability Bounded Authority,试图将凭证与 AI Agent 工作负载隔离 wechat 这波杀疯了!旗舰级模型不限量、1个月视频额度免费领 bloomberg.com AI 抛售蔓延至信用市场,软银领跌亚洲美元债 dev.to 如何在专用 GPU 服务器上用 OpenAI 兼容 API 部署 LLM dev.to 审计 153 份 Claude Code 与 Codex 会话记录:User-Agent 与 URL 中的邮箱泄露
多元动态
登录 注册
开源工具 www.freecodecamp.org · @Vaibhav Gupta

用 Vercel AI SDK 与 shadcn/ui 构建 AI 聊天应用界面

原文 我的收藏

freeCodeCamp 教程介绍如何用 Vercel AI SDK 处理流式输出与模型调用、用 shadcn/ui 构建聊天界面,覆盖 Next.js 脚手架、流式 API 路由、useChat 客户端、工具调用、markdown 渲染与滚动等细节,并说明可在无 API Key 时先做 UI 原型,以及用 MCP server 加速 shadcn 开发。

AI推荐理由 freeCodeCamp 发布的实操教程,给出用 Vercel AI SDK 与 shadcn/ui 构建流式 AI 聊天界面的完整步骤,涵盖流式状态、工具调用、markdown 渲染、无 API Key 的 UI 原型等具体工程问题,并提及用 MCP server 加速开发,对开发者有直接可复用价值;属教程而非首发研究,故未给更高分。

74

正文

Every other AI product you open today has the same screen: a message list, a text box at the bottom, and words that stream in one token at a time. It looks simple, but it's not simple to build well. You have to manage streaming state, partial tokens, tool calls, retries, markdown rendering, scroll position, and a dozen small UX details...all while keeping the interface accessible and fast. Do it with the wrong tools, and you'll spend more time fighting state bugs than building your actual product. In this tutorial, you'll build a real AI chat interface using two tools that were basically made for each other: the Vercel AI SDK for the streaming and model logic, and shadcn/ui for the interface itself. By the end, you'll have a working chat screen that streams responses, renders markdown, and looks like something you would actually ship. You'll also see how to speed up the UI side of this even further using an MCP server, and where to grab a production-ready chat template if you'd rather skip the setup entirely. Table of Contents Prerequisites What You'll Build Step 1: Scaffold the Next.js App Step 2: Install the Vercel AI SDK Step 3: Why shadcn/ui Pairs So Well With AI Chat UIs Step 4: Set Up shadcn/ui in Your Project Step 5: Build the Streaming API Route Step 6: Wire Up the Client With useChat Step 7: Let the Model Call Tools Step 8: Prototype the UI Without a Backend Turning Your Chat Into a Full Product Speed Up shadcn Development With an MCP Server A Few Things to Handle Before You Ship Skip the Boilerplate With a Ready-Made Template Wrapping Up Resources Prerequisites You will need: Node.js 18 or later Basic familiarity with React and Next.js, specifically the App Router An API key from an LLM provider such as OpenAI, Anthropic, or Google. You can follow along without one, more on that later. What You'll Build You're going to build a Next.js chat app with: A streaming API route that talks to an LLM provider A client-side chat interface built with useChat Message bubbles, an auto-growing input, and a scrollable conversation, all styled with shadcn/ui components A simple tool call so the model can do more than just talk A fallback state that works even before you add an API key, so you can build the UI first and wire up the model later Let's start from an empty folder and work up to something you would be comfortable showing a teammate. Step 1: Scaffold the Next.js App Create a new Next.js project with TypeScript and Tailwind enabled: npx create-next-app@latest ai-chat-app --typescript --tailwind --eslint --app cd ai-chat-app Keep the defaults for everything else the CLI asks you. You'll be working almost entirely inside the app directory. Step 2: Install the Vercel AI SDK The Vercel AI SDK is what does the heavy lifting here. It gives you one API for calling different model providers, streaming text and structured data, and handling tool calls, so you're not rewriting your chat logic every time you switch models. Install the core package, the React bindings, and an OpenAI-compatible provider: npm install ai @ai-sdk/react @ai-sdk/openai-compatible @ai-sdk/openai-compatible is worth calling out specifically: instead of installing a separate package per provider, it lets you point at any provider that speaks the OpenAI-style API (OpenAI itself, Gemini, Groq, and plenty of self-hosted setups) just by swapping a base URL. Combined with an AI_PROVIDER environment variable, you get provider switching without touching your route handler at all, which is exactly the pattern you'll build in the next step. Step 3: Why shadcn/ui Pairs So Well With AI Chat UIs Before you write any UI code, it's worth understanding why so many AI chat products lean on shadcn/ui instead of a traditional component library. Most component libraries hand you a compiled package and hide the internals behind props. That works fine for a settings page, but it works badly for a chat interface. For chat, you need to control exactly how a message bubble animates while it streams, how a "thinking" indicator behaves, or how a tool call renders differently from plain text. shadcn/ui takes a different approach: instead of installing a package, you copy the component's actual source code into your project. You own it completely, with no fighting an abstraction to bend it to your use case and no waiting on a maintainer to expose the one prop you need. That ownership model is exactly what a chat interface needs, since almost no two AI products render messages, reasoning, or tool output the same way. It's also why an entire ecosystem has grown up around it. If you want a wider set of production-ready blocks and templates beyond the default registry, including dashboards, marketing sections, and full chat UIs, the shadcn/ui community hub at Shadcn Space is worth bookmarking. You'll come back to it later in this tutorial. Step 4: Set Up shadcn/ui in Your Project Since you already have a Next.js project from Step 1, apply the Shadcn Space preset to it directly: npx shadcn@latest apply --preset b0 This sets up components.json, your Tailwind config, and lib/utils.ts inside your existing project. From there, pull in the components you need for a chat screen: npx shadcn@latest add button input textarea scroll-area avatar separator Each add call copies real, readable component source into components/ui/, ready to import and edit like any other file in your project, with no compiled package to fight with later. If you'd rather not assemble the message list and composer from individual primitives, Shadcn Space also has ready-made AI chat blocks that you can add directly: npx shadcn@latest add @shadcn-space/ai-chat-01 npx shadcn@latest add @shadcn-space/ai-chat-03 ai-chat-01 provides the conversation interface with a welcome screen, suggested prompts, a scrollable message thread, and a composer with attachments and a model picker. Live Preview of AI Chat 01: ai-chat-03 provides the surrounding application shell with a collapsible sidebar, pinned and recent chats, search, and a topbar. Live Preview of AI Chat 03: You can install either block separately, or install both if you want the complete chat layout without building the surrounding interface from scratch. Both blocks are Premium. If you want a free sidebar for your chat interface, the standard shadcn sidebar-07 block is a lightweight, no-cost alternative: npx shadcn@latest add sidebar-07 Step 5: Build the Streaming API Route Create app/api/chat/route.ts. This server-side piece talks to the model and streams the response back to the browser. // app/api/chat/route.ts import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; import { convertToModelMessages, streamText, type UIMessage } from "ai"; const PROVIDERS: Record = { openai: { baseURL: "https://api.openai.com/v1", model: "gpt-4o-mini", }, gemini: { baseURL: "https://generativelanguage.googleapis.com/v1beta/openai", model: "gemini-2.5-flash", }, groq: { baseURL: "https://api.groq.com/openai/v1", model: "llama-3.3-70b-versatile", }, }; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const providerName = process.env.AI_PROVIDER?.trim().toLowerCase() ?? "openai"; const { baseURL, model } = PROVIDERS[providerName] ?? PROVIDERS.openai; const provider = createOpenAICompatible({ name: providerName, baseURL: process.env.AI_BASE_URL ?? baseURL, apiKey: process.env.AI_API_KEY, }); const result = streamText({ model: provider(process.env.AI_MODEL ?? model), system: "You are a concise, helpful assistant.", messages: convertToModelMessages(messages), }); return result.toUIMessageStreamResponse(); } A few things worth calling out: createOpenAICompatible gives you one provider instance that works against any OpenAI-style API. Swap AI_PROVIDER between openai, gemini, or groq and your route handler doesn't change at all. convertToModelMessages bridges the UI message format, which the client sends, with the format the model provider expects. streamText starts the model generating and returns a stream you can pipe straight to the client. toUIMessageStreamResponse() wraps that stream in a response your useChat hook on the client knows how to consume, token by token. Set your provider and key in .env: # .env AI_PROVIDER=openai AI_API_KEY= If you don't have a key yet, you can still build the UI. Just have this route return a canned streamed response until you're ready to wire up a real provider. The client code below doesn't care where the stream comes from. Step 6: Wire Up the Client With useChat Now, let’s build the chat interface. Create a components/chat.tsx file: // components/chat.tsx "use client"; import { useState } from "react"; import { useChat } from "@ai-sdk/react"; import { DefaultChatTransport } from "ai"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { cn } from "@/lib/utils"; export function Chat() { const [input, setInput] = useState(""); const { messages, sendMessage, status } = useChat({ transport: new DefaultChatTransport({ api: "/api/chat" }), }); const isLoading = status === "submitted" || status === "streaming"; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!input.trim()) return; sendMessage({ text: input }); setInput(""); }; return ( {messages.map((message) => ( {message.role !== "user" && ( AI )} {message.parts.map((part, i) => part.type === "text" ? ( {part.text} ) : null )} ))} setInput(e.target.value)} placeholder="Message the assistant..." className="min-h-11 flex-1 resize-none" disabled={isLoading} /> Send ); } Drop into app/page.tsx and run npm run dev. You now have a working, streaming chat interface. Every message the model sends back appears word by word instead of all at once, and status gives you a clean way to disable the input while a response is in flight. Notice that useChat is doing a lot of quiet work here: it owns the message list, handles the streaming reassembly as chunks arrive, and manages the submitted, streaming, and ready lifecycle so you don't have to track any of that by yourself. Live Preview: Step 7: Let the Model Call Tools A chat box that can only talk is limiting. The AI SDK lets your model call real functions in your code, with a JSON schema describing the input it's allowed to send. Add this to your route handler, right next to the provider setup from Step 5: // app/api/chat/route.ts import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; import { convertToModelMessages, jsonSchema, streamText, tool, type UIMessage, } from "ai"; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const provider = createOpenAICompatible({ name: "openai", baseURL: "https://api.openai.com/v1", apiKey: process.env.AI_API_KEY, }); const result = streamText({ model: provider("gpt-4o-mini"), messages: convertToModelMessages(messages), tools: { getWeather: tool({ description: "Get the current weather for a city", inputSchema: jsonSchema({ type: "object", properties: { city: { type: "string", description: "The city to get the weather for", }, }, required: ["city"], }), execute: async ({ city }) => { // Call a real weather API here in production return { city, temperature: 22, condition: "clear", }; }, }), }, }); return result.toUIMessageStreamResponse(); } The model decides when to call getWeather, the SDK routes that call to your execute function, and the result streams back into the conversation as a message part. No extra plumbing is needed on the client beyond checking part.type for tool parts if you want to render them differently from plain text. Step 8: Prototype the UI Without a Backend Here's a problem you'll hit constantly: you want to polish the chat UI, including spacing, animations, and how a reasoning block collapses, before your backend or API keys are even ready. Rebuilding that UI against a live model every time you tweak a pixel is slow and burns tokens. This is exactly what the shadcn AI SDK helper package solves. It lets you script out a fake conversation and stream it through the same useChat hook you're already using, with no server, model, or API key involved: npm install @shadcn/helpers import { createChat } from "@shadcn/helpers"; import { useChat } from "@ai-sdk/react"; const chat = createChat() .user("What changed in the last release?") .assistant("The release added keyboard shortcuts and faster search."); function ChatPreview() { const { messages } = useChat({ messages: chat.get(0), transport: chat.transport(), }); // render `messages` exactly like you would with a real backend } It supports every part type the AI SDK understands, including reasoning, tool calls, files, and sources, and streams deterministically every time. This also makes it genuinely useful for writing reproducible demos or UI tests. You can build and refine your entire interface this way, then swap in the real /api/chat route the moment your backend is ready. Turning Your Chat Into a Full Product A chat window rarely ships alone. Once yours works, you'll usually need a sidebar for past conversations, a settings panel for model selection, and maybe an admin view to see usage across your users. That's a different problem than streaming text. It's application shell and data-table territory. Rather than hand-rolling that shell, most teams reach for a pre-built admin layout. A ready-made shadcn dashboard, like the one at Shadcn Space, ships with the layouts, data tables, charts, and navigation patterns an internal tool needs, so you're not rebuilding a sidebar and settings page from scratch just to give your chat feature a home. And if you'd rather skip building the chat screen itself too, that's a real option. Some teams start straight from a ready-made shadcn AI chat app template that already has a conversation sidebar, markdown and code rendering, and tool-call visualization built in. We'll come back to that at the end. Live Preview of full Chat App: Speed Up shadcn Development With an MCP Server However you build your chat UI, there's a faster way to pull in components than copy-pasting from docs: an MCP (Model Context Protocol) server that gives your AI coding assistant live access to a component registry. The Shadcn MCP server connects tools like Claude Code, Cursor, and Windsurf directly to the Shadcn Space component catalog. Instead of you searching docs and pasting install commands, you just ask your assistant for what you need, such as "add a message bubble component with an avatar and timestamp", and it pulls real, current component definitions instead of guessing from outdated training data. Setting it up is a one-line command for Claude Code: claude mcp add shadcnspace-mcp -- npx -y shadcnspace-mcp@latest Other editors just need the same command dropped into their MCP config file, for example .cursor/mcp.json in Cursor. The full getting started guide for the MCP server walks through configuration for each supported editor, and the Shadcn MCP page covers exactly what it can search, install, and generate once it's connected. If you'd rather watch the setup than read it, there's a short walkthrough that covers the same steps end to end. A Few Things to Handle Before You Ship The tutorial version above is intentionally minimal. Before this goes anywhere near production, add: Rate limiting on your /api/chat route. A chat endpoint with no limits is an easy way to run up a very large model bill. Abort handling so users can stop a response mid-stream, which useChat supports out of the box via its stop() function. Error boundaries around the chat component, since a dropped stream or provider outage shouldn't crash the whole page. Auth, if responses or conversation history should be scoped to a specific user. None of these are exotic. They're the same production basics you'd apply to any API route. Chat endpoints just make it easier to forget them because the happy path looks so smooth in development. Skip the Boilerplate With a Ready-Made Template Everything above gets you a real, working chat interface, but it's the tutorial version. A production chat product usually also needs a conversation sidebar, project grouping, markdown and syntax-highlighted code blocks, a reasoning panel, voice input, and a settings screen for switching models. Building all of that from scratch can be a multi-week job on its own. If you'd rather not build that shell yourself, it's worth checking out the shadcn AI chat app template at Shadcn Space. It's built on the same foundation covered in this article (Next.js, the Vercel AI SDK, and shadcn ui) but ships with the sidebar, reasoning and tool-call UI, file attachments, and multi-provider model switching already wired up. You set AI_PROVIDER and AI_API_KEY and you're talking to a real model through a finished interface. You can check out the live demo to see exactly how the sidebar, streaming, and tool calls behave before deciding whether to build it yourself or start from the template. Wrapping Up You now have a chat interface that streams real responses, calls tools, and is built entirely on components you own and can freely edit. And for an AI product, all this matters more than it sounds like it should. The Vercel AI SDK handles the hard streaming and model logic while shadcn/ui gives you full control over how that logic actually looks on screen. From here, the natural next steps are hooking up a real provider, adding the production basics above, and deciding whether to keep extending your own UI or lean on a finished template to get the surrounding product built faster. Either way, you now understand what's actually happening under the hood, which makes both paths a lot easier. Resources Vercel AI SDK Shadcn AI SDK shadcn dashboard Shadcn ui shadcn AI chat app I wrote this article with the help of Ashutosh Rada (Sr. Frontend Developer). Connect on LinkedIn.

来源报道 · 2 篇 · 以下链接均前往原始媒体

← 返回精选动态 原文

更多内容