喂!我是 Wei

Front-End Engineer

Be a Problem Solver.

⌘K

導覽

所有文章緣起互動小功能

文章分類

目錄
套件安裝Vercel AI SDK 是什麼為什麼選 Groq(免費)取得 Groq API Key設定環境變數API Route:Server-side StreamingReact Component:useChat Hook訊息格式:UIMessageChatStatus 的四個狀態部署到 Vercel小結

相關文章

用 Canvas 做刮刮樂:destination-out 的原理與實現

2026年3月11日

用 requestAnimationFrame 實作可中途停止的轉盤

2026年3月10日

從 jQuery 到 TypeScript:打造轉盤抽獎元件

2026年3月9日

最新文章
全部 →
前端 CI/CD 與正式環境除錯:從 Pull Request 到事故排查
2026-06-24
即時資料怎麼選?Polling、SSE、WebSocket 比較
2026-06-23
前端系統設計:如何拆元件、資料流與大型專案架構?
2026-06-22
無障礙不是加 ARIA:語意化 HTML、鍵盤操作與焦點管理
2026-06-21
CSS 與 RWD 面試整理:Flexbox、Grid、定位與層疊脈絡
2026-06-19
← 返回文章列表

用 Vercel AI SDK v6 在 Next.js 加上 AI 聊天助手(免費用 Groq)

2026年3月20日·約 7 分鐘閱讀·
Next.jsVercel AI SDKGroqReactTypeScript

最近幫這個部落格加了一個 AI 聊天助手,用右下角的浮動按鈕呈現,點擊就能展開聊天視窗。 這篇文章記錄整個實作過程,包含選擇 Groq 的原因、Vercel AI SDK 在整個架構中扮演什麼角色,以及實作細節。

套件安裝

npm install ai @ai-sdk/groq @ai-sdk/react

這三個套件各自的職責:

套件版本用途
aiv6核心:streamText、型別定義、server utilities
@ai-sdk/groq—Groq provider(包裝 llama-3.3-70b-versatile 等模型)
@ai-sdk/reactv3React hooks:useChat

注意:ai v6 搭配 @ai-sdk/react v3,這兩個版本號是獨立的,不要混淆。


Vercel AI SDK 是什麼

簡單說,它是一個整合各家 AI 模型平台的統一工具層。

Groq、OpenAI、Anthropic 各自都有 REST API,但格式和細節不太一樣。如果直接用 fetch 呼叫,除了要自己對接各家格式,還要處理 streaming 回應的解析邏輯:

// 直接呼叫 API 的話,stream 這段要自己處理
const res = await fetch("https://api.groq.com/openai/v1/chat/completions", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.GROQ_API_KEY}`, ... },
  body: JSON.stringify({ model: "llama-3.3-70b-versatile", messages, stream: true }),
});
// 之後要自己逐行解析 SSE、去掉 `data:` 前綴、偵測 `[DONE]` 結束...

用了 SDK 之後,同樣的事情變成:

const result = streamText({ model: groq("llama-3.3-70b-versatile"), messages });
return result.toUIMessageStreamResponse();

SDK 在背後處理掉的事情:

  • Server 端:stream 解析、錯誤處理、正確的 response headers
  • Client 端:useChat hook 負責管理整個對話狀態,包含訊息列表和 loading 狀態
  • 統一介面:Groq、OpenAI、Anthropic 都用同一套寫法呼叫,想換平台只要換 model: openai("gpt-4o") 這一行,其他程式碼完全不動

為什麼選 Groq(免費)

Groq 對個人開發者非常友好:

  • 完全免費:每天有 rate limit(每分鐘幾百個請求),對 demo 和學習完全夠用
  • 不需要信用卡:用 GitHub 或 Google 帳號登入就能拿到 API Key
  • 速度極快:Groq 用自研的 LPU 硬體,inference 速度比 OpenAI 快很多

取得 Groq API Key

前往 console.groq.com,用 GitHub 或 Google 帳號登入(不需要信用卡):

Groq 首頁

登入後,點擊右上方的 API Keys:

API Keys 頁面

點擊 Create API Key,幫這把 key 取一個識別用的名稱後送出:

建立 API Key

Key 只會顯示這一次,記得馬上複製起來,它是 gsk_ 開頭的字串。

設定環境變數

拿到 key 之後,在專案根目錄建立 .env.local 並貼進去:

GROQ_API_KEY=gsk_你的key

.env.local 預設被 git 忽略,key 不會被 push 出去。createGroq() 會自動讀取這個環境變數,不需要手動傳入。


API Route:Server-side Streaming

在 app/api/chat/route.ts 建立 POST endpoint:

import { createGroq } from "@ai-sdk/groq";
import { streamText, convertToModelMessages } from "ai";
 
const groq = createGroq();
 
export const maxDuration = 30;
 
export async function POST(req: Request) {
  const { messages } = await req.json();
 
  const result = streamText({
    model: groq("llama-3.3-70b-versatile"),
    system: `你是這個技術部落格的 AI 助手。
請用繁體中文回答,語氣友善、回答簡潔。`,
    messages: await convertToModelMessages(messages),
  });
 
  return result.toUIMessageStreamResponse();
}

幾個要注意的地方:

await convertToModelMessages(messages):前端送來的訊息格式跟 AI model 接受的格式不同,這個函式負責轉換,而且是非同步的,要記得加 await。

toUIMessageStreamResponse():把結果包成 streaming Response 回傳給前端,讓回應可以邊生成邊傳、而不是等全部完成才送出。


React Component:useChat Hook

useChat 是 @ai-sdk/react 提供的 hook,專門用來處理 AI 聊天的 client 端狀態:它幫你維護訊息列表、追蹤目前是 loading 還是 streaming、接收 server 回傳的 stream chunks 並即時把內容更新到畫面上。

建立 components/AiChatBox.tsx:

"use client";
 
import { useChat } from "@ai-sdk/react";
import { isTextUIPart } from "ai";
import { useEffect, useRef, useState } from "react";
 
export default function AiChatBox() {
  const { messages, sendMessage, status, stop, error } = useChat();
 
  const [input, setInput] = useState("");
  const isLoading = status === "submitted" || status === "streaming";
  const bottomRef = useRef<HTMLDivElement>(null);
 
  useEffect(() => {
    bottomRef.current?.scrollIntoView({ behavior: "smooth" });
  }, [messages]);
 
  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (!input.trim() || isLoading) return;
    sendMessage({ text: input });
    setInput("");
  };
 
  return (
    <div className="flex flex-col h-[480px] ...">
      {/* 訊息列表 */}
      <div className="flex-1 overflow-y-auto p-4 space-y-4">
        {messages.map((m) => {
          const text = m.parts
            .filter(isTextUIPart)
            .map((p) => p.text)
            .join("");
 
          return (
            <div key={m.id} className={`flex ${m.role === "user" ? "justify-end" : "justify-start"}`}>
              <div className="...">{text}</div>
            </div>
          );
        })}
 
        {/* 等待動畫 */}
        {status === "submitted" && <LoadingDots />}
 
        <div ref={bottomRef} />
      </div>
 
      {/* 輸入區 */}
      <form onSubmit={handleSubmit} className="...">
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          disabled={isLoading}
        />
        {isLoading ? (
          <button type="button" onClick={stop}>停止</button>
        ) : (
          <button type="submit" disabled={!input.trim()}>送出</button>
        )}
      </form>
    </div>
  );
}

訊息格式:UIMessage

useChat 的 messages 是 UIMessage[],每則訊息有 parts 陣列:

{
  id: "abc",
  role: "assistant",
  parts: [
    { type: "text", text: "你好,有什麼可以幫忙的?" }
  ]
}

parts 是陣列設計,是因為同一則訊息可以包含不只文字,未來也能放圖片、工具呼叫、推理過程等。目前純聊天的情境只需要取出 type: "text" 的部分,isTextUIPart 就是專門用來過濾它的 type guard。


ChatStatus 的四個狀態

useChat 的 status 可以是這四種之一:

ready → submitted → streaming → ready
                 ↓
               error
  • ready:閒置,可以送出新訊息
  • submitted:已送出,等待 server 開始回應(這時顯示 loading dots)
  • streaming:server 正在回傳 stream chunks(訊息在即時增長)
  • error:發生錯誤

我定義 isLoading = status === "submitted" || status === "streaming",這個條件下 input 停用、按鈕換成「停止」。


部署到 Vercel

.env.local 不會被 push,deploy 之前要在 Vercel 補上環境變數:進到專案的 Settings → Environment Variables,新增 GROQ_API_KEY 並填入 key,Production 和 Preview 都勾起來,儲存後 Redeploy 一次。

Vercel 環境變數設定


小結

整個功能的資料流:

使用者輸入
    ↓
sendMessage({ text })          ← @ai-sdk/react
    ↓
POST /api/chat                 ← 帶著 UIMessage[]
    ↓
convertToModelMessages()       ← UIMessage → ModelMessage(await!)
    ↓
streamText({ model, messages }) ← ai
    ↓
toUIMessageStreamResponse()    ← 開始 stream 回傳
    ↓
useChat 接收 chunks,更新 messages
    ↓
m.parts.filter(isTextUIPart)   ← 取出文字顯示

整體來說 Vercel AI SDK 把最繁瑣的部分都藏起來了,讓你只需要關心三件事:選哪個模型、給什麼 system prompt、UI 怎麼呈現。 實際效果可以直接用右下角的 🤖 按鈕試試看。

分享:XLinkedIn
← 上一篇個人 Blog 需要 GitHub Actions CI 嗎?我試了之後的結論
下一篇 →Harness Engineering:AI Agent 的系統設計