feat: 上传图片自动识别并命名画布

1. 使用千问VL模型识别图片主体作为画布名称
2. 第一张上传的图片自动设为画布缩略图
3. 画布列表显示缩略图预览

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Your Name 2026-02-01 12:41:01 +08:00
parent ccf610bcf8
commit 5e642165df

View File

@ -256,14 +256,20 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
// 标记是否已经为当前画布设置过标题(避免重复调用)
const hasSetCanvasTitle = useRef(false);
// 处理发送
const handleSend = useCallback(() => {
const handleSend = useCallback(async () => {
if (!input.trim() && attachments.length === 0) return;
if (!selectedModelId) {
toast.error("请选择一个模型");
return;
}
// 获取第一张图片的 URL(用于自动命名和缩略图)
const firstImageAttachment = attachments.find(a => a.contentType?.startsWith('image/'));
// 构建 parts 数组:文件在前,文本在后(与 multimodal-input 一致)
const parts: any[] = [];
@ -313,9 +319,50 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
toast.success(`已将 ${attachments.length} 张图片添加到画布`);
}
// 如果是第一次上传图片,自动识别并设置画布标题和缩略图
if (firstImageAttachment && !hasSetCanvasTitle.current) {
hasSetCanvasTitle.current = true;
// 异步调用 AI 识别图片主体并更新画布
(async () => {
try {
// 调用 auto-title API 识别图片主体
const response = await fetch('/api/canvas/auto-title', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ imageUrl: firstImageAttachment.url }),
});
if (response.ok) {
const data = await response.json();
const title = data.title || '未命名画布';
// 提取纯 UUID(去掉 canvas- 前缀)
const rawId = chatId.startsWith('canvas-')
? chatId.replace('canvas-', '')
: chatId;
// 更新画布标题和缩略图
await fetch(`/api/canvas/${rawId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: title,
thumbnail: firstImageAttachment.url,
}),
});
console.log('[CanvasChat] 画布已自动命名为:', title);
}
} catch (error) {
console.error('[CanvasChat] 自动命名失败:', error);
}
})();
}
setInput("");
setAttachments([]);
}, [input, attachments, selectedModelId, sendMessage, addNode, nodes.length]);
}, [input, attachments, selectedModelId, sendMessage, addNode, nodes.length, chatId]);
// 处理文件选择
const handleFileSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {