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,
|
||||
});
|
||||
|
||||
// 🆕 模型 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',
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user