ai-sdk-ui by jezweb/claude-skills
npx skills add https://github.com/jezweb/claude-skills --skill ai-sdk-ui用于 Vercel AI SDK v6 的 AI 驱动用户界面的前端 React hooks。
版本 : AI SDK v6.0.42 (稳定版) 框架 : React 18+/19, Next.js 14+/15+ 最后更新 : 2026-01-20
状态: 稳定版本 最新版本: ai@6.0.42, @ai-sdk/react@3.0.44, @ai-sdk/openai@3.0.7 迁移: 从 v5 到 v6 仅有少量破坏性变更
1. 消息部分结构 (破坏性变更) 在 v6 中,消息内容现在通过 .parts 数组访问,而不是 .content:
// ❌ v5 (旧版)
{messages.map(m => (
<div key={m.id}>{m.content}</div>
))}
// ✅ v6 (新版)
{messages.map(m => (
<div key={m.id}>
{m.parts.map((part, i) => {
if (part.type === 'text') return <span key={i}>{part.text}</span>;
if (part.type === 'tool-invocation') return <ToolCall key={i} tool={part} />;
if (part.type === 'file') return <FilePreview key={i} file={part} />;
return null;
})}
</div>
))}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
text - 带有 .text 属性的文本内容tool-invocation - 带有 .toolName、.args、.result 的工具调用file - 带有 .mimeType、.data 的文件附件reasoning - 模型推理(当可用时)source - 来源引用3. 智能体集成 使用 InferAgentUIMessage<typeof agent> 进行类型安全的智能体消息传递:
import { useChat } from '@ai-sdk/react';
import type { InferAgentUIMessage } from 'ai';
import { myAgent } from './agent';
export default function AgentChat() {
const { messages, sendMessage } = useChat<InferAgentUIMessage<typeof myAgent>>({
api: '/api/chat',
});
// 消息现在会根据智能体模式进行类型检查
}
4. 工具批准工作流 (人工介入) 在执行工具之前请求用户确认:
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';
export default function ChatWithApproval() {
const { messages, sendMessage, addToolApprovalResponse } = useChat({
api: '/api/chat',
});
const handleApprove = (toolCallId: string) => {
addToolApprovalResponse({
toolCallId,
approved: true, // 或 false 表示拒绝
});
};
return (
<div>
{messages.map(message => (
<div key={message.id}>
{message.toolInvocations?.map(tool => (
tool.state === 'awaiting-approval' && (
<div key={tool.toolCallId}>
<p>批准工具调用:{tool.toolName}?</p>
<button onClick={() => handleApprove(tool.toolCallId)}>
批准
</button>
<button onClick={() => addToolApprovalResponse({
toolCallId: tool.toolCallId,
approved: false
})}>
拒绝
</button>
</div>
)
))}
</div>
))}
</div>
);
}
5. 自动提交功能 在处理批准后自动继续对话:
import { useChat, lastAssistantMessageIsCompleteWithApprovalResponses } from '@ai-sdk/react';
export default function AutoSubmitChat() {
const { messages, sendMessage } = useChat({
api: '/api/chat',
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
// 在提供所有批准响应后自动重新提交
});
}
6. 聊天中的结构化输出 在工具调用的同时生成结构化数据(以前仅在 useObject 中可用):
import { useChat } from '@ai-sdk/react';
import { z } from 'zod';
const schema = z.object({
summary: z.string(),
sentiment: z.enum(['positive', 'neutral', 'negative']),
});
export default function StructuredChat() {
const { messages, sendMessage } = useChat({
api: '/api/chat',
// 服务器现在可以流式传输带有聊天消息的结构化输出
});
}
重要提示:在 v5 中,useChat 不再管理输入状态!
v4 (旧版 - 请勿使用):
const { messages, input, handleInputChange, handleSubmit, append } = useChat();
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} />
</form>
v5 (新版 - 正确):
const { messages, sendMessage } = useChat();
const [input, setInput] = useState('');
<form onSubmit={(e) => {
e.preventDefault();
sendMessage({ content: input });
setInput('');
}}>
<input value={input} onChange={(e) => setInput(e.target.value)} />
</form>
v5 变更总结:
input、handleInputChange、handleSubmit 不再存在append() → sendMessage() : 发送消息的新方法onResponse : 改用 onFinishinitialMessages → 受控模式 : 使用 messages 属性进行完全控制maxSteps : 仅在服务器端处理完整的迁移指南请参阅 references/use-chat-migration.md。
⚠️ 弃用通知 : 自 AI SDK v5 起,
useAssistant已被弃用。OpenAI Assistants API v2 将于 2026 年 8 月 26 日停止服务。对于新项目,请改用带有自定义后端逻辑的useChat。迁移指南请参阅 openai-assistants 技能。
与 OpenAI 兼容的助手 API 交互,具有自动 UI 状态管理功能。
导入:
import { useAssistant } from '@ai-sdk/react';
基本用法:
'use client';
import { useAssistant } from '@ai-sdk/react';
import { useState, FormEvent } from 'react';
export default function AssistantChat() {
const { messages, sendMessage, isLoading, error } = useAssistant({
api: '/api/assistant',
});
const [input, setInput] = useState('');
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
sendMessage({ content: input });
setInput('');
};
return (
<div>
{messages.map(m => (
<div key={m.id}>
<strong>{m.role}:</strong> {m.content}
</div>
))}
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
disabled={isLoading}
/>
</form>
{error && <div>{error.message}</div>}
</div>
);
}
使用场景:
完整的 API 参考请参阅官方文档:https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-assistant
完整的文档请参阅 references/top-ui-errors.md。快速参考:
错误 : SyntaxError: Unexpected token in JSON at position X
原因 : API 路由未返回正确的流格式。
解决方案 :
// ✅ 正确
return result.toDataStreamResponse();
// ❌ 错误
return new Response(result.textStream);
原因 : API 路由未正确流式传输。
解决方案 :
// App Router - 使用 toDataStreamResponse()
export async function POST(req: Request) {
const result = streamText({ /* ... */ });
return result.toDataStreamResponse(); // ✅
}
// Pages Router - 使用 pipeDataStreamToResponse()
export default async function handler(req, res) {
const result = streamText({ /* ... */ });
return result.pipeDataStreamToResponse(res); // ✅
}
原因 : 部署平台缓冲响应。
解决方案 : Vercel 自动检测流式传输。其他平台可能需要配置。
原因 : body 选项仅在首次渲染时捕获。
解决方案 :
// ❌ 错误 - body 仅捕获一次
const { userId } = useUser();
const { messages } = useChat({
body: { userId }, // 陈旧!
});
// ✅ 正确 - 使用受控模式
const { userId } = useUser();
const { messages, sendMessage } = useChat();
sendMessage({
content: input,
data: { userId }, // 每次发送时都是新鲜的
});
原因 : useEffect 中的无限循环。
解决方案 :
// ❌ 错误
useEffect(() => {
saveMessages(messages);
}, [messages, saveMessages]); // saveMessages 触发重新渲染!
// ✅ 正确
useEffect(() => {
saveMessages(messages);
}, [messages]); // 仅依赖于 messages
更多常见错误(共记录了 18 个)请参阅 references/top-ui-errors.md。
始终使用流式传输以获得更好的用户体验:
// ✅ 良好 - 流式传输(显示到达的令牌)
const { messages } = useChat({ api: '/api/chat' });
// ❌ 差 - 非流式传输(用户等待完整响应)
const response = await fetch('/api/chat', { method: 'POST' });
显示加载状态:
{isLoading && <div>AI 正在输入...</div>}
提供停止按钮:
{isLoading && <button onClick={stop}>停止</button>}
自动滚动到最新消息:
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
加载时禁用输入:
<input disabled={isLoading} />
全面的最佳实践请参阅 references/streaming-patterns.md。
React 严格模式有意双次调用 effect 以捕获错误。当在 effect 中使用 useChat 或 useCompletion 时(自动恢复、初始消息),请防止双重执行,以避免重复的 API 调用和令牌浪费。
问题:
'use client';
import { useChat } from '@ai-sdk/react';
import { useEffect } from 'react';
export default function Chat() {
const { messages, sendMessage, resumeStream } = useChat({
api: '/api/chat',
resume: true,
});
useEffect(() => {
// ❌ 在严格模式下触发两次 → 两个并发流
sendMessage({ content: 'Hello' });
// 或
resumeStream();
}, []);
}
解决方案:
// ✅ 使用 ref 跟踪执行
import { useRef } from 'react';
const hasSentRef = useRef(false);
useEffect(() => {
if (hasSentRef.current) return;
hasSentRef.current = true;
sendMessage({ content: 'Hello' });
}, []);
// 特别是对于 resumeStream:
const hasResumedRef = useRef(false);
useEffect(() => {
if (!autoResume || hasResumedRef.current || status === 'streaming') return;
hasResumedRef.current = true;
resumeStream();
}, [autoResume, resumeStream, status]);
为什么会发生这种情况: React 严格模式双次调用 effect 以暴露副作用。SDK 不防止并发请求,因此两次调用都会创建单独的流,这些流会争夺状态更新。
影响: 重复消息、令牌使用量翻倍、竞争条件导致 TypeError: "Cannot read properties of undefined (reading 'state')"。
稳定版 (v6 - 推荐):
{
"dependencies": {
"ai": "^6.0.8",
"@ai-sdk/react": "^3.0.6",
"@ai-sdk/openai": "^3.0.2",
"react": "^18.3.0",
"zod": "^3.24.2"
}
}
旧版 (v5):
{
"dependencies": {
"ai": "^5.0.99",
"@ai-sdk/react": "^1.0.0",
"@ai-sdk/openai": "^2.0.68"
}
}
版本说明:
核心 UI Hooks:
高级主题 (仅链接):
Next.js 集成:
迁移与故障排除:
Vercel 部署:
此技能在 templates/ 中包含以下模板:
请参阅 references/ 中的:
生产环境测试 : WordPress Auditor (https://wordpress-auditor.webfonts.workers.dev) 最后验证 : 2026-01-20 | 技能版本 : 3.1.0 | 变更 : 更新至 AI SDK v6.0.42 (+19 个补丁)。添加了 React 严格模式部分。扩展了问题 #7(陈旧 body)并提供了 3 种解决方法。添加了 6 个新问题:resume+onFinish 的 TypeError (#13)、并发 sendMessage 状态损坏 (#14)、工具批准回调边缘情况 (#15)、早期停止时的 ZodError (#16)、convertToModelMessages 工具批准错误 (#17)、未定义 id 无限循环 (#18)。错误数量:12→18。
每周安装次数
517
仓库
GitHub 星标数
650
首次出现
2026年1月20日
安全审计
安装于
claude-code405
opencode359
gemini-cli355
codex323
cursor308
antigravity294
Frontend React hooks for AI-powered user interfaces with Vercel AI SDK v6.
Version : AI SDK v6.0.42 (Stable) Framework : React 18+/19, Next.js 14+/15+ Last Updated : 2026-01-20
Status: Stable Release Latest: ai@6.0.42, @ai-sdk/react@3.0.44, @ai-sdk/openai@3.0.7 Migration: Minimal breaking changes from v5 → v6
1. Message Parts Structure (Breaking Change) In v6, message content is now accessed via .parts array instead of .content:
// ❌ v5 (OLD)
{messages.map(m => (
<div key={m.id}>{m.content}</div>
))}
// ✅ v6 (NEW)
{messages.map(m => (
<div key={m.id}>
{m.parts.map((part, i) => {
if (part.type === 'text') return <span key={i}>{part.text}</span>;
if (part.type === 'tool-invocation') return <ToolCall key={i} tool={part} />;
if (part.type === 'file') return <FilePreview key={i} file={part} />;
return null;
})}
</div>
))}
Part Types:
text - Text content with .text propertytool-invocation - Tool calls with .toolName, .args, .resultfile - File attachments with .mimeType, .datareasoning - Model reasoning (when available)source - Source citations3. Agent Integration Type-safe messaging with agents using InferAgentUIMessage<typeof agent>:
import { useChat } from '@ai-sdk/react';
import type { InferAgentUIMessage } from 'ai';
import { myAgent } from './agent';
export default function AgentChat() {
const { messages, sendMessage } = useChat<InferAgentUIMessage<typeof myAgent>>({
api: '/api/chat',
});
// messages are now type-checked against agent schema
}
4. Tool Approval Workflows (Human-in-the-Loop) Request user confirmation before executing tools:
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';
export default function ChatWithApproval() {
const { messages, sendMessage, addToolApprovalResponse } = useChat({
api: '/api/chat',
});
const handleApprove = (toolCallId: string) => {
addToolApprovalResponse({
toolCallId,
approved: true, // or false to deny
});
};
return (
<div>
{messages.map(message => (
<div key={message.id}>
{message.toolInvocations?.map(tool => (
tool.state === 'awaiting-approval' && (
<div key={tool.toolCallId}>
<p>Approve tool call: {tool.toolName}?</p>
<button onClick={() => handleApprove(tool.toolCallId)}>
Approve
</button>
<button onClick={() => addToolApprovalResponse({
toolCallId: tool.toolCallId,
approved: false
})}>
Deny
</button>
</div>
)
))}
</div>
))}
</div>
);
}
5. Auto-Submit Capability Automatically continue conversation after handling approvals:
import { useChat, lastAssistantMessageIsCompleteWithApprovalResponses } from '@ai-sdk/react';
export default function AutoSubmitChat() {
const { messages, sendMessage } = useChat({
api: '/api/chat',
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
// Automatically resubmit after all approval responses provided
});
}
6. Structured Output in Chat Generate structured data alongside tool calling (previously only available in useObject):
import { useChat } from '@ai-sdk/react';
import { z } from 'zod';
const schema = z.object({
summary: z.string(),
sentiment: z.enum(['positive', 'neutral', 'negative']),
});
export default function StructuredChat() {
const { messages, sendMessage } = useChat({
api: '/api/chat',
// Server can now stream structured output with chat messages
});
}
CRITICAL: useChat no longer manages input state in v5!
v4 (OLD - DON'T USE):
const { messages, input, handleInputChange, handleSubmit, append } = useChat();
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} />
</form>
v5 (NEW - CORRECT):
const { messages, sendMessage } = useChat();
const [input, setInput] = useState('');
<form onSubmit={(e) => {
e.preventDefault();
sendMessage({ content: input });
setInput('');
}}>
<input value={input} onChange={(e) => setInput(e.target.value)} />
</form>
Summary of v5 Changes:
input, handleInputChange, handleSubmit no longer existappend() → sendMessage(): New method for sending messagesonResponse removed: Use onFinish insteadinitialMessages → controlled mode: Use messages prop for full controlmaxSteps removed: Handle on server-side onlySee references/use-chat-migration.md for complete migration guide.
⚠️ Deprecation Notice :
useAssistantis deprecated as of AI SDK v5. OpenAI Assistants API v2 will sunset on August 26, 2026. For new projects, useuseChatwith custom backend logic instead. See the openai-assistants skill for migration guidance.
Interact with OpenAI-compatible assistant APIs with automatic UI state management.
Import:
import { useAssistant } from '@ai-sdk/react';
Basic Usage:
'use client';
import { useAssistant } from '@ai-sdk/react';
import { useState, FormEvent } from 'react';
export default function AssistantChat() {
const { messages, sendMessage, isLoading, error } = useAssistant({
api: '/api/assistant',
});
const [input, setInput] = useState('');
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
sendMessage({ content: input });
setInput('');
};
return (
<div>
{messages.map(m => (
<div key={m.id}>
<strong>{m.role}:</strong> {m.content}
</div>
))}
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
disabled={isLoading}
/>
</form>
{error && <div>{error.message}</div>}
</div>
);
}
Use Cases:
See official docs for complete API reference: https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-assistant
See references/top-ui-errors.md for complete documentation. Quick reference:
Error : SyntaxError: Unexpected token in JSON at position X
Cause : API route not returning proper stream format.
Solution :
// ✅ CORRECT
return result.toDataStreamResponse();
// ❌ WRONG
return new Response(result.textStream);
Cause : API route not streaming correctly.
Solution :
// App Router - use toDataStreamResponse()
export async function POST(req: Request) {
const result = streamText({ /* ... */ });
return result.toDataStreamResponse(); // ✅
}
// Pages Router - use pipeDataStreamToResponse()
export default async function handler(req, res) {
const result = streamText({ /* ... */ });
return result.pipeDataStreamToResponse(res); // ✅
}
Cause : Deployment platform buffering responses.
Solution : Vercel auto-detects streaming. Other platforms may need configuration.
Cause : body option captured at first render only.
Solution :
// ❌ WRONG - body captured once
const { userId } = useUser();
const { messages } = useChat({
body: { userId }, // Stale!
});
// ✅ CORRECT - use controlled mode
const { userId } = useUser();
const { messages, sendMessage } = useChat();
sendMessage({
content: input,
data: { userId }, // Fresh on each send
});
Cause : Infinite loop in useEffect.
Solution :
// ❌ WRONG
useEffect(() => {
saveMessages(messages);
}, [messages, saveMessages]); // saveMessages triggers re-render!
// ✅ CORRECT
useEffect(() => {
saveMessages(messages);
}, [messages]); // Only depend on messages
See references/top-ui-errors.md for 13 more common errors (18 total documented).
Always use streaming for better UX:
// ✅ GOOD - Streaming (shows tokens as they arrive)
const { messages } = useChat({ api: '/api/chat' });
// ❌ BAD - Non-streaming (user waits for full response)
const response = await fetch('/api/chat', { method: 'POST' });
Show loading states:
{isLoading && <div>AI is typing...</div>}
Provide stop button:
{isLoading && <button onClick={stop}>Stop</button>}
Auto-scroll to latest message:
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
Disable input while loading:
<input disabled={isLoading} />
See references/streaming-patterns.md for comprehensive best practices.
React Strict Mode intentionally double-invokes effects to catch bugs. When using useChat or useCompletion in effects (auto-resume, initial messages), guard against double execution to prevent duplicate API calls and token waste.
Problem:
'use client';
import { useChat } from '@ai-sdk/react';
import { useEffect } from 'react';
export default function Chat() {
const { messages, sendMessage, resumeStream } = useChat({
api: '/api/chat',
resume: true,
});
useEffect(() => {
// ❌ Triggers twice in strict mode → two concurrent streams
sendMessage({ content: 'Hello' });
// or
resumeStream();
}, []);
}
Solution:
// ✅ Use ref to track execution
import { useRef } from 'react';
const hasSentRef = useRef(false);
useEffect(() => {
if (hasSentRef.current) return;
hasSentRef.current = true;
sendMessage({ content: 'Hello' });
}, []);
// For resumeStream specifically:
const hasResumedRef = useRef(false);
useEffect(() => {
if (!autoResume || hasResumedRef.current || status === 'streaming') return;
hasResumedRef.current = true;
resumeStream();
}, [autoResume, resumeStream, status]);
Why It Happens: React Strict Mode double-invokes effects to surface side effects. The SDK doesn't guard against concurrent requests, so both invocations create separate streams that fight for state updates.
Impact: Duplicate messages, doubled token usage, race conditions causing TypeError: "Cannot read properties of undefined (reading 'state')".
Source: GitHub Issue #7891, Issue #6166
Stable (v6 - Recommended):
{
"dependencies": {
"ai": "^6.0.8",
"@ai-sdk/react": "^3.0.6",
"@ai-sdk/openai": "^3.0.2",
"react": "^18.3.0",
"zod": "^3.24.2"
}
}
Legacy (v5):
{
"dependencies": {
"ai": "^5.0.99",
"@ai-sdk/react": "^1.0.0",
"@ai-sdk/openai": "^2.0.68"
}
}
Version Notes:
Core UI Hooks:
Advanced Topics (Link Only):
Next.js Integration:
Migration & Troubleshooting:
Vercel Deployment:
This skill includes the following templates in templates/:
See references/ for:
Production Tested : WordPress Auditor (https://wordpress-auditor.webfonts.workers.dev) Last verified : 2026-01-20 | Skill version : 3.1.0 | Changes : Updated to AI SDK v6.0.42 (+19 patches). Added React Strict Mode section. Expanded Issue #7 (stale body) with 3 workarounds. Added 6 new issues: TypeError with resume+onFinish (#13), concurrent sendMessage state corruption (#14), tool approval callback edge case (#15), ZodError on early stop (#16), convertToModelMessages tool approval bug (#17), undefined id infinite loop (#18). Error count: 12→18.
Weekly Installs
517
Repository
GitHub Stars
650
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
claude-code405
opencode359
gemini-cli355
codex323
cursor308
antigravity294
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
106,200 周安装
竞争对手研究指南:SEO、内容、反向链接与定价分析工具
231 周安装
Azure 工作负载自动升级评估工具 - 支持 Functions、App Service 计划与 SKU 迁移
231 周安装
Kaizen持续改进方法论:软件开发中的渐进式优化与防错设计实践指南
231 周安装
软件UI/UX设计指南:以用户为中心的设计原则、WCAG可访问性与平台规范
231 周安装
Apify 网络爬虫和自动化平台 - 无需编码抓取亚马逊、谷歌、领英等网站数据
231 周安装
llama.cpp 中文指南:纯 C/C++ LLM 推理,CPU/非 NVIDIA 硬件优化部署
231 周安装