170 lines
4.4 KiB
TypeScript
170 lines
4.4 KiB
TypeScript
// Image Generation API service
|
|
|
|
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 = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5008'
|
|
|
|
private async makeRequest<T>(url: string, options: RequestInit = {}): Promise<ApiResponse<T>> {
|
|
const token = localStorage.getItem('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() |