added diagnostic page
This commit is contained in:
parent
c5d835ea09
commit
dc8ff7a406
|
|
@ -1,6 +1,7 @@
|
|||
# Bridge → aria-core (OpenClaw Gateway)
|
||||
# Standard: ws://aria-core:18789 (internes Docker-Netz)
|
||||
ARIA_CORE_WS=ws://aria-core:18789
|
||||
# Bridge teilt Netzwerk mit aria-core (network_mode: service:aria)
|
||||
# → localhost ist aria-core
|
||||
ARIA_CORE_WS=ws://127.0.0.1:18789
|
||||
|
||||
# Piper TTS Stimmen
|
||||
PIPER_RAMONA=/voices/de_DE-ramona-low.onnx
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ logger = logging.getLogger("aria-bridge")
|
|||
|
||||
CONFIG_PATH = Path("/config/aria.env")
|
||||
VOICES_DIR = Path("/voices")
|
||||
CORE_WS_URL = os.getenv("ARIA_CORE_WS", "ws://aria-core:18789")
|
||||
CORE_WS_URL = os.getenv("ARIA_CORE_WS", "ws://127.0.0.1:18789")
|
||||
CORE_AUTH_TOKEN = os.getenv("ARIA_AUTH_TOKEN", "") # OpenClaw Gateway Token
|
||||
RVS_HOST = os.getenv("RVS_HOST", "") # z.B. rvs.hackersoft.de
|
||||
RVS_PORT = os.getenv("RVS_PORT", "443") # Port des RVS
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
FROM node:22-alpine
|
||||
WORKDIR /app
|
||||
COPY package.json ./
|
||||
RUN npm install --production
|
||||
COPY . .
|
||||
EXPOSE 3001
|
||||
CMD ["node", "server.js"]
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ARIA Diagnostic</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { background: #0D0D1A; color: #E0E0F0; font-family: 'Courier New', monospace; padding: 16px; }
|
||||
h1 { font-size: 20px; margin-bottom: 16px; color: #0096FF; }
|
||||
h2 { font-size: 14px; margin-bottom: 8px; color: #8888AA; text-transform: uppercase; letter-spacing: 1px; }
|
||||
|
||||
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 16px; }
|
||||
.card { background: #12122A; border: 1px solid #1E1E2E; border-radius: 8px; padding: 12px; }
|
||||
.card.full { grid-column: 1 / -1; }
|
||||
|
||||
.status-row { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
|
||||
.dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
|
||||
.dot.connected { background: #34C759; box-shadow: 0 0 6px #34C759; }
|
||||
.dot.connecting { background: #FFD60A; }
|
||||
.dot.disconnected { background: #FF3B30; }
|
||||
.dot.error { background: #FF3B30; box-shadow: 0 0 6px #FF3B30; }
|
||||
.dot.not_configured { background: #555570; }
|
||||
|
||||
.status-label { font-size: 13px; }
|
||||
.error-text { color: #FF6B6B; font-size: 11px; margin-top: 4px; }
|
||||
|
||||
.btn { background: #0096FF; color: #fff; border: none; border-radius: 6px; padding: 8px 16px;
|
||||
font-family: inherit; font-size: 13px; cursor: pointer; margin: 4px 4px 4px 0; }
|
||||
.btn:hover { background: #007ADB; }
|
||||
.btn:disabled { background: #333; color: #666; cursor: not-allowed; }
|
||||
.btn.secondary { background: #1E1E2E; border: 1px solid #333; }
|
||||
.btn.secondary:hover { background: #2A2A3E; }
|
||||
|
||||
.log-box { background: #080810; border: 1px solid #1E1E2E; border-radius: 6px;
|
||||
height: 300px; overflow-y: auto; padding: 8px; font-size: 11px; line-height: 1.6; }
|
||||
.log-entry { white-space: pre-wrap; word-break: break-all; }
|
||||
.log-entry.error { color: #FF6B6B; }
|
||||
.log-entry.warn { color: #FFD60A; }
|
||||
.log-entry.info { color: #AAB; }
|
||||
.log-entry.debug { color: #555570; }
|
||||
|
||||
.chat-box { background: #080810; border: 1px solid #1E1E2E; border-radius: 6px;
|
||||
min-height: 120px; max-height: 250px; overflow-y: auto; padding: 8px; margin-bottom: 8px; }
|
||||
.chat-msg { margin-bottom: 6px; padding: 6px 10px; border-radius: 6px; font-size: 13px; }
|
||||
.chat-msg.sent { background: #0096FF; color: #fff; margin-left: 20%; text-align: right; }
|
||||
.chat-msg.received { background: #1E1E2E; margin-right: 20%; }
|
||||
.chat-msg.error { background: #3B1010; color: #FF6B6B; }
|
||||
.chat-msg .meta { font-size: 10px; color: rgba(255,255,255,0.4); margin-top: 2px; }
|
||||
|
||||
.input-row { display: flex; gap: 6px; }
|
||||
.input-row input { flex: 1; background: #1E1E2E; border: 1px solid #333; border-radius: 6px;
|
||||
padding: 8px 12px; color: #E0E0F0; font-family: inherit; font-size: 13px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>ARIA Diagnostic</h1>
|
||||
|
||||
<!-- Verbindungsstatus -->
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<h2>OpenClaw Gateway</h2>
|
||||
<div class="status-row">
|
||||
<div class="dot" id="gw-dot"></div>
|
||||
<span class="status-label" id="gw-status">-</span>
|
||||
</div>
|
||||
<div class="error-text" id="gw-error"></div>
|
||||
<button class="btn secondary" onclick="send({action:'reconnect_gateway'})">Reconnect</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>RVS (Rendezvous)</h2>
|
||||
<div class="status-row">
|
||||
<div class="dot" id="rvs-dot"></div>
|
||||
<span class="status-label" id="rvs-status">-</span>
|
||||
</div>
|
||||
<div class="error-text" id="rvs-error"></div>
|
||||
<button class="btn secondary" onclick="send({action:'reconnect_rvs'})">Reconnect</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chat Test -->
|
||||
<div class="grid">
|
||||
<div class="card full">
|
||||
<h2>Chat Test</h2>
|
||||
<div class="chat-box" id="chat-box"></div>
|
||||
<div class="input-row">
|
||||
<input type="text" id="chat-input" value="aria lebst du noch?" placeholder="Nachricht...">
|
||||
<button class="btn" id="btn-gw" onclick="testGateway()">Gateway senden</button>
|
||||
<button class="btn" id="btn-rvs" onclick="testRVS()">Via RVS senden</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Verbindungslog -->
|
||||
<div class="card" style="margin-top:12px">
|
||||
<h2>Verbindungslog</h2>
|
||||
<div class="log-box" id="log-box"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const logBox = document.getElementById('log-box');
|
||||
const chatBox = document.getElementById('chat-box');
|
||||
let ws;
|
||||
|
||||
const STATUS_LABELS = {
|
||||
connected: 'Verbunden',
|
||||
connecting: 'Verbinde...',
|
||||
disconnected: 'Getrennt',
|
||||
error: 'Fehler',
|
||||
not_configured: 'Nicht konfiguriert',
|
||||
};
|
||||
|
||||
function connectWS() {
|
||||
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
ws = new WebSocket(`${proto}://${location.host}`);
|
||||
|
||||
ws.onopen = () => addLog('info', 'browser', 'Verbunden mit Diagnostic Server');
|
||||
ws.onclose = () => {
|
||||
addLog('warn', 'browser', 'Verbindung zum Diagnostic Server verloren — Reconnect in 2s');
|
||||
setTimeout(connectWS, 2000);
|
||||
};
|
||||
|
||||
ws.onmessage = (e) => {
|
||||
const msg = JSON.parse(e.data);
|
||||
|
||||
if (msg.type === 'init') {
|
||||
updateState(msg.state);
|
||||
msg.logs.forEach(entry => addLog(entry.level, entry.source, entry.message, entry.ts));
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.type === 'state') { updateState(msg.state); return; }
|
||||
if (msg.type === 'log') { addLog(msg.entry.level, msg.entry.source, msg.entry.message, msg.entry.ts); return; }
|
||||
|
||||
if (msg.type === 'chat_final') {
|
||||
addChat('received', msg.text, 'chat:final');
|
||||
return;
|
||||
}
|
||||
if (msg.type === 'chat_delta') {
|
||||
// Deltas optional anzeigen
|
||||
return;
|
||||
}
|
||||
if (msg.type === 'chat_error') {
|
||||
addChat('error', msg.error, 'chat:error');
|
||||
return;
|
||||
}
|
||||
if (msg.type === 'rvs_chat') {
|
||||
const p = msg.msg.payload || {};
|
||||
addChat('received', p.text || '?', `via RVS (${p.sender || '?'})`);
|
||||
return;
|
||||
}
|
||||
if (msg.type === 'response') {
|
||||
// chat.send Ack — nur loggen
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function send(obj) {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify(obj));
|
||||
}
|
||||
}
|
||||
|
||||
function testGateway() {
|
||||
const text = document.getElementById('chat-input').value.trim();
|
||||
if (!text) return;
|
||||
addChat('sent', text, 'Gateway direkt');
|
||||
send({ action: 'test_gateway', text });
|
||||
}
|
||||
|
||||
function testRVS() {
|
||||
const text = document.getElementById('chat-input').value.trim();
|
||||
if (!text) return;
|
||||
addChat('sent', text, 'via RVS');
|
||||
send({ action: 'test_rvs', text });
|
||||
}
|
||||
|
||||
function updateState(state) {
|
||||
// Gateway
|
||||
const gw = state.gateway || {};
|
||||
document.getElementById('gw-dot').className = `dot ${gw.status || 'disconnected'}`;
|
||||
document.getElementById('gw-status').textContent =
|
||||
(STATUS_LABELS[gw.status] || gw.status) + (gw.handshakeOk ? ' (Handshake OK)' : '');
|
||||
document.getElementById('gw-error').textContent = gw.lastError || '';
|
||||
|
||||
// RVS
|
||||
const rvs = state.rvs || {};
|
||||
document.getElementById('rvs-dot').className = `dot ${rvs.status || 'disconnected'}`;
|
||||
document.getElementById('rvs-status').textContent = STATUS_LABELS[rvs.status] || rvs.status;
|
||||
document.getElementById('rvs-error').textContent = rvs.lastError || '';
|
||||
|
||||
// Buttons
|
||||
document.getElementById('btn-gw').disabled = gw.status !== 'connected';
|
||||
document.getElementById('btn-rvs').disabled = rvs.status !== 'connected';
|
||||
}
|
||||
|
||||
function addLog(level, source, message, ts) {
|
||||
const time = ts ? new Date(ts).toLocaleTimeString('de-DE') : new Date().toLocaleTimeString('de-DE');
|
||||
const el = document.createElement('div');
|
||||
el.className = `log-entry ${level}`;
|
||||
el.textContent = `${time} [${source}] ${message}`;
|
||||
logBox.appendChild(el);
|
||||
logBox.scrollTop = logBox.scrollHeight;
|
||||
}
|
||||
|
||||
function addChat(type, text, meta) {
|
||||
const el = document.createElement('div');
|
||||
el.className = `chat-msg ${type}`;
|
||||
el.innerHTML = `${escapeHtml(text)}<div class="meta">${escapeHtml(meta)} — ${new Date().toLocaleTimeString('de-DE')}</div>`;
|
||||
chatBox.appendChild(el);
|
||||
chatBox.scrollTop = chatBox.scrollHeight;
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||
}
|
||||
|
||||
// Enter-Taste sendet via Gateway
|
||||
document.getElementById('chat-input').addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') testGateway();
|
||||
});
|
||||
|
||||
connectWS();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "aria-diagnostic",
|
||||
"version": "0.0.1",
|
||||
"description": "ARIA Diagnostic — Verbindungstest und Chat-Test",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"ws": "^8.18.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,401 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* ARIA Diagnostic Server
|
||||
*
|
||||
* Leichtgewichtiges Diagnose-Tool:
|
||||
* - Verbindet sich direkt mit OpenClaw Gateway (ws://127.0.0.1:18789)
|
||||
* - Fuehrt den vollstaendigen Handshake durch
|
||||
* - Sendet Testnachrichten und zeigt Antworten
|
||||
* - Zeigt Verbindungsstatus aller Komponenten
|
||||
*
|
||||
* Laeuft im selben Netzwerk wie aria-core (network_mode: service:aria)
|
||||
*/
|
||||
|
||||
const http = require("http");
|
||||
const { WebSocket, WebSocketServer } = require("ws");
|
||||
const crypto = require("crypto");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
// ── Konfiguration ───────────────────────────────────────
|
||||
const HTTP_PORT = parseInt(process.env.DIAG_PORT || "3001", 10);
|
||||
const GATEWAY_URL = process.env.ARIA_CORE_WS || "ws://127.0.0.1:18789";
|
||||
const GATEWAY_TOKEN = process.env.ARIA_AUTH_TOKEN || "";
|
||||
const RVS_HOST = process.env.RVS_HOST || "";
|
||||
const RVS_PORT = process.env.RVS_PORT || "443";
|
||||
const RVS_TLS = process.env.RVS_TLS || "true";
|
||||
const RVS_TOKEN = process.env.RVS_TOKEN || "";
|
||||
|
||||
// ── State ───────────────────────────────────────────────
|
||||
const state = {
|
||||
gateway: { status: "disconnected", lastError: null, handshakeOk: false },
|
||||
rvs: { status: "disconnected", lastError: null },
|
||||
};
|
||||
const logs = [];
|
||||
let gatewayWs = null;
|
||||
let rvsWs = null;
|
||||
let reqIdCounter = 0;
|
||||
const browserClients = new Set();
|
||||
|
||||
function nextReqId() {
|
||||
return `diag-${++reqIdCounter}`;
|
||||
}
|
||||
|
||||
function log(level, source, message) {
|
||||
const entry = { ts: new Date().toISOString(), level, source, message };
|
||||
logs.push(entry);
|
||||
if (logs.length > 500) logs.shift();
|
||||
console.log(`[${entry.ts}] [${level}] [${source}] ${message}`);
|
||||
// An alle Browser-Clients senden
|
||||
broadcast({ type: "log", entry });
|
||||
}
|
||||
|
||||
function broadcast(msg) {
|
||||
const data = JSON.stringify(msg);
|
||||
for (const client of browserClients) {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
client.send(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function broadcastState() {
|
||||
broadcast({ type: "state", state });
|
||||
}
|
||||
|
||||
// ── OpenClaw Gateway Verbindung ─────────────────────────
|
||||
|
||||
async function connectGateway() {
|
||||
if (gatewayWs) {
|
||||
try { gatewayWs.close(); } catch (_) {}
|
||||
gatewayWs = null;
|
||||
}
|
||||
|
||||
state.gateway.status = "connecting";
|
||||
state.gateway.handshakeOk = false;
|
||||
broadcastState();
|
||||
log("info", "gateway", `Verbinde: ${GATEWAY_URL}`);
|
||||
|
||||
try {
|
||||
const ws = new WebSocket(GATEWAY_URL);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
ws.close();
|
||||
reject(new Error("Verbindungs-Timeout (10s)"));
|
||||
}, 10000);
|
||||
|
||||
ws.on("open", () => {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
});
|
||||
|
||||
ws.on("error", (err) => {
|
||||
clearTimeout(timeout);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
log("info", "gateway", "TCP-Verbindung hergestellt — warte auf Challenge");
|
||||
|
||||
// Schritt 1: Auf Challenge warten
|
||||
const challengeRaw = await waitForMessage(ws, 10000);
|
||||
const challenge = JSON.parse(challengeRaw);
|
||||
|
||||
if (challenge.type !== "event" || challenge.event !== "connect.challenge") {
|
||||
throw new Error(`Unerwartete erste Nachricht: ${challengeRaw.slice(0, 200)}`);
|
||||
}
|
||||
|
||||
const nonce = challenge.payload?.nonce || "";
|
||||
log("info", "gateway", `Challenge empfangen (nonce: ${nonce.slice(0, 8)}...)`);
|
||||
|
||||
// Schritt 2: Connect Request senden
|
||||
const connectReq = {
|
||||
type: "req",
|
||||
id: nextReqId(),
|
||||
method: "connect",
|
||||
params: {
|
||||
minProtocol: 3,
|
||||
maxProtocol: 3,
|
||||
client: {
|
||||
id: "aria-diagnostic",
|
||||
version: "0.0.1",
|
||||
platform: "linux",
|
||||
mode: "operator",
|
||||
},
|
||||
role: "operator",
|
||||
scopes: ["operator.read", "operator.write"],
|
||||
caps: [],
|
||||
commands: [],
|
||||
permissions: {},
|
||||
auth: GATEWAY_TOKEN ? { token: GATEWAY_TOKEN } : {},
|
||||
locale: "de-DE",
|
||||
userAgent: "aria-diagnostic/0.0.1",
|
||||
},
|
||||
};
|
||||
|
||||
ws.send(JSON.stringify(connectReq));
|
||||
log("info", "gateway", "Connect-Request gesendet");
|
||||
|
||||
// Schritt 3: hello-ok warten
|
||||
const responseRaw = await waitForMessage(ws, 10000);
|
||||
const response = JSON.parse(responseRaw);
|
||||
|
||||
if (response.type === "res" && response.ok) {
|
||||
log("info", "gateway", "Handshake erfolgreich — hello-ok!");
|
||||
state.gateway.status = "connected";
|
||||
state.gateway.handshakeOk = true;
|
||||
state.gateway.lastError = null;
|
||||
} else {
|
||||
const error = response.error || JSON.stringify(response).slice(0, 200);
|
||||
throw new Error(`Handshake fehlgeschlagen: ${error}`);
|
||||
}
|
||||
|
||||
gatewayWs = ws;
|
||||
broadcastState();
|
||||
|
||||
// Nachrichten-Loop
|
||||
ws.on("message", (raw) => {
|
||||
try {
|
||||
const msg = JSON.parse(raw.toString());
|
||||
handleGatewayMessage(msg);
|
||||
} catch (err) {
|
||||
log("error", "gateway", `Parse-Fehler: ${err.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on("close", (code, reason) => {
|
||||
log("warn", "gateway", `Verbindung geschlossen (Code: ${code}, Reason: ${reason || "-"})`);
|
||||
state.gateway.status = "disconnected";
|
||||
state.gateway.handshakeOk = false;
|
||||
gatewayWs = null;
|
||||
broadcastState();
|
||||
// Auto-Reconnect nach 5s
|
||||
setTimeout(connectGateway, 5000);
|
||||
});
|
||||
|
||||
ws.on("error", (err) => {
|
||||
log("error", "gateway", `WebSocket-Fehler: ${err.message}`);
|
||||
state.gateway.lastError = err.message;
|
||||
broadcastState();
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
log("error", "gateway", `Fehler: ${err.message}`);
|
||||
state.gateway.status = "error";
|
||||
state.gateway.lastError = err.message;
|
||||
state.gateway.handshakeOk = false;
|
||||
gatewayWs = null;
|
||||
broadcastState();
|
||||
// Retry nach 5s
|
||||
setTimeout(connectGateway, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function handleGatewayMessage(msg) {
|
||||
if (msg.type === "res") {
|
||||
const status = msg.ok ? "OK" : `FEHLER: ${JSON.stringify(msg.error).slice(0, 100)}`;
|
||||
log("info", "gateway", `Response [${msg.id}]: ${status}`);
|
||||
broadcast({ type: "response", msg });
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.type === "event") {
|
||||
const event = msg.event || "?";
|
||||
const payload = msg.payload || {};
|
||||
|
||||
if (event === "chat:delta") {
|
||||
const delta = payload.delta || payload.text || "";
|
||||
if (delta) {
|
||||
log("info", "gateway", `Delta: "${delta.slice(0, 60)}"`);
|
||||
broadcast({ type: "chat_delta", delta, payload });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event === "chat:final") {
|
||||
const text = payload.text || payload.message || "";
|
||||
log("info", "gateway", `ANTWORT: "${text.slice(0, 200)}"`);
|
||||
broadcast({ type: "chat_final", text, payload });
|
||||
return;
|
||||
}
|
||||
|
||||
if (event === "chat:error") {
|
||||
const error = payload.error || payload.message || "Unbekannt";
|
||||
log("error", "gateway", `Chat-Fehler: ${error}`);
|
||||
broadcast({ type: "chat_error", error, payload });
|
||||
return;
|
||||
}
|
||||
|
||||
// Andere Events (presence, tick, etc.)
|
||||
log("debug", "gateway", `Event: ${event}`);
|
||||
}
|
||||
}
|
||||
|
||||
function sendToGateway(text) {
|
||||
if (!gatewayWs || gatewayWs.readyState !== WebSocket.OPEN) {
|
||||
log("error", "gateway", "Nicht verbunden — kann nicht senden");
|
||||
return false;
|
||||
}
|
||||
|
||||
const reqId = nextReqId();
|
||||
const msg = {
|
||||
type: "req",
|
||||
id: reqId,
|
||||
method: "chat.send",
|
||||
params: {
|
||||
sessionKey: "aria-diagnostic",
|
||||
text,
|
||||
idempotencyKey: crypto.randomUUID(),
|
||||
},
|
||||
};
|
||||
|
||||
gatewayWs.send(JSON.stringify(msg));
|
||||
log("info", "gateway", `chat.send [${reqId}]: "${text}"`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── RVS Verbindung (optional) ───────────────────────────
|
||||
|
||||
function connectRVS() {
|
||||
if (!RVS_HOST || !RVS_TOKEN) {
|
||||
log("info", "rvs", "Nicht konfiguriert — ueberspringe");
|
||||
state.rvs.status = "not_configured";
|
||||
broadcastState();
|
||||
return;
|
||||
}
|
||||
|
||||
const proto = RVS_TLS === "true" ? "wss" : "ws";
|
||||
const url = `${proto}://${RVS_HOST}:${RVS_PORT}?token=${RVS_TOKEN}`;
|
||||
|
||||
state.rvs.status = "connecting";
|
||||
broadcastState();
|
||||
log("info", "rvs", `Verbinde: ${proto}://${RVS_HOST}:${RVS_PORT}`);
|
||||
|
||||
const ws = new WebSocket(url);
|
||||
|
||||
ws.on("open", () => {
|
||||
log("info", "rvs", "Verbunden");
|
||||
state.rvs.status = "connected";
|
||||
state.rvs.lastError = null;
|
||||
rvsWs = ws;
|
||||
broadcastState();
|
||||
});
|
||||
|
||||
ws.on("message", (raw) => {
|
||||
try {
|
||||
const msg = JSON.parse(raw.toString());
|
||||
if (msg.type === "chat" && msg.payload) {
|
||||
log("info", "rvs", `Chat von ${msg.payload.sender || "?"}: "${(msg.payload.text || "").slice(0, 100)}"`);
|
||||
broadcast({ type: "rvs_chat", msg });
|
||||
} else if (msg.type === "heartbeat") {
|
||||
// ignorieren
|
||||
} else {
|
||||
log("debug", "rvs", `Nachricht: ${JSON.stringify(msg).slice(0, 150)}`);
|
||||
}
|
||||
} catch {}
|
||||
});
|
||||
|
||||
ws.on("close", () => {
|
||||
log("warn", "rvs", "Verbindung geschlossen");
|
||||
state.rvs.status = "disconnected";
|
||||
rvsWs = null;
|
||||
broadcastState();
|
||||
setTimeout(connectRVS, 5000);
|
||||
});
|
||||
|
||||
ws.on("error", (err) => {
|
||||
log("error", "rvs", `Fehler: ${err.message}`);
|
||||
state.rvs.lastError = err.message;
|
||||
broadcastState();
|
||||
});
|
||||
}
|
||||
|
||||
function sendToRVS(text) {
|
||||
if (!rvsWs || rvsWs.readyState !== WebSocket.OPEN) {
|
||||
log("error", "rvs", "Nicht verbunden");
|
||||
return false;
|
||||
}
|
||||
|
||||
rvsWs.send(JSON.stringify({
|
||||
type: "chat",
|
||||
payload: { text, sender: "diagnostic" },
|
||||
timestamp: Date.now(),
|
||||
}));
|
||||
log("info", "rvs", `Gesendet via RVS: "${text}"`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── Hilfsfunktionen ─────────────────────────────────────
|
||||
|
||||
function waitForMessage(ws, timeoutMs) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error(`Timeout (${timeoutMs}ms)`));
|
||||
}, timeoutMs);
|
||||
|
||||
ws.once("message", (data) => {
|
||||
clearTimeout(timeout);
|
||||
resolve(data.toString());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ── HTTP Server + WebSocket fuer Browser ────────────────
|
||||
|
||||
const htmlPath = path.join(__dirname, "index.html");
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
if (req.url === "/" || req.url === "/index.html") {
|
||||
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
||||
res.end(fs.readFileSync(htmlPath, "utf-8"));
|
||||
} else if (req.url === "/api/state") {
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ state, logs: logs.slice(-100) }));
|
||||
} else {
|
||||
res.writeHead(404);
|
||||
res.end("Not Found");
|
||||
}
|
||||
});
|
||||
|
||||
const wss = new WebSocketServer({ server });
|
||||
|
||||
wss.on("connection", (ws) => {
|
||||
browserClients.add(ws);
|
||||
// Initialen State + letzte Logs senden
|
||||
ws.send(JSON.stringify({ type: "init", state, logs: logs.slice(-100) }));
|
||||
|
||||
ws.on("message", (raw) => {
|
||||
try {
|
||||
const msg = JSON.parse(raw.toString());
|
||||
|
||||
if (msg.action === "test_gateway") {
|
||||
sendToGateway(msg.text || "aria lebst du noch?");
|
||||
} else if (msg.action === "test_rvs") {
|
||||
sendToRVS(msg.text || "aria lebst du noch?");
|
||||
} else if (msg.action === "reconnect_gateway") {
|
||||
connectGateway();
|
||||
} else if (msg.action === "reconnect_rvs") {
|
||||
connectRVS();
|
||||
}
|
||||
} catch {}
|
||||
});
|
||||
|
||||
ws.on("close", () => {
|
||||
browserClients.delete(ws);
|
||||
});
|
||||
});
|
||||
|
||||
// ── Start ───────────────────────────────────────────────
|
||||
|
||||
server.listen(HTTP_PORT, "0.0.0.0", () => {
|
||||
log("info", "server", `Diagnostic Server laeuft auf http://0.0.0.0:${HTTP_PORT}`);
|
||||
log("info", "server", `Gateway: ${GATEWAY_URL}`);
|
||||
log("info", "server", `Token: ${GATEWAY_TOKEN ? GATEWAY_TOKEN.slice(0, 8) + "..." : "(keiner)"}`);
|
||||
log("info", "server", `RVS: ${RVS_HOST ? `${RVS_HOST}:${RVS_PORT}` : "(nicht konfiguriert)"}`);
|
||||
|
||||
// Verbindungen aufbauen
|
||||
connectGateway();
|
||||
connectRVS();
|
||||
});
|
||||
|
|
@ -18,9 +18,10 @@ services:
|
|||
privileged: true # ARIAs Wohnung — sie hat die Schlüssel
|
||||
depends_on:
|
||||
- proxy
|
||||
ports:
|
||||
- "3001:3001" # Diagnostic Web-UI (laeuft im shared network)
|
||||
environment:
|
||||
- CANVAS_HOST=127.0.0.1
|
||||
- OPENCLAW_GATEWAY_BIND=0.0.0.0 # Bridge muss von Docker-Netz zugreifen (kein Port-Mapping nach aussen)
|
||||
- OPENCLAW_GATEWAY_TOKEN=${ARIA_AUTH_TOKEN}
|
||||
- OPENAI_API_KEY=not-needed
|
||||
- OPENAI_BASE_URL=http://proxy:3456/v1
|
||||
|
|
@ -45,6 +46,7 @@ services:
|
|||
container_name: aria-bridge
|
||||
depends_on:
|
||||
- aria
|
||||
network_mode: "service:aria" # Teilt Netzwerk mit aria-core → localhost:18789 erreichbar
|
||||
volumes:
|
||||
- ./aria-data/voices:/voices:ro # TTS Stimmen
|
||||
- ./aria-data/config/aria.env:/config/aria.env
|
||||
|
|
@ -62,8 +64,21 @@ services:
|
|||
- RVS_TLS_FALLBACK=${RVS_TLS_FALLBACK:-true}
|
||||
- RVS_TOKEN=${RVS_TOKEN:-}
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- aria-net
|
||||
|
||||
# ─── Diagnostic (Selbstcheck-UI) ──────────────────────
|
||||
diagnostic:
|
||||
build: ./diagnostic
|
||||
container_name: aria-diagnostic
|
||||
depends_on:
|
||||
- aria
|
||||
network_mode: "service:aria" # Teilt Netzwerk mit aria-core → localhost:18789
|
||||
environment:
|
||||
- ARIA_AUTH_TOKEN=${ARIA_AUTH_TOKEN:-}
|
||||
- RVS_HOST=${RVS_HOST:-}
|
||||
- RVS_PORT=${RVS_PORT:-443}
|
||||
- RVS_TLS=${RVS_TLS:-true}
|
||||
- RVS_TOKEN=${RVS_TOKEN:-}
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
aria-net:
|
||||
|
|
|
|||
Loading…
Reference in New Issue