From e0539eb6aed346d4b25a6ea019044e88771e7690 Mon Sep 17 00:00:00 2001
From: Isaac McFadyen
Date: Fri, 26 Sep 2025 11:36:48 -0400
Subject: [PATCH] webui: switch to hash-based routing (alternative of #16079)
(#16157)
* Switched web UI to hash-based routing
* Added hash to missed goto function call
* Removed outdated SPA handling code
* Fixed broken sidebar home link
---
tools/server/server.cpp | 36 -------------------
.../app/chat/ChatSidebar/ChatSidebar.svelte | 4 +--
.../ChatSidebar/ChatSidebarActions.svelte | 2 +-
.../app/server/ServerErrorSplash.svelte | 4 +--
tools/server/webui/src/lib/services/chat.ts | 4 +--
tools/server/webui/src/lib/services/slots.ts | 2 +-
.../webui/src/lib/stores/chat.svelte.ts | 4 +--
.../webui/src/lib/stores/server.svelte.ts | 2 +-
.../webui/src/lib/utils/api-key-validation.ts | 2 +-
tools/server/webui/src/routes/+error.svelte | 4 +--
tools/server/webui/src/routes/+layout.svelte | 4 +--
tools/server/webui/src/routes/+layout.ts | 3 --
.../webui/src/routes/chat/[id]/+page.svelte | 4 +--
tools/server/webui/svelte.config.js | 4 +++
14 files changed, 22 insertions(+), 57 deletions(-)
delete mode 100644 tools/server/webui/src/routes/+layout.ts
diff --git a/tools/server/server.cpp b/tools/server/server.cpp
index 129801fe06..6062904a8c 100644
--- a/tools/server/server.cpp
+++ b/tools/server/server.cpp
@@ -5262,42 +5262,6 @@ int main(int argc, char ** argv) {
svr->Get (params.api_prefix + "/slots", handle_slots);
svr->Post(params.api_prefix + "/slots/:id_slot", handle_slots_action);
- // SPA fallback route - serve index.html for any route that doesn't match API endpoints
- // This enables client-side routing for dynamic routes like /chat/[id]
- if (params.webui && params.public_path.empty()) {
- // Only add fallback when using embedded static files
- svr->Get(".*", [](const httplib::Request & req, httplib::Response & res) {
- // Skip API routes - they should have been handled above
- if (req.path.find("/v1/") != std::string::npos ||
- req.path.find("/health") != std::string::npos ||
- req.path.find("/metrics") != std::string::npos ||
- req.path.find("/props") != std::string::npos ||
- req.path.find("/models") != std::string::npos ||
- req.path.find("/api/tags") != std::string::npos ||
- req.path.find("/completions") != std::string::npos ||
- req.path.find("/chat/completions") != std::string::npos ||
- req.path.find("/embeddings") != std::string::npos ||
- req.path.find("/tokenize") != std::string::npos ||
- req.path.find("/detokenize") != std::string::npos ||
- req.path.find("/lora-adapters") != std::string::npos ||
- req.path.find("/slots") != std::string::npos) {
- return false; // Let other handlers process API routes
- }
-
- // Serve index.html for all other routes (SPA fallback)
- if (req.get_header_value("Accept-Encoding").find("gzip") == std::string::npos) {
- res.set_content("Error: gzip is not supported by this browser", "text/plain");
- } else {
- res.set_header("Content-Encoding", "gzip");
- // COEP and COOP headers, required by pyodide (python interpreter)
- res.set_header("Cross-Origin-Embedder-Policy", "require-corp");
- res.set_header("Cross-Origin-Opener-Policy", "same-origin");
- res.set_content(reinterpret_cast(index_html_gz), index_html_gz_len, "text/html; charset=utf-8");
- }
- return false;
- });
- }
-
//
// Start the server
//
diff --git a/tools/server/webui/src/lib/components/app/chat/ChatSidebar/ChatSidebar.svelte b/tools/server/webui/src/lib/components/app/chat/ChatSidebar/ChatSidebar.svelte
index 05c775f2f5..6af348e696 100644
--- a/tools/server/webui/src/lib/components/app/chat/ChatSidebar/ChatSidebar.svelte
+++ b/tools/server/webui/src/lib/components/app/chat/ChatSidebar/ChatSidebar.svelte
@@ -64,13 +64,13 @@
searchQuery = '';
}
- await goto(`/chat/${id}`);
+ await goto(`#/chat/${id}`);
}