From e89a7eb7e7f9bc68e52942cd8ac3ca8cc435223a Mon Sep 17 00:00:00 2001 From: Jamie Pine Date: Sat, 14 Mar 2026 03:24:30 -0700 Subject: [PATCH] Windows dev experience: CUDA detection, GPU display cleanup, hide drag region, dev-mode update status --- .../ServerSettings/ConnectionForm.tsx | 2 +- .../ServerSettings/GpuAcceleration.tsx | 48 +++-- .../ServerSettings/UpdateStatus.tsx | 176 +++++++++--------- app/src/components/TitleBarDragRegion.tsx | 11 +- app/src/lib/constants/ui.ts | 7 +- backend/main.py | 2 +- justfile | 71 ++++--- 7 files changed, 175 insertions(+), 142 deletions(-) diff --git a/app/src/components/ServerSettings/ConnectionForm.tsx b/app/src/components/ServerSettings/ConnectionForm.tsx index e84f20ea..c7963484 100644 --- a/app/src/components/ServerSettings/ConnectionForm.tsx +++ b/app/src/components/ServerSettings/ConnectionForm.tsx @@ -113,7 +113,7 @@ export function ConnectionForm() { GPU: {health.gpu_available ? 'Available' : 'Not Available'} - {health.vram_used_mb && ( + {health.vram_used_mb != null && health.vram_used_mb > 0 && ( VRAM: {health.vram_used_mb.toFixed(0)} MB )} diff --git a/app/src/components/ServerSettings/GpuAcceleration.tsx b/app/src/components/ServerSettings/GpuAcceleration.tsx index 94cc1d6d..bafcf738 100644 --- a/app/src/components/ServerSettings/GpuAcceleration.tsx +++ b/app/src/components/ServerSettings/GpuAcceleration.tsx @@ -26,7 +26,7 @@ export function GpuAcceleration() { // Query CUDA backend status const { data: cudaStatus, - isLoading: cudaStatusLoading, + isLoading: _cudaStatusLoading, refetch: refetchCudaStatus, } = useQuery({ queryKey: ['cuda-status', serverUrl], @@ -218,35 +218,33 @@ export function GpuAcceleration() { GPU Acceleration - {/* Current status */} + {/* GPU status */}
-
Backend
-
- {isCurrentlyCuda - ? 'CUDA (GPU accelerated)' - : hasNativeGpu - ? `${health.backend_type === 'mlx' ? 'MLX' : 'PyTorch'} (GPU accelerated)` - : 'CPU'} -
-
- - {/* GPU info from health */} - {health.gpu_type && ( -
-
GPU
-
{health.gpu_type}
- {health.vram_used_mb != null && ( -
- VRAM: {health.vram_used_mb.toFixed(0)} MB used + {health.gpu_available && health.gpu_type ? ( + <> +
+ {health.gpu_type.replace(/^(CUDA|ROCm|MPS|Metal|XPU|DirectML)\s*\((.+)\)$/, '$2') || + health.gpu_type}
- )} -
- )} +
+ {health.gpu_type.replace(/\s*\(.+\)$/, '')} + {health.vram_used_mb != null && health.vram_used_mb > 0 + ? ` \u00b7 ${health.vram_used_mb.toFixed(0)} MB VRAM used` + : ''} +
+ + ) : ( + <> +
CPU
+
No GPU acceleration available
+ + )} +
{/* Native GPU detected - no CUDA download needed */} - {/* CUDA download section - only show when native GPU is NOT detected (i.e., Windows/Linux NVIDIA users) */} - {!hasNativeGpu && ( + {/* CUDA download section - only show when no GPU is active (native or CUDA) */} + {!hasNativeGpu && !isCurrentlyCuda && ( <> {/* Download progress */} {cudaDownloading && downloadProgress && ( diff --git a/app/src/components/ServerSettings/UpdateStatus.tsx b/app/src/components/ServerSettings/UpdateStatus.tsx index b618a750..4af44fdd 100644 --- a/app/src/components/ServerSettings/UpdateStatus.tsx +++ b/app/src/components/ServerSettings/UpdateStatus.tsx @@ -11,6 +11,7 @@ export function UpdateStatus() { const platform = usePlatform(); const { status, checkForUpdates, downloadAndInstall, restartAndInstall } = useAutoUpdater(false); const [currentVersion, setCurrentVersion] = useState(''); + const isDev = !import.meta.env?.PROD; useEffect(() => { platform.metadata @@ -20,11 +21,7 @@ export function UpdateStatus() { }, [platform]); return ( - + App Updates @@ -32,97 +29,110 @@ export function UpdateStatus() {
Current Version
-
v{currentVersion}
+
+ v{currentVersion} + {isDev ? ' (dev)' : ''} +
- + {!isDev && ( + + )}
- {status.checking && ( -
- - Checking for updates... + {isDev ? ( +
+ Auto-updates are disabled in development mode.
- )} - - {status.error && ( -
- - {status.error} -
- )} - - {status.available && !status.downloading && !status.readyToInstall && ( -
-
-
-
Update Available
-
Version {status.version}
+ ) : ( + <> + {status.checking && ( +
+ + Checking for updates...
- New -
- -
- )} + )} - {status.downloading && ( -
-
-
- - Downloading update... + {status.error && ( +
+ + {status.error}
- {status.downloadProgress !== undefined && ( - {status.downloadProgress}% - )} -
- - {status.downloadedBytes !== undefined && - status.totalBytes !== undefined && - status.totalBytes > 0 && ( -
- {(status.downloadedBytes / 1024 / 1024).toFixed(1)} MB /{' '} - {(status.totalBytes / 1024 / 1024).toFixed(1)} MB + )} + + {status.available && !status.downloading && !status.readyToInstall && ( +
+
+
+
Update Available
+
Version {status.version}
+
+ New
- )} -
- )} + +
+ )} - {status.readyToInstall && ( -
-
-
-
Update Ready to Install
+ {status.downloading && ( +
+
+
+ + Downloading update... +
+ {status.downloadProgress !== undefined && ( + {status.downloadProgress}% + )} +
+ + {status.downloadedBytes !== undefined && + status.totalBytes !== undefined && + status.totalBytes > 0 && ( +
+ {(status.downloadedBytes / 1024 / 1024).toFixed(1)} MB /{' '} + {(status.totalBytes / 1024 / 1024).toFixed(1)} MB +
+ )} +
+ )} + + {status.readyToInstall && ( +
+
+
+
Update Ready to Install
+
+ Version {status.version} has been downloaded +
+
+
- Version {status.version} has been downloaded + The app needs to restart to complete the installation. You can do this now or + later at your convenience.
+
-
-
- The app needs to restart to complete the installation. You can do this now or later at - your convenience. -
- -
- )} + )} - {!status.available && !status.checking && !status.error && status.checking === false && ( -
- You're up to date -
+ {!status.available && !status.checking && !status.error && ( +
+ You're up to date +
+ )} + )} diff --git a/app/src/components/TitleBarDragRegion.tsx b/app/src/components/TitleBarDragRegion.tsx index 5da86ec4..4287ba13 100644 --- a/app/src/components/TitleBarDragRegion.tsx +++ b/app/src/components/TitleBarDragRegion.tsx @@ -1,8 +1,7 @@ +const isWindows = navigator.userAgent.includes('Windows'); + export function TitleBarDragRegion() { - return ( -
- ); + if (isWindows) return null; + + return
; } diff --git a/app/src/lib/constants/ui.ts b/app/src/lib/constants/ui.ts index 41a46483..af495e84 100644 --- a/app/src/lib/constants/ui.ts +++ b/app/src/lib/constants/ui.ts @@ -2,11 +2,14 @@ * 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 - * 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 diff --git a/backend/main.py b/backend/main.py index 487d35ab..9f1bce4a 100644 --- a/backend/main.py +++ b/backend/main.py @@ -263,7 +263,7 @@ async def health(): gpu_type=gpu_type, vram_used_mb=vram_used, 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"), ) diff --git a/justfile b/justfile index 95ae87b4..b1a9a1df 100644 --- a/justfile +++ b/justfile @@ -66,6 +66,11 @@ setup-python: } Write-Host "Installing Python dependencies..." & "{{ 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 --no-deps chatterbox-tts & "{{ pip }}" install git+https://github.com/QwenLM/Qwen3-TTS.git @@ -77,30 +82,37 @@ setup-js: # ─── Development ────────────────────────────────────────────────────── -# Start backend + frontend for development (two processes, one terminal) +# Start backend (if not already running) + frontend for development [unix] dev: _ensure-venv _ensure-sidecar #!/usr/bin/env bash set -euo pipefail - trap 'kill 0' EXIT - echo "Starting backend on http://localhost:17493 ..." - {{ venv_bin }}/uvicorn backend.main:app --reload --port 17493 & - sleep 2 + 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 & + backend_pid=$! + sleep 2 + fi + + trap '[ -n "$backend_pid" ] && kill "$backend_pid" 2>/dev/null; wait' EXIT echo "Starting Tauri desktop app..." - cd {{ tauri_dir }} && bun run tauri dev & - - wait + cd {{ tauri_dir }} && bun run tauri dev [windows] dev: _ensure-venv _ensure-sidecar - Write-Host "Starting backend on http://localhost:17493 ..." - $backend = Start-Process -NoNewWindow -PassThru -FilePath "{{ python }}" -ArgumentList "-m", "uvicorn", "backend.main:app", "--reload", "--port", "17493" - Start-Sleep -Seconds 2 + $backendJob = $null + 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 { \ + 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..." - $frontend = Start-Process -NoNewWindow -PassThru -WorkingDirectory "{{ tauri_dir }}" -FilePath "bun" -ArgumentList "run", "tauri", "dev" - try { Wait-Process -Id $backend.Id, $frontend.Id } finally { Stop-Process -Id $backend.Id, $frontend.Id -ErrorAction SilentlyContinue } + try { Set-Location "{{ tauri_dir }}"; bun run tauri dev } finally { if ($backendJob) { Stop-Job $backendJob -ErrorAction SilentlyContinue; Remove-Job $backendJob -Force -ErrorAction SilentlyContinue } } # Start backend only [unix] @@ -115,25 +127,36 @@ dev-backend: _ensure-venv dev-frontend: _ensure-sidecar cd {{ tauri_dir }} && bun run tauri dev -# Start backend + web app (no Tauri) +# Start backend (if not already running) + web app (no Tauri) [unix] dev-web: _ensure-venv #!/usr/bin/env bash set -euo pipefail - trap 'kill 0' EXIT - {{ venv_bin }}/uvicorn backend.main:app --reload --port 17493 & - sleep 2 - cd {{ web_dir }} && bun run dev & - wait + + 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 & + backend_pid=$! + sleep 2 + fi + + trap '[ -n "$backend_pid" ] && kill "$backend_pid" 2>/dev/null; wait' EXIT + + cd {{ web_dir }} && bun run dev [windows] dev-web: _ensure-venv - Write-Host "Starting backend on http://localhost:17493 ..." - $backend = Start-Process -NoNewWindow -PassThru -FilePath "{{ python }}" -ArgumentList "-m", "uvicorn", "backend.main:app", "--reload", "--port", "17493" - Start-Sleep -Seconds 2 + $backendJob = $null + 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 { \ + 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..." - $frontend = Start-Process -NoNewWindow -PassThru -WorkingDirectory "{{ web_dir }}" -FilePath "bun" -ArgumentList "run", "dev" - try { Wait-Process -Id $backend.Id, $frontend.Id } finally { Stop-Process -Id $backend.Id, $frontend.Id -ErrorAction SilentlyContinue } + try { Set-Location "{{ web_dir }}"; bun run dev } finally { if ($backendJob) { Stop-Job $backendJob -ErrorAction SilentlyContinue; Remove-Job $backendJob -Force -ErrorAction SilentlyContinue } } # Kill all dev processes [unix]