Ngày xuất bản: 16 tháng 7 năm 2026
Trong phần Sử dụng Prompt API tích hợp với Vercel AI SDK, bạn đã thấy 4 nguyên tắc cơ bản để tạo, cụ thể là generateText(), streamText(), mã kết hợp và đầu ra có cấu trúc bằng Output.object(), tất cả đều được hỗ trợ bởi @browser-ai/core. Lần này, bạn sẽ tạo một thứ gì đó mang tính tương tác hơn: một giao diện người dùng trò chuyện trực tuyến đầy đủ, chạy hoàn toàn trong trình duyệt, với khả năng tự động chuyển về mô hình đám mây khi Prompt API không hoạt động.
Sản phẩm bạn sẽ tạo ra
Một giao diện trò chuyện React có những đặc điểm sau:
- Sử dụng hook
useChatcủa Vercel AI SDK để phát trực tiếp cuộc trò chuyện nhiều lượt. - Chạy vòng lặp mô hình trong trình duyệt mà không cần máy chủ phụ trợ.
- Tự động chuyển về Gemini 2.5 Flash khi Prompt API không hoạt động.
- Kết xuất các câu trả lời của trợ lý dưới dạng Markdown theo kiểu GitHub, xử lý các mã thông báo chưa hoàn chỉnh trong quá trình truyền trực tuyến.
- Hiện hiệu ứng lấp lánh "Đang suy nghĩ…" trong khi chờ mã thông báo đầu tiên.
- Tự động cuộn đến tin nhắn mới, có nút cuộn xuống dưới cùng khi bạn đã cuộn lên.
Các phần phụ thuộc khác
Cùng với ai, @browser-ai/core và @ai-sdk/google, giao diện người dùng trò chuyện cần có React, các liên kết React của AI SDK và một số gói 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
Đối với giao diện người dùng, hãy thêm Tailwind CSS, một số tiện ích thành phần và Lucide cho các biểu tượng:
npm install -D tailwindcss postcss autoprefixer
npm install clsx tailwind-merge lucide-react
Cấu hình bình chọn: bản dựng nhiều mục
Dự án đã có index.html. Thêm chat.html làm điểm truy cập thứ hai và định cấu hình Vite để tạo cả hai:
// 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 là tối thiểu, chỉ có <div id="root"> và thẻ tập lệnh:
<!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>
Tự động chọn mô hình
Trong chatbot, ứng dụng sẽ tự động đưa ra lựa chọn đó: trước tiên, ứng dụng sẽ thử mô hình tích hợp và quay lại đám mây nếu Prompt API không hoạt động.
Thao tác này diễn ra vào thời điểm tải mô-đun, trước khi React gắn kết, vì vậy, tác nhân sẽ sẵn sàng vào thời điểm người dùng nhập tin nhắn đầu tiên:
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.' });
})();
Phần mới này là ToolLoopAgent; lớp trừu tượng SDK AI quản lý một vòng lặp trò chuyện nhiều lượt trên mọi mô hình. Nó lấy mô hình và một lời nhắc hệ thống, đồng thời xử lý qua lại nội bộ.
Kết nối nhân viên hỗ trợ với useChat
Thường thì lệnh gọi useChat của gói @ai-sdk/react sẽ giao tiếp với một điểm cuối HTTP.
Đối với suy luận phía trình duyệt, hãy sử dụng DirectChatTransport. Ứng dụng này chạy vòng lặp ToolLoopAgent hoàn toàn trong trình duyệt mà không cần máy chủ:
const transport = useMemo(() => new DirectChatTransport({ agent }), [agent]);
const { messages, sendMessage, status, stop } = useChat({ transport });
useMemo rất quan trọng vì DirectChatTransport lưu giữ trạng thái cuộc trò chuyện, vì vậy, đây phải là một tham chiếu ổn định. Việc tạo lại thành phần này trên mỗi lần kết xuất sẽ đặt lại cuộc trò chuyện.
useChat mang đến cho bạn:
Messages: toàn bộ cuộc trò chuyện dưới dạngUIMessage[], mỗi cuộc trò chuyện có mộtrolevà một mảngpartssendMessage({ text }): gửi lượt tương tác mới của người dùng và bắt đầu truyền trực tuyến phản hồiStatus:'idle'|'submitted'|'streaming'|'error'Stop: huỷ một thao tác tạo đang diễn ra
Kết xuất tin nhắn
Mỗi thông báo đều có một mảng parts. Đối với chatbot này, chúng ta chỉ quan tâm đến các phần type:
'text'. Tin nhắn của người dùng xuất hiện dưới dạng một bong bóng căn chỉnh bên phải; tin nhắn của trợ lý được căn chỉnh bên trái bằng một biểu tượng:
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 và Response là Phần tử AI.
Đây là các thành phần nguồn theo kiểu shadcn mà bạn sao chép vào dự án của mình thay vì cài đặt từ npm. Response bao bọc react-markdown bằng remark-gfm cho Markdown theo kiểu GitHub (bảng, danh sách việc cần làm, gạch ngang chữ) và harden-react-markdown để dọn dẹp các đường liên kết và hình ảnh trong đầu ra của AI.
Đạo cụ parseIncompleteMarkdown là true trong khi thông báo vẫn đang phát trực tuyến. Trong quá trình truyền phát trực tiếp, mô hình có thể ghi **bold nhưng chưa đóng **, để lại một mã thông báo lơ lửng sẽ hiển thị dưới dạng dấu hoa thị theo nghĩa đen. parseIncompleteMarkdown đóng mọi **, __, `, ~~ đang mở và cắt bớt các phần đầu của đường liên kết [ chưa hoàn chỉnh để đầu ra được kết xuất luôn rõ ràng trên mọi khối gia tăng.
Trạng thái "Đang suy nghĩ…"
Khoảng thời gian giữa lúc gửi thông báo và lúc nhận được mã thông báo đầu tiên là status 'submitted'. Trong khoảng thời gian này, ứng dụng sẽ hiển thị một hiệu ứng lấp lánh có chuyển động:
{status === 'submitted' && messages.at(-1)?.role !== 'assistant' && (
<ThinkingMessage />
)}
Điều kiện messages.at(-1)?.role !== 'assistant' ngăn hiệu ứng lấp lánh xuất hiện lại sau khi thông báo của trợ lý bắt đầu phát trực tuyến.
ThinkingMessage sử dụng thành phần Shimmer: một <span> có độ dốc chuyển động bằng background-clip: text, tạo cho văn bản "Đang suy nghĩ…" hiệu ứng làm nổi bật quét.
Tự động cuộn
Khi có nội dung mới, ứng dụng sẽ cuộn xuống dưới cùng, nhưng chỉ khi người dùng đã ở dưới cùng. Việc cuộn họ ra khỏi nội dung họ đang đọc trong lúc trò chuyện sẽ gây phiền toái.
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);
};
Nút cuộn xuống dưới cùng dạng nổi sẽ xuất hiện khi isAtBottom có giá trị là false và mờ dần khi người dùng quay lại cuối cùng.
Vùng nhập
textarea tự động điều chỉnh kích thước khi bạn nhập bằng cách đặt lại chiều cao thành auto trên mọi sự kiện đầu vào, sau đó đặt thành scrollHeight. Nút này gửi khi bạn nhấn Enter (không phải Shift+Enter) và trong khi câu trả lời đang được truyền trực tuyến, nút Gửi sẽ được thay thế bằng nút Dừng gọi 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>
);
}
Gắn với trạng thái tải
Vì agentPromise là không đồng bộ, hãy đợi lệnh này trước khi kết xuất Chat. Trình bao bọc App giải quyết lời hứa và cho thấy một chỉ báo xoay trong thời gian chờ:
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} />;
}
Sau khi nhân viên hỗ trợ giải quyết, cho dù đó là mô hình tích hợp khởi động ngay lập tức hay chờ tải mô hình xuống, thì biểu tượng xoay sẽ biến mất và giao diện người dùng trò chuyện sẽ gắn kết.
Bản minh hoạ
Bản minh hoạ trực tiếp là một chatbot có đầy đủ chức năng và chạy hoàn toàn trong trình duyệt của bạn. Nhập tin nhắn rồi nhấn phím Enter. Nếu Prompt API có sẵn, câu trả lời sẽ được truyền trực tiếp từ mô hình trên thiết bị mà không cần yêu cầu mạng. Nếu trình duyệt của bạn không hỗ trợ Prompt API, thì trình duyệt sẽ tự động chuyển về Gemini 2.5 Flash. Hãy thử yêu cầu Gemini giải thích một nội dung nào đó dưới dạng danh sách, viết một đoạn mã hoặc sử dụng định dạng Markdown. Các câu trả lời hiển thị với các khối mã, bảng và mã nội tuyến được định dạng sẵn.

Kết luận
Trong suốt hai bài viết này, bạn đã thấy toàn bộ phạm vi những gì mà Vercel AI SDK có thể làm được với Prompt API tích hợp của trình duyệt, từ các nguyên tắc cơ bản về việc tạo nội dung thô cho đến giao diện trò chuyện trực tuyến hoàn chỉnh.
Trong phần Sử dụng Prompt API tích hợp với Vercel AI SDK, bạn đã tìm hiểu cách sử dụng generateText() và streamText() để tạo văn bản không truyền trực tuyến và truyền trực tuyến, cách yêu cầu đầu ra JSON có cấu trúc bằng Output.object() và cách viết mã kết hợp chọn giữa mô hình tích hợp và nhà cung cấp đám mây trong thời gian chạy mà không cần thay đổi logic tạo.
Trong tài liệu này, bạn đã lấy những thành phần xây dựng tương tự đó và gói chúng trong một giao diện người dùng React hoàn chỉnh: ToolLoopAgent để quản lý vòng lặp cuộc trò chuyện, useChat có DirectChatTransport để hiện câu trả lời theo thời gian thực trực tiếp trong trình duyệt và các thành phần AI Elements để hiển thị các câu trả lời bằng Markdown một cách rõ ràng khi chúng xuất hiện; tất cả đều có tính năng dự phòng tự động trên đám mây khi Prompt API không hoạt động.
Kết quả là 2 bản minh hoạ hoạt động hoàn toàn trên trình duyệt, không cần có phần phụ trợ:
- Sử dụng Prompt API tích hợp với bản minh hoạ Vercel AI SDK: tạo văn bản, truyền phát trực tuyến và đầu ra có cấu trúc song song
- Sử dụng giao diện người dùng Vercel AI SDK và các phần tử AI với bản minh hoạ Prompt API: một chatbot truyền phát trực tiếp đầy đủ với tính năng kết xuất Markdown và chế độ tối