# AI 助手「一键复制对话」实现与验证（2026-09-18）

补丁脚本：`脚本/patch_ai_copy_feature.py`（一次注入 `工具/index.html` + `工具/app.html`，幂等：含 `function copyChatAll` 即跳过）。

## 三个入口（同一份 `chatHistory`，与「归档为经验」同源）
1. 顶部「📋 复制对话」→ `chatToMarkdown()`：标题 + 导出时间 + 用户 + 消息数 + 全部问答（角色标签 `**👤 我：**` / `**🤖 AI 助手：**`）。
2. 每条消息右上「📋」→ 单条复制。
3. 兜底：自动复制失败（飞书内置浏览器等）→ `showCopyFallback()` 弹框并自动全选，提示 ⌘C/Ctrl+C。

## 六个必踩坑
1. **单条必须取 `chatHistory[i].content`（Markdown 原文），不能取 `innerText`**——innerText 把表格拍平成制表符，且会把按钮 emoji 一起复制。逃生回退必须 `cloneNode(true)` 后 `clone.querySelector('.msg-copy').remove()`。
2. **`decorateChatMsgs()` 必须在 `chatHistory.push()` 之后**：流式收尾先装饰再 push，`data-mi` 指不到原文 → 退化成 innerText。
3. **气泡要 `padding-right:34px`** 给按钮留位，否则 24×24 按钮压住首行标题（实测重叠为真）。
4. **按钮常显**（飞书 webview 触屏无 hover）：`opacity:.9` + `#f7fafd` 底 + 边框；`.5` 透明在白色 AI 气泡上视觉不可见。
5. 去重靠 `if (!el.querySelector('.msg-copy'))`，重复 `renderAiAssistant()` 不叠加（实测三次渲染恒为 2）。
6. 索引对齐：欢迎语/错误提示不在 `chatHistory` 里，需 `chatHistory[hi].role==='user' === el.classList.contains('user')` 严格匹配才 `hi++`，否则错位。

## 验证方法
- 剪贴板拦截：`Object.defineProperty(navigator,'clipboard',{value:{writeText:function(t){window.__clip=t;return Promise.resolve();}},configurable:true})`。
- 兜底框：`navigator.clipboard` 置 undefined + `document.execCommand` 桩返回 false。
- **“按钮真的画出来了吗”只能用像素探针**（PIL 统计 rect 内暗像素）：vision 看 12px 图标会误报“未见按钮”；实测暗像素 user 207 / ai 81 / 空白对照 0。
- 未登录时 `#app` 为 `display:none`，全页 rect 皆 0×0 → 截图前先 `loginOverlay.style.display='none'` + `app.style.display='block'`。
- 流式路径模拟：`appendChatMsg('ai','正在连接…')` → `chatHistory.push({role:'assistant',...})` → `md.innerHTML=mdRender(...)` → `decorateChatMsgs()`，再断言单条复制含 `| 表格 |` 管道符。

## 部署四路径（sha256 逐一比对）
`工具/index.html` → `/workbench/index.html` + `/工具/index.html`；`工具/app.html` → `/workbench/app.html` + `/工具/app.html`。
本机 8765 直接读 `工具/`，改完即生效（URL `/工具/index.html`，编码后 `%E5%B7%A5%E5%85%B7/`）。

## CDN 验证陷阱
CloudBase 测试域名对**浏览器直访**返回「页面访问提示」拦截页（不是网站内容），而 `curl` 拿到的是真文件 → 验证部署产物应 `curl` 下载 + sha256 比对 + 本地实测，别用浏览器直开判成败。
