added setting for states and voices in setting diagnostic, added states in diagnostic, added watchdog and debug tts do diagnostic

This commit is contained in:
2026-03-29 17:12:25 +02:00
parent ff03d8ce62
commit 562f929056
2 changed files with 236 additions and 0 deletions
+125
View File
@@ -283,6 +283,7 @@
<button class="tab-btn" data-tab="bridge" onclick="switchTab('bridge')">Bridge <span class="tab-count" id="count-bridge">0</span></button>
<button class="tab-btn" data-tab="server" onclick="switchTab('server')">Server <span class="tab-count" id="count-server">0</span></button>
<button class="tab-btn" data-tab="pipeline" onclick="switchTab('pipeline')" style="margin-left:auto;border-color:#0096FF44;color:#0096FF">Pipeline <span class="tab-count" id="count-pipeline">0</span></button>
<button class="tab-btn" data-tab="tts" onclick="switchTab('tts')" style="border-color:#34C75944;color:#34C759">TTS</button>
</div>
</div>
<div class="log-panel">
@@ -302,6 +303,36 @@
<div class="log-box hidden" id="log-bridge"></div>
<div class="log-box hidden" id="log-server"></div>
<div class="log-box hidden" id="log-pipeline"></div>
<div class="log-box hidden" id="log-tts" style="padding:12px;">
<h3 style="color:#34C759;margin:0 0 12px;">TTS Diagnose</h3>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px;">
<div style="background:#1E1E2E;padding:8px;border-radius:6px;">
<div style="color:#8888AA;font-size:10px;text-transform:uppercase;">Standard-Stimme</div>
<div style="color:#fff;font-size:14px;margin-top:4px;" id="tts-default-voice">Ramona</div>
</div>
<div style="background:#1E1E2E;padding:8px;border-radius:6px;">
<div style="color:#8888AA;font-size:10px;text-transform:uppercase;">Highlight-Stimme</div>
<div style="color:#fff;font-size:14px;margin-top:4px;" id="tts-highlight-voice">Thorsten</div>
</div>
<div style="background:#1E1E2E;padding:8px;border-radius:6px;">
<div style="color:#8888AA;font-size:10px;text-transform:uppercase;">Status</div>
<div style="font-size:14px;margin-top:4px;" id="tts-status">Unbekannt</div>
</div>
<div style="background:#1E1E2E;padding:8px;border-radius:6px;">
<div style="color:#8888AA;font-size:10px;text-transform:uppercase;">Letzter Fehler</div>
<div style="color:#FF6B6B;font-size:12px;margin-top:4px;word-break:break-all;" id="tts-last-error">-</div>
</div>
</div>
<div style="margin-bottom:8px;">
<input type="text" id="tts-test-text" value="Hallo Stefan, ich bin ARIA." placeholder="Test-Text..." style="background:#1E1E2E;border:1px solid #2A2A3E;border-radius:6px;padding:8px;color:#fff;font-size:13px;width:100%;box-sizing:border-box;">
</div>
<div style="display:flex;gap:8px;">
<button class="btn" onclick="testTTS('ramona')" style="flex:1;">Ramona testen</button>
<button class="btn" onclick="testTTS('thorsten')" style="flex:1;">Thorsten testen</button>
<button class="btn secondary" onclick="checkTTSStatus()" style="flex:1;">Status pruefen</button>
</div>
<div id="tts-log" style="margin-top:12px;max-height:200px;overflow-y:auto;font-size:11px;font-family:monospace;color:#8888AA;"></div>
</div>
</div>
</div>
@@ -340,6 +371,31 @@
<!-- ══════ TAB: Einstellungen ══════ -->
<div id="tab-settings" class="main-tab">
<!-- Betriebsmodus -->
<div class="settings-section">
<h2>Betriebsmodus</h2>
<div class="card" style="max-width:500px;">
<div id="mode-selector" style="display:grid;grid-template-columns:1fr 1fr;gap:8px;">
<button class="btn mode-btn" data-mode="normal" onclick="setMode('normal')" style="background:#1E1E2E;border:2px solid transparent;">
<span style="font-size:18px;">&#x1F7E2;</span> Normal<br><span style="font-size:10px;color:#8888AA;">Hoert zu, antwortet, spricht</span>
</button>
<button class="btn mode-btn" data-mode="dnd" onclick="setMode('dnd')" style="background:#1E1E2E;border:2px solid transparent;">
<span style="font-size:18px;">&#x1F534;</span> Nicht stoeren<br><span style="font-size:10px;color:#8888AA;">Nur Kritikalarme</span>
</button>
<button class="btn mode-btn" data-mode="whisper" onclick="setMode('whisper')" style="background:#1E1E2E;border:2px solid transparent;">
<span style="font-size:18px;">&#x1F7E1;</span> Fluestern<br><span style="font-size:10px;color:#8888AA;">Nur Text, keine Sprache</span>
</button>
<button class="btn mode-btn" data-mode="hangar" onclick="setMode('hangar')" style="background:#1E1E2E;border:2px solid transparent;">
<span style="font-size:18px;">&#x2708;&#xFE0F;</span> Hangar<br><span style="font-size:10px;color:#8888AA;">Nur wichtige Meldungen</span>
</button>
<button class="btn mode-btn" data-mode="gaming" onclick="setMode('gaming')" style="background:#1E1E2E;border:2px solid transparent;grid-column:1/-1;">
<span style="font-size:18px;">&#x1F3AE;</span> Gaming<br><span style="font-size:10px;color:#8888AA;">Nur direkte Fragen</span>
</button>
</div>
<div style="margin-top:8px;font-size:11px;color:#555570;" id="mode-status">Aktueller Modus: Normal</div>
</div>
</div>
<!-- Tool-Berechtigungen -->
<div class="settings-section">
<h2>Tool-Berechtigungen</h2>
@@ -420,6 +476,7 @@
bridge: document.getElementById('log-bridge'),
server: document.getElementById('log-server'),
pipeline: document.getElementById('log-pipeline'),
tts: document.getElementById('log-tts'),
};
// Scroll-Pause pro aktivem Tab
@@ -513,11 +570,43 @@
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 === 'tts_result') {
if (msg.ok) {
ttsLog(`\u2705 ${msg.voice}: ${msg.duration}ms, ${msg.size} bytes`);
document.getElementById('tts-status').textContent = 'OK';
document.getElementById('tts-status').style.color = '#34C759';
} else {
ttsLog(`\u274C Fehler: ${msg.error}`);
document.getElementById('tts-status').textContent = 'Fehler';
document.getElementById('tts-status').style.color = '#FF3B30';
document.getElementById('tts-last-error').textContent = msg.error;
}
return;
}
if (msg.type === 'tts_status') {
document.getElementById('tts-default-voice').textContent = msg.defaultVoice || '?';
document.getElementById('tts-highlight-voice').textContent = msg.highlightVoice || '?';
document.getElementById('tts-status').textContent = msg.ok ? 'OK' : 'Fehler';
document.getElementById('tts-status').style.color = msg.ok ? '#34C759' : '#FF3B30';
if (msg.voices) ttsLog(`Stimmen: ${msg.voices.join(', ')}`);
if (msg.error) { document.getElementById('tts-last-error').textContent = msg.error; ttsLog(`Fehler: ${msg.error}`); }
else { document.getElementById('tts-last-error').textContent = '-'; ttsLog('TTS OK'); }
return;
}
if (msg.type === 'agent_activity') {
updateThinkingIndicator(msg);
return;
}
if (msg.type === 'watchdog') {
const colors = { warning: '#FFD60A', fixing: '#FF9500', fixed: '#34C759', error: '#FF3B30' };
const color = colors[msg.status] || '#FFD60A';
addChat('error', `\u26A0\uFE0F Watchdog: ${msg.message}`, `system — ${msg.status}`);
addLog('warn', 'server', `Watchdog: ${msg.message}`);
return;
}
if (msg.type === 'chat_final') {
addChat('received', msg.text, 'chat:final');
return;
@@ -991,6 +1080,42 @@
}, 120000);
}
// ── Modus-Wechsel ────────────────────────────
let currentMode = 'normal';
const MODE_LABELS = { normal: 'Normal', dnd: 'Nicht stoeren', whisper: 'Fluestern', hangar: 'Hangar', gaming: 'Gaming' };
function setMode(mode) {
currentMode = mode;
// Visuelles Feedback
document.querySelectorAll('.mode-btn').forEach(btn => {
btn.style.borderColor = btn.dataset.mode === mode ? '#0096FF' : 'transparent';
});
document.getElementById('mode-status').textContent = `Aktueller Modus: ${MODE_LABELS[mode] || mode}`;
// An Bridge senden via RVS
sendToRVS(`ARIA, ${MODE_LABELS[mode]}-Modus`, false);
log("info", "server", `Modus gewechselt: ${mode}`);
}
// ── TTS Diagnose ─────────────────────────────
function ttsLog(msg) {
const el = document.getElementById('tts-log');
const time = new Date().toLocaleTimeString('de-DE');
el.innerHTML += `<div>[${time}] ${escapeHtml(msg)}</div>`;
el.scrollTop = el.scrollHeight;
}
function testTTS(voice) {
const text = document.getElementById('tts-test-text').value.trim();
if (!text) return;
ttsLog(`Teste ${voice}: "${text}"...`);
send({ action: 'test_tts', voice, text });
}
function checkTTSStatus() {
ttsLog('Pruefe TTS-Status...');
send({ action: 'check_tts' });
}
function openLightbox(mediaType, url) {
const lb = document.getElementById('lightbox');
if (mediaType === 'video') {
+111
View File
@@ -336,6 +336,7 @@ function handleGatewayMessage(msg) {
// Genereller Activity-Heartbeat (ARIA denkt)
broadcast({ type: "agent_activity", activity: stream || "thinking" });
updateAgentActivity();
return;
}
@@ -352,6 +353,8 @@ function handleGatewayMessage(msg) {
if (pipelineActive) pipelineEnd(true, `"${text.slice(0, 120)}"`);
broadcast({ type: "chat_final", text, payload });
broadcast({ type: "agent_activity", activity: "idle" });
pendingMessageTime = 0; // Watchdog: Antwort erhalten
updateAgentActivity();
return;
}
@@ -424,6 +427,7 @@ function sendToGateway(text, isPipeline) {
const payload = JSON.stringify(msg);
log("debug", "gateway", `RAW >>> ${payload}`);
gatewayWs.send(payload);
pendingMessageTime = Date.now(); // Watchdog: Nachricht gesendet
log("info", "gateway", `chat.send [${reqId}]: "${text}"`);
if (isPipeline) plog(`chat.send [${reqId}] an Gateway gesendet — warte auf ACK...`);
@@ -1017,6 +1021,46 @@ function waitForMessage(ws, timeoutMs) {
});
}
// ── Watchdog: Stuck Run Erkennung ────────────────────────
let lastAgentActivity = Date.now();
let watchdogWarned = false;
let pendingMessageTime = 0; // Wann wurde die letzte Nachricht gesendet
function updateAgentActivity() {
lastAgentActivity = Date.now();
watchdogWarned = false;
}
// Watchdog prüft alle 30s ob ARIA nach einer gesendeten Nachricht reagiert
setInterval(async () => {
if (pendingMessageTime === 0) return; // Keine Nachricht gesendet
const waitingMs = Date.now() - pendingMessageTime;
// Nach 2min ohne Agent-Activity: Warnung
if (waitingMs > 120000 && !watchdogWarned) {
watchdogWarned = true;
log("warn", "server", `Watchdog: Keine ARIA-Aktivitaet seit ${Math.round(waitingMs / 1000)}s — moeglicherweise stuck`);
broadcast({ type: "watchdog", status: "warning", waitingMs, message: "ARIA reagiert nicht — moeglicherweise stuck Run" });
}
// Nach 5min: Auto-Fix anbieten
if (waitingMs > 300000 && watchdogWarned) {
log("error", "server", "Watchdog: 5min ohne Antwort — fuehre openclaw doctor --fix aus");
broadcast({ type: "watchdog", status: "fixing", message: "Auto-Fix: openclaw doctor --fix" });
try {
await dockerExec("aria-core", "openclaw doctor --fix 2>/dev/null || true");
log("info", "server", "Watchdog: doctor --fix ausgefuehrt");
broadcast({ type: "watchdog", status: "fixed", message: "doctor --fix ausgefuehrt — sende Nachricht erneut" });
} catch (err) {
log("error", "server", `Watchdog: doctor --fix fehlgeschlagen: ${err.message}`);
broadcast({ type: "watchdog", status: "error", message: `Auto-Fix fehlgeschlagen: ${err.message}` });
}
pendingMessageTime = 0; // Reset
watchdogWarned = false;
}
}, 30000);
// ── HTTP Server + WebSocket fuer Browser ────────────────
const htmlPath = path.join(__dirname, "index.html");
@@ -1103,6 +1147,10 @@ wss.on("connection", (ws) => {
if (ws._sshSock) ws._sshSock.write(msg.data);
} else if (msg.action === "live_ssh_close") {
if (ws._sshSock) { ws._sshSock.end(); ws._sshSock = null; }
} else if (msg.action === "test_tts") {
handleTestTTS(ws, msg.voice || "ramona", msg.text || "Test");
} else if (msg.action === "check_tts") {
handleCheckTTS(ws);
} else if (msg.action === "check_desktop") {
checkDesktopAvailable(ws);
} else if (msg.action === "load_chat_history") {
@@ -1229,6 +1277,69 @@ function startLiveSSH(clientWs) {
createReq.end(createBody);
}
// ── TTS Diagnose ──────────────────────────────────────
async function handleTestTTS(clientWs, voice, text) {
try {
log("info", "server", `TTS-Test: ${voice} — "${text}"`);
const result = await dockerExec("aria-bridge", `python3 -c "
import time, sys
sys.path.insert(0, '/app')
from piper import PiperVoice
import wave, tempfile, os
voices = {'ramona': '/voices/de_DE-ramona-low.onnx', 'thorsten': '/voices/de_DE-thorsten-high.onnx'}
path = voices.get('${voice}')
if not path or not os.path.exists(path):
print('FEHLER: Stimme nicht gefunden')
sys.exit(1)
v = PiperVoice.load(path)
start = time.time()
tmp = tempfile.NamedTemporaryFile(suffix='.wav', delete=False)
with wave.open(tmp.name, 'wb') as wf:
wf.setnchannels(1)
wf.setsampwidth(2)
wf.setframerate(v.config.sample_rate)
v.synthesize('${text.replace(/'/g, "\\'")}', wf)
size = os.path.getsize(tmp.name)
dur = int((time.time() - start) * 1000)
os.unlink(tmp.name)
print(f'OK:{dur}:{size}')
"`);
const parts = result.trim().split(":");
if (parts[0] === "OK") {
clientWs.send(JSON.stringify({ type: "tts_result", ok: true, voice, duration: parts[1], size: parts[2] }));
} else {
clientWs.send(JSON.stringify({ type: "tts_result", ok: false, voice, error: result.trim() }));
}
} catch (err) {
clientWs.send(JSON.stringify({ type: "tts_result", ok: false, voice, error: err.message }));
}
}
async function handleCheckTTS(clientWs) {
try {
const result = await dockerExec("aria-bridge", `python3 -c "
import os, json
voices = {}
for name, path in [('ramona', '/voices/de_DE-ramona-low.onnx'), ('thorsten', '/voices/de_DE-thorsten-high.onnx')]:
voices[name] = os.path.exists(path)
print(json.dumps(voices))
"`);
const voices = JSON.parse(result.trim());
const available = Object.entries(voices).filter(([,v]) => v).map(([k]) => k);
const missing = Object.entries(voices).filter(([,v]) => !v).map(([k]) => k);
clientWs.send(JSON.stringify({
type: "tts_status",
ok: missing.length === 0,
voices: available,
defaultVoice: "ramona",
highlightVoice: "thorsten",
error: missing.length > 0 ? `Fehlend: ${missing.join(", ")}` : null,
}));
} catch (err) {
clientWs.send(JSON.stringify({ type: "tts_status", ok: false, error: err.message }));
}
}
function checkDesktopAvailable(clientWs) {
// Pruefen ob VNC auf der VM laeuft (Port 5900/5901)
const checkSock = net.connect({ host: "host.docker.internal", port: 5901 }, () => {