import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import { useState, useEffect } from 'react'; import * as z from 'zod'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { useToast } from '@/components/ui/use-toast'; import { useAddSample, useProfile } from '@/lib/hooks/useProfiles'; import { useTranscription } from '@/lib/hooks/useTranscription'; import { useAudioRecording } from '@/lib/hooks/useAudioRecording'; import { Mic, Square, Upload } from 'lucide-react'; import { formatAudioDuration } from '@/lib/utils/audio'; const sampleSchema = z.object({ file: z.instanceof(File, { message: 'Please select an audio file' }), referenceText: z .string() .min(1, 'Reference text is required') .max(1000, 'Reference text must be less than 1000 characters'), }); type SampleFormValues = z.infer; interface SampleUploadProps { profileId: string; open: boolean; onOpenChange: (open: boolean) => void; } export function SampleUpload({ profileId, open, onOpenChange }: SampleUploadProps) { const addSample = useAddSample(); const transcribe = useTranscription(); const { data: profile } = useProfile(profileId); const { toast } = useToast(); const [mode, setMode] = useState<'upload' | 'record'>('upload'); const form = useForm({ resolver: zodResolver(sampleSchema), defaultValues: { referenceText: '', }, }); const selectedFile = form.watch('file'); const { isRecording, duration, error: recordingError, startRecording, stopRecording, cancelRecording, } = useAudioRecording({ maxDurationSeconds: 30, onRecordingComplete: (blob) => { // Convert blob to File object const file = new File([blob], `recording-${Date.now()}.webm`, { type: blob.type || 'audio/webm', }); form.setValue('file', file, { shouldValidate: true }); toast({ title: 'Recording complete', description: 'Audio has been recorded successfully.', }); }, }); // Show recording errors useEffect(() => { if (recordingError) { toast({ title: 'Recording error', description: recordingError, variant: 'destructive', }); } }, [recordingError, toast]); async function handleTranscribe() { const file = form.getValues('file'); if (!file) { toast({ title: 'No file selected', description: 'Please select an audio file first.', variant: 'destructive', }); return; } try { const language = profile?.language as 'en' | 'zh' | undefined; const result = await transcribe.mutateAsync({ file, language }); form.setValue('referenceText', result.text, { shouldValidate: true }); toast({ title: 'Transcription complete', description: 'Audio has been transcribed successfully.', }); } catch (error) { toast({ title: 'Transcription failed', description: error instanceof Error ? error.message : 'Failed to transcribe audio', variant: 'destructive', }); } } async function onSubmit(data: SampleFormValues) { try { await addSample.mutateAsync({ profileId, file: data.file, referenceText: data.referenceText, }); toast({ title: 'Sample added', description: 'Audio sample has been added successfully.', }); handleOpenChange(false); } catch (error) { toast({ title: 'Error', description: error instanceof Error ? error.message : 'Failed to add sample', variant: 'destructive', }); } } function handleOpenChange(newOpen: boolean) { if (!newOpen) { form.reset(); setMode('upload'); if (isRecording) { cancelRecording(); } } onOpenChange(newOpen); } function handleCancelRecording() { cancelRecording(); // Reset file field by clearing the input form.resetField('file'); } return ( Add Audio Sample Upload an audio file and provide the reference text that matches the audio.
setMode(v as 'upload' | 'record')}> Upload Record ( Audio File
{ const file = e.target.files?.[0]; if (file) { onChange(file); } }} {...field} /> {selectedFile && ( )}
Supported formats: WAV, MP3, M4A. Click "Transcribe" to automatically extract text from the audio.
)} />
( Record Audio
{!isRecording && !selectedFile && (

Click to start recording. Maximum duration: 30 seconds.

)} {isRecording && (
{formatAudioDuration(duration)}

Recording in progress... ({formatAudioDuration(30 - duration)}{' '} remaining)

)} {selectedFile && !isRecording && (
Recording complete

File: {selectedFile.name}

)}
Record audio directly from your microphone. Maximum duration is 30 seconds. )} /> ( Reference Text