mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-29 07:05:14 -07:00
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:
@@ -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' }),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user