搭配 Prompt API 使用 Vercel AI SDK UI 和 AI 元素

發布日期:2026 年 7 月 16 日

在「使用 Vercel AI SDK 搭配內建的 Prompt API」中,您已瞭解四個核心生成基本類型,也就是 generateText()streamText()、混合程式碼,以及使用 Output.object() 的結構化輸出內容,這些都是由 @browser-ai/core 提供支援。這次您要建構更具互動性的內容:完整的串流聊天使用者介面,完全在瀏覽器中執行,且在 Prompt API 無法使用時,會自動改用雲端模型。

要建構的內容

React 即時通訊介面,可執行下列操作:

  • 使用 Vercel AI SDK 的 useChat hook 串流多輪對話。
  • 在瀏覽器中執行模型迴圈,不需後端伺服器。
  • 如果 Prompt API 無法使用,系統會自動改用 Gemini 2.5 Flash。
  • 以 GitHub 風格的 Markdown 格式算繪助理回覆,並在串流期間處理不完整的權杖。
  • 等待第一個權杖時,顯示「思考中…」微光。
  • 自動捲動至新訊息,並在您向上捲動時顯示捲動至底部的按鈕。

其他依附元件

除了 ai@browser-ai/core@ai-sdk/google 之外,即時通訊 UI 還需要 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

此外,請為 UI 新增 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 抽象化,可管理任何模型的多輪對話迴圈。這個方法會採用模型和系統提示,並在內部處理來回通訊。

連結代理程式以使用 Chat

@ai-sdk/react 套件的 useChat 勾點通常會與 HTTP 端點通訊。 如要在瀏覽器端進行推論,請改用 DirectChatTransport。這個迴圈完全在瀏覽器中執行,不涉及任何伺服器:ToolLoopAgent

const transport = useMemo(() => new DirectChatTransport({ agent }), [agent]);
const { messages, sendMessage, status, stop } = useChat({ transport });

useMemo 很重要,因為 DirectChatTransport 會保留對話狀態,因此必須是穩定的參照。每次算繪時重新建立,都會重設對話。

useChat 的優點包括:

  • Messages:完整對話 (UIMessage[]),每個對話都包含 roleparts 陣列
  • 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>
  );
};

MessageContentResponseAI 元素。這些是 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 時,系統會顯示浮動的捲動至底部按鈕,使用者返回底部時,按鈕會淡出。

輸入區域

當您輸入內容時,文字區域會將高度重設為 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 是非同步,請先等待,再算繪 ChatApp 包裝函式會解析 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 格式。回覆內容會直接以格式化程式碼區塊、表格和內嵌程式碼呈現。

聊天介面:顯示與 AI 助理的對話,包括文字輸入欄位和串流回覆區域。

結論

在這兩篇文章中,您已瞭解 Vercel AI SDK 如何透過瀏覽器的內建 Prompt API,實現各種功能,從原始的生成基本項目,到完善的串流聊天介面,應有盡有。

在「搭配使用內建提示 API 和 Vercel AI SDK」中,您已瞭解如何使用 generateText()streamText() 生成非串流和串流文字、如何使用 Output.object() 要求結構化 JSON 輸出內容,以及如何編寫混合程式碼,在執行階段選取內建模型和雲端供應商,且生成邏輯不會變更。

在這份文件中,您採用了相同的建構區塊,並將其包裝在完整的 React UI 中:ToolLoopAgent 用於管理對話迴圈、useChat 用於在瀏覽器中直接串流回應,以及 AI Elements 元件,用於在收到 Markdown 回應時,以清楚的方式呈現;當 Prompt API 無法使用時,所有這些都會自動回復到雲端。DirectChatTransport

結果是兩個完全透過瀏覽器運作的試用版,不需要後端: