Files
2026Technology-Competition/docs/superpowers/plans/2026-09-12-attachment-with-message.md
T

98 lines
6.4 KiB
Markdown
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.
# 要件定义书作为附件随消息发送 + 在线查看 — 实施计划
目标文件:
- 后端:`src/genesis/server/{store.py, service.py, app.py}``src/genesis/chat/agent.py`
- 前端:`frontend/{package.json, src/main.ts, src/chat.css}`
- 测试:`tests/{test_server_store.py, test_server_api.py, test_server_service.py}`
## 背景 / 目标
现状:选择 `.xlsx` 后**立即上传**。改为:
1. 选择 `.xlsx`**不清空为上传,而是作为“待发附件”chip 停在输入框上方**(可 `×` 移除/替换)。
2. 输入文字后点发送 → **先建会话(如需)→ 上传附件 → 再发送文字消息**;用户气泡内显示附件。
3. **附件与消息绑定**,刷新会话后仍显示在对应消息上。
4. 点击附件可**在线查看**(前端 SheetJS 解析原 xlsx 渲染,多 sheet)。
## 已确认决策
- D1 **必须有文字才能发送**(有附件但无文字也不可发送)。
- D2 查看 = **前端 SheetJS 在线解析原 xlsx 渲染**(多 sheet 切换;样式有限)。
- D3 附件与消息绑定、**刷新后仍显示**(后端消息加 `attachment` 字段 + 旧库迁移)。
- D4 **单个**要件定义书(再选替换待发附件)。
## 后端改动
### `store.py`
- `_init_db``chat_messages` 建表增列 `attachment TEXT`;对旧库执行迁移:
```python
cols = {r["name"] for r in c.execute("PRAGMA table_info(chat_messages)")}
if "attachment" not in cols:
c.execute("ALTER TABLE chat_messages ADD COLUMN attachment TEXT")
```
- `add_message(session_id, role, content, action=None, attachment: str | None = None)`INSERT 增 `attachment`。
- `list_messages`SELECT 增 `attachment`,返回 dict 增该字段(原样字符串)。
### `chat/agent.py`
- `handle_message(session_id, content, attachment: str | None = None)``self.store.add_message(session_id, "user", content, attachment=attachment)`L29)。
### `app.py`
- 新增 `class AttachmentRef(BaseModel): file_type: str = "requirements"; name: str = ""``ChatMessageReq` 增 `attachment: AttachmentRef | None = None`。
- `POST /api/chat/{sid}/messages`:把 `body.attachment` 序列化为 JSON 传给 `handle_message`。
- 新增 `GET /api/sessions/{sid}/files/{file_type}` → `FileResponse`(供前端取字节)。
- `service.file_path(sid, file_type) -> str | None`(校验 `ALLOWED_FILE_TYPES` 与存在性)。
### `service.py`
- 新增 `file_path(sid, file_type)`:从 `session.files[file_type]["path"]` 取,校验存在;非法类型/缺失返回 None。
## 前端改动
### 依赖
- `frontend/package.json` 增 `xlsx`SheetJSApache-2.0)到 `devDependencies`esbuild 打包进 `chat.js`。
### `main.ts`
- 状态 `pendingAttachment: { file: File; name: string } | null`。
- `file-input.change`:校验 `.xlsx` → 存为待发 → 渲染待发 chip(**不立即上传**)→ 清空 `input.value`。
- 待发 chip 渲染在输入框上方(`#composer-inner` 之前):`📄 文件名 ×``×` 清空。
- `send()`(仍要求有文字):
1. 有附件 → 确保会话(`!sid` → `POST /api/sessions`)→ `POST /files``file_type=requirements`);
2. 成功 → 记 `attachmentRef={file_type:'requirements',name}`、清待发;
3. `POST /api/chat/{sid}/messages` body `{ content, attachment?: attachmentRef }`
4. 上传失败 → 报错并**保留**待发附件、中止发送。
- 用户气泡:本消息带附件 → 气泡内渲染附件 chip(点击查看)。
- 历史(`loadSession`):消息含 `attachment` → 用户气泡内渲染 chip`try/catch` 解析 JSON、文件名 `esc`)。
- 查看:点击 chip → `fetch('/api/sessions/{sid}/files/{file_type}')` → `arrayBuffer` → `XLSX.read(new Uint8Array(buf), {type:'array'})` → 各 sheet `XLSX.utils.sheet_to_html` → 渲染进**页面内弹层**(sheet 标签切换 + 关闭;DOM 由 JS 动态创建,无需改 chat.html)。
- `updateSendState()` 不变(仍以文字为准)。
### `chat.css`
- 待发 chip、气泡内附件 chip、查看弹层(overlay + sheet tabs + 表格滚动)样式。
## 验证
- `cd frontend && npm run verify`typecheck + node --test + build)。
- `python -m pytest`(覆盖率 ≥99%,含新增测试)。
- 手动:选文件→待发 chip;无文字时发送键禁用;有文字发送→先上传后发消息;气泡显示附件;点查看→SheetJS 弹层多 sheet;刷新会话后附件仍在对应消息。
## 评审结论(plan-eng-review
### Step 0 假设(已核对)
- 用户消息在 `chat/agent.py:29` 入库 → attachment 从此透传。✅
- `chat_messages` 无 `attachment` 列 → 需迁移。✅
- 无 xlsx 查看能力 → 前端 SheetJS。✅
- 无上传文件读取端点 → 新增 GET。✅
### 发现
- **F1(体积)**SheetJS 打包进 `chat.js` 会显著增大(约数百 KB)。为保持“单文件 + mtime 版本”简单性,本轮直接打包;后续可改动态 `import()` 懒加载。**接受**。
- **F2(依赖/许可)**npm `xlsx@0.18.5`SheetJS 新版走自有 CDN);Apache-2.0,合规;仅用读取功能。**接受**。
- **F3(迁移)**PRAGMA 检测后再 ALTER,兼容新老库;补迁移测试。**已纳入**。
- **F4(顺序)**:先上传后发消息;上传成功而发消息失败时,附件已在会话、消息未绑定,重试覆盖重发。**接受**。
- **F5(安全)**`GET /files/{file_type}` 限 `ALLOWED_FILE_TYPES`,路径取自会话存储,防穿越。**已纳入**。
- **F6(首条消息带附件)**:先建会话→上传(会话名取文件名)→发消息,agent 可读 requirements。✅
- **F7(兼容)**`attachment` 为 JSON 字符串,前端 try/catch 解析;文件名一律 `esc`。**已纳入**。
### 剩余风险(非阻塞,实施中若触发则询问)
- **R1**`xlsx` 为 CommonJSesbuild 打包时可能尝试解析 `fs`/`crypto` 等 Node 内建。若报错,改用其浏览器发行版或配置 `external` 处理。
## GSTACK REVIEW REPORT
- STATUS: DONE
- REVIEWED: docs/superpowers/plans/2026-09-12-attachment-with-message.md
- FINDINGS: F1 体积(接受) / F2 依赖许可(接受) / F3 迁移(纳入) / F4 顺序(接受) / F5 安全(纳入) / F6/F7 已覆盖
- KEY DECISION: 附件待发 → 发送时先上传后发消息;消息绑定 attachment 并持久;SheetJS 在线查看
- RISK: R1xlsx/esbuild 打包兼容)——实施中若触发即询问
- NEXT: 无阻塞问题点,开始实施