From 94640002d02576dd72a7d3d377dc6f2cf47ef8cf Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 1 Feb 2026 16:58:27 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=A4=9A=E5=9B=BE=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=92=8C=E7=94=BB=E5=B8=83=E8=87=AA=E5=8A=A8=E5=91=BD=E5=90=8D?= =?UTF-8?q?=E6=A8=A1=E5=9E=8BID=E6=98=A0=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- app/api/canvas/auto-title/route.ts | 12 ++- components/canvas/CanvasChatPanel.tsx | 123 ++++++++++++++++++-------- 2 files changed, 98 insertions(+), 37 deletions(-) diff --git a/app/api/canvas/auto-title/route.ts b/app/api/canvas/auto-title/route.ts index eb4e439..1602e8a 100644 --- a/app/api/canvas/auto-title/route.ts +++ b/app/api/canvas/auto-title/route.ts @@ -64,8 +64,18 @@ export async function POST(request: NextRequest) { baseURL: provider.baseUrl, }); + // 🆕 模型 ID 映射(处理数据库中的别名到实际 API 模型 ID) + const modelIdMapping: Record = { + 'qwen3-vl-plus': 'qwen-vl-plus', // 映射到阿里云实际模型 + 'qwen3-vl-max': 'qwen-vl-max', + 'qwen2.5-vl-plus': 'qwen-vl-plus', + }; + + const actualModelId = modelIdMapping[visionModel.modelId] || visionModel.modelId; + console.log("[Auto-title] Model ID mapping:", visionModel.modelId, "->", actualModelId); + const { text } = await generateText({ - model: openai(visionModel.modelId), + model: openai(actualModelId), messages: [ { role: 'user', diff --git a/components/canvas/CanvasChatPanel.tsx b/components/canvas/CanvasChatPanel.tsx index 11256b6..332cc49 100644 --- a/components/canvas/CanvasChatPanel.tsx +++ b/components/canvas/CanvasChatPanel.tsx @@ -356,41 +356,76 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF // 标记是否已经为当前画布设置过标题(避免重复调用) const hasSetCanvasTitle = useRef(false); - // 获取最近的图片 URL(用于自动附加) - const getRecentImageUrl = useCallback((): string | null => { - // 1. 优先使用画布中选中的图片 + // 获取最近的图片 URL(支持多张图片) + const getRecentImageUrls = useCallback((): string[] => { + const urls: string[] = []; + const addedUrls = new Set(); // 用于去重 + + // 辅助函数:添加图片 URL 并去重 + const addUrl = (url: string) => { + if (url && !addedUrls.has(url)) { + addedUrls.add(url); + urls.push(url); + } + }; + + // 1. 优先使用画布中选中的图片(支持多选) if (selectedImageNodes.length > 0) { - return (selectedImageNodes[0].data as any)?.src || null; + selectedImageNodes.forEach(node => { + const src = (node.data as any)?.src; + if (src) addUrl(src); + }); + // 如果选中了图片,只返回选中的 + if (urls.length > 0) { + console.log('[CanvasChat] 使用选中的图片:', urls.length, '张'); + return urls; + } } - // 2. 其次使用画布中最近添加的图片 - const imageNodes = nodes.filter(n => n.type === 'image' && (n.data as any)?.src); - if (imageNodes.length > 0) { - // 返回最后一个添加的图片(假设数组末尾是最新的) - return (imageNodes[imageNodes.length - 1].data as any)?.src || null; - } - - // 3. 最后从历史对话中查找图片 + // 2. 其次从历史对话中查找最近一次消息的所有图片 for (let i = messages.length - 1; i >= 0; i--) { const msg = messages[i]; + const msgUrls: string[] = []; + // 检查用户消息的附件 if (msg.role === 'user' && (msg as any).attachments?.length > 0) { - const attachment = (msg as any).attachments[0]; - if (attachment.url && attachment.contentType?.startsWith('image/')) { - return attachment.url; + for (const attachment of (msg as any).attachments) { + if (attachment.url && attachment.contentType?.startsWith('image/')) { + msgUrls.push(attachment.url); + } } } + // 检查用户消息的 parts if (msg.role === 'user' && (msg as any).parts) { for (const part of (msg as any).parts) { if (part.type === 'file' && part.url && part.mediaType?.startsWith('image/')) { - return part.url; + msgUrls.push(part.url); } } } + + // 如果这条消息有图片,返回该消息的所有图片 + if (msgUrls.length > 0) { + msgUrls.forEach(addUrl); + console.log('[CanvasChat] 使用历史对话图片:', urls.length, '张'); + return urls; + } } - return null; + // 3. 最后使用画布中最近添加的图片 + const imageNodes = nodes.filter(n => n.type === 'image' && (n.data as any)?.src); + if (imageNodes.length > 0) { + // 取最后几张(最多5张) + const recentNodes = imageNodes.slice(-5); + recentNodes.forEach(node => { + const src = (node.data as any)?.src; + if (src) addUrl(src); + }); + console.log('[CanvasChat] 使用画布图片:', urls.length, '张'); + } + + return urls; }, [selectedImageNodes, nodes, messages]); // 处理发送(图片已在上传时添加到画布,这里只发送消息) @@ -404,18 +439,20 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF // 构建 parts 数组:文件在前,文本在后(与 multimodal-input 一致) const parts: any[] = []; - // 🆕 如果没有附件,自动使用最近的图片 + // 🆕 如果没有附件,自动使用最近的图片(支持多张) let effectiveAttachments = [...attachments]; if (effectiveAttachments.length === 0 && input.trim()) { - const recentImageUrl = getRecentImageUrl(); - if (recentImageUrl) { - // 自动添加最近的图片作为附件 - effectiveAttachments.push({ - url: recentImageUrl, - name: '最近图片', - contentType: 'image/jpeg', + const recentImageUrls = getRecentImageUrls(); + if (recentImageUrls.length > 0) { + // 自动添加所有最近的图片作为附件 + recentImageUrls.forEach((url, index) => { + effectiveAttachments.push({ + url: url, + name: `图片 ${index + 1}`, + contentType: 'image/jpeg', + }); }); - console.log('[CanvasChat] 自动使用最近图片:', recentImageUrl); + console.log('[CanvasChat] 自动使用最近图片:', recentImageUrls.length, '张'); } } @@ -443,7 +480,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF setInput(""); setAttachments([]); - }, [input, attachments, selectedModelId, sendMessage, getRecentImageUrl]); + }, [input, attachments, selectedModelId, sendMessage, getRecentImageUrls]); // 处理文件选择 - 上传后立即添加到画布 const handleFileSelect = async (e: React.ChangeEvent) => { @@ -677,7 +714,8 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF {messages.map((message) => { // 🆕 清理消息中的内部标记并检查是否有实际内容 - const cleanedParts = message.parts?.map((part: any) => { + const rawParts = message.parts || []; + const cleanedParts = rawParts.map((part: any) => { if (part.type === 'text' && part.text) { return { ...part, @@ -687,15 +725,28 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF return part; }); - // 检查清理后是否有内容 - const hasContent = cleanedParts?.some((part: any) => { - if (part.type === 'text') return part.text && part.text.trim().length > 0; - if (part.type === 'file' || part.type === 'image') return part.url; - return true; - }); + // 检查清理后是否有实际内容(更严格的检查) + let hasContent = false; + for (const part of cleanedParts) { + if (part.type === 'text' && part.text && part.text.trim().length > 0) { + hasContent = true; + break; + } + if ((part.type === 'file' || part.type === 'image') && part.url) { + hasContent = true; + break; + } + } - // 没有内容则不渲染 - if (!hasContent && cleanedParts?.length > 0) { + // 同时检查附件 + const attachmentsList = (message as any).attachments || []; + if (attachmentsList.length > 0) { + hasContent = true; + } + + // 没有内容则不渲染(包括空 parts 的情况) + if (!hasContent) { + console.log('[CanvasChat] 跳过空消息:', message.id); return null; }