mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-10-03 00:55:14 -07:00
Windows dev experience: CUDA detection, GPU display cleanup, hide drag region, dev-mode update status
This commit is contained in:
@@ -113,7 +113,7 @@ export function ConnectionForm() {
|
|||||||
<Badge variant={health.gpu_available ? 'default' : 'secondary'}>
|
<Badge variant={health.gpu_available ? 'default' : 'secondary'}>
|
||||||
GPU: {health.gpu_available ? 'Available' : 'Not Available'}
|
GPU: {health.gpu_available ? 'Available' : 'Not Available'}
|
||||||
</Badge>
|
</Badge>
|
||||||
{health.vram_used_mb && (
|
{health.vram_used_mb != null && health.vram_used_mb > 0 && (
|
||||||
<Badge variant="outline">VRAM: {health.vram_used_mb.toFixed(0)} MB</Badge>
|
<Badge variant="outline">VRAM: {health.vram_used_mb.toFixed(0)} MB</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export function GpuAcceleration() {
|
|||||||
// Query CUDA backend status
|
// Query CUDA backend status
|
||||||
const {
|
const {
|
||||||
data: cudaStatus,
|
data: cudaStatus,
|
||||||
isLoading: cudaStatusLoading,
|
isLoading: _cudaStatusLoading,
|
||||||
refetch: refetchCudaStatus,
|
refetch: refetchCudaStatus,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ['cuda-status', serverUrl],
|
queryKey: ['cuda-status', serverUrl],
|
||||||
@@ -218,35 +218,33 @@ export function GpuAcceleration() {
|
|||||||
<CardTitle>GPU Acceleration</CardTitle>
|
<CardTitle>GPU Acceleration</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
{/* Current status */}
|
{/* GPU status */}
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="text-sm font-medium">Backend</div>
|
{health.gpu_available && health.gpu_type ? (
|
||||||
|
<>
|
||||||
|
<div className="text-sm font-medium">
|
||||||
|
{health.gpu_type.replace(/^(CUDA|ROCm|MPS|Metal|XPU|DirectML)\s*\((.+)\)$/, '$2') ||
|
||||||
|
health.gpu_type}
|
||||||
|
</div>
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground">
|
||||||
{isCurrentlyCuda
|
{health.gpu_type.replace(/\s*\(.+\)$/, '')}
|
||||||
? 'CUDA (GPU accelerated)'
|
{health.vram_used_mb != null && health.vram_used_mb > 0
|
||||||
: hasNativeGpu
|
? ` \u00b7 ${health.vram_used_mb.toFixed(0)} MB VRAM used`
|
||||||
? `${health.backend_type === 'mlx' ? 'MLX' : 'PyTorch'} (GPU accelerated)`
|
: ''}
|
||||||
: 'CPU'}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* GPU info from health */}
|
|
||||||
{health.gpu_type && (
|
|
||||||
<div className="space-y-1">
|
|
||||||
<div className="text-sm font-medium">GPU</div>
|
|
||||||
<div className="text-sm text-muted-foreground">{health.gpu_type}</div>
|
|
||||||
{health.vram_used_mb != null && (
|
|
||||||
<div className="text-xs text-muted-foreground">
|
|
||||||
VRAM: {health.vram_used_mb.toFixed(0)} MB used
|
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="text-sm font-medium">CPU</div>
|
||||||
|
<div className="text-sm text-muted-foreground">No GPU acceleration available</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Native GPU detected - no CUDA download needed */}
|
{/* Native GPU detected - no CUDA download needed */}
|
||||||
|
|
||||||
{/* CUDA download section - only show when native GPU is NOT detected (i.e., Windows/Linux NVIDIA users) */}
|
{/* CUDA download section - only show when no GPU is active (native or CUDA) */}
|
||||||
{!hasNativeGpu && (
|
{!hasNativeGpu && !isCurrentlyCuda && (
|
||||||
<>
|
<>
|
||||||
{/* Download progress */}
|
{/* Download progress */}
|
||||||
{cudaDownloading && downloadProgress && (
|
{cudaDownloading && downloadProgress && (
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export function UpdateStatus() {
|
|||||||
const platform = usePlatform();
|
const platform = usePlatform();
|
||||||
const { status, checkForUpdates, downloadAndInstall, restartAndInstall } = useAutoUpdater(false);
|
const { status, checkForUpdates, downloadAndInstall, restartAndInstall } = useAutoUpdater(false);
|
||||||
const [currentVersion, setCurrentVersion] = useState<string>('');
|
const [currentVersion, setCurrentVersion] = useState<string>('');
|
||||||
|
const isDev = !import.meta.env?.PROD;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
platform.metadata
|
platform.metadata
|
||||||
@@ -20,11 +21,7 @@ export function UpdateStatus() {
|
|||||||
}, [platform]);
|
}, [platform]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card role="region" aria-label="App Updates" tabIndex={0}>
|
||||||
role="region"
|
|
||||||
aria-label="App Updates"
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>App Updates</CardTitle>
|
<CardTitle>App Updates</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -32,8 +29,12 @@ export function UpdateStatus() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="text-sm font-medium">Current Version</div>
|
<div className="text-sm font-medium">Current Version</div>
|
||||||
<div className="text-sm text-muted-foreground">v{currentVersion}</div>
|
<div className="text-sm text-muted-foreground">
|
||||||
|
v{currentVersion}
|
||||||
|
{isDev ? ' (dev)' : ''}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{!isDev && (
|
||||||
<Button
|
<Button
|
||||||
onClick={checkForUpdates}
|
onClick={checkForUpdates}
|
||||||
disabled={status.checking || status.downloading || status.readyToInstall}
|
disabled={status.checking || status.downloading || status.readyToInstall}
|
||||||
@@ -43,8 +44,15 @@ export function UpdateStatus() {
|
|||||||
<RefreshCw className={`h-4 w-4 mr-2 ${status.checking ? 'animate-spin' : ''}`} />
|
<RefreshCw className={`h-4 w-4 mr-2 ${status.checking ? 'animate-spin' : ''}`} />
|
||||||
Check for Updates
|
Check for Updates
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{isDev ? (
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
Auto-updates are disabled in development mode.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
{status.checking && (
|
{status.checking && (
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
<RefreshCw className="h-4 w-4 animate-spin" />
|
<RefreshCw className="h-4 w-4 animate-spin" />
|
||||||
@@ -109,8 +117,8 @@ export function UpdateStatus() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground">
|
||||||
The app needs to restart to complete the installation. You can do this now or later at
|
The app needs to restart to complete the installation. You can do this now or
|
||||||
your convenience.
|
later at your convenience.
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={restartAndInstall} className="w-full" size="sm">
|
<Button onClick={restartAndInstall} className="w-full" size="sm">
|
||||||
<RefreshCw className="h-4 w-4 mr-2" />
|
<RefreshCw className="h-4 w-4 mr-2" />
|
||||||
@@ -119,11 +127,13 @@ export function UpdateStatus() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!status.available && !status.checking && !status.error && status.checking === false && (
|
{!status.available && !status.checking && !status.error && (
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
You're up to date
|
You're up to date
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
|
const isWindows = navigator.userAgent.includes('Windows');
|
||||||
|
|
||||||
export function TitleBarDragRegion() {
|
export function TitleBarDragRegion() {
|
||||||
return (
|
if (isWindows) return null;
|
||||||
<div
|
|
||||||
data-tauri-drag-region
|
return <div data-tauri-drag-region className="fixed top-0 left-0 right-0 h-12 z-[9999]" />;
|
||||||
className="fixed top-0 left-0 right-0 h-12 z-[9999]"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,14 @@
|
|||||||
* UI layout constants for safe area padding
|
* UI layout constants for safe area padding
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const isWindows = typeof navigator !== 'undefined' && navigator.userAgent.includes('Windows');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Top safe area padding - height of the drag region bar
|
* Top safe area padding - height of the drag region bar
|
||||||
* Corresponds to Tailwind's pt-12 (3rem / 48px)
|
* On macOS this accounts for the overlay titlebar (48px).
|
||||||
|
* On Windows the native title bar is outside the webview, so no padding is needed.
|
||||||
*/
|
*/
|
||||||
export const TOP_SAFE_AREA_PADDING = 'pt-12';
|
export const TOP_SAFE_AREA_PADDING = isWindows ? 'pt-8' : 'pt-12';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bottom safe area padding - height of the audio player
|
* Bottom safe area padding - height of the audio player
|
||||||
|
|||||||
+1
-1
@@ -263,7 +263,7 @@ async def health():
|
|||||||
gpu_type=gpu_type,
|
gpu_type=gpu_type,
|
||||||
vram_used_mb=vram_used,
|
vram_used_mb=vram_used,
|
||||||
backend_type=backend_type,
|
backend_type=backend_type,
|
||||||
backend_variant=os.environ.get("VOICEBOX_BACKEND_VARIANT", "cpu"),
|
backend_variant=os.environ.get("VOICEBOX_BACKEND_VARIANT", "cuda" if torch.cuda.is_available() else "cpu"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,11 @@ setup-python:
|
|||||||
}
|
}
|
||||||
Write-Host "Installing Python dependencies..."
|
Write-Host "Installing Python dependencies..."
|
||||||
& "{{ python }}" -m pip install --upgrade pip -q
|
& "{{ python }}" -m pip install --upgrade pip -q
|
||||||
|
$hasNvidia = $null -ne (Get-WmiObject Win32_VideoController | Where-Object { $_.Name -match 'NVIDIA' })
|
||||||
|
if ($hasNvidia) { \
|
||||||
|
Write-Host "NVIDIA GPU detected — installing PyTorch with CUDA support..."; \
|
||||||
|
& "{{ pip }}" install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126; \
|
||||||
|
}
|
||||||
& "{{ pip }}" install -r {{ backend_dir }}/requirements.txt
|
& "{{ pip }}" install -r {{ backend_dir }}/requirements.txt
|
||||||
& "{{ pip }}" install --no-deps chatterbox-tts
|
& "{{ pip }}" install --no-deps chatterbox-tts
|
||||||
& "{{ pip }}" install git+https://github.com/QwenLM/Qwen3-TTS.git
|
& "{{ pip }}" install git+https://github.com/QwenLM/Qwen3-TTS.git
|
||||||
@@ -77,30 +82,37 @@ setup-js:
|
|||||||
|
|
||||||
# ─── Development ──────────────────────────────────────────────────────
|
# ─── Development ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
# Start backend + frontend for development (two processes, one terminal)
|
# Start backend (if not already running) + frontend for development
|
||||||
[unix]
|
[unix]
|
||||||
dev: _ensure-venv _ensure-sidecar
|
dev: _ensure-venv _ensure-sidecar
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
trap 'kill 0' EXIT
|
|
||||||
|
|
||||||
|
backend_pid=""
|
||||||
|
if curl -sf http://127.0.0.1:17493/health > /dev/null 2>&1; then
|
||||||
|
echo "Backend already running on http://localhost:17493"
|
||||||
|
else
|
||||||
echo "Starting backend on http://localhost:17493 ..."
|
echo "Starting backend on http://localhost:17493 ..."
|
||||||
{{ venv_bin }}/uvicorn backend.main:app --reload --port 17493 &
|
{{ venv_bin }}/uvicorn backend.main:app --reload --port 17493 &
|
||||||
|
backend_pid=$!
|
||||||
sleep 2
|
sleep 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
trap '[ -n "$backend_pid" ] && kill "$backend_pid" 2>/dev/null; wait' EXIT
|
||||||
|
|
||||||
echo "Starting Tauri desktop app..."
|
echo "Starting Tauri desktop app..."
|
||||||
cd {{ tauri_dir }} && bun run tauri dev &
|
cd {{ tauri_dir }} && bun run tauri dev
|
||||||
|
|
||||||
wait
|
|
||||||
|
|
||||||
[windows]
|
[windows]
|
||||||
dev: _ensure-venv _ensure-sidecar
|
dev: _ensure-venv _ensure-sidecar
|
||||||
Write-Host "Starting backend on http://localhost:17493 ..."
|
$backendJob = $null
|
||||||
$backend = Start-Process -NoNewWindow -PassThru -FilePath "{{ python }}" -ArgumentList "-m", "uvicorn", "backend.main:app", "--reload", "--port", "17493"
|
try { $null = Invoke-WebRequest -Uri "http://127.0.0.1:17493/health" -UseBasicParsing -TimeoutSec 2 -ErrorAction Stop; Write-Host "Backend already running on http://localhost:17493" } catch { \
|
||||||
Start-Sleep -Seconds 2
|
Write-Host "Starting backend on http://localhost:17493 ..."; \
|
||||||
|
$backendJob = Start-Job -ScriptBlock { & "{{ python }}" -m uvicorn backend.main:app --reload --port 17493 } -WorkingDirectory (Get-Location); \
|
||||||
|
Start-Sleep -Seconds 2; \
|
||||||
|
}
|
||||||
Write-Host "Starting Tauri desktop app..."
|
Write-Host "Starting Tauri desktop app..."
|
||||||
$frontend = Start-Process -NoNewWindow -PassThru -WorkingDirectory "{{ tauri_dir }}" -FilePath "bun" -ArgumentList "run", "tauri", "dev"
|
try { Set-Location "{{ tauri_dir }}"; bun run tauri dev } finally { if ($backendJob) { Stop-Job $backendJob -ErrorAction SilentlyContinue; Remove-Job $backendJob -Force -ErrorAction SilentlyContinue } }
|
||||||
try { Wait-Process -Id $backend.Id, $frontend.Id } finally { Stop-Process -Id $backend.Id, $frontend.Id -ErrorAction SilentlyContinue }
|
|
||||||
|
|
||||||
# Start backend only
|
# Start backend only
|
||||||
[unix]
|
[unix]
|
||||||
@@ -115,25 +127,36 @@ dev-backend: _ensure-venv
|
|||||||
dev-frontend: _ensure-sidecar
|
dev-frontend: _ensure-sidecar
|
||||||
cd {{ tauri_dir }} && bun run tauri dev
|
cd {{ tauri_dir }} && bun run tauri dev
|
||||||
|
|
||||||
# Start backend + web app (no Tauri)
|
# Start backend (if not already running) + web app (no Tauri)
|
||||||
[unix]
|
[unix]
|
||||||
dev-web: _ensure-venv
|
dev-web: _ensure-venv
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
trap 'kill 0' EXIT
|
|
||||||
|
backend_pid=""
|
||||||
|
if curl -sf http://127.0.0.1:17493/health > /dev/null 2>&1; then
|
||||||
|
echo "Backend already running on http://localhost:17493"
|
||||||
|
else
|
||||||
|
echo "Starting backend on http://localhost:17493 ..."
|
||||||
{{ venv_bin }}/uvicorn backend.main:app --reload --port 17493 &
|
{{ venv_bin }}/uvicorn backend.main:app --reload --port 17493 &
|
||||||
|
backend_pid=$!
|
||||||
sleep 2
|
sleep 2
|
||||||
cd {{ web_dir }} && bun run dev &
|
fi
|
||||||
wait
|
|
||||||
|
trap '[ -n "$backend_pid" ] && kill "$backend_pid" 2>/dev/null; wait' EXIT
|
||||||
|
|
||||||
|
cd {{ web_dir }} && bun run dev
|
||||||
|
|
||||||
[windows]
|
[windows]
|
||||||
dev-web: _ensure-venv
|
dev-web: _ensure-venv
|
||||||
Write-Host "Starting backend on http://localhost:17493 ..."
|
$backendJob = $null
|
||||||
$backend = Start-Process -NoNewWindow -PassThru -FilePath "{{ python }}" -ArgumentList "-m", "uvicorn", "backend.main:app", "--reload", "--port", "17493"
|
try { $null = Invoke-WebRequest -Uri "http://127.0.0.1:17493/health" -UseBasicParsing -TimeoutSec 2 -ErrorAction Stop; Write-Host "Backend already running on http://localhost:17493" } catch { \
|
||||||
Start-Sleep -Seconds 2
|
Write-Host "Starting backend on http://localhost:17493 ..."; \
|
||||||
|
$backendJob = Start-Job -ScriptBlock { & "{{ python }}" -m uvicorn backend.main:app --reload --port 17493 } -WorkingDirectory (Get-Location); \
|
||||||
|
Start-Sleep -Seconds 2; \
|
||||||
|
}
|
||||||
Write-Host "Starting web app..."
|
Write-Host "Starting web app..."
|
||||||
$frontend = Start-Process -NoNewWindow -PassThru -WorkingDirectory "{{ web_dir }}" -FilePath "bun" -ArgumentList "run", "dev"
|
try { Set-Location "{{ web_dir }}"; bun run dev } finally { if ($backendJob) { Stop-Job $backendJob -ErrorAction SilentlyContinue; Remove-Job $backendJob -Force -ErrorAction SilentlyContinue } }
|
||||||
try { Wait-Process -Id $backend.Id, $frontend.Id } finally { Stop-Process -Id $backend.Id, $frontend.Id -ErrorAction SilentlyContinue }
|
|
||||||
|
|
||||||
# Kill all dev processes
|
# Kill all dev processes
|
||||||
[unix]
|
[unix]
|
||||||
|
|||||||
Reference in New Issue
Block a user