411 lines
12 KiB
TypeScript
411 lines
12 KiB
TypeScript
// 画布服务层
|
||
import type { CanvasProject, CanvasState, CanvasNode } from '@/types/canvas';
|
||
|
||
// API 响应类型
|
||
interface ApiResponse<T = any> {
|
||
success: boolean;
|
||
data?: T;
|
||
error?: string;
|
||
}
|
||
|
||
export interface CanvasTextDetectItem {
|
||
id: string;
|
||
text: string;
|
||
confidence?: number;
|
||
}
|
||
|
||
export interface CanvasTextEditPayloadItem {
|
||
id: string;
|
||
originalText: string;
|
||
editedText?: string;
|
||
action?: 'replace' | 'delete';
|
||
}
|
||
|
||
// 画布服务
|
||
export const canvasService = {
|
||
// 获取画布列表
|
||
async getProjects(): Promise<ApiResponse<CanvasProject[]>> {
|
||
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<ApiResponse<CanvasProject>> {
|
||
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<CanvasProject>): Promise<ApiResponse<CanvasProject>> {
|
||
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<CanvasProject>): Promise<ApiResponse<CanvasProject>> {
|
||
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<ApiResponse> {
|
||
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<ApiResponse> {
|
||
try {
|
||
const response = await fetch(`/api/canvas/${id}`, {
|
||
method: 'PUT',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ canvasData }),
|
||
});
|
||
const data = await response.json();
|
||
return data;
|
||
} 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<ApiResponse<{ url: string; pathname: string }>> {
|
||
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
|
||
}
|
||
};
|
||
} 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<ApiResponse<{ images: Array<{ thumbnailUrl: string; originalUrl: string }>; 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<ApiResponse<{ url: string }>> {
|
||
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: '抠图失败' };
|
||
}
|
||
},
|
||
|
||
// 去水印 - 使用图片URL
|
||
async removeWatermark(imageUrl: string): Promise<ApiResponse<{ url: string }>> {
|
||
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<ApiResponse<{ url: string }>> {
|
||
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: '图片增强失败' };
|
||
}
|
||
},
|
||
|
||
// 图片扩展 - 使用图片URL
|
||
async extendImage(imageUrl: string, direction?: string): Promise<ApiResponse<{ url: string }>> {
|
||
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: '图片扩展失败' };
|
||
}
|
||
},
|
||
|
||
// 尺寸调整 - 通过 URL 下载图片,调整大小后上传到 S3
|
||
async resizeImage(imageUrl: string, width: number, height: number): Promise<ApiResponse<{ url: string }>> {
|
||
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<void>((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<ApiResponse<{ url: string }>> {
|
||
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<ApiResponse<{ urls: string[] }>> {
|
||
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<ApiResponse<{ items: CanvasTextDetectItem[]; model?: string }>> {
|
||
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<ApiResponse<{ thumbnailUrl: string; originalUrl: string; model?: string }>> {
|
||
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<ApiResponse<{ url: string }>> {
|
||
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 default {
|
||
canvas: canvasService,
|
||
image: imageGenerationService,
|
||
video: videoGenerationService,
|
||
};
|