From 97608643e49d0df095b262e90ad94a30d2df9a56 Mon Sep 17 00:00:00 2001 From: SelfRef Date: Sat, 22 Aug 2026 18:28:08 +0200 Subject: [PATCH] Make embedded WebUI work behind a path-prefix reverse proxy Switch the SvelteKit router to hash mode so client-side routing no longer depends on the pathname the page is served under, and resolve API endpoint paths against the page directory so requests stay inside the proxy prefix (e.g. llama-swap's /upstream//). Serving at the domain root is unaffected. Fixes #294 --- webui/native/dist/index.html | 22 +++++++++++----------- webui/native/src/lib/api.ts | 8 ++++++-- webui/native/src/routes/+page.ts | 2 -- webui/native/svelte.config.js | 3 +++ 4 files changed, 20 insertions(+), 15 deletions(-) delete mode 100644 webui/native/src/routes/+page.ts diff --git a/webui/native/dist/index.html b/webui/native/dist/index.html index d9f133f2..3e597f38 100644 --- a/webui/native/dist/index.html +++ b/webui/native/dist/index.html @@ -5,7 +5,7 @@ - + @@ -13,20 +13,20 @@
diff --git a/webui/native/src/lib/api.ts b/webui/native/src/lib/api.ts index 6695ca93..b435ba52 100644 --- a/webui/native/src/lib/api.ts +++ b/webui/native/src/lib/api.ts @@ -1,5 +1,9 @@ import type { LoadedModel, ServerHealth } from './types'; +function apiUrl(path: string): string { + return new URL(path.replace(/^\//, ''), document.baseURI).toString(); +} + async function errorFrom(response: Response): Promise { let message = `${response.status} ${response.statusText}`; try { @@ -21,7 +25,7 @@ export async function jsonRequest( if (init.body && !(init.body instanceof FormData) && !headers.has('Content-Type')) { headers.set('Content-Type', 'application/json'); } - const response = await fetch(path, { ...init, headers, signal }); + const response = await fetch(apiUrl(path), { ...init, headers, signal }); if (!response.ok) throw await errorFrom(response); return response.json() as Promise; } @@ -168,7 +172,7 @@ export async function uploadWav(blob: Blob, signal?: AbortSignal): Promise, signal?: AbortSignal) { - const response = await fetch('/v1/audio/speech', { + const response = await fetch(apiUrl('/v1/audio/speech'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), diff --git a/webui/native/src/routes/+page.ts b/webui/native/src/routes/+page.ts deleted file mode 100644 index ceccaaf6..00000000 --- a/webui/native/src/routes/+page.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const prerender = true; -export const ssr = false; diff --git a/webui/native/svelte.config.js b/webui/native/svelte.config.js index 303b3035..a5ce70d0 100644 --- a/webui/native/svelte.config.js +++ b/webui/native/svelte.config.js @@ -12,6 +12,9 @@ const config = { output: { bundleStrategy: 'inline' }, + router: { + type: 'hash' + }, paths: { relative: true }