/** * Grok Client * 與 Gemini 相同介面 * 預設不啟用 */ import type { AIProvider } from '~/types/ai' import { getGrokToken, getTextModel, getVisionModel } from '~/utils/storage' import { DEFAULT_GROK_TEXT_MODEL, DEFAULT_GROK_VISION_MODEL, getGrokModelForTask } from './grok-models' const GROK_API_BASE = 'https://api.x.ai/v1' /** * Grok API Client */ export class GrokClient implements AIProvider { /** * 取得 API Token */ private getToken(): string { const token = getGrokToken() if (!token) { throw new Error('Grok API Token 未設定,請至設定頁面輸入') } return token } /** * 呼叫 Grok API * @param prompt - 文字提示 * @param model - 模型名稱,預設使用 grok-2 * @param parts - 多模態內容(圖像等) */ private async callAPI( prompt: string, model: string = DEFAULT_GROK_TEXT_MODEL, parts?: Array<{ text?: string; image_url?: { url: string } }> ): Promise { const token = this.getToken() const url = `${GROK_API_BASE}/chat/completions` // 組裝 messages const messages: Array<{ role: string; content: Array<{ type: string; text?: string; image_url?: { url: string } }> }> = [] const contentParts: Array<{ type: string; text?: string; image_url?: { url: string } }> = [] if (prompt) { contentParts.push({ type: 'text', text: prompt }) } if (parts) { parts.forEach(part => { if (part.image_url) { contentParts.push({ type: 'image_url', image_url: part.image_url }) } }) } messages.push({ role: 'user', content: contentParts }) const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ model, messages }) }) 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.choices?.[0]?.message?.content || '' } /** * 生成分鏡表 */ async generateStoryboard(input: unknown): Promise { // 如果選擇的模型是 Grok 模型,使用選擇的模型;否則使用預設 const selectedModel = getTextModel() const model = selectedModel && selectedModel.includes('grok') ? selectedModel : getGrokModelForTask('text') const prompt = typeof input === 'string' ? input : JSON.stringify(input) return this.callAPI(prompt, model) } /** * 分析攝影機鏡位與運鏡 * 支援圖像輸入 */ async analyzeCamera(input: unknown): Promise { // 如果選擇的模型是 Grok 模型,使用選擇的模型;否則使用預設 const selectedModel = getVisionModel() const model = selectedModel && selectedModel.includes('grok') ? selectedModel : getGrokModelForTask('vision') // 如果 input 是物件且包含圖像 if (typeof input === 'object' && input !== null) { const inputObj = input as { prompt?: string; image?: { url: string } } const prompt = inputObj.prompt || '請分析這張圖片的攝影機鏡位與運鏡建議' const parts: Array<{ text?: string; image_url?: { url: string } }> = [] if (inputObj.image) { parts.push({ image_url: 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) } }