mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-10-03 00:55:14 -07:00
Refactor components and implement generation state management
- Updated import order in App component for consistency. - Enhanced Sidebar component with loading indicator for audio generation state. - Integrated generation state management using Zustand in GenerationForm and Sidebar. - Improved ProfileList component formatting for better readability. - Added new generationStore for managing audio generation state across components.
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import voiceboxLogo from '@/assets/voicebox-logo.png';
|
import voiceboxLogo from '@/assets/voicebox-logo.png';
|
||||||
import { AudioPlayer } from '@/components/AudioPlayer/AudioPlayer';
|
import { AudioPlayer } from '@/components/AudioPlayer/AudioPlayer';
|
||||||
import { GenerationForm } from '@/components/Generation/GenerationForm';
|
import { GenerationForm } from '@/components/Generation/GenerationForm';
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { useToast } from '@/components/ui/use-toast';
|
|||||||
import { apiClient } from '@/lib/api/client';
|
import { apiClient } from '@/lib/api/client';
|
||||||
import { useGeneration } from '@/lib/hooks/useGeneration';
|
import { useGeneration } from '@/lib/hooks/useGeneration';
|
||||||
import { useProfile } from '@/lib/hooks/useProfiles';
|
import { useProfile } from '@/lib/hooks/useProfiles';
|
||||||
|
import { useGenerationStore } from '@/stores/generationStore';
|
||||||
import { usePlayerStore } from '@/stores/playerStore';
|
import { usePlayerStore } from '@/stores/playerStore';
|
||||||
import { useUIStore } from '@/stores/uiStore';
|
import { useUIStore } from '@/stores/uiStore';
|
||||||
|
|
||||||
@@ -45,6 +46,7 @@ export function GenerationForm() {
|
|||||||
const generation = useGeneration();
|
const generation = useGeneration();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const setAudio = usePlayerStore((state) => state.setAudio);
|
const setAudio = usePlayerStore((state) => state.setAudio);
|
||||||
|
const setIsGenerating = useGenerationStore((state) => state.setIsGenerating);
|
||||||
|
|
||||||
const form = useForm<GenerationFormValues>({
|
const form = useForm<GenerationFormValues>({
|
||||||
resolver: zodResolver(generationSchema),
|
resolver: zodResolver(generationSchema),
|
||||||
@@ -68,6 +70,7 @@ export function GenerationForm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
setIsGenerating(true);
|
||||||
const result = await generation.mutateAsync({
|
const result = await generation.mutateAsync({
|
||||||
profile_id: selectedProfileId,
|
profile_id: selectedProfileId,
|
||||||
text: data.text,
|
text: data.text,
|
||||||
@@ -93,6 +96,8 @@ export function GenerationForm() {
|
|||||||
description: error instanceof Error ? error.message : 'Failed to generate audio',
|
description: error instanceof Error ? error.message : 'Failed to generate audio',
|
||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
|
setIsGenerating(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Home, Settings } from 'lucide-react';
|
import { Home, Loader2, Settings } from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils/cn';
|
|
||||||
import voiceboxLogo from '@/assets/voicebox-logo.png';
|
import voiceboxLogo from '@/assets/voicebox-logo.png';
|
||||||
|
import { cn } from '@/lib/utils/cn';
|
||||||
|
import { useGenerationStore } from '@/stores/generationStore';
|
||||||
|
import { usePlayerStore } from '@/stores/playerStore';
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
activeTab: string;
|
activeTab: string;
|
||||||
@@ -13,15 +15,15 @@ const tabs = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export function Sidebar({ activeTab, onTabChange }: SidebarProps) {
|
export function Sidebar({ activeTab, onTabChange }: SidebarProps) {
|
||||||
|
const isGenerating = useGenerationStore((state) => state.isGenerating);
|
||||||
|
const audioUrl = usePlayerStore((state) => state.audioUrl);
|
||||||
|
const isPlayerVisible = !!audioUrl;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed left-0 top-0 h-full w-20 bg-sidebar border-r border-border flex flex-col items-center py-6 gap-6">
|
<div className="fixed left-0 top-0 h-full w-20 bg-sidebar border-r border-border flex flex-col items-center py-6 gap-6">
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<img
|
<img src={voiceboxLogo} alt="Voicebox" className="w-12 h-12 object-contain" />
|
||||||
src={voiceboxLogo}
|
|
||||||
alt="Voicebox"
|
|
||||||
className="w-12 h-12 object-contain"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Navigation Buttons */}
|
{/* Navigation Buttons */}
|
||||||
@@ -48,6 +50,21 @@ export function Sidebar({ activeTab, onTabChange }: SidebarProps) {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Spacer to push loader to bottom */}
|
||||||
|
<div className="flex-1" />
|
||||||
|
|
||||||
|
{/* Generation Loader */}
|
||||||
|
{isGenerating && (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'w-full flex items-center justify-center transition-all duration-200',
|
||||||
|
isPlayerVisible ? 'mb-[120px]' : 'mb-0',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Loader2 className="h-6 w-6 text-accent animate-spin" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { useProfiles, useImportProfile } from '@/lib/hooks/useProfiles';
|
import { useImportProfile, useProfiles } from '@/lib/hooks/useProfiles';
|
||||||
import { useUIStore } from '@/stores/uiStore';
|
import { useUIStore } from '@/stores/uiStore';
|
||||||
import { ProfileCard } from './ProfileCard';
|
import { ProfileCard } from './ProfileCard';
|
||||||
import { ProfileForm } from './ProfileForm';
|
import { ProfileForm } from './ProfileForm';
|
||||||
@@ -128,7 +128,8 @@ export function ProfileList() {
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Import Profile</DialogTitle>
|
<DialogTitle>Import Profile</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Import the profile from "{selectedFile?.name}". This will create a new profile with all samples.
|
Import the profile from "{selectedFile?.name}". This will create a new profile with
|
||||||
|
all samples.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
interface GenerationState {
|
||||||
|
isGenerating: boolean;
|
||||||
|
setIsGenerating: (generating: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useGenerationStore = create<GenerationState>((set) => ({
|
||||||
|
isGenerating: false,
|
||||||
|
setIsGenerating: (generating) => set({ isGenerating: generating }),
|
||||||
|
}));
|
||||||
Binary file not shown.
Reference in New Issue
Block a user