Files
infinite-canvas/docs/content/docs/progress/prompt-chip-input-plan.mdx
T

67 lines
5.7 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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` 默认 truevalue 是提示词字符串,引用以 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 ← DOMsyncFromEditor**:仿 `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. **占位符 & 样式**:空值时显示 placeholderabsolute 占位层);容器套用面板传入的 `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」文本(生成结果正常引用到图)。
- 中文输入法输入不吞字、不误触发发送。
- 回归:双击文本节点进入内文本编辑仍正常(未受影响)。