import React, { useState, useRef } from 'react'; import { Search, Mic2, Play, Pause, Upload, X, Filter, Plus, Volume2, Sparkles, Wand2, ChevronDown } from 'lucide-react'; import { Button, Input, TableHeader, TableRow, TableHead, TableCell, Dialog, Badge } from '../components/UI'; import { mockVoices } from '../services/mockData'; import { Voice } from '../types'; export const VoiceLibraryPage: React.FC = () => { const [voices, setVoices] = useState(mockVoices); const [searchTerm, setSearchTerm] = useState(''); const [vendorFilter, setVendorFilter] = useState<'all' | 'Ali' | 'Volcano' | 'Minimax' | '硅基流动'>('all'); const [genderFilter, setGenderFilter] = useState<'all' | 'Male' | 'Female'>('all'); const [langFilter, setLangFilter] = useState<'all' | 'zh' | 'en'>('all'); const [playingVoiceId, setPlayingVoiceId] = useState(null); const [isCloneModalOpen, setIsCloneModalOpen] = useState(false); const [isAddModalOpen, setIsAddModalOpen] = useState(false); const filteredVoices = voices.filter(voice => { const matchesSearch = voice.name.toLowerCase().includes(searchTerm.toLowerCase()); const matchesVendor = vendorFilter === 'all' || voice.vendor === vendorFilter; const matchesGender = genderFilter === 'all' || voice.gender === genderFilter; const matchesLang = langFilter === 'all' || voice.language === langFilter; return matchesSearch && matchesVendor && matchesGender && matchesLang; }); const handlePlayToggle = (id: string) => { if (playingVoiceId === id) { setPlayingVoiceId(null); } else { setPlayingVoiceId(id); setTimeout(() => { setPlayingVoiceId((current) => current === id ? null : current); }, 3000); } }; const handleAddSuccess = (newVoice: Voice) => { setVoices([newVoice, ...voices]); setIsAddModalOpen(false); setIsCloneModalOpen(false); }; return (

声音库

{/* Filter Bar */}
setSearchTerm(e.target.value)} />
声音名称 厂商 性别 语言 试听 {filteredVoices.map(voice => (
{voice.vendor === '硅基流动' && } {voice.name} {voice.description && {voice.description}}
{voice.vendor} {voice.gender === 'Male' ? '男' : '女'} {voice.language === 'zh' ? '中文' : 'English'}
))} {filteredVoices.length === 0 && ( 暂无声音数据 )}
setIsAddModalOpen(false)} onSuccess={handleAddSuccess} /> setIsCloneModalOpen(false)} onSuccess={handleAddSuccess} />
); }; // --- Unified Add Voice Modal --- const AddVoiceModal: React.FC<{ isOpen: boolean; onClose: () => void; onSuccess: (voice: Voice) => void; }> = ({ isOpen, onClose, onSuccess }) => { const [vendor, setVendor] = useState<'硅基流动' | 'Ali' | 'Volcano' | 'Minimax'>('硅基流动'); const [name, setName] = useState(''); const [sfModel, setSfModel] = useState('fishaudio/fish-speech-1.5'); const [sfVoiceId, setSfVoiceId] = useState('fishaudio:amy'); const [sfSpeed, setSfSpeed] = useState(1); const [sfGain, setSfGain] = useState(0); const [model, setModel] = useState(''); const [voiceKey, setVoiceKey] = useState(''); const [gender, setGender] = useState('Female'); const [language, setLanguage] = useState('zh'); const [description, setDescription] = useState(''); const [testInput, setTestInput] = useState('你好,正在测试语音合成效果。'); const [isAuditioning, setIsAuditioning] = useState(false); const handleAudition = () => { if (!testInput.trim()) return; setIsAuditioning(true); setTimeout(() => setIsAuditioning(false), 2000); }; const handleSubmit = () => { if (!name) { alert("请填写声音显示名称"); return; } let newVoice: Voice = { id: `${vendor === '硅基流动' ? 'sf' : 'gen'}-${Date.now()}`, name: name, vendor: vendor, gender: gender, language: language, description: description || (vendor === '硅基流动' ? `Model: ${sfModel}` : `Model: ${model}`) }; onSuccess(newVoice); setName(''); setVendor('硅基流动'); setDescription(''); }; return ( } >
setName(e.target.value)} placeholder="例如: 客服小美" />
{vendor === '硅基流动' ? (
setSfVoiceId(e.target.value)} placeholder="fishaudio:amy" />
setSfSpeed(parseFloat(e.target.value))} className="flex-1 accent-primary" /> {sfSpeed}x
setSfGain(parseInt(e.target.value))} className="flex-1 accent-primary" /> {sfGain}dB
) : (
setModel(e.target.value)} placeholder="API Model Key" />
setVoiceKey(e.target.value)} placeholder="Voice Key" />
)}