# 创作 AI 助手：多格式附件 + 对话体验升级

> 状态：**设计定稿，待进入 S1 实现**。mockup 已批：`docs/design/mockups/2026-06-09-creation-ai-attachments.html`（经资深设计师 agent 对抗评审修订）。
> 设计决策（2026-06-09 用户拍板）：
>   - 选项 **A**：`拆镜头/立角色卡` 维持在 composer 单行（不移到回复区）。
>   - 文件类型角标用**中性 tile（ink-10 + ink-60 字形）**，不动用语义 danger/accent 红蓝（避免撞色，density-first）。
>   - composer 单行：`回形针 · 模式 · 拆镜头 · 立角色卡 · 发送`；态④全屏去掉「可复制」冗余注释；chip ✕ 命中区 ≥22/24。
> 触发：用户反馈「所有对话都挤在一个小框，且不支持很多文件格式」。
> 决策已定（用户 2026-06-09 拍板）：① 格式 = 混合（图片/PDF 原生多模态 + Office 抽文本）；② 对话 = 自增高 + 可放大/全屏；③ 入口 = 点击上传 + 拖拽到面板 + 粘贴图片。

---

## 0. 通俗讲解（这事到底在改什么）

现在创作助手只能打字。这次让它能「**喂资料**」：把一张参考图、一份 PDF 剧本设定、一个 Word 大纲、一张 Excel 角色表直接拖进对话框（或点上传、或截图粘贴），AI 就能看着这些东西帮你写。同时把那个挤巴巴的输入框做成会自己长高的，并给整个助手面板加一个「放大」键，进入一个宽敞的大对话区。

## 1. 现状（代码事实，带 file:line）

- 组件：`src/workbench/creation/CreationAiPanel.tsx`。**已有**对话历史区（`[grid-area:messages]` 可滚动 + `NomiMarkdown`）；**缺口**在输入框与附件。
- 输入框是裸 `<textarea min-h-14>`（`CreationAiPanel.tsx:435`）——固定矮、不自增高。
- **零附件能力**：无文件选择器 / 无拖拽 / 无粘贴 / 无附件 chip。
- 链路纯字符串：`AgentsChatRequestDto`（`src/api/desktopAgentsChatStream.ts:9`）只有 `chatContext?: unknown`，无 file/image 字段。
- 模型调用唯一注入点：`electron/ai/agentChatV2.ts:413` —— `const userMessage = { role:'user', content: userPrompt }`（字符串），用的是 Vercel AI SDK `streamText`。
- **AI SDK `ai`^4.3.19 原生支持多模态**：`CoreUserMessage.content` 可为 `Array<TextPart|ImagePart|FilePart>`，PDF/图片对 OpenAI/Anthropic 是原生 part（Context7 已核实）。
- **无「模型是否吃图片/文件」能力声明**，仅一个负向 name-regex（`agentChatV2.ts:137`）。
- **文件搬运设施现成且格式无关**：`importWorkbenchLocalAssetFile`（`src/workbench/api/assetUploadApi.ts:61`）→ `assets.importFile` IPC → `writeAsset` → `nomi-local://` 协议（`electron/main.ts:260`）+ `assetLocalization` 解析器。生成画布参考图流程（`useNodeAssetDrop.ts` / `adapters/assetImportAdapter.ts`：乐观预览→上传→换 hosted URL，30MB 上限）是最近可借鉴模板。
- **未装任何文档解析库**（pdfjs / mammoth / xlsx / tesseract 均无）；MIME 表 `electron/assets/assetPaths.ts` 无 pdf/docx/xlsx。
- **无粘贴图片处理**（全仓 grep 零命中）——净新增。

## 2. 架构：附件如何流到模型（混合路由）

```
[Composer] 点击/拖拽/粘贴
   → 上传 nomi-local://（复用 importWorkbenchLocalAssetFile，格式无关）
   → 按 (文件类型 × 当前 agent 模型能力) 路由：
        图片  + 模型支持vision  → ImagePart（URL/base64，走 assetLocalization）
        PDF   + 模型支持file    → FilePart(application/pdf)
        其余/不支持(docx,xlsx,csv,txt,md / 或模型不收) → 主进程抽文本 → 拼成 TextPart 上下文块
   → DTO 带 attachments[] 透传到 electron
   → agentChatV2.ts:413 把 content 从 string 升级为 parts 数组
```

**能力门控**：新增 `Model.meta.supportsImageInput?: boolean`（必要时加 `supportsPdfInput?`）。缺省保守：未知 → 图片不能原生发就给清晰提示/降级，PDF 默认走抽文本（不静默失败，符合 P2 + 存储加固教训）。

## 3. 分片实现（每片一 commit，独立可回滚，leaf-first）

| 片 | 内容 | 关键改动点 | 验收 |
|---|---|---|---|
| **S0** | 本方案 + HTML mockup（R8）| `docs/plan` + mockup | 用户拍板 |
| **S1** | Composer 重构（纯前端，先不带附件发送）：自增高 textarea + 附件 chip 行 + 三入口（点击/拖拽蒙层/粘贴）+ 附件本地上传到 nomi-local | `CreationAiPanel.tsx` 拆出 `ChatComposer` + `useComposerAttachments`；store 加 `attachments` | 五门 + R13 真机（上传/拖拽/粘贴可见 chip）+ design-fidelity |
| **S2** | 附件透传链路 + 图片原生多模态（最小闭环，接入即验证）| DTO 加 `attachments?`（`desktopAgentsChatStream.ts:9`）→ `workbenchAiClient.buildWorkbenchAiPayload` → `workbenchAgentRunner` → `RunAgentChatV2Payload`；`agentChatV2.ts:413` 注入 parts；加 `supportsImageInput` 能力标记 | 五门 + **真实 E2E：一张图真发给 vision 模型跑通** |
| **S3** | PDF 原生 file part（Claude/GPT-4o）| 同 S2 注入点扩 file part；按能力门控 | 五门 + PDF 真实 E2E |
| **S4** | Office/csv/txt/md 抽文本：新 IPC `files.extractText` + 解析库 | `electron/files/extractText.ts` + preload/bridge + MIME 表补全 | 五门 + 各格式抽文本回归 |
| **S5** | 放大/全屏对话模式 | AI 面板加「放大」→ `createPortal` 全屏（仿 Scene3D `editorShell` + `FULLSCREEN_Z_INDEX`）| 五门 + R13 真机 |
| **S6** | 画布助手同构接入（复用同一 ChatComposer）| `CanvasAssistantPanel.tsx` 接 ChatComposer | 五门 + R13 |

> **实现状态（2026-06-09，全部完成并推送）**：S1 `4a61f23` · S2 `84ffe37` · S3 `4a7efc6` ·
> S4 `5fd8ca8` · S5 `a416ada`（含 R13 抓到的全屏遮挡修复：portal 带 workbench-shell 接回 token 作用域）·
> S6 `ca6c20a`。共享 composer 组件在 `src/workbench/ai/composer/`，创作助手 + 画布助手同构。
> 唯一 headless 无法验的环节：真实 vendor 多模态回路（需用户 vision 模型 + API key），
> 已用单测 + 真机附件 threading 覆盖到模型调用前一跳。

## 4. 技术栈选型（解析放 electron 主进程，匹配 desktop-bridge 套路、不污染渲染 bundle）

- PDF 文本：**`unpdf`**（主进程友好、无 Vite worker 坑）；不选 pdfjs-dist 以避 worker 配置。
- DOCX：**`mammoth`**。
- XLSX/CSV：**`xlsx`(SheetJS)**（或 exceljs + papaparse）。
- 类型嗅探：`file-type`（安全/MIME 兜底）。
- OCR（扫描件）：**二期** `tesseract.js`（重，本轮不做）。
- 新 IPC：`files.extractText({ url|bytes, contentType }) → { text, truncated, meta }`。

## 5. 不动项

- 现有对话历史区结构、工具确认卡（write tool 卡片）、`拆镜头`/`立角色卡`（storyboard/fixation）链路。
- 生成画布参考图流程：**只借鉴模式，不改其代码**。
- `electron/runtime.ts`：本特性不经它，不改。

## 6. 回滚策略

- 每片独立 commit；附件字段全为 **additive 可选**（DTO 加可选字段，老纯文本路径零影响）。
- 能力标记缺省保守，未知模型不会因新逻辑误发。
- S5 全屏是叠加入口，不改原内联面板，可单独 revert。

## 7. 验收门（缺一不算完成）

1. 五门：`check:filesize` / `lint:ci`(≤98) / `typecheck` / `test` / `build`。
2. **接入即验证 E2E**（S2/S3/S4）：真机驱动 + 主进程埋点，确认附件真到达模型（渲染层抓不到 vendor HTTP）。
3. **R13 真机走查**（S1/S5/S6）：点击上传 / 拖拽 / 粘贴 / 放大全屏 / 多附件 / 删除 chip，真实用户视角看顺不顺、被不被挡。
4. **R8 对账**：与获批 mockup 逐项并排；`design-fidelity.e2e.mjs` computed-style 断言（防 twMerge/Mantine 吞样式）。

## 8. 风险与缓解

| 风险 | 缓解 |
|---|---|
| 解析库体积 / Vite worker 坑 | 选 unpdf；解析全放主进程 |
| 大文件 base64 IPC 卡顿 | 30MB 上限（复用现有常量）；超大可复用 `exports.writeTempInput` chunked |
| 模型能力误判致静默失败 | 能力标记 + 清晰降级提示（图片不支持→提示换模型或转文本）|
| 拆 Composer 触碰 twMerge token | design-fidelity 断言 + token-only |
| 抽文本丢版式（表格/图）| 文档注明；图表类优先走 PDF 原生多模态路线 |

## 9. 待办（实现前）

- [x] 出 HTML mockup + 设计师 agent 对抗评审 + 用户确认（选项 A）。✅ 2026-06-09
- [x] `supportsPdfInput` **独立标记**（S3 定）：PDF file-part 支持面比 vision 窄（claude-3.5+/gpt-4o/gemini），与 `supportsImageInput` 分开两个能力标记 + 两条模型族正则；均支持 `meta.*` 显式声明覆盖。
