Initialize voicebox project with backend, frontend, and Tauri setup. Added configuration files, dependencies, and basic structure for components, hooks, and utilities. Included README and setup documentation for guidance.

This commit is contained in:
Jamie Pine
2026-01-25 02:19:06 -08:00
commit 01e3065692
166 changed files with 22764 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
# Voice generation components
@@ -0,0 +1,190 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { Loader2 } from 'lucide-react';
import { useForm } from 'react-hook-form';
import * as z from 'zod';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Textarea } from '@/components/ui/textarea';
import { useToast } from '@/components/ui/use-toast';
import { useGeneration } from '@/lib/hooks/useGeneration';
import { useProfiles } from '@/lib/hooks/useProfiles';
const generationSchema = z.object({
profileId: z.string().min(1, 'Please select a voice profile'),
text: z.string().min(1, 'Text is required').max(5000),
language: z.enum(['en', 'zh']),
seed: z.number().int().optional(),
});
type GenerationFormValues = z.infer<typeof generationSchema>;
export function GenerationForm() {
const { data: profiles } = useProfiles();
const generation = useGeneration();
const { toast } = useToast();
const form = useForm<GenerationFormValues>({
resolver: zodResolver(generationSchema),
defaultValues: {
profileId: '',
text: '',
language: 'en',
seed: undefined,
},
});
async function onSubmit(data: GenerationFormValues) {
try {
const result = await generation.mutateAsync({
profile_id: data.profileId,
text: data.text,
language: data.language,
seed: data.seed,
});
toast({
title: 'Generation complete!',
description: `Audio generated (${result.duration.toFixed(2)}s)`,
});
form.reset();
} catch (error) {
toast({
title: 'Generation failed',
description: error instanceof Error ? error.message : 'Failed to generate audio',
variant: 'destructive',
});
}
}
return (
<Card>
<CardHeader>
<CardTitle>Generate Speech</CardTitle>
</CardHeader>
<CardContent>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="profileId"
render={({ field }) => (
<FormItem>
<FormLabel>Voice Profile</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a voice" />
</SelectTrigger>
</FormControl>
<SelectContent>
{profiles?.map((profile) => (
<SelectItem key={profile.id} value={profile.id}>
{profile.name}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="text"
render={({ field }) => (
<FormItem>
<FormLabel>Text to Speak</FormLabel>
<FormControl>
<Textarea
placeholder="Enter the text you want to generate..."
className="min-h-[200px]"
{...field}
/>
</FormControl>
<FormDescription>Max 5000 characters</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<div className="grid gap-4 md:grid-cols-2">
<FormField
control={form.control}
name="language"
render={({ field }) => (
<FormItem>
<FormLabel>Language</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="en">English</SelectItem>
<SelectItem value="zh">Chinese</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="seed"
render={({ field }) => (
<FormItem>
<FormLabel>Seed (optional)</FormLabel>
<FormControl>
<Input
type="number"
placeholder="Random"
{...field}
onChange={(e) =>
field.onChange(e.target.value ? parseInt(e.target.value, 10) : undefined)
}
/>
</FormControl>
<FormDescription>For reproducible results</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</div>
<Button type="submit" className="w-full" disabled={generation.isPending}>
{generation.isPending ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Generating...
</>
) : (
'Generate Speech'
)}
</Button>
</form>
</Form>
</CardContent>
</Card>
);
}