'use client' import { useCallback, useEffect, useMemo } from 'react' import { Controller, useFieldArray, useForm } from 'react-hook-form' import type { FieldErrorsImpl } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' import { Plus, Trash2 } from 'lucide-react' import { SpeakerProfile } from '@/lib/types/podcasts' import { useCreateSpeakerProfile, useUpdateSpeakerProfile, } from '@/lib/hooks/use-podcasts' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Textarea } from '@/components/ui/textarea' import { Separator } from '@/components/ui/separator' const speakerConfigSchema = z.object({ name: z.string().min(1, 'Name is required'), voice_id: z.string().min(1, 'Voice ID is required'), backstory: z.string().min(1, 'Backstory is required'), personality: z.string().min(1, 'Personality is required'), }) const speakerProfileSchema = z.object({ name: z.string().min(1, 'Name is required'), description: z.string().optional(), tts_provider: z.string().min(1, 'Provider is required'), tts_model: z.string().min(1, 'Model is required'), speakers: z .array(speakerConfigSchema) .min(1, 'At least one speaker is required') .max(4, 'You can configure up to 4 speakers'), }) export type SpeakerProfileFormValues = z.infer interface SpeakerProfileFormDialogProps { mode: 'create' | 'edit' open: boolean onOpenChange: (open: boolean) => void modelOptions: Record initialData?: SpeakerProfile } const EMPTY_SPEAKER = { name: '', voice_id: '', backstory: '', personality: '', } export function SpeakerProfileFormDialog({ mode, open, onOpenChange, modelOptions, initialData, }: SpeakerProfileFormDialogProps) { const createProfile = useCreateSpeakerProfile() const updateProfile = useUpdateSpeakerProfile() const providers = useMemo(() => Object.keys(modelOptions), [modelOptions]) const getDefaults = useCallback((): SpeakerProfileFormValues => { const firstProvider = providers[0] ?? '' const firstModel = firstProvider ? modelOptions[firstProvider]?.[0] ?? '' : '' if (initialData) { return { name: initialData.name, description: initialData.description ?? '', tts_provider: initialData.tts_provider, tts_model: initialData.tts_model, speakers: initialData.speakers?.map((speaker) => ({ ...speaker })) ?? [{ ...EMPTY_SPEAKER }], } } return { name: '', description: '', tts_provider: firstProvider, tts_model: firstModel, speakers: [{ ...EMPTY_SPEAKER }], } }, [initialData, modelOptions, providers]) const { control, register, handleSubmit, reset, setValue, watch, formState: { errors }, } = useForm({ resolver: zodResolver(speakerProfileSchema), defaultValues: getDefaults(), }) const { fields, append, remove, } = useFieldArray({ control, name: 'speakers', }) const provider = watch('tts_provider') const currentModel = watch('tts_model') const availableModels = useMemo( () => modelOptions[provider] ?? [], [modelOptions, provider] ) const speakersArrayError = ( errors.speakers as FieldErrorsImpl<{ root?: { message?: string } }> | undefined )?.root?.message useEffect(() => { if (!open) { return } reset(getDefaults()) }, [open, reset, getDefaults]) useEffect(() => { if (!provider) { return } const models = modelOptions[provider] ?? [] if (models.length === 0) { setValue('tts_model', '') return } if (!models.includes(currentModel)) { setValue('tts_model', models[0]) } }, [provider, currentModel, modelOptions, setValue]) const onSubmit = async (values: SpeakerProfileFormValues) => { const payload = { ...values, description: values.description ?? '', } if (mode === 'create') { await createProfile.mutateAsync(payload) } else if (initialData) { await updateProfile.mutateAsync({ profileId: initialData.id, payload, }) } onOpenChange(false) } const isSubmitting = createProfile.isPending || updateProfile.isPending const disableSubmit = isSubmitting || providers.length === 0 const isEdit = mode === 'edit' return ( {isEdit ? 'Edit Speaker Profile' : 'Create Speaker Profile'} Configure text-to-speech settings and define up to four speakers. {providers.length === 0 ? ( No text-to-speech models available Add TTS models in the Models section before creating a speaker profile. ) : null}
{errors.name ? (

{errors.name.message}

) : null}
( )} /> {errors.tts_provider ? (

{errors.tts_provider.message}

) : null}
( )} /> {errors.tts_model ? (

{errors.tts_model.message}

) : null}