feat: use image URLs instead of Base64 for canvas image operations
- Modified ImageNode to upload images to storage bucket before use - Updated RMBG API to support imageUrl parameter (fetch from URL and convert to Base64 internally) - Updated Enhance API to support imageUrl parameter - Updated canvas service layer to use imageUrl for all image processing APIs - All processed images are saved to storage bucket and user gallery
This commit is contained in:
parent
bc7e967aba
commit
6a2cbb1b49
@ -366,7 +366,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 获取请求体(Base64图片或FormData)
|
||||
// 2. 获取请求体(支持 imageUrl、image_base64 或 FormData)
|
||||
console.log("[Enhance API] 开始获取请求体...");
|
||||
const contentType = request.headers.get("content-type") || "";
|
||||
console.log("[Enhance API] Content-Type:", contentType);
|
||||
@ -377,18 +377,41 @@ export async function POST(request: NextRequest) {
|
||||
let messageId: string | null = null; // 消息ID(可选)
|
||||
|
||||
if (contentType.includes("application/json")) {
|
||||
// JSON格式:Base64图片
|
||||
console.log("[Enhance API] 解析JSON请求体...");
|
||||
const body = await request.json();
|
||||
console.log("[Enhance API] JSON请求体解析完成,image_base64长度:", body.image_base64?.length || 0);
|
||||
imageBase64 = body.image_base64;
|
||||
prompt = body.prompt || "图片变清晰处理";
|
||||
chatId = body.chatId || null;
|
||||
messageId = body.messageId || null;
|
||||
|
||||
if (!imageBase64) {
|
||||
// 优先使用 imageUrl,其次使用 image_base64
|
||||
if (body.imageUrl) {
|
||||
// 从 URL 获取图片并转换为 Base64
|
||||
console.log(`[Enhance API] 从 URL 获取图片: ${body.imageUrl}`);
|
||||
try {
|
||||
const imageResponse = await fetch(body.imageUrl);
|
||||
if (!imageResponse.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: `无法获取图片: ${imageResponse.status} ${imageResponse.statusText}` },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
const arrayBuffer = await imageResponse.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
imageBase64 = buffer.toString("base64");
|
||||
console.log(`[Enhance API] 图片已转换为 Base64,大小: ${buffer.length} bytes`);
|
||||
} catch (fetchError) {
|
||||
console.error("[Enhance API] 获取图片失败:", fetchError);
|
||||
return NextResponse.json(
|
||||
{ error: "无法获取图片,请检查图片链接是否有效" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
} else if (body.image_base64) {
|
||||
console.log("[Enhance API] JSON请求体解析完成,image_base64长度:", body.image_base64?.length || 0);
|
||||
imageBase64 = body.image_base64;
|
||||
} else {
|
||||
return NextResponse.json(
|
||||
{ error: "缺少image_base64参数" },
|
||||
{ error: "缺少 imageUrl 或 image_base64 参数" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
@ -120,7 +120,7 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
// 2. 获取请求体(Base64图片或FormData)
|
||||
// 2. 获取请求体(支持 imageUrl、image_base64 或 FormData)
|
||||
const contentType = request.headers.get("content-type") || "";
|
||||
|
||||
let imageBase64: string;
|
||||
@ -129,16 +129,39 @@ export async function POST(request: NextRequest) {
|
||||
let messageId: string | null = null; // 消息ID(可选)
|
||||
|
||||
if (contentType.includes("application/json")) {
|
||||
// JSON格式:Base64图片
|
||||
const body = await request.json();
|
||||
imageBase64 = body.image_base64;
|
||||
prompt = body.prompt || "抠图处理";
|
||||
chatId = body.chatId || null;
|
||||
messageId = body.messageId || null;
|
||||
|
||||
if (!imageBase64) {
|
||||
// 优先使用 imageUrl,其次使用 image_base64
|
||||
if (body.imageUrl) {
|
||||
// 从 URL 获取图片并转换为 Base64
|
||||
console.log(`[RMBG] 从 URL 获取图片: ${body.imageUrl}`);
|
||||
try {
|
||||
const imageResponse = await fetch(body.imageUrl);
|
||||
if (!imageResponse.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: `无法获取图片: ${imageResponse.status} ${imageResponse.statusText}` },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
const arrayBuffer = await imageResponse.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
imageBase64 = buffer.toString("base64");
|
||||
console.log(`[RMBG] 图片已转换为 Base64,大小: ${buffer.length} bytes`);
|
||||
} catch (fetchError) {
|
||||
console.error("[RMBG] 获取图片失败:", fetchError);
|
||||
return NextResponse.json(
|
||||
{ error: "无法获取图片,请检查图片链接是否有效" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
} else if (body.image_base64) {
|
||||
imageBase64 = body.image_base64;
|
||||
} else {
|
||||
return NextResponse.json(
|
||||
{ error: "缺少image_base64参数" },
|
||||
{ error: "缺少 imageUrl 或 image_base64 参数" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
@ -34,19 +34,62 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
||||
const deleteNode = useCanvasStore((state) => state.deleteNode);
|
||||
const addNode = useCanvasStore((state) => state.addNode);
|
||||
|
||||
// 处理图片上传
|
||||
const handleImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
|
||||
// 处理图片上传 - 上传到存储桶,不使用 Base64
|
||||
const handleImageUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
const src = event.target?.result as string;
|
||||
// 验证文件类型
|
||||
if (!file.type.startsWith('image/')) {
|
||||
toast.error('请上传图片文件');
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证文件大小 (8MB)
|
||||
if (file.size > 8 * 1024 * 1024) {
|
||||
toast.error('图片大小不能超过 8MB');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsUploading(true);
|
||||
// 先显示上传中状态
|
||||
updateNode(node.id, {
|
||||
data: { ...data, status: 'generating' },
|
||||
});
|
||||
|
||||
try {
|
||||
// 上传到存储桶
|
||||
const result = await imageGenerationService.uploadImage(file);
|
||||
|
||||
if (result.success && result.data?.url) {
|
||||
updateNode(node.id, {
|
||||
data: {
|
||||
...data,
|
||||
src: result.data.url,
|
||||
status: 'completed'
|
||||
},
|
||||
});
|
||||
toast.success('图片上传成功');
|
||||
} else {
|
||||
updateNode(node.id, {
|
||||
data: { ...data, status: 'failed' },
|
||||
});
|
||||
toast.error(result.error || '图片上传失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('图片上传失败:', error);
|
||||
updateNode(node.id, {
|
||||
data: { ...data, src, status: 'completed' },
|
||||
data: { ...data, status: 'failed' },
|
||||
});
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
toast.error('图片上传失败,请重试');
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
}
|
||||
|
||||
// 重置 input
|
||||
e.target.value = '';
|
||||
};
|
||||
|
||||
// 处理图片编辑操作 - 集成我们现有的 API
|
||||
|
||||
@ -108,6 +108,36 @@ export const canvasService = {
|
||||
|
||||
// 图片生成服务
|
||||
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;
|
||||
@ -130,47 +160,59 @@ export const imageGenerationService = {
|
||||
}
|
||||
},
|
||||
|
||||
// 图片抠图
|
||||
// 图片抠图 - 使用图片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 }),
|
||||
body: JSON.stringify({ imageUrl }), // 使用 imageUrl 参数
|
||||
});
|
||||
const data = await response.json();
|
||||
return data;
|
||||
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 }),
|
||||
body: JSON.stringify({ imageUrl }), // 使用 imageUrl 参数
|
||||
});
|
||||
const data = await response.json();
|
||||
return data;
|
||||
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({ imageUrl, direction }),
|
||||
body: JSON.stringify({ image_url: imageUrl, direction }), // extend API 使用 image_url 参数名
|
||||
});
|
||||
const data = await response.json();
|
||||
return data;
|
||||
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: '图片扩展失败' };
|
||||
}
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user