提示词面板引用显示图片缩略图规划
将提示词面板输入框从 textarea 改造为 contentEditable,让 @ 引用直接显示真实图片缩略图
提示词面板 @ 引用显示真实图片缩略图
背景 / 要解决的问题
提示词面板(CanvasNodePromptPanel)的输入框里,@ 引用一张图片后,显示的是蓝色文字 chip「图片1」。期望直接显示这张图片的真实缩略图,而不是文字编号。
当前该输入框是原生 <textarea>,只能承载纯文本,无法内嵌图片。要显示缩略图,必须改用 contentEditable(可编辑 div),把引用做成带 <img> 的原子小卡片。项目里「组装提示词」组件(CanvasConfigComposer)已经是这套 contentEditable + 图片 chip 的成熟实现,可作蓝本。
关键约束(为什么不能直接改现有组件)
CanvasResourceMentionTextarea 被两处复用:
- 提示词面板
web/src/components/canvas/canvas-node-prompt-panel.tsx—— 本次要改的对象。highlightLabels默认 true,value 是提示词字符串,引用以 label 文本「图片1」嵌在其中,直接发给 AI(配合web/src/lib/image-reference-prompt.ts的「参考图片编号:图片1」前缀)。 - 节点内文本编辑
web/src/components/canvas/canvas-node.tsx(isEditingContent分支)——highlightLabels={false},通过ref调用textarea.setSelectionRange(...)等 textarea 专有 API。若把组件改成 contentEditable,这处会直接崩。
结论:新建一个 contentEditable chip 输入组件,只替换提示词面板那一处;
CanvasResourceMentionTextarea(textarea 版)保持不动,继续服务节点内文本编辑。
方案:新增 CanvasPromptChipInput 组件
新文件 web/src/components/canvas/canvas-prompt-chip-input.tsx,参照 canvas-config-composer.tsx 的 contentEditable 模式,但有一处核心差异:
- 「组装提示词」把引用序列化成
@[node:UUID]标记; - 本组件的 value 必须保持引用的 label 文本(如「图片1」),这样发给生成的提示词语义不变。因此 chip ↔ 文本的序列化用 label 文本,不是 node 标记。
组件契约(与现用法一致)
Props:value: string、references: CanvasResourceReference[]、onChange(value)、onSubmit?()、className、style、placeholder。图片缩略图取 reference.previewUrl(即 node.metadata.content,已存在于 CanvasResourceReference)。
实现要点(大量复用 composer 的做法)
- DOM ← value(未聚焦时重建):仿
CanvasConfigComposer的重建 effect。用 active label 列表(references.filter(active).map(label),按长度降序)把 value 字符串切成「文本片段 + 命中 label」,命中处插入 chip。聚焦时不重建(document.activeElement === editor直接 return),避免与输入 / IME 打架。 - chip 渲染:仿
createReferenceChip。image 类型 →size-6的<img src={reference.previewUrl}>;其它类型 → 截断文字。chip 为contentEditable="false"的<span>,用dataset.refLabel存 label 以便序列化。 - value ← DOM(syncFromEditor):仿
serializeNodes,但 chip 序列化为dataset.refLabel(label 文本)而非 node 标记;文本节点原样拼接,<br>→\n。 - @ 菜单:复用
textBeforeCaret+/@([^\s@]*)$/检测;菜单定位改用getSelection().getRangeAt(0).getBoundingClientRect()(比 textarea 的镜像 div 更简单、天然贴着光标)。菜单 UI 沿用现有MentionMenu的视觉(缩略图 + label + 副标题)。 - 原子删除:直接复用 composer 的
deleteAdjacentReference/adjacentReferenceNode/findReferenceSibling——contentEditable +contentEditable="false"chip 天然整体删除。可把这几个通用 DOM 助手连同textBeforeCaret/placeCaretAtEnd/closestEditor复制进新组件(保持 composer 不动,规避回归风险)。 - 回车发送 + IME:复用
isPlainEnterKey/isImeComposing(@/lib/keyboard-event)与onCompositionStart/End,发送时调用onSubmit。 - 占位符 & 样式:空值时显示 placeholder(absolute 占位层);容器套用面板传入的
className/style(min-h-40、透明底、无边框,与当前一致)。
替换点
canvas-node-prompt-panel.tsx 里把 <CanvasResourceMentionTextarea .../> 换成 <CanvasPromptChipInput .../>(同样的 value/references/onChange/onSubmit/className/style/placeholder)。其余(模型选择、设置、生成按钮)不动。
不改动
CanvasResourceMentionTextarea(textarea 版)保留,节点内文本编辑继续用它。- 生成链路、
image-reference-prompt.ts前缀逻辑不变(value 仍是 label 文本)。
影响文件
- 新增:
web/src/components/canvas/canvas-prompt-chip-input.tsx - 修改:
web/src/components/canvas/canvas-node-prompt-panel.tsx(仅替换输入框组件 + 相应 import)
验证
npx tsc --noEmit通过。- dev server 刷新后:
- 连一张图片到图片 / 视频节点 → 面板输入框输入
@→ 菜单贴着光标弹出 → 选中 → 输入框内显示该图的真实缩略图 chip(不再是「图片1」文字)。 - 光标在 chip 后按一次 Backspace → 整个 chip 一次删除。
- 普通文字 + chip 混排,回车触发生成;确认发给生成的 prompt 里引用仍是「图片1」文本(生成结果正常引用到图)。
- 中文输入法输入不吞字、不误触发发送。
- 回归:双击文本节点进入内文本编辑仍正常(未受影响)。
- 连一张图片到图片 / 视频节点 → 面板输入框输入