ai-cut/app/composables/useAIStoryboardAnalysis.ts

75 lines
2.3 KiB
TypeScript
Raw Permalink Normal View History

2025-12-16 10:08:51 +00:00
/**
* AI Composable
*
*/
import { useAIProvider } from './useAIProvider'
import { buildStoryAnalysisPrompt, type StoryAnalysisInput } from '~/utils/ai/prompts'
import { getTextModel } from '~/utils/storage'
import { DEFAULT_TEXT_MODEL } from '~/utils/clients/gemini-models'
import { getVisionModel } from '~/utils/storage'
import { DEFAULT_VISION_MODEL } from '~/utils/clients/gemini-models'
import type { StoryAnalysis } from '~/types/storyboard'
export function useAIStoryboardAnalysis() {
const isLoading = ref(false)
const error = ref<string | null>(null)
async function analyze(input: StoryAnalysisInput): Promise<StoryAnalysis> {
isLoading.value = true
error.value = null
try {
// 如果有圖片,使用視覺模型;否則使用文字模型
const modelName = input.styleImage
? (getVisionModel() || DEFAULT_VISION_MODEL)
: (getTextModel() || DEFAULT_TEXT_MODEL)
const provider = useAIProvider(modelName)
let prompt = buildStoryAnalysisPrompt(input)
// 如果有風格圖片,需要傳遞圖片給 API
if (input.styleImage) {
// 這裡需要根據 provider 類型處理圖片
// 暫時先傳遞文字 prompt實際實作時需要處理圖片
const result = await provider.analyzeCamera({
prompt,
image: {
mimeType: 'image/jpeg',
data: input.styleImage.replace(/^data:image\/\w+;base64,/, '')
}
})
// 解析 JSON 結果
const jsonMatch = result.match(/\{[\s\S]*\}/)
if (jsonMatch) {
return JSON.parse(jsonMatch[0]) as StoryAnalysis
}
throw new Error('無法解析分析結果')
} else {
const result = await provider.generateStoryboard(prompt)
// 解析 JSON 結果
const jsonMatch = result.match(/\{[\s\S]*\}/)
if (jsonMatch) {
return JSON.parse(jsonMatch[0]) as StoryAnalysis
}
throw new Error('無法解析分析結果')
}
} catch (err) {
const errorMessage = err instanceof Error ? err.message : '未知錯誤'
error.value = errorMessage
throw err
} finally {
isLoading.value = false
}
}
return {
analyze,
isLoading: readonly(isLoading),
error: readonly(error)
}
}