hetki ennen webgpu inferenssiä

This commit is contained in:
2026-04-02 12:49:40 +03:00
parent d2920e5ab4
commit e1326b145e
10 changed files with 375 additions and 80 deletions

View File

@@ -157,13 +157,30 @@ async function load() {
{v: stats.avg_overhead_pct + '%', l: 'FI ylikust. (ka.)'},
].map(s => `<div class="stat-card"><div class="val">${s.v}</div><div class="label">${s.l}</div></div>`).join('');
// Sessions
// Sessions — lajittelu: 1) aktiiviset nodet (online + ei viewer), 2) katsojat (online + viewer), 3) offline
const taskNames = {'tokenize':'Tokenisaatio','smollm-135m':'SmolLM 135M','qwen-05b':'Qwen2.5 0.5B','phi3-mini':'Phi-3 Mini','qwen-coder-05b':'Coder 0.5B','qwen-coder-3b':'Coder 3B','viewer':'Katsoja'};
sessions.sort((a, b) => {
const aOnline = !a.disconnected_at;
const bOnline = !b.disconnected_at;
const aViewer = a.selected_task === 'viewer';
const bViewer = b.selected_task === 'viewer';
// Online ennen offlinea
if (aOnline !== bOnline) return aOnline ? -1 : 1;
// Online: aktiiviset nodet ennen katsojia
if (aOnline && bOnline && aViewer !== bViewer) return aViewer ? 1 : -1;
// Saman ryhmän sisällä: uusin ensin
return new Date(b.connected_at) - new Date(a.connected_at);
});
document.getElementById('sessions-body').innerHTML = sessions.map(s => {
const online = !s.disconnected_at;
const status = online ? '<span class="online">ONLINE</span>' : '<span class="offline">offline</span>';
const isViewer = s.selected_task === 'viewer';
const status = online
? (isViewer ? '<span style="color:#d29922">CONNECTED</span>' : '<span class="online">ACTIVE</span>')
: '<span class="offline">offline</span>';
const typeBadge = s.node_type === 'native' ? badge('native','blue') : badge('browser','yellow');
const taskNames = {'tokenize':'Tokenisaatio','smollm-135m':'SmolLM 135M','qwen-05b':'Qwen2.5 0.5B','phi3-mini':'Phi-3 Mini'};
const taskBadge = badge(taskNames[s.selected_task] || s.selected_task || 'tokenize', s.selected_task === 'tokenize' ? 'green' : 'blue');
const taskColor = isViewer ? 'yellow' : s.selected_task === 'tokenize' ? 'green' : 'blue';
const taskBadge = badge(taskNames[s.selected_task] || s.selected_task || '?', taskColor);
const gpuBadge = s.has_webgpu ? badge('WebGPU','green') : badge('CPU','red');
const gpu = s.gpu_name ? `${s.gpu_name}` : '-';
const vram = s.vram_total_mb ? `${s.vram_total_mb} MB` : '-';
@@ -346,27 +363,73 @@ async fn main() {
}
async fn api_sessions(
headers: axum::http::HeaderMap,
axum::extract::State(state): axum::extract::State<Arc<AppState>>,
) -> impl IntoResponse {
axum::Json(state.db.get_sessions(200))
) -> axum::response::Response {
if !check_admin_auth(&headers) { return admin_unauthorized(); }
axum::Json(state.db.get_sessions(200)).into_response()
}
async fn api_pairs(
headers: axum::http::HeaderMap,
axum::extract::State(state): axum::extract::State<Arc<AppState>>,
) -> impl IntoResponse {
axum::Json(state.db.get_pair_results(500))
) -> axum::response::Response {
if !check_admin_auth(&headers) { return admin_unauthorized(); }
axum::Json(state.db.get_pair_results(500)).into_response()
}
async fn api_stats(
headers: axum::http::HeaderMap,
axum::extract::State(state): axum::extract::State<Arc<AppState>>,
) -> impl IntoResponse {
) -> axum::response::Response {
if !check_admin_auth(&headers) { return admin_unauthorized(); }
let mut stats = state.db.get_stats();
stats.as_object_mut().unwrap().insert("version".to_string(), serde_json::json!(env!("CARGO_PKG_VERSION")));
axum::Json(stats)
axum::Json(stats).into_response()
}
async fn admin_page() -> impl IntoResponse {
axum::response::Html(ADMIN_HTML)
fn check_admin_auth(headers: &axum::http::HeaderMap) -> bool {
let password = std::env::var("ADMIN_PASSWORD").unwrap_or_else(|_| "kipina".to_string());
if let Some(auth) = headers.get("authorization").and_then(|v| v.to_str().ok()) {
if auth.starts_with("Basic ") {
if let Ok(decoded) = String::from_utf8(
base64_decode(auth.trim_start_matches("Basic ").trim())
) {
// Tarkistetaan "user:password" — käyttäjänimi ei väliä
if let Some(pass) = decoded.split(':').nth(1) {
return pass == password;
}
}
}
}
false
}
fn base64_decode(input: &str) -> Vec<u8> {
// Yksinkertainen base64-dekooderi
const TABLE: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = Vec::new();
let bytes: Vec<u8> = input.bytes().filter(|&b| b != b'=').collect();
for chunk in bytes.chunks(4) {
let vals: Vec<u8> = chunk.iter().filter_map(|&b| TABLE.iter().position(|&t| t == b).map(|p| p as u8)).collect();
if vals.len() >= 2 { out.push((vals[0] << 2) | (vals[1] >> 4)); }
if vals.len() >= 3 { out.push((vals[1] << 4) | (vals[2] >> 2)); }
if vals.len() >= 4 { out.push((vals[2] << 6) | vals[3]); }
}
out
}
fn admin_unauthorized() -> axum::response::Response {
axum::response::Response::builder()
.status(401)
.header("WWW-Authenticate", "Basic realm=\"Kipinä Admin\"")
.body(axum::body::Body::from("Unauthorized"))
.unwrap()
}
async fn admin_page(headers: axum::http::HeaderMap) -> axum::response::Response {
if !check_admin_auth(&headers) { return admin_unauthorized(); }
axum::response::Html(ADMIN_HTML).into_response()
}
async fn ws_handler(