---
title: 提示词面板引用显示图片缩略图规划
description: 将提示词面板输入框从 textarea 改造为 contentEditable，让 @ 引用直接显示真实图片缩略图
---

# 提示词面板 @ 引用显示真实图片缩略图

## 背景 / 要解决的问题

提示词面板（`CanvasNodePromptPanel`）的输入框里，`@` 引用一张图片后，显示的是蓝色文字 chip「图片1」。期望**直接显示这张图片的真实缩略图**，而不是文字编号。

当前该输入框是原生 `<textarea>`，只能承载纯文本，无法内嵌图片。要显示缩略图，必须改用 `contentEditable`（可编辑 div），把引用做成带 `<img>` 的原子小卡片。项目里「组装提示词」组件（`CanvasConfigComposer`）已经是这套 contentEditable + 图片 chip 的成熟实现，可作蓝本。

## 关键约束（为什么不能直接改现有组件）

`CanvasResourceMentionTextarea` 被两处复用：

1. **提示词面板** `web/src/components/canvas/canvas-node-prompt-panel.tsx` —— 本次要改的对象。`highlightLabels` 默认 true，value 是提示词字符串，引用以 label 文本「图片1」嵌在其中，**直接发给 AI**（配合 `web/src/lib/image-reference-prompt.ts` 的「参考图片编号：图片1」前缀）。
2. **节点内文本编辑** `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 的做法）

1. **DOM ← value（未聚焦时重建）**：仿 `CanvasConfigComposer` 的重建 effect。用 active label 列表（`references.filter(active).map(label)`，按长度降序）把 value 字符串切成「文本片段 + 命中 label」，命中处插入 chip。聚焦时不重建（`document.activeElement === editor` 直接 return），避免与输入 / IME 打架。
2. **chip 渲染**：仿 `createReferenceChip`。image 类型 → `size-6` 的 `<img src={reference.previewUrl}>`；其它类型 → 截断文字。chip 为 `contentEditable="false"` 的 `<span>`，用 `dataset.refLabel` 存 label 以便序列化。
3. **value ← DOM（syncFromEditor）**：仿 `serializeNodes`，但 chip 序列化为 `dataset.refLabel`（label 文本）而非 node 标记；文本节点原样拼接，`<br>` → `\n`。
4. **@ 菜单**：复用 `textBeforeCaret` + `/@([^\s@]*)$/` 检测；菜单定位改用 `getSelection().getRangeAt(0).getBoundingClientRect()`（比 textarea 的镜像 div 更简单、天然贴着光标）。菜单 UI 沿用现有 `MentionMenu` 的视觉（缩略图 + label + 副标题）。
5. **原子删除**：直接复用 composer 的 `deleteAdjacentReference` / `adjacentReferenceNode` / `findReferenceSibling`——contentEditable + `contentEditable="false"` chip 天然整体删除。可把这几个通用 DOM 助手连同 `textBeforeCaret` / `placeCaretAtEnd` / `closestEditor` 复制进新组件（保持 composer 不动，规避回归风险）。
6. **回车发送 + IME**：复用 `isPlainEnterKey` / `isImeComposing`（`@/lib/keyboard-event`）与 `onCompositionStart/End`，发送时调用 `onSubmit`。
7. **占位符 & 样式**：空值时显示 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）

## 验证

1. `npx tsc --noEmit` 通过。
2. dev server 刷新后：
   - 连一张图片到图片 / 视频节点 → 面板输入框输入 `@` → 菜单贴着光标弹出 → 选中 → **输入框内显示该图的真实缩略图 chip**（不再是「图片1」文字）。
   - 光标在 chip 后按一次 Backspace → 整个 chip 一次删除。
   - 普通文字 + chip 混排，回车触发生成；确认发给生成的 prompt 里引用仍是「图片1」文本（生成结果正常引用到图）。
   - 中文输入法输入不吞字、不误触发发送。
   - 回归：双击文本节点进入内文本编辑仍正常（未受影响）。
