diff --git a/app/(chat)/api/chat/route.ts b/app/(chat)/api/chat/route.ts index a55c542b8..455693b9a 100644 --- a/app/(chat)/api/chat/route.ts +++ b/app/(chat)/api/chat/route.ts @@ -955,53 +955,82 @@ export async function POST(request: Request) { // 逻辑:只要用户在对话中没有附上新图片,就默认继续处理之前的图片 if (!hasImage && smartAutoHistoryMessagesFromDb.length > 0) { console.log("[Smart Auto Mode] 🔍 No image in current message, auto-searching history..."); - - // 从历史消息中倒序查找最近一轮包含图片的消息 - // 收集该消息中的所有图片(支持多图融合编辑) + + const explicitFollowLatestResult = /(上一张|上一轮|刚生成|最新生成|这个结果|该结果|按这个结果|继续在这张|基于这张结果|延续上一次结果)/i.test( + textContent + ); + + const latestAssistantImages: string[] = []; + const latestUserImages: string[] = []; + const pushUniqueUrl = (bucket: string[], maybeUrl?: string) => { + if (!maybeUrl || typeof maybeUrl !== "string") return; + const normalized = maybeUrl.trim(); + if (!/^https?:\/\//i.test(normalized)) return; + if (!bucket.includes(normalized)) { + bucket.push(normalized); + } + }; + + // 从历史消息中倒序查找最近的“AI生成图”和“用户上传图” for (let i = smartAutoHistoryMessagesFromDb.length - 1; i >= 0; i--) { const msg = smartAutoHistoryMessagesFromDb[i]; const parts = msg.parts as any[]; - + if (!parts) continue; - - // 查找AI生成的图片(从Markdown中提取所有图片) - if (msg.role === "assistant") { + + if (msg.role === "assistant" && latestAssistantImages.length === 0) { for (const part of parts) { if (part.type === "text" && part.text) { - // 从 markdown 中优先提取原图 URL,避免把缩略图当作下一轮输入导致画质下降 const parsedImageUrls = extractOriginalImageUrlsFromText(part.text); for (const parsedUrl of parsedImageUrls) { - if (!smartAutoHistoryImages.includes(parsedUrl)) { - smartAutoHistoryImages.push(parsedUrl); - } + pushUniqueUrl(latestAssistantImages, parsedUrl); } } } - // 如果在这条assistant消息中找到了图片,就停止搜索(使用最近一轮的结果) - if (smartAutoHistoryImages.length > 0) { - console.log("[Smart Auto Mode] 🖼️ Found AI-generated images:", smartAutoHistoryImages.length); - break; - } } - - // 查找用户上传的图片(收集该消息中的所有图片) - if (msg.role === "user" && smartAutoHistoryImages.length === 0) { + + if (msg.role === "user" && latestUserImages.length === 0) { for (const part of parts) { if ((part.type === "file" || part.type === "image") && part.url) { - if (!smartAutoHistoryImages.includes(part.url)) { - smartAutoHistoryImages.push(part.url); + pushUniqueUrl(latestUserImages, part.url); + } + if (part.type === "image_url" && part.image_url?.url) { + pushUniqueUrl(latestUserImages, part.image_url.url); + } + if (part.type === "text" && part.text) { + const parsedImageUrls = extractOriginalImageUrlsFromText(part.text); + for (const parsedUrl of parsedImageUrls) { + pushUniqueUrl(latestUserImages, parsedUrl); } } } - // 如果在这条user消息中找到了图片,就停止搜索 - if (smartAutoHistoryImages.length > 0) { - console.log("[Smart Auto Mode] 🖼️ Found user-uploaded images:", smartAutoHistoryImages.length); - break; - } + } + + if (latestAssistantImages.length > 0 && latestUserImages.length > 0) { + break; } } - - // 如果找到了历史图片,标记为有图片 + + if (explicitFollowLatestResult && latestAssistantImages.length > 0) { + smartAutoHistoryImages = latestAssistantImages.slice(0, 4); + console.log( + "[Smart Auto Mode] 🖼️ Using latest generated image(s) by explicit instruction:", + smartAutoHistoryImages.length + ); + } else if (latestUserImages.length > 0) { + smartAutoHistoryImages = latestUserImages.slice(0, 4); + console.log( + "[Smart Auto Mode] 🖼️ Using latest user-uploaded image(s) to avoid iterative blur:", + smartAutoHistoryImages.length + ); + } else if (latestAssistantImages.length > 0) { + smartAutoHistoryImages = latestAssistantImages.slice(0, 4); + console.log( + "[Smart Auto Mode] 🖼️ Fallback to latest generated image(s):", + smartAutoHistoryImages.length + ); + } + if (smartAutoHistoryImages.length > 0) { hasImage = true; imageUrls = smartAutoHistoryImages; @@ -1201,6 +1230,11 @@ export async function POST(request: Request) { hasUserCustomSettings: hasUserCustomImageParams, }) : requestBody.imageParams; + const defaultImageQualityParams = { + aspectRatio: "auto", + imageSize: smartAutoDetectedIntent === "image_edit" ? "4K" : "2K", + imageCount: 1, + } as const; if (shouldApplyAgentImageParams) { console.log("[Smart Auto Mode] 🎯 Effective image params:", { @@ -1564,7 +1598,11 @@ export async function POST(request: Request) { // 调用 doGenerate 获取图片(如果是备用模型,传入 overrideModelId) const result = await (languageModel as any).doGenerate({ messages: modelMessages, - imageParams: effectiveImageParams || { aspectRatio: "auto", imageSize: "2K" }, + imageParams: + effectiveImageParams || { + aspectRatio: defaultImageQualityParams.aspectRatio, + imageSize: defaultImageQualityParams.imageSize, + }, overrideModelId: isRetry ? modelIdToUse : undefined, // 🆕 使用备用模型 ID }); @@ -1930,7 +1968,12 @@ export async function POST(request: Request) { // 调用 doGenerate 获取图片,传递模型显示名称 const result = await (languageModel as any).doGenerate({ messages: modelMessages, - imageParams: effectiveImageParams || { aspectRatio: "auto", imageSize: "2K", imageCount: 1 }, + imageParams: + effectiveImageParams || { + aspectRatio: defaultImageQualityParams.aspectRatio, + imageSize: defaultImageQualityParams.imageSize, + imageCount: defaultImageQualityParams.imageCount, + }, modelDisplayName, // 🆕 传递模型显示名称 overrideModelId: isRetry ? modelIdToUse : undefined, }); @@ -2028,7 +2071,12 @@ export async function POST(request: Request) { const candidateResult = await (candidateLanguageModel as any).doGenerate({ messages: candidateMessages, - imageParams: effectiveImageParams || { aspectRatio: "auto", imageSize: "2K", imageCount: 1 }, + imageParams: + effectiveImageParams || { + aspectRatio: defaultImageQualityParams.aspectRatio, + imageSize: defaultImageQualityParams.imageSize, + imageCount: defaultImageQualityParams.imageCount, + }, modelDisplayName: candidate.model.displayName, overrideModelId: candidate.model.modelId, }); @@ -2274,7 +2322,12 @@ export async function POST(request: Request) { const tryGenerateImage = async (modelIdToUse: string, isRetry: boolean) => { const result = await (languageModel as any).doGenerate({ messages: modelMessages, - imageParams: effectiveImageParams || { aspectRatio: "auto", imageSize: "2K", imageCount: 1 }, + imageParams: + effectiveImageParams || { + aspectRatio: defaultImageQualityParams.aspectRatio, + imageSize: defaultImageQualityParams.imageSize, + imageCount: defaultImageQualityParams.imageCount, + }, overrideModelId: isRetry ? modelIdToUse : undefined, }); if (result?.finishReason === "error") { @@ -2366,7 +2419,12 @@ export async function POST(request: Request) { const candidateResult = await (candidateLanguageModel as any).doGenerate({ messages: candidateMessages, - imageParams: effectiveImageParams || { aspectRatio: "auto", imageSize: "2K", imageCount: 1 }, + imageParams: + effectiveImageParams || { + aspectRatio: defaultImageQualityParams.aspectRatio, + imageSize: defaultImageQualityParams.imageSize, + imageCount: defaultImageQualityParams.imageCount, + }, modelDisplayName: candidate.model.displayName, overrideModelId: candidate.model.modelId, }); diff --git a/components/image-preview.tsx b/components/image-preview.tsx index 1eeaacb3d..577b53f64 100644 --- a/components/image-preview.tsx +++ b/components/image-preview.tsx @@ -171,7 +171,7 @@ export function ImagePreview({ }; const { thumbnail, original, maxWidth, is4K } = parseImageUrls(); - const preferOriginalForPreview = maxWidth >= 380 && original && original !== thumbnail; + const preferOriginalForPreview = Boolean(original && original !== thumbnail); const previewSrc = preferOriginalForPreview ? original : thumbnail; const previewSrcSet = original ? `${thumbnail} 1x, ${original} 2x`