diff --git a/components/canvas/CanvasChatPanel.tsx b/components/canvas/CanvasChatPanel.tsx index 2dc5fe4..1ab713b 100644 --- a/components/canvas/CanvasChatPanel.tsx +++ b/components/canvas/CanvasChatPanel.tsx @@ -121,7 +121,42 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF n => selectedNodeIds.includes(n.id) && n.type === 'image' && (n.data as any)?.src ); - // 当选中画布中的图片时,自动添加到附件 + // 🆕 辅助函数:在 contenteditable 中插入图片标签 + const insertImageTagInEditor = useCallback((url: string, name: string, index: number) => { + const editableDiv = textareaRef.current as HTMLDivElement | null; + if (!editableDiv || !editableDiv.isContentEditable) return; + + // 检查是否已存在该 URL 的标签 + const existing = editableDiv.querySelector(`[data-attachment-url="${url}"]`); + if (existing) return; + + const tagSpan = document.createElement('span'); + tagSpan.contentEditable = 'false'; + tagSpan.setAttribute('data-attachment-index', String(index)); + tagSpan.setAttribute('data-attachment-url', url); + tagSpan.className = 'inline-flex items-center gap-1 bg-muted/60 hover:bg-muted rounded-full pl-1 pr-2 py-0.5 mx-0.5 align-middle cursor-default select-none'; + const shortName = name.length > 10 ? name.substring(0, 8) + '...' : name; + tagSpan.innerHTML = ` + + ${shortName} + `; + + // 在光标位置插入,或添加到末尾 + const selection = window.getSelection(); + if (selection && selection.rangeCount > 0 && editableDiv.contains(selection.anchorNode)) { + const range = selection.getRangeAt(0); + range.deleteContents(); + range.insertNode(tagSpan); + range.setStartAfter(tagSpan); + range.setEndAfter(tagSpan); + selection.removeAllRanges(); + selection.addRange(range); + } else { + editableDiv.appendChild(tagSpan); + } + }, []); + + // 当选中画布中的图片时,自动添加到附件并插入标签 useEffect(() => { if (selectedImageNodes.length > 0) { const newAttachments: Attachment[] = selectedImageNodes.map(node => { @@ -138,12 +173,16 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF const existingUrls = new Set(prev.map(a => a.url)); const toAdd = newAttachments.filter(a => !existingUrls.has(a.url)); if (toAdd.length > 0) { + // 插入图片标签到编辑器 + toAdd.forEach((att, i) => { + insertImageTagInEditor(att.url, att.name, prev.length + i); + }); return [...prev, ...toAdd]; } return prev; }); } - }, [selectedImageNodes.length, selectedNodeIds]); + }, [selectedImageNodes.length, selectedNodeIds, insertImageTagInEditor]); // 获取模型列表 const { data: modelsData } = useSWR<{ @@ -534,12 +573,20 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF firstImageUrl = imageUrl; } - // 添加到附件列表 - setAttachments(prev => [...prev, { - url: imageUrl, - name: file.name, - contentType: file.type, - }]); + // 添加到附件列表并在编辑器中插入标签 + setAttachments(prev => { + const newIndex = prev.length; + // 在编辑器中插入图片标签 + setTimeout(() => { + insertImageTagInEditor(imageUrl, file.name, newIndex); + (textareaRef.current as HTMLDivElement | null)?.focus(); + }, 0); + return [...prev, { + url: imageUrl, + name: file.name, + contentType: file.type, + }]; + }); // 立即将图片添加到画布 const existingNodes = nodes.length; @@ -877,52 +924,59 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF - {/* 底部输入区域 - Redesigned */} + {/* 底部输入区域 - 混合输入(图片标签+文字可自由混合) */}
- {/* 混合输入区域:使用 contenteditable 实现图片标签和文字混合 */} -
- {/* 图片标签显示在输入框上方 */} - {attachments.length > 0 && ( -
- {attachments.map((att, index) => ( -
- - - {att.name || 'Image'} - - -
- ))} -
- )} - - {/* 文本输入 */} -