// 画布服务层 import type { CanvasProject, CanvasState, CanvasNode } from '@/types/canvas'; import { requestLongTaskResult } from '@/lib/client/long-task'; import type { EnhanceProKreaModel, EnhanceProOutputFormat, EnhanceProSubjectDetection, } from '@/lib/enhance-pro/config'; // API 响应类型 interface ApiResponse { success: boolean; data?: T; error?: string; } export interface NormalizedCropRect { x: number; y: number; width: number; height: number; } export interface CanvasTextDetectItem { id: string; text: string; confidence?: number; } export interface CanvasTextEditPayloadItem { id: string; originalText: string; editedText?: string; action?: 'replace' | 'delete'; } export interface EnhanceProImageResult { originalUrl: string; thumbnailUrl: string; width?: number; height?: number; } export interface EnhanceProRequestParams { imageUrl: string; prompt?: string; chatId?: string | null; messageId?: string | null; width: number; height: number; batchSize?: number; seed?: number; output_format?: EnhanceProOutputFormat; subject_detection?: EnhanceProSubjectDetection; face_enhancement?: boolean; face_enhancement_creativity?: number; face_enhancement_strength?: number; crop_to_fill?: boolean; model?: EnhanceProKreaModel; upscaling_activated?: boolean; image_scaling_factor?: number; creativity?: number; texture?: number; sharpen?: number; denoise?: number; detail?: number; } // 画布服务 export const canvasService = { // 获取画布列表 async getProjects(): Promise> { try { const response = await fetch('/api/canvas'); const data = await response.json(); return data; } catch (error) { return { success: false, error: '获取画布列表失败' }; } }, // 获取单个画布 async getProject(id: string): Promise> { try { const response = await fetch(`/api/canvas/${id}`); const data = await response.json(); return data; } catch (error) { return { success: false, error: '获取画布失败' }; } }, // 创建画布 async createProject(project: Partial): Promise> { try { const response = await fetch('/api/canvas', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(project), }); const data = await response.json(); return data; } catch (error) { return { success: false, error: '创建画布失败' }; } }, // 更新画布 async updateProject(id: string, updates: Partial): Promise> { try { const response = await fetch(`/api/canvas/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updates), }); const data = await response.json(); return data; } catch (error) { return { success: false, error: '更新画布失败' }; } }, // 删除画布 async deleteProject(id: string): Promise { try { const response = await fetch(`/api/canvas/${id}`, { method: 'DELETE', }); const data = await response.json(); return data; } catch (error) { return { success: false, error: '删除画布失败' }; } }, // 保存画布状态 async saveCanvasState(id: string, canvasData: CanvasState): Promise { try { const requestBody = JSON.stringify({ canvasData }); const doSave = async () => fetch(`/api/canvas/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: requestBody, }); let response = await doSave(); if (response.status === 429) { const retryPayload = await response.json().catch(() => ({})); const retryAfter = Number(retryPayload?.retryAfter || 0); const waitMs = retryAfter > 0 ? retryAfter * 1000 : 800; await new Promise((resolve) => setTimeout(resolve, waitMs)); response = await doSave(); } const data = await response.json().catch(() => ({})); if (!response.ok) { return { success: false, error: String(data?.error || data?.message || '保存画布状态失败') }; } return data as ApiResponse; } catch (error) { return { success: false, error: '保存画布状态失败' }; } }, // 自动保存(防抖) createAutoSave(projectId: string, delay = 2000) { let timeoutId: NodeJS.Timeout | null = null; return (canvasData: CanvasState) => { if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(async () => { await this.saveCanvasState(projectId, canvasData); }, delay); }; }, }; // 图片生成服务 export const imageGenerationService = { // 上传图片到存储桶 async uploadImage(file: File): Promise> { try { const formData = new FormData(); formData.append('file', file); const response = await fetch('/api/files/upload', { method: 'POST', body: formData, }); if (!response.ok) { const errorData = await response.json(); return { success: false, error: errorData.error || '上传失败' }; } const data = await response.json(); return { success: true, data: { url: data.url, pathname: data.pathname, assetId: typeof data.assetId === "string" ? data.assetId : undefined, } }; } catch (error) { console.error('[Canvas] 图片上传失败:', error); return { success: false, error: '图片上传失败' }; } }, // AI 生成图片 async generateImage(params: { prompt: string; negativePrompt?: string; model?: string; aspectRatio?: string; imageCount?: number; referenceImage?: string; }): Promise; model?: string; prompt?: string }>> { try { const response = await fetch('/api/canvas/generate/image', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params), }); const data = await response.json(); return data; } catch (error) { return { success: false, error: '生成图片失败' }; } }, // 图片抠图 - 使用图片URL async removeBackground(imageUrl: string): Promise> { try { const response = await fetch('/api/rmbg', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ imageUrl }), // 使用 imageUrl 参数 }); const data = await response.json(); if (data.success && data.original_url) { return { success: true, data: { url: data.original_url } }; } return { success: false, error: data.error || '抠图失败' }; } catch (error) { console.error('[Canvas] 抠图失败:', error); return { success: false, error: '抠图失败' }; } }, async boxCutout(params: { imageUrl: string; cropRect: NormalizedCropRect; paddingRatio?: number; sourceNodeId?: string; }): Promise< ApiResponse<{ url: string; cropRectApplied?: NormalizedCropRect; paddedCropRectApplied?: NormalizedCropRect; }> > { try { const response = await fetch('/api/canvas/box-cutout', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params), }); const data = await response.json(); if (data.success && data.original_url) { return { success: true, data: { url: data.original_url, cropRectApplied: data.cropRectApplied, paddedCropRectApplied: data.paddedCropRectApplied, }, }; } return { success: false, error: data.error || '框选抠图失败' }; } catch (error) { console.error('[Canvas] 框选抠图失败:', error); return { success: false, error: '框选抠图失败' }; } }, // 去水印 - 使用图片URL async removeWatermark(imageUrl: string): Promise> { try { const response = await fetch('/api/watermark-remove', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ image_url: imageUrl }), }); const data = await response.json(); if (data.success && data.original_url) { return { success: true, data: { url: data.original_url } }; } return { success: false, error: data.error || '去水印失败' }; } catch (error) { console.error('[Canvas] 去水印失败:', error); return { success: false, error: '去水印失败' }; } }, // 图片增强 - 使用图片URL async enhanceImage(imageUrl: string): Promise> { try { const response = await fetch('/api/enhance', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ imageUrl }), // 使用 imageUrl 参数 }); const data = await response.json(); if (data.success && data.original_url) { return { success: true, data: { url: data.original_url } }; } return { success: false, error: data.error || '图片增强失败' }; } catch (error) { console.error('[Canvas] 图片增强失败:', error); return { success: false, error: '图片增强失败' }; } }, async enhanceImagePro( params: EnhanceProRequestParams, ): Promise> { try { const payload = await requestLongTaskResult({ endpoint: '/api/enhance/pro', payload: params, pollingParams: { prompt: params.prompt || '图像增强 Pro', chatId: params.chatId, messageId: params.messageId, sourceImageUrl: params.imageUrl, width: params.width, height: params.height, }, }); if (!payload?.success) { return { success: false, error: payload?.error || payload?.detail || '图像增强 Pro 失败' }; } const images: EnhanceProImageResult[] = []; if (Array.isArray(payload.images)) { for (const item of payload.images) { const originalUrl = String(item?.original_url || '').trim(); const thumbnailUrl = String(item?.thumbnail_url || item?.original_url || '').trim(); if (!originalUrl) { continue; } images.push({ originalUrl, thumbnailUrl, width: typeof item?.width === 'number' ? item.width : undefined, height: typeof item?.height === 'number' ? item.height : undefined, }); } } if (images.length === 0 && payload.original_url) { images.push({ originalUrl: payload.original_url, thumbnailUrl: payload.thumbnail_url || payload.original_url, width: typeof payload.width === 'number' ? payload.width : undefined, height: typeof payload.height === 'number' ? payload.height : undefined, }); } if (images.length === 0) { return { success: false, error: '图像增强 Pro 未返回有效结果' }; } return { success: true, data: { images, }, }; } catch (error) { console.error('[Canvas] 图像增强 Pro 失败:', error); return { success: false, error: error instanceof Error ? error.message : '图像增强 Pro 失败', }; } }, // 图片扩展 - 使用图片URL async extendImage(imageUrl: string, direction?: string): Promise> { try { const response = await fetch('/api/extend', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ image_url: imageUrl, direction }), // extend API 使用 image_url 参数名 }); const data = await response.json(); if (data.success && data.original_url) { return { success: true, data: { url: data.original_url } }; } return { success: false, error: data.error || '图片扩展失败' }; } catch (error) { console.error('[Canvas] 图片扩展失败:', error); return { success: false, error: '图片扩展失败' }; } }, async splitImageLayers(imageUrl: string): Promise< ApiResponse<{ layers: Array<{ originalUrl: string; thumbnailUrl: string; title: string; layerIndex: number; totalLayers: number; bounds?: { x: number; y: number; width: number; height: number; originalWidth: number; originalHeight: number; coverage: number; } | null; }>; model?: string; provider?: string; }> > { try { const response = await fetch('/api/layer-split', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ imageUrl }), }); const data = await response.json(); if (data.success && Array.isArray(data.layers)) { return { success: true, data: { layers: data.layers, model: data.model, provider: data.provider, }, }; } return { success: false, error: data.error || '图层拆分失败' }; } catch (error) { console.error('[Canvas] 图层拆分失败:', error); return { success: false, error: '图层拆分失败' }; } }, // 尺寸调整 - 通过 URL 下载图片,调整大小后上传到 S3 async resizeImage(imageUrl: string, width: number, height: number): Promise> { try { // 1. 下载原图 const response = await fetch(imageUrl); if (!response.ok) { throw new Error('下载图片失败'); } const blob = await response.blob(); // 2. 使用 canvas 调整尺寸 const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (!ctx) { throw new Error('无法创建 canvas context'); } const img = new Image(); img.crossOrigin = 'anonymous'; await new Promise((resolve, reject) => { img.onload = () => resolve(); img.onerror = () => reject(new Error('图片加载失败')); img.src = URL.createObjectURL(blob); }); canvas.width = width; canvas.height = height; ctx.drawImage(img, 0, 0, width, height); // 3. 转换为 base64 const base64 = canvas.toDataURL('image/png').split(',')[1]; // 4. 上传到 S3 const uploadResponse = await fetch('/api/resize', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ image_base64: base64, canvasWidth: width, canvasHeight: height, prompt: '尺寸调整', }), }); const data = await uploadResponse.json(); if (data.success && data.original_url) { return { success: true, data: { url: data.original_url } }; } return { success: false, error: data.error || '尺寸调整失败' }; } catch (error) { console.error('[Canvas] 尺寸调整失败:', error); return { success: false, error: '尺寸调整失败' }; } }, // 角度编辑 async editAngle(params: { imageUrl: string; horizontalAngle: number; verticalAngle: number; zoom: number; }): Promise> { try { const response = await fetch('/api/angle-edit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params), }); const data = await response.json(); return data; } catch (error) { return { success: false, error: '角度编辑失败' }; } }, // 场景裂变 async sceneFusion(params: { productImage: string; backgroundImages: string[]; productName?: string; }): Promise> { try { const response = await fetch('/api/scene-fusion', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params), }); const data = await response.json(); return data; } catch (error) { return { success: false, error: '场景裂变失败' }; } }, // 识别图片中的文字(OCR) async detectCanvasText(imageUrl: string): Promise> { try { const response = await fetch('/api/canvas/text-detect', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ imageUrl }), }); const data = await response.json(); return data; } catch (error) { console.error('[Canvas] 文字识别失败:', error); return { success: false, error: '文字识别失败' }; } }, // 应用文字替换并生成新图 async editCanvasText(params: { imageUrl: string; items: CanvasTextEditPayloadItem[]; }): Promise> { try { const response = await fetch('/api/canvas/text-edit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params), }); const data = await response.json(); return data; } catch (error) { console.error('[Canvas] 文字替换失败:', error); return { success: false, error: '文字替换失败' }; } }, }; // 视频生成服务 export const videoGenerationService = { // 生成视频 async generateVideo(params: { prompt: string; model: string; referenceImage?: string; duration?: number; }): Promise> { try { const response = await fetch('/api/canvas/generate/video', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params), }); const data = await response.json(); return data; } catch (error) { return { success: false, error: '生成视频失败' }; } }, }; export type Model3DTaskStatus = | 'queued' | 'running' | 'success' | 'failed' | 'banned' | 'expired' | 'cancelled' | 'unknown'; export const model3DGenerationService = { async uploadImage(file: File): Promise }>> { try { const formData = new FormData(); formData.append('file', file); const response = await fetch('/api/canvas/3d/upload-image', { method: 'POST', body: formData, }); const data = await response.json(); return data; } catch (error) { return { success: false, error: '上传参考图失败' }; } }, async createTask(params: Record): Promise> { try { const response = await fetch('/api/canvas/3d/tasks', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params), }); const data = await response.json(); return data; } catch (error) { return { success: false, error: '创建 3D 任务失败' }; } }, async getTask(taskId: string): Promise; modelUrl?: string | null; previewImageUrl?: string | null; errorCode?: number | null; errorMessage?: string | null; }>> { try { const response = await fetch(`/api/canvas/3d/tasks/${taskId}`); const data = await response.json(); return data; } catch (error) { return { success: false, error: '查询 3D 任务失败' }; } }, async getBalance(): Promise>> { try { const response = await fetch('/api/canvas/3d/balance'); const data = await response.json(); return data; } catch (error) { return { success: false, error: '查询余额失败' }; } }, }; export type WorldLabsTaskMode = 'text' | 'image' | 'multi-image' | 'video'; export type WorldLabsTaskAssetPayload = { worldId?: string | null; worldUrl?: string | null; displayName?: string | null; model?: string | null; caption?: string | null; thumbnailUrl?: string | null; panoUrl?: string | null; meshUrl?: string | null; spzUrls?: Record | null; semanticsMetadata?: Record | null; }; export const worldLabsGenerationService = { async uploadMedia( file: File, kind: 'image' | 'video' ): Promise; uploadInfo?: Record }>> { try { const formData = new FormData(); formData.append('file', file); formData.append('kind', kind); const response = await fetch('/api/canvas/world/upload', { method: 'POST', body: formData, }); const data = await response.json(); return data; } catch (error) { return { success: false, error: '上传 World Labs 媒体失败' }; } }, async createTask(params: Record): Promise> { try { const response = await fetch('/api/canvas/world/tasks', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params), }); const data = await response.json(); return data; } catch (error) { return { success: false, error: '创建 World Labs 任务失败' }; } }, async getTask(taskId: string): Promise; modelUrl?: string | null; previewImageUrl?: string | null; worldId?: string | null; worldUrl?: string | null; panoUrl?: string | null; caption?: string | null; assetPayload?: WorldLabsTaskAssetPayload | null; errorMessage?: string | null; }>> { try { const response = await fetch(`/api/canvas/world/tasks/${taskId}`); const data = await response.json(); return data; } catch (error) { return { success: false, error: '查询 World Labs 任务失败' }; } }, }; export default { canvas: canvasService, image: imageGenerationService, video: videoGenerationService, model3d: model3DGenerationService, world: worldLabsGenerationService, };