diff --git a/qwen3-tts-frontend/src/components/tts/VoiceCloneForm.tsx b/qwen3-tts-frontend/src/components/tts/VoiceCloneForm.tsx index 7c16440..bb4120b 100644 --- a/qwen3-tts-frontend/src/components/tts/VoiceCloneForm.tsx +++ b/qwen3-tts-frontend/src/components/tts/VoiceCloneForm.tsx @@ -9,7 +9,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter } from '@/components/ui/dialog' import { Checkbox } from '@/components/ui/checkbox' import { Label } from '@/components/ui/label' -import { Settings, Globe2, Type, Play, FileText, Mic, Zap, Database } from 'lucide-react' +import { Settings, Globe2, Type, Play, FileText, Mic, Zap, Database, ArrowRight, ArrowLeft } from 'lucide-react' import { toast } from 'sonner' import { IconLabel } from '@/components/IconLabel' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' @@ -18,11 +18,12 @@ import { useJobPolling } from '@/hooks/useJobPolling' import { useHistoryContext } from '@/contexts/HistoryContext' import { LoadingState } from '@/components/LoadingState' import { AudioPlayer } from '@/components/AudioPlayer' -import { AudioInputSelector } from '@/components/AudioInputSelector' +import { FileUploader } from '@/components/FileUploader' +import { AudioRecorder } from '@/components/AudioRecorder' import { PresetSelector } from '@/components/PresetSelector' -import { ParamInput } from '@/components/ParamInput' import { PRESET_REF_TEXTS, ADVANCED_PARAMS_INFO } from '@/lib/constants' import type { Language } from '@/types/tts' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' const formSchema = z.object({ text: z.string().min(1, '请输入要合成的文本').max(5000, '文本长度不能超过 5000 字符'), @@ -44,6 +45,8 @@ function VoiceCloneForm() { const [languages, setLanguages] = useState([]) const [isLoading, setIsLoading] = useState(false) const [advancedOpen, setAdvancedOpen] = useState(false) + const [step, setStep] = useState<1 | 2>(1) + const [inputTab, setInputTab] = useState<'upload' | 'record'>('upload') const [tempAdvancedParams, setTempAdvancedParams] = useState({ max_new_tokens: 2048 }) @@ -57,6 +60,7 @@ function VoiceCloneForm() { setValue, watch, control, + trigger, formState: { errors }, } = useForm({ resolver: zodResolver(formSchema), @@ -86,6 +90,14 @@ function VoiceCloneForm() { fetchData() }, []) + const handleNextStep = async () => { + // Validate step 1 fields + const valid = await trigger(['ref_audio', 'ref_text']) + if (valid) { + setStep(2) + } + } + const onSubmit = async (data: FormData) => { setIsLoading(true) try { @@ -97,7 +109,7 @@ function VoiceCloneForm() { startPolling(result.job_id) try { await refresh() - } catch {} + } catch { } } catch (error) { toast.error('创建任务失败') } finally { @@ -111,187 +123,257 @@ function VoiceCloneForm() { }, [currentJob?.id, currentJob?.audio_url]) return ( -
-
- -