Loading...
Loading...
Compare original and translation side by side
undefinedundefined
**Why this matters:**
- AI Elements is built ON TOP of shadcn/ui (won't work without it)
- Requires Next.js App Router (Pages Router not supported)
- AI SDK v5 has breaking changes from v4
**Missing prerequisites?** Use the `tailwind-v4-shadcn` skill first, then install AI SDK:
```bash
pnpm add ai@latest
**为什么这很重要**:
- AI Elements 构建于shadcn/ui之上(无shadcn/ui则无法运行)
- 要求使用Next.js App Router(不支持Pages Router)
- AI SDK v5与v4相比有破坏性变更
**缺少前置要求?** 先使用`tailwind-v4-shadcn` Skill,再安装AI SDK:
```bash
pnpm add ai@latestundefinedundefined
**CRITICAL:**
- Components are copied into `components/ui/ai/` (NOT installed as npm package)
- Full source code ownership (modify as needed)
- Registry URL must be correct in `components.json`
**关键注意事项**:
- 组件会被复制到`components/ui/ai/`目录下(不会作为npm包安装)
- 拥有完整源代码所有权(可按需修改)
- `components.json`中的注册中心URL必须正确// app/chat/page.tsx
'use client';
import { useChat } from 'ai/react';
import { Conversation } from '@/components/ui/ai/conversation';
import { Message } from '@/components/ui/ai/message';
import { MessageContent } from '@/components/ui/ai/message-content';
import { Response } from '@/components/ui/ai/response';
import { PromptInput } from '@/components/ui/ai/prompt-input';
export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
api: '/api/chat'
});
return (
<div className="flex h-screen flex-col">
<Conversation className="flex-1">
{messages.map((msg) => (
<Message key={msg.id} role={msg.role}>
<MessageContent>
<Response markdown={msg.content} />
</MessageContent>
</Message>
))}
</Conversation>
<PromptInput
value={input}
onChange={handleInputChange}
onSubmit={handleSubmit}
disabled={isLoading}
/>
</div>
);
}// app/chat/page.tsx
'use client';
import { useChat } from 'ai/react';
import { Conversation } from '@/components/ui/ai/conversation';
import { Message } from '@/components/ui/ai/message';
import { MessageContent } from '@/components/ui/ai/message-content';
import { Response } from '@/components/ui/ai/response';
import { PromptInput } from '@/components/ui/ai/prompt-input';
export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
api: '/api/chat'
});
return (
<div className="flex h-screen flex-col">
<Conversation className="flex-1">
{messages.map((msg) => (
<Message key={msg.id} role={msg.role}>
<MessageContent>
<Response markdown={msg.content} />
</MessageContent>
</Message>
))}
</Conversation>
<PromptInput
value={input}
onChange={handleInputChange}
onSubmit={handleSubmit}
disabled={isLoading}
/>
</div>
);
}pnpm dlx ai-elements@latest initcomponents/ui/ai/components.jsoncomponents.jsonpnpm dlx shadcn@latest initpnpm dlx ai-elements@latest initcomponents/ui/ai/components.jsoncomponents.jsonpnpm dlx shadcn@latest initundefinedundefined
**Component Purpose:**
- `message`: Container for single message (user/AI)
- `message-content`: Wrapper for message parts
- `conversation`: Auto-scrolling chat container
- `response`: Markdown renderer (streaming-optimized)
- `prompt-input`: Auto-resizing textarea with toolbar
- `actions`: Copy/regenerate/edit buttons
- `suggestion`: Quick prompt pills
**组件用途**:
- `message`:单条消息的容器(用户/AI消息)
- `message-content`:消息内容的包装器
- `conversation`:自动滚动的聊天容器
- `response`:流式优化的Markdown渲染器
- `prompt-input`:带工具栏的自动调整高度文本框
- `actions`:复制/重新生成/编辑按钮
- `suggestion`:快速提示胶囊undefinedundefined
**When to add each:**
- `tool`: Building AI assistants with function calling
- `reasoning`: Showing AI's thinking process (like Claude or o1)
- `sources`: Adding citations and references (like Perplexity)
- `code-block`: Chat includes code snippets
- `branch`: Multi-turn conversations with variations
- `task`: AI generates task lists with file references
- `image`: AI generates images (DALL-E, Stable Diffusion)
- `web-preview`: AI generates HTML/websites (Claude artifacts)
- `loader`: Show loading states during streaming
**各组件适用场景**:
- `tool`:开发带函数调用的AI助手
- `reasoning`:展示AI的思考过程(如Claude或o1)
- `sources`:添加引用和来源(如Perplexity)
- `code-block`:聊天包含代码片段时使用
- `branch`:带多版本分支的多轮对话
- `task`:AI生成带文件引用的任务列表
- `image`:AI生成图片(DALL-E、Stable Diffusion)
- `web-preview`:AI生成HTML/网页(Claude artifacts)
- `loader`:流式响应期间展示加载状态/app/api/chat/route.tsimport { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4o'),
messages,
});
return result.toDataStreamResponse();
}streamText()OpenAIStream()toDataStreamResponse()useChat()/app/api/chat/route.tsimport { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4o'),
messages,
});
return result.toDataStreamResponse();
}streamText()OpenAIStream()toDataStreamResponse()useChat()undefinedundefined
**Verification Checklist:**
- [ ] All components in `components/ui/ai/`
- [ ] `components.json` has AI Elements registry
- [ ] No TypeScript errors
- [ ] Chat interface renders
- [ ] Streaming works (type message → get response)
- [ ] Auto-scroll works during streaming
---
**验证清单**:
- [ ] 所有组件存在于`components/ui/ai/`目录
- [ ] `components.json`已添加AI Elements注册中心
- [ ] 无TypeScript错误
- [ ] 聊天界面正常渲染
- [ ] 流式响应正常(发送消息后实时接收回复)
- [ ] 流式响应期间自动滚动正常
---'use client''use client''use client''use client'SpeechRecognition is not defined// Conditionally show voice button
const isSpeechSupported = typeof window !== 'undefined' &&
('webkitSpeechRecognition' in window || 'SpeechRecognition' in window);
<PromptInput
enableSpeech={isSpeechSupported}
// Fallback: Implement server-side STT (Whisper, Google Speech)
/>SpeechRecognition is not defined// 条件展示语音按钮
const isSpeechSupported = typeof window !== 'undefined' &&
('webkitSpeechRecognition' in window || 'SpeechRecognition' in window);
<PromptInput
enableSpeech={isSpeechSupported}
// 备选方案:实现服务端语音转文字(Whisper、Google Speech)
/><PromptInput
className="min-h-[100px] sm:min-h-[60px]" // Add responsive classes
// ... other props
/><PromptInput
className="min-h-[100px] sm:min-h-[60px]" // 添加响应式类名
// ... 其他属性
/>// Merge reasoning chunks client-side
const processedMessages = messages.map(msg => {
if (msg.annotations?.reasoning) {
// Combine all reasoning into single string
const merged = Array.isArray(msg.annotations.reasoning)
? msg.annotations.reasoning.join('\n\n')
: msg.annotations.reasoning;
return { ...msg, annotations: { ...msg.annotations, reasoning: merged } };
}
return msg;
});// 在客户端合并推理片段
const processedMessages = messages.map(msg => {
if (msg.annotations?.reasoning) {
// 将所有推理内容合并为单个字符串
const merged = Array.isArray(msg.annotations.reasoning)
? msg.annotations.reasoning.join('\n\n')
: msg.annotations.reasoning;
return { ...msg, annotations: { ...msg.annotations, reasoning: merged } };
}
return msg;
});Cannot find module '@/components/ui/ai/message'undefinedCannot find module '@/components/ui/ai/message'undefinedundefinedundefined// ✅ v5 (correct)
const { messages } = useChat(); // Direct messages array
msg.toolInvocations // Tool calls here
// ❌ v4 (incorrect)
const { data } = useChat(); // Wrapped in data object
msg.tool_calls // Different property name// ✅ v5(正确写法)
const { messages } = useChat(); // 直接获取消息数组
msg.toolInvocations // 工具调用信息在此处
// ❌ v4(错误写法)
const { data } = useChat(); // 消息包裹在data对象中
msg.tool_calls // 属性名称不同// Memoize callbacks to prevent re-renders
import { useCallback } from 'react';
const { messages } = useChat({
api: '/api/chat',
onFinish: useCallback((message) => {
console.log('Finished', message);
}, []) // Stable dependency array
});// 对回调函数进行记忆化以避免重渲染
import { useCallback } from 'react';
const { messages } = useChat({
api: '/api/chat',
onFinish: useCallback((message) => {
console.log('Finished', message);
}, []) // 稳定的依赖数组
});// Pass raw markdown content, not rendered HTML
<Actions>
<Actions.Copy content={msg.content} format="markdown" />
</Actions>// 传入原始Markdown内容,而非渲染后的HTML
<Actions>
<Actions.Copy content={msg.content} format="markdown" />
</Actions>undefinedundefined
---
---{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/index.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {
"shadcn": "https://ui.shadcn.com/registry",
"ai-elements": "https://www.shadcn.io/ai/registry"
}
}"rsc": false"tsx": true"tailwind.config": ""registries{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/index.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {
"shadcn": "https://ui.shadcn.com/registry",
"ai-elements": "https://www.shadcn.io/ai/registry"
}
}"rsc": false"tsx": true"tailwind.config": ""registries'use client';
import { useChat } from 'ai/react';
import { Conversation, Message, MessageContent, Response, Actions } from '@/components/ui/ai';
import { PromptInput } from '@/components/ui/ai/prompt-input';
export default function ChatWithActions() {
const { messages, input, handleInputChange, handleSubmit, reload, stop } = useChat();
return (
<div className="flex h-screen flex-col">
<Conversation className="flex-1">
{messages.map((msg) => (
<Message key={msg.id} role={msg.role}>
<MessageContent>
<Response markdown={msg.content} />
{msg.role === 'assistant' && (
<Actions>
<Actions.Copy content={msg.content} />
<Actions.Regenerate onClick={() => reload()} />
</Actions>
)}
</MessageContent>
</Message>
))}
</Conversation>
<PromptInput
value={input}
onChange={handleInputChange}
onSubmit={handleSubmit}
/>
</div>
);
}'use client';
import { useChat } from 'ai/react';
import { Conversation, Message, MessageContent, Response, Actions } from '@/components/ui/ai';
import { PromptInput } from '@/components/ui/ai/prompt-input';
export default function ChatWithActions() {
const { messages, input, handleInputChange, handleSubmit, reload, stop } = useChat();
return (
<div className="flex h-screen flex-col">
<Conversation className="flex-1">
{messages.map((msg) => (
<Message key={msg.id} role={msg.role}>
<MessageContent>
<Response markdown={msg.content} />
{msg.role === 'assistant' && (
<Actions>
<Actions.Copy content={msg.content} />
<Actions.Regenerate onClick={() => reload()} />
</Actions>
)}
</MessageContent>
</Message>
))}
</Conversation>
<PromptInput
value={input}
onChange={handleInputChange}
onSubmit={handleSubmit}
/>
</div>
);
}'use client';
import { useChat } from 'ai/react';
import { Tool } from '@/components/ui/ai/tool';
import { z } from 'zod';
export default function ChatWithTools() {
const { messages } = useChat({
api: '/api/chat',
async onToolCall({ toolCall }) {
if (toolCall.toolName === 'get_weather') {
// Execute tool
return { temperature: 72, conditions: 'Sunny' };
}
}
});
return (
<Conversation>
{messages.map((msg) => (
<Message key={msg.id} role={msg.role}>
<MessageContent>
{msg.content && <Response markdown={msg.content} />}
{/* Render tool invocations */}
{msg.toolInvocations?.map((tool) => (
<Tool
key={tool.toolCallId}
name={tool.toolName}
args={tool.args}
result={tool.result}
status={tool.state} // "pending" | "success" | "error"
/>
))}
</MessageContent>
</Message>
))}
</Conversation>
);
}'use client';
import { useChat } from 'ai/react';
import { Tool } from '@/components/ui/ai/tool';
import { z } from 'zod';
export default function ChatWithTools() {
const { messages } = useChat({
api: '/api/chat',
async onToolCall({ toolCall }) {
if (toolCall.toolName === 'get_weather') {
// 执行工具调用
return { temperature: 72, conditions: 'Sunny' };
}
}
});
return (
<Conversation>
{messages.map((msg) => (
<Message key={msg.id} role={msg.role}>
<MessageContent>
{msg.content && <Response markdown={msg.content} />}
{/* 渲染工具调用 */}
{msg.toolInvocations?.map((tool) => (
<Tool
key={tool.toolCallId}
name={tool.toolName}
args={tool.args}
result={tool.result}
status={tool.state} // "pending" | "success" | "error"
/>
))}
</MessageContent>
</Message>
))}
</Conversation>
);
}'use client';
import { useChat } from 'ai/react';
import { Reasoning } from '@/components/ui/ai/reasoning';
export default function ChatWithReasoning() {
const { messages, isLoading } = useChat({
api: '/api/chat',
streamProtocol: 'text'
});
return (
<Conversation>
{messages.map((msg, idx) => {
const reasoning = msg.annotations?.reasoning;
const isStreaming = isLoading && idx === messages.length - 1;
return (
<Message key={msg.id} role={msg.role}>
<MessageContent>
{reasoning && (
<Reasoning
content={reasoning}
streaming={isStreaming}
collapsed={!isStreaming} // Collapse after done
/>
)}
<Response markdown={msg.content} />
</MessageContent>
</Message>
);
})}
</Conversation>
);
}'use client';
import { useChat } from 'ai/react';
import { Reasoning } from '@/components/ui/ai/reasoning';
export default function ChatWithReasoning() {
const { messages, isLoading } = useChat({
api: '/api/chat',
streamProtocol: 'text'
});
return (
<Conversation>
{messages.map((msg, idx) => {
const reasoning = msg.annotations?.reasoning;
const isStreaming = isLoading && idx === messages.length - 1;
return (
<Message key={msg.id} role={msg.role}>
<MessageContent>
{reasoning && (
<Reasoning
content={reasoning}
streaming={isStreaming}
collapsed={!isStreaming} // 完成后折叠
/>
)}
<Response markdown={msg.content} />
</MessageContent>
</Message>
);
})}
</Conversation>
);
}'use client';
import { useChat } from 'ai/react';
import { Sources, InlineCitation } from '@/components/ui/ai';
export default function ChatWithSources() {
const { messages } = useChat({
api: '/api/chat'
});
return (
<Conversation>
{messages.map((msg) => {
const sources = msg.annotations?.sources || [];
return (
<Message key={msg.id} role={msg.role}>
<MessageContent>
<Response markdown={msg.content} />
{/* Show sources at bottom */}
{sources.length > 0 && (
<Sources
sources={sources}
citations={msg.annotations?.citations || []}
/>
)}
</MessageContent>
</Message>
);
})}
</Conversation>
);
}'use client';
import { useChat } from 'ai/react';
import { Sources, InlineCitation } from '@/components/ui/ai';
export default function ChatWithSources() {
const { messages } = useChat({
api: '/api/chat'
});
return (
<Conversation>
{messages.map((msg) => {
const sources = msg.annotations?.sources || [];
return (
<Message key={msg.id} role={msg.role}>
<MessageContent>
<Response markdown={msg.content} />
{/* 在底部展示来源 */}
{sources.length > 0 && (
<Sources
sources={sources}
citations={msg.annotations?.citations || []}
/>
)}
</MessageContent>
</Message>
);
})}
</Conversation>
);
}'use client';
import { useChat } from 'ai/react';
import { CodeBlock } from '@/components/ui/ai/code-block';
import { Response } from '@/components/ui/ai/response';
export default function ChatWithCode() {
const { messages } = useChat();
return (
<Conversation>
{messages.map((msg) => (
<Message key={msg.id} role={msg.role}>
<MessageContent>
{/* Response component auto-renders code blocks */}
<Response
markdown={msg.content}
components={{
// Optionally override code rendering
code: ({ language, code }) => (
<CodeBlock
language={language}
code={code}
showLineNumbers={true}
/>
)
}}
/>
</MessageContent>
</Message>
))}
</Conversation>
);
}'use client';
import { useChat } from 'ai/react';
import { CodeBlock } from '@/components/ui/ai/code-block';
import { Response } from '@/components/ui/ai/response';
export default function ChatWithCode() {
const { messages } = useChat();
return (
<Conversation>
{messages.map((msg) => (
<Message key={msg.id} role={msg.role}>
<MessageContent>
{/* Response组件会自动渲染代码块 */}
<Response
markdown={msg.content}
components={{
// 可选:自定义代码渲染逻辑
code: ({ language, code }) => (
<CodeBlock
language={language}
code={code}
showLineNumbers={true}
/>
)
}}
/>
</MessageContent>
</Message>
))}
</Conversation>
);
}#!/bin/bash#!/bin/bash
**Example Usage:**
```bash
chmod +x scripts/setup-ai-elements.sh
./scripts/setup-ai-elements.sh
**使用示例**:
```bash
chmod +x scripts/setup-ai-elements.sh
./scripts/setup-ai-elements.shreferences/component-catalog.mdreferences/migration-v4-to-v5.mdreferences/common-patterns.mdcomponent-catalog.mdmigration-v4-to-v5.mdcommon-patterns.mdreferences/component-catalog.mdreferences/migration-v4-to-v5.mdreferences/common-patterns.mdcomponent-catalog.mdmigration-v4-to-v5.mdcommon-patterns.mdassets/chat-interface-starter.tsxassets/api-route-template.tsassets/components.jsonchat-interface-starter.tsxapi-route-template.tscomponents.jsonassets/chat-interface-starter.tsxassets/api-route-template.tsassets/components.jsonchat-interface-starter.tsxapi-route-template.tscomponents.jsonimport { useVirtualizer } from '@tanstack/react-virtual';
import { useRef } from 'react';
function VirtualizedChat({ messages }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: messages.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 150, // Average message height
overscan: 5 // Render 5 extra items for smooth scrolling
});
return (
<div ref={parentRef} className="h-screen overflow-y-auto">
<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
{virtualizer.getVirtualItems().map((virtualRow) => (
<div
key={virtualRow.index}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`
}}
>
<Message {...messages[virtualRow.index]} />
</div>
))}
</div>
</div>
);
}import { useVirtualizer } from '@tanstack/react-virtual';
import { useRef } from 'react';
function VirtualizedChat({ messages }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: messages.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 150, // 平均消息高度
overscan: 5 // 额外渲染5个项以保证滚动流畅
});
return (
<div ref={parentRef} className="h-screen overflow-y-auto">
<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
{virtualizer.getVirtualItems().map((virtualRow) => (
<div
key={virtualRow.index}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`
}}
>
<Message {...messages[virtualRow.index]} />
</div>
))}
</div>
</div>
);
}/* src/index.css - Customize AI message colors */
:root {
/* AI message background */
--ai-message: hsl(var(--muted));
--ai-message-foreground: hsl(var(--muted-foreground));
/* User message background */
--user-message: hsl(var(--primary));
--user-message-foreground: hsl(var(--primary-foreground));
/* Tool call colors */
--tool-success: hsl(142 76% 36%);
--tool-error: hsl(var(--destructive));
--tool-pending: hsl(47 91% 58%);
}
.dark {
--ai-message: hsl(var(--muted));
--ai-message-foreground: hsl(var(--muted-foreground));
/* ... dark mode variants */
}/* src/index.css - 自定义AI消息颜色 */
:root {
/* AI消息背景色 */
--ai-message: hsl(var(--muted));
--ai-message-foreground: hsl(var(--muted-foreground));
/* 用户消息背景色 */
--user-message: hsl(var(--primary));
--user-message-foreground: hsl(var(--primary-foreground));
/* 工具调用颜色 */
--tool-success: hsl(142 76% 36%);
--tool-error: hsl(var(--destructive));
--tool-pending: hsl(47 91% 58%);
}
.dark {
--ai-message: hsl(var(--muted));
--ai-message-foreground: hsl(var(--muted-foreground));
/* ... 暗色模式变体 */
}// Add ARIA labels to interactive elements
<PromptInput
aria-label="Chat message input"
aria-describedby="chat-instructions"
/>
<Actions>
<Actions.Copy
aria-label="Copy message to clipboard"
aria-live="polite" // Announce copy success
/>
<Actions.Regenerate
aria-label="Regenerate AI response"
/>
</Actions>
// Screen reader announcements for streaming
<Response
markdown={content}
aria-live="polite" // Announce updates during streaming
aria-atomic="false" // Only announce changes, not entire content
/>// 为交互元素添加ARIA标签
<PromptInput
aria-label="聊天消息输入框"
aria-describedby="chat-instructions"
/>
<Actions>
<Actions.Copy
aria-label="复制消息到剪贴板"
aria-live="polite" // 复制成功后通知屏幕阅读器
/>
<Actions.Regenerate
aria-label="重新生成AI回复"
/>
</Actions>
// 流式响应的屏幕阅读器通知
<Response
markdown={content}
aria-live="polite" // 流式更新时通知
aria-atomic="false" // 仅通知更新内容,而非全部内容
/>// app/api/chat/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText, tool } from 'ai';
import { z } from 'zod';
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4o'),
messages,
tools: {
get_weather: tool({
description: 'Get current weather for a location',
parameters: z.object({
location: z.string().describe('City name'),
unit: z.enum(['celsius', 'fahrenheit']).default('celsius')
}),
execute: async ({ location, unit }) => {
// Execute server-side (secure API keys)
const response = await fetch(`https://api.weather.com/...`);
const data = await response.json();
return { temperature: data.temp, conditions: data.conditions };
}
}),
search_database: tool({
description: 'Search internal database',
parameters: z.object({
query: z.string()
}),
execute: async ({ query }) => {
// Direct database access (server-side only)
const results = await db.search(query);
return results;
}
})
}
});
return result.toDataStreamResponse();
}// app/api/chat/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText, tool } from 'ai';
import { z } from 'zod';
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4o'),
messages,
tools: {
get_weather: tool({
description: '获取指定地点的当前天气',
parameters: z.object({
location: z.string().describe('城市名称'),
unit: z.enum(['celsius', 'fahrenheit']).default('celsius')
}),
execute: async ({ location, unit }) => {
// 服务端执行(API密钥安全)
const response = await fetch(`https://api.weather.com/...`);
const data = await response.json();
return { temperature: data.temp, conditions: data.conditions };
}
}),
search_database: tool({
description: '搜索内部数据库',
parameters: z.object({
query: z.string()
}),
execute: async ({ query }) => {
// 直接访问数据库(仅服务端可用)
const results = await db.search(query);
return results;
}
})
}
});
return result.toDataStreamResponse();
}ai@^5.0.0next@^15.0.0react@^19.0.0@tailwindcss/vite@^4.0.0tailwindcss@^4.0.0@radix-ui/react-*class-variance-authorityclsxtailwind-merge@tanstack/react-virtual@^3.0.0shiki@^1.0.0katex@^0.16.0react-markdown@^9.0.0ai@^5.0.0next@^15.0.0react@^19.0.0@tailwindcss/vite@^4.0.0tailwindcss@^4.0.0@radix-ui/react-*class-variance-authorityclsxtailwind-merge@tanstack/react-virtual@^3.0.0shiki@^1.0.0katex@^0.16.0react-markdown@^9.0.0/vercel/ai-elements/vercel/ai-elements{
"dependencies": {
"ai": "^5.0.0",
"next": "^15.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@tailwindcss/vite": "^4.1.14"
},
"devDependencies": {
"ai-elements": "1.6.0",
"typescript": "^5.6.0",
"@types/react": "^19.0.0",
"@types/node": "^20.0.0"
}
}{
"dependencies": {
"ai": "^5.0.0",
"next": "^15.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@tailwindcss/vite": "^4.1.14"
},
"devDependencies": {
"ai-elements": "1.6.0",
"typescript": "^5.6.0",
"@types/react": "^19.0.0",
"@types/node": "^20.0.0"
}
}// Check browser support
const supported = 'webkitSpeechRecognition' in window;
console.log('Speech supported:', supported); // false in Firefox/Safari
// Use only in supported browsers or implement server-side STT
<PromptInput enableSpeech={supported} />// 检查浏览器支持情况
const supported = 'webkitSpeechRecognition' in window;
console.log('Speech supported:', supported); // Firefox/Safari中为false
// 仅在支持的浏览器中使用,或实现服务端语音转文字
<PromptInput enableSpeech={supported} />undefinedundefinedundefinedundefined// API route MUST return toDataStreamResponse()
return result.toDataStreamResponse(); // ✅ Correct
// NOT:
return result.toTextStreamResponse(); // ❌ Wrong format for AI Elements// API路由必须返回toDataStreamResponse()
return result.toDataStreamResponse(); // ✅ 正确写法
// 错误写法:
return result.toTextStreamResponse(); // ❌ 不符合AI Elements格式要求undefinedundefined
---
---pnpm add ai@latestcomponents.jsonpnpm dlx ai-elements@latest init/app/api/chat/route.ts'use client'references/common-issues.mdtailwind-v4-shadcnpnpm add ai@latestcomponents.jsonpnpm dlx ai-elements@latest init/app/api/chat/route.ts'use client'references/common-issues.mdtailwind-v4-shadcnuseChatuseCompletionuseAssistant1. nextjs skill → Next.js 15 setup
2. tailwind-v4-shadcn skill → UI foundation
3. ai-sdk-ui skill → AI hooks and state management
4. ai-elements-chatbot skill (this) → UI components
5. clerk-auth skill (optional) → User authenticationuseChatuseCompletionuseAssistant1. nextjs Skill → Next.js 15搭建
2. tailwind-v4-shadcn Skill → UI基础搭建
3. ai-sdk-ui Skill → AI钩子和状态管理
4. ai-elements-chatbot Skill(本Skill) → UI组件
5. clerk-auth Skill(可选) → 用户认证