From 10f3d93a19cc10180220fd8c67f4e6d14507eee0 Mon Sep 17 00:00:00 2001 From: imeepos Date: Thu, 31 Jul 2025 13:07:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8D=87=E7=BA=A7=E5=A3=B0=E9=9F=B3?= =?UTF-8?q?=E5=85=8B=E9=9A=86=E4=B8=8ETTS=E5=B7=A5=E5=85=B7=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将首页改为语音生成历史列表页面,支持搜索和筛选功能 - 创建VoiceCloneModal组件,将声音克隆功能封装为弹框 - 创建SpeechGenerationModal组件,将语音合成功能封装为弹框 - 更新路由配置,/tools/voice-clone指向新的历史页面 - 支持音频播放、下载、删除等操作 - 使用App.tsx中的modal-root容器渲染Modal组件 主要变更: - 新增 VoiceGenerationHistory.tsx - 语音生成历史页面 - 新增 VoiceCloneModal.tsx - 声音克隆弹框组件 - 新增 SpeechGenerationModal.tsx - 语音合成弹框组件 - 修改 App.tsx - 更新路由配置 - 修改 VoiceCloneTool.tsx - 添加新的图标导入 --- apps/desktop/src/App.tsx | 4 +- .../src/components/SpeechGenerationModal.tsx | 504 ++++++++++++++++++ .../src/components/VoiceCloneModal.tsx | 356 +++++++++++++ .../src/pages/tools/VoiceCloneTool.tsx | 9 +- .../pages/tools/VoiceGenerationHistory.tsx | 498 +++++++++++++++++ 5 files changed, 1368 insertions(+), 3 deletions(-) create mode 100644 apps/desktop/src/components/SpeechGenerationModal.tsx create mode 100644 apps/desktop/src/components/VoiceCloneModal.tsx create mode 100644 apps/desktop/src/pages/tools/VoiceGenerationHistory.tsx diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index 90d1c9a..03670d7 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -26,7 +26,7 @@ import OutfitFavoritesTool from './pages/tools/OutfitFavoritesTool'; import OutfitComparisonTool from './pages/tools/OutfitComparisonTool'; import MaterialSearchTool from './pages/tools/MaterialSearchTool'; import ImageGenerationTool from './pages/tools/ImageGenerationTool'; -import VoiceCloneTool from './pages/tools/VoiceCloneTool'; +import VoiceGenerationHistory from './pages/tools/VoiceGenerationHistory'; import { EnrichedAnalysisDemo } from './pages/tools/EnrichedAnalysisDemo'; import MaterialCenter from './pages/MaterialCenter'; import VideoGeneration from './pages/VideoGeneration'; @@ -141,7 +141,7 @@ function App() { } /> } /> } /> - } /> + } /> } /> } /> diff --git a/apps/desktop/src/components/SpeechGenerationModal.tsx b/apps/desktop/src/components/SpeechGenerationModal.tsx new file mode 100644 index 0000000..57d3ea2 --- /dev/null +++ b/apps/desktop/src/components/SpeechGenerationModal.tsx @@ -0,0 +1,504 @@ +import React, { useState, useCallback, useEffect } from 'react'; +import { + Volume2, + Play, + Download, + CheckCircle, + XCircle, + Loader2, + Settings, + Users, + Mic +} from 'lucide-react'; +import { invoke } from '@tauri-apps/api/core'; +import { Modal } from './Modal'; +import { useNotifications } from './NotificationSystem'; +import SystemVoiceSelector from './SystemVoiceSelector'; +import { + SpeechGenerationRequest, + SpeechGenerationResponse, + SpeechGenerationStatus, + SpeechGenerationState, + VoiceInfo, + GetVoicesResponse +} from '../types/voiceClone'; +import { SystemVoice } from '../types/systemVoice'; + +interface SpeechGenerationModalProps { + isOpen: boolean; + onClose: () => void; + onSuccess?: (audioUrl: string) => void; +} + +/** + * 语音合成Modal组件 + * 封装语音合成功能为独立的弹框组件 + */ +export const SpeechGenerationModal: React.FC = ({ + isOpen, + onClose, + onSuccess +}) => { + const { addNotification } = useNotifications(); + + // ============= 状态管理 ============= + + // 音色管理状态 + const [voices, setVoices] = useState([]); + const [selectedVoiceId, setSelectedVoiceId] = useState(''); + const [isLoadingVoices, setIsLoadingVoices] = useState(false); + + // 系统音色状态 + const [selectedSystemVoice, setSelectedSystemVoice] = useState(null); + const [voiceSource, setVoiceSource] = useState<'system' | 'custom'>('system'); + + // 语音生成状态 + const [speechRequest, setSpeechRequest] = useState({ + text: '', + voice_id: '', + speed: 1.0, + vol: 1.0, + emotion: 'calm' + }); + const [speechState, setSpeechState] = useState({ + status: SpeechGenerationStatus.IDLE + }); + + // ============= 数据加载函数 ============= + + // 加载自定义音色列表 + const loadVoices = useCallback(async () => { + setIsLoadingVoices(true); + try { + const response = await invoke('get_voices'); + if (response.status && response.data) { + setVoices(response.data); + } else { + console.warn('获取音色列表失败:', response.msg); + } + } catch (error) { + console.error('加载音色列表失败:', error); + } finally { + setIsLoadingVoices(false); + } + }, []); + + // ============= 音色选择功能 ============= + + const handleVoiceSelect = useCallback((voiceId: string) => { + setSelectedVoiceId(voiceId); + setVoiceSource('custom'); + setSpeechRequest(prev => ({ + ...prev, + voice_id: voiceId + })); + }, []); + + // 系统音色选择处理 + const handleSystemVoiceSelect = useCallback((voiceId: string, voice: SystemVoice) => { + setSelectedSystemVoice(voice); + setVoiceSource('system'); + setSpeechRequest(prev => ({ + ...prev, + voice_id: voiceId + })); + }, []); + + // ============= 语音生成功能 ============= + + const handleGenerateSpeech = useCallback(async () => { + if (!speechRequest.text.trim()) { + addNotification({ + type: 'warning', + title: '请输入要合成的文本', + message: '请输入要转换为语音的文本内容' + }); + return; + } + + if (!speechRequest.voice_id) { + addNotification({ + type: 'warning', + title: '请选择音色', + message: '请选择要使用的音色' + }); + return; + } + + setSpeechState({ + status: SpeechGenerationStatus.GENERATING, + progress: '正在生成语音...' + }); + + try { + const response = await invoke('generate_speech', { + request: speechRequest + }); + + if (response.status && response.data) { + setSpeechState({ + status: SpeechGenerationStatus.SUCCESS, + result: response + }); + + addNotification({ + type: 'success', + title: '语音生成成功', + message: '语音已成功生成,可以播放或下载' + }); + + // 调用成功回调 + if (onSuccess) { + onSuccess(response.data); + } + } else { + throw new Error(response.msg || '生成失败'); + } + } catch (error) { + console.error('语音生成失败:', error); + setSpeechState({ + status: SpeechGenerationStatus.ERROR, + error: String(error) + }); + + addNotification({ + type: 'error', + title: '语音生成失败', + message: `生成失败: ${error}` + }); + } + }, [speechRequest, addNotification, onSuccess]); + + // ============= Modal控制 ============= + + const handleClose = useCallback(() => { + // 重置状态 + setSpeechRequest({ + text: '', + voice_id: '', + speed: 1.0, + vol: 1.0, + emotion: 'calm' + }); + setSpeechState({ status: SpeechGenerationStatus.IDLE }); + setSelectedVoiceId(''); + setSelectedSystemVoice(null); + setVoiceSource('system'); + onClose(); + }, [onClose]); + + // ============= 生命周期 ============= + + useEffect(() => { + if (isOpen) { + loadVoices(); + } + }, [isOpen, loadVoices]); + + return ( + } + size="lg" + variant="default" + closeOnBackdropClick={speechState.status !== SpeechGenerationStatus.GENERATING} + closeOnEscape={speechState.status !== SpeechGenerationStatus.GENERATING} + > +
+ {/* 文本输入 */} +
+ +