fix: 多图支持和画布自动命名模型ID映射
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
96e4c8ebe0
commit
94640002d0
@ -64,8 +64,18 @@ export async function POST(request: NextRequest) {
|
|||||||
baseURL: provider.baseUrl,
|
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({
|
const { text } = await generateText({
|
||||||
model: openai(visionModel.modelId),
|
model: openai(actualModelId),
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
|
|||||||
@ -356,41 +356,76 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
|||||||
// 标记是否已经为当前画布设置过标题(避免重复调用)
|
// 标记是否已经为当前画布设置过标题(避免重复调用)
|
||||||
const hasSetCanvasTitle = useRef(false);
|
const hasSetCanvasTitle = useRef(false);
|
||||||
|
|
||||||
// 获取最近的图片 URL(用于自动附加)
|
// 获取最近的图片 URL(支持多张图片)
|
||||||
const getRecentImageUrl = useCallback((): string | null => {
|
const getRecentImageUrls = useCallback((): string[] => {
|
||||||
// 1. 优先使用画布中选中的图片
|
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) {
|
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. 其次使用画布中最近添加的图片
|
// 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. 最后从历史对话中查找图片
|
|
||||||
for (let i = messages.length - 1; i >= 0; i--) {
|
for (let i = messages.length - 1; i >= 0; i--) {
|
||||||
const msg = messages[i];
|
const msg = messages[i];
|
||||||
|
const msgUrls: string[] = [];
|
||||||
|
|
||||||
// 检查用户消息的附件
|
// 检查用户消息的附件
|
||||||
if (msg.role === 'user' && (msg as any).attachments?.length > 0) {
|
if (msg.role === 'user' && (msg as any).attachments?.length > 0) {
|
||||||
const attachment = (msg as any).attachments[0];
|
for (const attachment of (msg as any).attachments) {
|
||||||
if (attachment.url && attachment.contentType?.startsWith('image/')) {
|
if (attachment.url && attachment.contentType?.startsWith('image/')) {
|
||||||
return attachment.url;
|
msgUrls.push(attachment.url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查用户消息的 parts
|
// 检查用户消息的 parts
|
||||||
if (msg.role === 'user' && (msg as any).parts) {
|
if (msg.role === 'user' && (msg as any).parts) {
|
||||||
for (const part of (msg as any).parts) {
|
for (const part of (msg as any).parts) {
|
||||||
if (part.type === 'file' && part.url && part.mediaType?.startsWith('image/')) {
|
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]);
|
}, [selectedImageNodes, nodes, messages]);
|
||||||
|
|
||||||
// 处理发送(图片已在上传时添加到画布,这里只发送消息)
|
// 处理发送(图片已在上传时添加到画布,这里只发送消息)
|
||||||
@ -404,18 +439,20 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
|||||||
// 构建 parts 数组:文件在前,文本在后(与 multimodal-input 一致)
|
// 构建 parts 数组:文件在前,文本在后(与 multimodal-input 一致)
|
||||||
const parts: any[] = [];
|
const parts: any[] = [];
|
||||||
|
|
||||||
// 🆕 如果没有附件,自动使用最近的图片
|
// 🆕 如果没有附件,自动使用最近的图片(支持多张)
|
||||||
let effectiveAttachments = [...attachments];
|
let effectiveAttachments = [...attachments];
|
||||||
if (effectiveAttachments.length === 0 && input.trim()) {
|
if (effectiveAttachments.length === 0 && input.trim()) {
|
||||||
const recentImageUrl = getRecentImageUrl();
|
const recentImageUrls = getRecentImageUrls();
|
||||||
if (recentImageUrl) {
|
if (recentImageUrls.length > 0) {
|
||||||
// 自动添加最近的图片作为附件
|
// 自动添加所有最近的图片作为附件
|
||||||
effectiveAttachments.push({
|
recentImageUrls.forEach((url, index) => {
|
||||||
url: recentImageUrl,
|
effectiveAttachments.push({
|
||||||
name: '最近图片',
|
url: url,
|
||||||
contentType: 'image/jpeg',
|
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("");
|
setInput("");
|
||||||
setAttachments([]);
|
setAttachments([]);
|
||||||
}, [input, attachments, selectedModelId, sendMessage, getRecentImageUrl]);
|
}, [input, attachments, selectedModelId, sendMessage, getRecentImageUrls]);
|
||||||
|
|
||||||
// 处理文件选择 - 上传后立即添加到画布
|
// 处理文件选择 - 上传后立即添加到画布
|
||||||
const handleFileSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFileSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@ -677,7 +714,8 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
|||||||
|
|
||||||
{messages.map((message) => {
|
{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) {
|
if (part.type === 'text' && part.text) {
|
||||||
return {
|
return {
|
||||||
...part,
|
...part,
|
||||||
@ -687,15 +725,28 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
|||||||
return part;
|
return part;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 检查清理后是否有内容
|
// 检查清理后是否有实际内容(更严格的检查)
|
||||||
const hasContent = cleanedParts?.some((part: any) => {
|
let hasContent = false;
|
||||||
if (part.type === 'text') return part.text && part.text.trim().length > 0;
|
for (const part of cleanedParts) {
|
||||||
if (part.type === 'file' || part.type === 'image') return part.url;
|
if (part.type === 'text' && part.text && part.text.trim().length > 0) {
|
||||||
return true;
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user