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
@@ -0,0 +1,197 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import * as z from 'zod';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
Form,
FormControl,
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 { useCreateProfile, useProfile, useUpdateProfile } from '@/lib/hooks/useProfiles';
import { useUIStore } from '@/stores/uiStore';
const profileSchema = z.object({
name: z.string().min(1, 'Name is required').max(100),
description: z.string().max(500).optional(),
language: z.enum(['en', 'zh']),
});
type ProfileFormValues = z.infer<typeof profileSchema>;
export function ProfileForm() {
const open = useUIStore((state) => state.profileDialogOpen);
const setOpen = useUIStore((state) => state.setProfileDialogOpen);
const editingProfileId = useUIStore((state) => state.editingProfileId);
const setEditingProfileId = useUIStore((state) => state.setEditingProfileId);
const { data: editingProfile } = useProfile(editingProfileId || '');
const createProfile = useCreateProfile();
const updateProfile = useUpdateProfile();
const { toast } = useToast();
const form = useForm<ProfileFormValues>({
resolver: zodResolver(profileSchema),
defaultValues: {
name: '',
description: '',
language: 'en',
},
});
useEffect(() => {
if (editingProfile) {
form.reset({
name: editingProfile.name,
description: editingProfile.description || '',
language: editingProfile.language as 'en' | 'zh',
});
} else {
form.reset({
name: '',
description: '',
language: 'en',
});
}
}, [editingProfile, form]);
async function onSubmit(data: ProfileFormValues) {
try {
if (editingProfileId) {
await updateProfile.mutateAsync({
profileId: editingProfileId,
data,
});
toast({
title: 'Profile updated',
description: `"${data.name}" has been updated successfully.`,
});
} else {
await createProfile.mutateAsync(data);
toast({
title: 'Profile created',
description: `"${data.name}" has been created successfully.`,
});
}
form.reset();
setEditingProfileId(null);
setOpen(false);
} catch (error) {
toast({
title: 'Error',
description: error instanceof Error ? error.message : 'Failed to save profile',
variant: 'destructive',
});
}
}
function handleOpenChange(open: boolean) {
setOpen(open);
if (!open) {
setEditingProfileId(null);
form.reset();
}
}
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>{editingProfileId ? 'Edit Profile' : 'Create Voice Profile'}</DialogTitle>
<DialogDescription>
{editingProfileId
? 'Update your voice profile details.'
: 'Add a new voice profile with samples.'}
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="My Voice" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormControl>
<Textarea placeholder="Describe this voice..." {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<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>
)}
/>
<div className="flex gap-2 justify-end">
<Button type="button" variant="outline" onClick={() => handleOpenChange(false)}>
Cancel
</Button>
<Button type="submit" disabled={createProfile.isPending || updateProfile.isPending}>
{createProfile.isPending || updateProfile.isPending
? 'Saving...'
: editingProfileId
? 'Update Profile'
: 'Create Profile'}
</Button>
</div>
</form>
</Form>
</DialogContent>
</Dialog>
);
}