Fix web API URL for remote access (#550)

Default production web builds to the page origin so Docker and LAN users do not fetch from browser-local localhost. Preserve the local/Tauri fallback and repair stale persisted loopback URLs.
This commit is contained in:
Vincent Schäffer
2026-04-25 10:59:58 -07:00
committed by GitHub
parent ed2eec591a
commit 627d40b42d
3 changed files with 48 additions and 5 deletions
+10 -1
View File
@@ -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;
}
+35 -1
View File
@@ -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<ServerStore>()(
persist(
(set, get) => ({
serverUrl: 'http://127.0.0.1:17493',
serverUrl: getDefaultServerUrl(),
setServerUrl: (url) => {
const prev = get().serverUrl;
set({ serverUrl: url });
+3 -3
View File
@@ -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<string> {
// 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<string> {
// 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<void> {