Skip to content

Commit 89e64a4

Browse files
committed
fix: enable WASM build for egui web UI and fix MIME types
Gate native-only dependencies (tokio, axum, teloxide) behind cfg(not(wasm32)) so localgpt-server compiles for wasm32 target. Add getrandom wasm_js support, enable glow rendering backend for eframe, fix egui 0.33 API changes, and add explicit MIME type overrides for .js and .wasm files served by the embedded HTTP server.
1 parent e18251d commit 89e64a4

5 files changed

Lines changed: 33 additions & 20 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/server/Cargo.toml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@ description = "LocalGPT HTTP server and Telegram bot"
1010
crate-type = ["cdylib", "rlib"]
1111

1212
[dependencies]
13+
serde = { workspace = true }
14+
serde_json = { workspace = true }
15+
16+
# Egui web UI (optional)
17+
eframe = { version = "0.33", optional = true, default-features = false, features = ["glow"] }
18+
19+
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
1320
localgpt-core = { workspace = true }
1421

1522
tokio = { workspace = true }
16-
serde = { workspace = true }
17-
serde_json = { workspace = true }
1823
tracing = { workspace = true }
1924
chrono = { workspace = true }
2025
anyhow = { workspace = true }
@@ -36,14 +41,15 @@ teloxide = { version = "0.17", features = ["macros"] }
3641
tokio-stream = "0.1"
3742
async-stream = "0.3"
3843

39-
# Egui web UI (optional)
40-
eframe = { version = "0.33", optional = true, default-features = false }
41-
4244
[target.'cfg(target_arch = "wasm32")'.dependencies]
4345
wasm-bindgen = "0.2"
4446
wasm-bindgen-futures = "0.4"
4547
web-sys = { version = "0.3", features = ["HtmlCanvasElement", "Window", "Document"] }
4648
console_error_panic_hook = "0.1"
49+
getrandom = { version = "0.3", features = ["wasm_js"] }
50+
51+
[package.metadata.wasm-pack.profile.release]
52+
wasm-opt = false
4753

4854
[features]
4955
default = []

crates/server/src/http.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -356,12 +356,14 @@ async fn serve_ui_file(Path(path): Path<String>) -> Response {
356356
fn serve_ui_asset(path: &str) -> Response {
357357
match UiAssets::get(path) {
358358
Some(content) => {
359-
let mime = mime_guess::from_path(path).first_or_octet_stream();
360-
(
361-
[(header::CONTENT_TYPE, mime.as_ref())],
362-
content.data.to_vec(),
363-
)
364-
.into_response()
359+
let mime = match path.rsplit('.').next() {
360+
Some("js") => "application/javascript".to_string(),
361+
Some("wasm") => "application/wasm".to_string(),
362+
_ => mime_guess::from_path(path)
363+
.first_or_octet_stream()
364+
.to_string(),
365+
};
366+
([(header::CONTENT_TYPE, mime)], content.data.to_vec()).into_response()
365367
}
366368
None => (StatusCode::NOT_FOUND, "Not found").into_response(),
367369
}

crates/server/src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
#[cfg(not(target_arch = "wasm32"))]
12
mod http;
3+
#[cfg(not(target_arch = "wasm32"))]
24
pub mod telegram;
5+
#[cfg(not(target_arch = "wasm32"))]
36
mod websocket;
47

58
#[cfg(feature = "egui-web")]
69
pub mod web;
710

11+
#[cfg(not(target_arch = "wasm32"))]
812
pub use http::Server;
913

1014
// WASM entry point for egui web UI
@@ -16,7 +20,7 @@ use wasm_bindgen::prelude::*;
1620
pub async fn start_web_ui(canvas_id: &str) -> Result<(), wasm_bindgen::JsValue> {
1721
// Redirect `log` message to `console.log` and friends:
1822
console_error_panic_hook::set_once();
19-
23+
2024
let web_options = eframe::WebOptions::default();
2125

2226
let document = web_sys::window()

crates/server/src/web/app.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,23 +47,23 @@ impl eframe::App for WebApp {
4747
ui.horizontal(|ui| {
4848
ui.heading("LocalGPT");
4949
ui.separator();
50-
50+
5151
// Status indicator
5252
let status_color = if self.status.connected {
5353
egui::Color32::GREEN
5454
} else {
5555
egui::Color32::RED
5656
};
5757
ui.colored_label(status_color, "●");
58-
58+
5959
ui.label(format!("Model: {}", self.status.model));
60-
60+
6161
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
6262
if ui.button("New Session").clicked() {
6363
self.messages.clear();
6464
self.session_id = None;
6565
}
66-
66+
6767
if let Some(ref id) = self.session_id {
6868
// Safe truncation that respects UTF-8 character boundaries
6969
let truncated: String = id.chars().take(8).collect();
@@ -107,7 +107,7 @@ impl eframe::App for WebApp {
107107
.hint_text("Type a message..."),
108108
);
109109

110-
let enter_without_shift = input_response.has_focus()
110+
let enter_without_shift = input_response.has_focus()
111111
&& ui.input(|i| i.key_pressed(egui::Key::Enter) && !i.modifiers.shift);
112112

113113
if ui.button("Send").clicked() || enter_without_shift {
@@ -127,10 +127,10 @@ impl WebApp {
127127
egui::Color32::from_rgb(30, 50, 40)
128128
};
129129

130-
egui::Frame::none()
130+
egui::Frame::NONE
131131
.fill(bg_color)
132-
.inner_margin(egui::Margin::same(10.0))
133-
.corner_radius(egui::CornerRadius::same(5.0))
132+
.inner_margin(egui::Margin::same(10))
133+
.corner_radius(egui::CornerRadius::same(5))
134134
.show(ui, |ui| {
135135
ui.horizontal(|ui| {
136136
let role_color = if msg.role == "user" {

0 commit comments

Comments
 (0)