dramaling-vocab-learning/frontend/lib/services/imageGeneration.ts

171 lines
4.4 KiB
TypeScript

// Image Generation API service
import { BASE_URL } from '@/lib/config/api'
export interface ImageGenerationRequest {
style: 'cartoon' | 'realistic' | 'minimal'
priority: 'normal' | 'high' | 'low'
width: number
height: number
replicateModel: string
options: {
useGeminiCache: boolean
useImageCache: boolean
maxRetries: number
learnerLevel: string
scenario: string
visualPreferences: string[]
}
}
export interface GenerationStatus {
requestId: string
overallStatus: string
currentStage?: string
stages: {
gemini: {
status: string
startedAt?: string
completedAt?: string
processingTimeMs?: number
cost?: number
generatedDescription?: string
}
replicate: {
status: string
startedAt?: string
completedAt?: string
processingTimeMs?: number
cost?: number
model?: string
modelVersion?: string
progress?: string
}
}
totalCost?: number
completedAt?: string
result?: {
imageUrl: string
imageId: string
qualityScore?: number
dimensions?: {
width: number
height: number
}
fileSize?: number
}
}
export interface ApiResponse<T> {
success: boolean
data?: T
error?: string
details?: string
}
class ImageGenerationService {
private baseUrl = BASE_URL
private async makeRequest<T>(url: string, options: RequestInit = {}): Promise<ApiResponse<T>> {
const token = localStorage.getItem('auth_token')
const response = await fetch(`${this.baseUrl}${url}`, {
headers: {
'Content-Type': 'application/json',
'Authorization': token ? `Bearer ${token}` : '',
...options.headers,
},
...options,
})
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: 'Network error' }))
throw new Error(errorData.error || errorData.details || `HTTP ${response.status}`)
}
return response.json()
}
// 啟動圖片生成
async generateImage(flashcardId: string, request?: Partial<ImageGenerationRequest>): Promise<ApiResponse<{ requestId: string }>> {
const defaultRequest: ImageGenerationRequest = {
style: 'cartoon',
priority: 'normal',
width: 512,
height: 512,
replicateModel: 'ideogram-v2a-turbo',
options: {
useGeminiCache: true,
useImageCache: true,
maxRetries: 3,
learnerLevel: 'B1',
scenario: 'daily',
visualPreferences: ['colorful', 'simple']
},
...request
}
return this.makeRequest(`/api/ImageGeneration/flashcards/${flashcardId}/generate`, {
method: 'POST',
body: JSON.stringify(defaultRequest)
})
}
// 查詢生成狀態
async getGenerationStatus(requestId: string): Promise<ApiResponse<GenerationStatus>> {
return this.makeRequest(`/api/ImageGeneration/requests/${requestId}/status`)
}
// 取消生成
async cancelGeneration(requestId: string): Promise<ApiResponse<{ message: string }>> {
return this.makeRequest(`/api/ImageGeneration/requests/${requestId}/cancel`, {
method: 'POST'
})
}
// 輪詢直到完成
async pollUntilComplete(
requestId: string,
onProgress?: (status: GenerationStatus) => void,
timeoutMinutes = 5
): Promise<GenerationStatus> {
const startTime = Date.now()
const timeout = timeoutMinutes * 60 * 1000
while (Date.now() - startTime < timeout) {
try {
const result = await this.getGenerationStatus(requestId)
if (!result.success || !result.data) {
throw new Error(result.error || 'Failed to get status')
}
const status = result.data
// 呼叫進度回調
if (onProgress) {
onProgress(status)
}
// 檢查是否完成
if (status.overallStatus === 'completed') {
return status
}
// 檢查是否失敗
if (status.overallStatus === 'failed') {
throw new Error('圖片生成失敗')
}
// 等待 2 秒後再次檢查
await new Promise(resolve => setTimeout(resolve, 2000))
} catch (error) {
console.error('輪詢狀態時發生錯誤:', error)
throw error
}
}
throw new Error('圖片生成超時')
}
}
export const imageGenerationService = new ImageGenerationService()