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:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user