Add profile export and import functionality

- Implemented API endpoints for exporting and importing voice profiles as ZIP archives.
- Enhanced the frontend with new hooks and components for profile export and import, including file handling and user dialogs.
- Integrated Tauri plugins for file system access and dialog interactions to facilitate seamless user experience.
- Updated ProfileCard and ProfileList components to support new export and import features, improving overall functionality.
- Added necessary error handling and validation for file operations to ensure robustness.
This commit is contained in:
Jamie Pine
2026-01-25 19:00:16 -08:00
parent c62f615162
commit 9125a4abe0
19 changed files with 7794 additions and 30 deletions
+71
View File
@@ -1,6 +1,7 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { apiClient } from '@/lib/api/client';
import type { VoiceProfileCreate } from '@/lib/api/types';
import { isTauri } from '@/lib/tauri';
export function useProfiles() {
return useQuery({
@@ -96,3 +97,73 @@ export function useDeleteSample() {
},
});
}
export function useExportProfile() {
return useMutation({
mutationFn: async (profileId: string) => {
const blob = await apiClient.exportProfile(profileId);
// Get profile name for filename
const profile = await apiClient.getProfile(profileId);
const safeName = profile.name.replace(/[^a-z0-9]/gi, '-').toLowerCase();
const filename = `profile-${safeName}.voicebox.zip`;
if (isTauri()) {
// Use Tauri's native save dialog
try {
const { save } = await import('@tauri-apps/plugin-dialog');
const filePath = await save({
defaultPath: filename,
filters: [
{
name: 'Voicebox Profile',
extensions: ['voicebox.zip', 'zip'],
},
],
});
if (filePath) {
// Write file using Tauri's filesystem API
const { writeBinaryFile } = await import('@tauri-apps/plugin-fs');
const arrayBuffer = await blob.arrayBuffer();
await writeBinaryFile(filePath, new Uint8Array(arrayBuffer));
}
} catch (error) {
console.error('Failed to use Tauri dialog, falling back to browser download:', error);
// Fall back to browser download if Tauri dialog fails
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}
} else {
// Browser: trigger download
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}
return blob;
},
});
}
export function useImportProfile() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (file: File) => apiClient.importProfile(file),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['profiles'] });
},
});
}