在昨天的半程小結中,我們完整檢視了《喵語日誌》的前端畫面與互動元件。雖然聊天室能收發文字與圖片,但先前為了驗證介面流程,送出訊息後都是透過定時器吐出寫死的罐頭字串,貓咪並無法真正理解使用者的喜怒哀樂。
在第 6 天時,我們曾在初版專案中成功驗證了 Gemini API 的結構化輸出概念。而今天我們的核心任務,就是將這顆強大的 AI 核心正式移植並整合進全新的聊天室模組中。我們將透過 Google 官方最新的 @google/genai SDK 與 gemini-3-flash-preview 模型,讓貓咪根據使用者的即時心情與文字,給予專屬、溫暖且充滿靈性的動態回覆。
要讓 AI 能針對當下的聊天情境做出共鳴,我們需要將使用者點選的 5 種心情標籤(Rough、Low、Okay、Good、Great)化為 Prompt 的一部分。
我們在 src/services/geminiService.ts 中封裝了 analyzeDiary 函式。除了設定溫柔貼心的貓咪陪伴者 System Instruction 之外,更要求模型必須嚴格遵循 responseSchema 規範,輸出包含心情指數(mood_score)、生活場景標籤(lifestyle_label)以及陪伴文字(cat_response)的結構化 JSON。
// src/services/geminiService.ts
import { GoogleGenAI, Type } from '@google/genai';
import type { CatDiaryResponse } from '../types/diary';
const apiKey = import.meta.env.VITE_GEMINI_API_KEY;
const ai = new GoogleGenAI({ apiKey: apiKey || '' });
export async function analyzeDiary(
userText: string,
moodKey?: string | null
): Promise<CatDiaryResponse> {
const systemInstruction = `你是一隻名為「喵喵」的貼心寵物貓兼生活陪伴者。
- 說話口氣可愛、傲嬌又溫暖,每句話結尾請自然帶上「喵~」、「🐾」等貓咪口吻。
- 必須仔細體察使用者的文字與當前心情標籤,給予專屬的共鳴與鼓勵(50~80字以內)。
- 嚴格以 JSON 格式回傳分析結果。`;
const moodContext = moodKey ? `【使用者選擇的心情標籤:${moodKey}】\n` : '';
const prompt = `${moodContext}使用者說:「${userText}」`;
try {
const response = await ai.models.generateContent({
model: 'gemini-3-flash-preview',
contents: prompt,
config: {
systemInstruction,
responseMimeType: 'application/json',
responseSchema: {
type: Type.OBJECT,
properties: {
mood_score: {
type: Type.INTEGER,
description: '1 到 10 的情緒指數',
},
lifestyle_label: {
type: Type.STRING,
description: '生活場景標籤,例如:美食日常、深夜加班、悠閒午後',
},
cat_response: {
type: Type.STRING,
description: '帶有貓咪動作描述與溫暖語氣的回應',
},
},
required: ['mood_score', 'lifestyle_label', 'cat_response'],
},
},
});
const jsonText = response.text;
if (!jsonText) throw new Error('未取得 Gemini 回應');
return JSON.parse(jsonText) as CatDiaryResponse;
} catch (error) {
console.error('Gemini API 呼叫失敗:', error);
return {
mood_score: 5,
lifestyle_label: '日常陪伴',
cat_response: '喵嗚…連線好像有點不穩,本喵先去抓個沙發,等一下再跟我說一次好嗎?🐾',
};
}
}
透過這種設計,前端既能取得貓咪的口語化回覆,同時還能獲取標準化的生活標籤與情緒分數,為後續寫入雲端資料庫做好最完美的準備。
在完成服務端封裝後,我們回到 ChatContainer.tsx 改造送出事件。
當使用者按下發送或 Enter 鍵時,前端會先將文字氣泡推入對話串,並將 isLoading 設為 true 啟動跳動的打字動畫(Typing Indicator)。接著透過非同步 await analyzeDiary() 發送請求,在收到回傳的 JSON 後,精準提取 result.cat_response 並渲染為貓咪回覆氣泡。
// src/components/ChatContainer.tsx 發送邏輯重構
const handleSend = async () => {
const trimmed = input.trim();
if ((!trimmed && !selectedImage) || isLoading) return;
const currentMood = mood;
const userMessage: Message = {
id: Date.now(),
role: "user",
text: trimmed,
image: selectedImage || undefined,
};
// 1. 立即將使用者訊息推入畫面
setMessages((prev) => [...prev, userMessage]);
setInput("");
setSelectedImage(null);
setIsLoading(true);
try {
// 2. 向 Gemini 請求真實即時回覆
const result = await analyzeDiary(trimmed || "(分享了一張照片)", currentMood);
// 3. 渲染貓咪回覆氣泡
const catReply: Message = {
id: Date.now() + 1,
role: "cat",
text: result.cat_response,
};
setMessages((prev) => [...prev, catReply]);
} catch (error) {
console.error(error);
} finally {
setIsLoading(false);
}
};
為了驗證模型在不同情緒與生活場景下的回覆表現,我們進行了兩組對照測試:

▲ 情境一:開心美食分享(心情 Great)
在選擇「Great」並傳送「今天吃了好吃的義大利麵~」時,貓咪回傳了傲嬌的回應:「看到你吃得這麼開心,喵喵也跟著高興起來了喔!雖然我不能吃義大利麵,但看著你滿足的樣子,就勉為其難讓你多摸兩下吧。既然心情這麼好,下次也要記得買罐罐犒賞我喵~🐾」。

▲ 情境二:疲憊家務日常(心情 Low)
在選擇「Low」並傳送「今天在家大掃除,好累喔...」時,貓咪則給予溫柔安撫:「哎呀,看你忙得滿頭大汗,家裡確實變乾淨了,但也要照顧好自己嘛!快過來沙發坐下,喵喵勉為其難讓你摸摸肚皮消消疲勞喔。辛苦了,剩下的明天再說吧!喵~🐾」。
兩次回覆不僅精準抓取了生活中的場景細節,更根據心情標籤給予最適切的情感支持,正式宣告靜態假資料退役!
值得一提的是,目前畫面上雖然已能附帶圖片預覽,但 AI 是依據文字與心情標籤進行推理。如果使用者只傳送一張照片而不輸入任何文字,貓咪目前還無法親眼辨識圖片內容,這也為明天的多模態改造留下了絕佳的突破點!
將靜態畫面真正接上生成式 AI 的那一刻,是整個開發過程中成就感最豐沛的瞬間之一。
過去在前端開發中,要做出一個會理解語意的對話系統門檻極高;如今借助 Google Gen AI SDK 與結構化輸出技術,我們不僅能用極少的程式碼賦予 App 靈魂,還能同時兼顧前端型別安全與資料格式的精準度。這種讓介面與智慧模型快速融合的能力,正是現代 Web 全端開發最迷人之處。
今天我們順利告別假資料,完成了文字與心情標籤的即時對話串接!明天我們將更進一步解鎖多模態能力——【多模態串接】實作圖文同時輸入!我們將把前端壓縮後的 Base64 照片直接打包送給 Gemini,讓貓咪不僅能聽懂你說的心事,還能親眼看懂你盤子裡的美食與生活照片!
明天見,喵~ 🐾