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] 开始获取请求体...");
|
console.log("[Enhance API] 开始获取请求体...");
|
||||||
const contentType = request.headers.get("content-type") || "";
|
const contentType = request.headers.get("content-type") || "";
|
||||||
console.log("[Enhance API] Content-Type:", contentType);
|
console.log("[Enhance API] Content-Type:", contentType);
|
||||||
@ -377,18 +377,41 @@ export async function POST(request: NextRequest) {
|
|||||||
let messageId: string | null = null; // 消息ID(可选)
|
let messageId: string | null = null; // 消息ID(可选)
|
||||||
|
|
||||||
if (contentType.includes("application/json")) {
|
if (contentType.includes("application/json")) {
|
||||||
// JSON格式:Base64图片
|
|
||||||
console.log("[Enhance API] 解析JSON请求体...");
|
console.log("[Enhance API] 解析JSON请求体...");
|
||||||
const body = await request.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 || "图片变清晰处理";
|
prompt = body.prompt || "图片变清晰处理";
|
||||||
chatId = body.chatId || null;
|
chatId = body.chatId || null;
|
||||||
messageId = body.messageId || 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(
|
return NextResponse.json(
|
||||||
{ error: "缺少image_base64参数" },
|
{ error: "缺少 imageUrl 或 image_base64 参数" },
|
||||||
{ status: 400 }
|
{ 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") || "";
|
const contentType = request.headers.get("content-type") || "";
|
||||||
|
|
||||||
let imageBase64: string;
|
let imageBase64: string;
|
||||||
@ -129,16 +129,39 @@ export async function POST(request: NextRequest) {
|
|||||||
let messageId: string | null = null; // 消息ID(可选)
|
let messageId: string | null = null; // 消息ID(可选)
|
||||||
|
|
||||||
if (contentType.includes("application/json")) {
|
if (contentType.includes("application/json")) {
|
||||||
// JSON格式:Base64图片
|
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
imageBase64 = body.image_base64;
|
|
||||||
prompt = body.prompt || "抠图处理";
|
prompt = body.prompt || "抠图处理";
|
||||||
chatId = body.chatId || null;
|
chatId = body.chatId || null;
|
||||||
messageId = body.messageId || 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(
|
return NextResponse.json(
|
||||||
{ error: "缺少image_base64参数" },
|
{ error: "缺少 imageUrl 或 image_base64 参数" },
|
||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,19 +34,62 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
|||||||
const deleteNode = useCanvasStore((state) => state.deleteNode);
|
const deleteNode = useCanvasStore((state) => state.deleteNode);
|
||||||
const addNode = useCanvasStore((state) => state.addNode);
|
const addNode = useCanvasStore((state) => state.addNode);
|
||||||
|
|
||||||
// 处理图片上传
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
const handleImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
|
// 处理图片上传 - 上传到存储桶,不使用 Base64
|
||||||
|
const handleImageUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const file = e.target.files?.[0];
|
const file = e.target.files?.[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
const reader = new FileReader();
|
// 验证文件类型
|
||||||
reader.onload = (event) => {
|
if (!file.type.startsWith('image/')) {
|
||||||
const src = event.target?.result as string;
|
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, {
|
updateNode(node.id, {
|
||||||
data: { ...data, src, status: 'completed' },
|
data: { ...data, status: 'failed' },
|
||||||
});
|
});
|
||||||
};
|
toast.error('图片上传失败,请重试');
|
||||||
reader.readAsDataURL(file);
|
} finally {
|
||||||
|
setIsUploading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置 input
|
||||||
|
e.target.value = '';
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理图片编辑操作 - 集成我们现有的 API
|
// 处理图片编辑操作 - 集成我们现有的 API
|
||||||
|
|||||||
@ -108,6 +108,36 @@ export const canvasService = {
|
|||||||
|
|
||||||
// 图片生成服务
|
// 图片生成服务
|
||||||
export const imageGenerationService = {
|
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 生成图片
|
// AI 生成图片
|
||||||
async generateImage(params: {
|
async generateImage(params: {
|
||||||
prompt: string;
|
prompt: string;
|
||||||
@ -130,47 +160,59 @@ export const imageGenerationService = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 图片抠图
|
// 图片抠图 - 使用图片URL
|
||||||
async removeBackground(imageUrl: string): Promise<ApiResponse<{ url: string }>> {
|
async removeBackground(imageUrl: string): Promise<ApiResponse<{ url: string }>> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/rmbg', {
|
const response = await fetch('/api/rmbg', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ imageUrl }),
|
body: JSON.stringify({ imageUrl }), // 使用 imageUrl 参数
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
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) {
|
} catch (error) {
|
||||||
|
console.error('[Canvas] 抠图失败:', error);
|
||||||
return { success: false, error: '抠图失败' };
|
return { success: false, error: '抠图失败' };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 图片增强
|
// 图片增强 - 使用图片URL
|
||||||
async enhanceImage(imageUrl: string): Promise<ApiResponse<{ url: string }>> {
|
async enhanceImage(imageUrl: string): Promise<ApiResponse<{ url: string }>> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/enhance', {
|
const response = await fetch('/api/enhance', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ imageUrl }),
|
body: JSON.stringify({ imageUrl }), // 使用 imageUrl 参数
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
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) {
|
} catch (error) {
|
||||||
|
console.error('[Canvas] 图片增强失败:', error);
|
||||||
return { success: false, error: '图片增强失败' };
|
return { success: false, error: '图片增强失败' };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 图片扩展
|
// 图片扩展 - 使用图片URL
|
||||||
async extendImage(imageUrl: string, direction?: string): Promise<ApiResponse<{ url: string }>> {
|
async extendImage(imageUrl: string, direction?: string): Promise<ApiResponse<{ url: string }>> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/extend', {
|
const response = await fetch('/api/extend', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
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();
|
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) {
|
} catch (error) {
|
||||||
|
console.error('[Canvas] 图片扩展失败:', error);
|
||||||
return { success: false, error: '图片扩展失败' };
|
return { success: false, error: '图片扩展失败' };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user