feat: complete revolution architecture modernization

- Decoupled robust frontend into an Astro framework in `frontend/`.
- Replaced direct WebSocket broadcast with Smart Routing to distribute workload only to idle capable nodes, preventing 503 errors and duplicate responses.
- Rewrote WASM panic points (`unwrap()` handling) into panic-safe match blocks in qwen_coder.rs preventing Node Web Workers from crashing.
- Integrated robust dynamic Three.js 3D visualization.
- Resolved mermaid and THREE.js frontend hydration issues.
This commit is contained in:
Jaakko Vanhala
2026-04-09 16:38:24 +03:00
parent 84b78eb9c6
commit 857afbe111
98 changed files with 26637 additions and 40 deletions

View File

@@ -330,15 +330,6 @@ async fn main() {
let idx = (rng_state as usize) % pairs.len();
let (en, fi) = pairs[idx];
// Tokenisointiparit
let pair_msg = serde_json::json!({
"type": "pair_task",
"en": en,
"fi": fi,
});
let _ = state_for_task.stats_tx.send(pair_msg.to_string());
// LLM-promptit
let llm_prompts = vec![
"Tell me a short joke.",
"What is WebGPU in one sentence?",
@@ -348,33 +339,39 @@ async fn main() {
];
let llm_idx = (rng_state as usize / 7) % llm_prompts.len();
// SmolLM-prompt
let smollm_msg = serde_json::json!({
"type": "llm_prompt",
"prompt": llm_prompts[llm_idx],
"model": "smollm-135m",
});
let _ = state_for_task.stats_tx.send(smollm_msg.to_string());
// Smart Routing: Lähetetään vain niille, jotka valittuna ja idle
let mut sends = Vec::new();
{
let channels = state_for_task.node_channels.read().await;
let tasks = state_for_task.node_tasks.lock().unwrap();
let mut busy = state_for_task.node_busy.lock().unwrap();
// Qwen-prompt (sama prompti, eri malli-tagi)
let qwen_msg = serde_json::json!({
"type": "llm_prompt",
"prompt": llm_prompts[llm_idx],
"model": "qwen-05b",
});
let _ = state_for_task.stats_tx.send(qwen_msg.to_string());
for (node_id, task) in tasks.iter() {
if !busy.contains(node_id) {
// Vapaa node -> lähetetään oikea tehtävä
let msg = match task.as_str() {
"tokenize" => Some(serde_json::json!({ "type": "pair_task", "en": en, "fi": fi })),
"smollm-135m" => Some(serde_json::json!({ "type": "llm_prompt", "prompt": llm_prompts[llm_idx], "model": "smollm-135m" })),
"qwen-05b" => Some(serde_json::json!({ "type": "llm_prompt", "prompt": llm_prompts[llm_idx], "model": "qwen-05b" })),
"phi3-mini" => Some(serde_json::json!({ "type": "llm_prompt", "prompt": llm_prompts[llm_idx], "model": "phi3-mini" })),
_ => None, // Coder ja viewer ei saa auto-tehtäviä
};
// Phi-3 prompt
let phi3_msg = serde_json::json!({
"type": "llm_prompt",
"prompt": llm_prompts[llm_idx],
"model": "phi3-mini",
});
let _ = state_for_task.stats_tx.send(phi3_msg.to_string());
if let Some(payload) = msg {
if let Some(ch) = channels.get(node_id) {
sends.push((ch.clone(), payload.to_string()));
busy.insert(*node_id);
}
}
}
}
}
// Coder ei saa automaattisia tehtäviä — vain käyttäjän user_text
for (ch, msg_str) in sends {
let _ = ch.send(msg_str);
}
tracing::debug!("Tehtävät lähetetty: pair + smollm + qwen + phi3");
// tracing::debug!("Tehtävät lähetetty reititetysti idle-nodeille");
}
});
@@ -391,7 +388,7 @@ async fn main() {
.route("/api/v1/agents/:id", axum::routing::delete(api_delete_agent))
.route("/admin", get(admin_page))
.nest_service("/", {
let static_dir = std::env::var("STATIC_DIR").unwrap_or_else(|_| "../static".to_string());
let static_dir = std::env::var("STATIC_DIR").unwrap_or_else(|_| "../frontend/dist".to_string());
ServeDir::new(&static_dir).fallback(ServeFile::new(format!("{}/index.html", static_dir)))
})
.with_state(state);
@@ -783,6 +780,7 @@ async fn handle_socket(socket: WebSocket, state: Arc<AppState>, ip: IpAddr) {
}
broadcast_stats(&state).await;
} else if msg_type == "pair_done" {
state.node_busy.lock().unwrap().remove(&node_id);
{
let mut json = json; // Siirretään omistajuus muokkausta varten
if let Some(obj) = json.as_object_mut() {
@@ -1102,8 +1100,12 @@ async fn api_chat_completions(
let node_types = state.node_types.lock().unwrap();
let matching: Vec<u64> = tasks.iter().filter(|(_, task)| {
// Eksakti match tai qwen-perheen yhteensopivuus (selain: qwen-coder-05b, natiivi: qwen2.5-coder:7b)
if payload.model.starts_with("qwen") {
task.starts_with("qwen")
let req_model = payload.model.to_lowercase();
let node_task = task.to_lowercase();
if req_model.starts_with("qwen") {
node_task.starts_with("qwen")
} else if req_model.starts_with("phi") {
node_task.starts_with("phi")
} else {
**task == payload.model
}