Files
xiaoxia-saas/docs/voice-library-refactor-plan.md
T
xiaoxia 40776cd4fa
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m36s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Validate - Code Quality (push) Failing after 4m40s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m22s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m21s
CI/CD Pipeline / Unit Tests (push) Failing after 6m50s
CI/CD Pipeline / Integration Tests (push) Successful in 2m30s
CI/CD Pipeline / Frontend Lint (push) Successful in 42s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m43s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 13m59s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m13s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m22s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 32s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 6m9s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 6m45s
refactor(voices): Phase 1 - extract types, constants and utils (#873)
2026-07-25 12:25:14 +08:00

199 lines
7.5 KiB
Markdown
Executable File
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.
# VoiceLibrary 页面重构方案
## 现状分析
**当前文件:** `apps/web/src/pages/voices/VoiceLibrary.tsx` — 1783 行
**代码质量:**
- `any`: 0 处
- `ts-ignore`: 0 处
- `eslint-disable`: 0 处
- `TODO`: 1 处
- 整体质量良好,重构阻力小
## 目录结构(重构后)
```
apps/web/src/pages/voices/
├── VoiceLibrary.tsx # 主组件(目标:~550 行,-69%)
├── types.ts # 类型定义
├── constants.ts # 常量 + 配置
├── utils/
│ ├── format.ts # 格式化工具函数
│ └── audio.ts # 音频工具函数
├── components/
│ ├── VoiceCard.tsx # 预设音色卡片
│ ├── CloneVoiceCard.tsx # 克隆音色卡片
│ ├── CloneDetailModal.tsx # 克隆详情弹窗
│ ├── CloneCardSkeleton.tsx # 克隆卡片骨架屏
│ ├── UploadModal.tsx # 上传音色弹窗
│ ├── TTSModal.tsx # TTS合成弹窗
│ ├── VoiceFilterBar.tsx # 筛选栏(搜索/性别/语言)
│ └── VoiceTabBar.tsx # Tab切换栏
└── hooks/
├── useVoices.ts # 音色数据查询 + 筛选
├── useAudioPlayer.ts # 音频播放控制
├── useVoiceUpload.ts # 音色上传逻辑
├── useTTS.ts # TTS合成逻辑
└── useCloneVoice.ts # 克隆音色操作
```
## 三阶段渐进式重构
### Phase 1:抽离类型、常量、工具函数
**目标:** 主文件 1783 → ~1550 行(-13%
**抽出内容:**
1. **`types.ts`** — 类型定义(~60行)
- `PresetVoiceDisplay` / `ClonedVoiceDisplay` / `VoiceCardProps` / `CloneVoiceCardProps`
- `TabKey` / `Toast` / `VoiceUploadMetadata`
- 现有 `mapPresetToDisplay` / `mapCloneToDisplay` 数据映射函数
2. **`constants.ts`** — 常量配置(~40行)
- `CLONE_STATUS_CONFIG` 克隆状态配置
- `GENDER_LABEL_MAP` / `LANGUAGE_LABEL_MAP` 性别/语言标签映射
- Tab 配置项
3. **`utils/format.ts`** — 格式化工具(~25行)
- `formatTime` 时长格式化
- `formatFileSize` 文件大小格式化
- `genderLabel` / `languageLabel` / `genderClass`
4. **`utils/audio.ts`** — 音频工具(~15行)
- `getAudioDuration` 获取音频文件时长
5. **`format.test.ts`** — 工具函数单测
- 覆盖 formatTime / formatFileSize 等纯函数
**Phase 1 交付:**
- 新增文件:6 个(types.ts / constants.ts / utils/format.ts / utils/audio.ts / format.test.ts
- 主文件减少:~230 行
- 纯机械抽离,无逻辑改动
---
### Phase 2:抽离子组件
**目标:** 主文件 1550 → ~950 行(-39%
**抽出组件:**
1. **`components/VoiceCard.tsx`** — 预设音色卡片(~120行)
- 卡片渲染:头像、名称、性别标签、播放按钮、进度条、收藏
- Propsvoice / playingId / currentTime / onPlay / onPause / onSeek / onToggleStar
2. **`components/CloneVoiceCard.tsx`** — 克隆音色卡片(~160行)
- 三种状态:processing / success / failed
- 操作:播放、详情、删除、重试、使用
- Propsvoice / playingId / currentTime / onPlayPause / onShowDetail / onDelete / onRetry / onUse
3. **`components/CloneDetailModal.tsx`** — 克隆详情弹窗(~90行)
- 展示克隆音色详细信息
- 状态展示、音频播放、操作按钮
4. **`components/CloneCardSkeleton.tsx`** — 克隆卡片骨架屏(~20行)
- 加载状态占位
5. **`components/UploadModal.tsx`** — 上传音色弹窗(~180行)
- 文件选择、名称/性别/描述填写
- 上传进度展示
- Propsopen / onClose / onUpload
6. **`components/TTSModal.tsx`** — TTS合成弹窗(~170行)
- 文本输入、音色选择、语速调节
- 合成状态轮询(idle/synthesizing/done/error
- 保存到素材库
- Propsopen / onClose / onSave
7. **`components/VoiceFilterBar.tsx`** — 筛选栏(~80行)
- 搜索框、性别筛选、语言筛选
- PropssearchText / filterGender / filterLang / onChange handlers
**Phase 2 交付:**
- 新增组件:7 个
- 主文件减少:~600 行
- 纯UI抽离,业务逻辑保留在主组件
---
### Phase 3:抽离业务逻辑 Hook
**目标:** 主文件 950 → ~550 行(-42%
**抽出 Hook**
1. **`hooks/useVoices.ts`** — 音色数据 Hook~200行)
- 三个 useQuerypresetVoices / cloneVoices / materialVoices / unifiedStats
- 筛选逻辑:searchText / filterGender / filterLang → filteredPreset / filteredClone
- 统计数据:presetCount / cloneCount / materialCount
- 收藏切换 handleToggleStar
- 返回:data / loading / filtered / counts / handlers
2. **`hooks/useAudioPlayer.ts`** — 音频播放控制 Hook~120行)
- playingId / currentTime / intervalRef 状态
- handlePlay / handlePause / handleSeek
- 自动停止(切换新音频时停止旧的)
- 组件卸载清理
- 注意:与 VoiceMaterialLibrary 的 useAudioPlayer 类似但有差异(一个操作DOM音频,一个操作audio元素),评估是否复用还是独立
3. **`hooks/useVoiceUpload.ts`** — 音色上传 Hook~120行)
- uploadFile / uploadName / uploadGender / uploadDesc / uploadProgress 状态
- uploadMutation(上传文件 + 创建音色)
- buildVoiceMetadata 元数据构建
- getAudioDuration 时长获取
- 成功后刷新列表 + toast 关闭弹窗
4. **`hooks/useTTS.ts`** — TTS合成 Hook~110行)
- ttsOpen / ttsText / ttsVoiceId / ttsSpeed 弹窗状态
- ttsJobId / ttsStatus / ttsAudioUrl / ttsError 合成状态
- handleTtsSynthesize 发起合成 + 轮询
- handleTtsSave 保存到素材库
- handleTtsClose 清理状态
5. **`hooks/useCloneVoice.ts`** — 克隆音色操作 Hook(~80行)
- deleteMutation / retryMutation
- handleCloneDelete / handleCloneRetry
- detailVoice 详情状态
- 操作后刷新列表
**Phase 3 交付:**
- 新增 Hook5 个
- 主文件减少:~400 行
- 主文件只剩:Tab切换逻辑 + JSX 组装 + 顶层状态编排
---
## 最终效果汇总
| 阶段 | 主文件行数 | 减少行数 | 减少比例 | 新增文件 |
|------|-----------|---------|----------|----------|
| 初始 | 1783 | — | — | — |
| Phase 1 | ~1550 | -233 | -13.1% | 5 |
| Phase 2 | ~950 | -600 | -38.7% | 7 |
| Phase 3 | ~550 | -400 | -42.1% | 5 |
| **总计** | **~550** | **-1233** | **-69.2%** | **17** |
## 与 GeneratePage / VoiceMaterialLibrary 的异同
**相同点:**
- 三阶段渐进式重构(类型常量 → 组件 → Hook)
- 每阶段独立PR,独立验证
- 每阶段加 smoke test 保证 vitest related 模式可用
- 代码质量基线高(0 any / 0 ts-ignore
**不同点:**
- VoiceLibrary 有两个数据源(预设音色 + 克隆音色),还有TTS和上传功能
- 音频播放逻辑与 VoiceMaterialLibrary 类似但操作的音频类型不同
- 有 CloneModal 是外部组件(已在 @/components/voice/CloneModal),不需要重写
- 骨架屏组件比较简单
## 风险与注意事项
1. **vitest related 模式**:每阶段新增文件需建立测试覆盖,避免 "No test files found"
2. **Preview Deploy**:环境问题导致失败,不影响代码质量
3. **音频播放逻辑**VoiceLibrary 和 VoiceMaterialLibrary 的播放控制类似但是两套独立实现,后续可考虑抽象为共享 Hook
4. **克隆状态**CLONE_STATUS_CONFIG 是核心常量,抽离时注意类型完整
5. **上传逻辑**uploadMutation 内部逻辑较复杂(上传文件 + 获取时长 + 创建音色),抽 Hook 时保持逻辑不变