diff --git a/app/src/App.tsx b/app/src/App.tsx index d277c802..ba07b7c1 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -11,7 +11,11 @@ import { cn } from '@/lib/utils/cn'; import { usePlatform } from '@/platform/PlatformContext'; import { router } from '@/router'; import { useLogStore } from '@/stores/logStore'; -import { useServerStore } from '@/stores/serverStore'; +import { + getDefaultServerUrl, + isLoopbackVoiceboxServerUrl, + useServerStore, +} from '@/stores/serverStore'; /** * Validate that a health response has the expected Voicebox-specific shape. @@ -105,6 +109,11 @@ function App() { // Setup window close handler and auto-start server when running in Tauri (production only) useEffect(() => { if (!platform.metadata.isTauri) { + const serverUrl = getDefaultServerUrl(); + const currentServerUrl = useServerStore.getState().serverUrl; + if (currentServerUrl !== serverUrl && isLoopbackVoiceboxServerUrl(currentServerUrl)) { + useServerStore.getState().setServerUrl(serverUrl); + } setServerReady(true); // Web assumes server is running return; } diff --git a/app/src/stores/serverStore.ts b/app/src/stores/serverStore.ts index c25deba7..d668ebba 100644 --- a/app/src/stores/serverStore.ts +++ b/app/src/stores/serverStore.ts @@ -39,10 +39,44 @@ function invalidateAllServerData() { queryClient.invalidateQueries(); } +export function getDefaultServerUrl(): string { + const fallback = 'http://127.0.0.1:17493'; + + if (!import.meta.env.PROD || typeof window === 'undefined') { + return fallback; + } + + const { protocol, origin, hostname } = window.location; + if ( + (protocol === 'http:' || protocol === 'https:') && + origin && + hostname !== 'tauri.localhost' + ) { + return origin; + } + + return fallback; +} + +export function isLoopbackVoiceboxServerUrl(url: string): boolean { + try { + const parsed = new URL(url); + return ( + parsed.port === '17493' && + (parsed.hostname === '127.0.0.1' || + parsed.hostname === 'localhost' || + parsed.hostname === '[::1]' || + parsed.hostname === '::1') + ); + } catch { + return false; + } +} + export const useServerStore = create()( persist( (set, get) => ({ - serverUrl: 'http://127.0.0.1:17493', + serverUrl: getDefaultServerUrl(), setServerUrl: (url) => { const prev = get().serverUrl; set({ serverUrl: url }); diff --git a/web/src/platform/lifecycle.ts b/web/src/platform/lifecycle.ts index 14156180..20fa40c8 100644 --- a/web/src/platform/lifecycle.ts +++ b/web/src/platform/lifecycle.ts @@ -1,12 +1,12 @@ import type { PlatformLifecycle, ServerLogEntry } from '@/platform/types'; +import { getDefaultServerUrl } from '@/stores/serverStore'; class WebLifecycle implements PlatformLifecycle { onServerReady?: () => void; async startServer(_remote = false, _modelsDir?: string | null): Promise { // Web assumes server is running externally - // Return a default URL - this should be configured via env vars - const serverUrl = import.meta.env.VITE_SERVER_URL || 'http://localhost:17493'; + const serverUrl = import.meta.env.VITE_SERVER_URL || getDefaultServerUrl(); this.onServerReady?.(); return serverUrl; } @@ -17,7 +17,7 @@ class WebLifecycle implements PlatformLifecycle { async restartServer(_modelsDir?: string | null): Promise { // No-op for web - server is managed externally - return import.meta.env.VITE_SERVER_URL || 'http://localhost:17493'; + return import.meta.env.VITE_SERVER_URL || getDefaultServerUrl(); } async setKeepServerRunning(_keep: boolean): Promise {