Skip to content

Commit e0539eb

Browse files
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
1 parent 5d0a40f commit e0539eb

File tree

14 files changed

+22
-57
lines changed

14 files changed

+22
-57
lines changed

tools/server/server.cpp

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5262,42 +5262,6 @@ int main(int argc, char ** argv) {
52625262
svr->Get (params.api_prefix + "/slots", handle_slots);
52635263
svr->Post(params.api_prefix + "/slots/:id_slot", handle_slots_action);
52645264

5265-
// SPA fallback route - serve index.html for any route that doesn't match API endpoints
5266-
// This enables client-side routing for dynamic routes like /chat/[id]
5267-
if (params.webui && params.public_path.empty()) {
5268-
// Only add fallback when using embedded static files
5269-
svr->Get(".*", [](const httplib::Request & req, httplib::Response & res) {
5270-
// Skip API routes - they should have been handled above
5271-
if (req.path.find("/v1/") != std::string::npos ||
5272-
req.path.find("/health") != std::string::npos ||
5273-
req.path.find("/metrics") != std::string::npos ||
5274-
req.path.find("/props") != std::string::npos ||
5275-
req.path.find("/models") != std::string::npos ||
5276-
req.path.find("/api/tags") != std::string::npos ||
5277-
req.path.find("/completions") != std::string::npos ||
5278-
req.path.find("/chat/completions") != std::string::npos ||
5279-
req.path.find("/embeddings") != std::string::npos ||
5280-
req.path.find("/tokenize") != std::string::npos ||
5281-
req.path.find("/detokenize") != std::string::npos ||
5282-
req.path.find("/lora-adapters") != std::string::npos ||
5283-
req.path.find("/slots") != std::string::npos) {
5284-
return false; // Let other handlers process API routes
5285-
}
5286-
5287-
// Serve index.html for all other routes (SPA fallback)
5288-
if (req.get_header_value("Accept-Encoding").find("gzip") == std::string::npos) {
5289-
res.set_content("Error: gzip is not supported by this browser", "text/plain");
5290-
} else {
5291-
res.set_header("Content-Encoding", "gzip");
5292-
// COEP and COOP headers, required by pyodide (python interpreter)
5293-
res.set_header("Cross-Origin-Embedder-Policy", "require-corp");
5294-
res.set_header("Cross-Origin-Opener-Policy", "same-origin");
5295-
res.set_content(reinterpret_cast<const char*>(index_html_gz), index_html_gz_len, "text/html; charset=utf-8");
5296-
}
5297-
return false;
5298-
});
5299-
}
5300-
53015265
//
53025266
// Start the server
53035267
//

tools/server/webui/src/lib/components/app/chat/ChatSidebar/ChatSidebar.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,13 @@
6464
searchQuery = '';
6565
}
6666
67-
await goto(`/chat/${id}`);
67+
await goto(`#/chat/${id}`);
6868
}
6969
</script>
7070

7171
<ScrollArea class="h-[100vh]">
7272
<Sidebar.Header class=" top-0 z-10 gap-6 bg-sidebar/50 px-4 pt-4 pb-2 backdrop-blur-lg md:sticky">
73-
<a href="/" onclick={handleMobileSidebarItemClick}>
73+
<a href="#/" onclick={handleMobileSidebarItemClick}>
7474
<h1 class="inline-flex items-center gap-1 px-2 text-xl font-semibold">llama.cpp</h1>
7575
</a>
7676

tools/server/webui/src/lib/components/app/chat/ChatSidebar/ChatSidebarActions.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
{:else}
5252
<Button
5353
class="w-full justify-between hover:[&>kbd]:opacity-100"
54-
href="/?new_chat=true"
54+
href="?new_chat=true#/"
5555
onclick={handleMobileSidebarItemClick}
5656
variant="ghost"
5757
>

tools/server/webui/src/lib/components/app/server/ServerErrorSplash.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
updateConfig('apiKey', apiKeyInput.trim());
6565
6666
// Test the API key by making a real request to the server
67-
const response = await fetch('/props', {
67+
const response = await fetch('./props', {
6868
headers: {
6969
'Content-Type': 'application/json',
7070
Authorization: `Bearer ${apiKeyInput.trim()}`
@@ -77,7 +77,7 @@
7777
7878
// Show success state briefly, then navigate to home
7979
setTimeout(() => {
80-
goto('/');
80+
goto(`#/`);
8181
}, 1000);
8282
} else {
8383
// API key is invalid - User Story A

tools/server/webui/src/lib/services/chat.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export class ChatService {
164164
const currentConfig = config();
165165
const apiKey = currentConfig.apiKey?.toString().trim();
166166

167-
const response = await fetch(`/v1/chat/completions`, {
167+
const response = await fetch(`./v1/chat/completions`, {
168168
method: 'POST',
169169
headers: {
170170
'Content-Type': 'application/json',
@@ -533,7 +533,7 @@ export class ChatService {
533533
const currentConfig = config();
534534
const apiKey = currentConfig.apiKey?.toString().trim();
535535

536-
const response = await fetch(`/props`, {
536+
const response = await fetch(`./props`, {
537537
headers: {
538538
'Content-Type': 'application/json',
539539
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {})

tools/server/webui/src/lib/services/slots.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ export class SlotsService {
138138
const currentConfig = config();
139139
const apiKey = currentConfig.apiKey?.toString().trim();
140140

141-
const response = await fetch('/slots', {
141+
const response = await fetch(`./slots`, {
142142
headers: {
143143
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {})
144144
}

tools/server/webui/src/lib/stores/chat.svelte.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class ChatStore {
100100

101101
this.maxContextError = null;
102102

103-
await goto(`/chat/${conversation.id}`);
103+
await goto(`#/chat/${conversation.id}`);
104104

105105
return conversation.id;
106106
}
@@ -910,7 +910,7 @@ class ChatStore {
910910
if (this.activeConversation?.id === convId) {
911911
this.activeConversation = null;
912912
this.activeMessages = [];
913-
await goto('/?new_chat=true');
913+
await goto(`?new_chat=true#/`);
914914
}
915915
} catch (error) {
916916
console.error('Failed to delete conversation:', error);

tools/server/webui/src/lib/stores/server.svelte.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ class ServerStore {
9898
const currentConfig = config();
9999
const apiKey = currentConfig.apiKey?.toString().trim();
100100

101-
const response = await fetch('/slots', {
101+
const response = await fetch(`./slots`, {
102102
headers: {
103103
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {})
104104
}

tools/server/webui/src/lib/utils/api-key-validation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export async function validateApiKey(fetch: typeof globalThis.fetch): Promise<vo
2222
headers.Authorization = `Bearer ${apiKey}`;
2323
}
2424

25-
const response = await fetch('/props', { headers });
25+
const response = await fetch(`./props`, { headers });
2626

2727
if (!response.ok) {
2828
if (response.status === 401 || response.status === 403) {

tools/server/webui/src/routes/+error.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
1818
function handleRetry() {
1919
// Navigate back to home page after successful API key validation
20-
goto('/');
20+
goto('#/');
2121
}
2222
</script>
2323

@@ -60,7 +60,7 @@
6060
</p>
6161
</div>
6262
<button
63-
onclick={() => goto('/')}
63+
onclick={() => goto('#/')}
6464
class="rounded-md bg-primary px-4 py-2 text-primary-foreground hover:bg-primary/90"
6565
>
6666
Go Home

0 commit comments

Comments
 (0)