fix: 多图支持和画布自动命名模型ID映射

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Your Name 2026-02-01 16:58:27 +08:00
parent 96e4c8ebe0
commit 94640002d0
2 changed files with 98 additions and 37 deletions

View File

@ -64,8 +64,18 @@ export async function POST(request: NextRequest) {
baseURL: provider.baseUrl,
});
// 🆕 模型 ID 映射(处理数据库中的别名到实际 API 模型 ID)
const modelIdMapping: Record<string, string> = {
'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',

View File

@ -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<string>(); // 用于去重
// 辅助函数:添加图片 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<HTMLInputElement>) => {
@ -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;
}