mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-19 23:00:45 -07:00
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:
@@ -0,0 +1 @@
|
||||
# React Query hooks will be placed here
|
||||
@@ -0,0 +1,15 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { apiClient } from '@/lib/api/client';
|
||||
import type { GenerationRequest } from '@/lib/api/types';
|
||||
|
||||
export function useGeneration() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (data: GenerationRequest) => apiClient.generateSpeech(data),
|
||||
onSuccess: () => {
|
||||
// Invalidate history to show new generation
|
||||
queryClient.invalidateQueries({ queryKey: ['history'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { apiClient } from '@/lib/api/client';
|
||||
import type { HistoryQuery } from '@/lib/api/types';
|
||||
|
||||
export function useHistory(query?: HistoryQuery) {
|
||||
return useQuery({
|
||||
queryKey: ['history', query],
|
||||
queryFn: () => apiClient.listHistory(query),
|
||||
});
|
||||
}
|
||||
|
||||
export function useGenerationDetail(generationId: string) {
|
||||
return useQuery({
|
||||
queryKey: ['history', generationId],
|
||||
queryFn: () => apiClient.getGeneration(generationId),
|
||||
enabled: !!generationId,
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteGeneration() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (generationId: string) => apiClient.deleteGeneration(generationId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['history'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { apiClient } from '@/lib/api/client';
|
||||
import type { VoiceProfileCreate } from '@/lib/api/types';
|
||||
|
||||
export function useProfiles() {
|
||||
return useQuery({
|
||||
queryKey: ['profiles'],
|
||||
queryFn: () => apiClient.listProfiles(),
|
||||
});
|
||||
}
|
||||
|
||||
export function useProfile(profileId: string) {
|
||||
return useQuery({
|
||||
queryKey: ['profiles', profileId],
|
||||
queryFn: () => apiClient.getProfile(profileId),
|
||||
enabled: !!profileId,
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateProfile() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (data: VoiceProfileCreate) => apiClient.createProfile(data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['profiles'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateProfile() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ profileId, data }: { profileId: string; data: VoiceProfileCreate }) =>
|
||||
apiClient.updateProfile(profileId, data),
|
||||
onSuccess: (_, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['profiles'] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['profiles', variables.profileId],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteProfile() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (profileId: string) => apiClient.deleteProfile(profileId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['profiles'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useProfileSamples(profileId: string) {
|
||||
return useQuery({
|
||||
queryKey: ['profiles', profileId, 'samples'],
|
||||
queryFn: () => apiClient.listProfileSamples(profileId),
|
||||
enabled: !!profileId,
|
||||
});
|
||||
}
|
||||
|
||||
export function useAddSample() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
profileId,
|
||||
file,
|
||||
referenceText,
|
||||
}: {
|
||||
profileId: string;
|
||||
file: File;
|
||||
referenceText: string;
|
||||
}) => apiClient.addProfileSample(profileId, file, referenceText),
|
||||
onSuccess: (_, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['profiles', variables.profileId, 'samples'],
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['profiles', variables.profileId],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteSample() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (sampleId: string) => apiClient.deleteProfileSample(sampleId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['profiles'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { apiClient } from '@/lib/api/client';
|
||||
import { useServerStore } from '@/stores/serverStore';
|
||||
|
||||
export function useServerHealth() {
|
||||
const serverUrl = useServerStore((state) => state.serverUrl);
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['server', 'health', serverUrl],
|
||||
queryFn: () => apiClient.getHealth(),
|
||||
refetchInterval: 30000, // Check every 30 seconds
|
||||
retry: 1,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { apiClient } from '@/lib/api/client';
|
||||
|
||||
export function useTranscription() {
|
||||
return useMutation({
|
||||
mutationFn: ({ file, language }: { file: File; language?: 'en' | 'zh' }) =>
|
||||
apiClient.transcribeAudio(file, language),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
# Utility functions will be placed here
|
||||
@@ -0,0 +1,18 @@
|
||||
export function createAudioUrl(audioId: string, serverUrl: string): string {
|
||||
return `${serverUrl}/audio/${audioId}`;
|
||||
}
|
||||
|
||||
export function downloadAudio(url: string, filename: string): void {
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
export function formatAudioDuration(seconds: number): string {
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { formatDistance } from 'date-fns';
|
||||
|
||||
export function formatDuration(seconds: number): string {
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
export function formatDate(date: string | Date): string {
|
||||
return formatDistance(new Date(date), new Date(), { addSuffix: true });
|
||||
}
|
||||
|
||||
export function formatFileSize(bytes: number): string {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return `${Math.round((bytes / k ** i) * 100) / 100} ${sizes[i]}`;
|
||||
}
|
||||
Reference in New Issue
Block a user