1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//! Web UI module — serves a minimal HTML chat interface from the Axum server.
//!
//! # Endpoints
//!
//! | Method | Path | Description |
//! |--------|------|-------------|
//! | `GET` | `/ui` | Serves the embedded [`CHAT_UI_HTML`] page |
//! | `GET` | `/ui/health` | Returns `{"status":"ok"}` |
//!
//! The HTML is embedded at compile time via [`include_str!`] from
//! `assets/chat.html`. No runtime file I/O is required.
//!
//! # Example
//!
//! ```rust,no_run
//! use oxibonsai_runtime::web_ui::create_ui_router;
//!
//! let router = create_ui_router();
//! // Merge into an existing Axum router:
//! // let app = existing_router.merge(router);
//! ```
use axum::{
http::{header, StatusCode},
response::{IntoResponse, Response},
routing::get,
Router,
};
// ─── Embedded HTML ────────────────────────────────────────────────────────────
/// The single-page chat UI, embedded from `assets/chat.html` at compile time.
///
/// This is a pure HTML/CSS/JS application with no external CDN dependencies.
/// It communicates with the inference server via `POST /v1/chat/completions`.
pub const CHAT_UI_HTML: &str = include_str!("../assets/chat.html");
// ─── Handlers ─────────────────────────────────────────────────────────────────
/// Serve the embedded chat UI as `text/html; charset=utf-8`.
async fn serve_chat_ui() -> Response {
(
StatusCode::OK,
[(header::CONTENT_TYPE, "text/html; charset=utf-8")],
CHAT_UI_HTML,
)
.into_response()
}
/// Liveness probe for the UI sub-router.
///
/// Returns `200 OK` with body `{"status":"ok"}`.
async fn ui_health() -> Response {
(
StatusCode::OK,
[(header::CONTENT_TYPE, "application/json")],
r#"{"status":"ok"}"#,
)
.into_response()
}
// ─── Router ───────────────────────────────────────────────────────────────────
/// Build the UI sub-router.
///
/// Routes:
/// - `GET /ui` → `serve_chat_ui`
/// - `GET /ui/health` → `ui_health`
///
/// Merge this into an existing [`axum::Router`] with
/// [`Router::merge`](axum::Router::merge).
pub fn create_ui_router() -> Router {
Router::new()
.route("/ui", get(serve_chat_ui))
.route("/ui/health", get(ui_health))
}
// ─── Unit tests ───────────────────────────────────────────────────────────────
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chat_ui_html_is_nonempty() {
assert!(!CHAT_UI_HTML.is_empty(), "CHAT_UI_HTML must not be empty");
}
#[test]
fn chat_ui_html_contains_doctype() {
assert!(
CHAT_UI_HTML.contains("<!DOCTYPE html>"),
"HTML should start with DOCTYPE"
);
}
#[test]
fn chat_ui_html_contains_fetch() {
assert!(
CHAT_UI_HTML.contains("fetch"),
"HTML should use fetch() for API calls"
);
}
#[test]
fn create_ui_router_does_not_panic() {
let _router = create_ui_router();
}
}