Skip to content

Commit d1a8bdc

Browse files
authored
feat: Add Jan API server Swagger UI (#6502)
* feat: Add Jan API server Swagger UI - Serve OpenAPI spec (`static/openapi.json`) directly from the proxy server. - Implement Swagger UI assets (`swagger-ui.css`, `swagger-ui-bundle.js`, `favicon.ico`) and a simple HTML wrapper under `/docs`. - Extend the proxy whitelist to include Swagger UI routes. - Add routing logic for `/openapi.json`, `/docs`, and Swagger UI static files. - Update whitelisted paths and integrate CORS handling for the new endpoints. * feat: serve Swagger UI at root path The Swagger UI endpoint previously lived under `/docs`. The route handling and exclusion list have been updated so the UI is now served directly at `/`. This simplifies access, aligns with the expected root URL in the Tauri frontend, and removes the now‑unused `/docs` path handling. * feat: add model loading state and translations for local API server Implemented a loading indicator for model startup, updated the start/stop button to reflect model loading and server starting states, and disabled interactions while pending. Added new translation keys (`loadingModel`, `startingServer`) across all supported locales (en, de, id, pl, vn, zh-CN, zh-TW) and integrated them into the UI. Included a small delay after model start to ensure backend state consistency. This improves user feedback and prevents race conditions during server initialization.
1 parent 359dd8f commit d1a8bdc

File tree

13 files changed

+835
-15
lines changed

13 files changed

+835
-15
lines changed

src-tauri/src/core/server/proxy.rs

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,14 @@ async fn proxy_request(
215215
let path = get_destination_path(original_path, &config.prefix);
216216
let method = parts.method.clone();
217217

218-
let whitelisted_paths = ["/", "/openapi.json", "/favicon.ico"];
218+
let whitelisted_paths = [
219+
"/",
220+
"/openapi.json",
221+
"/favicon.ico",
222+
"/docs/swagger-ui.css",
223+
"/docs/swagger-ui-bundle.js",
224+
"/docs/swagger-ui-standalone-preset.js",
225+
];
219226
let is_whitelisted_path = whitelisted_paths.contains(&path.as_str());
220227

221228
if !is_whitelisted_path {
@@ -448,6 +455,82 @@ async fn proxy_request(
448455

449456
return Ok(response_builder.body(Body::from(body_str)).unwrap());
450457
}
458+
459+
(hyper::Method::GET, "/openapi.json") => {
460+
let body = include_str!("../../../static/openapi.json"); // relative to src-tauri/src/
461+
return Ok(Response::builder()
462+
.status(StatusCode::OK)
463+
.header(hyper::header::CONTENT_TYPE, "application/json")
464+
.body(Body::from(body))
465+
.unwrap());
466+
}
467+
468+
// DOCS route
469+
(hyper::Method::GET, "/") => {
470+
let html = r#"
471+
<!DOCTYPE html>
472+
<html lang="en">
473+
<head>
474+
<meta charset="UTF-8">
475+
<title>API Docs</title>
476+
<link rel="stylesheet" type="text/css" href="/docs/swagger-ui.css">
477+
</head>
478+
<body>
479+
<div id="swagger-ui"></div>
480+
<script src="/docs/swagger-ui-bundle.js"></script>
481+
<script>
482+
window.onload = () => {
483+
SwaggerUIBundle({
484+
url: '/openapi.json',
485+
dom_id: '#swagger-ui',
486+
});
487+
};
488+
</script>
489+
</body>
490+
</html>
491+
"#;
492+
493+
let mut response_builder = Response::builder()
494+
.status(StatusCode::OK)
495+
.header(hyper::header::CONTENT_TYPE, "text/html");
496+
497+
response_builder = add_cors_headers_with_host_and_origin(
498+
response_builder,
499+
&host_header,
500+
&origin_header,
501+
&config.trusted_hosts,
502+
);
503+
504+
return Ok(response_builder.body(Body::from(html)).unwrap());
505+
}
506+
507+
(hyper::Method::GET, "/docs/swagger-ui.css") => {
508+
let css = include_str!("../../../static/swagger-ui/swagger-ui.css");
509+
return Ok(Response::builder()
510+
.status(StatusCode::OK)
511+
.header(hyper::header::CONTENT_TYPE, "text/css")
512+
.body(Body::from(css))
513+
.unwrap());
514+
}
515+
516+
(hyper::Method::GET, "/docs/swagger-ui-bundle.js") => {
517+
let js = include_str!("../../../static/swagger-ui/swagger-ui-bundle.js");
518+
return Ok(Response::builder()
519+
.status(StatusCode::OK)
520+
.header(hyper::header::CONTENT_TYPE, "application/javascript")
521+
.body(Body::from(js))
522+
.unwrap());
523+
}
524+
525+
(hyper::Method::GET, "/favicon.ico") => {
526+
let icon = include_bytes!("../../../static/swagger-ui/favicon.ico");
527+
return Ok(Response::builder()
528+
.status(StatusCode::OK)
529+
.header(hyper::header::CONTENT_TYPE, "image/x-icon")
530+
.body(Body::from(icon.as_ref()))
531+
.unwrap());
532+
}
533+
451534
_ => {
452535
let is_explicitly_whitelisted_get = method == hyper::Method::GET
453536
&& whitelisted_paths.contains(&destination_path.as_str());

0 commit comments

Comments
 (0)