Windows dev experience: CUDA detection, GPU display cleanup, hide drag region, dev-mode update status

This commit is contained in:
Jamie Pine
2026-03-14 03:24:39 -07:00
parent e18757bab3
commit e89a7eb7e7
7 changed files with 175 additions and 142 deletions
@@ -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>
); );
+5 -6
View File
@@ -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]"
/>
);
} }
+5 -2
View File
@@ -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
View File
@@ -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"),
) )
+42 -19
View File
@@ -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]