diff --git a/app/(chat)/api/chat/route.ts b/app/(chat)/api/chat/route.ts index 7742b8e7d..aac60b8ad 100644 --- a/app/(chat)/api/chat/route.ts +++ b/app/(chat)/api/chat/route.ts @@ -418,7 +418,7 @@ export async function POST(request: Request) { } } - console.log("[Smart Auto Mode] Input:", { textContent: textContent.substring(0, 100), hasImage, imageCount: imageUrls.length, fromHistory: !!smartAutoHistoryImage }); + console.log("[Smart Auto Mode] Input:", { textContent: textContent.substring(0, 100), hasImage, imageCount: imageUrls.length, fromHistory: smartAutoHistoryImages.length > 0 }); // 2. 调用智能分析器 const { analyzeIntent } = await import("@/lib/ai/smart-analyzer"); @@ -1019,10 +1019,12 @@ export async function POST(request: Request) { const imageParts = smartAutoHistoryImages.map((url) => ({ type: "file" as const, url, + mediaType: "image/png", mimeType: "image/png", })); return { ...msg, + // @ts-ignore - 类型兼容性问题,运行时正确 parts: [...msg.parts, ...imageParts], }; } @@ -1030,6 +1032,36 @@ export async function POST(request: Request) { }); } + // 🆕 如果是智能模式且有对话回复,先发送对话消息 + if (smartAutoFriendlyMessage) { + const chatMessageId = generateUUID(); + console.log("[Chat API] 💬 Sending conversational reply first:", smartAutoFriendlyMessage); + + // 写入对话消息到流 + writer.write({ + type: "data-appendMessage", + data: JSON.stringify({ + id: chatMessageId, + role: "assistant", + parts: [{ type: "text", text: smartAutoFriendlyMessage }], + }), + }); + + // 保存对话消息到数据库 + await saveMessages({ + messages: [ + { + id: chatMessageId, + chatId: id, + role: "assistant", + parts: [{ type: "text", text: smartAutoFriendlyMessage }], + attachments: [], + createdAt: new Date(), + }, + ], + }); + } + // 转换消息(阿里生图支持 URL,会自动上传 base64) const modelMessages = await convertMessagesWithImages(messagesForAlibaba, "alibaba-image"); @@ -1043,27 +1075,20 @@ export async function POST(request: Request) { const imageMarkdown = result.text || "图片生成失败"; console.log("[Chat API] Alibaba generated, length:", imageMarkdown.length); - // 🆕 如果是智能模式,在图片前加上友好消息 - let finalMessage = imageMarkdown; - if (smartAutoFriendlyMessage) { - finalMessage = `${smartAutoFriendlyMessage}\n\n${imageMarkdown}`; - console.log("[Chat API] Added friendly message:", smartAutoFriendlyMessage); - } - - // 生成消息 ID + // 生成消息 ID(图片消息) const messageId = generateUUID(); - // 写入消息到流 + // 写入图片消息到流 writer.write({ type: "data-appendMessage", data: JSON.stringify({ id: messageId, role: "assistant", - parts: [{ type: "text", text: finalMessage }], + parts: [{ type: "text", text: imageMarkdown }], }), }); - // 保存消息到数据库 + // 保存图片消息到数据库 try { await saveMessages({ messages: [ @@ -1071,7 +1096,7 @@ export async function POST(request: Request) { id: messageId, chatId: id, role: "assistant", - parts: [{ type: "text", text: finalMessage }], + parts: [{ type: "text", text: imageMarkdown }], attachments: [], createdAt: new Date(), videoStorageStatus: null, @@ -1175,6 +1200,36 @@ export async function POST(request: Request) { const volcanoStream = createUIMessageStream({ execute: async ({ writer }) => { try { + // 🆕 如果是智能模式且有对话回复,先发送对话消息 + if (smartAutoFriendlyMessage) { + const chatMessageId = generateUUID(); + console.log("[Chat API] 💬 Volcano: Sending conversational reply first:", smartAutoFriendlyMessage); + + // 写入对话消息到流 + writer.write({ + type: "data-appendMessage", + data: JSON.stringify({ + id: chatMessageId, + role: "assistant", + parts: [{ type: "text", text: smartAutoFriendlyMessage }], + }), + }); + + // 保存对话消息到数据库 + await saveMessages({ + messages: [ + { + id: chatMessageId, + chatId: id, + role: "assistant", + parts: [{ type: "text", text: smartAutoFriendlyMessage }], + attachments: [], + createdAt: new Date(), + }, + ], + }); + } + // 转换消息,火山引擎支持 URL 和 base64 const modelMessages = await convertMessagesWithImages(messagesForVolcano, "volcano"); @@ -1187,10 +1242,10 @@ export async function POST(request: Request) { console.log("[Chat API] Volcano Vision generated, length:", imageMarkdown.length); console.log("[Chat API] Volcano Vision image markdown:", imageMarkdown.substring(0, 200)); - // 生成一个统一的消息 ID + // 生成一个统一的消息 ID(图片消息) const messageId = generateUUID(); - // 写入消息到流 - 使用正确的 parts 格式 + // 写入图片消息到流 writer.write({ type: "data-appendMessage", data: JSON.stringify({ @@ -1200,7 +1255,7 @@ export async function POST(request: Request) { }), }); - // 保存消息到数据库 + // 保存图片消息到数据库 try { await saveMessages({ messages: [ diff --git a/components/messages.tsx b/components/messages.tsx index d160331e4..305731deb 100644 --- a/components/messages.tsx +++ b/components/messages.tsx @@ -167,21 +167,25 @@ function PureMessages({ ); // 判断是否是图片生成模型(包括 Gemini、火山、阿里) - // 注意:智能自动模式不在这里判断,因为后端还在分析意图,可能最终是对话而不是生图 const isImageGenModel = isGeminiModel || isVolcanoVisionModel || isAlibabaImageModel; + // 🆕 智能自动模式:检查用户消息是否包含图片,如果有则视为图片编辑场景 + const isAutoModeWithImage = selectedModelId === "auto" && lastMessage?.role === "user" && + lastMessage.parts?.some((p: any) => p.type === "file" || p.type === "image"); + // 根据模型类型判断是否有有效内容 let hasContent = false; if (isQianwenVideoModel || isVideoModel) { hasContent = hasVideoContent ?? false; // 视频模型:只有真正的视频才算有内容 - } else if (isImageGenModel) { - hasContent = hasImageContent ?? false; // 图片模型:只有图片才算有内容 + } else if (isImageGenModel || isAutoModeWithImage) { + hasContent = hasImageContent ?? false; // 图片模型或智能自动+图片:只有图片才算有内容 } else { hasContent = hasTextContent ?? false; // 其他模型:任何文本都算有内容 } // 根据模型类型设置延迟时间(视频模型 60 秒,图片模型 4 秒,其他 6 秒) - const delayTime = (isQianwenVideoModel || isVideoModel) ? 60000 : isImageGenModel ? 4000 : 6000; + // 🆕 智能自动模式如果有图片,也按图片模型处理 + const delayTime = (isQianwenVideoModel || isVideoModel) ? 60000 : (isImageGenModel || isAutoModeWithImage) ? 4000 : 6000; // 如果是 submitted 状态,立即显示 if (status === "submitted") { @@ -314,12 +318,21 @@ function PureMessages({ const showAlibabaImage = modelType === "alibaba-image" || (!modelType && isAlibabaImageModel); const showVideo = modelType === "volcano-video" || modelType === "qianwen-video" || (!modelType && (isQianwenVideoModel || isVideoModel)); + // 🆕 检查智能自动模式是否有图片(需要显示生图占位图) + const isAutoMode = selectedModelId === "auto"; + const userMessageHasImage = messages.length > 0 && + messages[messages.length - 1]?.role === "user" && + messages[messages.length - 1]?.parts?.some((p: any) => p.type === "file" || p.type === "image"); + const showAutoModeImage = isAutoMode && userMessageHasImage; + if (showVideo) return ; if (showGemini) return ; if (showVolcano) return ; // 🆕 阿里生图模型使用通用组件,橙色主题,最多 6 张,传递自定义模型名称 if (showAlibabaImage) return ; - // 智能自动模式会先显示通用思考动画,等后端确定模型后自然切换 + // 🆕 智能自动模式:如果用户发送了图片,显示智能处理占位图 + if (showAutoModeImage) return ; + // 其他情况显示通用思考动画 return ; })()} diff --git a/lib/ai/model-router.ts b/lib/ai/model-router.ts index 4ccd8625a..9261ff557 100644 --- a/lib/ai/model-router.ts +++ b/lib/ai/model-router.ts @@ -5,7 +5,8 @@ * 核心逻辑:图片编辑场景优先使用 alibaba-image (灵魂4.5) */ -import { getEnabledAiModelsWithProvider } from "@/lib/db/queries"; +// 使用动态导入避免循环依赖 +// import { getEnabledAiModelsWithProvider } from "@/lib/db/queries"; // 路由结果类型 export interface RouteResult { @@ -107,6 +108,9 @@ export async function routeToModel(params: { console.log("[Model Router] Routing:", { intent, subIntent, hasImage }); + // 使用动态导入避免循环依赖 + const { getEnabledAiModelsWithProvider } = await import("@/lib/db/queries"); + // 获取所有可用模型 const allModels = await getEnabledAiModelsWithProvider(); console.log("[Model Router] Available models:", allModels.length);