hugeicons

This commit is contained in:
Jamie Pine
2026-01-31 22:18:01 -08:00
parent dcbdf3e89b
commit 6dd5bb2311
42 changed files with 261 additions and 227 deletions
+2 -1
View File
@@ -17,6 +17,8 @@
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@hookform/resolvers": "^3.9.0",
"@hugeicons/core-free-icons": "^3.1.1",
"@hugeicons/react": "^1.1.4",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-dialog": "^1.1.1",
@@ -44,7 +46,6 @@
"clsx": "^2.1.1",
"date-fns": "^3.6.0",
"framer-motion": "^12.29.0",
"lucide-react": "^0.454.0",
"motion": "^12.29.0",
"react": "^18.3.0",
"react-dom": "^18.3.0",
+4 -7
View File
@@ -40,7 +40,7 @@ function App() {
const serverStartingRef = useRef(false);
// Automatically check for app updates on startup and show toast notifications
useAutoUpdater({ checkOnMount: true, showToast: true });
useAutoUpdater(true);
// Sync stored setting to Rust on startup
useEffect(() => {
@@ -82,8 +82,7 @@ function App() {
console.log('Dev mode: Skipping auto-start of server (run it separately)');
setServerReady(true); // Mark as ready so UI doesn't show loading screen
// Mark that server was not started by app (so we don't try to stop it on close)
// @ts-expect-error - adding property to window
window.__voiceboxServerStartedByApp = false;
(window as any).__voiceboxServerStartedByApp = false;
return;
}
@@ -103,14 +102,12 @@ function App() {
useServerStore.getState().setServerUrl(serverUrl);
setServerReady(true);
// Mark that we started the server (so we know to stop it on close)
// @ts-expect-error - adding property to window
window.__voiceboxServerStartedByApp = true;
(window as any).__voiceboxServerStartedByApp = true;
})
.catch((error) => {
console.error('Failed to auto-start server:', error);
serverStartingRef.current = false;
// @ts-expect-error - adding property to window
window.__voiceboxServerStartedByApp = false;
(window as any).__voiceboxServerStartedByApp = false;
});
// Cleanup: stop server on actual unmount (not StrictMode remount)
@@ -1,5 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import { Pause, Play, Repeat, Volume2, VolumeX, X } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { PauseIcon, PlayIcon, RepeatIcon, VolumeHighIcon, VolumeMuteIcon, Cancel01Icon } from '@hugeicons/core-free-icons';
import { useEffect, useMemo, useRef, useState } from 'react';
import WaveSurfer from 'wavesurfer.js';
import { Button } from '@/components/ui/button';
@@ -832,7 +833,7 @@ export function AudioPlayer() {
className="shrink-0"
title={duration === 0 && !isLoading ? 'Audio not loaded' : ''}
>
{isPlaying ? <Pause className="h-5 w-5" /> : <Play className="h-5 w-5" />}
{isPlaying ? <HugeiconsIcon icon={PauseIcon} size={20} className="h-5 w-5" /> : <HugeiconsIcon icon={PlayIcon} size={20} className="h-5 w-5" />}
</Button>
{/* Waveform */}
@@ -873,7 +874,7 @@ export function AudioPlayer() {
className={isLooping ? 'text-primary' : ''}
title="Toggle loop"
>
<Repeat className="h-4 w-4" />
<HugeiconsIcon icon={RepeatIcon} size={16} className="h-4 w-4" />
</Button>
{/* Volume Control */}
@@ -884,7 +885,7 @@ export function AudioPlayer() {
onClick={() => setVolume(volume > 0 ? 0 : 1)}
className="h-8 w-8"
>
{volume > 0 ? <Volume2 className="h-4 w-4" /> : <VolumeX className="h-4 w-4" />}
{volume > 0 ? <HugeiconsIcon icon={VolumeHighIcon} size={16} className="h-4 w-4" /> : <HugeiconsIcon icon={VolumeMuteIcon} size={16} className="h-4 w-4" />}
</Button>
<Slider
value={[volume * 100]}
@@ -903,7 +904,7 @@ export function AudioPlayer() {
className="shrink-0"
title="Close player"
>
<X className="h-5 w-5" />
<HugeiconsIcon icon={Cancel01Icon} size={20} className="h-5 w-5" />
</Button>
</div>
</div>
+14 -13
View File
@@ -1,5 +1,6 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { Check, CheckCircle2, Edit, Plus, Speaker, Trash2 } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { CheckmarkCircle01Icon, CheckmarkCircle02Icon, Edit01Icon, Add01Icon, SpeakerIcon, Delete01Icon } from '@hugeicons/core-free-icons';
import { useState } from 'react';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
@@ -135,7 +136,7 @@ export function AudioTab() {
<div className="flex items-center justify-between mb-6 shrink-0">
<h2 className="text-2xl font-bold">Audio Channels</h2>
<Button onClick={() => setCreateDialogOpen(true)}>
<Plus className="h-4 w-4 mr-2" />
<HugeiconsIcon icon={Add01Icon} size={16} className="h-4 w-4 mr-2" />
New Channel
</Button>
</div>
@@ -150,13 +151,13 @@ export function AudioTab() {
>
{allChannels.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 border-2 border-dashed border-muted rounded-md">
<Speaker className="h-12 w-12 text-muted-foreground mb-4" />
<HugeiconsIcon icon={SpeakerIcon} size={48} className="h-12 w-12 text-muted-foreground mb-4" />
<p className="text-muted-foreground mb-4">
No audio channels yet. Create your first channel to route voices to specific
devices.
</p>
<Button onClick={() => setCreateDialogOpen(true)}>
<Plus className="h-4 w-4 mr-2" />
<HugeiconsIcon icon={Add01Icon} size={16} className="h-4 w-4 mr-2" />
Create Channel
</Button>
</div>
@@ -178,7 +179,7 @@ export function AudioTab() {
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-3">
<div className="h-8 w-8 rounded-lg bg-muted flex items-center justify-center shrink-0">
<Speaker className="h-4 w-4 text-muted-foreground" />
<HugeiconsIcon icon={SpeakerIcon} size={16} className="h-4 w-4 text-muted-foreground" />
</div>
<div className="flex items-center gap-2 min-w-0">
<h3 className="font-semibold text-base truncate">{channel.name}</h3>
@@ -235,7 +236,7 @@ export function AudioTab() {
setEditingChannel(channel.id);
}}
>
<Edit className="h-4 w-4" />
<HugeiconsIcon icon={Edit01Icon} size={16} className="h-4 w-4" />
</Button>
<Button
variant="ghost"
@@ -248,7 +249,7 @@ export function AudioTab() {
}
}}
>
<Trash2 className="h-4 w-4" />
<HugeiconsIcon icon={Delete01Icon} size={16} className="h-4 w-4" />
</Button>
</div>
)}
@@ -325,10 +326,10 @@ export function AudioTab() {
isConnected ? 'bg-accent border-accent' : 'border-muted-foreground/30',
)}
>
{isConnected && <Check className="h-3 w-3 text-accent-foreground" />}
{isConnected && <HugeiconsIcon icon={CheckmarkCircle01Icon} size={12} className="h-3 w-3 text-accent-foreground" />}
</div>
) : device.is_default ? (
<CheckCircle2 className="h-4 w-4 text-primary shrink-0" />
<HugeiconsIcon icon={CheckmarkCircle02Icon} size={16} className="h-4 w-4 text-primary shrink-0" />
) : null}
<span className={cn('truncate flex-1', device.is_default && 'font-medium')}>
{device.name}
@@ -339,7 +340,7 @@ export function AudioTab() {
</div>
) : (
<div className="flex flex-col items-center justify-center py-12 border-2 border-dashed border-muted rounded-md">
<CheckCircle2 className="h-12 w-12 text-muted-foreground mb-4" />
<HugeiconsIcon icon={CheckmarkCircle02Icon} size={48} className="h-12 w-12 text-muted-foreground mb-4" />
<p className="text-muted-foreground text-center">
{platform.metadata.isTauri ? 'No audio devices found' : 'Audio device selection requires Tauri'}
</p>
@@ -494,7 +495,7 @@ function CreateChannelDialog({ open, onOpenChange, devices, onCreate }: CreateCh
setSelectedDevices(selectedDevices.filter((id) => id !== deviceId))
}
>
<Trash2 className="h-3 w-3" />
<HugeiconsIcon icon={Delete01Icon} size={12} className="h-3 w-3" />
</Button>
</div>
);
@@ -602,7 +603,7 @@ function EditChannelDialog({
setSelectedDevices(selectedDevices.filter((id) => id !== deviceId))
}
>
<Trash2 className="h-3 w-3" />
<HugeiconsIcon icon={Delete01Icon} size={12} className="h-3 w-3" />
</Button>
</div>
);
@@ -648,7 +649,7 @@ function EditChannelDialog({
setSelectedVoices(selectedVoices.filter((id) => id !== profileId))
}
>
<Trash2 className="h-3 w-3" />
<HugeiconsIcon icon={Delete01Icon} size={12} className="h-3 w-3" />
</Button>
</div>
);
@@ -1,6 +1,7 @@
import { useMatchRoute } from '@tanstack/react-router';
import { AnimatePresence, motion } from 'framer-motion';
import { Loader2, MessageSquare, Sparkles } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Loading01Icon, TextSquareIcon, SparklesIcon } from '@hugeicons/core-free-icons';
import { useEffect, useRef, useState } from 'react';
import { Button } from '@/components/ui/button';
import { Form, FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form';
@@ -301,9 +302,9 @@ export function FloatingGenerateBox({
size="icon"
>
{isPending ? (
<Loader2 className="h-4 w-4 animate-spin" />
<HugeiconsIcon icon={Loading01Icon} size={16} className="h-4 w-4 animate-spin" />
) : (
<Sparkles className="h-4 w-4" />
<HugeiconsIcon icon={SparklesIcon} size={16} className="h-4 w-4" />
)}
</Button>
<AnimatePresence>
@@ -327,7 +328,7 @@ export function FloatingGenerateBox({
: 'bg-card border border-border hover:bg-background/50',
)}
>
<MessageSquare className="h-4 w-4" />
<HugeiconsIcon icon={TextSquareIcon} size={16} className="h-4 w-4" />
</Button>
</motion.div>
)}
@@ -1,4 +1,5 @@
import { Loader2, Mic } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Loading01Icon, Mic01Icon } from '@hugeicons/core-free-icons';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import {
@@ -46,7 +47,7 @@ export function GenerationForm() {
<FormLabel>Voice Profile</FormLabel>
{selectedProfile ? (
<div className="mt-2 p-3 border rounded-md bg-muted/50 flex items-center gap-2">
<Mic className="h-4 w-4 text-muted-foreground" />
<HugeiconsIcon icon={Mic01Icon} size={16} className="h-4 w-4 text-muted-foreground" />
<span className="font-medium">{selectedProfile.name}</span>
<span className="text-sm text-muted-foreground">{selectedProfile.language}</span>
</div>
@@ -177,7 +178,7 @@ export function GenerationForm() {
>
{isPending ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<HugeiconsIcon icon={Loading01Icon} size={16} className="mr-2 h-4 w-4 animate-spin" />
Generating...
</>
) : (
+17 -16
View File
@@ -1,12 +1,13 @@
import { HugeiconsIcon } from '@hugeicons/react';
import {
AudioWaveform,
Download,
FileArchive,
Loader2,
MoreHorizontal,
Play,
Trash2,
} from 'lucide-react';
WaveIcon,
Download01Icon,
Archive01Icon,
Loading01Icon,
MoreHorizontalIcon,
PlayIcon,
Delete01Icon,
} from '@hugeicons/core-free-icons';
import { useEffect, useRef, useState } from 'react';
import { Button } from '@/components/ui/button';
import {
@@ -222,7 +223,7 @@ export function HistoryTable() {
if (isLoading && page === 0) {
return (
<div className="flex items-center justify-center h-full">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
<HugeiconsIcon icon={Loading01Icon} size={32} className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
);
}
@@ -268,7 +269,7 @@ export function HistoryTable() {
>
{/* Waveform icon */}
<div className="flex items-center shrink-0">
<AudioWaveform className="h-5 w-5 text-muted-foreground" />
<HugeiconsIcon icon={WaveIcon} size={20} className="h-5 w-5 text-muted-foreground" />
</div>
{/* Left side - Meta information */}
@@ -310,28 +311,28 @@ export function HistoryTable() {
className="h-8 w-8"
aria-label="Actions"
>
<MoreHorizontal className="h-4 w-4" />
<HugeiconsIcon icon={MoreHorizontalIcon} size={16} className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => handlePlay(gen.id, gen.text, gen.profile_id)}
>
<Play className="mr-2 h-4 w-4" />
<HugeiconsIcon icon={PlayIcon} size={16} className="mr-2 h-4 w-4" />
Play
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleDownloadAudio(gen.id, gen.text)}
disabled={exportGenerationAudio.isPending}
>
<Download className="mr-2 h-4 w-4" />
<HugeiconsIcon icon={Download01Icon} size={16} className="mr-2 h-4 w-4" />
Export Audio
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleExportPackage(gen.id, gen.text)}
disabled={exportGeneration.isPending}
>
<FileArchive className="mr-2 h-4 w-4" />
<HugeiconsIcon icon={Archive01Icon} size={16} className="mr-2 h-4 w-4" />
Export Package
</DropdownMenuItem>
<DropdownMenuItem
@@ -339,7 +340,7 @@ export function HistoryTable() {
disabled={deleteGeneration.isPending}
className="text-destructive focus:text-destructive"
>
<Trash2 className="mr-2 h-4 w-4" />
<HugeiconsIcon icon={Delete01Icon} size={16} className="mr-2 h-4 w-4" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
@@ -352,7 +353,7 @@ export function HistoryTable() {
{/* Load more trigger element */}
{hasMore && (
<div ref={loadMoreRef} className="flex items-center justify-center py-4">
{isFetching && <Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />}
{isFetching && <HugeiconsIcon icon={Loading01Icon} size={24} className="h-6 w-6 animate-spin text-muted-foreground" />}
</div>
)}
+4 -3
View File
@@ -1,4 +1,5 @@
import { Sparkles, Upload } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { SparklesIcon, Upload01Icon } from '@hugeicons/core-free-icons';
import { useRef, useState } from 'react';
import { FloatingGenerateBox } from '@/components/Generation/FloatingGenerateBox';
import { HistoryTable } from '@/components/History/HistoryTable';
@@ -89,7 +90,7 @@ export function MainEditor() {
<h2 className="text-2xl font-bold">Voicebox</h2>
<div className="flex gap-2">
<Button variant="outline" onClick={handleImportClick}>
<Upload className="mr-2 h-4 w-4" />
<HugeiconsIcon icon={Upload01Icon} size={16} className="mr-2 h-4 w-4" />
Import Voice
</Button>
<input
@@ -100,7 +101,7 @@ export function MainEditor() {
className="hidden"
/>
<Button onClick={() => setDialogOpen(true)}>
<Sparkles className="mr-2 h-4 w-4" />
<HugeiconsIcon icon={SparklesIcon} size={16} className="mr-2 h-4 w-4" />
Create Voice
</Button>
</div>
@@ -1,5 +1,6 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { Download, Loader2, Trash2 } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Download01Icon, Loading01Icon, Delete01Icon } from '@hugeicons/core-free-icons';
import { useCallback, useState } from 'react';
import {
AlertDialog,
@@ -67,11 +68,11 @@ export function ModelManagement() {
const handleDownload = async (modelName: string) => {
console.log('[Download] Button clicked for:', modelName, 'at', new Date().toISOString());
// Find display name
const model = modelStatus?.models.find((m) => m.model_name === modelName);
const displayName = model?.display_name || modelName;
try {
// IMPORTANT: Call the API FIRST before setting state
// Setting state enables the SSE EventSource in useModelDownloadToast,
@@ -79,11 +80,11 @@ export function ModelManagement() {
console.log('[Download] Calling download API for:', modelName);
const result = await apiClient.triggerModelDownload(modelName);
console.log('[Download] Download API responded:', result);
// NOW set state to enable SSE tracking (after download has started on backend)
setDownloadingModel(modelName);
setDownloadingDisplayName(displayName);
// Download initiated successfully - state will be cleared when SSE reports completion
// or by the polling interval detecting the model is downloaded
queryClient.invalidateQueries({ queryKey: ['modelStatus'] });
@@ -117,7 +118,7 @@ export function ModelManagement() {
// Invalidate AND explicitly refetch to ensure UI updates
// Using refetchType: 'all' ensures we refetch even if the query is stale
console.log('[Delete] Invalidating modelStatus query');
await queryClient.invalidateQueries({
await queryClient.invalidateQueries({
queryKey: ['modelStatus'],
refetchType: 'all',
});
@@ -153,7 +154,7 @@ export function ModelManagement() {
<CardContent className="space-y-4">
{isLoading ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
<HugeiconsIcon icon={Loading01Icon} size={24} className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
) : modelStatus ? (
<div className="space-y-4">
@@ -212,7 +213,6 @@ export function ModelManagement() {
))}
</div>
</div>
</div>
) : null}
</CardContent>
@@ -246,7 +246,7 @@ export function ModelManagement() {
>
{deleteMutation.isPending ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
<HugeiconsIcon icon={Loading01Icon} size={16} className="h-4 w-4 mr-2 animate-spin" />
Deleting...
</>
) : (
@@ -265,20 +265,20 @@ interface ModelItemProps {
model_name: string;
display_name: string;
downloaded: boolean;
downloading?: boolean; // From server - true if download in progress
downloading?: boolean; // From server - true if download in progress
size_mb?: number;
loaded: boolean;
};
onDownload: () => void;
onDelete: () => void;
isDownloading: boolean; // Local state - true if user just clicked download
isDownloading: boolean; // Local state - true if user just clicked download
formatSize: (sizeMb?: number) => string;
}
function ModelItem({ model, onDownload, onDelete, isDownloading, formatSize }: ModelItemProps) {
// Use server's downloading state OR local state (for immediate feedback before server updates)
const showDownloading = model.downloading || isDownloading;
return (
<div className="flex items-center justify-between p-3 border rounded-lg">
<div className="flex-1">
@@ -315,17 +315,17 @@ function ModelItem({ model, onDownload, onDelete, isDownloading, formatSize }: M
disabled={model.loaded}
title={model.loaded ? 'Unload model before deleting' : 'Delete model'}
>
<Trash2 className="h-4 w-4" />
<HugeiconsIcon icon={Delete01Icon} size={16} className="h-4 w-4" />
</Button>
</div>
) : showDownloading ? (
<Button size="sm" variant="outline" disabled>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
<HugeiconsIcon icon={Loading01Icon} size={16} className="h-4 w-4 mr-2 animate-spin" />
Downloading...
</Button>
) : (
<Button size="sm" onClick={onDownload} variant="outline">
<Download className="h-4 w-4 mr-2" />
<HugeiconsIcon icon={Download01Icon} size={16} className="h-4 w-4 mr-2" />
Download
</Button>
)}
@@ -1,4 +1,5 @@
import { Loader2, XCircle } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Loading01Icon, CancelCircleIcon } from '@hugeicons/core-free-icons';
import { useEffect, useState } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Progress } from '@/components/ui/progress';
@@ -74,10 +75,10 @@ export function ModelProgress({ modelName, displayName, isDownloading = false }:
const getStatusIcon = () => {
switch (progress.status) {
case 'error':
return <XCircle className="h-4 w-4 text-destructive" />;
return <HugeiconsIcon icon={CancelCircleIcon} size={16} className="h-4 w-4 text-destructive" />;
case 'downloading':
case 'extracting':
return <Loader2 className="h-4 w-4 animate-spin" />;
return <HugeiconsIcon icon={Loading01Icon} size={16} className="h-4 w-4 animate-spin" />;
default:
return null;
}
@@ -1,5 +1,6 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { Download, Loader2, Trash2 } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Download01Icon, Loading01Icon, Delete01Icon } from '@hugeicons/core-free-icons';
import { useCallback, useState } from 'react';
import {
AlertDialog,
@@ -155,7 +156,7 @@ export function ProviderSettings() {
</CardHeader>
<CardContent>
<div className="flex items-center justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin" />
<HugeiconsIcon icon={Loading01Icon} size={24} className="h-6 w-6 animate-spin" />
</div>
</CardContent>
</Card>
@@ -217,10 +218,10 @@ export function ProviderSettings() {
disabled={downloadingProvider === 'pytorch-cuda'}
>
{downloadingProvider === 'pytorch-cuda' ? (
<Loader2 className="h-4 w-4 animate-spin" />
<HugeiconsIcon icon={Loading01Icon} size={16} className="h-4 w-4 animate-spin" />
) : (
<>
<Download className="h-4 w-4 mr-1" />
<HugeiconsIcon icon={Download01Icon} size={16} className="h-4 w-4 mr-1" />
Download (2.4GB)
</>
)}
@@ -241,7 +242,7 @@ export function ProviderSettings() {
size="sm"
variant="ghost"
>
<Trash2 className="h-4 w-4" />
<HugeiconsIcon icon={Delete01Icon} size={16} className="h-4 w-4" />
</Button>
)}
</div>
@@ -270,10 +271,10 @@ export function ProviderSettings() {
disabled={downloadingProvider === 'pytorch-cpu'}
>
{downloadingProvider === 'pytorch-cpu' ? (
<Loader2 className="h-4 w-4 animate-spin" />
<HugeiconsIcon icon={Loading01Icon} size={16} className="h-4 w-4 animate-spin" />
) : (
<>
<Download className="h-4 w-4 mr-1" />
<HugeiconsIcon icon={Download01Icon} size={16} className="h-4 w-4 mr-1" />
Download (300MB)
</>
)}
@@ -294,7 +295,7 @@ export function ProviderSettings() {
size="sm"
variant="ghost"
>
<Trash2 className="h-4 w-4" />
<HugeiconsIcon icon={Delete01Icon} size={16} className="h-4 w-4" />
</Button>
)}
</div>
@@ -1,4 +1,5 @@
import { Loader2, XCircle } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Loading01Icon, CancelCircleIcon } from '@hugeicons/core-free-icons';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { useServerHealth } from '@/lib/hooks/useServer';
@@ -32,12 +33,12 @@ export function ServerStatus() {
{isLoading ? (
<div className="flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin" />
<HugeiconsIcon icon={Loading01Icon} size={16} className="h-4 w-4 animate-spin" />
<span className="text-sm">Checking connection...</span>
</div>
) : error ? (
<div className="flex items-center gap-2">
<XCircle className="h-4 w-4 text-destructive" />
<HugeiconsIcon icon={CancelCircleIcon} size={16} className="h-4 w-4 text-destructive" />
<span className="text-sm text-destructive">Connection failed: {error.message}</span>
</div>
) : health ? (
@@ -1,4 +1,5 @@
import { AlertCircle, Download, RefreshCw } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { AlertCircleIcon, Download01Icon, Refresh01Icon } from '@hugeicons/core-free-icons';
import { useEffect, useState } from 'react';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
@@ -36,21 +37,21 @@ export function UpdateStatus() {
variant="outline"
size="sm"
>
<RefreshCw className={`h-4 w-4 mr-2 ${status.checking ? 'animate-spin' : ''}`} />
<HugeiconsIcon icon={Refresh01Icon} size={16} className={`h-4 w-4 mr-2 ${status.checking ? 'animate-spin' : ''}`} />
Check for Updates
</Button>
</div>
{status.checking && (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<RefreshCw className="h-4 w-4 animate-spin" />
<HugeiconsIcon icon={Refresh01Icon} size={16} className="h-4 w-4 animate-spin" />
Checking for updates...
</div>
)}
{status.error && (
<div className="flex items-center gap-2 text-sm text-destructive">
<AlertCircle className="h-4 w-4" />
<HugeiconsIcon icon={AlertCircleIcon} size={16} className="h-4 w-4" />
{status.error}
</div>
)}
@@ -65,7 +66,7 @@ export function UpdateStatus() {
<Badge>New</Badge>
</div>
<Button onClick={downloadAndInstall} className="w-full" size="sm">
<Download className="h-4 w-4 mr-2" />
<HugeiconsIcon icon={Download01Icon} size={16} className="h-4 w-4 mr-2" />
Download Update
</Button>
</div>
@@ -75,7 +76,7 @@ export function UpdateStatus() {
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<div className="flex items-center gap-2">
<Download className="h-4 w-4" />
<HugeiconsIcon icon={Download01Icon} size={16} className="h-4 w-4" />
Downloading update...
</div>
{status.downloadProgress !== undefined && (
@@ -109,7 +110,7 @@ export function UpdateStatus() {
your convenience.
</div>
<Button onClick={restartAndInstall} className="w-full" size="sm">
<RefreshCw className="h-4 w-4 mr-2" />
<HugeiconsIcon icon={Refresh01Icon} size={16} className="h-4 w-4 mr-2" />
Restart Now
</Button>
</div>
+11 -10
View File
@@ -1,5 +1,6 @@
import { Link, useMatchRoute } from '@tanstack/react-router';
import { Box, BookOpen, Loader2, Mic, Server, Speaker, Volume2 } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { PackageIcon, Book01Icon, Loading01Icon, Mic01Icon, McpServerIcon, SpeakerIcon, VolumeHighIcon } from '@hugeicons/core-free-icons';
import voiceboxLogo from '@/assets/voicebox-logo.png';
import { cn } from '@/lib/utils/cn';
import { useGenerationStore } from '@/stores/generationStore';
@@ -10,12 +11,12 @@ interface SidebarProps {
}
const tabs = [
{ id: 'main', path: '/', icon: Volume2, label: 'Generate' },
{ id: 'stories', path: '/stories', icon: BookOpen, label: 'Stories' },
{ id: 'voices', path: '/voices', icon: Mic, label: 'Voices' },
{ id: 'audio', path: '/audio', icon: Speaker, label: 'Audio' },
{ id: 'models', path: '/models', icon: Box, label: 'Models' },
{ id: 'server', path: '/server', icon: Server, label: 'Server' },
{ id: 'main', path: '/', icon: VolumeHighIcon, label: 'Generate' },
{ id: 'stories', path: '/stories', icon: Book01Icon, label: 'Stories' },
{ id: 'voices', path: '/voices', icon: Mic01Icon, label: 'Voices' },
{ id: 'audio', path: '/audio', icon: SpeakerIcon, label: 'Audio' },
{ id: 'models', path: '/models', icon: PackageIcon, label: 'Models' },
{ id: 'server', path: '/server', icon: McpServerIcon, label: 'Server' },
];
export function Sidebar({ isMacOS }: SidebarProps) {
@@ -43,7 +44,7 @@ export function Sidebar({ isMacOS }: SidebarProps) {
// For index route, use exact match; for others, use default matching
const isActive =
tab.path === '/'
? matchRoute({ to: '/', exact: true })
? matchRoute({ to: '/' })
: matchRoute({ to: tab.path });
return (
@@ -58,7 +59,7 @@ export function Sidebar({ isMacOS }: SidebarProps) {
title={tab.label}
aria-label={tab.label}
>
<Icon className="h-5 w-5" />
<HugeiconsIcon icon={Icon} size={20} className="h-5 w-5" />
</Link>
);
})}
@@ -75,7 +76,7 @@ export function Sidebar({ isMacOS }: SidebarProps) {
isPlayerVisible ? 'mb-[120px]' : 'mb-0',
)}
>
<Loader2 className="h-6 w-6 text-accent animate-spin" />
<HugeiconsIcon icon={Loading01Icon} size={24} className="h-6 w-6 text-accent animate-spin" />
</div>
)}
</div>
@@ -1,6 +1,7 @@
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { GripVertical, Mic, MoreHorizontal, Play, Trash2 } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { DragDropVerticalIcon, Mic01Icon, MoreHorizontalIcon, PlayIcon, Delete01Icon } from '@hugeicons/core-free-icons';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import {
@@ -74,7 +75,7 @@ export function StoryChatItem({
className="shrink-0 cursor-grab active:cursor-grabbing touch-none text-muted-foreground hover:text-foreground transition-colors"
{...dragHandleProps}
>
<GripVertical className="h-5 w-5" />
<HugeiconsIcon icon={DragDropVerticalIcon} size={20} className="h-5 w-5" />
</button>
)}
@@ -92,7 +93,7 @@ export function StoryChatItem({
onError={() => setAvatarError(true)}
/>
) : (
<Mic className="h-5 w-5 text-muted-foreground" />
<HugeiconsIcon icon={Mic01Icon} size={20} className="h-5 w-5 text-muted-foreground" />
)}
</div>
</div>
@@ -119,16 +120,16 @@ export function StoryChatItem({
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8" aria-label="Actions">
<MoreHorizontal className="h-4 w-4" />
<HugeiconsIcon icon={MoreHorizontalIcon} size={16} className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={handlePlay}>
<Play className="mr-2 h-4 w-4" />
<HugeiconsIcon icon={PlayIcon} size={16} className="mr-2 h-4 w-4" />
Play from here
</DropdownMenuItem>
<DropdownMenuItem onClick={onRemove} className="text-destructive focus:text-destructive">
<Trash2 className="mr-2 h-4 w-4" />
<HugeiconsIcon icon={Delete01Icon} size={16} className="mr-2 h-4 w-4" />
Remove from Story
</DropdownMenuItem>
</DropdownMenuContent>
@@ -13,7 +13,8 @@ import {
sortableKeyboardCoordinates,
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { Download, Plus } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Download01Icon, Add01Icon } from '@hugeicons/core-free-icons';
import { useEffect, useMemo, useRef, useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@@ -271,7 +272,7 @@ export function StoryContent() {
<Popover open={isAddOpen} onOpenChange={setIsAddOpen}>
<PopoverTrigger asChild>
<Button variant="outline" size="sm">
<Plus className="mr-2 h-4 w-4" />
<HugeiconsIcon icon={Add01Icon} size={16} className="mr-2 h-4 w-4" />
Add
</Button>
</PopoverTrigger>
@@ -316,7 +317,7 @@ export function StoryContent() {
onClick={handleExportAudio}
disabled={exportAudio.isPending}
>
<Download className="mr-2 h-4 w-4" />
<HugeiconsIcon icon={Download01Icon} size={16} className="mr-2 h-4 w-4" />
Export Audio
</Button>
)}
+7 -6
View File
@@ -1,4 +1,5 @@
import { Plus, BookOpen, MoreHorizontal, Pencil, Trash2 } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Add01Icon, Book01Icon, MoreHorizontalIcon, PencilIcon, Delete01Icon } from '@hugeicons/core-free-icons';
import { useState } from 'react';
import {
AlertDialog,
@@ -177,7 +178,7 @@ export function StoryList() {
<div className="flex items-center justify-between mb-4 px-1">
<h2 className="text-2xl font-bold">Stories</h2>
<Button onClick={() => setCreateDialogOpen(true)} size="sm">
<Plus className="mr-2 h-4 w-4" />
<HugeiconsIcon icon={Add01Icon} size={16} className="mr-2 h-4 w-4" />
New Story
</Button>
</div>
@@ -186,7 +187,7 @@ export function StoryList() {
<div className="flex-1 min-h-0 overflow-y-auto space-y-2">
{storyList.length === 0 ? (
<div className="text-center py-12 px-5 border-2 border-dashed border-muted rounded-2xl text-muted-foreground">
<BookOpen className="h-12 w-12 mx-auto mb-4 opacity-50" />
<HugeiconsIcon icon={Book01Icon} size={48} className="h-12 w-12 mx-auto mb-4 opacity-50" />
<p className="text-sm">No stories yet</p>
<p className="text-xs mt-2">Create your first story to get started</p>
</div>
@@ -227,19 +228,19 @@ export function StoryList() {
className="h-8 w-8 opacity-0 group-hover:opacity-100 transition-opacity"
onClick={(e) => e.stopPropagation()}
>
<MoreHorizontal className="h-4 w-4" />
<HugeiconsIcon icon={MoreHorizontalIcon} size={16} className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => handleEditClick(story)}>
<Pencil className="mr-2 h-4 w-4" />
<HugeiconsIcon icon={PencilIcon} size={16} className="mr-2 h-4 w-4" />
Edit
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleDeleteClick(story.id)}
className="text-destructive focus:text-destructive"
>
<Trash2 className="mr-2 h-4 w-4" />
<HugeiconsIcon icon={Delete01Icon} size={16} className="mr-2 h-4 w-4" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
@@ -1,14 +1,15 @@
import { HugeiconsIcon } from '@hugeicons/react';
import {
Copy,
GripHorizontal,
Minus,
Pause,
Play,
Plus,
Scissors,
Square,
Trash2,
} from 'lucide-react';
Copy01Icon,
DragDropHorizontalIcon,
RemoveIcon,
PauseIcon,
PlayIcon,
Add01Icon,
Scissor01Icon,
SquareIcon,
Delete01Icon,
} from '@hugeicons/core-free-icons';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import WaveSurfer from 'wavesurfer.js';
import { Button } from '@/components/ui/button';
@@ -723,7 +724,7 @@ export function StoryTrackEditor({ storyId, items }: StoryTrackEditorProps) {
onMouseDown={handleResizeStart}
aria-label="Resize track editor"
>
<GripHorizontal className="h-3 w-3 text-muted-foreground/50 group-hover:text-muted-foreground" />
<HugeiconsIcon icon={DragDropHorizontalIcon} size={12} className="h-3 w-3 text-muted-foreground/50 group-hover:text-muted-foreground" />
</button>
{/* Toolbar */}
@@ -737,7 +738,7 @@ export function StoryTrackEditor({ storyId, items }: StoryTrackEditorProps) {
onClick={handlePlayPause}
title="Play/Pause (Space)"
>
{isCurrentlyPlaying ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />}
{isCurrentlyPlaying ? <HugeiconsIcon icon={PauseIcon} size={16} className="h-4 w-4" /> : <HugeiconsIcon icon={PlayIcon} size={16} className="h-4 w-4" />}
</Button>
<Button
variant="ghost"
@@ -746,7 +747,7 @@ export function StoryTrackEditor({ storyId, items }: StoryTrackEditorProps) {
onClick={handleStop}
disabled={!isCurrentlyPlaying}
>
<Square className="h-3 w-3" />
<HugeiconsIcon icon={SquareIcon} size={12} className="h-3 w-3" />
</Button>
<span className="text-xs text-muted-foreground tabular-nums ml-2">
{formatTime(currentTimeMs)} / {formatTime(totalDurationMs)}
@@ -763,7 +764,7 @@ export function StoryTrackEditor({ storyId, items }: StoryTrackEditorProps) {
onClick={handleSplit}
title="Split at playhead (S)"
>
<Scissors className="h-4 w-4" />
<HugeiconsIcon icon={Scissor01Icon} size={16} className="h-4 w-4" />
</Button>
<Button
variant="ghost"
@@ -772,7 +773,7 @@ export function StoryTrackEditor({ storyId, items }: StoryTrackEditorProps) {
onClick={handleDuplicate}
title="Duplicate (Cmd/Ctrl+D)"
>
<Copy className="h-4 w-4" />
<HugeiconsIcon icon={Copy01Icon} size={16} className="h-4 w-4" />
</Button>
<Button
variant="ghost"
@@ -781,7 +782,7 @@ export function StoryTrackEditor({ storyId, items }: StoryTrackEditorProps) {
onClick={handleDelete}
title="Delete (Delete/Backspace)"
>
<Trash2 className="h-4 w-4" />
<HugeiconsIcon icon={Delete01Icon} size={16} className="h-4 w-4" />
</Button>
</div>
)}
@@ -790,10 +791,10 @@ export function StoryTrackEditor({ storyId, items }: StoryTrackEditorProps) {
<div className="flex items-center gap-2">
<span className="text-xs text-muted-foreground">Zoom:</span>
<Button variant="ghost" size="icon" className="h-6 w-6" onClick={handleZoomOut}>
<Minus className="h-3 w-3" />
<HugeiconsIcon icon={RemoveIcon} size={12} className="h-3 w-3" />
</Button>
<Button variant="ghost" size="icon" className="h-6 w-6" onClick={handleZoomIn}>
<Plus className="h-3 w-3" />
<HugeiconsIcon icon={Add01Icon} size={12} className="h-3 w-3" />
</Button>
</div>
</div>
@@ -837,7 +838,7 @@ export function StoryTrackEditor({ storyId, items }: StoryTrackEditorProps) {
type="button"
className="h-6 border-b bg-muted/20 sticky top-0 z-10 cursor-pointer text-left"
style={{ width: `${timelineWidth}px` }}
onClick={handleTimelineClick}
onClick={(e) => handleTimelineClick(e as unknown as React.MouseEvent<HTMLDivElement>)}
aria-label="Seek timeline"
>
{timeMarkers.map((ms) => (
@@ -878,7 +879,7 @@ export function StoryTrackEditor({ storyId, items }: StoryTrackEditorProps) {
<button
type="button"
className="absolute inset-0 z-0 cursor-pointer"
onClick={handleTimelineClick}
onClick={(e) => handleTimelineClick(e as unknown as React.MouseEvent<HTMLDivElement>)}
aria-label="Seek timeline"
/>
@@ -1,4 +1,5 @@
import { Mic, Pause, Play, Square } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Mic01Icon, PauseIcon, PlayIcon, SquareIcon } from '@hugeicons/core-free-icons';
import { memo, useEffect, useState } from 'react';
import { Visualizer } from 'react-sound-visualizer';
import { Button } from '@/components/ui/button';
@@ -95,7 +96,7 @@ export function AudioSampleRecording({
size="lg"
className="relative z-10 flex items-center gap-2"
>
<Mic className="h-5 w-5" />
<HugeiconsIcon icon={Mic01Icon} size={20} className="h-5 w-5" />
Start Recording
</Button>
<p className="relative z-10 text-sm text-muted-foreground text-center">
@@ -122,7 +123,7 @@ export function AudioSampleRecording({
onClick={onStop}
className="relative z-10 flex items-center gap-2 bg-accent text-accent-foreground hover:bg-accent/90"
>
<Square className="h-4 w-4" />
<HugeiconsIcon icon={SquareIcon} size={16} className="h-4 w-4" />
Stop Recording
</Button>
<p className="relative z-10 text-sm text-muted-foreground text-center">
@@ -134,13 +135,13 @@ export function AudioSampleRecording({
{file && !isRecording && (
<div className="flex flex-col items-center justify-center gap-4 p-4 border-2 border-primary rounded-lg bg-primary/5 min-h-[180px]">
<div className="flex items-center gap-2">
<Mic className="h-5 w-5 text-primary" />
<HugeiconsIcon icon={Mic01Icon} size={20} className="h-5 w-5 text-primary" />
<span className="font-medium">Recording complete</span>
</div>
<p className="text-sm text-muted-foreground text-center">File: {file.name}</p>
<div className="flex gap-2">
<Button type="button" size="icon" variant="outline" onClick={onPlayPause}>
{isPlaying ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />}
{isPlaying ? <HugeiconsIcon icon={PauseIcon} size={16} className="h-4 w-4" /> : <HugeiconsIcon icon={PlayIcon} size={16} className="h-4 w-4" />}
</Button>
<Button
type="button"
@@ -149,7 +150,7 @@ export function AudioSampleRecording({
disabled={isTranscribing}
className="flex items-center gap-2"
>
<Mic className="h-4 w-4" />
<HugeiconsIcon icon={Mic01Icon} size={16} className="h-4 w-4" />
{isTranscribing ? 'Transcribing...' : 'Transcribe'}
</Button>
<Button
@@ -1,4 +1,5 @@
import { Mic, Monitor, Pause, Play, Square } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Mic01Icon, DeskIcon, PauseIcon, PlayIcon, SquareIcon } from '@hugeicons/core-free-icons';
import { Button } from '@/components/ui/button';
import { FormControl, FormItem, FormMessage } from '@/components/ui/form';
import { formatAudioDuration } from '@/lib/utils/audio';
@@ -35,7 +36,7 @@ export function AudioSampleSystem({
{!isRecording && !file && (
<div className="flex flex-col items-center justify-center gap-4 p-4 border-2 border-dashed rounded-lg min-h-[180px]">
<Button type="button" onClick={onStart} size="lg" className="flex items-center gap-2">
<Monitor className="h-5 w-5" />
<HugeiconsIcon icon={DeskIcon} size={20} className="h-5 w-5" />
Start Capture
</Button>
<p className="text-sm text-muted-foreground text-center">
@@ -60,7 +61,7 @@ export function AudioSampleSystem({
variant="destructive"
className="flex items-center gap-2"
>
<Square className="h-4 w-4" />
<HugeiconsIcon icon={SquareIcon} size={16} className="h-4 w-4" />
Stop Capture
</Button>
<p className="text-sm text-muted-foreground text-center">
@@ -72,13 +73,13 @@ export function AudioSampleSystem({
{file && !isRecording && (
<div className="flex flex-col items-center justify-center gap-4 p-4 border-2 border-primary rounded-lg bg-primary/5 min-h-[180px]">
<div className="flex items-center gap-2">
<Monitor className="h-5 w-5 text-primary" />
<HugeiconsIcon icon={DeskIcon} size={20} className="h-5 w-5 text-primary" />
<span className="font-medium">Capture complete</span>
</div>
<p className="text-sm text-muted-foreground text-center">File: {file.name}</p>
<div className="flex gap-2">
<Button type="button" size="icon" variant="outline" onClick={onPlayPause}>
{isPlaying ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />}
{isPlaying ? <HugeiconsIcon icon={PauseIcon} size={16} className="h-4 w-4" /> : <HugeiconsIcon icon={PlayIcon} size={16} className="h-4 w-4" />}
</Button>
<Button
type="button"
@@ -87,7 +88,7 @@ export function AudioSampleSystem({
disabled={isTranscribing}
className="flex items-center gap-2"
>
<Mic className="h-4 w-4" />
<HugeiconsIcon icon={Mic01Icon} size={16} className="h-4 w-4" />
{isTranscribing ? 'Transcribing...' : 'Transcribe'}
</Button>
<Button
@@ -1,4 +1,5 @@
import { Mic, Pause, Play, Upload } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Mic01Icon, PauseIcon, PlayIcon, Upload01Icon } from '@hugeicons/core-free-icons';
import { useRef, useState } from 'react';
import { Button } from '@/components/ui/button';
import { FormControl, FormItem, FormMessage } from '@/components/ui/form';
@@ -89,7 +90,7 @@ export function AudioSampleUpload({
onClick={() => fileInputRef.current?.click()}
className="flex items-center gap-2"
>
<Upload className="h-5 w-5" />
<HugeiconsIcon icon={Upload01Icon} size={20} className="h-5 w-5" />
Choose File
</Button>
<p className="text-sm text-muted-foreground text-center">
@@ -99,7 +100,7 @@ export function AudioSampleUpload({
) : (
<>
<div className="flex items-center gap-2">
<Upload className="h-5 w-5 text-primary" />
<HugeiconsIcon icon={Upload01Icon} size={20} className="h-5 w-5 text-primary" />
<span className="font-medium">File uploaded</span>
</div>
<p className="text-sm text-muted-foreground text-center">File: {file.name}</p>
@@ -111,7 +112,7 @@ export function AudioSampleUpload({
onClick={onPlayPause}
disabled={isValidating}
>
{isPlaying ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />}
{isPlaying ? <HugeiconsIcon icon={PauseIcon} size={16} className="h-4 w-4" /> : <HugeiconsIcon icon={PlayIcon} size={16} className="h-4 w-4" />}
</Button>
<Button
type="button"
@@ -120,7 +121,7 @@ export function AudioSampleUpload({
disabled={isTranscribing || isValidating || isDisabled}
className="flex items-center gap-2"
>
<Mic className="h-4 w-4" />
<HugeiconsIcon icon={Mic01Icon} size={16} className="h-4 w-4" />
{isTranscribing ? 'Transcribing...' : 'Transcribe'}
</Button>
<Button
@@ -1,4 +1,5 @@
import { Download, Edit, Mic, Trash2 } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Download01Icon, Edit01Icon, Mic01Icon, Delete01Icon } from '@hugeicons/core-free-icons';
import { useState } from 'react';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
@@ -84,7 +85,7 @@ export function ProfileCard({ profile }: ProfileCardProps) {
onError={() => setAvatarError(true)}
/>
) : (
<Mic className="h-3.5 w-3.5 text-muted-foreground" />
<HugeiconsIcon icon={Mic01Icon} size={14} className="h-3.5 w-3.5 text-muted-foreground" />
)}
</div>
<span className="break-words">{profile.name}</span>
@@ -101,13 +102,13 @@ export function ProfileCard({ profile }: ProfileCardProps) {
</div>
<div className="flex gap-0.5 justify-end items-end mt-auto">
<CircleButton
icon={Download}
icon={(props) => <HugeiconsIcon icon={Download01Icon} size={14} {...props} />}
onClick={handleExport}
disabled={exportProfile.isPending}
aria-label="Export profile"
/>
<CircleButton
icon={Edit}
icon={(props) => <HugeiconsIcon icon={Edit01Icon} size={14} {...props} />}
onClick={(e) => {
e.stopPropagation();
handleEdit();
@@ -115,7 +116,7 @@ export function ProfileCard({ profile }: ProfileCardProps) {
aria-label="Edit profile"
/>
<CircleButton
icon={Trash2}
icon={(props) => <HugeiconsIcon icon={Delete01Icon} size={14} {...props} />}
onClick={handleDeleteClick}
disabled={deleteProfile.isPending}
aria-label="Delete profile"
@@ -1,5 +1,6 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { Edit2, Mic, Monitor, Upload, X } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Edit02Icon, Mic01Icon, DeskIcon, Upload01Icon, Cancel01Icon } from '@hugeicons/core-free-icons';
import { useEffect, useRef, useState } from 'react';
import { useForm } from 'react-hook-form';
import * as z from 'zod';
@@ -635,7 +636,7 @@ export function ProfileForm() {
setSampleMode('record');
}}
>
<X className="h-3 w-3 mr-1" />
<HugeiconsIcon icon={Cancel01Icon} size={12} className="h-3 w-3 mr-1" />
Discard
</Button>
</div>
@@ -668,16 +669,16 @@ export function ProfileForm() {
className={`grid w-full ${platform.metadata.isTauri && isSystemAudioSupported ? 'grid-cols-3' : 'grid-cols-2'}`}
>
<TabsTrigger value="upload" className="flex items-center gap-2">
<Upload className="h-4 w-4 shrink-0" />
<HugeiconsIcon icon={Upload01Icon} size={16} className="h-4 w-4 shrink-0" />
Upload
</TabsTrigger>
<TabsTrigger value="record" className="flex items-center gap-2">
<Mic className="h-4 w-4 shrink-0" />
<HugeiconsIcon icon={Mic01Icon} size={16} className="h-4 w-4 shrink-0" />
Record
</TabsTrigger>
{platform.metadata.isTauri && isSystemAudioSupported && (
<TabsTrigger value="system" className="flex items-center gap-2">
<Monitor className="h-4 w-4 shrink-0" />
<HugeiconsIcon icon={DeskIcon} size={16} className="h-4 w-4 shrink-0" />
System Audio
</TabsTrigger>
)}
@@ -798,7 +799,7 @@ export function ProfileForm() {
className="h-full w-full object-cover"
/>
) : (
<Mic className="h-10 w-10 text-muted-foreground" />
<HugeiconsIcon icon={Mic01Icon} size={40} className="h-10 w-10 text-muted-foreground" />
)}
</div>
<button
@@ -806,7 +807,7 @@ export function ProfileForm() {
onClick={() => avatarInputRef.current?.click()}
className="absolute inset-0 rounded-full bg-accent/60 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center cursor-pointer"
>
<Edit2 className="h-6 w-6 text-accent-foreground" />
<HugeiconsIcon icon={Edit02Icon} size={24} className="h-6 w-6 text-accent-foreground" />
</button>
{(avatarPreview || editingProfile?.avatar_path) && (
<button
@@ -815,7 +816,7 @@ export function ProfileForm() {
disabled={deleteAvatar.isPending}
className="absolute bottom-0 right-0 h-6 w-6 rounded-full bg-background/60 backdrop-blur-sm text-muted-foreground flex items-center justify-center hover:bg-background/80 hover:text-foreground transition-colors shadow-sm border border-border/50"
>
<X className="h-3.5 w-3.5" />
<HugeiconsIcon icon={Cancel01Icon} size={14} className="h-3.5 w-3.5" />
</button>
)}
</div>
@@ -1,4 +1,5 @@
import { Mic, Sparkles } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Mic01Icon, SparklesIcon } from '@hugeicons/core-free-icons';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { useProfiles } from '@/lib/hooks/useProfiles';
@@ -30,12 +31,12 @@ export function ProfileList() {
{allProfiles.length === 0 ? (
<Card>
<CardContent className="flex flex-col items-center justify-center py-12">
<Mic className="h-12 w-12 text-muted-foreground mb-4" />
<HugeiconsIcon icon={Mic01Icon} size={48} className="h-12 w-12 text-muted-foreground mb-4" />
<p className="text-muted-foreground mb-4">
No voice profiles yet. Create your first profile to get started.
</p>
<Button onClick={() => setDialogOpen(true)}>
<Sparkles className="mr-2 h-4 w-4" />
<HugeiconsIcon icon={SparklesIcon} size={16} className="mr-2 h-4 w-4" />
Create Voice
</Button>
</CardContent>
+11 -10
View File
@@ -1,4 +1,5 @@
import { Check, Edit, Pause, Play, Plus, Trash2, Volume2, X } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { CheckmarkCircle01Icon, Edit01Icon, PauseIcon, PlayIcon, Add01Icon, Delete01Icon, VolumeHighIcon, Cancel01Icon } from '@hugeicons/core-free-icons';
import { useEffect, useRef, useState } from 'react';
import { Button } from '@/components/ui/button';
import { CircleButton } from '@/components/ui/circle-button';
@@ -103,7 +104,7 @@ function MiniSamplePlayer({ audioUrl }: MiniSamplePlayerProps) {
onClick={handlePlayPause}
disabled={isLoading}
>
{isPlaying ? <Pause className="h-3.5 w-3.5" /> : <Play className="h-3.5 w-3.5 ml-0.5" />}
{isPlaying ? <HugeiconsIcon icon={PauseIcon} size={14} className="h-3.5 w-3.5" /> : <HugeiconsIcon icon={PlayIcon} size={14} className="h-3.5 w-3.5 ml-0.5" />}
</Button>
<div className="flex-1 min-w-0 flex items-center gap-2">
@@ -129,7 +130,7 @@ function MiniSamplePlayer({ audioUrl }: MiniSamplePlayerProps) {
onClick={handleStop}
title="Stop"
>
<X className="h-3.5 w-3.5" />
<HugeiconsIcon icon={Cancel01Icon} size={14} className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -209,7 +210,7 @@ export function SampleList({ profileId }: SampleListProps) {
<div className="space-y-4 pt-4">
{samples && samples.length === 0 ? (
<div className="flex flex-col items-center justify-center py-8 text-center border border-dashed rounded-lg">
<Volume2 className="h-8 w-8 text-muted-foreground/50 mb-2" />
<HugeiconsIcon icon={VolumeHighIcon} size={32} className="h-8 w-8 text-muted-foreground/50 mb-2" />
<p className="text-sm text-muted-foreground">No samples yet</p>
<p className="text-xs text-muted-foreground/70 mt-1">
Add your first audio sample to get started
@@ -232,7 +233,7 @@ export function SampleList({ profileId }: SampleListProps) {
/* Edit Mode */
<div className="p-4 space-y-3">
<div className="flex items-center gap-2 text-xs text-muted-foreground mb-2">
<Edit className="h-3 w-3" />
<HugeiconsIcon icon={Edit01Icon} size={12} className="h-3 w-3" />
<span>Editing transcription</span>
</div>
<Textarea
@@ -250,7 +251,7 @@ export function SampleList({ profileId }: SampleListProps) {
onClick={handleCancelEdit}
disabled={updateSample.isPending}
>
<X className="h-4 w-4 mr-1" />
<HugeiconsIcon icon={Cancel01Icon} size={16} className="h-4 w-4 mr-1" />
Cancel
</Button>
<Button
@@ -259,7 +260,7 @@ export function SampleList({ profileId }: SampleListProps) {
onClick={() => handleSaveEdit(sample.id)}
disabled={updateSample.isPending}
>
<Check className="h-4 w-4 mr-1" />
<HugeiconsIcon icon={CheckmarkCircle01Icon} size={16} className="h-4 w-4 mr-1" />
{updateSample.isPending ? 'Saving...' : 'Save'}
</Button>
</div>
@@ -278,12 +279,12 @@ export function SampleList({ profileId }: SampleListProps) {
{/* Action Buttons */}
<div className="shrink-0 flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
<CircleButton
icon={Edit}
icon={(props) => <HugeiconsIcon icon={Edit01Icon} size={14} {...props} />}
title="Edit transcription"
onClick={() => handleStartEdit(sample.id, sample.reference_text)}
/>
<CircleButton
icon={Trash2}
icon={(props) => <HugeiconsIcon icon={Delete01Icon} size={14} {...props} />}
title="Delete sample"
onClick={() => handleDeleteClick(sample.id)}
disabled={deleteSample.isPending}
@@ -312,7 +313,7 @@ export function SampleList({ profileId }: SampleListProps) {
className="w-full"
onClick={() => setUploadOpen(true)}
>
<Plus className="mr-2 h-4 w-4" />
<HugeiconsIcon icon={Add01Icon} size={16} className="mr-2 h-4 w-4" />
Add Sample
</Button>
@@ -1,5 +1,6 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { Mic, Monitor, Upload } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Mic01Icon, DeskIcon, Upload01Icon } from '@hugeicons/core-free-icons';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import * as z from 'zod';
@@ -236,16 +237,16 @@ export function SampleUpload({ profileId, open, onOpenChange }: SampleUploadProp
className={`grid w-full ${platform.metadata.isTauri && isSystemAudioSupported ? 'grid-cols-3' : 'grid-cols-2'}`}
>
<TabsTrigger value="upload" className="flex items-center gap-2">
<Upload className="h-4 w-4 shrink-0" />
<HugeiconsIcon icon={Upload01Icon} size={16} className="h-4 w-4 shrink-0" />
Upload
</TabsTrigger>
<TabsTrigger value="record" className="flex items-center gap-2">
<Mic className="h-4 w-4 shrink-0" />
<HugeiconsIcon icon={Mic01Icon} size={16} className="h-4 w-4 shrink-0" />
Record
</TabsTrigger>
{platform.metadata.isTauri && isSystemAudioSupported && (
<TabsTrigger value="system" className="flex items-center gap-2">
<Monitor className="h-4 w-4 shrink-0" />
<HugeiconsIcon icon={DeskIcon} size={16} className="h-4 w-4 shrink-0" />
System Audio
</TabsTrigger>
)}
+7 -6
View File
@@ -1,5 +1,6 @@
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { Edit, MoreHorizontal, Plus, Trash2, Mic } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Edit01Icon, MoreHorizontalIcon, Add01Icon, Delete01Icon, Mic01Icon } from '@hugeicons/core-free-icons';
import { useMemo, useRef } from 'react';
import { Button } from '@/components/ui/button';
import {
@@ -112,7 +113,7 @@ export function VoicesTab() {
<div className="flex items-center justify-between mb-6">
<h1 className="text-2xl font-bold">Voices</h1>
<Button onClick={() => setDialogOpen(true)}>
<Plus className="h-4 w-4 mr-2" />
<HugeiconsIcon icon={Add01Icon} size={16} className="h-4 w-4 mr-2" />
New Voice
</Button>
</div>
@@ -185,7 +186,7 @@ function VoiceRow({
<TableCell>
<div className="flex items-center gap-2">
<div className="h-8 w-8 rounded-lg bg-muted flex items-center justify-center shrink-0">
<Mic className="h-4 w-4 text-muted-foreground" />
<HugeiconsIcon icon={Mic01Icon} size={16} className="h-4 w-4 text-muted-foreground" />
</div>
<div>
<div className="font-medium">{profile.name}</div>
@@ -214,16 +215,16 @@ function VoiceRow({
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<MoreHorizontal className="h-4 w-4" />
<HugeiconsIcon icon={MoreHorizontalIcon} size={16} className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={onEdit}>
<Edit className="h-4 w-4 mr-2" />
<HugeiconsIcon icon={Edit01Icon} size={16} className="h-4 w-4 mr-2" />
Edit
</DropdownMenuItem>
<DropdownMenuItem onClick={onDelete} className="text-destructive">
<Trash2 className="h-4 w-4 mr-2" />
<HugeiconsIcon icon={Delete01Icon} size={16} className="h-4 w-4 mr-2" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
+3 -2
View File
@@ -1,5 +1,6 @@
import * as React from 'react';
import { Check } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { CheckmarkCircle01Icon } from '@hugeicons/core-free-icons';
import { cn } from '@/lib/utils/cn';
export interface CheckboxProps {
@@ -34,7 +35,7 @@ const Checkbox = React.forwardRef<HTMLButtonElement, CheckboxProps>(
)}
{...props}
>
{checked && <Check className="h-3 w-3 text-accent-foreground" />}
{checked && <HugeiconsIcon icon={CheckmarkCircle01Icon} size={12} className="h-3 w-3 text-accent-foreground" />}
</button>
);
},
+3 -2
View File
@@ -1,5 +1,6 @@
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { X } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Cancel01Icon } from '@hugeicons/core-free-icons';
import * as React from 'react';
import { cn } from '@/lib/utils/cn';
@@ -42,7 +43,7 @@ const DialogContent = React.forwardRef<
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<HugeiconsIcon icon={Cancel01Icon} size={16} className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
+5 -4
View File
@@ -1,6 +1,7 @@
import * as React from 'react';
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import { MoreHorizontal } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { MoreHorizontalIcon } from '@hugeicons/core-free-icons';
import { cn } from '@/lib/utils/cn';
const DropdownMenu = DropdownMenuPrimitive.Root;
@@ -26,7 +27,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
{...props}
>
{children}
<MoreHorizontal className="ml-auto h-4 w-4" />
<HugeiconsIcon icon={MoreHorizontalIcon} size={16} className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
));
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
@@ -97,7 +98,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<MoreHorizontal className="h-4 w-4" />
<HugeiconsIcon icon={MoreHorizontalIcon} size={16} className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
@@ -119,7 +120,7 @@ const DropdownMenuRadioItem = React.forwardRef<
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<MoreHorizontal className="h-2 w-2 fill-current" />
<HugeiconsIcon icon={MoreHorizontalIcon} size={8} className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
+4 -3
View File
@@ -1,5 +1,6 @@
import * as React from 'react';
import { ChevronDown, Check } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { ArrowDown01Icon, CheckmarkCircle01Icon } from '@hugeicons/core-free-icons';
import { cn } from '@/lib/utils/cn';
import {
DropdownMenu,
@@ -36,7 +37,7 @@ const MultiSelectCheckboxItem = React.forwardRef<
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
<HugeiconsIcon icon={CheckmarkCircle01Icon} size={16} className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
@@ -78,7 +79,7 @@ export function MultiSelect({
)}
>
<span className="line-clamp-1">{displayText}</span>
<ChevronDown className="h-4 w-4 opacity-50" />
<HugeiconsIcon icon={ArrowDown01Icon} size={16} className="h-4 w-4 opacity-50" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent
+3 -2
View File
@@ -2,7 +2,8 @@
import * as React from "react"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
import { Circle } from "lucide-react"
import { HugeiconsIcon } from '@hugeicons/react';
import { CircleIcon } from '@hugeicons/core-free-icons';
import { cn } from "@/lib/utils/cn"
@@ -34,7 +35,7 @@ const RadioGroupItem = React.forwardRef<
{...props}
>
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<Circle className="h-2.5 w-2.5 fill-current text-current" />
<HugeiconsIcon icon={CircleIcon} size={10} className="h-2.5 w-2.5 fill-current text-current" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
)
+6 -5
View File
@@ -1,5 +1,6 @@
import * as SelectPrimitive from '@radix-ui/react-select';
import { Check, ChevronDown, ChevronUp } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { CheckmarkCircle01Icon, ArrowDown01Icon, ArrowUp01Icon } from '@hugeicons/core-free-icons';
import * as React from 'react';
import { cn } from '@/lib/utils/cn';
@@ -23,7 +24,7 @@ const SelectTrigger = React.forwardRef<
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
<HugeiconsIcon icon={ArrowDown01Icon} size={16} className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
@@ -38,7 +39,7 @@ const SelectScrollUpButton = React.forwardRef<
className={cn('flex cursor-default items-center justify-center py-1', className)}
{...props}
>
<ChevronUp className="h-4 w-4" />
<HugeiconsIcon icon={ArrowUp01Icon} size={16} className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
));
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
@@ -52,7 +53,7 @@ const SelectScrollDownButton = React.forwardRef<
className={cn('flex cursor-default items-center justify-center py-1', className)}
{...props}
>
<ChevronDown className="h-4 w-4" />
<HugeiconsIcon icon={ArrowDown01Icon} size={16} className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
));
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
@@ -115,7 +116,7 @@ const SelectItem = React.forwardRef<
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
<HugeiconsIcon icon={CheckmarkCircle01Icon} size={16} className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
+3 -2
View File
@@ -1,6 +1,7 @@
import * as ToastPrimitives from '@radix-ui/react-toast';
import { cva, type VariantProps } from 'class-variance-authority';
import { X } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Cancel01Icon } from '@hugeicons/core-free-icons';
import * as React from 'react';
import { cn } from '@/lib/utils/cn';
@@ -79,7 +80,7 @@ const ToastClose = React.forwardRef<
toast-close=""
{...props}
>
<X className="h-4 w-4" />
<HugeiconsIcon icon={Cancel01Icon} size={16} className="h-4 w-4" />
</ToastPrimitives.Close>
));
ToastClose.displayName = ToastPrimitives.Close.displayName;
+1 -1
View File
@@ -41,7 +41,7 @@ export function useAutoUpdater(checkOnMount = false) {
checkForUpdates();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [platform.metadata.isTauricheckOnMountcheckForUpdates]);
}, []);
return {
status,
+4 -3
View File
@@ -1,4 +1,5 @@
import { Download, RefreshCw } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Download01Icon, Refresh01Icon } from '@hugeicons/core-free-icons';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Progress } from '@/components/ui/progress';
import { ToastAction } from '@/components/ui/toast';
@@ -73,7 +74,7 @@ export function useAutoUpdater(options: boolean | UseAutoUpdaterOptions = false)
}
// Empty dependency array - only run once on mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [platform.metadata.isTauricheckOnMountcheckForUpdates]);
}, []);
// Show toast when update is available
useEffect(() => {
@@ -174,7 +175,7 @@ export function useAutoUpdater(options: boolean | UseAutoUpdaterOptions = false)
duration: Infinity,
action: (
<ToastAction altText="Restart now" onClick={handleRestartNow}>
<RefreshCw className="h-3 w-3 mr-1" />
<HugeiconsIcon icon={Refresh01Icon} size={12} className="h-3 w-3 mr-1" />
Restart Now
</ToastAction>
),
+1
View File
@@ -34,6 +34,7 @@ export interface GenerationRequest {
language: LanguageCode;
seed?: number;
model_size?: '1.7B' | '0.6B';
instruct?: string;
}
export interface GenerationResponse {
+8 -7
View File
@@ -1,4 +1,5 @@
import { CheckCircle2, Loader2, XCircle } from 'lucide-react';
import { HugeiconsIcon } from '@hugeicons/react';
import { CheckmarkCircle02Icon, Loading01Icon, CancelCircleIcon } from '@hugeicons/core-free-icons';
import { useCallback, useEffect, useRef } from 'react';
import { Progress } from '@/components/ui/progress';
import { useToast } from '@/components/ui/use-toast';
@@ -59,7 +60,7 @@ export function useModelDownloadToast({
title: displayName,
description: (
<div className="flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin" />
<HugeiconsIcon icon={Loading01Icon} size={16} className="h-4 w-4 animate-spin" />
<span>Connecting to download...</span>
</div>
),
@@ -96,19 +97,19 @@ export function useModelDownloadToast({
switch (progress.status) {
case 'complete':
statusIcon = <CheckCircle2 className="h-4 w-4 text-green-500" />;
statusIcon = <HugeiconsIcon icon={CheckmarkCircle02Icon} size={16} className="h-4 w-4 text-green-500" />;
statusText = 'Download complete';
break;
case 'error':
statusIcon = <XCircle className="h-4 w-4 text-destructive" />;
statusIcon = <HugeiconsIcon icon={CancelCircleIcon} size={16} className="h-4 w-4 text-destructive" />;
statusText = `Error: ${progress.error || 'Unknown error'}`;
break;
case 'downloading':
statusIcon = <Loader2 className="h-4 w-4 animate-spin" />;
statusIcon = <HugeiconsIcon icon={Loading01Icon} size={16} className="h-4 w-4 animate-spin" />;
statusText = progress.filename || 'Downloading...';
break;
case 'extracting':
statusIcon = <Loader2 className="h-4 w-4 animate-spin" />;
statusIcon = <HugeiconsIcon icon={Loading01Icon} size={16} className="h-4 w-4 animate-spin" />;
statusText = 'Extracting...';
break;
}
@@ -154,7 +155,7 @@ export function useModelDownloadToast({
toastUpdateRef.current({
title: (
<div className="flex items-center gap-2">
<CheckCircle2 className="h-4 w-4 text-green-500" />
<HugeiconsIcon icon={CheckmarkCircle02Icon} size={16} className="h-4 w-4 text-green-500" />
<span>{displayName}</span>
</div>
),
+1 -5
View File
@@ -1,15 +1,11 @@
# -*- mode: python ; coding: utf-8 -*-
from PyInstaller.utils.hooks import collect_data_files
from PyInstaller.utils.hooks import collect_submodules
from PyInstaller.utils.hooks import copy_metadata
datas = []
hiddenimports = ['backend', 'backend.main', 'backend.config', 'backend.database', 'backend.models', 'backend.profiles', 'backend.history', 'backend.tts', 'backend.transcribe', 'backend.platform_detect', 'backend.backends', 'backend.backends.pytorch_backend', 'backend.utils.audio', 'backend.utils.cache', 'backend.utils.progress', 'backend.utils.hf_progress', 'backend.utils.validation', 'torch', 'transformers', 'fastapi', 'uvicorn', 'sqlalchemy', 'librosa', 'soundfile', 'qwen_tts', 'qwen_tts.inference', 'qwen_tts.inference.qwen3_tts_model', 'qwen_tts.inference.qwen3_tts_tokenizer', 'qwen_tts.core', 'qwen_tts.cli', 'pkg_resources.extern', 'backend.backends.mlx_backend', 'mlx', 'mlx.core', 'mlx.nn', 'mlx_audio', 'mlx_audio.tts', 'mlx_audio.stt']
datas += collect_data_files('qwen_tts')
hiddenimports = ['backend', 'backend.main', 'backend.config', 'backend.database', 'backend.models', 'backend.profiles', 'backend.history', 'backend.tts', 'backend.transcribe', 'backend.platform_detect', 'backend.providers', 'backend.providers.base', 'backend.providers.bundled', 'backend.providers.types', 'backend.utils.audio', 'backend.utils.cache', 'backend.utils.progress', 'backend.utils.hf_progress', 'backend.utils.validation', 'fastapi', 'uvicorn', 'sqlalchemy', 'librosa', 'soundfile', 'pkg_resources.extern', 'backend.backends', 'backend.backends.mlx_backend', 'mlx', 'mlx.core', 'mlx.nn', 'mlx_audio', 'mlx_audio.tts', 'mlx_audio.stt']
datas += collect_data_files('mlx')
datas += collect_data_files('mlx_audio')
datas += copy_metadata('qwen-tts')
hiddenimports += collect_submodules('qwen_tts')
hiddenimports += collect_submodules('jaraco')
hiddenimports += collect_submodules('mlx')
hiddenimports += collect_submodules('mlx_audio')
+10 -4
View File
@@ -13,12 +13,14 @@
},
"app": {
"name": "@voicebox/app",
"version": "0.1.12",
"version": "0.1.13",
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@hookform/resolvers": "^3.9.0",
"@hugeicons/core-free-icons": "^3.1.1",
"@hugeicons/react": "^1.1.4",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-dialog": "^1.1.1",
@@ -69,7 +71,7 @@
},
"landing": {
"name": "@voicebox/landing",
"version": "0.1.12",
"version": "0.1.13",
"dependencies": {
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.2.4",
@@ -94,7 +96,7 @@
},
"tauri": {
"name": "@voicebox/tauri",
"version": "0.1.12",
"version": "0.1.13",
"dependencies": {
"@tauri-apps/api": "^2.0.0",
"@tauri-apps/plugin-dialog": "^2.0.0",
@@ -117,7 +119,7 @@
},
"web": {
"name": "@voicebox/web",
"version": "0.1.12",
"version": "0.1.13",
"dependencies": {
"@tanstack/react-query": "^5.0.0",
"react": "^18.3.0",
@@ -272,6 +274,10 @@
"@hookform/resolvers": ["@hookform/[email protected]", "", { "peerDependencies": { "react-hook-form": "^7.0.0" } }, "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag=="],
"@hugeicons/core-free-icons": ["@hugeicons/[email protected]", "", {}, "sha512-UpS2lUQFi5sKyJSWwM6rO+BnPLvVz1gsyCpPHeZyVuZqi89YH8ksliza4cwaODqKOZyeXmG8juo1ty4QtQofkg=="],
"@hugeicons/react": ["@hugeicons/[email protected]", "", { "peerDependencies": { "react": ">=16.0.0" } }, "sha512-gsc3eZyd2fGqRUThW9+lfjxxsOkz6KNVmRXRgJjP32GL0OnnLJnl3hytKt47CBbiQj2xE2kCw+rnP3UQCThcKw=="],
"@humanwhocodes/config-array": ["@humanwhocodes/[email protected]", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="],
"@humanwhocodes/module-importer": ["@humanwhocodes/[email protected]", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
+1 -1
View File
@@ -5041,7 +5041,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "voicebox"
version = "0.1.12"
version = "0.1.13"
dependencies = [
"base64 0.22.1",
"core-foundation-sys",
Binary file not shown.