发布时间:2026 年 7 月 16 日
在将内置 Prompt API 与 Vercel AI
SDK 搭配使用中,您了解了四个核心生成
基元,即 generateText()、streamText()、混合代码和使用
Output.object() 的结构化输出,所有这些都由 @browser-ai/core 提供支持。这次,您将构建更具互动性的内容:一个完全在浏览器中运行的完整流式聊天界面,当 Prompt API 不可用时,会自动回退到云模型。
您要构建的内容
一个 React 聊天界面,可执行以下操作:
- 使用 Vercel AI SDK 的
useChathook 进行流式多轮对话。 - 在浏览器中 运行模型循环,无需后端服务器。
- 当 Prompt API 不可用时,自动回退到 Gemini 2.5 Flash。
- 将助理回复呈现为 GitHub 风味的 Markdown,并在流式传输期间处理不完整的令牌。
- 在等待第一个令牌时,显示“正在思考…”微光效果。
- 自动滚动到新消息,并在向上滚动时显示滚动到底部按钮。
其他依赖项
除了 ai、@browser-ai/core 和 @ai-sdk/google 之外,聊天界面还需要
React、AI SDK 的 React 绑定和一些 Markdown 软件包:
npm install react react-dom @ai-sdk/react
npm install react-markdown remark-gfm harden-react-markdown
npm install -D @types/react @types/react-dom
对于界面,还要添加 Tailwind CSS、一些组件实用程序和 Lucide 图标:
npm install -D tailwindcss postcss autoprefixer
npm install clsx tailwind-merge lucide-react
投票配置:多条目构建
该项目已包含 index.html。添加 chat.html 作为第二个入口点,并配置 Vite 以构建这两个文件:
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
resolve: { alias: { '@': resolve(__dirname, './src') } },
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
chat: resolve(__dirname, 'chat.html'),
},
},
},
});
chat.html 非常简单,仅包含一个 <div id="root"> 和一个脚本标记:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Built-in AI Chatbot</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/chat.tsx"></script>
</body>
</html>
自动模型选择
在聊天机器人中,应用会自动做出选择:它会先尝试内置模型,如果 Prompt API 不可用,则会回退到云。
它会在模块加载时(在 React 挂载之前)执行此操作,因此在用户输入第一条消息时,代理已准备就绪:
const agentPromise: Promise<ToolLoopAgent> = (async () => {
const builtIn = browserAI();
let model: any = builtIn;
if (typeof builtIn.availability === 'function') {
const availability = await builtIn.availability();
if (availability === 'unavailable') {
const { createGoogleGenerativeAI } = await import('@ai-sdk/google');
model = createGoogleGenerativeAI({ apiKey })('gemini-2.5-flash');
} else if (availability === 'downloadable') {
await builtIn.createSessionWithProgress(() => {});
}
}
return new ToolLoopAgent({ model, instructions: 'You are a helpful assistant.' });
})();
新部分是 ToolLoopAgent;AI SDK 抽象,用于在任何模型之上管理多轮对话循环。它接受模型和系统提示,并在内部处理来回交互。
连接代理以使用 useChat
@ai-sdk/react 软件包的 useChat hook 通常与 HTTP 端点通信。对于浏览器端推理,请改用 DirectChatTransport。它完全在浏览器中运行 ToolLoopAgent 循环,不涉及任何服务器:
const transport = useMemo(() => new DirectChatTransport({ agent }), [agent]);
const { messages, sendMessage, status, stop } = useChat({ transport });
useMemo 非常重要,因为 DirectChatTransport 会保留对话状态,因此它必须是稳定的引用。在每次渲染时重新创建它会重置对话。
useChat 为您提供:
Messages:以UIMessage[]形式呈现的完整对话,每个对话都包含一个role和一个parts数组sendMessage({ text }):发送新的用户轮次并开始流式传输响应Status:'idle'|'submitted'|'streaming'|'error'Stop:取消正在进行的生成
渲染消息
每条消息都有一个 parts 数组。对于此聊天机器人,我们只关心 type:
'text' 部分。用户消息显示为右对齐的气泡;助理消息左对齐并带有图标:
const ChatMessage = ({ message, isStreaming }: { message: UIMessage; isStreaming: boolean }) => {
const isUser = message.role === 'user';
const textParts = message.parts.map((part, i) => {
if (part.type !== 'text') return null;
if (isUser) return <span key={i}>{part.text}</span>;
return <Response key={i} parseIncompleteMarkdown={isStreaming}>{part.text}</Response>;
});
if (isUser) {
return (
<div className="flex flex-col items-end gap-2 animate-fade-up">
<MessageContent className="w-fit max-w-[min(80%,56ch)] ...">
{textParts}
</MessageContent>
</div>
);
}
return (
<div className="flex items-start gap-3">
<AIIcon />
<MessageContent className="text-[13px] leading-[1.65]">{textParts}</MessageContent>
</div>
);
};
MessageContent 和 Response 是 AI 元素。
它们是您复制到
项目中的 shadcn 样式的源组件,而不是从 npm 安装的组件。Response 使用 react-markdown
封装了 remark-gfm,用于 GitHub 风味的 Markdown(表格、任务列表、
删除线),并使用 harden-react-markdown 来清理 AI
输出中的链接和图片。
在消息仍在流式传输时,parseIncompleteMarkdown 属性为 true。在流式传输期间,模型可能会写入 **bold,但尚未关闭 **,留下一个悬空令牌,该令牌将呈现为字面星号。parseIncompleteMarkdown 会关闭任何打开的 **、__、`、~~,并
截断悬空的 [ 链接开头,以便渲染的输出在每个
增量块上保持干净。
“正在思考…”状态
在发送消息和接收第一个令牌之间,status 为
'submitted'。在此期间,应用会显示动画微光效果:
{status === 'submitted' && messages.at(-1)?.role !== 'assistant' && (
<ThinkingMessage />
)}
条件 messages.at(-1)?.role !== 'assistant' 可防止在助理消息开始流式传输后再次显示微光效果。
ThinkingMessage 使用 Shimmer 组件:一个 <span>,其中包含使用 background-clip: text 的移动渐变,为“正在思考…”文本提供扫掠突出显示效果。
自动滚动
当有新内容到达时,应用会滚动到底部,但前提是用户已位于底部。在对话过程中,如果用户正在阅读某些内容,滚动到其他位置会很烦人。
const [isAtBottom, setIsAtBottom] = useState(true);
useEffect(() => {
if (isAtBottom) endRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages, status, isAtBottom]);
const handleScroll = () => {
const el = containerRef.current;
if (!el) return;
setIsAtBottom(el.scrollHeight - el.scrollTop - el.clientHeight < 50);
};
当 isAtBottom 为 false 时,会显示一个浮动的滚动到底部按钮,当用户返回到底部时,该按钮会淡出。
输入区域
当您输入内容时,textarea 会自动调整大小,方法是在每个输入事件中将其高度重置为 auto,然后将其设置为 scrollHeight。它会在按 Enter 键时提交(而不是 Shift+Enter),并且在流式传输响应时,发送按钮会被调用 stop() 的停止按钮替换:
<textarea
onInput={(e) => {
const el = e.currentTarget;
el.style.height = 'auto';
el.style.height = `${el.scrollHeight}px`;
}}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
if (input.trim() && !isStreaming) {
sendMessage({ text: input });
setInput('');
}
}
}}
/>;
{
isStreaming ? (
<Button variant="outline" onClick={stop}>
Stop
</Button>
) : (
<Button type="submit" disabled={!input.trim()}>
Send
</Button>
);
}
挂载并显示加载状态
由于 agentPromise 是异步的,因此请先等待它,然后再渲染 Chat。App 封装容器会解析该 promise,并在此期间显示微调框:
function App() {
const [agent, setAgent] = (useState < ToolLoopAgent) | (null > null);
useEffect(() => {
agentPromise.then(setAgent);
}, []);
if (!agent) {
return (
<div className="flex h-dvh items-center justify-center">
<Loader size={20} />
</div>
);
}
return <Chat agent={agent} />;
}
代理解析后(无论是内置模型立即启动还是等待模型下载),微调框都会消失,并且聊天界面会挂载。
演示
实时演示是一个功能齐全的聊天机器人,完全在浏览器中运行。输入消息并按 Enter 键。如果 Prompt API 可用,则回复会直接从设备端模型流式传输,而无需网络请求。如果您的浏览器不支持 Prompt API,则会自动回退到 Gemini 2.5 Flash。尝试让它解释列表中的内容、编写代码段或使用 Markdown 格式。响应会直接呈现格式化的代码块、表格和内嵌代码。

总结
在这两篇文章中,您了解了 Vercel AI SDK 通过浏览器的内置 Prompt API 实现的全部功能,从原始生成基元到完善的流式聊天界面。
在将内置 Prompt API 与 Vercel AI SDK 搭配使用中,您学习了如何
使用 generateText() 和 streamText() 进行非流式和流式文本
生成,如何使用 Output.object() 请求结构化 JSON 输出,以及
如何编写混合代码,以便在运行时在内置模型和云
提供商之间进行选择,而无需更改生成逻辑。
在本文档中,您使用了相同的构建块,并将它们封装在一个完整的 React 界面中:一个 ToolLoopAgent 来管理对话循环,一个带有 DirectChatTransport 的 useChat 来直接在浏览器中流式传输响应,以及 AI 元素组件来在 Markdown 响应到达时对其进行清晰地渲染;所有这些都具有在 Prompt API 不可用时自动回退到云的功能。
结果是两个完全在浏览器中运行的演示,无需后端:
- 将内置 Prompt API 与 Vercel AI SDK 搭配使用演示:文本生成、流式传输和结构化输出并排显示
- 将 Vercel AI SDK 界面和 AI 元素与 Prompt API 搭配使用演示:一个完整的流式聊天机器人,具有 Markdown 渲染和深色模式