eka toimiva
This commit is contained in:
26
network-poc/deploy.sh
Executable file
26
network-poc/deploy.sh
Executable file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SERVER="ubuntu@86.50.252.98"
|
||||||
|
REMOTE_DIR="~/code/agentic-studio/network-poc"
|
||||||
|
SSH_OPTS="-o StrictHostKeyChecking=no"
|
||||||
|
|
||||||
|
echo "=== Kipinä Studio Deploy ==="
|
||||||
|
|
||||||
|
# 1. Rakennetaan Docker-image lokaalisti
|
||||||
|
echo "[1/4] Rakennetaan image lokaalisti..."
|
||||||
|
docker build -f Dockerfile.prod -t kipina-agentic:latest .
|
||||||
|
|
||||||
|
# 2. Tallennetaan ja siirretään
|
||||||
|
echo "[2/4] Siirretään image palvelimelle..."
|
||||||
|
docker save kipina-agentic:latest | gzip | ssh $SSH_OPTS $SERVER "gunzip | docker load"
|
||||||
|
|
||||||
|
# 3. Päivitetään konfiguraatiot
|
||||||
|
echo "[3/4] Päivitetään konfiguraatiot..."
|
||||||
|
scp $SSH_OPTS docker-compose.prod.yml Caddyfile.prod $SERVER:$REMOTE_DIR/
|
||||||
|
|
||||||
|
# 4. Käynnistetään uudelleen
|
||||||
|
echo "[4/4] Käynnistetään palvelut..."
|
||||||
|
ssh $SSH_OPTS $SERVER "cd $REMOTE_DIR && docker compose -f docker-compose.prod.yml up -d"
|
||||||
|
|
||||||
|
echo "=== Valmis! https://kipina.studio ==="
|
||||||
@@ -14,9 +14,7 @@ services:
|
|||||||
- hub
|
- hub
|
||||||
|
|
||||||
hub:
|
hub:
|
||||||
build:
|
image: kipina-agentic:latest
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile.prod
|
|
||||||
container_name: kipina-agentic-hub
|
container_name: kipina-agentic-hub
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "hub"
|
name = "hub"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ impl NodeDb {
|
|||||||
en: &serde_json::Value,
|
en: &serde_json::Value,
|
||||||
fi: &serde_json::Value,
|
fi: &serde_json::Value,
|
||||||
overhead: f64,
|
overhead: f64,
|
||||||
duration_ms: u64,
|
duration_ms: f64,
|
||||||
) {
|
) {
|
||||||
let conn = self.conn.lock().unwrap();
|
let conn = self.conn.lock().unwrap();
|
||||||
let now = chrono::Utc::now().to_rfc3339();
|
let now = chrono::Utc::now().to_rfc3339();
|
||||||
@@ -265,7 +265,7 @@ impl NodeDb {
|
|||||||
en.get("chars_per_token").and_then(|v| v.as_f64()),
|
en.get("chars_per_token").and_then(|v| v.as_f64()),
|
||||||
fi.get("chars_per_token").and_then(|v| v.as_f64()),
|
fi.get("chars_per_token").and_then(|v| v.as_f64()),
|
||||||
overhead,
|
overhead,
|
||||||
duration_ms as i64,
|
duration_ms,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ tr:hover td { background:#1c2333; }
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Kipina Admin</h1>
|
<h1>Kipina Admin</h1>
|
||||||
<p class="sub">Node-sessiot ja tokenisointivertailut</p>
|
<p class="sub">Node-sessiot ja tokenisointivertailut · <span id="admin-version" style="color:var(--accent)">-</span></p>
|
||||||
|
|
||||||
<div id="stats" class="stats-grid"></div>
|
<div id="stats" class="stats-grid"></div>
|
||||||
|
|
||||||
@@ -140,6 +140,9 @@ async function load() {
|
|||||||
const sessions = await sessionsRes.json();
|
const sessions = await sessionsRes.json();
|
||||||
const pairs = await pairsRes.json();
|
const pairs = await pairsRes.json();
|
||||||
|
|
||||||
|
// Versio
|
||||||
|
if (stats.version) document.getElementById('admin-version').textContent = 'v' + stats.version;
|
||||||
|
|
||||||
// Stats
|
// Stats
|
||||||
document.getElementById('stats').innerHTML = [
|
document.getElementById('stats').innerHTML = [
|
||||||
{v: stats.total_sessions, l: 'Sessioita'},
|
{v: stats.total_sessions, l: 'Sessioita'},
|
||||||
@@ -194,7 +197,7 @@ async function load() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
load();
|
load();
|
||||||
setInterval(load, 10000);
|
setInterval(load, 1000);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>"##;
|
</html>"##;
|
||||||
@@ -275,7 +278,7 @@ async fn main() {
|
|||||||
.with_state(state);
|
.with_state(state);
|
||||||
|
|
||||||
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
|
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
|
||||||
tracing::debug!("Kipinä Agent Hub käynnistyy osoitteessa http://localhost:3000");
|
tracing::info!("Kipinä Agent Hub v{} käynnistyy osoitteessa http://localhost:3000", env!("CARGO_PKG_VERSION"));
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
||||||
axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>()).await.unwrap();
|
axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>()).await.unwrap();
|
||||||
@@ -296,7 +299,9 @@ async fn api_pairs(
|
|||||||
async fn api_stats(
|
async fn api_stats(
|
||||||
axum::extract::State(state): axum::extract::State<Arc<AppState>>,
|
axum::extract::State(state): axum::extract::State<Arc<AppState>>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
axum::Json(state.db.get_stats())
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn admin_page() -> impl IntoResponse {
|
async fn admin_page() -> impl IntoResponse {
|
||||||
@@ -358,6 +363,7 @@ async fn broadcast_stats(state: &Arc<AppState>) {
|
|||||||
let completed = *state.total_tasks.lock().unwrap();
|
let completed = *state.total_tasks.lock().unwrap();
|
||||||
let stats_msg = serde_json::json!({
|
let stats_msg = serde_json::json!({
|
||||||
"type": "stats",
|
"type": "stats",
|
||||||
|
"version": env!("CARGO_PKG_VERSION"),
|
||||||
"nodes": total_nodes,
|
"nodes": total_nodes,
|
||||||
"vram_gb": total_vram,
|
"vram_gb": total_vram,
|
||||||
"tasks": completed
|
"tasks": completed
|
||||||
@@ -553,7 +559,7 @@ async fn handle_socket(socket: WebSocket, state: Arc<AppState>, ip: IpAddr) {
|
|||||||
let en = obj.get("en").unwrap_or(&empty);
|
let en = obj.get("en").unwrap_or(&empty);
|
||||||
let fi = obj.get("fi").unwrap_or(&empty);
|
let fi = obj.get("fi").unwrap_or(&empty);
|
||||||
let overhead = obj.get("overhead_pct").and_then(|v| v.as_f64()).unwrap_or(0.0);
|
let overhead = obj.get("overhead_pct").and_then(|v| v.as_f64()).unwrap_or(0.0);
|
||||||
let duration = obj.get("duration_ms").and_then(|v| v.as_u64()).unwrap_or(0);
|
let duration = obj.get("duration_ms").and_then(|v| v.as_f64()).unwrap_or(0.0);
|
||||||
|
|
||||||
let en_text = en.get("text").and_then(|v| v.as_str()).unwrap_or("");
|
let en_text = en.get("text").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
let en_tokens = en.get("token_count").and_then(|v| v.as_u64()).unwrap_or(0);
|
let en_tokens = en.get("token_count").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||||
@@ -568,7 +574,7 @@ async fn handle_socket(socket: WebSocket, state: Arc<AppState>, ip: IpAddr) {
|
|||||||
let overhead_color = if overhead > 10.0 { "\x1b[31m" } else if overhead < -10.0 { "\x1b[32m" } else { "\x1b[33m" };
|
let overhead_color = if overhead > 10.0 { "\x1b[31m" } else if overhead < -10.0 { "\x1b[32m" } else { "\x1b[33m" };
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
println!("\x1b[36m━━━ Solmu {} ━━━ {}ms ━━━\x1b[0m", node_id, duration);
|
println!("\x1b[36m━━━ Solmu {} ━━━ {:.2}ms ━━━\x1b[0m", node_id, duration);
|
||||||
println!(" \x1b[34mEN\x1b[0m \"{}\"", en_text);
|
println!(" \x1b[34mEN\x1b[0m \"{}\"", en_text);
|
||||||
println!(" {} merkkiä → \x1b[35m{} tokenia\x1b[0m | \x1b[32m{:.2} merkkiä/token\x1b[0m", en_chars, en_tokens, en_cpt);
|
println!(" {} merkkiä → \x1b[35m{} tokenia\x1b[0m | \x1b[32m{:.2} merkkiä/token\x1b[0m", en_chars, en_tokens, en_cpt);
|
||||||
println!(" \x1b[33mFI\x1b[0m \"{}\"", fi_text);
|
println!(" \x1b[33mFI\x1b[0m \"{}\"", fi_text);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ web-sys = { version = "0.3.68", features = [
|
|||||||
"HtmlElement",
|
"HtmlElement",
|
||||||
"WebSocket",
|
"WebSocket",
|
||||||
"MessageEvent",
|
"MessageEvent",
|
||||||
|
"Performance",
|
||||||
"console",
|
"console",
|
||||||
] }
|
] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|||||||
@@ -117,10 +117,11 @@ async fn run_pair_comparison(en_text: String, fi_text: String, ws: Rc<RefCell<We
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let start_time = js_sys::Date::now();
|
let perf = web_sys::window().unwrap().performance().unwrap();
|
||||||
|
let start_time = perf.now();
|
||||||
let en_result = tokenize_text(&tokenizer, &en_text);
|
let en_result = tokenize_text(&tokenizer, &en_text);
|
||||||
let fi_result = tokenize_text(&tokenizer, &fi_text);
|
let fi_result = tokenize_text(&tokenizer, &fi_text);
|
||||||
let duration = (js_sys::Date::now() - start_time) as u64;
|
let duration_ms = perf.now() - start_time; // millisekunteja desimaalitarkkuudella
|
||||||
|
|
||||||
let en_cpt = en_result["chars_per_token"].as_f64().unwrap_or(0.0);
|
let en_cpt = en_result["chars_per_token"].as_f64().unwrap_or(0.0);
|
||||||
let fi_cpt = fi_result["chars_per_token"].as_f64().unwrap_or(0.0);
|
let fi_cpt = fi_result["chars_per_token"].as_f64().unwrap_or(0.0);
|
||||||
@@ -132,15 +133,15 @@ async fn run_pair_comparison(en_text: String, fi_text: String, ws: Rc<RefCell<We
|
|||||||
((fi_tokens as f64 / en_tokens as f64) - 1.0) * 100.0
|
((fi_tokens as f64 / en_tokens as f64) - 1.0) * 100.0
|
||||||
} else { 0.0 };
|
} else { 0.0 };
|
||||||
|
|
||||||
console_log!("EN: {} tokenia ({:.2} m/t) vs FI: {} tokenia ({:.2} m/t) | ylikustannus: {:.0}%",
|
console_log!("EN: {} tokenia ({:.2} m/t) vs FI: {} tokenia ({:.2} m/t) | ylikustannus: {:.0}% | {:.2}ms",
|
||||||
en_tokens, en_cpt, fi_tokens, fi_cpt, overhead_pct);
|
en_tokens, en_cpt, fi_tokens, fi_cpt, overhead_pct, duration_ms);
|
||||||
|
|
||||||
let pair_done = serde_json::json!({
|
let pair_done = serde_json::json!({
|
||||||
"type": "pair_done",
|
"type": "pair_done",
|
||||||
"en": en_result,
|
"en": en_result,
|
||||||
"fi": fi_result,
|
"fi": fi_result,
|
||||||
"overhead_pct": (overhead_pct * 10.0).round() / 10.0,
|
"overhead_pct": (overhead_pct * 10.0).round() / 10.0,
|
||||||
"duration_ms": duration,
|
"duration_ms": (duration_ms * 100.0).round() / 100.0,
|
||||||
"tokenizer": "Qwen2.5-Coder-0.5B",
|
"tokenizer": "Qwen2.5-Coder-0.5B",
|
||||||
});
|
});
|
||||||
let _ = ws.borrow().send_with_str(&pair_done.to_string());
|
let _ = ws.borrow().send_with_str(&pair_done.to_string());
|
||||||
|
|||||||
@@ -129,6 +129,40 @@
|
|||||||
.btn:hover { background-color: #2ea043; }
|
.btn:hover { background-color: #2ea043; }
|
||||||
.hidden { display: none; }
|
.hidden { display: none; }
|
||||||
|
|
||||||
|
.compat-banner {
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 14px 18px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.compat-banner.gpu {
|
||||||
|
background: #23392020;
|
||||||
|
border: 1px solid #3fb95040;
|
||||||
|
color: var(--success-color);
|
||||||
|
}
|
||||||
|
.compat-banner.cpu {
|
||||||
|
background: #d2992215;
|
||||||
|
border: 1px solid #d2992240;
|
||||||
|
color: #d29922;
|
||||||
|
}
|
||||||
|
.compat-banner code {
|
||||||
|
background: #0d1117;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
.compat-banner summary {
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.compat-banner details[open] summary {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-box {
|
.chat-box {
|
||||||
background-color: var(--panel-bg);
|
background-color: var(--panel-bg);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
@@ -191,12 +225,30 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.toggle-tokens:hover { color: var(--text-color); border-color: #8b949e; }
|
.toggle-tokens:hover { color: var(--text-color); border-color: #8b949e; }
|
||||||
|
|
||||||
|
.metric-card {
|
||||||
|
background: var(--panel-bg);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.metric-val {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
.metric-label {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #8b949e;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>Kipinä <span>Agent Dashboard</span></h1>
|
<h1>Kipinä <span>Agent Dashboard</span></h1>
|
||||||
<p class="sub">Hajautettu WebGPU Laskentaverkko</p>
|
<p class="sub">Hajautettu WebGPU Laskentaverkko · <span id="hub-version" style="color:#58a6ff">-</span></p>
|
||||||
|
|
||||||
<!-- Global Cluster Statistics (UI) -->
|
<!-- Global Cluster Statistics (UI) -->
|
||||||
<div class="dashboard-panel">
|
<div class="dashboard-panel">
|
||||||
@@ -215,16 +267,51 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="device-info" class="device-info"></div>
|
<div id="device-info" class="device-info"></div>
|
||||||
|
<div id="compat-banner" class="compat-banner"></div>
|
||||||
|
|
||||||
<div id="initial-state">
|
<div id="initial-state">
|
||||||
<button id="start-btn" class="btn">Liity laskentaverkkoon</button>
|
<button id="start-btn" class="btn">Liity laskentaverkkoon</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="active-state" class="hidden">
|
<div id="active-state" class="hidden">
|
||||||
<div class="slider-container">
|
<!-- Resurssipaneeli -->
|
||||||
<label for="gpu-load">Oman Laitteen Kuormitusrajoitin: <strong id="load-display" style="color:var(--accent-color);">50%</strong></label>
|
<div style="background:#0d1117;border:1px solid var(--border-color);border-radius:6px;padding:16px;margin-bottom:16px">
|
||||||
<input type="range" id="gpu-load" min="0" max="75" value="50">
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
|
||||||
<p style="font-size: 11px; color:#8b949e;">Hallitsee "Duty Cyclea" – kuinka pitkään Wasm-ydin pakotetaan nukkumaan Tensorimatriisien laskennan välissä.</p>
|
<span style="font-weight:600;font-size:15px">Resurssien hallinta</span>
|
||||||
|
<span id="node-status" style="font-size:12px;color:var(--success-color)">Aktiivinen</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Kuormitussäädin -->
|
||||||
|
<div style="margin-bottom:14px">
|
||||||
|
<div style="display:flex;justify-content:space-between;font-size:13px;margin-bottom:4px">
|
||||||
|
<span>Laskentatehon rajoitin</span>
|
||||||
|
<strong id="load-display" style="color:var(--accent-color)">50%</strong>
|
||||||
|
</div>
|
||||||
|
<input type="range" id="gpu-load" min="0" max="100" value="50" style="width:100%;accent-color:var(--accent-color)">
|
||||||
|
<div style="display:flex;justify-content:space-between;font-size:11px;color:#8b949e;margin-top:2px">
|
||||||
|
<span>Pysäytetty</span><span>Säästö</span><span>Tasapaino</span><span>Suorituskyky</span><span>Maksimi</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Reaaliaikaiset metriikat -->
|
||||||
|
<div id="metrics-grid" style="display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-top:12px">
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-val" id="m-tasks">0</div>
|
||||||
|
<div class="metric-label">Tehtäviä</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-val" id="m-avg-time">-</div>
|
||||||
|
<div class="metric-label">Ka. aika</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-val" id="m-tokens">0</div>
|
||||||
|
<div class="metric-label">Tokeneita</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-val" id="m-uptime">0s</div>
|
||||||
|
<div class="metric-label">Käynnissä</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="chat-box" class="chat-box hidden">
|
<div id="chat-box" class="chat-box hidden">
|
||||||
@@ -251,6 +338,29 @@
|
|||||||
|
|
||||||
let currentChatMsg = null;
|
let currentChatMsg = null;
|
||||||
|
|
||||||
|
// Reaaliaikaiset metriikat
|
||||||
|
const metrics = {
|
||||||
|
tasks: 0,
|
||||||
|
totalTokens: 0,
|
||||||
|
totalTimeMs: 0,
|
||||||
|
startTime: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
function updateMetrics() {
|
||||||
|
document.getElementById('m-tasks').textContent = metrics.tasks;
|
||||||
|
document.getElementById('m-tokens').textContent = metrics.totalTokens.toLocaleString('fi-FI');
|
||||||
|
document.getElementById('m-avg-time').textContent = metrics.tasks > 0
|
||||||
|
? (metrics.totalTimeMs / metrics.tasks).toFixed(1) + 'ms'
|
||||||
|
: '-';
|
||||||
|
if (metrics.startTime) {
|
||||||
|
const sec = Math.floor((Date.now() - metrics.startTime) / 1000);
|
||||||
|
if (sec < 60) document.getElementById('m-uptime').textContent = sec + 's';
|
||||||
|
else if (sec < 3600) document.getElementById('m-uptime').textContent = Math.floor(sec/60) + 'min';
|
||||||
|
else document.getElementById('m-uptime').textContent = Math.floor(sec/3600) + 'h ' + (Math.floor(sec/60)%60) + 'min';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setInterval(updateMetrics, 1000);
|
||||||
|
|
||||||
// Ylikirjoitetaan console.log uppoamaan lokilaatikkoon
|
// Ylikirjoitetaan console.log uppoamaan lokilaatikkoon
|
||||||
const originalLog = console.log;
|
const originalLog = console.log;
|
||||||
console.log = function(...args) {
|
console.log = function(...args) {
|
||||||
@@ -272,9 +382,22 @@
|
|||||||
|
|
||||||
// UI Slider Listener -> Lähettää arvon suoraan WebAssemblyn ytimeen!
|
// UI Slider Listener -> Lähettää arvon suoraan WebAssemblyn ytimeen!
|
||||||
loadSlider.addEventListener('input', (e) => {
|
loadSlider.addEventListener('input', (e) => {
|
||||||
loadDisplay.textContent = e.target.value + '%';
|
const val = parseInt(e.target.value);
|
||||||
|
loadDisplay.textContent = val + '%';
|
||||||
if (window.wasm_active) {
|
if (window.wasm_active) {
|
||||||
set_gpu_load(parseInt(e.target.value));
|
set_gpu_load(val);
|
||||||
|
}
|
||||||
|
// Tilapäivitys
|
||||||
|
const statusEl = document.getElementById('node-status');
|
||||||
|
if (val === 0) {
|
||||||
|
statusEl.textContent = 'Pysäytetty';
|
||||||
|
statusEl.style.color = '#f85149';
|
||||||
|
} else if (val <= 25) {
|
||||||
|
statusEl.textContent = 'Säästötila';
|
||||||
|
statusEl.style.color = '#d29922';
|
||||||
|
} else {
|
||||||
|
statusEl.textContent = 'Aktiivinen';
|
||||||
|
statusEl.style.color = 'var(--success-color)';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -289,6 +412,9 @@
|
|||||||
if (data.tasks !== undefined) {
|
if (data.tasks !== undefined) {
|
||||||
statTasks.textContent = data.tasks;
|
statTasks.textContent = data.tasks;
|
||||||
}
|
}
|
||||||
|
if (data.version) {
|
||||||
|
document.getElementById('hub-version').textContent = 'v' + data.version;
|
||||||
|
}
|
||||||
} else if (data.type === "node_joined") {
|
} else if (data.type === "node_joined") {
|
||||||
chatBox.classList.remove('hidden');
|
chatBox.classList.remove('hidden');
|
||||||
const msgDiv = document.createElement('div');
|
const msgDiv = document.createElement('div');
|
||||||
@@ -321,6 +447,12 @@
|
|||||||
const nodeId = data.node_id || "?";
|
const nodeId = data.node_id || "?";
|
||||||
const ms = data.duration_ms || 0;
|
const ms = data.duration_ms || 0;
|
||||||
|
|
||||||
|
// Päivitetään metriikat
|
||||||
|
metrics.tasks++;
|
||||||
|
metrics.totalTokens += (en.token_count || 0) + (fi.token_count || 0);
|
||||||
|
metrics.totalTimeMs += ms;
|
||||||
|
updateMetrics();
|
||||||
|
|
||||||
const enCpt = parseFloat((en.chars_per_token || 0).toFixed(2));
|
const enCpt = parseFloat((en.chars_per_token || 0).toFixed(2));
|
||||||
const fiCpt = parseFloat((fi.chars_per_token || 0).toFixed(2));
|
const fiCpt = parseFloat((fi.chars_per_token || 0).toFixed(2));
|
||||||
|
|
||||||
@@ -432,6 +564,52 @@
|
|||||||
`Varaus: <span>${deviceInfo.allocated_gb} GB</span>`
|
`Varaus: <span>${deviceInfo.allocated_gb} GB</span>`
|
||||||
].filter(Boolean).join(' · ');
|
].filter(Boolean).join(' · ');
|
||||||
|
|
||||||
|
// Yhteensopivuusbanneri
|
||||||
|
const banner = document.getElementById('compat-banner');
|
||||||
|
banner.style.display = 'block';
|
||||||
|
|
||||||
|
if (hasWebGPU) {
|
||||||
|
banner.className = 'compat-banner gpu';
|
||||||
|
banner.innerHTML = `GPU-kiihdytys aktiivinen — ${gpuStr}`;
|
||||||
|
} else {
|
||||||
|
// Tunnistetaan selain ohjeen personointia varten
|
||||||
|
const ua = navigator.userAgent;
|
||||||
|
const isFirefox = ua.includes('Firefox');
|
||||||
|
const isChrome = ua.includes('Chrome') && !ua.includes('Edg');
|
||||||
|
const isBrave = ua.includes('Brave') || (navigator.brave && navigator.brave.isBrave);
|
||||||
|
const isSafari = ua.includes('Safari') && !ua.includes('Chrome');
|
||||||
|
const isLinux = ua.includes('Linux');
|
||||||
|
|
||||||
|
let browserTip = '';
|
||||||
|
if (isFirefox) {
|
||||||
|
browserTip = `
|
||||||
|
<p><strong>Firefox</strong> ei tue WebGPU:ta oletuksena.</p>
|
||||||
|
<p>Ota käyttöön: <code>about:config</code> → <code>dom.webgpu.enabled</code> = <code>true</code> → käynnistä uudelleen.</p>
|
||||||
|
<p>Tai vaihda Chromeen/Braveen — niissä WebGPU toimii oletuksena.</p>`;
|
||||||
|
} else if ((isChrome || isBrave) && isLinux) {
|
||||||
|
const browser = isBrave ? 'brave-browser' : 'google-chrome';
|
||||||
|
browserTip = `
|
||||||
|
<p><strong>${isBrave ? 'Brave' : 'Chrome'} + Linux</strong>: GPU-ajuri ei ehkä tarjoa WebGPU:ta Wayland-ympäristössä.</p>
|
||||||
|
<p>Kokeile käynnistää selain komentoriviltä:</p>
|
||||||
|
<code>${browser} --enable-unsafe-webgpu --enable-features=Vulkan --ignore-gpu-blocklist --use-angle=vulkan --ozone-platform=x11</code>`;
|
||||||
|
} else if (isSafari) {
|
||||||
|
browserTip = `
|
||||||
|
<p><strong>Safari</strong>: WebGPU on tuettu versiosta 26 alkaen (macOS Tahoe).</p>
|
||||||
|
<p>Vanhemmissa versioissa: Develop → Feature Flags → WebGPU.</p>`;
|
||||||
|
} else {
|
||||||
|
browserTip = `
|
||||||
|
<p>Selaimesi ei tue WebGPU:ta. Kokeile <strong>Chrome 113+</strong> tai <strong>Brave</strong>.</p>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
banner.className = 'compat-banner cpu';
|
||||||
|
banner.innerHTML = `
|
||||||
|
<details>
|
||||||
|
<summary>CPU-laskenta (WebGPU ei käytettävissä) — klikkaa ohjeita</summary>
|
||||||
|
${browserTip}
|
||||||
|
<p style="margin-top:8px;color:#8b949e;font-size:12px">Laskenta toimii silti CPU:lla, mutta GPU-kiihdytys olisi nopeampi.</p>
|
||||||
|
</details>`;
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('initial-state').classList.add('hidden');
|
document.getElementById('initial-state').classList.add('hidden');
|
||||||
document.getElementById('active-state').classList.remove('hidden');
|
document.getElementById('active-state').classList.remove('hidden');
|
||||||
btn.style.display = 'none';
|
btn.style.display = 'none';
|
||||||
@@ -440,6 +618,7 @@
|
|||||||
console.log("Ladataan Burn Wasm -binääriä...");
|
console.log("Ladataan Burn Wasm -binääriä...");
|
||||||
await init();
|
await init();
|
||||||
window.wasm_active = true;
|
window.wasm_active = true;
|
||||||
|
metrics.startTime = Date.now();
|
||||||
|
|
||||||
// Varmistetaan, että Wasm saa nykyisen sliderin arvon heti kärkeen
|
// Varmistetaan, että Wasm saa nykyisen sliderin arvon heti kärkeen
|
||||||
set_gpu_load(parseInt(loadSlider.value));
|
set_gpu_load(parseInt(loadSlider.value));
|
||||||
|
|||||||
Reference in New Issue
Block a user