|
23 | 23 | #define JSON_ASSERT GGML_ASSERT |
24 | 24 | #include "json.hpp" |
25 | 25 | #include "index.html.gz.hpp" |
| 26 | +#include "index_llamacpp.html.gz.hpp" |
26 | 27 | #include "loading.html.hpp" |
27 | 28 |
|
28 | 29 | #include <atomic> |
@@ -4141,6 +4142,7 @@ int main(int argc, char ** argv) { |
4141 | 4142 | { "chat_template", common_chat_templates_source(ctx_server.chat_templates.get()) }, |
4142 | 4143 | { "bos_token", llama_token_to_piece(ctx_server.ctx, llama_token_bos(ctx_server.model), /* special= */ true)}, |
4143 | 4144 | { "eos_token", llama_token_to_piece(ctx_server.ctx, llama_token_eos(ctx_server.model), /* special= */ true)}, |
| 4145 | + { "model_path", ctx_server.params.model }, |
4144 | 4146 | { "n_ctx", ctx_server.n_ctx } |
4145 | 4147 |
|
4146 | 4148 | }; |
@@ -4998,38 +5000,51 @@ int main(int argc, char ** argv) { |
4998 | 5000 | // |
4999 | 5001 | // Router |
5000 | 5002 | // |
5001 | | - |
5002 | | - // register static assets routes |
5003 | | - if (!params.public_path.empty()) { |
5004 | | - // Set the base directory for serving static files |
5005 | | - svr->set_base_dir(params.public_path); |
| 5003 | + if (params.webui == COMMON_WEBUI_NONE) { |
| 5004 | + LLAMA_LOG_INFO("Web UI is disabled\n"); |
5006 | 5005 | } |
5007 | | - |
5008 | | - { |
| 5006 | + else { |
5009 | 5007 | // register static assets routes |
5010 | 5008 | if (!params.public_path.empty()) { |
5011 | 5009 | // Set the base directory for serving static files |
5012 | | - bool is_found = svr->set_mount_point("/", params.public_path); |
5013 | | - if (!is_found) { |
5014 | | - GGML_ABORT("%s: static assets path not found: %s\n", __func__, params.public_path.c_str()); |
5015 | | - return 1; |
5016 | | - } |
| 5010 | + svr->set_base_dir(params.public_path); |
5017 | 5011 | } |
5018 | | - else { |
5019 | | - // using embedded static index.html |
5020 | | - svr->Get("/", [](const httplib::Request& req, httplib::Response& res) { |
5021 | | - if (req.get_header_value("Accept-Encoding").find("gzip") == std::string::npos) { |
5022 | | - res.set_content("Error: gzip is not supported by this browser", "text/plain"); |
5023 | | - } |
5024 | | - else { |
5025 | | - res.set_header("Content-Encoding", "gzip"); |
5026 | | - // COEP and COOP headers, required by pyodide (python interpreter) |
5027 | | - res.set_header("Cross-Origin-Embedder-Policy", "require-corp"); |
5028 | | - res.set_header("Cross-Origin-Opener-Policy", "same-origin"); |
5029 | | - res.set_content(reinterpret_cast<const char*>(index_html_gz), index_html_gz_len, "text/html; charset=utf-8"); |
| 5012 | + |
| 5013 | + { |
| 5014 | + // register static assets routes |
| 5015 | + if (!params.public_path.empty()) { |
| 5016 | + // Set the base directory for serving static files |
| 5017 | + bool is_found = svr->set_mount_point("/", params.public_path); |
| 5018 | + if (!is_found) { |
| 5019 | + GGML_ABORT("%s: static assets path not found: %s\n", __func__, params.public_path.c_str()); |
| 5020 | + return 1; |
5030 | 5021 | } |
5031 | | - return false; |
5032 | | - }); |
| 5022 | + } |
| 5023 | + else { |
| 5024 | + |
| 5025 | + // using embedded static index.html |
| 5026 | + svr->Get("/", [params](const httplib::Request& req, httplib::Response& res) { |
| 5027 | + if (req.get_header_value("Accept-Encoding").find("gzip") == std::string::npos) { |
| 5028 | + res.set_content("Error: gzip is not supported by this browser", "text/plain"); |
| 5029 | + } |
| 5030 | + else { |
| 5031 | + res.set_header("Content-Encoding", "gzip"); |
| 5032 | + // COEP and COOP headers, required by pyodide (python interpreter) |
| 5033 | + res.set_header("Cross-Origin-Embedder-Policy", "require-corp"); |
| 5034 | + res.set_header("Cross-Origin-Opener-Policy", "same-origin"); |
| 5035 | + if (params.webui == COMMON_WEBUI_AUTO) { |
| 5036 | + res.set_content(reinterpret_cast<const char*>(index_html_gz), index_html_gz_len, "text/html; charset=utf-8"); |
| 5037 | + } |
| 5038 | + else if (params.webui == COMMON_WEBUI_LLAMACPP) { |
| 5039 | + res.set_content(reinterpret_cast<const char*>(index_llamacpp_html_gz), index_llamacpp_html_gz_len, "text/html; charset=utf-8"); |
| 5040 | + } |
| 5041 | + else { |
| 5042 | + res.set_content(reinterpret_cast<const char*>(index_html_gz), index_html_gz_len, "text/html; charset=utf-8"); |
| 5043 | + } |
| 5044 | + } |
| 5045 | + return false; |
| 5046 | + }); |
| 5047 | + } |
5033 | 5048 | } |
5034 | 5049 | } |
5035 | 5050 | // register API routes |
@@ -5062,6 +5077,42 @@ int main(int argc, char ** argv) { |
5062 | 5077 | svr->Post("/rename_prompt", rename_saved_prompt); |
5063 | 5078 |
|
5064 | 5079 | } |
| 5080 | + // SPA fallback route - serve index.html for any route that doesn't match API endpoints |
| 5081 | +// This enables client-side routing for dynamic routes like /chat/[id] |
| 5082 | + if (params.webui && params.public_path.empty()) { |
| 5083 | + // Only add fallback when using embedded static files |
| 5084 | + svr->Get(".*", [](const httplib::Request& req, httplib::Response& res) { |
| 5085 | + // Skip API routes - they should have been handled above |
| 5086 | + if (req.path.find("/v1/") != std::string::npos || |
| 5087 | + req.path.find("/health") != std::string::npos || |
| 5088 | + req.path.find("/metrics") != std::string::npos || |
| 5089 | + req.path.find("/props") != std::string::npos || |
| 5090 | + req.path.find("/models") != std::string::npos || |
| 5091 | + req.path.find("/api/tags") != std::string::npos || |
| 5092 | + req.path.find("/completions") != std::string::npos || |
| 5093 | + req.path.find("/chat/completions") != std::string::npos || |
| 5094 | + req.path.find("/embeddings") != std::string::npos || |
| 5095 | + req.path.find("/tokenize") != std::string::npos || |
| 5096 | + req.path.find("/detokenize") != std::string::npos || |
| 5097 | + req.path.find("/lora-adapters") != std::string::npos || |
| 5098 | + req.path.find("/slots") != std::string::npos) { |
| 5099 | + return false; // Let other handlers process API routes |
| 5100 | + } |
| 5101 | + |
| 5102 | + // Serve index.html for all other routes (SPA fallback) |
| 5103 | + if (req.get_header_value("Accept-Encoding").find("gzip") == std::string::npos) { |
| 5104 | + res.set_content("Error: gzip is not supported by this browser", "text/plain"); |
| 5105 | + } |
| 5106 | + else { |
| 5107 | + res.set_header("Content-Encoding", "gzip"); |
| 5108 | + // COEP and COOP headers, required by pyodide (python interpreter) |
| 5109 | + res.set_header("Cross-Origin-Embedder-Policy", "require-corp"); |
| 5110 | + res.set_header("Cross-Origin-Opener-Policy", "same-origin"); |
| 5111 | + res.set_content(reinterpret_cast<const char*>(index_html_gz), index_html_gz_len, "text/html; charset=utf-8"); |
| 5112 | + } |
| 5113 | + return false; |
| 5114 | + }); |
| 5115 | + } |
5065 | 5116 | svr->Get ("/version", handle_version); |
5066 | 5117 | if (!params.sql_save_file.empty()) { |
5067 | 5118 | // these endpoints rely on sql_save_file existing |
|
0 commit comments