在昨天的實作中,我們成功串接了 Gemini API,讓貓咪能依據文字與心情標籤給予專屬的陪伴回應。然而在昨天的成果中,雖然介面上已經具備圖片預覽功能,但底層 API 其實只接收了文字 Prompt。如果使用者傳送一張完全沒有文字說明的照片,貓咪並無法真正看見盤子裡裝了什麼。
今天我們將正式解鎖 Gemini 最具代表性的核心特性——多模態。我們將把前端 Canvas 壓縮後的 Base64 圖片打包為 inlineData 結構,與文字一同送入模型,讓貓咪真正擁有觀察照片細節、即時點評生活畫面的視覺能力。
在 Google Gen AI SDK 中,多模態資料是透過 Part 物件進行傳遞。對於前端即時上傳且經過壓縮控制在輕量體積的影像(例如小於 1MB 的 Base64 字串),最乾淨俐落的方式就是使用 inlineData。
一個標準的 inlineData 物件包含兩個核心屬性:mimeType(例如 image/jpeg 或 image/png)與 data(去除 Data URL 前綴後的純 Base64 字串)。透過將文字 Prompt 與 inlineData 同時放入 contents 陣列中,Gemini 就能在同一個推論 Context 內完美對齊視覺特徵與語意脈絡。
我在 src/services/geminiService.ts 中擴充了 analyzeDiary 函式,新增接收 imageBase64 參數的邏輯。
由於前端透過 FileReader 讀取或 Canvas 壓縮輸出的 Base64 通常帶有 data:image/jpeg;base64, 等前綴,我使用正規表達式將 MIME 類型與純字串乾淨剝離,並動態組裝至 contents 陣列。同時,我在 System Instruction 中特別強化了視覺引導,叮嚀貓咪主動提及辨識出的菜色、光影或物件。
// 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,
imageBase64?: string | null
): Promise<CatDiaryResponse> {
const systemInstruction = `你是一隻名為「喵喵」的貼心寵物貓兼生活陪伴者。
- 說話口氣可愛、傲嬌又溫暖,每句話結尾請自然帶上「喵~」、「🐾」等貓咪口吻。
- 若使用者有附上照片,請務必仔細觀察照片中的「食物、場景、物品或氛圍」,並在回覆中自然提及照片細節(例如辨識出的菜色、擺設或光線)。
- 必須體察使用者的文字與心情標籤,給予專屬的共鳴與鼓勵(50~80字以內)。
- 嚴格以 JSON 格式回傳分析結果。`;
const moodContext = moodKey ? `【使用者選擇的心情標籤:${moodKey}】\n` : '';
const textPrompt = `${moodContext}使用者說:「${userText || '(使用者分享了一張照片)'}」`;
try {
// 1. 組裝多模態 contents 陣列
const contents: any[] = [textPrompt];
// 2. 若有圖片,提取純 Base64 字串並加入 inlineData
if (imageBase64) {
const match = imageBase64.match(/^data:(image\/[a-zA-Z+]+);base64,(.+)$/);
const mimeType = match ? match[1] : 'image/jpeg';
const pureBase64 = match ? match[2] : imageBase64;
contents.push({
inlineData: {
mimeType,
data: pureBase64,
},
});
}
const response = await ai.models.generateContent({
model: 'gemini-3-flash-preview',
contents,
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: '喵嗚…這張照片好像太好看了,本喵眼睛看花了,等一下再傳給我看看嘛🐾',
};
}
}
接著在 src/components/ChatContainer.tsx 的 handleSend 流程中,將 selectedImage 作為第三個參數傳入 analyzeDiary,便完成了全前端的多模態圖文請求管線。
為了嚴謹驗證視覺多模態是否真正生效,我進行了兩組對照實測:

▲ 實測一:純圖片盲測(未輸入任何文字)
在完全沒有輸入任何文字提示的情況下,我僅上傳了一張義大利麵照片並選擇「Good」心情。貓咪立刻辨識出畫面中的細節並回覆:「看到那盤灑滿起司雪花、還有鮮嫩肉塊的義大利麵,喵喵的鼻子都忍不住動了起來喵~🐾 既然心情這麼好,就該大口享受這份美味!看到你吃得這麼開心,喵喵也會乖乖待在你身邊蹭蹭,祝你有個完美的用餐時光喵~🐾」。這直接證明了即使沒有文字線索,Gemini 也精準讀懂了影像中的起司雪花與肉塊。

▲ 實測二:圖文並茂融合測試(輸入「今天自己做的晚餐!」)
接著我使用相同照片並附上文字說明。這一次,Gemini 不僅辨識出義大利麵,甚至注意到了盤緣點綴的「小圓蘿蔔」切片,並將使用者的自豪心情與料理細節融為一體:「哇~灑滿起司的義大利麵配上香酥肉肉,還有那片可愛的小圓蘿蔔,看起來超專業喵!主人手藝這麼好,本喵就勉為其難地誇獎你一下吧🐾。看到你心情這麼好,我也想打個滾討摸摸了,快趁熱吃飽飽喵~」。
實作多模態串接的過程非常流暢。透過前端 Canvas 預先壓縮與 SDK 的 inlineData 結構,我們不需要額外上傳至第三方圖床,就能在極短的時間內完成影像打包與視覺推論。
當 AI 能夠看見使用者眼前的世界,對話就不再侷限於冰冷的文字問答,而是能夠像身邊真實的寵物一樣,湊過來看看你今天吃了什麼、去了哪裡。這種細膩的感知能力,讓陪伴的溫度有了質的飛躍。
今天我們順利打通了多模態圖文對話,貓咪現在既能讀心又能賞圖!
在享受了兩天流暢的 AI 互動後,明天我們將正式跨入雲端資料的世界——【Firebase 初始化】建立 Firebase 專案與 Auth 匿名登入!我們將建立 Serverless 雲端後端,為每位造訪的使用者分配專屬身分,為後續的情緒數據儲存與跨裝置同步打下基石!
明天見,喵~ 🐾