fix: streamline extend image payload flow
This commit is contained in:
parent
2814669881
commit
c4c0bd8f64
@ -12,6 +12,22 @@ import {
|
||||
|
||||
export const maxDuration = 180;
|
||||
|
||||
function parseIncludeBase64(value: unknown): boolean {
|
||||
if (typeof value === "boolean") return value;
|
||||
const normalized = String(value ?? "").trim().toLowerCase();
|
||||
return normalized === "true" || normalized === "1" || normalized === "yes";
|
||||
}
|
||||
|
||||
function buildExtendSuccessPayload(result: Awaited<ReturnType<typeof executeExtendJob>>) {
|
||||
return {
|
||||
success: true,
|
||||
original_url: result.original_url,
|
||||
thumbnail_url: result.thumbnail_url,
|
||||
...(result.image_base64 ? { base64: result.image_base64 } : {}),
|
||||
processing_time: result.processing_time,
|
||||
};
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth();
|
||||
@ -26,6 +42,8 @@ export async function POST(request: NextRequest) {
|
||||
let prompt = "图像拓展处理";
|
||||
let chatId: string | null = null;
|
||||
let messageId: string | null = null;
|
||||
let includeBase64 = false;
|
||||
const requestId = `extend-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
|
||||
if (contentType.includes("application/json")) {
|
||||
const body = await request.json();
|
||||
@ -34,6 +52,7 @@ export async function POST(request: NextRequest) {
|
||||
prompt = body.prompt || prompt;
|
||||
chatId = body.chatId || null;
|
||||
messageId = body.messageId || null;
|
||||
includeBase64 = parseIncludeBase64(body.includeBase64);
|
||||
|
||||
if (!imageUrl && !imageBase64) {
|
||||
return NextResponse.json(
|
||||
@ -42,6 +61,16 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
console.log("[Extend API] Request received:", {
|
||||
requestId,
|
||||
source: imageUrl ? "url" : "base64",
|
||||
includeBase64,
|
||||
chatId,
|
||||
messageId,
|
||||
xScale: body.xScale,
|
||||
yScale: body.yScale,
|
||||
});
|
||||
|
||||
const pointsConfig = await getPointsSystemConfig();
|
||||
const pointsCharge = await prepareFeaturePointsCharge({
|
||||
userId: session.user.id,
|
||||
@ -64,6 +93,8 @@ export async function POST(request: NextRequest) {
|
||||
messageId,
|
||||
xScale: body.xScale ? Number(body.xScale) : undefined,
|
||||
yScale: body.yScale ? Number(body.yScale) : undefined,
|
||||
includeBase64,
|
||||
requestId,
|
||||
});
|
||||
|
||||
if (pointsCharge.chargeable) {
|
||||
@ -74,13 +105,14 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
original_url: result.original_url,
|
||||
thumbnail_url: result.thumbnail_url,
|
||||
base64: result.image_base64,
|
||||
processing_time: result.processing_time,
|
||||
console.log("[Extend API] Request completed:", {
|
||||
requestId,
|
||||
includeBase64,
|
||||
processingTime: result.processing_time,
|
||||
originalUrl: result.original_url.slice(0, 120),
|
||||
});
|
||||
|
||||
return NextResponse.json(buildExtendSuccessPayload(result));
|
||||
}
|
||||
|
||||
if (contentType.includes("multipart/form-data")) {
|
||||
@ -92,6 +124,17 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
const multipartChatId = String(formData.get("chatId") || "") || null;
|
||||
const multipartMessageId = String(formData.get("messageId") || "") || null;
|
||||
includeBase64 = parseIncludeBase64(formData.get("includeBase64"));
|
||||
console.log("[Extend API] Multipart request received:", {
|
||||
requestId,
|
||||
includeBase64,
|
||||
chatId: multipartChatId,
|
||||
messageId: multipartMessageId,
|
||||
bytes: buffer.length,
|
||||
});
|
||||
|
||||
const pointsConfig = await getPointsSystemConfig();
|
||||
const pointsCharge = await prepareFeaturePointsCharge({
|
||||
userId: session.user.id,
|
||||
@ -109,8 +152,10 @@ export async function POST(request: NextRequest) {
|
||||
userId: session.user.id,
|
||||
imageBase64: buffer.toString("base64"),
|
||||
prompt: String(formData.get("prompt") || prompt),
|
||||
chatId: String(formData.get("chatId") || "") || null,
|
||||
messageId: String(formData.get("messageId") || "") || null,
|
||||
chatId: multipartChatId,
|
||||
messageId: multipartMessageId,
|
||||
includeBase64,
|
||||
requestId,
|
||||
});
|
||||
|
||||
if (pointsCharge.chargeable) {
|
||||
@ -121,13 +166,14 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
original_url: result.original_url,
|
||||
thumbnail_url: result.thumbnail_url,
|
||||
base64: result.image_base64,
|
||||
processing_time: result.processing_time,
|
||||
console.log("[Extend API] Multipart request completed:", {
|
||||
requestId,
|
||||
includeBase64,
|
||||
processingTime: result.processing_time,
|
||||
originalUrl: result.original_url.slice(0, 120),
|
||||
});
|
||||
|
||||
return NextResponse.json(buildExtendSuccessPayload(result));
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
|
||||
@ -2583,6 +2583,7 @@ export function AgentPsdTemplateEditorDialog({
|
||||
body: JSON.stringify({
|
||||
image_url: normalizedImageUrl,
|
||||
prompt: "PSD 套版图片拓展处理",
|
||||
includeBase64: false,
|
||||
}),
|
||||
});
|
||||
const payload = await parseActionJsonResponse<{
|
||||
|
||||
@ -49,6 +49,23 @@ import type { VideoToolCapabilityContract } from "@/types/video-tool-capability"
|
||||
|
||||
type ImageGenerationPhase = "idle" | "queued" | "processing" | "finalizing" | "failed";
|
||||
|
||||
function getImageFileExtension(mimeType: string | null | undefined): string {
|
||||
switch (String(mimeType || "").toLowerCase()) {
|
||||
case "image/jpeg":
|
||||
case "image/jpg":
|
||||
return "jpg";
|
||||
case "image/webp":
|
||||
return "webp";
|
||||
case "image/gif":
|
||||
return "gif";
|
||||
case "image/svg+xml":
|
||||
return "svg";
|
||||
case "image/png":
|
||||
default:
|
||||
return "png";
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeImageParamsForProvider(
|
||||
params: ImageGenerationParams,
|
||||
providerType?: string
|
||||
@ -1821,122 +1838,139 @@ export function Chat({
|
||||
}, 100);
|
||||
|
||||
try {
|
||||
// 获取图片并转换为Base64
|
||||
const response = await fetch(imageUrl);
|
||||
const blob = await response.blob();
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onloadend = async () => {
|
||||
let extendSourceUrl = String(imageUrl || "").trim();
|
||||
if (!extendSourceUrl) {
|
||||
throw new Error("缺少可拓展的图片地址");
|
||||
}
|
||||
|
||||
if (extendSourceUrl.startsWith("data:") || extendSourceUrl.startsWith("blob:")) {
|
||||
const sourceResponse = await fetch(extendSourceUrl);
|
||||
if (!sourceResponse.ok) {
|
||||
throw new Error("读取待拓展图片失败");
|
||||
}
|
||||
const blob = await sourceResponse.blob();
|
||||
const mimeType =
|
||||
blob.type || sourceResponse.headers.get("content-type") || "image/png";
|
||||
const file = new File(
|
||||
[blob],
|
||||
`extend-source-${Date.now()}.${getImageFileExtension(mimeType)}`,
|
||||
{ type: mimeType },
|
||||
);
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
||||
const uploadResponse = await fetchUploadWithRetry(formData);
|
||||
const uploadPayload = await uploadResponse
|
||||
.json()
|
||||
.catch(() => null as { url?: string; error?: string } | null);
|
||||
if (!uploadResponse.ok) {
|
||||
throw new Error(uploadPayload?.error || "上传待拓展图片失败");
|
||||
}
|
||||
|
||||
const uploadedUrl = String(uploadPayload?.url || "").trim();
|
||||
if (!uploadedUrl) {
|
||||
throw new Error("上传待拓展图片成功,但未返回图片地址");
|
||||
}
|
||||
extendSourceUrl = uploadedUrl;
|
||||
} else {
|
||||
extendSourceUrl = new URL(extendSourceUrl, window.location.origin).toString();
|
||||
}
|
||||
|
||||
const extendResponse = await fetch("/api/extend", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
image_url: extendSourceUrl,
|
||||
prompt: "图片图像拓展处理",
|
||||
chatId: id,
|
||||
messageId,
|
||||
includeBase64: false,
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await extendResponse.json().catch(
|
||||
() =>
|
||||
null as {
|
||||
success?: boolean;
|
||||
error?: string;
|
||||
original_url?: string;
|
||||
thumbnail_url?: string;
|
||||
processing_time?: number;
|
||||
} | null,
|
||||
);
|
||||
|
||||
if (!extendResponse.ok) {
|
||||
throw new Error(result?.error || "图像拓展处理失败");
|
||||
}
|
||||
|
||||
if (result?.success && result.original_url) {
|
||||
// 构造图片Markdown(使用与Gemini生成相同的格式)
|
||||
const imageMarkdown = ``;
|
||||
|
||||
// 创建最终消息(前端显示用)
|
||||
const finalMessage: ChatMessage = {
|
||||
id: messageId,
|
||||
role: "assistant",
|
||||
parts: [{ type: "text", text: imageMarkdown }],
|
||||
metadata: {
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
} as ChatMessage;
|
||||
|
||||
// 更新消息
|
||||
setMessages((prev) =>
|
||||
prev.map((msg) => (msg.id === messageId ? finalMessage : msg)),
|
||||
);
|
||||
|
||||
// 滚动到底部,显示处理结果
|
||||
setTimeout(() => {
|
||||
const messagesContainer = document.querySelector('[role="log"]');
|
||||
if (messagesContainer) {
|
||||
messagesContainer.scrollTo({
|
||||
top: messagesContainer.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// 保存消息到数据库
|
||||
try {
|
||||
const base64Data = (reader.result as string).split(",")[1];
|
||||
|
||||
// 调用图像拓展API
|
||||
const extendResponse = await fetch("/api/extend", {
|
||||
await fetch("/api/messages", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
image_base64: base64Data,
|
||||
prompt: "图片图像拓展处理",
|
||||
}),
|
||||
});
|
||||
|
||||
if (!extendResponse.ok) {
|
||||
const error = await extendResponse.json();
|
||||
throw new Error(error.error || "图像拓展处理失败");
|
||||
}
|
||||
|
||||
const result = await extendResponse.json();
|
||||
|
||||
if (result.success && result.original_url) {
|
||||
// 构造图片Markdown(使用与Gemini生成相同的格式)
|
||||
const imageMarkdown = ``;
|
||||
|
||||
// 创建最终消息(前端显示用)
|
||||
const finalMessage: ChatMessage = {
|
||||
id: messageId,
|
||||
role: "assistant",
|
||||
parts: [{ type: "text", text: imageMarkdown }],
|
||||
metadata: {
|
||||
chatId: id,
|
||||
message: {
|
||||
id: messageId,
|
||||
role: "assistant",
|
||||
parts: [{ type: "text", text: imageMarkdown }],
|
||||
attachments: [],
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
} as ChatMessage;
|
||||
|
||||
// 更新消息
|
||||
setMessages((prev) =>
|
||||
prev.map((msg) => (msg.id === messageId ? finalMessage : msg))
|
||||
);
|
||||
|
||||
// 滚动到底部,显示处理结果
|
||||
setTimeout(() => {
|
||||
const messagesContainer = document.querySelector('[role="log"]');
|
||||
if (messagesContainer) {
|
||||
messagesContainer.scrollTo({
|
||||
top: messagesContainer.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// 保存消息到数据库
|
||||
try {
|
||||
await fetch("/api/messages", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
chatId: id,
|
||||
message: {
|
||||
id: messageId,
|
||||
role: "assistant",
|
||||
parts: [{ type: "text", text: imageMarkdown }],
|
||||
attachments: [],
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
}),
|
||||
});
|
||||
} catch (saveError) {
|
||||
console.error("[Chat] Failed to save message:", saveError);
|
||||
}
|
||||
|
||||
toast.success(`图像拓展完成!处理时间: ${result.processing_time?.toFixed(2) || "未知"}秒`);
|
||||
} else {
|
||||
throw new Error(result.error || "图像拓展处理失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[图像拓展] 错误:", error);
|
||||
toast.error(
|
||||
getUserFacingRequestErrorMessage(error, "图像拓展失败,请稍后重试", {
|
||||
rateLimitMessage: getFriendlyRateLimitMessage("当前图像拓展请求过于频繁,请稍后再试"),
|
||||
})
|
||||
);
|
||||
// 移除加载消息
|
||||
setMessages((prev) => prev.filter((msg) => msg.id !== messageId));
|
||||
} finally {
|
||||
console.log("[图像拓展] finally 块执行,重置 isProcessingExtend");
|
||||
setIsProcessingExtend(false);
|
||||
}),
|
||||
});
|
||||
} catch (saveError) {
|
||||
console.error("[Chat] Failed to save message:", saveError);
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = () => {
|
||||
console.log("[图像拓展] reader.onerror 执行,重置 isProcessingExtend");
|
||||
toast.error("图片读取失败");
|
||||
setMessages((prev) => prev.filter((msg) => msg.id !== messageId));
|
||||
setIsProcessingExtend(false);
|
||||
};
|
||||
|
||||
reader.readAsDataURL(blob);
|
||||
|
||||
toast.success(`图像拓展完成!处理时间: ${result.processing_time?.toFixed(2) || "未知"}秒`);
|
||||
} else {
|
||||
throw new Error(result?.error || "图像拓展处理失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[图像拓展] 错误:", error);
|
||||
console.log("[图像拓展] 外层 catch 执行,重置 isProcessingExtend");
|
||||
toast.error(
|
||||
getUserFacingRequestErrorMessage(error, "图像拓展失败,请稍后重试", {
|
||||
rateLimitMessage: getFriendlyRateLimitMessage("当前图像拓展请求过于频繁,请稍后再试"),
|
||||
})
|
||||
);
|
||||
setMessages((prev) => prev.filter((msg) => msg.id !== messageId));
|
||||
} finally {
|
||||
console.log("[图像拓展] finally 块执行,重置 isProcessingExtend");
|
||||
setIsProcessingExtend(false);
|
||||
}
|
||||
};
|
||||
|
||||
@ -1205,6 +1205,7 @@ export function ImageAnnotator({
|
||||
body: JSON.stringify({
|
||||
image_url: originalImageUrl, // 直接发送图片URL
|
||||
prompt: "画图标注-图像拓展处理", // 可选:添加提示词
|
||||
includeBase64: false,
|
||||
}),
|
||||
});
|
||||
|
||||
@ -1228,9 +1229,7 @@ export function ImageAnnotator({
|
||||
setProcessedImageUrl(null);
|
||||
}
|
||||
// 优先使用返回的base64,如果没有则使用thumbnail_url或original_url
|
||||
if (result.base64) {
|
||||
setProcessedImageBase64(result.base64);
|
||||
}
|
||||
setProcessedImageBase64(result.base64 || null);
|
||||
setProcessType("extend");
|
||||
|
||||
toast.success(`图像拓展完成!图片已保存到相册。处理时间: ${result.processing_time?.toFixed(2) || "未知"}秒`);
|
||||
|
||||
@ -23,12 +23,14 @@ export interface ExecuteExtendJobParams {
|
||||
messageId?: string | null;
|
||||
xScale?: number;
|
||||
yScale?: number;
|
||||
includeBase64?: boolean;
|
||||
skipGallerySave?: boolean;
|
||||
sourceImageUrl?: string | null;
|
||||
requestId?: string | null;
|
||||
}
|
||||
|
||||
export interface ExecuteExtendJobResult {
|
||||
image_base64: string;
|
||||
image_base64?: string;
|
||||
thumbnail_url: string;
|
||||
original_url: string;
|
||||
processing_time: number;
|
||||
@ -221,8 +223,10 @@ export async function executeExtendJob(
|
||||
prompt = "图像拓展处理",
|
||||
chatId = null,
|
||||
messageId = null,
|
||||
includeBase64 = false,
|
||||
skipGallerySave = false,
|
||||
sourceImageUrl = imageUrl || null,
|
||||
requestId = null,
|
||||
} = params;
|
||||
|
||||
if (!imageUrl && !imageBase64) {
|
||||
@ -250,8 +254,15 @@ export async function executeExtendJob(
|
||||
}
|
||||
|
||||
const buffer = Buffer.from(await imageResponse.arrayBuffer());
|
||||
const imageBase64Result = buffer.toString("base64");
|
||||
const imageBase64Result = includeBase64 ? buffer.toString("base64") : undefined;
|
||||
const uploadResult = await uploadBufferPairToS3(buffer);
|
||||
console.log("[Extend Service] Result uploaded:", {
|
||||
requestId,
|
||||
includeBase64,
|
||||
inputImageUrl: inputImageUrl.slice(0, 120),
|
||||
extendedImageUrl: extendedImageUrl.slice(0, 120),
|
||||
outputBytes: buffer.length,
|
||||
});
|
||||
let mediaAssetId: string | null = null;
|
||||
let generatedImageId: string | null = null;
|
||||
|
||||
|
||||
@ -487,6 +487,7 @@ export const imageGenerationService = {
|
||||
body: JSON.stringify({
|
||||
image_url: imageUrl,
|
||||
prompt: normalizedOptions.prompt || '图像拓展处理',
|
||||
includeBase64: false,
|
||||
...(typeof normalizedOptions.xScale === 'number'
|
||||
? { xScale: normalizedOptions.xScale }
|
||||
: {}),
|
||||
@ -499,7 +500,7 @@ export const imageGenerationService = {
|
||||
if (data.success && data.original_url) {
|
||||
return { success: true, data: { url: data.original_url } };
|
||||
}
|
||||
return { success: false, error: data.error || '图片扩展失败' };
|
||||
return { success: false, error: data.error || data.detail || '图片扩展失败' };
|
||||
} catch (error) {
|
||||
console.error('[Canvas] 图片扩展失败:', error);
|
||||
return { success: false, error: '图片扩展失败' };
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user