## 文档分层收敛 + 音色试听修复 + 录音弹窗重构 + 弹窗体系统一 (Day 31)

### 概述

今天的工作聚焦四件事：

1. 清理并收敛根目录文档（README/DEV 职责边界、历史内容归档、参数描述与代码对齐）
2. 完成 EdgeTTS 音色列表「一键试听」能力，并修复浏览器端试听失败问题
3. 重构声音克隆录音交互：录音入口下沉到参考音频区域底部右侧，流程改为弹窗
4. 抽离统一弹窗基座 `AppModal`，将主要弹窗迁移到同一视觉和交互规范

---

## ✅ 1) 文档体系与内容一致性优化

### 1.1 README / DEV 边界明确

- 为 `FRONTEND_README.md`、`BACKEND_README.md`、`FRONTEND_DEV.md`、`BACKEND_DEV.md` 增加「文档定位」
- README 只保留稳定说明（功能、接口、运行），DEV 保留规范（约束、分层、Checklist）
- 将 README 中偏日志化内容（如 Day 标注）清理为稳定表述

### 1.2 部署与参数文档对齐当前代码

- 将唇形路由阈值文案统一为阈值驱动，并以当前 `.env` 示例 `100` 为参考
- 修正旧编码描述（将 MuseTalk 合成描述对齐为 rawvideo 管道 + `libx264`）
- 修复文档中不存在的 `.env.example` 指引，改为基于 `backend/.env` 的说明
- 将 Qwen3-TTS 文档标注为「历史归档（已停用）」并指向 CosyVoice 3.0

---

## ✅ 2) 音色试听能力落地与故障修复

### 2.1 功能实现

- 音色下拉项新增试听按钮（播放/暂停/加载态）
- 新增后端试听接口：`/api/videos/voice-preview`
- 试听文本按音色 locale 自动选择固定示例文案（9 国语言 + 中文兜底）

### 2.2 兼容与稳定性调整

- 保留 `POST /api/videos/voice-preview`（兼容）
- 新增 `GET /api/videos/voice-preview?voice=...`，前端改为直接播放 GET 音频流，减少浏览器自动播放策略干扰

```python
@router.get("/voice-preview")
async def preview_voice_get(voice: str, current_user: dict = Depends(get_current_user)):
    voice_value = voice.strip()
    if not voice_value:
        raise HTTPException(status_code=400, detail="voice 不能为空")
    text = _get_preview_text_for_voice(voice_value)
    return await _render_voice_preview(voice=voice_value, text=text)
```

### 2.3 本次线上问题结论（已修复）

- 现象：浏览器端试听请求 404
- 根因：新增 GET 路由后，后端进程未重启，运行中的代码仍是旧版本
- 处理：`pm2 restart vigent2-backend` 后路由生效
- 补充：`curl` 返回 401（无 auth cookie）属于预期；浏览器同源请求会自动带 cookie

---

## ✅ 3) 录音交互重构（声音克隆）

### 3.1 入口重排

- 去掉参考音频面板内的独立录音大块区域
- 将「上传音频 / 录音」入口放到「我的参考音频」区域底部右侧

### 3.2 录音流程改为弹窗

- 录音弹窗支持：开始录音 / 停止录音 / 状态计时 / 试听
- 保留并强化「使用此录音」和「弃用本次录音」
- 关闭弹窗时若仍在录音，会先停止录音再关闭
- 修正弹窗挂载位置：从局部组件渲染改为 `AppModal` Portal 到 `document.body`，确保是全页面弹窗体验
- 参考音频区按钮文案更新：`录音` -> `在线录音`

### 3.4 文案区按钮视觉统一

- 统一「文案提取与编辑」区按钮尺寸与圆角（`px-3 py-1.5 text-xs rounded-lg`）
- 将 `AI智能改写`、`保存文案` 按钮改为与上传/在线录音同等级的视觉规格
- 同步统一图标尺寸与禁用态样式，消除“底部按钮偏小”问题

### 3.5 录音试听条 UI 美化

- 将录音完成后的原生白色 `<audio controls>` 替换为项目深色风格的自定义试听条
- 新试听条包含：播放/暂停按钮、进度拖拽、当前时长/总时长显示
- 统一配色到当前页面（深色底 + 绿色强调），避免与整体 UI 风格割裂

### 3.6 录音上传关闭时机优化

- 原逻辑：点击「使用此录音」后，需等待上传+识别完成才关闭弹窗（体感卡顿）
- 新逻辑：点击后立即关闭弹窗，上传/识别在后台继续进行
- 状态反馈仍在参考音频区域显示（上传识别中的提示 + 失败错误提示）

---

## ✅ 5) 发布管理抖音登录「无法获取二维码」修复

### 问题定位

- 现象：发布管理中点击抖音登录，前端提示无法获取二维码
- 后端日志显示根因：
  - `Page.goto: Timeout 30000ms exceeded`
  - 导航目标：`https://creator.douyin.com/`
  - 等待条件：`wait_until="networkidle"`

### 修复方案

- 抖音登录页改为与微信一致的更稳策略：`wait_until="domcontentloaded"`
- 对抖音导航超时增加容错：即使 `goto` 超时，也继续执行二维码提取流程（避免长连接导致误失败）

### 验证

- 本地接口冒烟：`POST /api/publish/login/douyin` 返回 `success=true` 且包含 `qr_code`
- 已重启后端进程使修复生效：`pm2 restart vigent2-backend`

### 3.3 状态逻辑补齐

- 新增 `discardRecording()`：清空本次录音与计时
- 开始新录音前先清空旧录音，避免旧状态残留

---

## ✅ 4) 弹窗 UI/UX 统一（AppModal）

新增统一弹窗基座：`frontend/src/shared/ui/AppModal.tsx`

- 统一遮罩：`bg-black/80 + backdrop-blur-sm`
- 统一容器：深色半透明背景、`border-white/10`、`rounded-2xl`、重阴影
- 统一 Header：标题/副标题/关闭按钮
- 统一行为：ESC 关闭、背景滚动锁定、按需控制 overlay 点击关闭
- 统一挂载：通过 Portal 渲染到 `document.body`，避免出现“看起来只在配音区弹出”的层叠问题
- 统一可访问性：补齐 `role="dialog"` + `aria-modal="true"`
- 统一焦点管理：打开弹窗自动聚焦，关闭后恢复到打开前焦点元素
- 统一滚动锁计数：支持多弹窗并存，避免一个弹窗关闭后提前恢复页面滚动

已迁移弹窗：

- 视频预览（`VideoPreviewModal`）
- 文案提取（`ScriptExtractionModal`）
- AI 改写（`RewriteModal`）
- 截取设置（`ClipTrimmer`）
- 录音弹窗（`RefAudioPanel` 内）
- 修改密码弹窗（`AccountSettingsDropdown`）
- 发布管理扫码登录弹窗（`PublishPage` 内 QR 登录弹窗）

---

## ✅ 6) 微信视频号登录二维码观感优化（“能扫但像被截断”）

### 问题现象

- 微信视频号登录二维码可扫码成功，但视觉上像“边缘不完整/被切掉”，观感不佳

### 修复方案

- 后端二维码提取策略增强（`qr_login_service.py`）：
  - 优先导出二维码原始 PNG 数据（`canvas.toDataURL('image/png')` / `img[data:image/png]`），减少二次截图导致的边缘损失
  - 微信回退截图时改为“按二维码 bbox 外扩留白裁剪”，避免贴边截取带来的不完整感
  - 仅接受 PNG Data URL，避免把非 PNG（如 SVG 片段）直接当二维码返回造成边角异常
- 前端扫码弹窗展示优化（`PublishPage.tsx`）：
  - 取消二维码图片本体圆角裁切，改为外层白底容器 + 内边距（模拟 quiet zone）
  - 同步调整二维码显示宽度与边框，提升完整感与观感一致性

### 验证

- 本地接口冒烟：`POST /api/publish/login/weixin` 返回 `success=true` 且包含 `qr_code`
- 解码后图片尺寸为 `1000x1000`，扫码仍正常
- 前后端进程已重启使修复生效：
  - `pm2 restart vigent2-frontend`
  - `pm2 restart vigent2-backend`

---

## ✅ 7) 发布流程性能与日志可读性优化（双平台发布场景）

### 7.1 发布请求并发优化（前端）

- 原逻辑：发布页按平台串行 `for...of await`，多平台总耗时为各平台耗时累加
- 新逻辑：引入受限并发执行（并发度=2），两平台可并行发布，显著缩短总等待时长
- 结果列表仍按用户选择的平台顺序回填，避免并发返回导致顺序抖动

### 7.2 微信上传日志噪声优化（后端）

- 原逻辑：`set_input_files` 后若立即读不到 `input.files[0]` 就直接打 warning：`[weixin][file_input] empty`
- 新逻辑：先轮询确认“是否已进入上传中状态”，再决定是否告警；非最后一次重试只记 info，最后一次才 warning
- 效果：减少误报警（实际已开始上传时不再刷 warning），排障日志更干净

### 验证

- `python -m py_compile backend/app/services/uploader/weixin_uploader.py` ✅
- `npm run build`（frontend）✅
- 服务重启：`pm2 restart vigent2-frontend && pm2 restart vigent2-backend` ✅

---

## ✅ 8) 小红书发布链路对齐改造（启动模式 / Cookie 格式 / 成功截图）

### 8.1 启动模式与反检测参数对齐

- 在 `config.py` 新增小红书 Playwright 配置：
  - `XIAOHONGSHU_HEADLESS_MODE`（默认 `headless-new`）
  - `XIAOHONGSHU_USER_AGENT / LOCALE / TIMEZONE_ID`
  - `XIAOHONGSHU_CHROME_PATH / BROWSER_CHANNEL`
  - `XIAOHONGSHU_FORCE_SWIFTSHADER / DEBUG_ARTIFACTS`
- `xiaohongshu_uploader.py` 改为与抖音/微信一致的可配置启动策略，并保留反检测基础参数（`--disable-blink-features=AutomationControlled`）

### 8.2 小红书 uploader 重构增强

- 重写小红书 uploader 主流程（参考抖音/微信模式）：
  - 上传入口/文件 input 多选择器回退
  - 上传中/成功/失败状态轮询判定
  - 标题与正文/话题填充容错
  - 发布按钮多选择器与可点击检查
- 发布成功判定从“仅 URL”增强为“多信号组合”：
  - URL 跳转判定
  - 页面成功/失败文案判定
  - 发布 API 响应监听（`publish` / `note create` 类接口）
- 发布成功后补齐截图能力并返回 `screenshot_url`（路径格式与抖音/微信一致）：
  - `/api/publish/screenshot/{filename}`

### 8.3 Cookie 保存格式统一

- `publish_service.save_cookie_string()` 调整：
  - `bilibili` 继续使用原有简化 cookie dict（兼容既有上传库）
  - 非 `bilibili` 平台统一保存为 Playwright `storage_state`：
    - `{"cookies": [...], "origins": []}`
  - 补充平台默认 domain（抖音/微信/小红书），使 cookie 文件可直接用于 `browser.new_context(storage_state=...)`

### 8.4 验证与生效

- `python -m py_compile backend/app/core/config.py backend/app/services/publish_service.py backend/app/services/uploader/xiaohongshu_uploader.py` ✅
- `pm2 restart vigent2-backend` ✅

---

## ✅ 9) 小红书登录二维码修复（默认短信登录需先切换）

### 问题现象

- 小红书创作平台 `https://creator.xiaohongshu.com/` 默认落在“短信登录”视图
- 二维码需要先点击右上角切换图标才会出现，导致后端直接按二维码选择器抓取失败

### 修复方案（`qr_login_service.py`）

- 新增 `_ensure_xiaohongshu_qr_mode()`：
  - 先检测是否处于短信登录（`input[placeholder*='手机号']`）
  - 自动点击登录卡片右上角切换图标（优先稳定选择器，失败后用几何位置兜底）
  - 切换后等待二维码渲染再进入提取流程
- 扩展小红书二维码选择器集合：
  - 增加登录卡片内二维码图片选择器（包含当前页面结构）
  - 保留通用 `img[src*='qr'/'qrcode']` 兜底
- 提高小红书候选过滤阈值（`min_side=120`），避免误选右上角切换小图标
- 文本策略补充小红书关键词（如 `APP扫一扫登录`）

### 验证

- 本地接口冒烟：`POST /api/publish/login/xiaohongshu` 返回 `success=true` 且 `qr_code` 非空
- 后端日志确认修复链路生效：
  - `已点击登录方式切换，等待二维码渲染`
  - `策略1(CSS): 匹配成功`

---

## ✅ 10) 小红书发布上传阶段修复（“发布笔记 - 上传视频”场景）

### 问题现象

- 小红书发布在“上传视频”阶段失败，页面停留在发布页，前端提示发布失败
- 后端日志显示 `set_input_files` 触发成功，但短时间内未检测到上传状态，导致重复触发上传并误判失败
- 进一步定位到上传文件实际是 Supabase 本地对象文件（无后缀），日志里 `file_input type=` 为空，平台可能无法正确识别视频 MIME

### 修复方案（`xiaohongshu_uploader.py`）

- 新增上传启动探测窗口 `UPLOAD_SIGNAL_TIMEOUT=12s`：
  - `set_input_files` 成功后给上传状态留出启动时间
  - 检测到“上传中/处理中/转码中”等信号即进入后续上传轮询
  - 启动窗口内未出现明显信号时，不再立即判失败，转入主上传监控阶段继续等待
- 修正失败判定词：
  - 从失败关键词中移除 `重新上传`（该文案在小红书页面常作为正常状态/操作入口，不能直接视为失败）
- 增补上传文件诊断日志：
  - 输出 `file_input` 选中文件名/大小/类型，便于确认文件是否真正注入 input
  - 上传失败命中时记录明确告警日志，便于线上快速定位
- 增加无后缀视频文件兜底：
  - 若原文件无后缀且父目录名带后缀（如 `xxx.mp4/<uuid>`），自动在 `/tmp/vigent_uploads` 生成同名临时文件（硬链接/软链接/复制兜底）
  - 上传改用带后缀临时文件，提升站点 MIME 识别稳定性
  - 任务结束后自动清理临时上传文件

### 10.1 二次定位与加固（卡住复现后）

- 复现日志显示：即使传入了带后缀临时路径，`file_input` 中仍出现无后缀文件名，且长时间停留在 `等待上传状态...`
- 根因进一步确认：此前在跨设备场景下会走 `symlink` 回退，浏览器实际取到原始目标文件名（无后缀），导致站点识别失败
- 加固修复：
  - 去掉 `symlink` 回退，仅保留 `hardlink -> copy`，确保最终上传文件名稳定带 `.mp4`
  - 新增 `file_input` 文件名后缀一致性校验：若与预期后缀不一致，直接重试并在最终失败时提前返回（不再无意义长时间等待）
  - 新增上传空转超时保护（`UPLOAD_IDLE_TIMEOUT=90s`）：长时间无有效上传信号时提前失败并保留调试截图，避免前端“看起来卡死”
  - 优化失败文案为“未能触发有效视频上传，请确认发布页状态及视频文件格式”

### 10.2 实时发布验证（修复后）

- 重新发起 `POST /api/publish`（小红书），后端完整走通上传+发布，接口返回 `200`
- 本次实测耗时约 `45.77s`，属于上传与发布等待区间内的正常时长
- 发布成功截图可访问：`GET /api/publish/screenshot/xiaohongshu_success_20260303_115944_633.png` 返回 `200`
- 关键日志链路：`正在上传` -> `已设置上传文件` -> `等待发布结果` -> `Cookie 更新完毕`

### 验证

- `python -m py_compile backend/app/services/uploader/xiaohongshu_uploader.py` ✅
- `pm2 restart vigent2-backend` ✅
- `curl http://127.0.0.1:8006/health` 返回 `{"status":"ok"}` ✅

---

## 📁 今日主要修改文件

| 文件 | 改动 |
|------|------|
| `backend/app/modules/videos/router.py` | 新增/增强 `voice-preview` GET+POST，试听文本 locale 路由，临时文件清理 |
| `backend/app/modules/videos/schemas.py` | 新增 `VoicePreviewRequest` |
| `frontend/src/features/home/ui/VoiceSelector.tsx` | 音色下拉增加试听按钮，改为 GET 音频流播放 |
| `frontend/src/features/home/model/useHomeController.ts` | 录音状态重置、`discardRecording` |
| `frontend/src/features/home/ui/HomePage.tsx` | 透传录音弃用动作 |
| `frontend/src/features/home/ui/RefAudioPanel.tsx` | 上传/录音入口重排；录音改弹窗；使用/弃用流程 |
| `frontend/src/features/home/ui/ScriptEditor.tsx` | 文案编辑区按钮视觉统一（含 AI智能改写/保存文案） |
| `frontend/src/features/home/ui/RefAudioPanel.tsx` | 录音完成试听条改为自定义深色播放器（替换原生白色控制条） |
| `frontend/src/features/home/ui/RefAudioPanel.tsx` | 使用录音后弹窗立即关闭，上传识别后台进行（提升交互流畅度） |
| `frontend/src/features/publish/model/usePublishController.ts` | 发布改为受限并发（并发度=2），缩短多平台发布总耗时 |
| `backend/app/core/config.py` | 新增小红书 Playwright 配置（headless/UA/locale/timezone/chrome/debug） |
| `backend/app/services/uploader/xiaohongshu_uploader.py` | 按抖音/微信模式重构；补充上传启动容错窗口、无后缀文件兜底（hardlink/copy）、后缀一致性校验、空转超时保护与上传诊断日志 |
| `backend/app/services/publish_service.py` | `save_cookie_string` 非 bilibili 统一存储为 Playwright `storage_state`；小红书 uploader 透传 `user_id` |
| `backend/app/services/qr_login_service.py` | 抖音导航超时容错 + 微信二维码提取增强 + 小红书登录自动切换到扫码模式并提取二维码 |
| `backend/app/services/uploader/weixin_uploader.py` | `file_input empty` 告警策略优化：先检测上传信号，非最后一次重试降级为 info |
| `frontend/src/shared/ui/AppModal.tsx` | 统一弹窗组件 + 无障碍语义 + 焦点管理 + 多弹窗滚动锁计数 |
| `frontend/src/components/VideoPreviewModal.tsx` | 迁移到 `AppModal` |
| `frontend/src/features/home/ui/ScriptExtractionModal.tsx` | 迁移到 `AppModal` |
| `frontend/src/features/home/ui/RewriteModal.tsx` | 迁移到 `AppModal` |
| `frontend/src/features/home/ui/ClipTrimmer.tsx` | 迁移到 `AppModal` |
| `frontend/src/components/AccountSettingsDropdown.tsx` | 修改密码弹窗迁移到 `AppModal` |
| `frontend/src/features/publish/ui/PublishPage.tsx` | 扫码登录（QR）弹窗迁移到 `AppModal` + 二维码白底留白容器优化（避免边缘观感被裁） |
| `Docs/FRONTEND_DEV.md` | 新增统一弹窗规范（AppModal）和录音交互规范 |
| `Docs/FRONTEND_README.md` | 增补录音入口与弹窗交互说明 |
| `Docs/BACKEND_README.md` | 增补 `voice-preview` 接口说明；更新发布 API 路径（`/login/{platform}` 等）并链接发布专项文档 |
| `Docs/BACKEND_DEV.md` | 更新后端规范中的发布器覆盖范围与小红书配置项；补充发布专项文档指引 |
| `Docs/PUBLISH_DEPLOY.md` | 新增多平台发布专项文档（登录实现、自动化发布流程、部署要点与排障） |
| `Docs/DEPLOY_MANUAL.md` | 部署参数与扫码说明补充小红书要点；新增发布专项文档入口 |
| `README.md` | 文档中心新增 `PUBLISH_DEPLOY.md` 入口；发布结果可视化描述补齐小红书 |
| `Docs/TASK_COMPLETE.md` | 新增 Day31 任务汇总，更新 Current 标签与更新时间 |
| `Docs/DOC_RULES.md` | 增补“发布相关三检”（路由真值/专项文档/入口回写）、敏感信息处理规范，更新工具规范为 `Read/Grep/apply_patch`，并对齐 TASK_COMPLETE 检查清单 |
| `Docs/SUBTITLE_DEPLOY.md` | 与当前阈值/参数说明对齐 |
| `Docs/LATENTSYNC_DEPLOY.md` | 与当前阈值/参数说明对齐 |
| `Docs/COSYVOICE3_DEPLOY.md` | TTS 部署说明与当前运行路径对齐 |
| `Docs/QWEN3_TTS_DEPLOY.md` | 标注为历史归档并指向 CosyVoice 3.0 |

---

## 🔍 验证记录

- `python -m py_compile backend/app/modules/videos/router.py backend/app/modules/videos/schemas.py` ✅
- `python -m py_compile backend/app/services/qr_login_service.py` ✅
- `python -m py_compile backend/app/services/uploader/weixin_uploader.py` ✅
- `python -m py_compile backend/app/core/config.py backend/app/services/publish_service.py backend/app/services/uploader/xiaohongshu_uploader.py` ✅
- `POST /api/publish/login/xiaohongshu` 冒烟返回 `success=true` + `qr_code` ✅
- `python -m py_compile backend/app/services/uploader/xiaohongshu_uploader.py`（上传阶段修复后）✅
- `pm2 restart vigent2-backend`（上传阶段修复后）✅
- `curl http://127.0.0.1:8006/health` 返回 `{"status":"ok"}` ✅
- `backend/venv/bin/python` 本地探针验证 `_prepare_upload_file()`：临时文件非 symlink、后缀 `.mp4`、清理成功 ✅
- 小红书发布实测：`POST /api/publish` 返回 `200`（`Duration: 45.77s`）且成功截图接口返回 `200` ✅
- 新增 `Docs/PUBLISH_DEPLOY.md`（抖音/微信/B站/小红书登录与发布实现说明）✅
- `npm run build`（frontend）✅
- `POST /api/publish/login/weixin` 冒烟返回 `success=true` + `qr_code` ✅
- `npx eslint` 定向检查以下文件通过：
  - `VoiceSelector.tsx`
  - `RefAudioPanel.tsx`
  - `HomePage.tsx`
  - `useHomeController.ts`
  - `AppModal.tsx`
  - `VideoPreviewModal.tsx`
  - `ScriptExtractionModal.tsx`
  - `RewriteModal.tsx`
  - `AccountSettingsDropdown.tsx`
- `ClipTrimmer.tsx` 仍有仓库既有 lint 规则项（`react-hooks/set-state-in-effect`），与本次弹窗风格迁移无关
- 音色试听线上问题经后端重启后已恢复可用（浏览器同源携带 cookie）

---

## ☑️ Day31 覆盖核对（今日新增补充）

已对照今天新增改动做二次核对，以下内容已写入本日志：

- `AppModal` 的可访问性与焦点/滚动锁稳健性增强
- 微信视频号二维码“观感不完整”问题的后端提取修复
- 发布页二维码展示样式优化（白底留白、去除本体圆角裁切）
- 小红书 uploader 对齐重构（启动参数、发布判定、成功截图）
- 小红书“上传阶段卡住”二次定位与加固（文件名后缀一致性 + 空转超时）并完成实测发布成功
- 形成发布专项文档 `Docs/PUBLISH_DEPLOY.md`，沉淀四平台登录与自动化发布实现
- 回写 `Docs/BACKEND_README.md` / `Docs/BACKEND_DEV.md` / `Docs/DEPLOY_MANUAL.md`，统一发布 API 与部署说明口径
- 回写 `README.md`，补充发布专项文档入口与小红书发布成功截图能力描述
- 回写 `Docs/TASK_COMPLETE.md`，补齐 Day31 任务完成记录
- 回写 `Docs/DOC_RULES.md`，同步文档更新规则到当前文档结构与工具链
- 非 bilibili 平台 cookie 保存为 `storage_state` 格式
- 小红书登录二维码自动切换（短信登录 -> 扫码登录）与提取修复
- 对应构建/重启/冒烟验证记录
- 今日运行期产物（`backend/user_data/**/cookies/*.json`、`watchdog.log`）为会话副产物，不属于代码/文档变更项
