Enhance update status display and audio sample components

- Improved the UpdateStatus component to show download progress and total bytes downloaded during updates.
- Refactored AudioSampleRecording and AudioSampleSystem components for cleaner button rendering and consistent layout.
- Updated import order in SampleUpload component for better organization.
This commit is contained in:
Jamie Pine
2026-01-26 19:22:43 -08:00
parent cd77b80b4f
commit 8b67faf96d
5 changed files with 60 additions and 45 deletions
@@ -71,11 +71,23 @@ export function UpdateStatus() {
{status.downloading && ( {status.downloading && (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center gap-2 text-sm"> <div className="flex items-center justify-between text-sm">
<Download className="h-4 w-4" /> <div className="flex items-center gap-2">
Downloading update... <Download className="h-4 w-4" />
Downloading update...
</div>
{status.downloadProgress !== undefined && (
<span className="text-muted-foreground">
{status.downloadProgress}%
</span>
)}
</div> </div>
<Progress /> <Progress value={status.downloadProgress} />
{status.downloadedBytes !== undefined && status.totalBytes !== undefined && status.totalBytes > 0 && (
<div className="text-xs text-muted-foreground">
{(status.downloadedBytes / 1024 / 1024).toFixed(1)} MB / {(status.totalBytes / 1024 / 1024).toFixed(1)} MB
</div>
)}
</div> </div>
)} )}
@@ -1,4 +1,4 @@
import { Mic, Square, Play, Pause } from 'lucide-react'; import { Mic, Pause, Play, Square } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { FormControl, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; import { FormControl, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
import { formatAudioDuration } from '@/lib/utils/audio'; import { formatAudioDuration } from '@/lib/utils/audio';
@@ -35,12 +35,7 @@ export function AudioSampleRecording({
<div className="space-y-4"> <div className="space-y-4">
{!isRecording && !file && ( {!isRecording && !file && (
<div className="flex flex-col items-center justify-center gap-4 p-4 border-2 border-dashed rounded-lg min-h-[180px]"> <div className="flex flex-col items-center justify-center gap-4 p-4 border-2 border-dashed rounded-lg min-h-[180px]">
<Button <Button type="button" onClick={onStart} size="lg" className="flex items-center gap-2">
type="button"
onClick={onStart}
size="lg"
className="flex items-center gap-2"
>
<Mic className="h-5 w-5" /> <Mic className="h-5 w-5" />
Start Recording Start Recording
</Button> </Button>
@@ -81,16 +76,9 @@ export function AudioSampleRecording({
<Mic className="h-5 w-5 text-primary" /> <Mic className="h-5 w-5 text-primary" />
<span className="font-medium">Recording complete</span> <span className="font-medium">Recording complete</span>
</div> </div>
<p className="text-sm text-muted-foreground text-center"> <p className="text-sm text-muted-foreground text-center">File: {file.name}</p>
File: {file.name}
</p>
<div className="flex gap-2"> <div className="flex gap-2">
<Button <Button type="button" size="icon" variant="outline" onClick={onPlayPause}>
type="button"
size="icon"
variant="outline"
onClick={onPlayPause}
>
{isPlaying ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />} {isPlaying ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />}
</Button> </Button>
<Button <Button
@@ -1,4 +1,4 @@
import { Monitor, Square, Play, Pause, Mic } from 'lucide-react'; import { Mic, Monitor, Pause, Play, Square } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { FormControl, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; import { FormControl, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
import { formatAudioDuration } from '@/lib/utils/audio'; import { formatAudioDuration } from '@/lib/utils/audio';
@@ -35,12 +35,7 @@ export function AudioSampleSystem({
<div className="space-y-4"> <div className="space-y-4">
{!isRecording && !file && ( {!isRecording && !file && (
<div className="flex flex-col items-center justify-center gap-4 p-4 border-2 border-dashed rounded-lg min-h-[180px]"> <div className="flex flex-col items-center justify-center gap-4 p-4 border-2 border-dashed rounded-lg min-h-[180px]">
<Button <Button type="button" onClick={onStart} size="lg" className="flex items-center gap-2">
type="button"
onClick={onStart}
size="lg"
className="flex items-center gap-2"
>
<Monitor className="h-5 w-5" /> <Monitor className="h-5 w-5" />
Start Capture Start Capture
</Button> </Button>
@@ -81,16 +76,9 @@ export function AudioSampleSystem({
<Monitor className="h-5 w-5 text-primary" /> <Monitor className="h-5 w-5 text-primary" />
<span className="font-medium">Capture complete</span> <span className="font-medium">Capture complete</span>
</div> </div>
<p className="text-sm text-muted-foreground text-center"> <p className="text-sm text-muted-foreground text-center">File: {file.name}</p>
File: {file.name}
</p>
<div className="flex gap-2"> <div className="flex gap-2">
<Button <Button type="button" size="icon" variant="outline" onClick={onPlayPause}>
type="button"
size="icon"
variant="outline"
onClick={onPlayPause}
>
{isPlaying ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />} {isPlaying ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />}
</Button> </Button>
<Button <Button
@@ -22,15 +22,15 @@ import {
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
import { useToast } from '@/components/ui/use-toast'; import { useToast } from '@/components/ui/use-toast';
import { useAudioRecording } from '@/lib/hooks/useAudioRecording';
import { useAudioPlayer } from '@/lib/hooks/useAudioPlayer'; import { useAudioPlayer } from '@/lib/hooks/useAudioPlayer';
import { useAudioRecording } from '@/lib/hooks/useAudioRecording';
import { useAddSample, useProfile } from '@/lib/hooks/useProfiles'; import { useAddSample, useProfile } from '@/lib/hooks/useProfiles';
import { useSystemAudioCapture } from '@/lib/hooks/useSystemAudioCapture'; import { useSystemAudioCapture } from '@/lib/hooks/useSystemAudioCapture';
import { useTranscription } from '@/lib/hooks/useTranscription'; import { useTranscription } from '@/lib/hooks/useTranscription';
import { isTauri } from '@/lib/tauri'; import { isTauri } from '@/lib/tauri';
import { AudioSampleUpload } from './AudioSampleUpload';
import { AudioSampleRecording } from './AudioSampleRecording'; import { AudioSampleRecording } from './AudioSampleRecording';
import { AudioSampleSystem } from './AudioSampleSystem'; import { AudioSampleSystem } from './AudioSampleSystem';
import { AudioSampleUpload } from './AudioSampleUpload';
const sampleSchema = z.object({ const sampleSchema = z.object({
file: z.instanceof(File, { message: 'Please select an audio file' }), file: z.instanceof(File, { message: 'Please select an audio file' }),
+34 -7
View File
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { check, type Update } from '@tauri-apps/plugin-updater'; import { check, type Update } from '@tauri-apps/plugin-updater';
import { relaunch } from '@tauri-apps/plugin-process'; import { relaunch } from '@tauri-apps/plugin-process';
@@ -9,6 +9,9 @@ export interface UpdateStatus {
downloading: boolean; downloading: boolean;
installing: boolean; installing: boolean;
error?: string; error?: string;
downloadProgress?: number; // 0-100 percentage
downloadedBytes?: number;
totalBytes?: number;
} }
const isTauri = () => { const isTauri = () => {
@@ -25,7 +28,7 @@ export function useAutoUpdater(checkOnMount = false) {
const [update, setUpdate] = useState<Update | null>(null); const [update, setUpdate] = useState<Update | null>(null);
const checkForUpdates = async () => { const checkForUpdates = useCallback(async () => {
if (!isTauri()) { if (!isTauri()) {
return; return;
} }
@@ -61,7 +64,7 @@ export function useAutoUpdater(checkOnMount = false) {
error: error instanceof Error ? error.message : 'Failed to check for updates', error: error instanceof Error ? error.message : 'Failed to check for updates',
}); });
} }
}; }, []);
const downloadAndInstall = async () => { const downloadAndInstall = async () => {
if (!update || !isTauri()) return; if (!update || !isTauri()) return;
@@ -69,19 +72,40 @@ export function useAutoUpdater(checkOnMount = false) {
try { try {
setStatus((prev) => ({ ...prev, downloading: true, error: undefined })); setStatus((prev) => ({ ...prev, downloading: true, error: undefined }));
let downloadedBytes = 0;
let totalBytes = 0;
await update.downloadAndInstall((event) => { await update.downloadAndInstall((event) => {
switch (event.event) { switch (event.event) {
case 'Started': case 'Started':
setStatus((prev) => ({ ...prev, downloading: true })); totalBytes = event.data.contentLength || 0;
downloadedBytes = 0;
setStatus((prev) => ({
...prev,
downloading: true,
totalBytes,
downloadedBytes: 0,
downloadProgress: 0
}));
break; break;
case 'Progress': case 'Progress': {
console.log(`Downloaded ${event.data.chunkLength} bytes`); downloadedBytes += event.data.chunkLength;
const progress = totalBytes > 0
? Math.round((downloadedBytes / totalBytes) * 100)
: undefined;
setStatus((prev) => ({
...prev,
downloadedBytes,
downloadProgress: progress
}));
break; break;
}
case 'Finished': case 'Finished':
setStatus((prev) => ({ setStatus((prev) => ({
...prev, ...prev,
downloading: false, downloading: false,
installing: true, installing: true,
downloadProgress: 100
})); }));
break; break;
} }
@@ -93,6 +117,9 @@ export function useAutoUpdater(checkOnMount = false) {
...prev, ...prev,
downloading: false, downloading: false,
installing: false, installing: false,
downloadProgress: undefined,
downloadedBytes: undefined,
totalBytes: undefined,
error: error instanceof Error ? error.message : 'Failed to install update', error: error instanceof Error ? error.message : 'Failed to install update',
})); }));
} }
@@ -102,7 +129,7 @@ export function useAutoUpdater(checkOnMount = false) {
if (checkOnMount && isTauri()) { if (checkOnMount && isTauri()) {
checkForUpdates(); checkForUpdates();
} }
}, [checkOnMount]); }, [checkOnMount, checkForUpdates]);
return { return {
status, status,