import axios from 'axios' const API_BASE_URL = '/api' export interface Page { index: number type: 'cover' | 'content' | 'summary' content: string } export interface OutlineResponse { success: boolean outline?: string pages?: Page[] error?: string } export interface ProgressEvent { index: number status: 'generating' | 'done' | 'error' current?: number total?: number image_url?: string message?: string } export interface FinishEvent { success: boolean task_id: string images: string[] } // 生成大纲(支持图片上传) export async function generateOutline( topic: string, images?: File[] ): Promise { // 如果有图片,使用 FormData if (images && images.length > 0) { const formData = new FormData() formData.append('topic', topic) images.forEach((file) => { formData.append('images', file) }) const response = await axios.post( `${API_BASE_URL}/outline`, formData, { headers: { 'Content-Type': 'multipart/form-data' } } ) return response.data } // 无图片,使用 JSON const response = await axios.post(`${API_BASE_URL}/outline`, { topic }) return response.data } // 获取图片 URL(新格式:task_id/filename) // thumbnail 参数:true=缩略图(默认),false=原图 export function getImageUrl(taskId: string, filename: string, thumbnail: boolean = true): string { const thumbParam = thumbnail ? '?thumbnail=true' : '?thumbnail=false' return `${API_BASE_URL}/images/${taskId}/${filename}${thumbParam}` } // 重新生成图片(即使成功的也可以重新生成) export async function regenerateImage( taskId: string, page: Page, useReference: boolean = true, context?: { fullOutline?: string userTopic?: string } ): Promise<{ success: boolean; index: number; image_url?: string; error?: string }> { const response = await axios.post(`${API_BASE_URL}/regenerate`, { task_id: taskId, page, use_reference: useReference, full_outline: context?.fullOutline, user_topic: context?.userTopic }) return response.data } // 批量重试失败的图片(SSE) export async function retryFailedImages( taskId: string, pages: Page[], onProgress: (event: ProgressEvent) => void, onComplete: (event: ProgressEvent) => void, onError: (event: ProgressEvent) => void, onFinish: (event: { success: boolean; total: number; completed: number; failed: number }) => void, onStreamError: (error: Error) => void ) { try { const response = await fetch(`${API_BASE_URL}/retry-failed`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ task_id: taskId, pages }) }) if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) } const reader = response.body?.getReader() if (!reader) { throw new Error('无法读取响应流') } const decoder = new TextDecoder() let buffer = '' while (true) { const { done, value } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }) const lines = buffer.split('\n\n') buffer = lines.pop() || '' for (const line of lines) { if (!line.trim()) continue const [eventLine, dataLine] = line.split('\n') if (!eventLine || !dataLine) continue const eventType = eventLine.replace('event: ', '').trim() const eventData = dataLine.replace('data: ', '').trim() try { const data = JSON.parse(eventData) switch (eventType) { case 'retry_start': onProgress({ index: -1, status: 'generating', message: data.message }) break case 'complete': onComplete(data) break case 'error': onError(data) break case 'retry_finish': onFinish(data) break } } catch (e) { console.error('解析 SSE 数据失败:', e) } } } } catch (error) { onStreamError(error as Error) } } // ==================== 历史记录相关 API ==================== export interface HistoryRecord { id: string title: string created_at: string updated_at: string status: string thumbnail: string | null page_count: number task_id: string | null } export interface HistoryDetail { id: string title: string created_at: string updated_at: string outline: { raw: string pages: Page[] } images: { task_id: string | null generated: string[] } status: string thumbnail: string | null } // 创建历史记录 export async function createHistory( topic: string, outline: { raw: string; pages: Page[] }, taskId?: string ): Promise<{ success: boolean; record_id?: string; error?: string }> { const response = await axios.post(`${API_BASE_URL}/history`, { topic, outline, task_id: taskId }) return response.data } // 获取历史记录列表 export async function getHistoryList( page: number = 1, pageSize: number = 20, status?: string ): Promise<{ success: boolean records: HistoryRecord[] total: number page: number page_size: number total_pages: number }> { const params: any = { page, page_size: pageSize } if (status) params.status = status const response = await axios.get(`${API_BASE_URL}/history`, { params }) return response.data } // 获取历史记录详情 export async function getHistory(recordId: string): Promise<{ success: boolean record?: HistoryDetail error?: string }> { const response = await axios.get(`${API_BASE_URL}/history/${recordId}`) return response.data } // 更新历史记录 export async function updateHistory( recordId: string, data: { outline?: { raw: string; pages: Page[] } images?: { task_id: string | null; generated: string[] } status?: string thumbnail?: string } ): Promise<{ success: boolean; error?: string }> { const response = await axios.put(`${API_BASE_URL}/history/${recordId}`, data) return response.data } // 删除历史记录 export async function deleteHistory(recordId: string): Promise<{ success: boolean error?: string }> { const response = await axios.delete(`${API_BASE_URL}/history/${recordId}`) return response.data } // 搜索历史记录 export async function searchHistory(keyword: string): Promise<{ success: boolean records: HistoryRecord[] }> { const response = await axios.get(`${API_BASE_URL}/history/search`, { params: { keyword } }) return response.data } // 获取统计信息 export async function getHistoryStats(): Promise<{ success: boolean total: number by_status: Record }> { const response = await axios.get(`${API_BASE_URL}/history/stats`) return response.data } // 使用 POST 方式生成图片(更可靠) export async function generateImagesPost( pages: Page[], taskId: string | null, fullOutline: string, onProgress: (event: ProgressEvent) => void, onComplete: (event: ProgressEvent) => void, onError: (event: ProgressEvent) => void, onFinish: (event: FinishEvent) => void, onStreamError: (error: Error) => void, userImages?: File[], userTopic?: string ) { try { // 将用户图片转换为 base64 let userImagesBase64: string[] = [] if (userImages && userImages.length > 0) { userImagesBase64 = await Promise.all( userImages.map(file => { return new Promise((resolve, reject) => { const reader = new FileReader() reader.onload = () => resolve(reader.result as string) reader.onerror = reject reader.readAsDataURL(file) }) }) ) } const response = await fetch(`${API_BASE_URL}/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ pages, task_id: taskId, full_outline: fullOutline, user_images: userImagesBase64.length > 0 ? userImagesBase64 : undefined, user_topic: userTopic || '' }) }) if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) } const reader = response.body?.getReader() if (!reader) { throw new Error('无法读取响应流') } const decoder = new TextDecoder() let buffer = '' while (true) { const { done, value } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }) const lines = buffer.split('\n\n') buffer = lines.pop() || '' for (const line of lines) { if (!line.trim()) continue const [eventLine, dataLine] = line.split('\n') if (!eventLine || !dataLine) continue const eventType = eventLine.replace('event: ', '').trim() const eventData = dataLine.replace('data: ', '').trim() try { const data = JSON.parse(eventData) switch (eventType) { case 'progress': onProgress(data) break case 'complete': onComplete(data) break case 'error': onError(data) break case 'finish': onFinish(data) break } } catch (e) { console.error('解析 SSE 数据失败:', e) } } } } catch (error) { onStreamError(error as Error) } } // 扫描所有任务并同步图片列表 export async function scanAllTasks(): Promise<{ success: boolean total_tasks?: number synced?: number failed?: number orphan_tasks?: string[] results?: any[] error?: string }> { const response = await axios.post(`${API_BASE_URL}/history/scan-all`) return response.data } // ==================== 配置管理 API ==================== export interface Config { text_generation: { active_provider: string providers: Record } image_generation: { active_provider: string providers: Record } } // 获取配置 export async function getConfig(): Promise<{ success: boolean config?: Config error?: string }> { const response = await axios.get(`${API_BASE_URL}/config`) return response.data } // 更新配置 export async function updateConfig(config: Partial): Promise<{ success: boolean message?: string error?: string }> { const response = await axios.post(`${API_BASE_URL}/config`, config) return response.data } // 测试服务商连接 export async function testConnection(config: { type: string provider_name?: string api_key?: string base_url?: string model: string }): Promise<{ success: boolean message?: string error?: string }> { const response = await axios.post(`${API_BASE_URL}/config/test`, config) return response.data }