403 lines
9.6 KiB
Markdown
403 lines
9.6 KiB
Markdown
# Day 14 - 模型升级 + 标题标签生成 + 前端修复
|
||
|
||
**日期**:2026-01-30
|
||
|
||
---
|
||
|
||
## 🚀 Qwen3-TTS 模型升级 (0.6B → 1.7B)
|
||
|
||
### 背景
|
||
|
||
为提升声音克隆质量,将 Qwen3-TTS 模型从 0.6B-Base 升级到 1.7B-Base。
|
||
|
||
### 变更内容
|
||
|
||
| 项目 | 升级前 | 升级后 |
|
||
|------|--------|--------|
|
||
| 模型 | 0.6B-Base | **1.7B-Base** |
|
||
| 大小 | 2.4GB | 6.8GB |
|
||
| 质量 | 基础 | 更高质量 |
|
||
|
||
### 代码修改
|
||
|
||
**文件**: `models/Qwen3-TTS/qwen_tts_server.py`
|
||
|
||
```python
|
||
# 升级前
|
||
MODEL_PATH = Path(__file__).parent / "checkpoints" / "0.6B-Base"
|
||
|
||
# 升级后
|
||
MODEL_PATH = Path(__file__).parent / "checkpoints" / "1.7B-Base"
|
||
```
|
||
|
||
### 模型下载
|
||
|
||
```bash
|
||
cd /home/rongye/ProgramFiles/ViGent2/models/Qwen3-TTS
|
||
|
||
# 下载 1.7B-Base 模型 (6.8GB)
|
||
modelscope download --model Qwen/Qwen3-TTS-12Hz-1.7B-Base --local_dir ./checkpoints/1.7B-Base
|
||
```
|
||
|
||
### 结果
|
||
|
||
- ✅ 模型加载正常 (GPU0, bfloat16)
|
||
- ✅ 声音克隆质量提升
|
||
- ✅ 推理速度可接受
|
||
|
||
---
|
||
|
||
## 🎨 标题和字幕显示优化
|
||
|
||
### 字幕组件优化 (`Subtitles.tsx`)
|
||
|
||
**文件**: `remotion/src/components/Subtitles.tsx`
|
||
|
||
优化内容:
|
||
- 调整高亮颜色配置
|
||
- 优化文字描边效果(多层阴影)
|
||
- 调整字间距和行高
|
||
|
||
```typescript
|
||
export const Subtitles: React.FC<SubtitlesProps> = ({
|
||
captions,
|
||
highlightColor = '#FFFF00', // 高亮颜色
|
||
normalColor = '#FFFFFF', // 普通文字颜色
|
||
fontSize = 52,
|
||
}) => {
|
||
// 样式优化
|
||
const style = {
|
||
textShadow: `
|
||
2px 2px 4px rgba(0,0,0,0.8),
|
||
-2px -2px 4px rgba(0,0,0,0.8),
|
||
...
|
||
`,
|
||
letterSpacing: '2px',
|
||
lineHeight: 1.4,
|
||
maxWidth: '90%',
|
||
};
|
||
};
|
||
```
|
||
|
||
### 标题组件优化 (`Title.tsx`)
|
||
|
||
**文件**: `remotion/src/components/Title.tsx`
|
||
|
||
优化内容:
|
||
- 淡入淡出动画效果
|
||
- 下滑入场动画
|
||
- 可配置显示时长
|
||
|
||
```typescript
|
||
interface TitleProps {
|
||
title: string;
|
||
duration?: number; // 标题显示时长(秒,默认3秒)
|
||
fadeOutStart?: number; // 开始淡出的时间(秒,默认2秒)
|
||
}
|
||
|
||
// 动画效果
|
||
// 淡入:0-0.5 秒
|
||
// 淡出:2-3 秒
|
||
// 下滑:0-0.5 秒,-20px → 0px
|
||
```
|
||
|
||
### 结果
|
||
|
||
- ✅ 字幕显示更清晰
|
||
- ✅ 标题动画更流畅
|
||
|
||
---
|
||
|
||
## 🤖 标题标签自动生成功能
|
||
|
||
### 功能描述
|
||
|
||
使用 AI(智谱 GLM-4-Flash)根据口播文案自动生成视频标题和标签。
|
||
|
||
### 后端实现
|
||
|
||
#### 1. GLM 服务 (`glm_service.py`)
|
||
|
||
**文件**: `backend/app/services/glm_service.py`
|
||
|
||
```python
|
||
class GLMService:
|
||
"""智谱 GLM AI 服务"""
|
||
|
||
async def generate_meta(self, text: str) -> dict:
|
||
"""根据文案生成标题和标签"""
|
||
|
||
prompt = """根据以下口播文案,生成一个吸引人的短视频标题和3个相关标签。
|
||
|
||
要求:
|
||
1. 标题要简洁有力,能吸引观众点击,不超过10个字
|
||
2. 标签要与内容相关,便于搜索和推荐,只要3个
|
||
|
||
返回格式:{"title": "标题", "tags": ["标签1", "标签2", "标签3"]}
|
||
"""
|
||
# 调用 GLM-4-Flash API
|
||
response = await self._call_api(prompt + text)
|
||
return self._parse_json(response)
|
||
```
|
||
|
||
**JSON 解析容错**:
|
||
- 支持直接 JSON 解析
|
||
- 支持提取 JSON 块
|
||
- 支持 ```json 代码块提取
|
||
|
||
#### 2. API 端点 (`ai.py`)
|
||
|
||
**文件**: `backend/app/api/ai.py`
|
||
|
||
```python
|
||
from pydantic import BaseModel
|
||
|
||
class GenerateMetaRequest(BaseModel):
|
||
text: str # 口播文案
|
||
|
||
class GenerateMetaResponse(BaseModel):
|
||
title: str # 生成的标题
|
||
tags: list[str] # 生成的标签列表
|
||
|
||
@router.post("/generate-meta", response_model=GenerateMetaResponse)
|
||
async def generate_meta(request: GenerateMetaRequest):
|
||
"""AI 生成标题和标签"""
|
||
result = await glm_service.generate_meta(request.text)
|
||
return result
|
||
```
|
||
|
||
### 前端实现
|
||
|
||
**文件**: `frontend/src/app/page.tsx`
|
||
|
||
#### UI 按钮
|
||
|
||
```tsx
|
||
<button
|
||
onClick={handleGenerateMeta}
|
||
disabled={isGeneratingMeta || !text.trim()}
|
||
className="px-2 py-1 text-xs rounded transition-all whitespace-nowrap"
|
||
>
|
||
{isGeneratingMeta ? "⏳ 生成中..." : "🤖 AI生成标题标签"}
|
||
</button>
|
||
```
|
||
|
||
#### 处理逻辑
|
||
|
||
```typescript
|
||
const handleGenerateMeta = async () => {
|
||
if (!text.trim()) {
|
||
alert("请先输入口播文案");
|
||
return;
|
||
}
|
||
|
||
setIsGeneratingMeta(true);
|
||
try {
|
||
const { data } = await api.post('/api/ai/generate-meta', { text: text.trim() });
|
||
|
||
// 更新首页标题
|
||
setVideoTitle(data.title || "");
|
||
|
||
// 同步到发布页 localStorage
|
||
localStorage.setItem(`vigent_${storageKey}_publish_title`, data.title || "");
|
||
localStorage.setItem(`vigent_${storageKey}_publish_tags`, JSON.stringify(data.tags || []));
|
||
} catch (err: any) {
|
||
alert(`AI 生成失败: ${err.message}`);
|
||
} finally {
|
||
setIsGeneratingMeta(false);
|
||
}
|
||
};
|
||
```
|
||
|
||
### 发布页集成
|
||
|
||
**文件**: `frontend/src/app/publish/page.tsx`
|
||
|
||
从 localStorage 恢复 AI 生成的标题和标签:
|
||
|
||
```typescript
|
||
// 恢复标题和标签
|
||
const savedTitle = localStorage.getItem(`vigent_${storageKey}_publish_title`);
|
||
const savedTags = localStorage.getItem(`vigent_${storageKey}_publish_tags`);
|
||
|
||
if (savedTags) {
|
||
try {
|
||
const parsed = JSON.parse(savedTags);
|
||
if (Array.isArray(parsed)) {
|
||
setTags(parsed.join(', ')); // 数组转逗号分隔字符串
|
||
} else {
|
||
setTags(savedTags);
|
||
}
|
||
} catch {
|
||
setTags(savedTags);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 结果
|
||
|
||
- ✅ AI 生成标题和标签功能正常
|
||
- ✅ 数据自动同步到发布页
|
||
- ✅ 支持 JSON 数组和字符串格式兼容
|
||
|
||
---
|
||
|
||
## 🐛 前端文本保存问题修复
|
||
|
||
### 问题描述
|
||
|
||
**现象**:页面刷新后,用户输入的文案、标题等数据丢失
|
||
|
||
**原因**:
|
||
1. 认证状态恢复失败时,`userId` 为 `null`
|
||
2. 原代码判断 `!userId` 后用默认值覆盖 localStorage 数据
|
||
3. 导致已保存的用户数据被清空
|
||
|
||
### 解决方案
|
||
|
||
**文件**: `frontend/src/app/page.tsx`
|
||
|
||
#### 1. 添加恢复完成标志
|
||
|
||
```typescript
|
||
const [isRestored, setIsRestored] = useState(false);
|
||
```
|
||
|
||
#### 2. 等待认证完成后恢复数据
|
||
|
||
```typescript
|
||
useEffect(() => {
|
||
if (isAuthLoading) return; // 等待认证完成
|
||
|
||
// 使用 userId 或 'guest' 作为 key
|
||
const key = userId || 'guest';
|
||
|
||
// 从 localStorage 恢复数据
|
||
const savedText = localStorage.getItem(`vigent_${key}_text`);
|
||
if (savedText) setText(savedText);
|
||
|
||
// ... 恢复其他数据
|
||
|
||
setIsRestored(true); // 标记恢复完成
|
||
}, [userId, isAuthLoading]);
|
||
```
|
||
|
||
#### 3. 恢复完成后才保存
|
||
|
||
```typescript
|
||
useEffect(() => {
|
||
if (isRestored) {
|
||
localStorage.setItem(`vigent_${storageKey}_text`, text);
|
||
}
|
||
}, [text, storageKey, isRestored]);
|
||
```
|
||
|
||
### 用户隔离机制
|
||
|
||
```typescript
|
||
const storageKey = userId || 'guest';
|
||
```
|
||
|
||
| 用户状态 | storageKey | 说明 |
|
||
|----------|------------|------|
|
||
| 已登录 | `user_xxx` | 数据按用户隔离 |
|
||
| 未登录/认证失败 | `guest` | 使用统一 key |
|
||
|
||
### 数据恢复流程
|
||
|
||
```
|
||
1. 页面加载
|
||
↓
|
||
2. 检查 isAuthLoading
|
||
├─ true: 等待认证完成
|
||
└─ false: 继续
|
||
↓
|
||
3. 确定 storageKey (userId || 'guest')
|
||
↓
|
||
4. 从 localStorage 读取数据
|
||
├─ 有保存数据: 恢复到状态
|
||
└─ 无保存数据: 使用默认值
|
||
↓
|
||
5. 设置 isRestored = true
|
||
↓
|
||
6. 后续状态变化时保存到 localStorage
|
||
```
|
||
|
||
### 保存的数据项
|
||
|
||
| Key | 说明 |
|
||
|-----|------|
|
||
| `vigent_${key}_text` | 口播文案 |
|
||
| `vigent_${key}_title` | 视频标题 |
|
||
| `vigent_${key}_subtitles` | 字幕开关 |
|
||
| `vigent_${key}_ttsMode` | TTS 模式 |
|
||
| `vigent_${key}_voice` | 选择的音色 |
|
||
| `vigent_${key}_material` | 选择的素材 |
|
||
| `vigent_${key}_publish_title` | 发布标题 |
|
||
| `vigent_${key}_publish_tags` | 发布标签 |
|
||
|
||
### 结果
|
||
|
||
- ✅ 页面刷新后数据正常恢复
|
||
- ✅ 认证失败时不会覆盖已保存数据
|
||
- ✅ 多用户数据隔离正常
|
||
|
||
---
|
||
|
||
## 🐛 登录页刷新循环修复
|
||
|
||
### 问题描述
|
||
|
||
**现象**:登录页未登录时不断刷新,无法停留在表单页面。
|
||
|
||
**原因**:
|
||
1. `AuthProvider` 初始化时调用 `/api/auth/me`
|
||
2. 未登录返回 401
|
||
3. `axios` 全局拦截器遇到 401/403 重定向 `/login`
|
||
4. 登录页本身也在 Provider 中,导致循环刷新
|
||
|
||
### 解决方案
|
||
|
||
**文件**: `frontend/src/shared/api/axios.ts`
|
||
|
||
在拦截器中对公开路由跳过重定向,仅在受保护页面触发登录跳转:
|
||
|
||
```typescript
|
||
const PUBLIC_PATHS = new Set(['/login', '/register']);
|
||
const isPublicPath = typeof window !== 'undefined' && PUBLIC_PATHS.has(window.location.pathname);
|
||
|
||
if ((status === 401 || status === 403) && !isRedirecting && !isPublicPath) {
|
||
// ... 保持原有重定向逻辑
|
||
}
|
||
```
|
||
|
||
### 结果
|
||
|
||
- ✅ 登录页不再刷新,表单可正常输入
|
||
- ✅ 受保护页面仍会在 401/403 时跳转登录页
|
||
|
||
---
|
||
|
||
## 📁 今日修改文件清单
|
||
|
||
| 文件 | 变更类型 | 说明 |
|
||
|------|----------|------|
|
||
| `models/Qwen3-TTS/qwen_tts_server.py` | 修改 | 模型路径升级到 1.7B-Base |
|
||
| `Docs/QWEN3_TTS_DEPLOY.md` | 修改 | 更新部署文档为 1.7B 版本 |
|
||
| `remotion/src/components/Subtitles.tsx` | 修改 | 优化字幕显示效果 |
|
||
| `remotion/src/components/Title.tsx` | 修改 | 优化标题动画效果 |
|
||
| `backend/app/services/glm_service.py` | 新增 | GLM AI 服务 |
|
||
| `backend/app/api/ai.py` | 新增 | AI 生成标题标签 API |
|
||
| `backend/app/main.py` | 修改 | 注册 ai 路由 |
|
||
| `frontend/src/app/page.tsx` | 修改 | AI 生成按钮 + localStorage 修复 |
|
||
| `frontend/src/app/publish/page.tsx` | 修改 | 恢复 AI 生成的标签 |
|
||
| `frontend/src/shared/api/axios.ts` | 修改 | 公开路由跳过 401/403 登录重定向 |
|
||
|
||
---
|
||
|
||
## 🔗 相关文档
|
||
|
||
- [task_complete.md](../task_complete.md) - 任务总览
|
||
- [Day13.md](./Day13.md) - 声音克隆功能集成 + 字幕功能
|
||
- [QWEN3_TTS_DEPLOY.md](../QWEN3_TTS_DEPLOY.md) - Qwen3-TTS 1.7B 部署指南
|