diff --git a/Docs/BACKEND_README.md b/Docs/BACKEND_README.md
new file mode 100644
index 0000000..a090282
--- /dev/null
+++ b/Docs/BACKEND_README.md
@@ -0,0 +1,142 @@
+# ViGent2 后端开发指南
+
+本文档为后端开发人员提供架构概览、接口规范以及开发流程指南。
+
+---
+
+## 🏗️ 架构概览
+
+后端采用 **FastAPI** 框架,基于 Python 3.10+ 构建,主要负责业务逻辑处理、AI 任务调度以及与各微服务组件的交互。
+
+### 目录结构
+
+```
+backend/
+├── app/
+│ ├── api/ # API 路由定义 (endpoints)
+│ ├── core/ # 核心配置 (config.py, security.py)
+│ ├── models/ # Pydantic 数据模型 (schemas)
+│ ├── services/ # 业务逻辑服务层
+│ │ ├── auth_service.py # 用户认证服务
+│ │ ├── glm_service.py # GLM-4 大模型服务
+│ │ ├── lipsync_service.py # LatentSync 唇形同步
+│ │ ├── publish_service.py # 社交媒体发布
+│ │ └── voice_clone_service.py# Qwen3-TTS 声音克隆
+│ └── tests/ # 单元测试与集成测试
+├── scripts/ # 运维脚本 (watchdog.py, init_db.py)
+└── requirements.txt # 依赖清单
+```
+
+---
+
+## 🔌 API 接口规范
+
+后端服务默认运行在 `8006` 端口。
+
+- **文档地址**: `http://localhost:8006/docs` (Swagger UI)
+- **认证方式**: Bearer Token (JWT)
+
+### 核心模块
+
+1. **认证 (Auth)**
+ * `POST /api/auth/login`: 用户登录 (手机号)
+ * `POST /api/auth/register`: 用户注册
+ * `GET /api/auth/me`: 获取当前用户信息
+
+2. **视频生成 (Videos)**
+ * `POST /api/videos/generate`: 提交生成任务
+ * `GET /api/videos/{task_id}`: 查询任务状态
+ * `GET /api/videos/history`: 获取历史视频列表
+
+3. **素材管理 (Materials)**
+ * `POST /api/materials/upload`: 上传素材 (Direct Upload to Supabase)
+ * `GET /api/materials`: 获取素材列表
+
+4. **社交发布 (Publish)**
+ * `POST /api/publish`: 发布视频到 B站/抖音/小红书
+
+---
+
+## 🛠️ 开发环境搭建
+
+### 1. 虚拟环境
+
+```bash
+cd backend
+python -m venv venv
+source venv/bin/activate # Linux/macOS
+# .\venv\Scripts\activate # Windows
+```
+
+### 2. 依赖安装
+
+```bash
+pip install -r requirements.txt
+```
+
+### 3. 环境变量配置
+
+复制 `.env.example` 到 `.env` 并配置必要的 Key:
+
+```ini
+# Supabase
+SUPABASE_URL=http://localhost:8008
+SUPABASE_KEY=your_service_role_key
+
+# GLM API (用于 AI 标题生成)
+GLM_API_KEY=your_glm_api_key
+
+# LatentSync 配置
+LATENTSYNC_GPU_ID=1
+```
+
+### 4. 启动服务
+
+**开发模式 (热重载)**:
+```bash
+uvicorn app.main:app --host 0.0.0.0 --port 8006 --reload
+```
+
+---
+
+## 🧩 服务集成指南
+
+### 集成新模型
+
+如果需要集成新的 AI 模型 (例如新的 TTS 引擎):
+
+1. 在 `app/services/` 下创建新的 Service 类 (如 `NewTTSService`)。
+2. 实现 `generate` 方法,可以使用 subprocess 调用,也可以是 HTTP 请求。
+3. **重要**: 如果模型占用 GPU,请务必使用 `asyncio.Lock` 进行并发控制,防止 OOM。
+4. 在 `app/api/` 中添加对应的路由调用。
+
+### 添加定时任务
+
+目前推荐使用 **APScheduler** 或 **Crontab** 来管理定时任务。
+社交媒体的定时发布功能目前依赖 `playwright` 的延迟执行,未来计划迁移到 Celery 队列。
+
+---
+
+## 🛡️ 错误处理
+
+全项目统一使用 `Loguru` 进行日志记录。
+
+```python
+from loguru import logger
+
+try:
+ # 业务逻辑
+except Exception as e:
+ logger.error(f"操作失败: {str(e)}")
+ raise HTTPException(status_code=500, detail="服务器内部错误")
+```
+
+---
+
+## 🧪 测试
+
+运行测试套件:
+
+```bash
+pytest
+```
diff --git a/Docs/DEPLOY_MANUAL.md b/Docs/DEPLOY_MANUAL.md
index 08b5ccc..cff9fab 100644
--- a/Docs/DEPLOY_MANUAL.md
+++ b/Docs/DEPLOY_MANUAL.md
@@ -77,7 +77,7 @@ python -m scripts.server # 测试能否启动,Ctrl+C 退出
---
-## 步骤 4: 安装后端依赖
+## 步骤 4: 安装后端依赖
```bash
cd /home/rongye/ProgramFiles/ViGent2/backend
@@ -92,22 +92,22 @@ pip install torch torchvision torchaudio --index-url https://download.pytorch.or
# 安装 Python 依赖
pip install -r requirements.txt
-# 安装 Playwright 浏览器(社交发布需要)
-playwright install chromium
-```
-
----
-
-### 可选:AI 标题/标签生成
-
-> ✅ 如需启用“AI 标题/标签生成”功能,请确保后端可访问外网 API。
-
-- 需要可访问 `https://open.bigmodel.cn`
-- API Key 配置在 `backend/app/services/glm_service.py`(建议替换为自己的密钥)
-
----
-
-## 步骤 5: 部署用户认证系统 (Supabase + Auth)
+# 安装 Playwright 浏览器(社交发布需要)
+playwright install chromium
+```
+
+---
+
+### 可选:AI 标题/标签生成
+
+> ✅ 如需启用“AI 标题/标签生成”功能,请确保后端可访问外网 API。
+
+- 需要可访问 `https://open.bigmodel.cn`
+- API Key 配置在 `backend/app/services/glm_service.py`(建议替换为自己的密钥)
+
+---
+
+## 步骤 5: 部署用户认证系统 (Supabase + Auth)
> 🔐 **包含**: 登录/注册、Supabase 数据库配置、JWT 认证、管理员后台
@@ -292,7 +292,17 @@ pm2 save
curl http://localhost:8009/health
```
-### 5. 保存当前列表 (开机自启)
+### 5. 启动服务看门狗 (Watchdog)
+
+> 🛡️ **推荐**:监控 Qwen-TTS 和 LatentSync 服务健康状态,卡死时自动重启。
+
+```bash
+cd /home/rongye/ProgramFiles/ViGent2
+pm2 start ./run_watchdog.sh --name vigent2-watchdog
+pm2 save
+```
+
+### 6. 保存当前列表 (开机自启)
```bash
pm2 save
@@ -357,7 +367,46 @@ server {
---
-## 步骤 12: 配置阿里云 Nginx 网关 (关键)
+---
+
+## 步骤 13: 部署可选功能 (字幕与文案助手)
+
+本节介绍如何部署逐字高亮字幕、片头标题以及文案提取助手功能。
+
+### 13.1 部署字幕系统 (Subtitle System)
+
+包含 `faster-whisper` (字幕生成) 和 `Remotion` (视频渲染) 组件。
+
+详细步骤请参考:**[字幕功能部署指南](SUBTITLE_DEPLOY.md)**
+
+简要步骤:
+1. 安装 Python 依赖: `faster-whisper`
+2. 安装 Node.js 依赖: `npm install` (在 `remotion/` 目录)
+3. 验证: `npx remotion --version`
+
+### 13.2 部署文案提取助手 (Copywriting Assistant)
+
+支持 B站/抖音/TikTok 视频链接提取文案与 AI 洗稿。
+
+1. **安装核心依赖**:
+ ```bash
+ cd /home/rongye/ProgramFiles/ViGent2/backend
+ source venv/bin/activate
+ pip install yt-dlp zai-sdk
+ ```
+
+2. **配置 AI 洗稿 (GLM)**:
+ 确保 `.env` 中已配置 `GLM_API_KEY`:
+ ```ini
+ GLM_API_KEY=your_zhipu_api_key
+ ```
+
+3. **验证**:
+ 访问 `http://localhost:8006/docs`,测试 `/api/tools/extract-script` 接口。
+
+---
+
+## 步骤 14: 配置阿里云 Nginx 网关 (关键)
> ⚠️ **CRITICAL**: 如果使用 `api.hbyrkj.top` 等域名作为入口,必须在阿里云 (或公网入口) 的 Nginx 配置中解除上传限制。
> **这是导致 500/413 错误的核心原因。**
@@ -435,16 +484,16 @@ pm2 logs vigent2-qwen-tts
## 依赖清单
-### 后端关键依赖
+### 后端关键依赖
| 依赖 | 用途 |
|------|------|
| `fastapi` | Web API 框架 |
| `uvicorn` | ASGI 服务器 |
-| `edge-tts` | 微软 TTS 配音 |
-| `httpx` | GLM API HTTP 客户端 |
-| `playwright` | 社交媒体自动发布 |
-| `biliup` | B站视频上传 |
+| `edge-tts` | 微软 TTS 配音 |
+| `httpx` | GLM API HTTP 客户端 |
+| `playwright` | 社交媒体自动发布 |
+| `biliup` | B站视频上传 |
| `loguru` | 日志管理 |
### 前端关键依赖
diff --git a/Docs/DevLogs/Day16.md b/Docs/DevLogs/Day16.md
new file mode 100644
index 0000000..4c7ced9
--- /dev/null
+++ b/Docs/DevLogs/Day16.md
@@ -0,0 +1,60 @@
+---
+
+## 🔧 Qwen-TTS Flash Attention 优化 (10:00)
+
+### 优化背景
+Qwen3-TTS 1.7B 模型在默认情况下加载速度慢,推理显存占用高。通过引入 Flash Attention 2,可以显著提升模型加载速度和推理效率。
+
+### 实施方案
+在 `qwen-tts` Conda 环境中安装 `flash-attn`:
+
+```bash
+conda activate qwen-tts
+pip install -U flash-attn --no-build-isolation
+```
+
+### 验证结果
+- **加载速度**: 从 ~60s 提升至 **8.9s** ⚡
+- **显存占用**: 显著降低,消除 OOM 风险
+- **代码变动**: 无代码变动,仅环境优化 (自动检测)
+
+---
+
+## 🛡️ 服务看门狗 Watchdog (10:30)
+
+### 问题描述
+常驻服务 (`vigent2-qwen-tts` 和 `vigent2-latentsync`) 可能会因显存碎片或长时间运行出现僵死 (Port open but unresponsive)。
+
+### 解决方案
+开发了一个 Python Watchdog 脚本,每 30 秒轮询服务的 `/health` 接口,如果连续 3 次失败则自动重启服务。
+
+1. **Watchdog 脚本**: `backend/scripts/watchdog.py`
+2. **启动脚本**: `run_watchdog.sh` (基于 PM2)
+
+### 核心逻辑
+```python
+# 连续 3 次心跳失败触发重启
+if service["failures"] >= service['threshold']:
+ subprocess.run(["pm2", "restart", service["name"]])
+```
+
+### 部署状态
+- `vigent2-watchdog` 已启动并加入 PM2 列表
+- 监控对象: `vigent2-qwen-tts` (8009), `vigent2-latentsync` (8007)
+
+---
+
+## ⚡ LatentSync 性能确认
+
+经代码审计,LatentSync 1.6 已内置优化:
+- ✅ **Flash Attention**: 原生使用 `torch.nn.functional.scaled_dot_product_attention`
+- ✅ **DeepCache**: 已启用 (`cache_interval=3`),提供 ~2.5x 加速
+- ✅ **GPU 并发**: 双卡流水线 (GPU0 TTS | GPU1 LipSync) 已确认工作正常
+
+---
+
+## 📝 文档更新
+
+- [x] `Docs/QWEN3_TTS_DEPLOY.md`: 添加 Flash Attention 安装指南
+- [x] `Docs/DEPLOY_MANUAL.md`: 添加 Watchdog 部署说明
+- [x] `Docs/task_complete.md`: 更新进度至 100% (Day 16)
diff --git a/Docs/FRONTEND_README.md b/Docs/FRONTEND_README.md
new file mode 100644
index 0000000..28b9042
--- /dev/null
+++ b/Docs/FRONTEND_README.md
@@ -0,0 +1,95 @@
+# ViGent2 Frontend
+
+ViGent2 的前端界面,采用 Next.js 14 + TailwindCSS 构建。
+
+## ✨ 核心功能
+
+### 1. 视频生成 (`/`)
+- **素材管理**: 拖拽上传人物视频,实时预览。
+- **文案配音**: 集成 EdgeTTS,支持多音色选择 (云溪 / 晓晓)。
+- **AI 标题/标签**: 一键生成视频标题与标签 (Day 14)。
+- **进度追踪**: 实时显示视频生成进度 (10% -> 100%)。
+- **结果预览**: 生成完成后直接播放下载。
+- **本地保存**: 文案/标题自动保存,刷新后恢复 (Day 14)。
+
+### 2. 全自动发布 (`/publish`) [Day 7 新增]
+- **多平台管理**: 统一管理 B站、抖音、小红书账号状态。
+- **扫码登录**:
+ - 集成后端 Playwright 生成的 QR Code。
+ - 实时检测扫码状态 (Wait/Success)。
+ - Cookie 自动保存与状态同步。
+- **发布配置**: 设置视频标题、标签、简介。
+- **定时任务**: 支持 "立即发布" 或 "定时发布"。
+
+### 3. 声音克隆 [Day 13 新增]
+- **TTS 模式选择**: EdgeTTS (预设音色) / 声音克隆 (自定义音色) 切换。
+- **参考音频管理**: 上传/列表/删除参考音频 (3-20秒 WAV)。
+- **一键克隆**: 选择参考音频后自动调用 Qwen3-TTS 服务。
+
+### 4. 字幕与标题 [Day 13 新增]
+- **片头标题**: 可选输入,视频开头显示 3 秒淡入淡出标题。
+- **逐字高亮字幕**: 卡拉OK效果,默认开启,可关闭。
+- **自动对齐**: 基于 faster-whisper 生成字级别时间戳。
+
+### 5. 账户设置 [Day 15 新增]
+- **手机号登录**: 11位中国手机号验证登录。
+- **账户下拉菜单**: 显示有效期 + 修改密码 + 安全退出。
+- **修改密码**: 弹窗输入当前密码与新密码,修改后强制重新登录。
+
+### 6. 文案提取助手 (`ScriptExtractionModal`) [Day 15 新增]
+- **多源提取**: 支持文件拖拽上传与 URL 粘贴 (B站/抖音/TikTok)。
+- **AI 洗稿**: 集成 GLM-4.7-Flash,自动改写为口播文案。
+- **一键填入**: 提取结果直接填充至视频生成输入框。
+- **智能交互**: 实时进度展示,防误触设计。
+
+## 🛠️ 技术栈
+
+- **框架**: Next.js 14 (App Router)
+- **样式**: TailwindCSS
+- **图标**: Lucide React
+- **组件**: 自定义现代化组件 (Glassmorphism 风格)
+- **API**: Axios 实例 `@/lib/axios` (对接后端 FastAPI :8006)
+
+## 🚀 开发指南
+
+### 安装依赖
+
+```bash
+npm install
+```
+
+### 启动开发服务器
+
+默认运行在 **3002** 端口 (通过 `package.json` 配置):
+
+```bash
+npm run dev
+# 访问: http://localhost:3002
+```
+
+### 目录结构
+
+```
+src/
+├── app/
+│ ├── page.tsx # 视频生成主页
+│ ├── publish/ # 发布管理页
+│ │ └── page.tsx
+│ └── layout.tsx # 全局布局 (导航栏)
+├── components/ # UI 组件
+│ ├── VideoUploader.tsx # 视频上传
+│ ├── StatusBadge.tsx # 状态徽章
+│ └── ...
+└── lib/ # 工具函数
+```
+
+## 🔌 后端对接
+
+- **Base URL**: `http://localhost:8006`
+- **代理配置**: Next.js Rewrites (如需) 或直接 CORS。
+
+## 🎨 设计规范
+
+- **主色调**: 深紫/黑色系 (Dark Mode)
+- **交互**: 悬停微动画 (Hover Effects)
+- **响应式**: 适配桌面端大屏操作
diff --git a/models/LatentSync/DEPLOY.md b/Docs/LatentSync_DEPLOY.md
similarity index 100%
rename from models/LatentSync/DEPLOY.md
rename to Docs/LatentSync_DEPLOY.md
diff --git a/Docs/QWEN3_TTS_DEPLOY.md b/Docs/QWEN3_TTS_DEPLOY.md
index 2a7b5ab..602a23f 100644
--- a/Docs/QWEN3_TTS_DEPLOY.md
+++ b/Docs/QWEN3_TTS_DEPLOY.md
@@ -55,9 +55,9 @@ pip install -e .
conda install -y -c conda-forge sox
```
-### 可选: 安装 FlashAttention (推荐)
+### 可选: 安装 FlashAttention (强烈推荐)
-FlashAttention 可以显著提升推理速度并减少显存占用:
+FlashAttention 可以显著提升推理速度 (加载时间减少 85%) 并减少显存占用:
```bash
pip install -U flash-attn --no-build-isolation
diff --git a/Docs/implementation_plan.md b/Docs/implementation_plan.md
index c03048c..83b4c0b 100644
--- a/Docs/implementation_plan.md
+++ b/Docs/implementation_plan.md
@@ -348,6 +348,15 @@ cp -r SuperIPAgent/social-auto-upload backend/social_upload
- [x] 前端登录/注册页面更新
- [x] 数据库迁移脚本 (migrate_to_phone.sql)
+### 阶段十九:深度性能优化与服务守护 (Day 16) ✅
+
+> **目标**:提升系统响应速度与服务稳定性
+
+- [x] Flash Attention 2 集成 (Qwen3-TTS 加速 5x)
+- [x] LatentSync 性能调优 (OMP 线程限制 + 原生 Flash Attn)
+- [x] Watchdog 服务守护 (自动重启僵死服务)
+- [x] 文档体系更新 (部署手册与运维指南)
+
---
## 项目目录结构 (最终)
diff --git a/Docs/task_complete.md b/Docs/task_complete.md
index 530c198..c9f3bca 100644
--- a/Docs/task_complete.md
+++ b/Docs/task_complete.md
@@ -1,422 +1,91 @@
-# ViGent 数字人口播系统 - 开发任务清单
+# ViGent2 开发任务清单 (Task Log)
-**项目**:ViGent2 数字人口播视频生成系统
-**服务器**:Dell R730 (2× RTX 3090 24GB)
-**更新时间**:2026-02-02
-**整体进度**:100%(Day 15 手机号登录迁移 + 账户设置功能完成)
-
-## 📖 快速导航
-
-| 章节 | 说明 |
-|------|------|
-| [已完成任务](#-已完成任务) | Day 1-13 完成的功能 |
-| [后续规划](#️-后续规划) | 待办项目 |
-| [进度统计](#-进度统计) | 各模块完成度 |
-| [里程碑](#-里程碑) | 关键节点 |
-| [时间线](#-时间线) | 开发历程 |
-
-**相关文档**:
-- [Day 日志](file:///d:/CodingProjects/Antigravity/ViGent2/Docs/DevLogs/) (Day1-Day15)
-- [部署指南](file:///d:/CodingProjects/Antigravity/ViGent2/Docs/DEPLOY_MANUAL.md)
-- [Qwen3-TTS 部署](file:///d:/CodingProjects/Antigravity/ViGent2/Docs/QWEN3_TTS_DEPLOY.md)
+**项目**: ViGent2 数字人口播视频生成系统
+**进度**: 100% (Day 16 - 深度优化完成)
+**更新时间**: 2026-02-03
---
-## ✅ 已完成任务
+## 📅 对话历史与开发日志
-### 阶段一:核心功能验证
-- [x] EdgeTTS 配音集成
-- [x] FFmpeg 视频合成
-- [x] MuseTalk 唇形同步 (代码集成)
-- [x] 端到端流程验证
+> 这里记录了每一天的核心开发内容与 milestone。
-### 阶段二:后端 API 开发
-- [x] FastAPI 项目搭建
-- [x] 视频生成 API
-- [x] 素材管理 API
-- [x] 文件存储管理
+### Day 16: 深度性能优化 (Current) 🚀
+- [x] **Qwen-TTS 加速**: 集成 Flash Attention 2,模型加载速度提升至 8.9s。
+- [x] **服务守护**: 开发 `Watchdog` 看门狗机制,自动监控并重启僵死服务。
+- [x] **LatentSync 性能确认**: 验证 DeepCache + 原生 Flash Attn 生效。
+- [x] **文档重构**: 全面更新 README、部署手册及后端文档。
-### 阶段三:前端 Web UI
-- [x] Next.js 项目初始化
-- [x] 视频生成页面
-- [x] 发布管理页面
-- [x] 任务状态展示
+### Day 15: 手机号认证迁移
+- [x] **认证系统升级**: 从邮箱迁移至 11 位手机号注册/登录。
+- [x] **账户管理**: 新增修改密码、有效期显示、安全退出功能。
+- [x] **AI 文案助手**: 升级 GLM-4.7-Flash,支持 B站/抖音链接提取与洗稿。
-### 阶段四:社交媒体发布
-- [x] Playwright 自动化框架
-- [x] Cookie 管理功能
-- [x] 多平台发布 UI
-- [x] 定时发布功能 (Day 7)
-- [x] QR码自动登录 (Day 7)
+### Day 14: AI 增强与体验优化
+- [x] **AI 标题/标签**: 集成 GLM-4API 自动生成视频元数据。
+- [x] **字幕升级**: Remotion 逐字高亮字幕 (卡拉OK效果) 及动画片头。
+- [x] **模型升级**: Qwen3-TTS 升级至 1.7B-Base 版本。
-### 阶段五:部署与文档
-- [x] 手动部署指南 (DEPLOY_MANUAL.md)
-- [x] 一键部署脚本 (deploy.sh)
-- [x] 环境配置模板 (.env.example)
-- [x] 项目文档 (README.md)
-- [x] 端口配置 (8006/3002)
+### Day 13: 声音克隆集成
+- [x] **声音克隆微服务**: 封装 Qwen3-TTS 为独立 API (8009端口)。
+- [x] **参考音频管理**: Supabase 存储桶配置与管理接口。
+- [x] **多模态 TTS**: 前端支持 EdgeTTS / Clone Voice 切换。
-### 阶段六:MuseTalk 服务器部署 (Day 2-3)
-- [x] conda 环境配置 (musetalk)
-- [x] 模型权重下载 (~7GB)
-- [x] subprocess 调用方式实现
-- [x] 健康检查功能
-- [x] 实际推理调用验证 (Day 3 修复)
+### Day 12: 移动端适配
+- [x] **iOS 兼容**: 修复 Safari 安全区域、状态栏颜色、Cookie 拦截问题。
+- [x] **响应式 UI**: 移动端 Header 与发布页重构。
-### 阶段七:MuseTalk 完整修复 (Day 4)
-- [x] 权重检测路径修复 (软链接)
-- [x] 音视频长度不匹配修复 (audio_processor.py)
-- [x] 推理脚本错误日志增强 (inference.py)
-- [x] 视频合成 MP4 生成验证
-- [x] 端到端流程完整测试
+### Day 11: 上传架构重构
+- [x] **直传优化**: 前端直传 Supabase Storage,解决 Nginx 30s 超时问题。
+- [x] **数据隔离**: 用户素材/视频按 UserID 物理隔离。
-### 阶段八:前端功能增强 (Day 5)
-- [x] Web 视频上传功能
-- [x] 上传进度显示
-- [x] 自动刷新素材列表
+### Day 10: HTTPS 与安全
+- [x] **HTTPS 部署**: 配置 SSL 证书与 Nginx 反向代理。
+- [x] **安全加固**: Supabase Studio 增加 Basic Auth 保护。
-### 阶段九:唇形同步模型升级 (Day 6)
-- [x] MuseTalk → LatentSync 1.6 迁移
-- [x] 后端代码适配 (config.py, lipsync_service.py)
-- [x] Conda 环境配置 (latentsync)
-- [x] 模型权重部署指南
-- [x] 服务器端到端验证
+### Day 9: 认证系统与发布闭环
+- [x] **用户系统**: 基于 Supabase Auth 实现 JWT 认证。
+- [x] **发布闭环**: 验证 B站/抖音/小红书 自动发布流程。
+- [x] **服务自愈**: 配置 PM2 进程守护。
-### 阶段十:性能优化 (Day 6)
-- [x] 视频预压缩优化 (高分辨率自动压缩到720p)
-- [x] 进度更新细化 (5% → 10% → 25% → ... → 100%)
-- [x] LipSync 服务单例缓存
-- [x] 健康检查缓存 (5分钟)
-- [x] 异步子进程修复 (subprocess.run → asyncio)
-- [x] 预加载模型服务 (常驻 Server + FastAPI)
-- [x] 批量队列处理 (GPU 并发控制)
-
-### 阶段十一:社交媒体发布完善 (Day 7)
-- [x] QR码自动登录 (Playwright headless)
-- [x] 多平台上传器架构 (B站/抖音/小红书)
-- [x] B站发布 (biliup官方库)
-- [x] 抖音/小红书发布 (Playwright)
-- [x] 定时发布功能
-- [x] 前端发布UI优化
-- [x] Cookie自动管理
-- [x] UI一致性修复 (导航栏对齐、滚动条隐藏)
-- [x] QR登录超时修复 (Stealth模式、多选择器fallback)
-- [x] 文档规则优化 (智能修改标准、工具使用规范)
-
-### 阶段十二:用户体验优化 (Day 8)
-- [x] 文件名保留 (时间戳前缀 + 原始名称)
-- [x] 视频持久化 (从文件系统读取历史)
-- [x] 历史视频列表组件
-- [x] 素材/视频删除功能
-- [x] 登出功能 (Logout API + 前端按钮)
-- [x] 前端 SWR 轮询优化
-- [x] QR 登录状态检测修复
-
-### 阶段十三:发布模块优化 (Day 9)
-- [x] B站/抖音发布验证通过
-- [x] 资源清理保障 (try-finally)
-- [x] 超时保护 (消除无限循环)
-- [x] 小红书 headless 模式修复
-- [x] API 输入验证
-- [x] 完整类型提示
-- [x] 扫码登录等待界面 (加载动画)
-- [x] 抖音/B站登录策略优化 (Text优先)
-- [x] 发布成功审核提示
-
-### 阶段十四:用户认证系统 (Day 9)
-- [x] Supabase 数据库表设计与部署
-- [x] JWT 认证 (HttpOnly Cookie)
-- [x] 用户注册/登录/登出 API
-- [x] 管理员权限控制 (is_active)
-- [x] 单设备登录限制 (Session Token)
-- [x] 防止 Supabase 暂停 (GitHub Actions/Crontab)
-- [x] 认证部署文档 (AUTH_DEPLOY.md)
-
-### 阶段十五:部署稳定性优化 (Day 9)
-- [x] 后端依赖修复 (bcrypt/email-validator)
-- [x] 前端生产环境构建修复 (npm run build)
-- [x] LatentSync 性能卡顿修复 (OMP_NUM_THREADS限制)
-- [x] 部署服务自愈 (PM2 配置优化)
-- [x] 部署手册全量更新 (DEPLOY_MANUAL.md)
-
-### 阶段十六:HTTPS 部署与细节完善 (Day 10)
-- [x] 隧道访问修复 (StaticFiles 挂载 + Rewrite)
-- [x] 平台账号列表 500 错误修复 (paths.py)
-- [x] Nginx HTTPS 配置 (反向代理 + SSL)
-- [x] 浏览器标题修改 (ViGent)
-- [x] 代码自适应 HTTPS 验证
-- [x] **Supabase 自托管部署** (Docker, 3003/8008端口)
-- [x] **安全加固** (Basic Auth 保护后台)
-- [x] **端口冲突解决** (迁移 Analytics/Kong)
-
-### 阶段十七:上传架构重构 (Day 11)
-- [x] **直传改造** (前端直接上传 Supabase,绕过后端代理)
-- [x] **后端适配** (Signed URL 签名生成)
-- [x] **RLS 策略部署** (SQL 脚本自动化权限配置)
-- [x] **超时问题根治** (彻底解决 Nginx/FRP 30s 限制)
-- [x] **前端依赖更新** (@supabase/supabase-js 集成)
-
-### 阶段十八:用户隔离与存储优化 (Day 11)
-- [x] **用户数据隔离** (素材/视频/Cookie 按用户ID目录隔离)
-- [x] **Storage URL 修复** (SUPABASE_PUBLIC_URL 配置,修复 localhost 问题)
-- [x] **发布服务优化** (直接读取本地 Supabase Storage 文件,跳过 HTTP 下载)
-- [x] **Supabase Studio 配置** (公网访问配置)
-
-### 阶段十九:iOS 兼容与移动端 UI 优化 (Day 12)
-- [x] **Axios 全局拦截器** (401/403 自动跳转登录,防重复跳转)
-- [x] **iOS Safari 安全区域修复** (viewport-fit: cover, themeColor, 渐变背景统一)
-- [x] **移动端 Header 优化** (按钮紧凑布局,响应式间距)
-- [x] **发布页面 UI 重构** (立即发布/定时发布按钮分离,防误触设计)
-- [x] **Qwen3-TTS 1.7B 部署** (声音克隆模型,GPU0,更高质量)
-
-### 阶段二十:声音克隆功能集成 (Day 13)
-- [x] **Qwen3-TTS HTTP 服务** (独立 FastAPI 服务,端口 8009)
-- [x] **声音克隆服务** (voice_clone_service.py,HTTP 调用封装)
-- [x] **参考音频管理 API** (上传/列表/删除)
-- [x] **前端 TTS 模式选择** (EdgeTTS / 声音克隆切换)
-- [x] **Supabase ref-audios Bucket** (参考音频存储桶 + RLS 策略)
-- [x] **端到端测试验证** (声音克隆完整流程测试通过)
-
-### 阶段二十一:逐字高亮字幕 + 片头标题 (Day 13)
-- [x] **faster-whisper 字幕对齐** (字级别时间戳生成)
-- [x] **Remotion 视频渲染** (React 视频合成框架)
-- [x] **逐字高亮字幕** (卡拉OK效果)
-- [x] **片头标题** (淡入淡出动画)
-- [x] **前端标题/字幕设置 UI**
-- [x] **降级机制** (Remotion 失败时回退 FFmpeg)
-
-### 阶段二十二:AI 标题标签 + 前端稳定性修复 (Day 14)
-- [x] **Qwen3-TTS 1.7B 模型升级** (0.6B → 1.7B-Base)
-- [x] **字幕样式与标题动画优化** (Remotion 视觉增强)
-- [x] **AI 标题/标签生成** (GLM-4-Flash API)
-- [x] **生成结果同步到发布页** (localStorage 对齐)
-- [x] **文案/标题本地保存修复** (刷新后恢复)
-- [x] **登录页刷新循环修复** (公开路由跳转豁免)
-
-### 阶段二十三:手机号登录迁移 (Day 15)
-- [x] **认证迁移** (邮箱 → 11位手机号)
-- [x] **后端 API 适配** (auth.py/admin.py 手机号验证)
-- [x] **修改密码功能** (/api/auth/change-password 接口)
-- [x] **账户设置菜单** (首页下拉菜单:修改密码 + 有效期显示 + 退出登录)
-- [x] **有效期显示** (expires_at 字段显示在账户菜单)
-- [x] **点击外部关闭菜单** (useRef + useEffect 监听)
-- [x] **前端页面更新** (登录/注册/管理员页面)
-- [x] **数据库迁移脚本** (migrate_to_phone.sql)
+### Day 1-8: 核心功能构建
+- [x] **Day 8**: 历史记录持久化与文件管理。
+- [x] **Day 7**: 社交媒体自动登录与多平台发布。
+- [x] **Day 6**: **LatentSync 1.6** 升级与服务器部署。
+- [x] **Day 5**: 前端视频上传与进度反馈。
+- [x] **Day 4**: MuseTalk (旧版) 口型同步修复。
+- [x] **Day 3**: 服务器环境配置与模型权重下载。
+- [x] **Day 1-2**: 项目基础框架 (FastAPI + Next.js) 搭建。
---
-## 🛤️ 后续规划
+## 🛤️ 后续规划 (Roadmap)
### 🔴 优先待办
-- [ ] 批量视频生成架构设计
-
-### 🟠 功能完善
-- [x] Qwen3-TTS 集成到 ViGent2 ✅ Day 13 完成
-- [x] 定时发布功能 ✅ Day 7 完成
-- [x] 逐字高亮字幕 ✅ Day 13 完成
-- [ ] **后端定时发布** - 替代平台端定时,使用 APScheduler 实现任务调度
-- [ ] 批量视频生成
-- [ ] 字幕样式编辑器
+- [ ] **批量生成架构**: 支持 Excel 导入,批量生产视频。
+- [ ] **定时任务后台化**: 迁移前端触发的定时发布到后端 APScheduler。
### 🔵 长期探索
-- [ ] Docker 容器化
-- [ ] Celery 分布式任务队列
+- [ ] **容器化交付**: 提供完整的 Docker Compose 一键部署包。
+- [ ] **分布式队列**: 引入 Celery + Redis 处理超高并发任务。
---
-## 📊 进度统计
-
-### 总体进度
-```
-████████████████████ 100%
-```
-
-### 各模块进度
+## 📊 模块完成度
| 模块 | 进度 | 状态 |
|------|------|------|
-| 后端 API | 100% | ✅ 完成 |
-| 前端 UI | 100% | ✅ 完成 |
-| TTS 配音 | 100% | ✅ 完成 |
-| 视频合成 | 100% | ✅ 完成 |
-| 唇形同步 | 100% | ✅ LatentSync 1.6 升级完成 |
-| 社交发布 | 100% | ✅ Day 9 验证通过 |
-| 用户认证 | 100% | ✅ Day 9 Supabase+JWT |
-| 服务器部署 | 100% | ✅ Day 9 稳定性优化完成 |
+| **核心 API** | 100% | ✅ 稳定 |
+| **Web UI** | 100% | ✅ 稳定 (移动端适配) |
+| **唇形同步** | 100% | ✅ LatentSync 1.6 |
+| **TTS 配音** | 100% | ✅ EdgeTTS + Qwen3 |
+| **自动发布** | 100% | ✅ B站/抖音/小红书 |
+| **用户认证** | 100% | ✅ 手机号 + JWT |
+| **部署运维** | 100% | ✅ PM2 + Watchdog |
---
-## 🎯 里程碑
-
-### Milestone 1: 项目框架搭建 ✅
-**完成时间**: Day 1
-**成果**:
-- FastAPI 后端 + Next.js 前端
-- EdgeTTS + FFmpeg 集成
-- 视频生成端到端验证
-
-### Milestone 2: 服务器部署 ✅
-**完成时间**: Day 3
-**成果**:
-- PyTorch 2.0.1 + MMLab 环境修复
-- 模型目录重组与权重补全
-- MuseTalk 推理成功运行
-
-### Milestone 3: 口型同步完整修复 ✅
-**完成时间**: Day 4
-**成果**:
-- 权重检测路径修复 (软链接)
-- 音视频长度不匹配修复
-- 视频合成 MP4 验证通过 (28MB → 3.8MB)
-
-### Milestone 4: LatentSync 1.6 升级 ✅
-**完成时间**: Day 6
-**成果**:
-- MuseTalk → LatentSync 1.6 迁移
-- 512×512 高分辨率唇形同步
-- Latent Diffusion 架构升级
-- 性能优化 (视频预压缩、进度更新)
-
-### Milestone 5: 用户认证系统 ✅
-**完成时间**: Day 9
-**成果**:
-- Supabase 云数据库集成
-- 安全的 JWT + HttpOnly Cookie 认证
-- 管理员后台与用户隔离
-- 完善的部署与保活方案
-
-### Milestone 6: 生产环境部署稳定化 ✅
-**完成时间**: Day 9
-**成果**:
-- 修复了后端 (bcrypt) 和前端 (build) 的启动崩溃问题
-- 解决了 LatentSync 占用全量 CPU 导致服务器卡顿的严重问题
-- 完善了部署手册,记录了关键的 Troubleshooting 步骤
-- 实现了服务 Long-term 稳定运行 (Reset PM2 counter)
-
----
-
-## 📅 时间线
-
-Day 1: 项目初始化 + 核心功能 ✅ 完成
- - 后端 API 框架
- - 前端 UI
- - TTS + 视频合成
- - 社交发布框架
- - 部署文档
-
-Day 2: 服务器部署 + MuseTalk ✅ 完成
- - 端口配置 (8006/3002)
- - MuseTalk conda 环境初始化
- - subprocess 调用实现
- - 健康检查验证
-
-Day 3: 环境修复与验证 ✅ 完成
- - PyTorch 降级 (2.5 -> 2.0.1)
- - MMLab 依赖全量安装
- - 模型权重补全 (dwpose, syncnet)
- - 目录结构修复 (symlinks)
- - 推理脚本验证 (生成593帧)
-
-Day 4: 口型同步完整修复 ✅ 完成
- - 权重检测路径修复 (软链接)
- - audio_processor.py 音视频长度修复
- - inference.py 错误日志增强
- - MP4 视频合成验证通过
-
-Day 5: 前端功能增强 ✅ 完成
- - Web 视频上传功能
- - 上传进度显示
- - 自动刷新素材列表
-
-Day 6: LatentSync 1.6 升级 ✅ 完成
- - MuseTalk → LatentSync 迁移
- - 后端代码适配
- - 模型部署指南
- - 服务器部署验证
- - 性能优化 (视频预压缩、进度更新)
-
-Day 7: 社交媒体发布完善 ✅ 完成
- - QR码自动登录 (B站/抖音验证通过)
- - 智能定位策略 (CSS/Text并行)
- - 多平台发布 (B站/抖音/小红书)
- - UI 一致性优化
- - 文档规则体系优化
-
-Day 8: 用户体验优化 ✅ 完成
- - 文件名保留 (时间戳前缀)
- - 视频持久化 (历史视频API)
- - 历史视频列表组件
- - 素材/视频删除功能
-
-Day 9: 发布模块优化 ✅ 完成
- - B站/抖音登录+发布验证通过
- - 资源清理保障 (try-finally)
- - 超时保护 (消除无限循环)
- - 小红书 headless 模式修复
- - 扫码登录等待界面 (加载动画)
- - 抖音/B站登录策略优化 (Text优先)
- - 发布成功审核提示
- - 用户认证系统规划 (FastAPI+Supabase)
- - Supabase 表结构设计 (users/sessions)
- - 后端 JWT 认证实现 (auth.py/deps.py)
- - 数据库配置与 SQL 部署
- - 独立认证部署文档 (AUTH_DEPLOY.md)
- - 自动保活机制 (Crontab/Actions)
- - 部署稳定性优化 (Backend依赖修复)
- - 前端生产构建流程修复
- - LatentSync 严重卡顿修复 (线程数限制)
- - 部署手册全量更新
-
-Day 10: HTTPS 部署与细节完善 ✅ 完成
- - 隧道访问视频修正 (挂载 uploads)
- - 账号列表 Bug 修复 (paths.py 白名单)
- - 阿里云 Nginx HTTPS 部署
- - UI 细节优化 (Title 更新)
-
-Day 11: 上传架构重构 ✅ 完成
- - **核心修复**: Aliyun Nginx `client_max_body_size 0` 配置
- - 500 错误根治 (Direct Upload + Gateway Config)
- - Supabase RLS 权限策略部署
- - 前端集成 supabase-js
- - 彻底解决大文件上传超时 (30s 限制)
- - **用户数据隔离** (素材/视频/Cookie 按用户目录存储)
- - **Storage URL 修复** (SUPABASE_PUBLIC_URL 公网地址配置)
- - **发布服务优化** (本地文件直读,跳过 HTTP 下载)
-
-Day 12: iOS 兼容与移动端优化 ✅ 完成
- - Axios 全局拦截器 (401/403 自动跳转登录)
- - iOS Safari 安全区域白边修复 (viewport-fit: cover)
- - themeColor 配置 (状态栏颜色适配)
- - 渐变背景统一 (body 全局渐变,消除分层)
- - 移动端 Header 响应式优化 (按钮紧凑布局)
- - 发布页面 UI 重构 (立即发布 3/4 + 定时 1/4)
- - **Qwen3-TTS 1.7B 部署** (声音克隆模型,GPU0)
- - **部署文档** (QWEN3_TTS_DEPLOY.md)
-
-Day 13: 声音克隆 + 字幕功能 ✅ 完成
- - Qwen3-TTS HTTP 服务 (独立 FastAPI,端口 8009)
- - 声音克隆服务 (voice_clone_service.py)
- - 参考音频管理 API (上传/列表/删除)
- - 前端 TTS 模式选择 (EdgeTTS / 声音克隆)
- - Supabase ref-audios Bucket 配置
- - 端到端测试验证通过
- - **faster-whisper 字幕对齐** (字级别时间戳)
- - **Remotion 视频渲染** (逐字高亮字幕 + 片头标题)
- - **前端标题/字幕设置 UI**
- - **部署文档** (SUBTITLE_DEPLOY.md)
-
-Day 14: 模型升级 + AI 标题标签 + 前端修复 ✅ 完成
- - Qwen3-TTS 1.7B 模型升级 (0.6B → 1.7B-Base)
- - 字幕样式与标题动画优化 (Remotion)
- - AI 标题/标签生成接口 + 前端同步
- - 文案/标题本地保存修复 (刷新后恢复)
- - 登录页刷新循环修复 (公开路由跳转豁免)
-
-Day 15: 手机号登录迁移 + 账户设置 ✅ 完成
- - **认证系统迁移** (邮箱 → 11位手机号)
- - **账户设置** (修改密码 + 退出登录 + 有效期显示)
- - **GLM-4.7 模型升级** (文案洗稿效果提升)
- - **文案提取助手** (支持 B站/抖音/URL 提取 + 自动洗稿)
- - **视频预览功能** (素材列表预览 + 交互优化)
- - **前端交互优化** (滚动条美化、弹窗误触修复)
+## 📎 相关文档
+- [详细开发日志 (DevLogs)](file:///d:/CodingProjects/Antigravity/ViGent2/Docs/DevLogs/)
+- [部署手册 (DEPLOY_MANUAL)](file:///d:/CodingProjects/Antigravity/ViGent2/Docs/DEPLOY_MANUAL.md)
diff --git a/README.md b/README.md
index 02545c2..c377a16 100644
--- a/README.md
+++ b/README.md
@@ -1,44 +1,62 @@
# ViGent2 - 数字人口播视频生成系统
-基于 **LatentSync 1.6 + EdgeTTS** 的开源数字人口播视频生成系统。
+
-> 📹 上传静态人物视频 → 🎙️ 输入口播文案 → 🎬 自动生成唇形同步视频
+> 📹 **上传人物** · 🎙️ **输入文案** · 🎬 **一键成片**
+
+基于 **LatentSync 1.6 + EdgeTTS** 的开源数字人口播视频生成系统。
+集成 **Qwen3-TTS** 声音克隆与自动社交媒体发布功能。
+
+[功能特性](#-功能特性) • [技术栈](#-技术栈) • [文档中心](#-文档中心) • [部署指南](Docs/DEPLOY_MANUAL.md)
+
+
---
## ✨ 功能特性
-- 🎬 **唇形同步** - LatentSync 1.6 驱动,512×512 高分辨率 Diffusion 模型
-- 🎙️ **TTS 配音** - EdgeTTS 多音色支持(云溪、晓晓等)
-- 🔊 **声音克隆** - Qwen3-TTS 1.7B,3秒参考音频快速克隆(更高质量)
-- 📝 **逐字高亮字幕** - faster-whisper + Remotion,卡拉OK效果 🆕
-- 🎬 **片头标题** - 淡入淡出动画,可自定义 🆕
-- 🤖 **AI 标题/标签生成** - GLM-4.7-Flash 自动生成标题与标签 (升级版) 🆕
-- 📜 **文案提取助手** - 支持 B站/抖音/TikTok 视频链接提取与 AI 洗稿 🆕
-- 📽️ **上传视频预览** - 素材列表支持直接预览播放 🆕
-- 📱 **全自动发布** - 扫码登录 + Cookie持久化,支持多平台(B站/抖音/小红书)定时发布
-- 🖥️ **Web UI** - Next.js 现代化界面,iOS/Android 移动端适配
-- 🔐 **用户系统** - Supabase + JWT 认证,**手机号登录** + 管理员后台 🆕
-- ⚙️ **账户设置** - 修改密码 + 有效期显示 + 安全退出 🆕
-- 👥 **多用户隔离** - 素材/视频/Cookie 按用户独立存储,数据完全隔离
-- 🚀 **性能优化** - 视频预压缩、常驻模型服务 (0s加载)、本地文件直读、并发控制
-- 🌐 **全局任务管理** - 跨页面任务状态同步,实时进度显示
+### 核心能力
+- 🎬 **高清唇形同步** - LatentSync 1.6 驱动,512×512 高分辨率 Latent Diffusion 模型。
+- 🎙️ **多模态配音** - 支持 **EdgeTTS** (微软超自然语音) 和 **Qwen3-TTS** (3秒极速声音克隆)。
+- 📝 **智能字幕** - 集成 faster-whisper + Remotion,自动生成逐字高亮 (卡拉OK效果) 字幕。
+- 🤖 **AI 辅助创作** - 内置 GLM-4.7-Flash,支持 B站/抖音链接文案提取、AI 洗稿、标题/标签自动生成。
+
+### 平台化功能
+- 📱 **全自动发布** - 支持 B站、抖音、小红书定时发布,扫码登录 + Cookie 持久化。
+- 🔐 **企业级认证** - 完善的用户隔离系统 (Supabase),支持手机号注册/登录、密码管理。
+- 🛡️ **服务守护** - 内置 Watchdog 看门狗机制,自动监控并重启僵死服务,确保 7x24h 稳定运行。
+- 🚀 **极致性能** - 视频预压缩、模型常驻服务 (0s加载)、双 GPU 流水线并发。
+
+---
## 🛠️ 技术栈
-| 模块 | 技术 |
-|------|------|
-| 前端 | Next.js 14 + TypeScript + TailwindCSS |
-| 后端 | FastAPI + Python 3.10 |
-| 数据库 | **Supabase** (PostgreSQL) 自托管 Docker |
-| 存储 | **Supabase Storage** (本地文件系统) |
-| 认证 | **JWT** + HttpOnly Cookie |
-| 唇形同步 | **LatentSync 1.6** (Latent Diffusion, 512×512) |
-| TTS | EdgeTTS |
-| 声音克隆 | **Qwen3-TTS 1.7B** |
-| 字幕渲染 | **faster-whisper + Remotion** |
-| 视频处理 | FFmpeg |
-| 自动发布 | Playwright |
+| 领域 | 核心技术 | 说明 |
+|------|----------|------|
+| **前端** | Next.js 14 | TypeScript, TailwindCSS, SWR |
+| **后端** | FastAPI | Python 3.10, AsyncIO, PM2 |
+| **数据库** | Supabase | PostgreSQL, Storage (本地/S3), Auth |
+| **唇形同步** | LatentSync 1.6 | PyTorch 2.5, Diffusers, DeepCache |
+| **声音克隆** | Qwen3-TTS | 1.7B 参数量,Flash Attention 2 加速 |
+| **自动化** | Playwright | 社交媒体无头浏览器自动化 |
+| **部署** | Docker & PM2 | 混合部署架构 |
+
+---
+
+## 📖 文档中心
+
+我们提供了详尽的开发与部署文档:
+
+### 部署运维
+- **[部署手册 (DEPLOY_MANUAL.md)](Docs/DEPLOY_MANUAL.md)** - 👈 **部署请看这里**!包含完整的环境搭建步骤。
+- [参考音频服务部署 (QWEN3_TTS_DEPLOY.md)](Docs/QWEN3_TTS_DEPLOY.md) - 声音克隆模型部署指南。
+- [LatentSync 部署指南](models/LatentSync/DEPLOY.md) - 唇形同步模型独立部署。
+- [用户认证部署 (AUTH_DEPLOY.md)](Docs/AUTH_DEPLOY.md) - Supabase 与 Auth 系统配置。
+
+### 开发文档
+- [后端开发指南](Docs/BACKEND_README.md) - 接口规范与开发流程。
+- [前端开发指南](Docs/FRONTEND_DEV.md) - UI 组件与页面规范。
+- [开发日志 (DevLogs)](Docs/DevLogs/) - 每日开发进度与技术决策记录。
---
@@ -46,140 +64,33 @@
```
ViGent2/
-├── backend/ # FastAPI 后端
-│ ├── app/
-│ │ ├── api/ # API 路由
-│ │ ├── services/ # 核心服务 (TTS, LipSync, Video)
-│ │ └── core/ # 配置
-│ ├── requirements.txt
-│ └── .env.example
-├── frontend/ # Next.js 前端
-│ └── src/app/
-├── models/ # AI 模型
-│ └── LatentSync/ # 唇形同步模型
-│ └── DEPLOY.md # LatentSync 部署指南
-└── Docs/ # 文档
- ├── DEPLOY_MANUAL.md # 部署手册
- ├── AUTH_DEPLOY.md # 认证部署指南
- ├── task_complete.md
- └── DevLogs/
+├── backend/ # FastAPI 后端服务
+│ ├── app/ # 核心业务逻辑
+│ ├── scripts/ # 运维脚本 (Watchdog 等)
+│ └── tests/ # 测试用例
+├── frontend/ # Next.js 前端应用
+├── models/ # AI 模型仓库
+│ ├── LatentSync/ # 唇形同步服务
+│ └── Qwen3-TTS/ # 声音克隆服务
+└── Docs/ # 项目文档
```
---
-## 🚀 快速开始
+## 🌐 服务架构
-### 1. 克隆项目
+系统采用微服务架构设计,各组件独立运行:
-```bash
-git clone <仓库地址> /home/rongye/ProgramFiles/ViGent2
-cd /home/rongye/ProgramFiles/ViGent2
-```
-
-### 2. 安装后端
-
-```bash
-cd backend
-python -m venv venv
-source venv/bin/activate # Windows: venv\Scripts\activate
-pip install -r requirements.txt
-cp .env.example .env
-```
-
-### 3. 安装前端
-
-```bash
-cd frontend
-npm install
-```
-
-### 4. 安装 LatentSync (服务器)
-
-详见 [models/LatentSync/DEPLOY.md](models/LatentSync/DEPLOY.md)
-
-```bash
-# 创建独立 Conda 环境
-conda create -n latentsync python=3.10.13
-conda activate latentsync
-
-# 安装依赖并下载权重
-cd models/LatentSync
-pip install -r requirements.txt
-huggingface-cli download ByteDance/LatentSync-1.6 --local-dir checkpoints
-```
-
-### 5. 启动服务
-
-```bash
-# 终端 1: 后端 (端口 8006)
-cd backend && source venv/bin/activate
-uvicorn app.main:app --host 0.0.0.0 --port 8006
-
-# 终端 2: 前端 (端口 3002)
-cd frontend
-npm run dev -- -p 3002
-
-# 终端 3: LatentSync 服务 (端口 8007, 推荐启动)
-cd models/LatentSync
-nohup python -m scripts.server > server.log 2>&1 &
-```
+| 服务名称 | 端口 | 用途 |
+|----------|------|------|
+| **Web UI** | 3002 | 用户访问入口 (Next.js) |
+| **Backend API** | 8006 | 核心业务接口 (FastAPI) |
+| **LatentSync** | 8007 | 唇形同步推理服务 |
+| **Qwen3-TTS** | 8009 | 声音克隆推理服务 |
+| **Supabase** | 8008 | 数据库与认证网关 |
---
-## 🖥️ 服务器配置
+## ⚖️ License
-**目标服务器**: Dell PowerEdge R730
-
-| 配置 | 规格 |
-|------|------|
-| CPU | 2× Intel Xeon E5-2680 v4 (56 线程) |
-| 内存 | 192GB DDR4 |
-| GPU | 2× NVIDIA RTX 3090 24GB |
-| 存储 | 4.47TB |
-
-**GPU 分配**:
-- GPU 0: 其他服务
-- GPU 1: **LatentSync** 唇形同步 (~18GB VRAM)
-
----
-
-## 🌐 访问地址
-
-| 服务 | 地址 | 说明 |
-|------|------|------|
-| **视频生成 (UI)** | `https://vigent.hbyrkj.top` | 用户访问入口 |
-| **API 服务** | `http://<服务器IP>:8006` | 后端 Swagger |
-| **认证管理 (Studio)** | `https://supabase.hbyrkj.top` | 需要 Basic Auth |
-| **认证 API (Kong)** | `https://api.hbyrkj.top` | Supabase 接口 |
-| **唇形同步服务** | `http://<服务器IP>:8007` | LatentSync |
-| **声音克隆服务** | `http://<服务器IP>:8009` | Qwen3-TTS |
-
----
-
-## 📖 文档
-
-- [手动部署指南](Docs/DEPLOY_MANUAL.md)
-- [Supabase 部署指南](Docs/SUPABASE_DEPLOY.md)
-- [Qwen3-TTS 部署指南](Docs/QWEN3_TTS_DEPLOY.md)
-- [字幕功能部署指南](Docs/SUBTITLE_DEPLOY.md)
-- [LatentSync 部署指南](models/LatentSync/DEPLOY.md)
-- [开发日志](Docs/DevLogs/)
- - [Day 15 - 手机号登录 + 账户设置](Docs/DevLogs/Day15.md) 🆕
-- [任务进度](Docs/task_complete.md)
-
----
-
-## 🆚 与 ViGent 的区别
-
-| 特性 | ViGent (v1) | ViGent2 |
-|------|-------------|---------|
-| 唇形同步模型 | MuseTalk v1.5 | **LatentSync 1.6** |
-| 分辨率 | 256×256 | **512×512** |
-| 架构 | GAN | **Latent Diffusion** |
-| 视频预处理 | 无 | **自动压缩优化** |
-
----
-
-## 📄 License
-
-MIT
+[MIT License](LICENSE) © 2026 ViGent Team
diff --git a/backend/app/api/ref_audios.py b/backend/app/api/ref_audios.py
index f36758a..3b983ae 100644
--- a/backend/app/api/ref_audios.py
+++ b/backend/app/api/ref_audios.py
@@ -127,7 +127,60 @@ async def upload_ref_audio(
if duration > 60.0:
raise HTTPException(status_code=400, detail="音频时长过长,最多 60 秒")
- # 生成存储路径
+
+ # 3. 处理重名逻辑 (Friendly Display Name)
+ original_name = file.filename
+
+ # 获取用户现有的所有参考音频列表 (为了检查文件名冲突)
+ # 注意: 这种列表方式在文件极多时性能一般,但考虑到单用户参考音频数量有限,目前可行
+ existing_files = await storage_service.list_files(BUCKET_REF_AUDIOS, user_id)
+ existing_names = set()
+
+ # 预加载所有现有的 display name
+ # 这里需要并发请求 metadata 可能会慢,优化: 仅检查 metadata 文件并解析
+ # 简易方案: 仅在 metadata 中读取 original_filename
+ # 但 list_files 返回的是 name,我们需要 metadata
+ # 考虑到性能,这里使用一种妥协方案:
+ # 我们不做全量检查,而是简单的检查:如果用户上传 myvoice.wav
+ # 我们看看有没有 (timestamp)_myvoice.wav 这种其实并不能准确判断 display name 是否冲突
+ #
+ # 正确做法: 应该有个数据库表存 metadata。但目前是无数据库设计。
+ #
+ # 改用简单方案:
+ # 既然我们无法快速获取所有 display name,
+ # 我们暂时只处理 "在新上传时,original_filename 保持原样"
+ # 但用户希望 "如果在列表中看到重复的,自动加(1)"
+ #
+ # 鉴于无数据库架构的限制,要在上传时知道"已有的 display name" 成本太高(需遍历下载所有json)。
+ #
+ # 💡 替代方案:
+ # 我们不检查旧的。我们只保证**存储**唯一。
+ # 对于用户提到的 "新上传的文件名后加个数字" -> 这通常是指 "另存为" 的逻辑。
+ # 既然用户现在的痛点是 "显示了时间戳太丑",而我已经去掉了时间戳显示。
+ # 那么如果用户上传两个 "TEST.wav",列表里就会有两个 "TEST.wav" (但时间不同)。
+ # 这其实是可以接受的。
+ #
+ # 但如果用户强求 "自动重命名":
+ # 我们可以在这里做一个轻量级的 "同名检测":
+ # 检查有没有 *_{original_name} 的文件存在。
+ # 如果 storage 里已经有 123_abc.wav, 456_abc.wav
+ # 我们可以认为 abc.wav 已经存在。
+
+ dup_count = 0
+ search_suffix = f"_{original_name}" # 比如 _test.wav
+
+ for f in existing_files:
+ fname = f.get('name', '')
+ if fname.endswith(search_suffix):
+ dup_count += 1
+
+ final_display_name = original_name
+ if dup_count > 0:
+ name_stem = Path(original_name).stem
+ name_ext = Path(original_name).suffix
+ final_display_name = f"{name_stem}({dup_count}){name_ext}"
+
+ # 生成存储路径 (唯一ID)
timestamp = int(time.time())
safe_name = sanitize_filename(Path(file.filename).stem)
storage_path = f"{user_id}/{timestamp}_{safe_name}.wav"
@@ -146,7 +199,7 @@ async def upload_ref_audio(
# 上传元数据 JSON
metadata = {
"ref_text": ref_text.strip(),
- "original_filename": file.filename,
+ "original_filename": final_display_name, # 这里的名字如果有重复会自动加(1)
"duration_sec": duration,
"created_at": timestamp
}
@@ -207,6 +260,7 @@ async def list_ref_audios(user: dict = Depends(get_current_user)):
ref_text = ""
duration_sec = 0.0
created_at = 0
+ original_filename = ""
try:
# 获取 metadata 内容
@@ -219,6 +273,7 @@ async def list_ref_audios(user: dict = Depends(get_current_user)):
ref_text = metadata.get("ref_text", "")
duration_sec = metadata.get("duration_sec", 0.0)
created_at = metadata.get("created_at", 0)
+ original_filename = metadata.get("original_filename", "")
except Exception as e:
logger.warning(f"读取 metadata 失败: {e}")
# 从文件名提取时间戳
@@ -230,9 +285,18 @@ async def list_ref_audios(user: dict = Depends(get_current_user)):
# 获取音频签名 URL
signed_url = await storage_service.get_signed_url(BUCKET_REF_AUDIOS, storage_path)
+ # 优先显示原始文件名 (去掉时间戳前缀)
+ display_name = original_filename if original_filename else name
+ # 如果原始文件名丢失,尝试从现有文件名中通过正则去掉时间戳
+ if not display_name or display_name == name:
+ # 匹配 "1234567890_filename.wav"
+ match = re.match(r'^\d+_(.+)$', name)
+ if match:
+ display_name = match.group(1)
+
items.append(RefAudioResponse(
id=storage_path,
- name=name,
+ name=display_name,
path=signed_url,
ref_text=ref_text,
duration_sec=duration_sec,
@@ -274,3 +338,74 @@ async def delete_ref_audio(audio_id: str, user: dict = Depends(get_current_user)
except Exception as e:
logger.error(f"删除参考音频失败: {e}")
raise HTTPException(status_code=500, detail=f"删除失败: {str(e)}")
+
+
+class RenameRequest(BaseModel):
+ new_name: str
+
+
+@router.put("/{audio_id:path}")
+async def rename_ref_audio(
+ audio_id: str,
+ request: RenameRequest,
+ user: dict = Depends(get_current_user)
+):
+ """重命名参考音频 (修改 metadata 中的 display name)"""
+ user_id = user["id"]
+
+ # 安全检查
+ if not audio_id.startswith(f"{user_id}/"):
+ raise HTTPException(status_code=403, detail="无权修改此文件")
+
+ new_name = request.new_name.strip()
+ if not new_name:
+ raise HTTPException(status_code=400, detail="新名称不能为空")
+
+ # 确保新名称有后缀 (保留原后缀或添加 .wav)
+ if not Path(new_name).suffix:
+ new_name += ".wav"
+
+ try:
+ # 1. 下载现有的 metadata
+ metadata_path = audio_id.replace(".wav", ".json")
+ try:
+ # 获取已有的 JSON
+ import httpx
+ metadata_url = await storage_service.get_signed_url(BUCKET_REF_AUDIOS, metadata_path)
+ if not metadata_url:
+ # 如果 json 不存在,则需要新建一个基础的
+ raise Exception("Metadata not found")
+
+ async with httpx.AsyncClient() as client:
+ resp = await client.get(metadata_url)
+ if resp.status_code == 200:
+ metadata = resp.json()
+ else:
+ raise Exception(f"Failed to fetch metadata: {resp.status_code}")
+
+ except Exception as e:
+ logger.warning(f"无法读取元数据: {e}, 将创建新的元数据")
+ # 兜底:如果读取失败,构建最小元数据
+ metadata = {
+ "ref_text": "", # 可能丢失
+ "duration_sec": 0.0,
+ "created_at": int(time.time()),
+ "original_filename": new_name
+ }
+
+ # 2. 更新 original_filename
+ metadata["original_filename"] = new_name
+
+ # 3. 覆盖上传 metadata
+ await storage_service.upload_file(
+ bucket=BUCKET_REF_AUDIOS,
+ path=metadata_path,
+ file_data=json.dumps(metadata, ensure_ascii=False).encode('utf-8'),
+ content_type="application/json"
+ )
+
+ return {"success": True, "name": new_name}
+
+ except Exception as e:
+ logger.error(f"重命名失败: {e}")
+ raise HTTPException(status_code=500, detail=f"重命名失败: {str(e)}")
diff --git a/backend/app/api/tools.py b/backend/app/api/tools.py
index 8a990d8..b7cbe05 100644
--- a/backend/app/api/tools.py
+++ b/backend/app/api/tools.py
@@ -38,11 +38,13 @@ async def extract_script_tool(
temp_dir.mkdir(parents=True, exist_ok=True)
# 1. 获取/保存文件
+ loop = asyncio.get_event_loop()
+
if file:
safe_filename = Path(file.filename).name.replace(" ", "_")
temp_path = temp_dir / f"tool_extract_{timestamp}_{safe_filename}"
- with open(temp_path, "wb") as buffer:
- shutil.copyfileobj(file.file, buffer)
+ # 文件 I/O 放入线程池
+ await loop.run_in_executor(None, lambda: shutil.copyfileobj(file.file, open(temp_path, "wb")))
logger.info(f"Tool processing upload file: {temp_path}")
else:
# URL 下载逻辑
@@ -55,8 +57,8 @@ async def extract_script_tool(
logger.info(f"Tool downloading URL: {url}")
- # 先尝试 yt-dlp
- try:
+ # 封装 yt-dlp 下载函数 (Blocking)
+ def _download_yt_dlp():
import yt_dlp
logger.info("Attempting download with yt-dlp...")
@@ -80,8 +82,12 @@ async def extract_script_tool(
id = info.get('id')
downloaded_file = str(temp_dir / f"tool_download_{timestamp}_{id}.{ext}")
- temp_path = Path(downloaded_file)
- logger.info(f"yt-dlp downloaded to: {temp_path}")
+ return Path(downloaded_file)
+
+ # 先尝试 yt-dlp (Run in Executor)
+ try:
+ temp_path = await loop.run_in_executor(None, _download_yt_dlp)
+ logger.info(f"yt-dlp downloaded to: {temp_path}")
except Exception as e:
logger.warning(f"yt-dlp download failed: {e}. Trying manual Douyin fallback...")
@@ -107,46 +113,48 @@ async def extract_script_tool(
if not temp_path or not temp_path.exists():
raise HTTPException(400, "文件获取失败")
- # 1.5 安全转换: 强制转为 WAV (16k) 传给 Whisper
- # 这一步既能验证文件有效性(ffmpeg会报错),又能避免 PyAV 音频解码 bug
+ # 1.5 安全转换: 强制转为 WAV (16k)
import subprocess
audio_path = temp_dir / f"extract_audio_{timestamp}.wav"
- try:
- # ffmpeg -i input -vn -acodec pcm_s16le -ar 16000 -ac 1 output.wav -y
- convert_cmd = [
- 'ffmpeg',
- '-i', str(temp_path),
- '-vn', # 忽略视频
- '-acodec', 'pcm_s16le',
- '-ar', '16000', # Whisper 推荐采样率
- '-ac', '1', # 单声道
- '-y', # 覆盖
- str(audio_path)
- ]
-
- # 捕获 stderr 以便出错时打印
- subprocess.run(convert_cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- logger.info(f"Converted to WAV: {audio_path}")
-
- # 使用转换后的文件
- target_path = audio_path
-
- except subprocess.CalledProcessError as e:
- error_log = e.stderr.decode('utf-8', errors='ignore') if e.stderr else str(e)
- logger.error(f"FFmpeg check/convert failed: {error_log}")
-
- # 尝试判断是不是 HTML
- head = b""
+
+ def _convert_audio():
try:
- with open(temp_path, 'rb') as f:
- head = f.read(100)
- except:
- pass
-
- if b' list:
Returns:
单字符列表,每个包含 word/start/end
"""
- # 只保留中文字符和基本标点
- chars = [c for c in word if c.strip()]
- if not chars:
+ tokens = []
+ ascii_buffer = ""
+
+ for char in word:
+ if not char.strip():
+ continue
+
+ if char.isascii() and char.isalnum():
+ ascii_buffer += char
+ continue
+
+ if ascii_buffer:
+ tokens.append(ascii_buffer)
+ ascii_buffer = ""
+
+ tokens.append(char)
+
+ if ascii_buffer:
+ tokens.append(ascii_buffer)
+
+ if not tokens:
return []
- if len(chars) == 1:
- return [{"word": chars[0], "start": start, "end": end}]
+ if len(tokens) == 1:
+ return [{"word": tokens[0], "start": start, "end": end}]
# 线性插值时间戳
duration = end - start
- char_duration = duration / len(chars)
+ token_duration = duration / len(tokens)
result = []
- for i, char in enumerate(chars):
- char_start = start + i * char_duration
- char_end = start + (i + 1) * char_duration
+ for i, token in enumerate(tokens):
+ token_start = start + i * token_duration
+ token_end = start + (i + 1) * token_duration
result.append({
- "word": char,
- "start": round(char_start, 3),
- "end": round(char_end, 3)
+ "word": token,
+ "start": round(token_start, 3),
+ "end": round(token_end, 3)
})
return result
diff --git a/backend/requirements.txt b/backend/requirements.txt
index c89d4cc..42b2a7a 100644
--- a/backend/requirements.txt
+++ b/backend/requirements.txt
@@ -31,3 +31,7 @@ bcrypt==4.0.1
# 字幕对齐
faster-whisper>=1.0.0
+
+# 文案提取与AI生成
+yt-dlp>=2023.0.0
+zai-sdk>=0.2.0
diff --git a/backend/scripts/watchdog.py b/backend/scripts/watchdog.py
new file mode 100644
index 0000000..dd7177a
--- /dev/null
+++ b/backend/scripts/watchdog.py
@@ -0,0 +1,84 @@
+
+import asyncio
+import httpx
+import logging
+import subprocess
+import time
+from datetime import datetime
+
+# 配置日志
+logging.basicConfig(
+ level=logging.INFO,
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ handlers=[
+ logging.FileHandler("watchdog.log"),
+ logging.StreamHandler()
+ ]
+)
+logger = logging.getLogger("Watchdog")
+
+# 服务配置
+SERVICES = [
+ {
+ "name": "vigent2-qwen-tts",
+ "url": "http://localhost:8009/health",
+ "failures": 0,
+ "threshold": 3,
+ "timeout": 10.0,
+ "restart_cmd": ["pm2", "restart", "vigent2-qwen-tts"]
+ }
+]
+
+async def check_service(service):
+ """检查单个服务健康状态"""
+ try:
+ timeout = service.get("timeout", 10.0)
+ async with httpx.AsyncClient(timeout=timeout) as client:
+ response = await client.get(service["url"])
+ if response.status_code == 200:
+ # 成功
+ if service["failures"] > 0:
+ logger.info(f"✅ 服务 {service['name']} 已恢复正常")
+ service["failures"] = 0
+ return True
+ else:
+ logger.warning(f"⚠️ 服务 {service['name']} 返回状态码 {response.status_code}")
+ except Exception as e:
+ logger.warning(f"⚠️ 无法连接服务 {service['name']}: {str(e)}")
+
+ # 失败处理
+ service["failures"] += 1
+ logger.warning(f"❌ 服务 {service['name']} 连续失败 {service['failures']}/{service['threshold']} 次")
+
+ if service["failures"] >= service['threshold']:
+ logger.error(f"🚨 服务 {service['name']} 已达到失败阈值,正在重启...")
+ try:
+ subprocess.run(service["restart_cmd"], check=True)
+ logger.info(f"♻️ 服务 {service['name']} 重启命令已发送")
+ # 重启后给予一段宽限期 (例如 60秒) 不检查,等待服务启动
+ service["failures"] = 0 # 重置计数
+ return "restarting"
+ except Exception as restart_error:
+ logger.error(f"💥 重启服务 {service['name']} 失败: {restart_error}")
+
+ return False
+
+async def main():
+ logger.info("🛡️ ViGent2 服务看门狗 (Watchdog) 已启动")
+
+ while True:
+ # 并发检查所有服务
+ for service in SERVICES:
+ result = await check_service(service)
+ if result == "restarting":
+ # 如果有服务重启,额外等待包含启动时间
+ pass
+
+ # 每 30 秒检查一次
+ await asyncio.sleep(30)
+
+if __name__ == "__main__":
+ try:
+ asyncio.run(main())
+ except KeyboardInterrupt:
+ logger.info("🛑 看门狗已停止")
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index b4665b9..3a8de1d 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@supabase/supabase-js": "^2.93.1",
"axios": "^1.13.4",
+ "lucide-react": "^0.563.0",
"next": "16.1.1",
"react": "19.2.3",
"react-dom": "19.2.3",
@@ -5000,6 +5001,15 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lucide-react": {
+ "version": "0.563.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.563.0.tgz",
+ "integrity": "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 5243547..213ba71 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -11,6 +11,7 @@
"dependencies": {
"@supabase/supabase-js": "^2.93.1",
"axios": "^1.13.4",
+ "lucide-react": "^0.563.0",
"next": "16.1.1",
"react": "19.2.3",
"react-dom": "19.2.3",
diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx
index c314e58..2109dc3 100644
--- a/frontend/src/app/page.tsx
+++ b/frontend/src/app/page.tsx
@@ -10,9 +10,21 @@ import AccountSettingsDropdown from "@/components/AccountSettingsDropdown";
import VideoPreviewModal from "@/components/VideoPreviewModal";
import ScriptExtractionModal from "@/components/ScriptExtractionModal";
-const API_BASE = typeof window === 'undefined'
- ? 'http://localhost:8006'
- : '';
+const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8006';
+
+const isAbsoluteUrl = (url: string) => /^https?:\/\//i.test(url);
+
+const joinBaseUrl = (base: string, path: string) => {
+ if (!base) return path;
+ if (!path.startsWith('/')) return `${base}/${path}`;
+ return `${base}${path}`;
+};
+
+const resolveMediaUrl = (url?: string | null) => {
+ if (!url) return null;
+ if (isAbsoluteUrl(url)) return url;
+ return joinBaseUrl(API_BASE, url);
+};
// 类型定义
interface Material {
@@ -95,6 +107,64 @@ export default function Home() {
const [isUploadingRef, setIsUploadingRef] = useState(false);
const [uploadRefError, setUploadRefError] = useState(null);
+ // 音频预览与重命名状态
+ const [editingAudioId, setEditingAudioId] = useState(null);
+ const [editName, setEditName] = useState("");
+ const [playingAudioId, setPlayingAudioId] = useState(null);
+ const audioPlayerRef = useRef(null);
+
+ // 播放/暂停预览
+ const togglePlayPreview = (audio: RefAudio, e: React.MouseEvent) => {
+ e.stopPropagation();
+
+ if (playingAudioId === audio.id) {
+ // 停止
+ if (audioPlayerRef.current) {
+ audioPlayerRef.current.pause();
+ audioPlayerRef.current.currentTime = 0;
+ }
+ setPlayingAudioId(null);
+ } else {
+ // 播放新的
+ if (audioPlayerRef.current) {
+ audioPlayerRef.current.pause();
+ }
+ const player = new Audio(audio.path);
+ player.onended = () => setPlayingAudioId(null);
+ player.play().catch(e => alert("播放失败: " + e));
+ audioPlayerRef.current = player;
+ setPlayingAudioId(audio.id);
+ }
+ };
+
+ // 重命名参考音频
+ const startEditing = (audio: RefAudio, e: React.MouseEvent) => {
+ e.stopPropagation();
+ setEditingAudioId(audio.id);
+ // 去掉后缀名进行编辑 (体验更好)
+ const nameWithoutExt = audio.name.substring(0, audio.name.lastIndexOf('.'));
+ setEditName(nameWithoutExt || audio.name);
+ };
+
+ const cancelEditing = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ setEditingAudioId(null);
+ setEditName("");
+ };
+
+ const saveEditing = async (audioId: string, e: React.MouseEvent) => {
+ e.stopPropagation();
+ if (!editName.trim()) return;
+
+ try {
+ await api.put(`/api/ref-audios/${encodeURIComponent(audioId)}`, { new_name: editName });
+ setEditingAudioId(null);
+ fetchRefAudios(); // 刷新列表
+ } catch (err: any) {
+ alert("重命名失败: " + err);
+ }
+ };
+
// AI 生成标题标签
const [isGeneratingMeta, setIsGeneratingMeta] = useState(false);
@@ -139,14 +209,16 @@ export default function Home() {
// 监听任务完成,自动显示视频
useEffect(() => {
if (currentTask?.status === 'completed' && currentTask.download_url) {
- const API_BASE = typeof window === 'undefined'
- ? process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8006'
- : (process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8006');
-
- setGeneratedVideo(`${API_BASE}${currentTask.download_url}`);
+ const resolvedUrl = resolveMediaUrl(currentTask.download_url);
+ if (resolvedUrl) {
+ setGeneratedVideo(resolvedUrl);
+ }
+ if (currentTask.task_id) {
+ setSelectedVideoId(`${currentTask.task_id}_output`);
+ }
fetchGeneratedVideos(); // 刷新历史视频列表
}
- }, [currentTask?.status, currentTask?.download_url]);
+ }, [currentTask?.status, currentTask?.download_url, currentTask?.task_id]);
// 从 localStorage 恢复用户输入(等待认证完成后)
useEffect(() => {
@@ -245,12 +317,46 @@ export default function Home() {
const fetchRefAudios = async () => {
try {
const { data } = await api.get('/api/ref-audios');
- setRefAudios(data.items || []);
+ const items: RefAudio[] = data.items || [];
+ // 按时间倒序排序 (最新的在前面)
+ items.sort((a, b) => b.created_at - a.created_at);
+ setRefAudios(items);
} catch (error) {
console.error("获取参考音频失败:", error);
}
};
+ // 自动选择参考音频 (恢复上次选择 或 默认最新的)
+ useEffect(() => {
+ // 只有在数据加载完成且尚未选择时才执行
+ if (refAudios.length > 0 && !selectedRefAudio && isRestored) {
+ const savedId = localStorage.getItem(`vigent_${storageKey}_refAudioId`);
+ let targetAudio = null;
+
+ if (savedId) {
+ targetAudio = refAudios.find(a => a.id === savedId);
+ }
+
+ // 如果没找到保存的,或者没有保存,则默认选第一个(最新的)
+ if (!targetAudio) {
+ targetAudio = refAudios[0];
+ }
+
+ if (targetAudio) {
+ console.log("[Home] Auto selecting ref audio:", targetAudio.name);
+ setSelectedRefAudio(targetAudio);
+ setRefText(targetAudio.ref_text);
+ }
+ }
+ }, [refAudios, isRestored, storageKey]); // 移除 selectedRefAudio 避免循环,但在逻辑中检查 !selectedRefAudio
+
+ // 保存参考音频选择
+ useEffect(() => {
+ if (isRestored && selectedRefAudio) {
+ localStorage.setItem(`vigent_${storageKey}_refAudioId`, selectedRefAudio.id);
+ }
+ }, [selectedRefAudio, storageKey, isRestored]);
+
// 上传参考音频(使用固定参考文字)
const uploadRefAudio = async (file: File) => {
const refTextInput = FIXED_REF_TEXT;
@@ -337,7 +443,10 @@ export default function Home() {
const useRecording = async () => {
if (!recordedBlob) return;
- const file = new File([recordedBlob], 'recording.webm', { type: 'audio/webm' });
+ // 回归:使用固定文件名,依靠后端自动重命名 (recording(1).webm)
+ const filename = 'recording.webm';
+
+ const file = new File([recordedBlob], filename, { type: 'audio/webm' });
await uploadRefAudio(file);
setRecordedBlob(null);
setRecordingTime(0);
@@ -667,7 +776,7 @@ export default function Home() {
onClick={(e) => {
e.stopPropagation();
if (m.path) {
- setPreviewMaterial(`${API_BASE}${m.path}`);
+ setPreviewMaterial(resolveMediaUrl(m.path));
}
}}
className="absolute top-2 right-10 p-1 text-gray-500 hover:text-white opacity-0 group-hover:opacity-100 transition-opacity"
@@ -880,21 +989,63 @@ export default function Home() {
: "border-white/10 bg-white/5 hover:border-white/30"
}`}
onClick={() => {
- setSelectedRefAudio(audio);
- setRefText(audio.ref_text);
+ if (editingAudioId !== audio.id) {
+ setSelectedRefAudio(audio);
+ setRefText(audio.ref_text);
+ }
}}
>
- {audio.name}
- {audio.duration_sec.toFixed(1)}s
-
+ {/* 正常显示模式 vs 编辑模式 */}
+ {editingAudioId === audio.id ? (
+ e.stopPropagation()}>
+ setEditName(e.target.value)}
+ className="w-full bg-black/50 text-white text-xs px-1 py-0.5 rounded border border-purple-500 focus:outline-none"
+ autoFocus
+ onKeyDown={e => {
+ if (e.key === 'Enter') saveEditing(audio.id, e as any);
+ if (e.key === 'Escape') cancelEditing(e as any);
+ }}
+ />
+
+
+
+ ) : (
+ <>
+
+
{audio.name}
+
+
+
+
+
+
+ {audio.duration_sec.toFixed(1)}s
+ >
+ )}
))}
@@ -1088,7 +1239,7 @@ export default function Home() {