143 lines
4.1 KiB
TypeScript
143 lines
4.1 KiB
TypeScript
|
|
/**
|
|||
|
|
* 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<string> {
|
|||
|
|
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<string> {
|
|||
|
|
// 如果選擇的模型是 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<string> {
|
|||
|
|
// 如果選擇的模型是 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<string> {
|
|||
|
|
const prompt = typeof input === 'string' ? input : JSON.stringify(input)
|
|||
|
|
return this.callAPI(prompt)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成剪輯建議
|
|||
|
|
*/
|
|||
|
|
async generateEditSuggestion(input: unknown): Promise<string> {
|
|||
|
|
const prompt = typeof input === 'string' ? input : JSON.stringify(input)
|
|||
|
|
return this.callAPI(prompt)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|