/** * Gemini Client * 單一責任:呼叫 Gemini API * 從 localStorage 讀取 token */ import type { AIProvider } from '~/types/ai' import { getGeminiToken, getTextModel, getVisionModel } from '~/utils/storage' import { DEFAULT_TEXT_MODEL, DEFAULT_VISION_MODEL, getModelForTask } from './gemini-models' const GEMINI_API_BASE = 'https://generativelanguage.googleapis.com/v1beta' /** * Gemini API Client */ export class GeminiClient implements AIProvider { private token: string | null constructor() { this.token = getGeminiToken() } /** * 取得 API Token */ private getToken(): string { const token = getGeminiToken() if (!token) { throw new Error('Gemini API Token 未設定,請至設定頁面輸入') } return token } /** * 呼叫 Gemini API * @param prompt - 文字提示 * @param model - 模型名稱,預設使用儲存的模型或 DEFAULT_TEXT_MODEL * @param parts - 多模態內容(圖像、音頻等) */ private async callAPI( prompt: string, model?: string, parts?: Array<{ text?: string; inlineData?: { mimeType: string; data: string } }> ): Promise { // 如果沒有指定模型,使用儲存的模型或預設模型 const selectedModel = model || getTextModel() || DEFAULT_TEXT_MODEL const token = this.getToken() const url = `${GEMINI_API_BASE}/models/${selectedModel}:generateContent?key=${token}` // 組裝 parts const contentParts: Array<{ text?: string; inlineData?: { mimeType: string; data: string } }> = [] if (prompt) { contentParts.push({ text: prompt }) } if (parts) { contentParts.push(...parts) } const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contents: [{ parts: contentParts }] }) }) if (!response.ok) { const error = await response.json().catch(() => ({ error: { message: 'Unknown error' } })) throw new Error(error.error?.message || `API 錯誤: ${response.status}`) } const data = await response.json() return data.candidates?.[0]?.content?.parts?.[0]?.text || '' } /** * 生成分鏡表 */ async generateStoryboard(input: unknown): Promise { // Prompt 組裝將由 utils/ai 處理,這裡只負責呼叫 API const prompt = typeof input === 'string' ? input : JSON.stringify(input) return this.callAPI(prompt) } /** * 分析攝影機鏡位與運鏡 * 支援圖像輸入(base64 或 URL) */ async analyzeCamera(input: unknown): Promise { const model = getVisionModel() || DEFAULT_VISION_MODEL // 如果 input 是物件且包含圖像 if (typeof input === 'object' && input !== null) { const inputObj = input as { prompt?: string; image?: { mimeType: string; data: string } } const prompt = inputObj.prompt || '請分析這張圖片的攝影機鏡位與運鏡建議' const parts: Array<{ text?: string; inlineData?: { mimeType: string; data: string } }> = [] if (inputObj.image) { parts.push({ inlineData: inputObj.image }) } return this.callAPI(prompt, model, parts) } const prompt = typeof input === 'string' ? input : JSON.stringify(input) return this.callAPI(prompt, model) } /** * 生成影片規劃 */ async generateVideoPlan(input: unknown): Promise { const prompt = typeof input === 'string' ? input : JSON.stringify(input) return this.callAPI(prompt) } /** * 生成剪輯建議 */ async generateEditSuggestion(input: unknown): Promise { const prompt = typeof input === 'string' ? input : JSON.stringify(input) return this.callAPI(prompt) } }