import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import * as z from 'zod' import { useEffect, useState, forwardRef, useImperativeHandle, useMemo } from 'react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Textarea } from '@/components/ui/textarea' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter } from '@/components/ui/dialog' import { Label } from '@/components/ui/label' import { Settings, Globe2, Type, Play, Palette } from 'lucide-react' import { toast } from 'sonner' import { IconLabel } from '@/components/IconLabel' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' import { ttsApi, jobApi } from '@/lib/api' import { useJobPolling } from '@/hooks/useJobPolling' import { LoadingState } from '@/components/LoadingState' import { AudioPlayer } from '@/components/AudioPlayer' import { PresetSelector } from '@/components/PresetSelector' import { ParamInput } from '@/components/ParamInput' import { PRESET_VOICE_DESIGNS, ADVANCED_PARAMS_INFO } from '@/lib/constants' import type { Language } from '@/types/tts' const formSchema = z.object({ text: z.string().min(1, '请输入要合成的文本').max(5000, '文本长度不能超过 5000 字符'), language: z.string().min(1, '请选择语言'), instruct: z.string().min(10, '音色描述至少需要 10 个字符').max(500, '音色描述不能超过 500 字符'), max_new_tokens: z.number().min(1).max(10000).optional(), temperature: z.number().min(0).max(2).optional(), top_k: z.number().min(1).max(100).optional(), top_p: z.number().min(0).max(1).optional(), repetition_penalty: z.number().min(0).max(2).optional(), }) type FormData = z.infer export interface VoiceDesignFormHandle { loadParams: (params: any) => void } const VoiceDesignForm = forwardRef((_props, ref) => { const [languages, setLanguages] = useState([]) const [isLoading, setIsLoading] = useState(false) const [advancedOpen, setAdvancedOpen] = useState(false) const [tempAdvancedParams, setTempAdvancedParams] = useState({ max_new_tokens: 2048, temperature: 0.3, top_k: 20, top_p: 0.7, repetition_penalty: 1.05 }) const { currentJob, isPolling, isCompleted, startPolling, elapsedTime } = useJobPolling() const { register, handleSubmit, setValue, watch, formState: { errors }, } = useForm({ resolver: zodResolver(formSchema), defaultValues: { text: '', language: 'Auto', instruct: '', max_new_tokens: 2048, temperature: 0.3, top_k: 20, top_p: 0.7, repetition_penalty: 1.05, }, }) useImperativeHandle(ref, () => ({ loadParams: (params: any) => { setValue('text', params.text || '') setValue('language', params.language || 'Auto') setValue('instruct', params.instruct || '') setValue('max_new_tokens', params.max_new_tokens || 2048) setValue('temperature', params.temperature || 0.3) setValue('top_k', params.top_k || 20) setValue('top_p', params.top_p || 0.7) setValue('repetition_penalty', params.repetition_penalty || 1.05) } })) useEffect(() => { const fetchData = async () => { try { const langs = await ttsApi.getLanguages() setLanguages(langs) } catch (error) { toast.error('加载数据失败') } } fetchData() }, []) const onSubmit = async (data: FormData) => { setIsLoading(true) try { const result = await ttsApi.createVoiceDesignJob(data) toast.success('任务已创建') startPolling(result.job_id) } catch (error) { toast.error('创建任务失败') } finally { setIsLoading(false) } } const memoizedAudioUrl = useMemo(() => { if (!currentJob) return '' return jobApi.getAudioUrl(currentJob.id, currentJob.audio_url) }, [currentJob?.id, currentJob?.audio_url]) return (
{errors.language && (

{errors.language.message}

)}