diff --git a/app/(chat)/api/chat/route.ts b/app/(chat)/api/chat/route.ts index 737b5f8..8687727 100644 --- a/app/(chat)/api/chat/route.ts +++ b/app/(chat)/api/chat/route.ts @@ -124,6 +124,54 @@ function applyHiddenPromptToMessages(messages: ChatMessage[], hiddenPrompt?: str return cloned; } +function guessImageMediaType(url: string): string { + const cleanUrl = url.split("?")[0]?.toLowerCase() || ""; + if (cleanUrl.endsWith(".jpg") || cleanUrl.endsWith(".jpeg")) return "image/jpeg"; + if (cleanUrl.endsWith(".webp")) return "image/webp"; + if (cleanUrl.endsWith(".gif")) return "image/gif"; + if (cleanUrl.endsWith(".bmp")) return "image/bmp"; + if (cleanUrl.endsWith(".svg")) return "image/svg+xml"; + return "image/png"; +} + +function injectHistoryImagesToLastUserMessage( + messages: ChatMessage[], + historyImages: string[] +): ChatMessage[] { + if (!historyImages || historyImages.length === 0) return messages; + + let lastUserIndex = -1; + for (let i = messages.length - 1; i >= 0; i--) { + if (messages[i]?.role === "user") { + lastUserIndex = i; + break; + } + } + + if (lastUserIndex < 0) return messages; + + const lastUserMessage = messages[lastUserIndex]; + const hasExistingImage = lastUserMessage.parts?.some( + (p: any) => p.type === "file" || p.type === "image" || p.type === "image_url" + ); + if (hasExistingImage) return messages; + + const imageParts = historyImages.map((url, index) => ({ + type: "file" as const, + url, + name: `history-image-${index + 1}`, + mediaType: guessImageMediaType(url), + mimeType: guessImageMediaType(url), + })); + + const updatedMessage: ChatMessage = { + ...lastUserMessage, + parts: [...lastUserMessage.parts, ...imageParts], + }; + + return messages.map((msg, idx) => (idx === lastUserIndex ? updatedMessage : msg)); +} + /** * 自定义消息转换函数 * 注意:gemini-image 和 huanke-gemini 都需要 URL 格式(不是 Base64) @@ -823,7 +871,8 @@ export async function POST(request: Request) { // 只传递最后一条用户消息,避免上下文历史干扰生图效果 const lastUserMessage = uiMessagesForModel.filter((m) => m.role === "user").pop(); - const messagesForGemini = lastUserMessage ? [lastUserMessage] : uiMessagesForModel; + let messagesForGemini = lastUserMessage ? [lastUserMessage] : uiMessagesForModel; + messagesForGemini = injectHistoryImagesToLastUserMessage(messagesForGemini, smartAutoHistoryImages); console.log("[Chat API] Gemini: Using only last user message (no history context)"); // 转换消息:gemini-image 需要 base64 格式,huanke-gemini 需要 URL 格式 @@ -971,7 +1020,10 @@ export async function POST(request: Request) { console.log("[Chat API] Executing Volcano Vision failover..."); // 转换消息(火山引擎会自动处理 Base64) - const volcanoMessages = await convertMessagesWithImages(uiMessagesForModel, "volcano"); + const volcanoMessages = await convertMessagesWithImages( + injectHistoryImagesToLastUserMessage(uiMessagesForModel, smartAutoHistoryImages), + "volcano" + ); // 调用火山引擎生成(故障转移时只生成 1 张,保持与 Gemini 一致) const volcanoResult = await (volcanoLanguageModel as any).doGenerate({ @@ -1189,32 +1241,7 @@ export async function POST(request: Request) { })); } - // 🆕 如果使用了历史图片,将其注入到消息中(支持多张) - if (smartAutoHistoryImages.length > 0 && messagesForAlibaba.length > 0) { - console.log("[Chat API] 🖼️ Injecting", smartAutoHistoryImages.length, "history image(s) into message"); - messagesForAlibaba = messagesForAlibaba.map((msg) => { - // 检查消息中是否已有图片 - const hasExistingImage = msg.parts.some( - (p: any) => p.type === "file" || p.type === "image" - ); - - if (!hasExistingImage) { - // 注入所有历史图片 - const imageParts = smartAutoHistoryImages.map((url) => ({ - type: "file" as const, - url, - mediaType: "image/png", - mimeType: "image/png", - })); - return { - ...msg, - // @ts-ignore - 类型兼容性问题,运行时正确 - parts: [...msg.parts, ...imageParts], - }; - } - return msg; - }); - } + messagesForAlibaba = injectHistoryImagesToLastUserMessage(messagesForAlibaba, smartAutoHistoryImages); const tryGenerateImage = async (modelIdToUse: string, isRetry: boolean) => { // 转换消息(阿里生图支持 URL,会自动上传 base64) @@ -1435,6 +1462,8 @@ export async function POST(request: Request) { return msg; }); } + + messagesForVolcano = injectHistoryImagesToLastUserMessage(messagesForVolcano, smartAutoHistoryImages); const volcanoStream = createUIMessageStream({ execute: async ({ writer }) => { @@ -1763,7 +1792,10 @@ export async function POST(request: Request) { console.log("[Chat API] Provider type:", (languageModel as any).provider); // 调试日志 // 转换消息,对于 Gemini 和千问视频将图片 URL 转换为 base64 - const convertedMessages = await convertMessagesWithImages(uiMessagesForModel, (languageModel as any).provider || ""); + const convertedMessages = await convertMessagesWithImages( + injectHistoryImagesToLastUserMessage(uiMessagesForModel, smartAutoHistoryImages), + (languageModel as any).provider || "" + ); console.log("[Chat API] Messages converted, count:", convertedMessages.length); // 调试日志 // 🆕 检测是否是视频模型