From 42db72a8a4fcae7867b7554f6980e5934a42b984 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 27 Jul 2026 00:32:48 +0800 Subject: [PATCH 1/6] =?UTF-8?q?refactor(voice-library):=20=E6=B7=B1?= =?UTF-8?q?=E5=8C=96=20VoiceLibrary=20=E6=8B=86=E5=88=86=EF=BC=8C=E6=8A=BD?= =?UTF-8?q?=E7=A6=BB=20Tab=20=E5=86=85=E5=AE=B9/=E5=BC=B9=E7=AA=97/Toast?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 抽 VoiceTabBar 组件: Tab 切换按钮栏 - 抽 PresetVoiceTab 组件: 预置音色列表 + 筛选 + 空状态 - 抽 ClonedVoiceTab 组件: 克隆音色列表 + 加载 + 空状态 + 处理中提示 - 抽 MaterialVoiceTab 组件: 配音素材列表 + 骨架屏 + 空状态 - 抽 VoiceModals 组件: 4 个弹窗集合 - 抽 VoiceToasts 组件: Toast 提示 - 主文件 480→287 行 (-40%),仅保留 Hook 组装与整体布局 --- apps/web/src/pages/voices/VoiceLibrary.tsx | 369 +++++------------- .../voices/components/ClonedVoiceTab.tsx | 94 +++++ .../voices/components/MaterialVoiceTab.tsx | 87 +++++ .../voices/components/PresetVoiceTab.tsx | 104 +++++ .../pages/voices/components/VoiceModals.tsx | 148 +++++++ .../pages/voices/components/VoiceTabBar.tsx | 54 +++ .../pages/voices/components/VoiceToasts.tsx | 29 ++ 7 files changed, 604 insertions(+), 281 deletions(-) mode change 100644 => 100755 apps/web/src/pages/voices/VoiceLibrary.tsx create mode 100755 apps/web/src/pages/voices/components/ClonedVoiceTab.tsx create mode 100644 apps/web/src/pages/voices/components/MaterialVoiceTab.tsx create mode 100644 apps/web/src/pages/voices/components/PresetVoiceTab.tsx create mode 100644 apps/web/src/pages/voices/components/VoiceModals.tsx create mode 100644 apps/web/src/pages/voices/components/VoiceTabBar.tsx create mode 100644 apps/web/src/pages/voices/components/VoiceToasts.tsx diff --git a/apps/web/src/pages/voices/VoiceLibrary.tsx b/apps/web/src/pages/voices/VoiceLibrary.tsx old mode 100644 new mode 100755 index 1fc379c5f..5f354e33e --- a/apps/web/src/pages/voices/VoiceLibrary.tsx +++ b/apps/web/src/pages/voices/VoiceLibrary.tsx @@ -1,47 +1,39 @@ /** * 配音库页面 — V21 设计系统 * - * Phase 3: 逻辑抽离为 Hooks - * - useVoicesData: 三 Tab 数据查询 + 筛选 - * - useAudioPlayer: 播放控制 - * - useCloneOperations: 克隆音色删除/重试/详情 - * - useTtsSynthesize: AI 配音合成 - * - useVoiceUpload: 上传音频 + * 主组件仅保留 Hook 组装与整体布局 + * 数据查询 → hooks/useVoicesData + * 播放控制 → hooks/useAudioPlayer + * 克隆操作 → hooks/useCloneOperations + * TTS 合成 → hooks/useTtsSynthesize + * 上传音频 → hooks/useVoiceUpload + * Tab 切换 → components/VoiceTabBar + * 预置音色 → components/PresetVoiceTab + * 克隆音色 → components/ClonedVoiceTab + * 配音素材 → components/MaterialVoiceTab + * 弹窗集合 → components/VoiceModals + * Toast 提示 → components/VoiceToasts */ import React, { useCallback, useState } from "react" -import { - SoundOutlined, - PlusOutlined, - RobotOutlined, - UploadOutlined, - AudioOutlined, - UserOutlined, -} from "@ant-design/icons" +import { UploadOutlined, AudioOutlined, RobotOutlined } from "@ant-design/icons" import { Button } from "@/components/ui" import PageHead from "@/components/layout/PageHead" import { type AssetItem } from "@/api/assets" -import { genderLabel, languageLabel } from "@/pages/voices/utils/format" -import CloneModal from "@/components/voice/CloneModal" -import VoiceCard from "@/pages/voices/components/VoiceCard" -import CloneVoiceCard from "@/pages/voices/components/CloneVoiceCard" -import CloneDetailModal from "@/pages/voices/components/CloneDetailModal" -import CloneCardSkeleton from "@/pages/voices/components/CloneCardSkeleton" -import UploadVoiceModal from "@/pages/voices/components/UploadVoiceModal" -import TtsModal from "@/pages/voices/components/TtsModal" -import VoiceFilterBar from "@/pages/voices/components/VoiceFilterBar" -import { useVoicesData } from "@/pages/voices/hooks/useVoicesData" -import { useAudioPlayer } from "@/pages/voices/hooks/useAudioPlayer" -import { useCloneOperations } from "@/pages/voices/hooks/useCloneOperations" -import { useTtsSynthesize } from "@/pages/voices/hooks/useTtsSynthesize" -import { useVoiceUpload } from "@/pages/voices/hooks/useVoiceUpload" +import VoiceTabBar from "./components/VoiceTabBar" +import type { VoiceTabKey } from "./components/VoiceTabBar" +import PresetVoiceTab from "./components/PresetVoiceTab" +import ClonedVoiceTab from "./components/ClonedVoiceTab" +import MaterialVoiceTab from "./components/MaterialVoiceTab" +import VoiceModals from "./components/VoiceModals" +import VoiceToasts from "./components/VoiceToasts" +import type { Toast } from "./components/VoiceToasts" +import { useVoicesData } from "./hooks/useVoicesData" +import { useAudioPlayer } from "./hooks/useAudioPlayer" +import { useCloneOperations } from "./hooks/useCloneOperations" +import { useTtsSynthesize } from "./hooks/useTtsSynthesize" +import { useVoiceUpload } from "./hooks/useVoiceUpload" import "./voices.css" -interface Toast { - id: number - message: string - type: "success" | "error" -} - let toastIdSeq = 0 const VoiceLibrary: React.FC = () => { @@ -138,17 +130,9 @@ const VoiceLibrary: React.FC = () => { handleUploadClose, } = useVoiceUpload({ showToast }) - // ── 克隆音色播放切换 ────────────────────────────────── - const handleClonePlayPause = useCallback( - (voiceId: string, duration: number) => { - handleTogglePlay(voiceId, duration) - }, - [handleTogglePlay], - ) - // ── 切换 Tab 时停止播放 ─────────────────────────────── const handleTabChange = useCallback( - (tab: typeof activeTab) => { + (tab: VoiceTabKey) => { setActiveTab(tab) stopPlayback() }, @@ -200,254 +184,85 @@ const VoiceLibrary: React.FC = () => { actions={pageActions} /> - {/* ── Tabs ──────────────────────────────────────── */} -
- - - -
+ {/* ── Tab 切换栏 ────────────────────────────────── */} + {/* ── 预置音色 ──────────────────────────────────── */} {activeTab === "preset" && ( -
- - - {presetLoading && ( -
-
- -
-

加载预置音色中...

-
- )} - - {!presetLoading && filteredPreset.length > 0 && ( -
- {filteredPreset.map((voice) => ( - handlePlay(voice.id, voice.duration)} - onPause={handlePause} - onSeek={(time) => handleSeek(voice.id, time, voice.duration)} - onToggleStar={() => {}} - /> - ))} -
- )} - - {!presetLoading && filteredPreset.length === 0 && ( -
-
- -
-

未找到匹配的音色

- -
- )} -
+ )} {/* ── 我的克隆 ──────────────────────────────────── */} {activeTab === "cloned" && ( -
- {/* 骨架屏加载 */} - {cloneLoading && ( -
- {Array.from({ length: 6 }).map((_, i) => ( - - ))} -
- )} - - {/* 卡片列表 */} - {!cloneLoading && clonedVoices.length > 0 && ( -
- {clonedVoices.map((voice) => ( - handleClonePlayPause(voice.id, voice.duration)} - onPause={handlePause} - onUse={() => handleCloneUse(voice)} - onDelete={() => handleCloneDelete(voice)} - onRetry={() => handleCloneRetry(voice)} - onShowDetail={() => handleShowDetail(voice)} - /> - ))} -
- )} - - {/* 空状态 */} - {!cloneLoading && clonedVoices.length === 0 && ( -
-
- -
-

暂无克隆音色

-

上传音频素材即可克隆专属音色

- -
- )} - - {/* 处理中提示 */} - {!cloneLoading && clonedVoices.some((v) => v.status === "processing") && ( -
- - 部分音色正在克隆处理中,完成后将自动出现在列表中。 -
- )} -
+ setCloneModalOpen(true)} + /> )} {/* ── 配音素材 ──────────────────────────────────── */} {activeTab === "material" && ( -
- {/* 骨架屏加载 */} - {materialLoading && ( -
- {Array.from({ length: 6 }).map((_, i) => ( -
-
-
-
-
-
-
- ))} -
- )} - - {/* 卡片列表 */} - {!materialLoading && materials.length > 0 && ( -
- {materials.map((asset: AssetItem) => { - const duration = (asset.metadata?.duration as number) || 0 - const minutes = Math.floor(duration / 60) - const seconds = Math.floor(duration % 60) - return ( -
-
- - - {minutes}:{seconds.toString().padStart(2, "0")} - -
-
-
- {asset.name} -
-
- - {asset.file_size - ? `${(asset.file_size / 1024 / 1024).toFixed(1)} MB` - : "--"} - -
-
-
- ) - })} -
- )} - - {/* 空状态 */} - {!materialLoading && materials.length === 0 && ( -
-
- -
-

暂无配音素材

-

上传您的音频素材,用于视频配音

- -
- )} -
- )} - - {/* ── 克隆音色弹窗 ──────────────────────────────── */} - setCloneModalOpen(false)} - onSuccess={handleCloneSuccess} - /> - - {/* ── 详情弹窗 ──────────────────────────────────── */} - {detailVoice && ( - handleCloneDelete(detailVoice)} - onRetry={() => handleCloneRetry(detailVoice)} - onUse={() => handleCloneUse(detailVoice)} + setUploadOpen(true)} /> )} - {/* ── 上传音频弹窗 ──────────────────────────────── */} - setCloneModalOpen(false)} + onCloneSuccess={handleCloneSuccess} + detailVoice={detailVoice} + onDetailClose={handleCloseDetail} + onDetailDelete={() => detailVoice && handleCloneDelete(detailVoice)} + onDetailRetry={() => detailVoice && handleCloneRetry(detailVoice)} + onDetailUse={() => detailVoice && handleCloneUse(detailVoice)} + uploadOpen={uploadOpen} uploadFile={uploadFile} uploadName={uploadName} uploadGender={uploadGender} uploadDesc={uploadDesc} uploadProgress={uploadProgress} - onClose={handleUploadClose} + onUploadClose={handleUploadClose} onFileSelect={handleFileSelect} onFileRemove={handleFileRemove} onNameChange={setUploadName} onGenderChange={setUploadGender} onDescChange={setUploadDesc} onUpload={handleUpload} - /> - - {/* ── AI 配音弹窗 ───────────────────────────────── */} - { ttsAudioUrl={ttsAudioUrl} ttsError={ttsError} presetVoices={presetVoices} - onClose={handleTtsClose} - onTextChange={setTtsText} - onVoiceChange={setTtsVoiceId} - onSpeedChange={setTtsSpeed} - onSynthesize={handleTtsSynthesize} - onSave={handleTtsSave} + onTtsClose={handleTtsClose} + onTtsTextChange={setTtsText} + onTtsVoiceChange={setTtsVoiceId} + onTtsSpeedChange={setTtsSpeed} + onTtsSynthesize={handleTtsSynthesize} + onTtsSave={handleTtsSave} /> {/* ── Toast 提示 ────────────────────────────────── */} - {toasts.length > 0 && ( -
- {toasts.map((t) => ( -
- {t.type === "success" ? "✅" : "❌"} {t.message} -
- ))} -
- )} +
) } diff --git a/apps/web/src/pages/voices/components/ClonedVoiceTab.tsx b/apps/web/src/pages/voices/components/ClonedVoiceTab.tsx new file mode 100755 index 000000000..075eb6fdc --- /dev/null +++ b/apps/web/src/pages/voices/components/ClonedVoiceTab.tsx @@ -0,0 +1,94 @@ +/** + * VoiceLibrary 我的克隆 Tab 内容 + */ +import React from "react" +import { UserOutlined, PlusOutlined, RobotOutlined } from "@ant-design/icons" +import { Button } from "@/components/ui" +import CloneVoiceCard from "./CloneVoiceCard" +import CloneCardSkeleton from "./CloneCardSkeleton" +import type { VoiceClone } from "@/api/voice-clone" + +export interface ClonedVoiceTabProps { + loading: boolean + voices: VoiceClone[] + playingId: string | null + currentTime: number + onPlay: (voiceId: string, duration: number) => void + onPause: () => void + onUse: (voice: VoiceClone) => void + onDelete: (voice: VoiceClone) => void + onRetry: (voice: VoiceClone) => void + onShowDetail: (voice: VoiceClone) => void + onOpenClone: () => void +} + +export const ClonedVoiceTab: React.FC = ({ + loading, + voices, + playingId, + currentTime, + onPlay, + onPause, + onUse, + onDelete, + onRetry, + onShowDetail, + onOpenClone, +}) => { + return ( +
+ {/* 骨架屏加载 */} + {loading && ( +
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+ )} + + {/* 卡片列表 */} + {!loading && voices.length > 0 && ( +
+ {voices.map((voice) => ( + onPlay(voice.id, voice.duration)} + onPause={onPause} + onUse={() => onUse(voice)} + onDelete={() => onDelete(voice)} + onRetry={() => onRetry(voice)} + onShowDetail={() => onShowDetail(voice)} + /> + ))} +
+ )} + + {/* 空状态 */} + {!loading && voices.length === 0 && ( +
+
+ +
+

暂无克隆音色

+

上传音频素材即可克隆专属音色

+ +
+ )} + + {/* 处理中提示 */} + {!loading && voices.some((v) => v.status === "processing") && ( +
+ + 部分音色正在克隆处理中,完成后将自动出现在列表中。 +
+ )} +
+ ) +} + +export default ClonedVoiceTab diff --git a/apps/web/src/pages/voices/components/MaterialVoiceTab.tsx b/apps/web/src/pages/voices/components/MaterialVoiceTab.tsx new file mode 100644 index 000000000..6f7de554d --- /dev/null +++ b/apps/web/src/pages/voices/components/MaterialVoiceTab.tsx @@ -0,0 +1,87 @@ +/** + * VoiceLibrary 配音素材 Tab 内容 + */ +import React from "react" +import { SoundOutlined, UploadOutlined, AudioOutlined } from "@ant-design/icons" +import { Button } from "@/components/ui" +import type { AssetItem } from "@/api/assets" + +export interface MaterialVoiceTabProps { + loading: boolean + materials: AssetItem[] + onOpenUpload: () => void +} + +export const MaterialVoiceTab: React.FC = ({ + loading, + materials, + onOpenUpload, +}) => { + return ( +
+ {/* 骨架屏加载 */} + {loading && ( +
+ {Array.from({ length: 6 }).map((_, i) => ( +
+
+
+
+
+
+
+ ))} +
+ )} + + {/* 卡片列表 */} + {!loading && materials.length > 0 && ( +
+ {materials.map((asset: AssetItem) => { + const duration = (asset.metadata?.duration as number) || 0 + const minutes = Math.floor(duration / 60) + const seconds = Math.floor(duration % 60) + return ( +
+
+ + + {minutes}:{seconds.toString().padStart(2, "0")} + +
+
+
+ {asset.name} +
+
+ + {asset.file_size + ? `${(asset.file_size / 1024 / 1024).toFixed(1)} MB` + : "--"} + +
+
+
+ ) + })} +
+ )} + + {/* 空状态 */} + {!loading && materials.length === 0 && ( +
+
+ +
+

暂无配音素材

+

上传您的音频素材,用于视频配音

+ +
+ )} +
+ ) +} + +export default MaterialVoiceTab diff --git a/apps/web/src/pages/voices/components/PresetVoiceTab.tsx b/apps/web/src/pages/voices/components/PresetVoiceTab.tsx new file mode 100644 index 000000000..562a02529 --- /dev/null +++ b/apps/web/src/pages/voices/components/PresetVoiceTab.tsx @@ -0,0 +1,104 @@ +/** + * VoiceLibrary 预置音色 Tab 内容 + */ +import React from "react" +import { SoundOutlined } from "@ant-design/icons" +import { Button } from "@/components/ui" +import VoiceFilterBar from "./VoiceFilterBar" +import VoiceCard from "./VoiceCard" +import { genderLabel, languageLabel } from "../utils/format" +import type { PresetVoiceItem } from "@/api/voices" + +export interface PresetVoiceTabProps { + searchText: string + filterGender: string + filterLang: string + onSearchChange: (text: string) => void + onGenderChange: (gender: string) => void + onLangChange: (lang: string) => void + loading: boolean + voices: PresetVoiceItem[] + playingId: string | null + currentTime: number + onPlay: (id: string, duration: number) => void + onPause: () => void + onSeek: (id: string, time: number, duration: number) => void + onClearFilters: () => void +} + +export const PresetVoiceTab: React.FC = ({ + searchText, + filterGender, + filterLang, + onSearchChange, + onGenderChange, + onLangChange, + loading, + voices, + playingId, + currentTime, + onPlay, + onPause, + onSeek, + onClearFilters, +}) => { + return ( +
+ + + {loading && ( +
+
+ +
+

加载预置音色中...

+
+ )} + + {!loading && voices.length > 0 && ( +
+ {voices.map((voice) => ( + onPlay(voice.id, voice.duration)} + onPause={onPause} + onSeek={(time) => onSeek(voice.id, time, voice.duration)} + onToggleStar={() => {}} + /> + ))} +
+ )} + + {!loading && voices.length === 0 && ( +
+
+ +
+

未找到匹配的音色

+ +
+ )} +
+ ) +} + +export default PresetVoiceTab diff --git a/apps/web/src/pages/voices/components/VoiceModals.tsx b/apps/web/src/pages/voices/components/VoiceModals.tsx new file mode 100644 index 000000000..84b33ed71 --- /dev/null +++ b/apps/web/src/pages/voices/components/VoiceModals.tsx @@ -0,0 +1,148 @@ +/** + * VoiceLibrary 弹窗集合 + */ +import React from "react" +import type { VoiceClone } from "@/api/voice-clone" +import type { PresetVoiceItem } from "@/api/voices" +import CloneModal from "@/components/voice/CloneModal" +import CloneDetailModal from "./CloneDetailModal" +import UploadVoiceModal from "./UploadVoiceModal" +import TtsModal from "./TtsModal" + +export interface VoiceModalsProps { + /* 克隆音色弹窗 */ + cloneModalOpen: boolean + onCloneClose: () => void + onCloneSuccess: (voice: VoiceClone) => void + + /* 克隆详情弹窗 */ + detailVoice: VoiceClone | null + onDetailClose: () => void + onDetailDelete: () => void + onDetailRetry: () => void + onDetailUse: () => void + + /* 上传音频弹窗 */ + uploadOpen: boolean + uploadFile: File | null + uploadName: string + uploadGender: string + uploadDesc: string + uploadProgress: number + onUploadClose: () => void + onFileSelect: (file: File) => void + onFileRemove: () => void + onNameChange: (name: string) => void + onGenderChange: (gender: string) => void + onDescChange: (desc: string) => void + onUpload: () => void + + /* TTS 弹窗 */ + ttsOpen: boolean + ttsText: string + ttsVoiceId: string + ttsSpeed: number + ttsStatus: string + ttsAudioUrl: string + ttsError: string | null + presetVoices: PresetVoiceItem[] + onTtsClose: () => void + onTtsTextChange: (text: string) => void + onTtsVoiceChange: (id: string) => void + onTtsSpeedChange: (speed: number) => void + onTtsSynthesize: () => void + onTtsSave: () => void +} + +export const VoiceModals: React.FC = ({ + cloneModalOpen, + onCloneClose, + onCloneSuccess, + detailVoice, + onDetailClose, + onDetailDelete, + onDetailRetry, + onDetailUse, + uploadOpen, + uploadFile, + uploadName, + uploadGender, + uploadDesc, + uploadProgress, + onUploadClose, + onFileSelect, + onFileRemove, + onNameChange, + onGenderChange, + onDescChange, + onUpload, + ttsOpen, + ttsText, + ttsVoiceId, + ttsSpeed, + ttsStatus, + ttsAudioUrl, + ttsError, + presetVoices, + onTtsClose, + onTtsTextChange, + onTtsVoiceChange, + onTtsSpeedChange, + onTtsSynthesize, + onTtsSave, +}) => { + return ( + <> + {/* 克隆音色弹窗 */} + + + {/* 详情弹窗 */} + {detailVoice && ( + + )} + + {/* 上传音频弹窗 */} + + + {/* AI 配音弹窗 */} + + + ) +} + +export default VoiceModals diff --git a/apps/web/src/pages/voices/components/VoiceTabBar.tsx b/apps/web/src/pages/voices/components/VoiceTabBar.tsx new file mode 100644 index 000000000..ea9ec296c --- /dev/null +++ b/apps/web/src/pages/voices/components/VoiceTabBar.tsx @@ -0,0 +1,54 @@ +/** + * VoiceLibrary Tab 切换栏 + */ +import React from "react" +import { AudioOutlined, UserOutlined, SoundOutlined } from "@ant-design/icons" + +export type VoiceTabKey = "preset" | "cloned" | "material" + +export interface VoiceTabBarProps { + activeTab: VoiceTabKey + presetCount: number + cloneCount: number + materialCount: number + onTabChange: (tab: VoiceTabKey) => void +} + +export const VoiceTabBar: React.FC = ({ + activeTab, + presetCount, + cloneCount, + materialCount, + onTabChange, +}) => { + return ( +
+ + + +
+ ) +} + +export default VoiceTabBar diff --git a/apps/web/src/pages/voices/components/VoiceToasts.tsx b/apps/web/src/pages/voices/components/VoiceToasts.tsx new file mode 100644 index 000000000..4b6857886 --- /dev/null +++ b/apps/web/src/pages/voices/components/VoiceToasts.tsx @@ -0,0 +1,29 @@ +/** + * VoiceLibrary Toast 提示 + */ +import React from "react" + +export interface Toast { + id: number + message: string + type: "success" | "error" +} + +export interface VoiceToastsProps { + toasts: Toast[] +} + +export const VoiceToasts: React.FC = ({ toasts }) => { + if (toasts.length === 0) return null + return ( +
+ {toasts.map((t) => ( +
+ {t.type === "success" ? "✅" : "❌"} {t.message} +
+ ))} +
+ ) +} + +export default VoiceToasts -- 2.54.0 From ea9b5c8ab3d7f37b7053e8408b88917cef302cec Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 27 Jul 2026 15:20:23 +0800 Subject: [PATCH 2/6] =?UTF-8?q?fix:=20PresetVoiceTab=20voices=20=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E6=94=B9=E4=B8=BA=20PresetVoiceDisplay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/pages/voices/components/PresetVoiceTab.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/src/pages/voices/components/PresetVoiceTab.tsx b/apps/web/src/pages/voices/components/PresetVoiceTab.tsx index 562a02529..7399601e5 100644 --- a/apps/web/src/pages/voices/components/PresetVoiceTab.tsx +++ b/apps/web/src/pages/voices/components/PresetVoiceTab.tsx @@ -7,7 +7,7 @@ import { Button } from "@/components/ui" import VoiceFilterBar from "./VoiceFilterBar" import VoiceCard from "./VoiceCard" import { genderLabel, languageLabel } from "../utils/format" -import type { PresetVoiceItem } from "@/api/voices" +import type { PresetVoiceDisplay } from "../types" export interface PresetVoiceTabProps { searchText: string @@ -17,7 +17,7 @@ export interface PresetVoiceTabProps { onGenderChange: (gender: string) => void onLangChange: (lang: string) => void loading: boolean - voices: PresetVoiceItem[] + voices: PresetVoiceDisplay[] playingId: string | null currentTime: number onPlay: (id: string, duration: number) => void -- 2.54.0 From 5c6ce75237830d05dd9e41d1e5e7ffabbb3a8273 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 27 Jul 2026 15:20:24 +0800 Subject: [PATCH 3/6] =?UTF-8?q?fix:=20ClonedVoiceTab=20=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=20ClonedVoiceDisplay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/voices/components/ClonedVoiceTab.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/web/src/pages/voices/components/ClonedVoiceTab.tsx b/apps/web/src/pages/voices/components/ClonedVoiceTab.tsx index 075eb6fdc..a3c578091 100755 --- a/apps/web/src/pages/voices/components/ClonedVoiceTab.tsx +++ b/apps/web/src/pages/voices/components/ClonedVoiceTab.tsx @@ -6,19 +6,19 @@ import { UserOutlined, PlusOutlined, RobotOutlined } from "@ant-design/icons" import { Button } from "@/components/ui" import CloneVoiceCard from "./CloneVoiceCard" import CloneCardSkeleton from "./CloneCardSkeleton" -import type { VoiceClone } from "@/api/voice-clone" +import type { ClonedVoiceDisplay } from "../types" export interface ClonedVoiceTabProps { loading: boolean - voices: VoiceClone[] + voices: ClonedVoiceDisplay[] playingId: string | null currentTime: number onPlay: (voiceId: string, duration: number) => void onPause: () => void - onUse: (voice: VoiceClone) => void - onDelete: (voice: VoiceClone) => void - onRetry: (voice: VoiceClone) => void - onShowDetail: (voice: VoiceClone) => void + onUse: (voice: ClonedVoiceDisplay) => void + onDelete: (voice: ClonedVoiceDisplay) => void + onRetry: (voice: ClonedVoiceDisplay) => void + onShowDetail: (voice: ClonedVoiceDisplay) => void onOpenClone: () => void } -- 2.54.0 From c01062ab8a347d5cc9fe9b09fe267a7f451964fa Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 27 Jul 2026 15:21:20 +0800 Subject: [PATCH 4/6] =?UTF-8?q?fix:=20VoiceModals=20=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=88Display=E7=B1=BB=E5=9E=8B/VoiceGende?= =?UTF-8?q?r/TtsStatus=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/voices/components/VoiceModals.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/web/src/pages/voices/components/VoiceModals.tsx b/apps/web/src/pages/voices/components/VoiceModals.tsx index 84b33ed71..e56478ced 100644 --- a/apps/web/src/pages/voices/components/VoiceModals.tsx +++ b/apps/web/src/pages/voices/components/VoiceModals.tsx @@ -2,8 +2,8 @@ * VoiceLibrary 弹窗集合 */ import React from "react" -import type { VoiceClone } from "@/api/voice-clone" -import type { PresetVoiceItem } from "@/api/voices" +import type { ClonedVoiceDisplay, PresetVoiceDisplay, VoiceGender } from "../types" +import type { TtsStatus } from "./TtsModal" import CloneModal from "@/components/voice/CloneModal" import CloneDetailModal from "./CloneDetailModal" import UploadVoiceModal from "./UploadVoiceModal" @@ -16,7 +16,7 @@ export interface VoiceModalsProps { onCloneSuccess: (voice: VoiceClone) => void /* 克隆详情弹窗 */ - detailVoice: VoiceClone | null + detailVoice: ClonedVoiceDisplay | null onDetailClose: () => void onDetailDelete: () => void onDetailRetry: () => void @@ -26,14 +26,14 @@ export interface VoiceModalsProps { uploadOpen: boolean uploadFile: File | null uploadName: string - uploadGender: string + uploadGender: VoiceGender uploadDesc: string - uploadProgress: number + uploadProgress: number | null onUploadClose: () => void onFileSelect: (file: File) => void onFileRemove: () => void onNameChange: (name: string) => void - onGenderChange: (gender: string) => void + onGenderChange: (gender: VoiceGender) => void onDescChange: (desc: string) => void onUpload: () => void @@ -42,10 +42,10 @@ export interface VoiceModalsProps { ttsText: string ttsVoiceId: string ttsSpeed: number - ttsStatus: string - ttsAudioUrl: string + ttsStatus: TtsStatus + ttsAudioUrl: string | null ttsError: string | null - presetVoices: PresetVoiceItem[] + presetVoices: PresetVoiceDisplay[] onTtsClose: () => void onTtsTextChange: (text: string) => void onTtsVoiceChange: (id: string) => void -- 2.54.0 From 48ab2a43290013d6aa4bc4693d2f153a95bd95dc Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 27 Jul 2026 18:51:26 +0800 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20VoiceModals=20=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E7=BC=BA=E5=A4=B1=E7=9A=84=20VoiceClone=20=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/pages/voices/components/VoiceModals.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/web/src/pages/voices/components/VoiceModals.tsx b/apps/web/src/pages/voices/components/VoiceModals.tsx index e56478ced..9198a3686 100644 --- a/apps/web/src/pages/voices/components/VoiceModals.tsx +++ b/apps/web/src/pages/voices/components/VoiceModals.tsx @@ -3,6 +3,7 @@ */ import React from "react" import type { ClonedVoiceDisplay, PresetVoiceDisplay, VoiceGender } from "../types" +import type { VoiceClone } from "@/api/voice-clone" import type { TtsStatus } from "./TtsModal" import CloneModal from "@/components/voice/CloneModal" import CloneDetailModal from "./CloneDetailModal" -- 2.54.0 From e31cea40fdf7a3c27037a797e626da6db156fff7 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 27 Jul 2026 19:04:46 +0800 Subject: [PATCH 6/6] style: prettier format MaterialVoiceTab.tsx --- apps/web/src/pages/voices/components/MaterialVoiceTab.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/web/src/pages/voices/components/MaterialVoiceTab.tsx b/apps/web/src/pages/voices/components/MaterialVoiceTab.tsx index 6f7de554d..74c1dc444 100644 --- a/apps/web/src/pages/voices/components/MaterialVoiceTab.tsx +++ b/apps/web/src/pages/voices/components/MaterialVoiceTab.tsx @@ -55,9 +55,7 @@ export const MaterialVoiceTab: React.FC = ({
- {asset.file_size - ? `${(asset.file_size / 1024 / 1024).toFixed(1)} MB` - : "--"} + {asset.file_size ? `${(asset.file_size / 1024 / 1024).toFixed(1)} MB` : "--"}
-- 2.54.0