Compare commits
11 Commits
revolution
...
403f35efdc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
403f35efdc | ||
|
|
ce0ccbddd3 | ||
|
|
80806498e0 | ||
|
|
660e80c2bc | ||
|
|
591cfcb04b | ||
|
|
3cda57f0bc | ||
|
|
23e7b92d03 | ||
|
|
9f58febe21 | ||
|
|
b1de0d37f7 | ||
|
|
4ff626ab88 | ||
|
|
a45616046d |
@@ -4,19 +4,28 @@ set -e
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
OUT="$SCRIPT_DIR/frontend/public/download"
|
OUT="$SCRIPT_DIR/frontend/public/download"
|
||||||
|
HASH_FILE="$OUT/.build-hash"
|
||||||
mkdir -p "$OUT"
|
mkdir -p "$OUT"
|
||||||
|
|
||||||
|
# Tarkistetaan onko native-node muuttunut edellisen buildin jälkeen
|
||||||
|
CURRENT_HASH=$(git -C "$SCRIPT_DIR" log -1 --format=%H -- native-node/ Cargo.toml Cargo.lock)
|
||||||
|
if [ -f "$HASH_FILE" ] && [ "$(cat "$HASH_FILE")" = "$CURRENT_HASH" ]; then
|
||||||
|
echo "=== Kipinä Node — ei muutoksia, ohitetaan build ==="
|
||||||
|
ls -lh "$OUT"/kipina-node-* 2>/dev/null || true
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
echo "=== Kipinä Node — Binary Build ==="
|
echo "=== Kipinä Node — Binary Build ==="
|
||||||
|
|
||||||
# macOS ARM (natiivi)
|
# macOS ARM (natiivi)
|
||||||
echo "[1/3] macOS ARM64..."
|
echo "[1/4] macOS ARM64..."
|
||||||
cd "$SCRIPT_DIR"
|
cd "$SCRIPT_DIR"
|
||||||
cargo build --release -p native-node --no-default-features 2>&1 | tail -1
|
cargo build --release -p native-node --no-default-features 2>&1 | tail -1
|
||||||
cp target/release/native-node "$OUT/kipina-node-macos-arm64"
|
cp target/release/native-node "$OUT/kipina-node-macos-arm64"
|
||||||
echo " $(ls -lh "$OUT/kipina-node-macos-arm64" | awk '{print $5}')"
|
echo " $(ls -lh "$OUT/kipina-node-macos-arm64" | awk '{print $5}')"
|
||||||
|
|
||||||
# Linux x86_64 (Docker)
|
# Linux x86_64 (Docker)
|
||||||
echo "[2/3] Linux x86_64..."
|
echo "[2/4] Linux x86_64..."
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
-v "$SCRIPT_DIR":/app -w /app \
|
-v "$SCRIPT_DIR":/app -w /app \
|
||||||
--platform linux/amd64 \
|
--platform linux/amd64 \
|
||||||
@@ -25,7 +34,7 @@ docker run --rm \
|
|||||||
echo " $(ls -lh "$OUT/kipina-node-linux-x86_64" | awk '{print $5}')"
|
echo " $(ls -lh "$OUT/kipina-node-linux-x86_64" | awk '{print $5}')"
|
||||||
|
|
||||||
# Linux ARM64 (Docker)
|
# Linux ARM64 (Docker)
|
||||||
echo "[3/3] Linux ARM64..."
|
echo "[3/4] Linux ARM64..."
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
-v "$SCRIPT_DIR":/app -w /app \
|
-v "$SCRIPT_DIR":/app -w /app \
|
||||||
--platform linux/arm64 \
|
--platform linux/arm64 \
|
||||||
@@ -33,6 +42,18 @@ docker run --rm \
|
|||||||
bash -c "apt-get update -qq && apt-get install -y -qq pkg-config libssl-dev >/dev/null 2>&1 && cargo build --release -p native-node --no-default-features 2>&1 | tail -1 && cp target/release/native-node /app/frontend/public/download/kipina-node-linux-arm64"
|
bash -c "apt-get update -qq && apt-get install -y -qq pkg-config libssl-dev >/dev/null 2>&1 && cargo build --release -p native-node --no-default-features 2>&1 | tail -1 && cp target/release/native-node /app/frontend/public/download/kipina-node-linux-arm64"
|
||||||
echo " $(ls -lh "$OUT/kipina-node-linux-arm64" | awk '{print $5}')"
|
echo " $(ls -lh "$OUT/kipina-node-linux-arm64" | awk '{print $5}')"
|
||||||
|
|
||||||
|
# Windows x86_64 (Docker + mingw-w64)
|
||||||
|
echo "[4/4] Windows x86_64..."
|
||||||
|
docker run --rm \
|
||||||
|
-v "$SCRIPT_DIR":/app -w /app \
|
||||||
|
--platform linux/amd64 \
|
||||||
|
rust:slim \
|
||||||
|
bash -c "apt-get update -qq && apt-get install -y -qq gcc-mingw-w64-x86-64 pkg-config libssl-dev >/dev/null 2>&1 && rustup target add x86_64-pc-windows-gnu && cargo build --release -p native-node --no-default-features --target x86_64-pc-windows-gnu 2>&1 | tail -1 && cp target/x86_64-pc-windows-gnu/release/native-node.exe /app/frontend/public/download/kipina-node-windows-x86_64.exe"
|
||||||
|
echo " $(ls -lh "$OUT/kipina-node-windows-x86_64.exe" | awk '{print $5}')"
|
||||||
|
|
||||||
|
# Tallennetaan onnistuneen buildin hash
|
||||||
|
echo "$CURRENT_HASH" > "$HASH_FILE"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Binäärit valmiina ==="
|
echo "=== Binäärit valmiina ==="
|
||||||
ls -lh "$OUT"/kipina-node-*
|
ls -lh "$OUT"/kipina-node-*
|
||||||
|
|||||||
13
network-poc/deploy-with-native.sh
Executable file
13
network-poc/deploy-with-native.sh
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Deploy + native-node-binäärien käännös (jos muutoksia)
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
|
echo "=== Kipinä Studio Deploy (+ native binäärit) ==="
|
||||||
|
|
||||||
|
# Käännetään native-node-binäärit (ohittaa automaattisesti jos ei muutoksia)
|
||||||
|
"$SCRIPT_DIR/build-binaries.sh"
|
||||||
|
|
||||||
|
# Ajetaan normaali deploy
|
||||||
|
exec "$SCRIPT_DIR/deploy.sh"
|
||||||
@@ -40,20 +40,20 @@ echo "[1/4] Rakennetaan image lokaalisti..."
|
|||||||
docker build --platform linux/amd64 -f Dockerfile.prod -t kipina-agentic:latest .
|
docker build --platform linux/amd64 -f Dockerfile.prod -t kipina-agentic:latest .
|
||||||
|
|
||||||
# 2. Tallennetaan tiedostoon
|
# 2. Tallennetaan tiedostoon
|
||||||
echo "[2/5] Pakataan image..."
|
echo "[2/4] Pakataan image..."
|
||||||
docker save kipina-agentic:latest | gzip > /tmp/kipina-agentic.tar.gz
|
docker save kipina-agentic:latest | gzip > /tmp/kipina-agentic.tar.gz
|
||||||
echo " Koko: $(du -h /tmp/kipina-agentic.tar.gz | cut -f1)"
|
echo " Koko: $(du -h /tmp/kipina-agentic.tar.gz | cut -f1)"
|
||||||
|
|
||||||
# 3. Siirretään palvelimelle
|
# 3. Siirretään palvelimelle
|
||||||
echo "[3/5] Siirretään palvelimelle..."
|
echo "[3/4] Siirretään palvelimelle..."
|
||||||
scp $SSH_OPTS /tmp/kipina-agentic.tar.gz $SERVER:/tmp/
|
scp $SSH_OPTS /tmp/kipina-agentic.tar.gz $SERVER:/tmp/
|
||||||
scp $SSH_OPTS docker-compose.prod.yml Caddyfile.prod $SERVER:$REMOTE_DIR/
|
scp $SSH_OPTS docker-compose.prod.yml Caddyfile.prod $SERVER:$REMOTE_DIR/
|
||||||
|
|
||||||
# 4. Ladataan image ja käynnistetään
|
# 4. Ladataan image ja käynnistetään
|
||||||
echo "[4/5] Ladataan image palvelimella..."
|
echo "[4/4] Ladataan image palvelimella..."
|
||||||
ssh $SSH_OPTS $SERVER "gunzip -c /tmp/kipina-agentic.tar.gz | docker load && rm /tmp/kipina-agentic.tar.gz"
|
ssh $SSH_OPTS $SERVER "gunzip -c /tmp/kipina-agentic.tar.gz | docker load && rm /tmp/kipina-agentic.tar.gz"
|
||||||
|
|
||||||
echo "[5/5] Käynnistetään palvelut uudelleen..."
|
echo "[4/4] Käynnistetään palvelut uudelleen..."
|
||||||
ssh $SSH_OPTS $SERVER "cd $REMOTE_DIR && docker compose -f docker-compose.prod.yml down && docker compose -f docker-compose.prod.yml up -d"
|
ssh $SSH_OPTS $SERVER "cd $REMOTE_DIR && docker compose -f docker-compose.prod.yml down && docker compose -f docker-compose.prod.yml up -d"
|
||||||
|
|
||||||
echo "=== Valmis! https://kipina.studio ==="
|
echo "=== Valmis! https://kipina.studio ==="
|
||||||
|
|||||||
1
network-poc/frontend/public/download/.build-hash
Normal file
1
network-poc/frontend/public/download/.build-hash
Normal file
@@ -0,0 +1 @@
|
|||||||
|
80806498e06e5dc31a04aa319ac17f766684d422
|
||||||
BIN
network-poc/frontend/public/download/kipina-node-linux-arm64
Executable file
BIN
network-poc/frontend/public/download/kipina-node-linux-arm64
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
network-poc/frontend/public/download/kipina-node-windows-x86_64.exe
Executable file
BIN
network-poc/frontend/public/download/kipina-node-windows-x86_64.exe
Executable file
Binary file not shown.
@@ -4,7 +4,6 @@ set -e
|
|||||||
|
|
||||||
BASE_URL="https://kipina.studio/download"
|
BASE_URL="https://kipina.studio/download"
|
||||||
HUB_URL="${KIPINA_HUB:-wss://kipina.studio/ws}"
|
HUB_URL="${KIPINA_HUB:-wss://kipina.studio/ws}"
|
||||||
MODEL="${KIPINA_MODEL:-qwen2.5-coder:3b}"
|
|
||||||
OLLAMA_URL="${OLLAMA_URL:-http://localhost:11434}"
|
OLLAMA_URL="${OLLAMA_URL:-http://localhost:11434}"
|
||||||
|
|
||||||
# Tunnista OS ja arkkitehtuuri
|
# Tunnista OS ja arkkitehtuuri
|
||||||
@@ -96,26 +95,35 @@ fi
|
|||||||
echo ""
|
echo ""
|
||||||
echo " Hub: $HUB_URL"
|
echo " Hub: $HUB_URL"
|
||||||
echo " Ollama: $OLLAMA_URL"
|
echo " Ollama: $OLLAMA_URL"
|
||||||
echo " Malli: $MODEL"
|
if [ -n "$KIPINA_MODEL" ]; then
|
||||||
|
echo " Malli: $KIPINA_MODEL (Ympäristömuuttujasta)"
|
||||||
# Lataa malli (toimii sekä lokaalilla binäärillä että API:n kautta)
|
|
||||||
if ! curl -s "$OLLAMA_URL/api/tags" | grep -q "$MODEL"; then
|
|
||||||
echo " Ladataan $MODEL..."
|
|
||||||
curl -s "$OLLAMA_URL/api/pull" -d "{\"name\":\"$MODEL\"}" > /dev/null
|
|
||||||
fi
|
fi
|
||||||
echo " ✓ Malli $MODEL valmis"
|
|
||||||
|
|
||||||
# Lataa binääri
|
# Lataa binääri
|
||||||
BIN_PATH="./kipina-node-bin"
|
BIN_PATH="./kipina-node-bin"
|
||||||
|
if [ -f "$BIN_PATH" ]; then
|
||||||
|
echo ""
|
||||||
|
read -p " Löydettiin vanha kipina-node-bin lokaalisti. Haluatko poistaa sen ja ladata uusimman version? [y/N] " -r DEL_CHOICE
|
||||||
|
if [[ "$DEL_CHOICE" =~ ^[Yy]$ ]]; then
|
||||||
|
rm -f "$BIN_PATH"
|
||||||
|
echo " ✓ Vanha binääri poistettu."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [ ! -f "$BIN_PATH" ]; then
|
if [ ! -f "$BIN_PATH" ]; then
|
||||||
echo " Ladataan $BINARY..."
|
echo " Ladataan tuorein $BINARY..."
|
||||||
curl -sSL "$BASE_URL/$BINARY" -o "$BIN_PATH"
|
curl -sSL "$BASE_URL/$BINARY" -o "$BIN_PATH"
|
||||||
chmod +x "$BIN_PATH"
|
chmod +x "$BIN_PATH"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo " ✓ Yhdistetään laskentaverkkoon..."
|
echo " ✓ Siirrytään Kipinä Noden hallintaan..."
|
||||||
echo " Ctrl+C pysäyttää"
|
echo " Ctrl+C pysäyttää"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
HUB_URL="$HUB_URL" OLLAMA_URL="$OLLAMA_URL" OLLAMA_MODEL="$MODEL" exec "$BIN_PATH"
|
if [ -n "$KIPINA_MODEL" ]; then
|
||||||
|
export OLLAMA_MODEL="$KIPINA_MODEL"
|
||||||
|
fi
|
||||||
|
export HUB_URL="$HUB_URL"
|
||||||
|
export OLLAMA_URL="$OLLAMA_URL"
|
||||||
|
exec "$BIN_PATH"
|
||||||
|
|||||||
7
network-poc/hub-local.log
Normal file
7
network-poc/hub-local.log
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s
|
||||||
|
Running `target/debug/hub`
|
||||||
|
[2m2026-04-12T03:21:35.058711Z[0m [32m INFO[0m [2mhub[0m[2m:[0m Tietokanta alustettu
|
||||||
|
[2m2026-04-12T03:21:35.061635Z[0m [32m INFO[0m [2mhub[0m[2m:[0m Kipinä Agent Hub v0.3.1 käynnistyy osoitteessa http://localhost:3000
|
||||||
|
[2m2026-04-12T03:21:49.577628Z[0m [32m INFO[0m [2mhub[0m[2m:[0m Solmu 1 yhdistyi osoitteesta 127.0.0.1
|
||||||
|
[2m2026-04-12T03:21:49.600021Z[0m [32m INFO[0m [2mhub[0m[2m:[0m Solmu 1 (natiivi) | 127.0.0.1 | Mac | Darwin 26.3.1 | 12 ydintä | 32768 MB RAM | varaus: 4 GB
|
||||||
|
[2m2026-04-12T03:21:49.600099Z[0m [32m INFO[0m [2mhub[0m[2m:[0m GPU 0: Apple M2 Max | VRAM: 0/24576 MB | 0°C | 0%
|
||||||
@@ -49,6 +49,13 @@ impl NodeDb {
|
|||||||
INSERT INTO _schema_version VALUES (3);
|
INSERT INTO _schema_version VALUES (3);
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
if version < 4 {
|
||||||
|
let _ = conn.execute_batch("
|
||||||
|
ALTER TABLE node_sessions ADD COLUMN is_paused BOOLEAN DEFAULT 0;
|
||||||
|
DELETE FROM _schema_version;
|
||||||
|
INSERT INTO _schema_version VALUES (4);
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
conn.execute_batch("
|
conn.execute_batch("
|
||||||
CREATE TABLE IF NOT EXISTS node_sessions (
|
CREATE TABLE IF NOT EXISTS node_sessions (
|
||||||
@@ -84,7 +91,10 @@ impl NodeDb {
|
|||||||
has_webgpu BOOLEAN,
|
has_webgpu BOOLEAN,
|
||||||
|
|
||||||
-- Tehtävätilastot
|
-- Tehtävätilastot
|
||||||
tasks_completed INTEGER DEFAULT 0
|
tasks_completed INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
-- Ohjaustilat
|
||||||
|
is_paused BOOLEAN DEFAULT 0
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS pair_results (
|
CREATE TABLE IF NOT EXISTS pair_results (
|
||||||
@@ -183,6 +193,14 @@ impl NodeDb {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_session_status(&self, node_id: u64, is_paused: bool) {
|
||||||
|
let conn = self.conn.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
|
let _ = conn.execute(
|
||||||
|
"UPDATE node_sessions SET is_paused = ?1 WHERE node_id = ?2 AND disconnected_at IS NULL",
|
||||||
|
params![is_paused as i64, node_id as i64],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Sulkee saman IP:n viewer-sessiot kun aktiivinen node liittyy
|
/// Sulkee saman IP:n viewer-sessiot kun aktiivinen node liittyy
|
||||||
pub fn close_viewers_by_ip(&self, ip: &str) {
|
pub fn close_viewers_by_ip(&self, ip: &str) {
|
||||||
let conn = self.conn.lock().unwrap_or_else(|e| e.into_inner());
|
let conn = self.conn.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
@@ -216,7 +234,7 @@ impl NodeDb {
|
|||||||
"SELECT id, node_id, ip, node_type, connected_at, disconnected_at,
|
"SELECT id, node_id, ip, node_type, connected_at, disconnected_at,
|
||||||
platform, hostname, os, cpu_cores, cpu_model, ram_mb,
|
platform, hostname, os, cpu_cores, cpu_model, ram_mb,
|
||||||
gpu_name, gpu_vendor, gpu_backend, vram_total_mb, gpu_temp_c, gpu_util_pct,
|
gpu_name, gpu_vendor, gpu_backend, vram_total_mb, gpu_temp_c, gpu_util_pct,
|
||||||
allocated_gb, selected_task, has_webgpu, tasks_completed
|
allocated_gb, selected_task, has_webgpu, tasks_completed, is_paused
|
||||||
FROM node_sessions ORDER BY id DESC LIMIT ?1"
|
FROM node_sessions ORDER BY id DESC LIMIT ?1"
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
@@ -244,6 +262,7 @@ impl NodeDb {
|
|||||||
"selected_task": row.get::<_, Option<String>>(19)?,
|
"selected_task": row.get::<_, Option<String>>(19)?,
|
||||||
"has_webgpu": row.get::<_, Option<bool>>(20)?,
|
"has_webgpu": row.get::<_, Option<bool>>(20)?,
|
||||||
"tasks_completed": row.get::<_, i64>(21)?,
|
"tasks_completed": row.get::<_, i64>(21)?,
|
||||||
|
"is_paused": row.get::<_, Option<bool>>(22)?.unwrap_or(false),
|
||||||
}))
|
}))
|
||||||
}).unwrap().filter_map(|r| r.ok()).collect()
|
}).unwrap().filter_map(|r| r.ok()).collect()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const ALLOWED_ORIGINS: &[&str] = &[
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Sallitut viestityyypit clientilta
|
// Sallitut viestityyypit clientilta
|
||||||
const ALLOWED_MSG_TYPES: &[&str] = &["auth", "result", "pair_done", "llm_chunk", "llm_done", "llm_error", "download_progress", "user_text", "single_tokenize_done"];
|
const ALLOWED_MSG_TYPES: &[&str] = &["auth", "result", "pair_done", "llm_chunk", "llm_done", "llm_error", "download_progress", "user_text", "single_tokenize_done", "status_update"];
|
||||||
|
|
||||||
struct AppState {
|
struct AppState {
|
||||||
next_node_id: Mutex<u64>,
|
next_node_id: Mutex<u64>,
|
||||||
@@ -40,9 +40,12 @@ struct AppState {
|
|||||||
node_ips: Mutex<HashMap<u64, IpAddr>>,
|
node_ips: Mutex<HashMap<u64, IpAddr>>,
|
||||||
node_tasks: Mutex<HashMap<u64, String>>, // node_id → selected_task
|
node_tasks: Mutex<HashMap<u64, String>>, // node_id → selected_task
|
||||||
node_types: Mutex<HashMap<u64, String>>, // node_id → "native" | "browser"
|
node_types: Mutex<HashMap<u64, String>>, // node_id → "native" | "browser"
|
||||||
|
node_paused: Mutex<std::collections::HashSet<u64>>, // node_id → onko tauolla
|
||||||
node_busy: Mutex<std::collections::HashSet<u64>>, // Solmut joilla on aktiivinen tehtävä
|
node_busy: Mutex<std::collections::HashSet<u64>>, // Solmut joilla on aktiivinen tehtävä
|
||||||
pending_task_ids: Mutex<std::collections::HashSet<String>>, // Hubin jakamat task_id:t (gamification-validointi)
|
pending_task_ids: Mutex<std::collections::HashSet<String>>, // Hubin jakamat task_id:t (gamification-validointi)
|
||||||
|
pending_responses: Mutex<HashMap<String, tokio::sync::oneshot::Sender<serde_json::Value>>>, // task_id → oneshot API-vastaukselle
|
||||||
api_rate_limits: Mutex<HashMap<IpAddr, (std::time::Instant, u32)>>, // IP → (ikkuna-alku, pyyntömäärä)
|
api_rate_limits: Mutex<HashMap<IpAddr, (std::time::Instant, u32)>>, // IP → (ikkuna-alku, pyyntömäärä)
|
||||||
|
node_models: tokio::sync::RwLock<HashMap<u64, serde_json::Value>>, // node_id → ollama tags JSON
|
||||||
db: db::NodeDb,
|
db: db::NodeDb,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +83,8 @@ tr:hover td { background:#1c2333; }
|
|||||||
.table-wrap { overflow-x:auto; max-height:70vh; overflow-y:auto; }
|
.table-wrap { overflow-x:auto; max-height:70vh; overflow-y:auto; }
|
||||||
.online { color:var(--green); }
|
.online { color:var(--green); }
|
||||||
.offline { color:#8b949e; }
|
.offline { color:#8b949e; }
|
||||||
|
.pause-btn { background:var(--panel); border:1px solid var(--border); color:var(--text); padding:4px 8px; border-radius:4px; cursor:pointer; font-size:12px; }
|
||||||
|
.pause-btn:hover { border-color:var(--yellow); }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -91,6 +96,7 @@ tr:hover td { background:#1c2333; }
|
|||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
<div class="tab active" onclick="showTab('sessions')">Sessiot</div>
|
<div class="tab active" onclick="showTab('sessions')">Sessiot</div>
|
||||||
<div class="tab" onclick="showTab('pairs')">Tokenisointiparit</div>
|
<div class="tab" onclick="showTab('pairs')">Tokenisointiparit</div>
|
||||||
|
<div class="tab" onclick="showTab('hardware')">Laitteisto & Mallit</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="sessions" class="panel active">
|
<div id="sessions" class="panel active">
|
||||||
@@ -99,12 +105,12 @@ tr:hover td { background:#1c2333; }
|
|||||||
<colgroup>
|
<colgroup>
|
||||||
<col style="width:35px"><col style="width:85px"><col style="width:95px"><col style="width:65px"><col style="width:110px"><col style="width:80px">
|
<col style="width:35px"><col style="width:85px"><col style="width:95px"><col style="width:65px"><col style="width:110px"><col style="width:80px">
|
||||||
<col style="width:65px"><col style="width:40px"><col style="width:70px"><col style="width:90px"><col style="width:60px">
|
<col style="width:65px"><col style="width:40px"><col style="width:70px"><col style="width:90px"><col style="width:60px">
|
||||||
<col style="width:65px"><col style="width:40px"><col style="width:130px"><col style="width:60px">
|
<col style="width:65px"><col style="width:40px"><col style="width:130px"><col style="width:60px"><col style="width:80px">
|
||||||
</colgroup>
|
</colgroup>
|
||||||
<thead><tr>
|
<thead><tr>
|
||||||
<th>ID</th><th>Tila</th><th>Tehtävä</th><th>Tyyppi</th><th>IP</th><th>Alusta</th>
|
<th>ID</th><th>Tila</th><th>Tehtävä</th><th>Tyyppi</th><th>IP</th><th>Alusta</th>
|
||||||
<th>OS</th><th>CPU</th><th>RAM</th><th>GPU</th><th>VRAM</th>
|
<th>OS</th><th>CPU</th><th>RAM</th><th>GPU</th><th>VRAM</th>
|
||||||
<th>WebGPU</th><th>Teht.</th><th>Yhdistetty</th><th>Kesto</th>
|
<th>WebGPU</th><th>Teht.</th><th>Yhdistetty</th><th>Kesto</th><th>Toiminnot</th>
|
||||||
</tr></thead><tbody id="sessions-body"></tbody></table>
|
</tr></thead><tbody id="sessions-body"></tbody></table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -118,6 +124,19 @@ tr:hover td { background:#1c2333; }
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="hardware" class="panel">
|
||||||
|
<div class="stats-grid" id="hardware-stats"></div>
|
||||||
|
<h2 style="margin-top: 10px; margin-bottom: 10px; color: var(--accent); font-size: 16px;">Käytettävissä olevat paikalliset kielimallit</h2>
|
||||||
|
<div class="table-wrap">
|
||||||
|
<table>
|
||||||
|
<thead><tr>
|
||||||
|
<th>Nimi</th><th>Koko</th><th>Parametrit</th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody id="models-body"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function showTab(name) {
|
function showTab(name) {
|
||||||
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
|
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
|
||||||
@@ -149,12 +168,16 @@ function duration(start, end) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
const [statsRes, sessionsRes, pairsRes] = await Promise.all([
|
const [statsRes, sessionsRes, pairsRes, hwRes, modelsRes] = await Promise.all([
|
||||||
fetch('/api/stats'), fetch('/api/sessions'), fetch('/api/pairs')
|
fetch('/api/stats'), fetch('/api/sessions'), fetch('/api/pairs'),
|
||||||
|
fetch('/api/v1/hardware').catch(() => ({json: async()=>({gpu_name:'', vram_mb:0, ram_mb:0})})),
|
||||||
|
fetch('/api/v1/ollama/tags').catch(() => ({json: async()=>({models:[]})}))
|
||||||
]);
|
]);
|
||||||
const stats = await statsRes.json();
|
const stats = await statsRes.json();
|
||||||
const sessions = await sessionsRes.json();
|
const sessions = await sessionsRes.json();
|
||||||
const pairs = await pairsRes.json();
|
const pairs = await pairsRes.json();
|
||||||
|
const hw = await hwRes.json().catch(() => ({gpu_name:'', vram_mb:0, ram_mb:0}));
|
||||||
|
const modelsData = await modelsRes.json().catch(() => ({models:[]}));
|
||||||
|
|
||||||
// Versio
|
// Versio
|
||||||
if (stats.version) document.getElementById('admin-version').textContent = 'v' + stats.version;
|
if (stats.version) document.getElementById('admin-version').textContent = 'v' + stats.version;
|
||||||
@@ -190,9 +213,17 @@ async function load() {
|
|||||||
document.getElementById('sessions-body').innerHTML = sessions.map(s => {
|
document.getElementById('sessions-body').innerHTML = sessions.map(s => {
|
||||||
const online = !s.disconnected_at;
|
const online = !s.disconnected_at;
|
||||||
const isViewer = s.selected_task === 'viewer';
|
const isViewer = s.selected_task === 'viewer';
|
||||||
const status = online
|
let status;
|
||||||
? (isViewer ? '<span style="color:#d29922">CONNECTED</span>' : '<span class="online">ACTIVE</span>')
|
if (!online) {
|
||||||
: '<span class="offline">offline</span>';
|
status = '<span class="offline">offline</span>';
|
||||||
|
} else if (isViewer) {
|
||||||
|
status = '<span style="color:#d29922">CONNECTED</span>';
|
||||||
|
} else if (s.is_paused) {
|
||||||
|
status = '<span style="color:#8b949e">PAUSED</span>';
|
||||||
|
} else {
|
||||||
|
status = '<span class="online">ACTIVE</span>';
|
||||||
|
}
|
||||||
|
|
||||||
const typeBadge = s.node_type === 'native' ? badge('native','blue') : badge('browser','yellow');
|
const typeBadge = s.node_type === 'native' ? badge('native','blue') : badge('browser','yellow');
|
||||||
const taskColor = isViewer ? 'yellow' : 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 taskBadge = badge(taskNames[s.selected_task] || s.selected_task || '?', taskColor);
|
||||||
@@ -205,11 +236,16 @@ async function load() {
|
|||||||
const os = s.os || '-';
|
const os = s.os || '-';
|
||||||
const time = s.connected_at ? new Date(s.connected_at).toLocaleString('fi-FI') : '';
|
const time = s.connected_at ? new Date(s.connected_at).toLocaleString('fi-FI') : '';
|
||||||
const dur = duration(s.connected_at, s.disconnected_at);
|
const dur = duration(s.connected_at, s.disconnected_at);
|
||||||
|
const actionBtn = online && !isViewer
|
||||||
|
? `<button class="pause-btn" onclick="togglePause(${s.node_id}, ${s.is_paused})">${s.is_paused ? '▶ Työhön' : '⏸ Tauolle'}</button>`
|
||||||
|
: '';
|
||||||
|
|
||||||
return `<tr>
|
return `<tr>
|
||||||
<td>${s.node_id}</td><td>${status}</td><td>${taskBadge}</td><td>${typeBadge}</td><td>${s.ip}</td>
|
<td>${s.node_id}</td><td>${status}</td><td>${taskBadge}</td><td>${typeBadge}</td><td>${s.ip}</td>
|
||||||
<td>${plat}</td><td>${os}</td><td>${cores}</td><td>${ram}</td>
|
<td>${plat}</td><td>${os}</td><td>${cores}</td><td>${ram}</td>
|
||||||
<td>${gpu}</td><td>${vram}</td><td>${gpuBadge}</td>
|
<td>${gpu}</td><td>${vram}</td><td>${gpuBadge}</td>
|
||||||
<td>${s.tasks_completed}</td><td>${time}</td><td>${dur}</td>
|
<td>${s.tasks_completed}</td><td>${time}</td><td>${dur}</td>
|
||||||
|
<td>${actionBtn}</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
@@ -229,6 +265,35 @@ async function load() {
|
|||||||
<td>${p.duration_ms||0}ms</td>
|
<td>${p.duration_ms||0}ms</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
|
// Hardware
|
||||||
|
document.getElementById('hardware-stats').innerHTML = [
|
||||||
|
{v: hw.gpu_name || '-', l: 'Paikallinen GPU tila'},
|
||||||
|
{v: hw.vram_mb ? hw.vram_mb + ' MB' : '-', l: 'GPU Muisti (VRAM)'},
|
||||||
|
{v: hw.ram_mb ? hw.ram_mb + ' MB' : '-', l: 'RAM'},
|
||||||
|
].map(s => `<div class="stat-card"><div class="val">${s.v}</div><div class="label">${s.l}</div></div>`).join('');
|
||||||
|
|
||||||
|
// Models
|
||||||
|
document.getElementById('models-body').innerHTML = (modelsData.models || []).map(m => {
|
||||||
|
const sizeGb = (m.size / (1024*1024*1024)).toFixed(2) + ' GB';
|
||||||
|
const params = m.details?.parameter_size || '-';
|
||||||
|
return `<tr>
|
||||||
|
<td><strong>${m.name}</strong></td>
|
||||||
|
<td>${sizeGb}</td>
|
||||||
|
<td>${params}</td>
|
||||||
|
</tr>`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function togglePause(nodeId, isPaused) {
|
||||||
|
try {
|
||||||
|
await fetch('/api/v1/control/' + nodeId, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ action: isPaused ? 'resume' : 'pause' })
|
||||||
|
});
|
||||||
|
load(); // virkistetään
|
||||||
|
} catch(e) { console.error(e); }
|
||||||
}
|
}
|
||||||
|
|
||||||
load();
|
load();
|
||||||
@@ -262,9 +327,12 @@ async fn main() {
|
|||||||
node_ips: Mutex::new(HashMap::new()),
|
node_ips: Mutex::new(HashMap::new()),
|
||||||
node_tasks: Mutex::new(HashMap::new()),
|
node_tasks: Mutex::new(HashMap::new()),
|
||||||
node_types: Mutex::new(HashMap::new()),
|
node_types: Mutex::new(HashMap::new()),
|
||||||
|
node_paused: Mutex::new(std::collections::HashSet::new()),
|
||||||
node_busy: Mutex::new(std::collections::HashSet::new()),
|
node_busy: Mutex::new(std::collections::HashSet::new()),
|
||||||
pending_task_ids: Mutex::new(std::collections::HashSet::new()),
|
pending_task_ids: Mutex::new(std::collections::HashSet::new()),
|
||||||
|
pending_responses: Mutex::new(HashMap::new()),
|
||||||
api_rate_limits: Mutex::new(HashMap::new()),
|
api_rate_limits: Mutex::new(HashMap::new()),
|
||||||
|
node_models: tokio::sync::RwLock::new(HashMap::new()),
|
||||||
db: db::NodeDb::new(&std::env::var("DATABASE_PATH").unwrap_or_else(|_| "nodes.db".to_string())),
|
db: db::NodeDb::new(&std::env::var("DATABASE_PATH").unwrap_or_else(|_| "nodes.db".to_string())),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -381,6 +449,7 @@ async fn main() {
|
|||||||
.route("/api/pairs", get(api_pairs))
|
.route("/api/pairs", get(api_pairs))
|
||||||
.route("/api/stats", get(api_stats))
|
.route("/api/stats", get(api_stats))
|
||||||
.route("/api/v1/chat/completions", axum::routing::post(api_chat_completions))
|
.route("/api/v1/chat/completions", axum::routing::post(api_chat_completions))
|
||||||
|
.route("/api/v1/control/:id", axum::routing::post(api_control_node))
|
||||||
.route("/api/v1/model", axum::routing::post(api_change_model))
|
.route("/api/v1/model", axum::routing::post(api_change_model))
|
||||||
.route("/api/v1/hardware", get(api_hardware))
|
.route("/api/v1/hardware", get(api_hardware))
|
||||||
.route("/api/v1/ollama/tags", get(api_ollama_tags))
|
.route("/api/v1/ollama/tags", get(api_ollama_tags))
|
||||||
@@ -400,6 +469,26 @@ async fn main() {
|
|||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn api_control_node(
|
||||||
|
headers: axum::http::HeaderMap,
|
||||||
|
axum::extract::State(state): axum::extract::State<Arc<AppState>>,
|
||||||
|
axum::extract::Path(id): axum::extract::Path<u64>,
|
||||||
|
axum::Json(payload): axum::Json<serde_json::Value>,
|
||||||
|
) -> axum::response::Response {
|
||||||
|
if !check_admin_auth(&headers) { return admin_unauthorized(); }
|
||||||
|
let action = payload.get("action").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
|
if action == "pause" || action == "resume" {
|
||||||
|
let msg = serde_json::json!({ "type": "control", "action": action });
|
||||||
|
let channels = state.node_channels.read().await;
|
||||||
|
if let Some(tx) = channels.get(&id) {
|
||||||
|
let _ = tx.send(msg.to_string());
|
||||||
|
tracing::info!("Lähetetty control: {} solmulle {}", action, id);
|
||||||
|
return axum::Json(serde_json::json!({"status": "ok"})).into_response();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(axum::http::StatusCode::BAD_REQUEST, "Invalid action or node offline").into_response()
|
||||||
|
}
|
||||||
|
|
||||||
async fn api_sessions(
|
async fn api_sessions(
|
||||||
headers: axum::http::HeaderMap,
|
headers: axum::http::HeaderMap,
|
||||||
axum::extract::State(state): axum::extract::State<Arc<AppState>>,
|
axum::extract::State(state): axum::extract::State<Arc<AppState>>,
|
||||||
@@ -563,6 +652,17 @@ async fn broadcast_stats(state: &Arc<AppState>) {
|
|||||||
"tasks": completed
|
"tasks": completed
|
||||||
});
|
});
|
||||||
let _ = state.stats_tx.send(stats_msg.to_string());
|
let _ = state.stats_tx.send(stats_msg.to_string());
|
||||||
|
|
||||||
|
// Uutta: Laitetaan sama tieto myös kaikille yhdistyneille solmuille (viesti Hubilta Solmuille)
|
||||||
|
let node_status = serde_json::json!({
|
||||||
|
"type": "network_status",
|
||||||
|
"active_nodes": total_nodes,
|
||||||
|
"tasks": completed
|
||||||
|
});
|
||||||
|
let msg_str = node_status.to_string();
|
||||||
|
for tx in state.node_channels.read().await.values() {
|
||||||
|
let _ = tx.send(msg_str.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validoi client-viesti: pakollinen "type"-kenttä, sallittu tyyppi, validi JSON
|
/// Validoi client-viesti: pakollinen "type"-kenttä, sallittu tyyppi, validi JSON
|
||||||
@@ -730,6 +830,9 @@ async fn handle_socket(socket: WebSocket, state: Arc<AppState>, ip: IpAddr) {
|
|||||||
}
|
}
|
||||||
state.node_tasks.lock().unwrap().insert(node_id, selected_task);
|
state.node_tasks.lock().unwrap().insert(node_id, selected_task);
|
||||||
state.node_types.lock().unwrap().insert(node_id, node_type.to_string());
|
state.node_types.lock().unwrap().insert(node_id, node_type.to_string());
|
||||||
|
// Uudelleen-kirjautuessa nollataan tauko
|
||||||
|
state.node_paused.lock().unwrap().remove(&node_id);
|
||||||
|
state.db.update_session_status(node_id, false);
|
||||||
|
|
||||||
if node_type == "native" {
|
if node_type == "native" {
|
||||||
let sys = json.get("system");
|
let sys = json.get("system");
|
||||||
@@ -743,6 +846,12 @@ async fn handle_socket(socket: WebSocket, state: Arc<AppState>, ip: IpAddr) {
|
|||||||
node_id, ip, hostname, os, cores, ram, allocated
|
node_id, ip, hostname, os, cores, ram, allocated
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Tallennetaan välitetyt mallit muistiin
|
||||||
|
if let Some(models) = json.get("models") {
|
||||||
|
let mut nm = state.node_models.write().await;
|
||||||
|
nm.insert(node_id, models.clone());
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(gpus) = json.get("gpus").and_then(|v| v.as_array()) {
|
if let Some(gpus) = json.get("gpus").and_then(|v| v.as_array()) {
|
||||||
for gpu in gpus {
|
for gpu in gpus {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
@@ -780,6 +889,18 @@ async fn handle_socket(socket: WebSocket, state: Arc<AppState>, ip: IpAddr) {
|
|||||||
});
|
});
|
||||||
let _ = state.stats_tx.send(join_msg.to_string());
|
let _ = state.stats_tx.send(join_msg.to_string());
|
||||||
|
|
||||||
|
} else if msg_type == "status_update" {
|
||||||
|
let status = json.get("status").and_then(|v| v.as_str()).unwrap_or("active");
|
||||||
|
if status == "paused" {
|
||||||
|
state.node_paused.lock().unwrap().insert(node_id);
|
||||||
|
state.db.update_session_status(node_id, true);
|
||||||
|
tracing::info!("Solmu {} ({}) asettui tauolle.", node_id, ip);
|
||||||
|
} else {
|
||||||
|
state.node_paused.lock().unwrap().remove(&node_id);
|
||||||
|
state.db.update_session_status(node_id, false);
|
||||||
|
tracing::info!("Solmu {} ({}) on taas aktiivinen.", node_id, ip);
|
||||||
|
}
|
||||||
|
broadcast_stats(&state).await;
|
||||||
} else if msg_type == "result" {
|
} else if msg_type == "result" {
|
||||||
tracing::info!("Solmu {} sai tuloksen: {}", node_id, text);
|
tracing::info!("Solmu {} sai tuloksen: {}", node_id, text);
|
||||||
{
|
{
|
||||||
@@ -875,11 +996,18 @@ async fn handle_socket(socket: WebSocket, state: Arc<AppState>, ip: IpAddr) {
|
|||||||
} else if msg_type == "llm_done" {
|
} else if msg_type == "llm_done" {
|
||||||
// Vapautetaan solmu ja tarkistetaan task_id:n aitous
|
// Vapautetaan solmu ja tarkistetaan task_id:n aitous
|
||||||
state.node_busy.lock().unwrap().remove(&node_id);
|
state.node_busy.lock().unwrap().remove(&node_id);
|
||||||
let valid_task = if let Some(tid) = json.get("task_id").and_then(|v| v.as_str()) {
|
let task_id = json.get("task_id").and_then(|v| v.as_str()).map(|s| s.to_string());
|
||||||
state.pending_task_ids.lock().unwrap().remove(tid)
|
let valid_task = if let Some(ref tid) = task_id {
|
||||||
|
state.pending_task_ids.lock().unwrap().remove(tid.as_str())
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Jos API-pyyntö odottaa tätä vastausta, reititetään suoraan oneshot-kanavaan
|
||||||
|
let api_sender = task_id.as_ref().and_then(|tid| {
|
||||||
|
state.pending_responses.lock().unwrap().remove(tid)
|
||||||
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut json = json;
|
let mut json = json;
|
||||||
if let Some(obj) = json.as_object_mut() {
|
if let Some(obj) = json.as_object_mut() {
|
||||||
@@ -899,6 +1027,12 @@ async fn handle_socket(socket: WebSocket, state: Arc<AppState>, ip: IpAddr) {
|
|||||||
state.db.increment_tasks(node_id);
|
state.db.increment_tasks(node_id);
|
||||||
obj.insert("node_id".to_string(), serde_json::json!(node_id));
|
obj.insert("node_id".to_string(), serde_json::json!(node_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(sender) = api_sender {
|
||||||
|
// API-pyyntö: reititetään vastaus suoraan odottajalle
|
||||||
|
let _ = sender.send(json.clone());
|
||||||
|
}
|
||||||
|
// UI-broadcast jatkuu normaalisti
|
||||||
let _ = state.stats_tx.send(json.to_string());
|
let _ = state.stats_tx.send(json.to_string());
|
||||||
|
|
||||||
let active_incentives = state.feature_flags.read().await.get("Insentiivit").copied().unwrap_or(false);
|
let active_incentives = state.feature_flags.read().await.get("Insentiivit").copied().unwrap_or(false);
|
||||||
@@ -931,40 +1065,45 @@ async fn handle_socket(socket: WebSocket, state: Arc<AppState>, ip: IpAddr) {
|
|||||||
}
|
}
|
||||||
} else if msg_type == "llm_error" {
|
} else if msg_type == "llm_error" {
|
||||||
state.node_busy.lock().unwrap().remove(&node_id);
|
state.node_busy.lock().unwrap().remove(&node_id);
|
||||||
if let Some(tid) = json.get("task_id").and_then(|v| v.as_str()) {
|
let task_id = json.get("task_id").and_then(|v| v.as_str()).map(|s| s.to_string());
|
||||||
state.pending_task_ids.lock().unwrap().remove(tid);
|
if let Some(ref tid) = task_id {
|
||||||
|
state.pending_task_ids.lock().unwrap().remove(tid.as_str());
|
||||||
}
|
}
|
||||||
|
// Jos API-pyyntö odottaa, reititetään virhe oneshot-kanavaan
|
||||||
|
let api_sender = task_id.as_ref().and_then(|tid| {
|
||||||
|
state.pending_responses.lock().unwrap().remove(tid)
|
||||||
|
});
|
||||||
{
|
{
|
||||||
let mut json = json;
|
let mut json = json;
|
||||||
if let Some(obj) = json.as_object_mut() {
|
if let Some(obj) = json.as_object_mut() {
|
||||||
obj.insert("node_id".to_string(), serde_json::json!(node_id));
|
obj.insert("node_id".to_string(), serde_json::json!(node_id));
|
||||||
}
|
}
|
||||||
|
if let Some(sender) = api_sender {
|
||||||
|
let _ = sender.send(json.clone());
|
||||||
|
}
|
||||||
let _ = state.stats_tx.send(json.to_string());
|
let _ = state.stats_tx.send(json.to_string());
|
||||||
}
|
}
|
||||||
} else if msg_type == "user_text" {
|
} else if msg_type == "user_text" {
|
||||||
// Käyttäjän lähettämä teksti — broadcastataan pair_taskina ja llm_promptina
|
// Käyttäjän lähettämä teksti — kohdennettu reititys lähettäjäsolmulle
|
||||||
let text = json.get("text").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
let text = json.get("text").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||||
let task_type = json.get("task_type").and_then(|v| v.as_str()).unwrap_or("tokenize");
|
let task_type = json.get("task_type").and_then(|v| v.as_str()).unwrap_or("tokenize");
|
||||||
if !text.is_empty() {
|
if !text.is_empty() {
|
||||||
let preview: String = text.chars().take(80).collect();
|
let preview: String = text.chars().take(80).collect();
|
||||||
tracing::info!("Solmu {} lähetti oman tekstin ({}): \"{}\"", node_id, task_type, preview);
|
tracing::info!("Solmu {} lähetti oman tekstin ({}): \"{}\"", node_id, task_type, preview);
|
||||||
match task_type {
|
let msg = match task_type {
|
||||||
"tokenize" => {
|
"tokenize" => serde_json::json!({
|
||||||
let msg = serde_json::json!({
|
|
||||||
"type": "single_tokenize",
|
"type": "single_tokenize",
|
||||||
"text": text,
|
"text": text,
|
||||||
});
|
}),
|
||||||
let _ = state.stats_tx.send(msg.to_string());
|
_ => serde_json::json!({
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// LLM-prompti: lähetetään VAIN valitulle mallille, ei kaikille (välttää turhaa ruuhkaa ja busy-tiloja)
|
|
||||||
let prompt = serde_json::json!({
|
|
||||||
"type": "llm_prompt",
|
"type": "llm_prompt",
|
||||||
"prompt": text,
|
"prompt": text,
|
||||||
"model": task_type,
|
"model": task_type,
|
||||||
});
|
}),
|
||||||
let _ = state.stats_tx.send(prompt.to_string());
|
};
|
||||||
}
|
// Lähetetään takaisin lähettäjäsolmulle (käyttäjä haluaa oman tekstinsä tuloksen)
|
||||||
|
if let Some(tx) = state.node_channels.read().await.get(&node_id) {
|
||||||
|
let _ = tx.send(msg.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -989,6 +1128,8 @@ async fn handle_socket(socket: WebSocket, state: Arc<AppState>, ip: IpAddr) {
|
|||||||
vram.remove(&node_id);
|
vram.remove(&node_id);
|
||||||
}
|
}
|
||||||
state.node_types.lock().unwrap().remove(&node_id);
|
state.node_types.lock().unwrap().remove(&node_id);
|
||||||
|
state.node_paused.lock().unwrap().remove(&node_id);
|
||||||
|
state.node_models.write().await.remove(&node_id);
|
||||||
tracing::info!("Solmu {} ({}) poistui verkosta.", node_id, ip);
|
tracing::info!("Solmu {} ({}) poistui verkosta.", node_id, ip);
|
||||||
broadcast_stats(&state).await;
|
broadcast_stats(&state).await;
|
||||||
sender_task.abort();
|
sender_task.abort();
|
||||||
@@ -1009,7 +1150,16 @@ struct ChatCompletionResponse {
|
|||||||
tokens_generated: u64,
|
tokens_generated: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn api_ollama_tags() -> axum::response::Response {
|
async fn api_ollama_tags(
|
||||||
|
axum::extract::State(state): axum::extract::State<Arc<AppState>>,
|
||||||
|
) -> axum::response::Response {
|
||||||
|
// Haetaan natiivisolmun tila muistista — priorisoidaan aito verkko-solmu
|
||||||
|
let node_models = state.node_models.read().await;
|
||||||
|
if let Some((_, models_json)) = node_models.iter().next() {
|
||||||
|
return axum::Json(models_json.clone()).into_response();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Haetaan lokaalista infra-Ollamasta ohjaimesta käsin (esim dev ympäristö)
|
||||||
let ollama_url = std::env::var("OLLAMA_URL").unwrap_or_else(|_| "http://ollama:11434".to_string());
|
let ollama_url = std::env::var("OLLAMA_URL").unwrap_or_else(|_| "http://ollama:11434".to_string());
|
||||||
match reqwest::get(format!("{}/api/tags", ollama_url)).await {
|
match reqwest::get(format!("{}/api/tags", ollama_url)).await {
|
||||||
Ok(resp) => {
|
Ok(resp) => {
|
||||||
@@ -1033,11 +1183,10 @@ async fn api_hardware(
|
|||||||
});
|
});
|
||||||
|
|
||||||
let (mut vram_mb, mut gpu_name, ram_mb) = if let Some(s) = native {
|
let (mut vram_mb, mut gpu_name, ram_mb) = if let Some(s) = native {
|
||||||
let gpus = s.get("gpus").and_then(|v| v.as_array());
|
// Tieto on tietokannassa litteänä
|
||||||
let gpu = gpus.and_then(|g| g.first());
|
let vram = s.get("vram_total_mb").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||||
let vram = gpu.and_then(|g| g.get("vram_total_mb")).and_then(|v| v.as_u64()).unwrap_or(0);
|
let name = s.get("gpu_name").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||||
let name = gpu.and_then(|g| g.get("name")).and_then(|v| v.as_str()).unwrap_or("").to_string();
|
let ram = s.get("ram_mb").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||||
let ram = s.get("system").and_then(|v| v.get("ram_total_mb")).and_then(|v| v.as_u64()).unwrap_or(0);
|
|
||||||
(vram, name, ram)
|
(vram, name, ram)
|
||||||
} else {
|
} else {
|
||||||
(0, String::new(), 0)
|
(0, String::new(), 0)
|
||||||
@@ -1106,7 +1255,9 @@ async fn api_chat_completions(
|
|||||||
let tasks = state.node_tasks.lock().unwrap();
|
let tasks = state.node_tasks.lock().unwrap();
|
||||||
let _busy = state.node_busy.lock().unwrap();
|
let _busy = state.node_busy.lock().unwrap();
|
||||||
let node_types = state.node_types.lock().unwrap();
|
let node_types = state.node_types.lock().unwrap();
|
||||||
let matching: Vec<u64> = tasks.iter().filter(|(_, task)| {
|
let paused = state.node_paused.lock().unwrap();
|
||||||
|
let matching: Vec<u64> = tasks.iter().filter(|(k, task)| {
|
||||||
|
if paused.contains(k) { return false; } // Ei sallita tauotettuja
|
||||||
// Eksakti match tai qwen-perheen yhteensopivuus (selain: qwen-coder-05b, natiivi: qwen2.5-coder:7b)
|
// Eksakti match tai qwen-perheen yhteensopivuus (selain: qwen-coder-05b, natiivi: qwen2.5-coder:7b)
|
||||||
let req_model = payload.model.to_lowercase();
|
let req_model = payload.model.to_lowercase();
|
||||||
let node_task = task.to_lowercase();
|
let node_task = task.to_lowercase();
|
||||||
@@ -1161,8 +1312,9 @@ async fn api_chat_completions(
|
|||||||
msg.as_object_mut().unwrap().insert("max_tokens".to_string(), serde_json::json!(mt));
|
msg.as_object_mut().unwrap().insert("max_tokens".to_string(), serde_json::json!(mt));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Odotuskanava valmiiksi (solmu palauttaa tuloksen stats_tx kautta)
|
// Oneshot-kanava: solmu palauttaa tuloksen suoraan tälle pyynnölle
|
||||||
let mut rx = state.stats_tx.subscribe();
|
let (resp_tx, resp_rx) = tokio::sync::oneshot::channel::<serde_json::Value>();
|
||||||
|
state.pending_responses.lock().unwrap().insert(payload.task_id.clone(), resp_tx);
|
||||||
|
|
||||||
// Kohdennettu reititys: lähetetään AI-tehtävä suoraan VAIN valitulle solmulle
|
// Kohdennettu reititys: lähetetään AI-tehtävä suoraan VAIN valitulle solmulle
|
||||||
{
|
{
|
||||||
@@ -1171,48 +1323,34 @@ async fn api_chat_completions(
|
|||||||
let _ = tx.send(msg.to_string());
|
let _ = tx.send(msg.to_string());
|
||||||
tracing::info!("Reititettiin API-pyyntö solmulle {} (Malli: {})", target_node_id, payload.model);
|
tracing::info!("Reititettiin API-pyyntö solmulle {} (Malli: {})", target_node_id, payload.model);
|
||||||
} else {
|
} else {
|
||||||
|
state.pending_responses.lock().unwrap().remove(&payload.task_id);
|
||||||
return (axum::http::StatusCode::SERVICE_UNAVAILABLE, "Verkkovirhe: solmun yhteys katkesi reitityksen aikana").into_response();
|
return (axum::http::StatusCode::SERVICE_UNAVAILABLE, "Verkkovirhe: solmun yhteys katkesi reitityksen aikana").into_response();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let timeout = tokio::time::timeout(std::time::Duration::from_secs(600), async move {
|
let timeout = tokio::time::timeout(std::time::Duration::from_secs(600), resp_rx).await;
|
||||||
loop {
|
|
||||||
let msg_str = match rx.recv().await {
|
match timeout {
|
||||||
Ok(msg) => msg,
|
Ok(Ok(v)) => {
|
||||||
Err(broadcast::error::RecvError::Lagged(n)) => {
|
if v["type"].as_str() == Some("llm_error") {
|
||||||
tracing::debug!("API-kanava lagged {} viestiä", n);
|
let err = v["error"].as_str().unwrap_or("Määrittelemätön virhe solmussa").to_string();
|
||||||
continue;
|
(axum::http::StatusCode::CONFLICT, err).into_response()
|
||||||
}
|
} else {
|
||||||
Err(_) => return Ok(None), // Kanava suljettu
|
axum::Json(ChatCompletionResponse {
|
||||||
};
|
|
||||||
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&msg_str) {
|
|
||||||
if v["type"].as_str() == Some("llm_done") {
|
|
||||||
if let Some(tid) = v["task_id"].as_str() {
|
|
||||||
if tid == payload.task_id {
|
|
||||||
return Ok(Some(ChatCompletionResponse {
|
|
||||||
response: v["response"].as_str().unwrap_or("").to_string(),
|
response: v["response"].as_str().unwrap_or("").to_string(),
|
||||||
model: v["model"].as_str().unwrap_or("").to_string(),
|
model: v["model"].as_str().unwrap_or("").to_string(),
|
||||||
tokens_generated: v["tokens_generated"].as_u64().unwrap_or(0),
|
tokens_generated: v["tokens_generated"].as_u64().unwrap_or(0),
|
||||||
}));
|
}).into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if v["type"].as_str() == Some("llm_error") {
|
Ok(Err(_)) => {
|
||||||
if let Some(tid) = v["task_id"].as_str() {
|
// Oneshot-kanava sulkeutui (solmu katosi)
|
||||||
if tid == payload.task_id {
|
state.pending_responses.lock().unwrap().remove(&payload.task_id);
|
||||||
return Err(v["error"].as_str().unwrap_or("Määrittelemätön virhe solmussa").to_string());
|
(axum::http::StatusCode::INTERNAL_SERVER_ERROR, "Verkkovirhe: yhteys katkesi").into_response()
|
||||||
}
|
}
|
||||||
|
Err(_) => {
|
||||||
|
state.pending_responses.lock().unwrap().remove(&payload.task_id);
|
||||||
|
(axum::http::StatusCode::GATEWAY_TIMEOUT, "Aikakatkaisu: solmu ei saanut tehtävää ajoissa valmiiksi").into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
#[allow(unreachable_code)]
|
|
||||||
Ok(None)
|
|
||||||
}).await;
|
|
||||||
|
|
||||||
match timeout {
|
|
||||||
Ok(Ok(Some(res))) => axum::Json(res).into_response(),
|
|
||||||
Ok(Ok(None)) => (axum::http::StatusCode::INTERNAL_SERVER_ERROR, "Verkkovirhe: yhteys katkesi").into_response(),
|
|
||||||
Ok(Err(err)) => (axum::http::StatusCode::CONFLICT, err).into_response(),
|
|
||||||
Err(_) => (axum::http::StatusCode::GATEWAY_TIMEOUT, "Aikakatkaisu: solmu ei saanut tehtävää ajoissa valmiiksi").into_response(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
208
network-poc/native-node.log
Normal file
208
network-poc/native-node.log
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
[2m2026-04-12T02:49:51.405400Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Kipinä Native Node käynnistyy — hub: ws://127.0.0.1:3000/ws, varaus: 4 GB
|
||||||
|
[2m2026-04-12T02:49:51.422359Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Järjestelmä: Mac | Darwin 26.3.1 | 12 ydintä | 32768 MB RAM
|
||||||
|
[2m2026-04-12T02:49:51.437017Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m GPU 0: Apple M2 Max (Apple) [Metal] | VRAM: ?/24576 MB | ? | kuormitus: ?
|
||||||
|
[2m2026-04-12T02:49:51.437041Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Alustetaan Ollama-yhteyttä...
|
||||||
|
[2m2026-04-12T02:49:51.441582Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama löytyi osoitteesta: http://localhost:11434
|
||||||
|
[2m2026-04-12T02:50:03.018835Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Kipinä Native Node käynnistyy — hub: ws://127.0.0.1:3000/ws, varaus: 4 GB
|
||||||
|
[2m2026-04-12T02:50:03.036749Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Järjestelmä: Mac | Darwin 26.3.1 | 12 ydintä | 32768 MB RAM
|
||||||
|
[2m2026-04-12T02:50:03.052051Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m GPU 0: Apple M2 Max (Apple) [Metal] | VRAM: ?/24576 MB | ? | kuormitus: ?
|
||||||
|
[2m2026-04-12T02:50:03.052084Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Alustetaan Ollama-yhteyttä...
|
||||||
|
[2m2026-04-12T02:50:03.056985Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama löytyi osoitteesta: http://localhost:11434
|
||||||
|
[2m2026-04-12T02:50:14.540287Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Kipinä Native Node käynnistyy — hub: ws://127.0.0.1:3000/ws, varaus: 4 GB
|
||||||
|
[2m2026-04-12T02:50:14.558901Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Järjestelmä: Mac | Darwin 26.3.1 | 12 ydintä | 32768 MB RAM
|
||||||
|
[2m2026-04-12T02:50:14.572180Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m GPU 0: Apple M2 Max (Apple) [Metal] | VRAM: ?/24576 MB | ? | kuormitus: ?
|
||||||
|
[2m2026-04-12T02:50:14.572210Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Alustetaan Ollama-yhteyttä...
|
||||||
|
[2m2026-04-12T02:50:14.575891Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama löytyi osoitteesta: http://localhost:11434
|
||||||
|
[2m2026-04-12T02:52:05.405865Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Kipinä Native Node käynnistyy — hub: ws://127.0.0.1:3000/ws, varaus: 4 GB
|
||||||
|
[2m2026-04-12T02:52:05.422571Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Järjestelmä: Mac | Darwin 26.3.1 | 12 ydintä | 32768 MB RAM
|
||||||
|
[2m2026-04-12T02:52:05.433853Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m GPU 0: Apple M2 Max (Apple) [Metal] | VRAM: ?/24576 MB | ? | kuormitus: ?
|
||||||
|
[2m2026-04-12T02:52:05.433870Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Alustetaan Ollama-yhteyttä...
|
||||||
|
[2m2026-04-12T02:52:05.437718Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama löytyi osoitteesta: http://localhost:11434
|
||||||
|
[2m2026-04-12T02:52:11.707486Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama backend: http://localhost:11434 | malli: qwen2.5-coder:7b-instruct-q4_K_M
|
||||||
|
[2m2026-04-12T02:52:11.707646Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Tarkistetaan malli qwen2.5-coder:7b-instruct-q4_K_M...
|
||||||
|
[2m2026-04-12T02:52:12.713480Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Malli qwen2.5-coder:7b-instruct-q4_K_M valmis
|
||||||
|
[2m2026-04-12T02:52:12.713706Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Ollama valmis inferenssiin!
|
||||||
|
[2m2026-04-12T02:52:12.713749Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Käytettävä kielimalli konfiguroitu (selected_task): qwen2.5-coder:7b-instruct-q4_K_M
|
||||||
|
[2m2026-04-12T02:52:12.719515Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Yhdistetty hubiin!
|
||||||
|
[2m2026-04-12T02:52:22.800662Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Tauotetaan solmun suoritus (Hub ei lähetä tehtäviä)...
|
||||||
|
[2m2026-04-12T02:52:25.475326Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Jatketaan solmun suoritusta...
|
||||||
|
[2m2026-04-12T02:52:28.692378Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Kipinä Native Node käynnistyy — hub: ws://127.0.0.1:3000/ws, varaus: 4 GB
|
||||||
|
[2m2026-04-12T02:52:28.708978Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Järjestelmä: Mac | Darwin 26.3.1 | 12 ydintä | 32768 MB RAM
|
||||||
|
[2m2026-04-12T02:52:28.723045Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m GPU 0: Apple M2 Max (Apple) [Metal] | VRAM: ?/24576 MB | ? | kuormitus: ?
|
||||||
|
[2m2026-04-12T02:52:28.723071Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Alustetaan Ollama-yhteyttä...
|
||||||
|
[2m2026-04-12T02:52:28.728130Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama löytyi osoitteesta: http://localhost:11434
|
||||||
|
[2m2026-04-12T02:52:30.362566Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama backend: http://localhost:11434 | malli: qwen2.5-coder:7b-instruct-q4_K_M
|
||||||
|
[2m2026-04-12T02:52:30.362707Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Tarkistetaan malli qwen2.5-coder:7b-instruct-q4_K_M...
|
||||||
|
[2m2026-04-12T02:52:31.248236Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Malli qwen2.5-coder:7b-instruct-q4_K_M valmis
|
||||||
|
[2m2026-04-12T02:52:31.248469Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Ollama valmis inferenssiin!
|
||||||
|
[2m2026-04-12T02:52:31.248505Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Käytettävä kielimalli konfiguroitu (selected_task): qwen2.5-coder:7b-instruct-q4_K_M
|
||||||
|
[2m2026-04-12T02:52:31.254260Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Yhdistetty hubiin!
|
||||||
|
[2m2026-04-12T02:53:35.385696Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Kipinä Native Node käynnistyy — hub: ws://127.0.0.1:3000/ws, varaus: 4 GB
|
||||||
|
[2m2026-04-12T02:53:35.401489Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Järjestelmä: Mac | Darwin 26.3.1 | 12 ydintä | 32768 MB RAM
|
||||||
|
[2m2026-04-12T02:53:35.412450Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m GPU 0: Apple M2 Max (Apple) [Metal] | VRAM: ?/24576 MB | ? | kuormitus: ?
|
||||||
|
[2m2026-04-12T02:53:35.412471Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Alustetaan Ollama-yhteyttä...
|
||||||
|
[2m2026-04-12T02:53:35.416006Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama löytyi osoitteesta: http://localhost:11434
|
||||||
|
[2m2026-04-12T02:53:44.463643Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama backend: http://localhost:11434 | malli: qwen2.5-coder:7b-instruct-q4_K_M
|
||||||
|
[2m2026-04-12T02:53:44.463794Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Tarkistetaan malli qwen2.5-coder:7b-instruct-q4_K_M...
|
||||||
|
[2m2026-04-12T02:53:45.213997Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Malli qwen2.5-coder:7b-instruct-q4_K_M valmis
|
||||||
|
[2m2026-04-12T02:53:45.214232Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Ollama valmis inferenssiin!
|
||||||
|
[2m2026-04-12T02:53:45.214287Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Käytettävä kielimalli konfiguroitu (selected_task): qwen2.5-coder:7b-instruct-q4_K_M
|
||||||
|
[2m2026-04-12T02:53:45.233548Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Yhdistetty hubiin!
|
||||||
|
[2m2026-04-12T02:57:27.480736Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Kipinä Native Node käynnistyy — hub: ws://127.0.0.1:3000/ws, varaus: 4 GB
|
||||||
|
[2m2026-04-12T02:57:27.497388Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Järjestelmä: Mac | Darwin 26.3.1 | 12 ydintä | 32768 MB RAM
|
||||||
|
[2m2026-04-12T02:57:27.508598Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m GPU 0: Apple M2 Max (Apple) [Metal] | VRAM: ?/24576 MB | ? | kuormitus: ?
|
||||||
|
[2m2026-04-12T02:57:27.508629Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Alustetaan Ollama-yhteyttä...
|
||||||
|
[2m2026-04-12T02:57:27.512329Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama löytyi osoitteesta: http://localhost:11434
|
||||||
|
[2m2026-04-12T02:57:27.512356Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama backend: http://localhost:11434 | malli: qwen2.5-coder:3b
|
||||||
|
[2m2026-04-12T02:57:27.512365Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Tarkistetaan malli qwen2.5-coder:3b...
|
||||||
|
[2m2026-04-12T02:57:28.357205Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Malli qwen2.5-coder:3b valmis
|
||||||
|
[2m2026-04-12T02:57:28.357432Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Ollama valmis inferenssiin!
|
||||||
|
[2m2026-04-12T02:57:28.357489Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Käytettävä kielimalli konfiguroitu (selected_task): qwen2.5-coder:3b
|
||||||
|
[2m2026-04-12T02:57:28.365906Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Yhdistetty hubiin!
|
||||||
|
[2m2026-04-12T02:57:50.406189Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Kipinä Native Node käynnistyy — hub: ws://127.0.0.1:3000/ws, varaus: 4 GB
|
||||||
|
[2m2026-04-12T02:57:50.423119Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Järjestelmä: Mac | Darwin 26.3.1 | 12 ydintä | 32768 MB RAM
|
||||||
|
[2m2026-04-12T02:57:50.433561Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m GPU 0: Apple M2 Max (Apple) [Metal] | VRAM: ?/24576 MB | ? | kuormitus: ?
|
||||||
|
[2m2026-04-12T02:57:50.433582Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Alustetaan Ollama-yhteyttä...
|
||||||
|
[2m2026-04-12T02:57:50.437105Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama löytyi osoitteesta: http://localhost:11434
|
||||||
|
[2m2026-04-12T02:57:54.978974Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama backend: http://localhost:11434 | malli: qwen2.5-coder:3b
|
||||||
|
[2m2026-04-12T02:57:54.979107Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Tarkistetaan malli qwen2.5-coder:3b...
|
||||||
|
[2m2026-04-12T02:57:55.795772Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Malli qwen2.5-coder:3b valmis
|
||||||
|
[2m2026-04-12T02:57:55.796000Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Ollama valmis inferenssiin!
|
||||||
|
[2m2026-04-12T02:57:55.796037Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Käytettävä kielimalli konfiguroitu (selected_task): qwen2.5-coder:3b
|
||||||
|
[2m2026-04-12T02:57:55.800614Z[0m [33m WARN[0m [2mnative_node[0m[2m:[0m Hubiin yhdistäminen epäonnistui: IO error: Connection refused (os error 61) — yritetään uudelleen 5s...
|
||||||
|
[2m2026-04-12T02:58:00.808617Z[0m [33m WARN[0m [2mnative_node[0m[2m:[0m Hubiin yhdistäminen epäonnistui: IO error: Connection refused (os error 61) — yritetään uudelleen 5s...
|
||||||
|
[2m2026-04-12T02:58:05.810771Z[0m [33m WARN[0m [2mnative_node[0m[2m:[0m Hubiin yhdistäminen epäonnistui: IO error: Connection refused (os error 61) — yritetään uudelleen 5s...
|
||||||
|
[2m2026-04-12T02:58:10.813443Z[0m [33m WARN[0m [2mnative_node[0m[2m:[0m Hubiin yhdistäminen epäonnistui: IO error: Connection refused (os error 61) — yritetään uudelleen 5s...
|
||||||
|
[2m2026-04-12T03:01:16.781321Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Kipinä Native Node käynnistyy — hub: ws://127.0.0.1:3000/ws, varaus: 4 GB
|
||||||
|
[2m2026-04-12T03:01:16.794319Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Järjestelmä: Mac | Darwin 26.3.1 | 12 ydintä | 32768 MB RAM
|
||||||
|
[2m2026-04-12T03:01:16.806165Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m GPU 0: Apple M2 Max (Apple) [Metal] | VRAM: ?/24576 MB | ? | kuormitus: ?
|
||||||
|
[2m2026-04-12T03:01:16.806188Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Alustetaan Ollama-yhteyttä...
|
||||||
|
[2m2026-04-12T03:01:16.810275Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama löytyi osoitteesta: http://localhost:11434
|
||||||
|
[2m2026-04-12T03:01:18.325121Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama backend: http://localhost:11434 | malli: qwen2.5-coder:3b
|
||||||
|
[2m2026-04-12T03:01:18.325250Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Tarkistetaan malli qwen2.5-coder:3b...
|
||||||
|
[2m2026-04-12T03:01:20.454444Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Malli qwen2.5-coder:3b valmis
|
||||||
|
[2m2026-04-12T03:01:20.454587Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Ollama valmis inferenssiin!
|
||||||
|
[2m2026-04-12T03:01:20.454618Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Käytettävä kielimalli konfiguroitu (selected_task): qwen2.5-coder:3b
|
||||||
|
[2m2026-04-12T03:01:20.468023Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Yhdistetty hubiin!
|
||||||
|
[2m2026-04-12T03:02:28.510843Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Kipinä Native Node käynnistyy — hub: ws://127.0.0.1:3000/ws, varaus: 4 GB
|
||||||
|
[2m2026-04-12T03:02:28.526577Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Järjestelmä: Mac | Darwin 26.3.1 | 12 ydintä | 32768 MB RAM
|
||||||
|
[2m2026-04-12T03:02:28.539782Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m GPU 0: Apple M2 Max (Apple) [Metal] | VRAM: ?/24576 MB | ? | kuormitus: ?
|
||||||
|
[2m2026-04-12T03:02:28.539802Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Alustetaan Ollama-yhteyttä...
|
||||||
|
[2m2026-04-12T03:02:28.545400Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama löytyi osoitteesta: http://localhost:11434
|
||||||
|
[2m2026-04-12T03:02:32.541462Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama backend: http://localhost:11434 | malli: qwen2.5-coder:7b-instruct-q4_K_M
|
||||||
|
[2m2026-04-12T03:02:32.541603Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Tarkistetaan malli qwen2.5-coder:7b-instruct-q4_K_M...
|
||||||
|
[2m2026-04-12T03:02:33.365087Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Malli qwen2.5-coder:7b-instruct-q4_K_M valmis
|
||||||
|
[2m2026-04-12T03:02:33.365313Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Ollama valmis inferenssiin!
|
||||||
|
[2m2026-04-12T03:02:33.365349Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Käytettävä kielimalli konfiguroitu (selected_task): qwen2.5-coder:7b-instruct-q4_K_M
|
||||||
|
[2m2026-04-12T03:02:33.401538Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Yhdistetty hubiin!
|
||||||
|
[2m2026-04-12T03:06:31.660670Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Kipinä Native Node käynnistyy — hub: ws://127.0.0.1:3000/ws, varaus: 4 GB
|
||||||
|
[2m2026-04-12T03:06:31.676386Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Järjestelmä: Mac | Darwin 26.3.1 | 12 ydintä | 32768 MB RAM
|
||||||
|
[2m2026-04-12T03:06:31.690957Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m GPU 0: Apple M2 Max (Apple) [Metal] | VRAM: ?/24576 MB | ? | kuormitus: ?
|
||||||
|
[2m2026-04-12T03:06:31.690977Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Alustetaan Ollama-yhteyttä...
|
||||||
|
[2m2026-04-12T03:06:31.696155Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama löytyi osoitteesta: http://localhost:11434
|
||||||
|
[2m2026-04-12T03:06:33.075746Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama backend: http://localhost:11434 | malli: qwen2.5-coder:7b-instruct-q4_K_M
|
||||||
|
[2m2026-04-12T03:06:33.075900Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Tarkistetaan malli qwen2.5-coder:7b-instruct-q4_K_M...
|
||||||
|
[2m2026-04-12T03:06:34.006750Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Malli qwen2.5-coder:7b-instruct-q4_K_M valmis
|
||||||
|
[2m2026-04-12T03:06:34.006975Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Ollama valmis inferenssiin!
|
||||||
|
[2m2026-04-12T03:06:34.007013Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Käytettävä kielimalli konfiguroitu (selected_task): qwen2.5-coder:7b-instruct-q4_K_M
|
||||||
|
[2m2026-04-12T03:06:34.012582Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Yhdistetty hubiin!
|
||||||
|
[2m2026-04-12T03:06:42.497863Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:status-check | 1r prompti | "ping..."
|
||||||
|
[2m2026-04-12T03:06:46.162703Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 11 tok | 3664ms | 59.1 tok/s
|
||||||
|
[2m2026-04-12T03:06:49.156537Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:status-check | 1r prompti | "ping..."
|
||||||
|
[2m2026-04-12T03:06:49.655724Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 20 tok | 499ms | 57.5 tok/s
|
||||||
|
[2m2026-04-12T03:06:52.299481Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:status-check | 1r prompti | "ping..."
|
||||||
|
[2m2026-04-12T03:06:52.614573Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 11 tok | 314ms | 59.9 tok/s
|
||||||
|
[2m2026-04-12T03:07:04.444950Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:f889ae9e-ff18-4867-826a-1399ce39ea11 | 41r prompti | "Adapt the example to match the project description. Import f..."
|
||||||
|
[2m2026-04-12T03:07:10.060424Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 220 tok | 5615ms | 47.8 tok/s
|
||||||
|
[2m2026-04-12T03:07:10.065996Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:52aaf7f1-7d6a-48df-9329-702bf4438329 | 64r prompti | "Adapt the example to match the project description. Import f..."
|
||||||
|
[2m2026-04-12T03:07:13.387062Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 54 tok | 3320ms | 37.3 tok/s
|
||||||
|
[2m2026-04-12T03:07:13.394231Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:bea80c1b-84f3-4859-8372-a01947a181f5 | 122r prompti | "Adapt the example to match the project description. Import f..."
|
||||||
|
[2m2026-04-12T03:07:28.815501Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 435 tok | 15420ms | 33.8 tok/s
|
||||||
|
[2m2026-04-12T03:07:28.849290Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:b33bfb94-5ea3-4483-bea6-6eae2e87f622 | 130r prompti | "Adapt the example to match the project description. Import f..."
|
||||||
|
[2m2026-04-12T03:07:33.403851Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 73 tok | 4554ms | 36.0 tok/s
|
||||||
|
[2m2026-04-12T03:07:33.410281Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:c7271b5b-9572-4c2d-b1ca-8573153eb818 | 124r prompti | "dev = "uvicorn main:app --reload"..."
|
||||||
|
[2m2026-04-12T03:07:36.058218Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 3 tok | 2647ms | 66.7 tok/s
|
||||||
|
[2m2026-04-12T03:07:36.063363Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:3f197227-4c45-4a17-8980-0f9d523b104a | 126r prompti | "Write a complete test_main.py file with TestClient...."
|
||||||
|
[2m2026-04-12T03:08:04.081504Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 818 tok | 28017ms | 32.6 tok/s
|
||||||
|
[2m2026-04-12T03:08:04.089182Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:95ff2b0d-b2e8-483b-83a4-68d7227a4784 | 28r prompti | "Write ONLY the Dockerfile, no explanations...."
|
||||||
|
[2m2026-04-12T03:08:08.956198Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 127 tok | 4866ms | 33.3 tok/s
|
||||||
|
[2m2026-04-12T03:08:08.962241Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:70a6888c-1327-462f-81e0-238d75c80e27 | 298r prompti | "CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0"..."
|
||||||
|
[2m2026-04-12T03:08:20.512602Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 170 tok | 11550ms | 31.8 tok/s
|
||||||
|
[2m2026-04-12T03:10:23.640488Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:status-check | 1r prompti | "ping..."
|
||||||
|
[2m2026-04-12T03:10:24.016781Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 11 tok | 376ms | 59.8 tok/s
|
||||||
|
[2m2026-04-12T03:10:28.471504Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:status-check | 1r prompti | "ping..."
|
||||||
|
[2m2026-04-12T03:10:28.851913Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 11 tok | 380ms | 56.4 tok/s
|
||||||
|
[2m2026-04-12T03:10:32.080898Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:status-check | 1r prompti | "ping..."
|
||||||
|
[2m2026-04-12T03:10:32.761760Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 30 tok | 680ms | 56.2 tok/s
|
||||||
|
[2m2026-04-12T03:10:33.543595Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:status-check | 1r prompti | "ping..."
|
||||||
|
[2m2026-04-12T03:10:34.155195Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 27 tok | 611ms | 56.8 tok/s
|
||||||
|
[2m2026-04-12T03:10:37.170032Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:status-check | 1r prompti | "ping..."
|
||||||
|
[2m2026-04-12T03:10:37.490844Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 11 tok | 320ms | 59.3 tok/s
|
||||||
|
[2m2026-04-12T03:10:41.071790Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:status-check | 1r prompti | "ping..."
|
||||||
|
[2m2026-04-12T03:10:41.393921Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 11 tok | 321ms | 58.8 tok/s
|
||||||
|
[2m2026-04-12T03:10:47.007835Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:status-check | 1r prompti | "ping..."
|
||||||
|
[2m2026-04-12T03:10:47.352103Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 11 tok | 344ms | 56.9 tok/s
|
||||||
|
[2m2026-04-12T03:10:55.861324Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:status-check | 1r prompti | "ping..."
|
||||||
|
[2m2026-04-12T03:10:56.193953Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 11 tok | 332ms | 57.8 tok/s
|
||||||
|
[2m2026-04-12T03:11:09.557527Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:status-check | 1r prompti | "ping..."
|
||||||
|
[2m2026-04-12T03:11:09.890235Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 11 tok | 332ms | 58.7 tok/s
|
||||||
|
[2m2026-04-12T03:11:11.846667Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:status-check | 1r prompti | "ping..."
|
||||||
|
[2m2026-04-12T03:11:12.166974Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 11 tok | 320ms | 59.8 tok/s
|
||||||
|
[2m2026-04-12T03:11:22.243681Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:status-check | 1r prompti | "ping..."
|
||||||
|
[2m2026-04-12T03:11:22.590303Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 11 tok | 346ms | 56.8 tok/s
|
||||||
|
[2m2026-04-12T03:11:43.577820Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:0d939424-723a-4898-b182-e9959e16abb9 | 41r prompti | "Adapt the example to match the project description. Import f..."
|
||||||
|
[2m2026-04-12T03:11:50.105047Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 256 tok | 6527ms | 47.0 tok/s
|
||||||
|
[2m2026-04-12T03:11:50.147446Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:a3492a26-284c-4874-80b6-4aeafdbb5a75 | 69r prompti | "Adapt the example to match the project description. Import f..."
|
||||||
|
[2m2026-04-12T03:11:53.451037Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 55 tok | 3303ms | 39.0 tok/s
|
||||||
|
[2m2026-04-12T03:11:53.459737Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:7cc73c11-57a8-4cf2-be31-7302e81d25c7 | 127r prompti | "Adapt the example to match the project description. Import f..."
|
||||||
|
[2m2026-04-12T03:12:08.447730Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 435 tok | 14987ms | 35.1 tok/s
|
||||||
|
[2m2026-04-12T03:12:08.455128Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:a7adc096-78b1-4314-93a2-26824e2c6ab1 | 135r prompti | "Adapt the example to match the project description. Import f..."
|
||||||
|
[2m2026-04-12T03:12:13.297818Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 73 tok | 4842ms | 36.0 tok/s
|
||||||
|
[2m2026-04-12T03:12:13.308601Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:cda5ff01-e88e-4c58-bf5d-24d7a449b4b2 | 128r prompti | "dev = "uvicorn main:app --reload"..."
|
||||||
|
[2m2026-04-12T03:12:16.062246Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 3 tok | 2753ms | 70.0 tok/s
|
||||||
|
[2m2026-04-12T03:12:16.070379Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:8d04d6eb-2439-4038-8ffe-ea37eec8d673 | 128r prompti | "Write a complete test_main.py file with TestClient...."
|
||||||
|
[2m2026-04-12T03:12:40.311815Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 788 tok | 24240ms | 36.7 tok/s
|
||||||
|
[2m2026-04-12T03:12:40.348709Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:b112c319-e2ad-4293-8699-4feb4f7cee5e | 30r prompti | "Write ONLY the Dockerfile, no explanations...."
|
||||||
|
[2m2026-04-12T03:12:45.108493Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 145 tok | 4759ms | 38.6 tok/s
|
||||||
|
[2m2026-04-12T03:12:45.151914Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:80d8e1cf-d536-4ac4-9577-08fbab53d159 | 295r prompti | "EXPOSE 8000..."
|
||||||
|
[2m2026-04-12T03:12:55.265794Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 171 tok | 10113ms | 36.7 tok/s
|
||||||
|
[2m2026-04-12T03:18:26.329119Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Kipinä Native Node käynnistyy — hub: ws://127.0.0.1:3000/ws, varaus: 4 GB
|
||||||
|
[2m2026-04-12T03:18:26.350176Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Järjestelmä: Mac | Darwin 26.3.1 | 12 ydintä | 32768 MB RAM
|
||||||
|
[2m2026-04-12T03:18:26.367757Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m GPU 0: Apple M2 Max (Apple) [Metal] | VRAM: ?/24576 MB | ? | kuormitus: ?
|
||||||
|
[2m2026-04-12T03:18:26.367784Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Alustetaan Ollama-yhteyttä...
|
||||||
|
[2m2026-04-12T03:18:26.374430Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama löytyi osoitteesta: http://localhost:11434
|
||||||
|
[2m2026-04-12T03:18:29.330257Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama backend: http://localhost:11434 | malli: qwen2.5-coder:7b-instruct-q4_K_M
|
||||||
|
[2m2026-04-12T03:18:29.330419Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Tarkistetaan malli qwen2.5-coder:7b-instruct-q4_K_M...
|
||||||
|
[2m2026-04-12T03:18:30.297228Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Malli qwen2.5-coder:7b-instruct-q4_K_M valmis
|
||||||
|
[2m2026-04-12T03:18:30.297413Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Ollama valmis inferenssiin!
|
||||||
|
[2m2026-04-12T03:18:30.297502Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Käytettävä kielimalli konfiguroitu (selected_task): qwen2.5-coder:7b-instruct-q4_K_M
|
||||||
|
[2m2026-04-12T03:18:30.304013Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Yhdistetty hubiin!
|
||||||
|
[2m2026-04-12T03:19:02.300905Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:status-check | 1r prompti | "ping..."
|
||||||
|
[2m2026-04-12T03:19:03.443612Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 11 tok | 1142ms | 58.2 tok/s
|
||||||
|
[2m2026-04-12T03:19:11.592789Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:3625298a-8cf3-4cfc-b24f-96eb7d8487c3 | 41r prompti | "Adapt the example to match the project description. Import f..."
|
||||||
|
[2m2026-04-12T03:19:17.955448Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 253 tok | 6362ms | 47.9 tok/s
|
||||||
|
[2m2026-04-12T03:19:17.965059Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:d8d651b2-2d1b-4652-91c7-0e3d99f12ead | 69r prompti | "Adapt the example to match the project description. Import f..."
|
||||||
|
[2m2026-04-12T03:19:21.159047Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 54 tok | 3193ms | 40.3 tok/s
|
||||||
|
[2m2026-04-12T03:19:21.167536Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:fa5f4645-a440-4624-83a6-fb24799d0edc | 127r prompti | "Adapt the example to match the project description. Import f..."
|
||||||
|
[2m2026-04-12T03:19:35.681742Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 435 tok | 14513ms | 36.2 tok/s
|
||||||
|
[2m2026-04-12T03:19:35.691591Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:24684ea4-2b1b-41c2-b2b0-068fb4e38483 | 136r prompti | "Adapt the example to match the project description. Import f..."
|
||||||
|
[2m2026-04-12T03:19:40.236213Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 76 tok | 4544ms | 37.9 tok/s
|
||||||
|
[2m2026-04-12T03:19:40.244741Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:d4e407e9-a0fd-4b25-b355-317e16851854 | 129r prompti | "dev = "uv run uvicorn main:app --reload"..."
|
||||||
|
[2m2026-04-12T03:19:43.089356Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 3 tok | 2844ms | 67.8 tok/s
|
||||||
|
[2m2026-04-12T03:19:43.099954Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:7fa43b8d-8ed1-4f44-be45-bc99619ae74f | 129r prompti | "Write a complete test_main.py file with TestClient...."
|
||||||
|
[2m2026-04-12T03:20:08.686318Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 773 tok | 25585ms | 34.0 tok/s
|
||||||
|
[2m2026-04-12T03:20:08.696389Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:98a7c691-29f8-4385-9fe6-389d0edfb404 | 30r prompti | "Write ONLY the Dockerfile, no explanations...."
|
||||||
|
[2m2026-04-12T03:20:12.560366Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 100 tok | 3863ms | 36.6 tok/s
|
||||||
|
[2m2026-04-12T03:20:12.569728Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m → task_id:b95245cb-6c5a-4a8e-b903-40f7471d2e72 | 289r prompti | "CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0"..."
|
||||||
|
[2m2026-04-12T03:20:22.658899Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m ✓ qwen2.5-coder:7b-instruct-q4_K_M | 174 tok | 10089ms | 36.3 tok/s
|
||||||
|
[2m2026-04-12T03:21:37.867699Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Kipinä Native Node käynnistyy — hub: ws://127.0.0.1:3000/ws, varaus: 4 GB
|
||||||
|
[2m2026-04-12T03:21:37.879381Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Järjestelmä: Mac | Darwin 26.3.1 | 12 ydintä | 32768 MB RAM
|
||||||
|
[2m2026-04-12T03:21:37.899474Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m GPU 0: Apple M2 Max (Apple) [Metal] | VRAM: ?/24576 MB | ? | kuormitus: ?
|
||||||
|
[2m2026-04-12T03:21:37.899494Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Alustetaan Ollama-yhteyttä...
|
||||||
|
[2m2026-04-12T03:21:37.903580Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama löytyi osoitteesta: http://localhost:11434
|
||||||
|
[2m2026-04-12T03:21:48.551879Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Ollama backend: http://localhost:11434 | malli: qwen2.5-coder:7b-instruct-q4_K_M
|
||||||
|
[2m2026-04-12T03:21:48.552045Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Tarkistetaan malli qwen2.5-coder:7b-instruct-q4_K_M...
|
||||||
|
[2m2026-04-12T03:21:49.569609Z[0m [32m INFO[0m [2mnative_node::inference[0m[2m:[0m Malli qwen2.5-coder:7b-instruct-q4_K_M valmis
|
||||||
|
[2m2026-04-12T03:21:49.569839Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Ollama valmis inferenssiin!
|
||||||
|
[2m2026-04-12T03:21:49.569961Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Käytettävä kielimalli konfiguroitu (selected_task): qwen2.5-coder:7b-instruct-q4_K_M
|
||||||
|
[2m2026-04-12T03:21:49.577531Z[0m [32m INFO[0m [2mnative_node[0m[2m:[0m Yhdistetty hubiin!
|
||||||
@@ -19,3 +19,8 @@ wgpu = { version = "24", optional = true }
|
|||||||
reqwest = { version = "0.12", features = ["json"] }
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
dialoguer = "0.12.0"
|
||||||
|
console = "0.16.3"
|
||||||
|
ratatui = "0.29.0"
|
||||||
|
crossterm = { version = "0.28.1", features = ["event-stream"] }
|
||||||
|
tracing-appender = "0.2.4"
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ pub struct LlmEngine {
|
|||||||
|
|
||||||
impl LlmEngine {
|
impl LlmEngine {
|
||||||
pub async fn load() -> Result<Self, String> {
|
pub async fn load() -> Result<Self, String> {
|
||||||
let model = std::env::var("OLLAMA_MODEL").unwrap_or_else(|_| "qwen2.5-coder:3b".to_string());
|
|
||||||
|
|
||||||
let client = reqwest::Client::builder()
|
let client = reqwest::Client::builder()
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
.timeout(std::time::Duration::from_secs(600))
|
||||||
.connect_timeout(std::time::Duration::from_secs(3))
|
.connect_timeout(std::time::Duration::from_secs(3))
|
||||||
@@ -48,6 +46,12 @@ impl LlmEngine {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Kysytään malli TUI:lla jos ei pakotettu ympäristöstä
|
||||||
|
let model = match std::env::var("OLLAMA_MODEL") {
|
||||||
|
Ok(m) if !m.is_empty() => m,
|
||||||
|
_ => crate::tui::select_model(&ollama_url, &client).await?
|
||||||
|
};
|
||||||
|
|
||||||
tracing::info!("Ollama backend: {} | malli: {}", ollama_url, model);
|
tracing::info!("Ollama backend: {} | malli: {}", ollama_url, model);
|
||||||
Ok(LlmEngine { ollama_url, model: RefCell::new(model), client })
|
Ok(LlmEngine { ollama_url, model: RefCell::new(model), client })
|
||||||
}
|
}
|
||||||
@@ -78,6 +82,20 @@ impl LlmEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Hakee kaikki Ollamaan asennetut mallit
|
||||||
|
pub async fn fetch_models(&self) -> Result<serde_json::Value, String> {
|
||||||
|
let resp = self.client.get(format!("{}/api/tags", self.ollama_url))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Ollama tags fetch: {}", e))?;
|
||||||
|
|
||||||
|
if resp.status().is_success() {
|
||||||
|
resp.json().await.map_err(|e| format!("Ollama tags json: {}", e))
|
||||||
|
} else {
|
||||||
|
Err(format!("Ollama tags epäonnistui: {}", resp.status()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn generate(&self, prompt: &str, max_tokens: usize) -> Result<GenerateResult, String> {
|
pub async fn generate(&self, prompt: &str, max_tokens: usize) -> Result<GenerateResult, String> {
|
||||||
// System prompt tulee agentin konfiguraatiosta (frontend lähettää sen osana promptia).
|
// System prompt tulee agentin konfiguraatiosta (frontend lähettää sen osana promptia).
|
||||||
// Tässä ei yliajeta sitä — Ollama saa vain prompt-kentän.
|
// Tässä ei yliajeta sitä — Ollama saa vain prompt-kentän.
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ use tokio_tungstenite::connect_async;
|
|||||||
use tokio_tungstenite::tungstenite::Message;
|
use tokio_tungstenite::tungstenite::Message;
|
||||||
|
|
||||||
mod inference;
|
mod inference;
|
||||||
|
mod tui;
|
||||||
|
mod tui_dashboard;
|
||||||
|
|
||||||
/// GPU-tietorakenne — yhtenäinen kaikille valmistajille
|
/// GPU-tietorakenne — yhtenäinen kaikille valmistajille
|
||||||
struct GpuInfo {
|
struct GpuInfo {
|
||||||
@@ -222,7 +224,7 @@ fn collect_system_info() -> serde_json::Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Koko auth-viesti hubille
|
/// Koko auth-viesti hubille
|
||||||
fn build_auth_message(allocated_gb: u32) -> String {
|
fn build_auth_message(allocated_gb: u32, model_name: &str, models_data: Option<serde_json::Value>) -> String {
|
||||||
let sys = collect_system_info();
|
let sys = collect_system_info();
|
||||||
let gpus = collect_all_gpus();
|
let gpus = collect_all_gpus();
|
||||||
|
|
||||||
@@ -239,7 +241,7 @@ fn build_auth_message(allocated_gb: u32) -> String {
|
|||||||
"status": "agent_ready",
|
"status": "agent_ready",
|
||||||
"node_type": "native",
|
"node_type": "native",
|
||||||
"allocated_gb": allocated_gb,
|
"allocated_gb": allocated_gb,
|
||||||
"selected_task": "qwen2.5-coder:7b",
|
"selected_task": model_name,
|
||||||
"system": sys,
|
"system": sys,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -251,6 +253,10 @@ fn build_auth_message(allocated_gb: u32) -> String {
|
|||||||
msg.as_object_mut().unwrap().insert("gpus".to_string(), json!(gpu_json));
|
msg.as_object_mut().unwrap().insert("gpus".to_string(), json!(gpu_json));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(models) = models_data {
|
||||||
|
msg.as_object_mut().unwrap().insert("models".to_string(), models);
|
||||||
|
}
|
||||||
|
|
||||||
msg.to_string()
|
msg.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,10 +269,24 @@ fn format_optional<T: std::fmt::Display>(val: Option<T>, suffix: &str) -> String
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
let file_appender = tracing_appender::rolling::never(".", "native-node.log");
|
||||||
|
let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
|
||||||
|
|
||||||
tracing_subscriber::fmt()
|
tracing_subscriber::fmt()
|
||||||
.with_env_filter("native_node=debug")
|
.with_env_filter("native_node=debug")
|
||||||
|
.with_writer(non_blocking)
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
|
// Hookataan paniikkitilanteet palauttamaan terminaalin raw-moodista
|
||||||
|
let original_hook = std::panic::take_hook();
|
||||||
|
std::panic::set_hook(Box::new(move |panic_info| {
|
||||||
|
tui_dashboard::restore_terminal();
|
||||||
|
original_hook(panic_info);
|
||||||
|
}));
|
||||||
|
|
||||||
|
let tui_state = std::sync::Arc::new(tokio::sync::RwLock::new(tui_dashboard::DashboardState::new()));
|
||||||
|
let (cmd_tx, mut cmd_rx) = tokio::sync::mpsc::unbounded_channel::<String>();
|
||||||
|
|
||||||
let hub_url = std::env::var("HUB_URL").unwrap_or_else(|_| "ws://hub:3000/ws".to_string());
|
let hub_url = std::env::var("HUB_URL").unwrap_or_else(|_| "ws://hub:3000/ws".to_string());
|
||||||
let allocated_gb: u32 = std::env::var("ALLOCATED_GB")
|
let allocated_gb: u32 = std::env::var("ALLOCATED_GB")
|
||||||
.ok()
|
.ok()
|
||||||
@@ -283,6 +303,18 @@ async fn main() {
|
|||||||
sys["ram_total_mb"]
|
sys["ram_total_mb"]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut st = tui_state.write().await;
|
||||||
|
st.sys_info = format!("{} | {} | {} ydintä | {} MB RAM",
|
||||||
|
sys["hostname"].as_str().unwrap_or("?"),
|
||||||
|
sys["os"].as_str().unwrap_or("?"),
|
||||||
|
sys["cpu_cores"],
|
||||||
|
sys["ram_total_mb"]
|
||||||
|
);
|
||||||
|
let i = st.sys_info.clone();
|
||||||
|
st.push_log("System", format!("Järjestelmä: {}", i), None);
|
||||||
|
}
|
||||||
|
|
||||||
let gpus = collect_all_gpus();
|
let gpus = collect_all_gpus();
|
||||||
if gpus.is_empty() {
|
if gpus.is_empty() {
|
||||||
#[cfg(not(feature = "gpu-detect"))]
|
#[cfg(not(feature = "gpu-detect"))]
|
||||||
@@ -321,6 +353,36 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let active_model = llm.as_ref().map(|e| e.model_name()).unwrap_or_else(|| "unknown".to_string());
|
||||||
|
tracing::info!("Käytettävä kielimalli konfiguroitu (selected_task): {}", active_model);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut st = tui_state.write().await;
|
||||||
|
st.model_name = active_model.clone();
|
||||||
|
st.push_log("System", format!("Malli valmis: {}", active_model), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Käynnistetään graafinen TUI vasta kun TUI:n Prompt (LlmEngine::load) on ohitettu!
|
||||||
|
let ui_state = tui_state.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Err(e) = tui_dashboard::run_dashboard(ui_state, cmd_tx).await {
|
||||||
|
tracing::error!("Pääluupin TUI kaatui: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Haetaan paikalliset mallit hubille lähetettäväksi
|
||||||
|
let mut available_models = None;
|
||||||
|
if let Some(ref engine) = llm {
|
||||||
|
match engine.fetch_models().await {
|
||||||
|
Ok(models) => {
|
||||||
|
available_models = Some(models);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("Mallilistauksen haku epäonnistui: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Yhdistetään hubiin
|
// Yhdistetään hubiin
|
||||||
loop {
|
loop {
|
||||||
match connect_async(&hub_url).await {
|
match connect_async(&hub_url).await {
|
||||||
@@ -328,14 +390,79 @@ async fn main() {
|
|||||||
tracing::info!("Yhdistetty hubiin!");
|
tracing::info!("Yhdistetty hubiin!");
|
||||||
let (mut write, mut read) = ws_stream.split();
|
let (mut write, mut read) = ws_stream.split();
|
||||||
|
|
||||||
let auth = build_auth_message(allocated_gb);
|
let auth = build_auth_message(allocated_gb, &active_model, available_models.clone());
|
||||||
if write.send(Message::Text(auth)).await.is_err() {
|
if write.send(Message::Text(auth)).await.is_err() {
|
||||||
tracing::error!("Auth-viestin lähetys epäonnistui");
|
tracing::error!("Auth-viestin lähetys epäonnistui");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Some(Ok(msg)) = read.next().await {
|
loop {
|
||||||
if let Message::Text(text) = msg {
|
tokio::select! {
|
||||||
|
cmd = cmd_rx.recv() => {
|
||||||
|
if let Some(cmd_str) = cmd {
|
||||||
|
if cmd_str == "pause" {
|
||||||
|
tracing::info!("Tauotetaan solmun suoritus (Hub ei lähetä tehtäviä)...");
|
||||||
|
let req = json!({"type": "status_update", "status": "paused"});
|
||||||
|
let _ = write.send(Message::Text(req.to_string())).await;
|
||||||
|
{
|
||||||
|
let mut st = tui_state.write().await;
|
||||||
|
st.status = "PAUSED".to_string();
|
||||||
|
st.push_log("Network", "Solmu siirretty taukotilaan".to_string(), None);
|
||||||
|
}
|
||||||
|
} else if cmd_str == "resume" {
|
||||||
|
tracing::info!("Jatketaan solmun suoritusta...");
|
||||||
|
let req = json!({"type": "status_update", "status": "active"});
|
||||||
|
let _ = write.send(Message::Text(req.to_string())).await;
|
||||||
|
{
|
||||||
|
let mut st = tui_state.write().await;
|
||||||
|
st.status = "ACTIVE".to_string();
|
||||||
|
st.push_log("System", "Suoritus jatkuu...".to_string(), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ws_msg = read.next() => {
|
||||||
|
match ws_msg {
|
||||||
|
Some(Ok(Message::Text(text))) => {
|
||||||
|
// Hubin control-viestit
|
||||||
|
if text.contains(r#""type":"control""#) {
|
||||||
|
if let Ok(task) = serde_json::from_str::<serde_json::Value>(&text) {
|
||||||
|
if let Some(action) = task.get("action").and_then(|v| v.as_str()) {
|
||||||
|
if action == "pause" {
|
||||||
|
tracing::info!("Hub pakotti solmun tauolle (Pause)");
|
||||||
|
let req = json!({"type": "status_update", "status": "paused"});
|
||||||
|
let _ = write.send(Message::Text(req.to_string())).await;
|
||||||
|
{
|
||||||
|
let mut st = tui_state.write().await;
|
||||||
|
st.status = "PAUSED".to_string();
|
||||||
|
st.push_log("Network", "Hub kytki solmun tauolle".to_string(), None);
|
||||||
|
}
|
||||||
|
} else if action == "resume" {
|
||||||
|
tracing::info!("Hub aktivoi solmun suorituksen (Resume)");
|
||||||
|
let req = json!({"type": "status_update", "status": "active"});
|
||||||
|
let _ = write.send(Message::Text(req.to_string())).await;
|
||||||
|
{
|
||||||
|
let mut st = tui_state.write().await;
|
||||||
|
st.status = "ACTIVE".to_string();
|
||||||
|
st.push_log("Network", "Hub palautti solmun töihin".to_string(), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Verkon globaali tila
|
||||||
|
if text.contains(r#""type":"network_status""#) {
|
||||||
|
if let Ok(status) = serde_json::from_str::<serde_json::Value>(&text) {
|
||||||
|
if let Some(nodes) = status.get("active_nodes").and_then(|v| v.as_u64()) {
|
||||||
|
if let Some(tasks) = status.get("tasks").and_then(|v| v.as_u64()) {
|
||||||
|
let mut st = tui_state.write().await;
|
||||||
|
st.network_active_nodes = nodes as usize;
|
||||||
|
st.network_total_tasks = tasks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// LLM-promptit
|
// LLM-promptit
|
||||||
if text.contains("llm_prompt") {
|
if text.contains("llm_prompt") {
|
||||||
if let Ok(task) = serde_json::from_str::<serde_json::Value>(&text) {
|
if let Ok(task) = serde_json::from_str::<serde_json::Value>(&text) {
|
||||||
@@ -343,26 +470,41 @@ async fn main() {
|
|||||||
let task_id = task.get("task_id").and_then(|v| v.as_str()).unwrap_or("?");
|
let task_id = task.get("task_id").and_then(|v| v.as_str()).unwrap_or("?");
|
||||||
let msg_model = task.get("model").and_then(|v| v.as_str()).unwrap_or("");
|
let msg_model = task.get("model").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
|
|
||||||
if !prompt.is_empty() && (msg_model.starts_with("qwen-coder") || msg_model.starts_with("qwen2.5-coder")) {
|
if !prompt.is_empty() && (msg_model.starts_with("qwen-coder") || msg_model.starts_with("qwen2.5-coder") || msg_model.starts_with("phi")) {
|
||||||
|
|
||||||
if let Some(ref engine) = llm {
|
if let Some(ref engine) = llm {
|
||||||
let max_tokens = task.get("max_tokens").and_then(|v| v.as_u64()).unwrap_or(1024) as usize;
|
let max_tokens = task.get("max_tokens").and_then(|v| v.as_u64()).unwrap_or(1024) as usize;
|
||||||
let prompt_lines = prompt.lines().count();
|
let prompt_lines = prompt.lines().count();
|
||||||
let prompt_last: String = prompt.lines().last().unwrap_or("").chars().take(60).collect();
|
let prompt_last: String = prompt.lines().last().unwrap_or("").chars().take(60).collect();
|
||||||
tracing::info!("→ task_id:{} | {}r prompti | \"{}...\"", task_id, prompt_lines, prompt_last);
|
tracing::info!("→ task_id:{} | {}r prompti | \"{}...\"", task_id, prompt_lines, prompt_last);
|
||||||
|
{
|
||||||
|
let mut st = tui_state.write().await;
|
||||||
|
st.cur_task_id = Some(task_id.to_string());
|
||||||
|
st.cur_prompt = Some(format!("→ {} riviä | \"{}...\"", prompt_lines, prompt_last));
|
||||||
|
// Ei login puskemista vielä tässä! Yhdistetään se valmiin lohkoon yhdelle riville.
|
||||||
|
}
|
||||||
|
|
||||||
let model_name = engine.model_name();
|
let model_name = engine.model_name();
|
||||||
match engine.generate(prompt, max_tokens).await {
|
match engine.generate(prompt, max_tokens).await {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
|
let tokens_sec = (result.tokens_per_sec * 10.0).round() / 10.0;
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"✓ {} | {} tok | {:.0}ms | {:.1} tok/s",
|
"✓ {} | {} tok | {:.0}ms | {:.1} tok/s",
|
||||||
model_name,
|
model_name,
|
||||||
result.tokens_generated,
|
result.tokens_generated,
|
||||||
result.duration_ms,
|
result.duration_ms,
|
||||||
result.tokens_per_sec,
|
tokens_sec,
|
||||||
);
|
);
|
||||||
|
{
|
||||||
|
let mut st = tui_state.write().await;
|
||||||
|
st.tasks_completed += 1;
|
||||||
|
st.last_tokens_sec = tokens_sec as f64;
|
||||||
|
st.cur_task_id = None;
|
||||||
|
st.cur_prompt = None;
|
||||||
|
|
||||||
// Lähetetään vain lyhyt prompti-esikatselu (ei koko kontekstia)
|
let msg_type = if task_id == "status-check" { "Ping" } else { "Task" };
|
||||||
|
let msg_text = format!("{} ({} tok)", task_id, result.tokens_generated);
|
||||||
|
st.push_log(msg_type, msg_text, Some(tokens_sec as f64));
|
||||||
|
}
|
||||||
let prompt_short: String = prompt.lines().last().unwrap_or("").chars().take(100).collect();
|
let prompt_short: String = prompt.lines().last().unwrap_or("").chars().take(100).collect();
|
||||||
let done = json!({
|
let done = json!({
|
||||||
"type": "llm_done",
|
"type": "llm_done",
|
||||||
@@ -371,7 +513,7 @@ async fn main() {
|
|||||||
"response": result.text,
|
"response": result.text,
|
||||||
"tokens_generated": result.tokens_generated,
|
"tokens_generated": result.tokens_generated,
|
||||||
"duration_ms": result.duration_ms,
|
"duration_ms": result.duration_ms,
|
||||||
"tokens_per_sec": (result.tokens_per_sec * 10.0).round() / 10.0,
|
"tokens_per_sec": tokens_sec,
|
||||||
"load_time_ms": 0,
|
"load_time_ms": 0,
|
||||||
"task_id": task_id,
|
"task_id": task_id,
|
||||||
});
|
});
|
||||||
@@ -379,12 +521,19 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("Inferenssivirhe: {}", e);
|
tracing::error!("Inferenssivirhe: {}", e);
|
||||||
|
{
|
||||||
|
let mut st = tui_state.write().await;
|
||||||
|
st.cur_task_id = None;
|
||||||
|
st.cur_prompt = None;
|
||||||
|
st.push_log("System", format!("Virhe inferenssissä: {}", e), None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Mallin vaihto lennossa
|
// Mallin vaihto lennossa
|
||||||
if text.contains("change_model") {
|
if text.contains("change_model") {
|
||||||
if let Ok(task) = serde_json::from_str::<serde_json::Value>(&text) {
|
if let Ok(task) = serde_json::from_str::<serde_json::Value>(&text) {
|
||||||
@@ -393,7 +542,12 @@ async fn main() {
|
|||||||
tracing::info!("Vaihdetaan malli: {}", new_model);
|
tracing::info!("Vaihdetaan malli: {}", new_model);
|
||||||
engine.set_model(new_model.to_string());
|
engine.set_model(new_model.to_string());
|
||||||
match engine.ensure_model().await {
|
match engine.ensure_model().await {
|
||||||
Ok(()) => tracing::info!("Malli {} valmis!", new_model),
|
Ok(()) => {
|
||||||
|
tracing::info!("Malli {} valmis!", new_model);
|
||||||
|
let mut st = tui_state.write().await;
|
||||||
|
st.model_name = new_model.to_string();
|
||||||
|
st.push_log("System", format!("Malli {} ladattu & valmis!", new_model), None);
|
||||||
|
}
|
||||||
Err(e) => tracing::error!("Mallin lataus epäonnistui: {}", e),
|
Err(e) => tracing::error!("Mallin lataus epäonnistui: {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -401,7 +555,13 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some(Ok(_)) => {} // Muut viestityypit (binary/ping)
|
||||||
|
Some(Err(_)) | None => break, // Yhteys poikki
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tracing::warn!("Yhteys hubiin katkesi — yritetään uudelleen 5s...");
|
tracing::warn!("Yhteys hubiin katkesi — yritetään uudelleen 5s...");
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
67
network-poc/native-node/src/tui.rs
Normal file
67
network-poc/native-node/src/tui.rs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
use dialoguer::{Select, Input, theme::ColorfulTheme};
|
||||||
|
use reqwest::Client;
|
||||||
|
|
||||||
|
pub async fn select_model(ollama_url: &str, client: &Client) -> Result<String, String> {
|
||||||
|
// 1. Hae tagit
|
||||||
|
let mut models = vec![];
|
||||||
|
println!(" Haetaan asennettuja malleja osoitteesta {}...", ollama_url);
|
||||||
|
if let Ok(resp) = client.get(&format!("{}/api/tags", ollama_url)).send().await {
|
||||||
|
if resp.status().is_success() {
|
||||||
|
if let Ok(json) = resp.json::<serde_json::Value>().await {
|
||||||
|
if let Some(arr) = json.get("models").and_then(|v| v.as_array()) {
|
||||||
|
for m in arr {
|
||||||
|
if let Some(name) = m.get("name").and_then(|v| v.as_str()) {
|
||||||
|
models.push(name.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let download_opt = "[➕ Lataa uusi malli internetistä]";
|
||||||
|
let mut options = vec![download_opt.to_string()];
|
||||||
|
options.extend(models);
|
||||||
|
|
||||||
|
// 2. Kysy käyttäjältä Selectillä
|
||||||
|
let theme = ColorfulTheme::default();
|
||||||
|
let selection = Select::with_theme(&theme)
|
||||||
|
.with_prompt("Valitse Ollama-malli Kipinä-verkkoa varten:")
|
||||||
|
.default(if options.len() > 1 { 1 } else { 0 })
|
||||||
|
.items(&options)
|
||||||
|
.interact()
|
||||||
|
.map_err(|e| format!("TUI virhe: {}", e))?;
|
||||||
|
|
||||||
|
let selected = &options[selection];
|
||||||
|
|
||||||
|
// 3. Jos käyttäjä haluaa uuden, kysy nimeä
|
||||||
|
if selected == download_opt {
|
||||||
|
let new_model: String = Input::with_theme(&theme)
|
||||||
|
.with_prompt("Syötä ladattavan mallin nimi (esim. llama3 tai qwen2.5-coder:3b)")
|
||||||
|
.interact_text()
|
||||||
|
.map_err(|e| format!("TUI virhe: {}", e))?;
|
||||||
|
|
||||||
|
let new_model = new_model.trim().to_string();
|
||||||
|
if new_model.is_empty() {
|
||||||
|
return Err("Mallin nimi ei voi olla tyhjä".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(" Ladataan malleja taustalla... Tämä voi kestää hetken ({})", new_model);
|
||||||
|
// Odotetaan että pull on valmis
|
||||||
|
let pull_body = serde_json::json!({ "name": &new_model });
|
||||||
|
let resp = client.post(&format!("{}/api/pull", ollama_url))
|
||||||
|
.json(&pull_body)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Pull req virhe: {}", e))?;
|
||||||
|
|
||||||
|
if resp.status().is_success() {
|
||||||
|
println!(" ✓ Malli {} ladattu onnistuneesti!", new_model);
|
||||||
|
return Ok(new_model);
|
||||||
|
} else {
|
||||||
|
return Err(format!("Ollama pull epäonnistui: {}", resp.status()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(selected.clone())
|
||||||
|
}
|
||||||
223
network-poc/native-node/src/tui_dashboard.rs
Normal file
223
network-poc/native-node/src/tui_dashboard.rs
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
use crossterm::{
|
||||||
|
event::{self, Event, EventStream, KeyCode},
|
||||||
|
execute,
|
||||||
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
|
};
|
||||||
|
use ratatui::{
|
||||||
|
backend::CrosstermBackend,
|
||||||
|
layout::{Constraint, Direction, Layout, Alignment},
|
||||||
|
style::{Color, Modifier, Style},
|
||||||
|
widgets::{Block, Borders, Paragraph, Wrap},
|
||||||
|
Terminal,
|
||||||
|
};
|
||||||
|
use std::io;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use futures_util::StreamExt;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LogEntry {
|
||||||
|
pub ty: String,
|
||||||
|
pub msg: String,
|
||||||
|
pub speed: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DashboardState {
|
||||||
|
pub logs: Vec<LogEntry>,
|
||||||
|
pub status: String,
|
||||||
|
pub node_id: Option<u64>,
|
||||||
|
pub sys_info: String,
|
||||||
|
pub model_name: String,
|
||||||
|
pub cur_task_id: Option<String>,
|
||||||
|
pub cur_prompt: Option<String>,
|
||||||
|
pub tasks_completed: u32,
|
||||||
|
pub last_tokens_sec: f64,
|
||||||
|
pub network_active_nodes: usize,
|
||||||
|
pub network_total_tasks: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DashboardState {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
logs: Vec::new(),
|
||||||
|
status: "ACTIVE".to_string(),
|
||||||
|
node_id: None,
|
||||||
|
sys_info: "".to_string(),
|
||||||
|
model_name: "Yhdistetään...".to_string(),
|
||||||
|
cur_task_id: None,
|
||||||
|
cur_prompt: None,
|
||||||
|
tasks_completed: 0,
|
||||||
|
last_tokens_sec: 0.0,
|
||||||
|
network_active_nodes: 1, // oletetaan itsemme
|
||||||
|
network_total_tasks: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_log(&mut self, ty: &str, msg: String, speed: Option<f64>) {
|
||||||
|
self.logs.push(LogEntry {
|
||||||
|
ty: ty.to_string(),
|
||||||
|
msg,
|
||||||
|
speed,
|
||||||
|
});
|
||||||
|
if self.logs.len() > 100 {
|
||||||
|
self.logs.remove(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_dashboard(
|
||||||
|
state: Arc<RwLock<DashboardState>>,
|
||||||
|
cmd_tx: tokio::sync::mpsc::UnboundedSender<String>,
|
||||||
|
) -> Result<(), io::Error> {
|
||||||
|
enable_raw_mode()?;
|
||||||
|
let mut stdout = io::stdout();
|
||||||
|
execute!(stdout, EnterAlternateScreen)?;
|
||||||
|
let backend = CrosstermBackend::new(stdout);
|
||||||
|
let mut terminal = Terminal::new(backend)?;
|
||||||
|
terminal.clear()?;
|
||||||
|
|
||||||
|
let mut reader = EventStream::new();
|
||||||
|
let mut interval = tokio::time::interval(Duration::from_millis(100));
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
_ = interval.tick() => {
|
||||||
|
let st = state.read().await;
|
||||||
|
terminal.draw(|f| ui(f, &st))?;
|
||||||
|
}
|
||||||
|
ev = reader.next() => {
|
||||||
|
if let Some(Ok(Event::Key(key))) = ev {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Char('q') | KeyCode::Esc => {
|
||||||
|
// Palautetaan näyttö ja suljetaan ohjelma
|
||||||
|
disable_raw_mode()?;
|
||||||
|
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
KeyCode::Char('p') | KeyCode::Char('P') => {
|
||||||
|
let _ = cmd_tx.send("pause".to_string());
|
||||||
|
}
|
||||||
|
KeyCode::Char('r') | KeyCode::Char('R') | KeyCode::Char('s') => {
|
||||||
|
let _ = cmd_tx.send("resume".to_string());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn restore_terminal() {
|
||||||
|
let _ = disable_raw_mode();
|
||||||
|
let _ = execute!(io::stdout(), LeaveAlternateScreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui(f: &mut ratatui::Frame, st: &DashboardState) {
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Length(3), // Header
|
||||||
|
Constraint::Min(0), // Body
|
||||||
|
Constraint::Length(3), // Footer / Status
|
||||||
|
].as_ref())
|
||||||
|
.split(f.area());
|
||||||
|
|
||||||
|
// --- Header ---
|
||||||
|
let header_text = match st.node_id {
|
||||||
|
Some(id) => format!(" Kipinä Agentic Node #{} ", id),
|
||||||
|
None => " Kipinä Agentic Node (Yhdistää...) ".to_string(),
|
||||||
|
};
|
||||||
|
let header = Paragraph::new(header_text)
|
||||||
|
.style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD))
|
||||||
|
.alignment(Alignment::Center)
|
||||||
|
.block(Block::default().borders(Borders::ALL).style(Style::default().fg(Color::DarkGray)));
|
||||||
|
f.render_widget(header, chunks[0]);
|
||||||
|
|
||||||
|
// --- Body ---
|
||||||
|
let body_chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Length(7), // Yläosan info ja tehtävä
|
||||||
|
Constraint::Min(0), // Lokit / Chat alas
|
||||||
|
].as_ref())
|
||||||
|
.split(chunks[1]);
|
||||||
|
|
||||||
|
let top_panels = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Percentage(40), // Vasen paneeli (Info)
|
||||||
|
Constraint::Percentage(60), // Oikea paneeli (Tehtävä)
|
||||||
|
].as_ref())
|
||||||
|
.split(body_chunks[0]);
|
||||||
|
|
||||||
|
// Vasen paneeli: Laitteisto, Malli & Verkosto
|
||||||
|
let info_text = format!(
|
||||||
|
"🚀 Malli: {}\n💻 Järjestelmä: {}\n📊 Tehdyt: {} | Nopeus: {} t/s\n🌐 Verkosto: {} solmua | {} tehtävää",
|
||||||
|
st.model_name, st.sys_info, st.tasks_completed, st.last_tokens_sec, st.network_active_nodes, st.network_total_tasks
|
||||||
|
);
|
||||||
|
let left_panel = Paragraph::new(info_text)
|
||||||
|
.block(Block::default().title(" Laitteisto ja AI ").borders(Borders::ALL))
|
||||||
|
.style(Style::default().fg(Color::White))
|
||||||
|
.wrap(Wrap { trim: true });
|
||||||
|
f.render_widget(left_panel, top_panels[0]);
|
||||||
|
|
||||||
|
// Oikea paneeli: Käynnissä oleva tehtävä
|
||||||
|
let task_title = match &st.cur_task_id {
|
||||||
|
Some(id) => format!(" Työn alla: {} ", id),
|
||||||
|
None => " Vapaana ".to_string(),
|
||||||
|
};
|
||||||
|
let task_content = st.cur_prompt.clone().unwrap_or_else(|| "Odotetaan tehtäviä Hubilta...".to_string());
|
||||||
|
|
||||||
|
let task_style = if st.cur_task_id.is_some() {
|
||||||
|
Style::default().fg(Color::Magenta)
|
||||||
|
} else {
|
||||||
|
Style::default().fg(Color::DarkGray)
|
||||||
|
};
|
||||||
|
|
||||||
|
let task_panel = Paragraph::new(task_content)
|
||||||
|
.wrap(Wrap { trim: true })
|
||||||
|
.block(Block::default().title(task_title).borders(Borders::ALL).style(task_style));
|
||||||
|
f.render_widget(task_panel, top_panels[1]);
|
||||||
|
|
||||||
|
// Alaosan paneeli: Tapahtumaloki koko leveydeltä
|
||||||
|
let area_height = body_chunks[1].height.saturating_sub(2) as usize;
|
||||||
|
let skip_count = if st.logs.len() > area_height { st.logs.len() - area_height } else { 0 };
|
||||||
|
|
||||||
|
let visible_logs: Vec<ratatui::text::Line> = st.logs.iter().skip(skip_count).map(|log| {
|
||||||
|
let ty_color = match log.ty.as_str() {
|
||||||
|
"System" => Color::Yellow,
|
||||||
|
"Network" => Color::Blue,
|
||||||
|
"Task" => Color::Magenta,
|
||||||
|
"Ping" => Color::DarkGray,
|
||||||
|
_ => Color::White,
|
||||||
|
};
|
||||||
|
|
||||||
|
let speed_str = if let Some(s) = log.speed {
|
||||||
|
format!(" | {:.1} tok/s", s)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
ratatui::text::Line::from(vec![
|
||||||
|
ratatui::text::Span::styled(format!("{: <8}", log.ty), Style::default().fg(ty_color).add_modifier(Modifier::BOLD)),
|
||||||
|
ratatui::text::Span::raw(" | "),
|
||||||
|
ratatui::text::Span::styled(log.msg.clone(), Style::default().fg(Color::White)),
|
||||||
|
ratatui::text::Span::styled(speed_str, Style::default().fg(ty_color)),
|
||||||
|
])
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let logs_panel = Paragraph::new(visible_logs)
|
||||||
|
.block(Block::default().title(" Tapahtumaloki ").borders(Borders::ALL).style(Style::default().fg(Color::Cyan)));
|
||||||
|
f.render_widget(logs_panel, body_chunks[1]);
|
||||||
|
|
||||||
|
// --- Footer / Status ---
|
||||||
|
let status_color = if st.status == "ACTIVE" { Color::Green } else { Color::Yellow };
|
||||||
|
let status_text = format!(" Tila: {} | Komennot: [P] Pause / [R] Työhön / [Q] Sulje ", st.status);
|
||||||
|
let footer = Paragraph::new(status_text)
|
||||||
|
.style(Style::default().fg(status_color).add_modifier(Modifier::BOLD))
|
||||||
|
.alignment(Alignment::Center)
|
||||||
|
.block(Block::default().borders(Borders::ALL));
|
||||||
|
f.render_widget(footer, chunks[2]);
|
||||||
|
}
|
||||||
Binary file not shown.
33
network-poc/start-local.sh
Executable file
33
network-poc/start-local.sh
Executable file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Kipinä Agentic Network lokaali pikatesti
|
||||||
|
# Tämä ohjelma käynnistää lokaalin Kipinä Hubin taustalle, ja heittää sen jälkeen näkyviin
|
||||||
|
# visuaalisen Natiivisolmun Ratatui-ruudun yhdistäen sen automaattisesti siihen.
|
||||||
|
# Kun poistut Ratatui-näytöstä (esim painamalla Q), niin skripti sammuttaa siististi
|
||||||
|
# myös taustalla pyörivän lokaalin Hubin!
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
echo "========================================="
|
||||||
|
echo " 🔥 Kipinä Agentic Network - Local Run 🔥"
|
||||||
|
echo "========================================="
|
||||||
|
echo "Varmistetaan portin 3000 vapaus..."
|
||||||
|
lsof -ti :3000 | xargs kill -9 2>/dev/null || true
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Käynnistetään Kipinä Hub taustalle..."
|
||||||
|
# Ohjataan Hubin logit erilliseen tiedostoon jottei se sotke näkymää!
|
||||||
|
env STATIC_DIR=frontend/dist cargo run -p hub > hub-local.log 2>&1 &
|
||||||
|
HUB_PID=$!
|
||||||
|
|
||||||
|
# Odotellaan, että Hub saa portit kuunteluun
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
echo "Käynnistetään Natiivisolmu ja Ratatui-dashboard..."
|
||||||
|
# Käynnistetään TUI ja pakotetaan yhdistämään lokaaliin Hubiin.
|
||||||
|
env -u OLLAMA_MODEL HUB_URL=ws://127.0.0.1:3000/ws cargo run -p native-node
|
||||||
|
|
||||||
|
# Kun TUI ohjelmasta on poistuttu
|
||||||
|
echo ""
|
||||||
|
echo "Dashboard suljettu! Ajetaan lokaali Hub (#$HUB_PID) siististi alas..."
|
||||||
|
kill $HUB_PID
|
||||||
|
echo "Kaikki sammutettu. Kiitos!"
|
||||||
Reference in New Issue
Block a user