Compare commits

...
2 Commits
Author SHA1 Message Date
duffyduckandClaude Opus 4.7 950a9d009c feat(diagnostic): Info-Buttons mit Modal-Erklaerungen im Gehirn-Tab
Reusable Info-Modal-System: kleines (ℹ)-Button neben Ueberschriften, beim
Klick oeffnet ein Modal mit ausfuehrlicher Erklaerung. Fuer die 4 wichtigsten
Brain-Konzepte sind die Texte vor-definiert (INFO_TEXTS dict).

  - Gehirn — Status     online/offline, N Memories, Qdrant-Endpoint
  - Konversation        Rolling Window 50, Schwelle 60, Destillat-Logik,
                        Hinweis warum chat_backup ≠ conversation.jsonl
  - Memories            Hot vs. Cold, alle 8 Typen erklaert, semantische Suche
  - Bootstrap           Die drei Wege (Migration / Snapshot / Komplett-Gehirn)

Plus inline-ℹ-Button neben der "Konversation: N Turns"-Zeile in der
Status-Card, damit man dort wo's relevant ist sofort die Erklaerung findet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 23:03:47 +02:00
duffyduckandClaude Opus 4.7 693542ef19 fix(diagnostic): Brain-Card live + Runtime-Konfig-Text + Konversation-Reset macht beides
Drei zusammenhaengende Fixes — alle aus der OpenClaw-Umstellung uebrig.

Brain-Card im Main-Tab
  - updateState() schrieb "aria-core entfernt — Brain-Loop in Arbeit" in
    die Card, weil state.gateway noch den alten OpenClaw-Status-Text
    transportiert hat. Brain ist online, sah aber auf der Diagnostic
    aus als waere alles tot.
  - Fix: updateState laesst die Brain-Card jetzt in Ruhe. Daten kommen
    exklusiv von loadBrainStatus() (fetch /api/brain/health).
  - WS-onopen ruft jetzt loadBrainStatus(), plus 15s-Interval fuer
    Live-Refresh.

Runtime-Konfiguration
  - Hinweis-Text erwaehnte noch aria.env + "Bridge-Container neu starten".
    aria.env gibt's nicht mehr, und Brain liest die Config auch.
  - Neu: ".env / Bridge UND Brain lesen sie beim Start — nach Aenderung
    den jeweiligen Container neu starten (Reparatur-Section oben)."

Konversation-Reset
  - Vorher zwei verschiedene Konversations-Stores ohne klare UI:
    chat_backup.jsonl (Diagnostic-Anzeige) und conversation.jsonl
    (Brain Rolling Window). "Konversation leeren" leerte nur Brain,
    die UI zeigte alle Turns weiter → verwirrend.
  - Neu: ein Button "🧹 Konversation komplett zurücksetzen" leert
    beides parallel (Promise.all auf /api/brain/conversation/reset
    + /api/chat-history-clear) plus die lokale Chat-View.
  - Erklaerungstext darunter macht klar was passiert + was bleibt
    (destillierte Facts + Memories in der Vector-DB).
  - Neuer Endpoint /api/chat-history-clear loescht chat_backup.jsonl
    und broadcastet leere chat_history an alle Browser-Clients.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 22:59:35 +02:00
2 changed files with 149 additions and 25 deletions
+132 -25
View File
@@ -120,6 +120,16 @@
/* Settings */
.settings-section { margin-bottom:20px; }
.settings-section h2 { margin-bottom:12px; }
/* Info-Button: kleines (i) neben Ueberschriften */
.info-btn { background:transparent; border:1px solid #0096FF; color:#0096FF; width:20px; height:20px;
border-radius:50%; padding:0; font-size:11px; font-weight:bold; cursor:pointer; margin-left:6px;
line-height:18px; text-align:center; vertical-align:middle; font-family:serif; }
.info-btn:hover { background:#0096FF; color:#fff; }
.info-btn-small { background:transparent; border:1px solid #0096FF44; color:#0096FF; width:14px; height:14px;
border-radius:50%; padding:0; font-size:9px; font-weight:bold; cursor:pointer; margin-left:4px;
line-height:11px; text-align:center; vertical-align:middle; font-family:serif; }
.info-btn-small:hover { background:#0096FF; color:#fff; }
.toggle { position:relative; width:40px; height:22px; flex-shrink:0; margin-left:8px; }
.toggle input { opacity:0; width:0; height:0; }
.toggle .slider { position:absolute; cursor:pointer; top:0; left:0; right:0; bottom:0;
@@ -592,14 +602,14 @@
</div>
</div>
<!-- Runtime-Konfiguration (migriert von .env) -->
<!-- Runtime-Konfiguration -->
<div class="settings-section">
<h2>Runtime-Konfiguration</h2>
<div style="font-size:11px;color:#8888AA;margin-bottom:8px;">
Werte werden in <code>/shared/config/runtime.json</code> persistiert und
ueberschreiben die ENV-Variablen aus <code>aria.env</code>. Bridge liest
sie beim naechsten Start — nach Aenderung <b>Bridge-Container neu starten</b>
(Diagnostic-Container bleibt auf ENV).
ueberschreiben die ENV-Variablen aus der <code>.env</code>. Bridge und Brain
lesen sie beim Start — nach Aenderung den jeweiligen Container neu starten
(Reparatur-Section oben).
</div>
<div class="card" style="max-width:600px;">
<div style="display:grid;grid-template-columns:140px 1fr;gap:8px 10px;align-items:center;font-size:13px;">
@@ -689,20 +699,22 @@
<div id="tab-brain" class="main-tab">
<div class="settings-section">
<h2>Gehirn — Status</h2>
<h2>Gehirn — Status <button class="info-btn" onclick="showInfo('brain-status')" title="Was bedeutet was?"></button></h2>
<div class="card">
<div id="brain-status" style="font-size:12px;color:#8888AA;margin-bottom:8px;">(Lade...)</div>
<div id="conversation-status" style="font-size:12px;color:#8888AA;margin-bottom:8px;"></div>
<div id="conversation-status" style="font-size:12px;color:#8888AA;margin-bottom:8px;">
<button class="info-btn-small" onclick="showInfo('conversation')" title="Konversation — wie funktioniert das?"></button>
</div>
<div style="display:flex;gap:6px;flex-wrap:wrap;">
<button class="btn secondary" onclick="loadBrainStatus()" style="padding:4px 12px;font-size:11px;">Aktualisieren</button>
<button class="btn secondary" onclick="distillNow()" style="padding:4px 12px;font-size:11px;color:#FFD60A;border-color:#FFD60A;" title="Destilliert die aeltesten Turns sofort zu fact-Memories">⚗ Jetzt destillieren</button>
<button class="btn secondary" onclick="resetConversation()" style="padding:4px 12px;font-size:11px;color:#FF6B6B;border-color:#FF6B6B;" title="Rolling-Window leeren — destillierte Facts bleiben in der DB">🧹 Konversation leeren</button>
<button class="btn secondary" onclick="resetConversation()" style="padding:4px 12px;font-size:11px;color:#FF6B6B;border-color:#FF6B6B;" title="Leert ARIAs Rolling-Window (Brain) + die Chat-Anzeige (chat_backup). Memories bleiben in der Vector-DB.">🧹 Konversation komplett zurücksetzen</button>
</div>
</div>
</div>
<div class="settings-section">
<h2>Bootstrap & Migration</h2>
<h2>Bootstrap & Migration <button class="info-btn" onclick="showInfo('bootstrap')" title="Was sind die drei Wege?"></button></h2>
<div class="card" style="line-height:1.6;">
<p style="color:#8888AA;font-size:12px;margin:0 0 12px;">
Drei Wege ARIA mit "Grundregeln" zu füttern — von leichtgewichtig bis Voll-Wiederherstellung.
@@ -754,7 +766,7 @@
<div class="settings-section">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
<h2 style="margin:0;">Memories</h2>
<h2 style="margin:0;">Memories <button class="info-btn" onclick="showInfo('memories')" title="Hot vs. Cold — wie funktioniert das Gedaechtnis?"></button></h2>
<div>
<button class="btn secondary" onclick="resetBrainFilters();loadBrainMemoryList()" style="padding:4px 10px;font-size:11px;">Aktualisieren</button>
<button class="btn" onclick="openMemoryModal()" style="padding:4px 10px;font-size:11px;">+ Neu</button>
@@ -845,6 +857,17 @@
</div>
</div><!-- /tab-skills -->
<!-- Generisches Info-Modal — wird via openInfoModal(title, html) gefuellt -->
<div class="modal-overlay" id="info-modal">
<div class="modal-box" style="max-width:640px;">
<div class="modal-header">
<h3 id="info-modal-title">Info</h3>
<button class="modal-close" onclick="closeInfoModal()">&times;</button>
</div>
<div class="modal-body" id="info-modal-body" style="padding:16px;font-size:13px;color:#E0E0F0;line-height:1.6;"></div>
</div>
</div>
<!-- Memory-Modal (Neu + Editieren) -->
<div class="modal-overlay" id="memory-modal">
<div class="modal-box" style="max-width:640px;">
@@ -993,7 +1016,16 @@
ws.onopen = () => {
addLog('info', 'browser', 'Verbunden mit Diagnostic Server');
send({ action: 'load_chat_history' });
// Brain-Card initial laden (sonst zeigt sie "Lade...")
try { loadBrainStatus(); } catch {}
};
// Brain-Status periodisch refreshen damit die Card live bleibt
if (!window.__brainStatusInterval) {
window.__brainStatusInterval = setInterval(() => {
try { loadBrainStatus(); } catch {}
}, 15000);
}
ws.onclose = () => {
addLog('warn', 'browser', 'Verbindung zum Diagnostic Server verloren — Reconnect in 2s');
setTimeout(connectWS, 2000);
@@ -1471,15 +1503,8 @@
}
function updateState(state) {
// Brain-Status (loest die alte gw-* Gateway-Card ab — wenn loadBrainStatus
// gleich danach laeuft, kriegt es die korrekten Werte; hier nur Default)
const brainDot = document.getElementById('brain-dot');
const brainShort = document.getElementById('brain-status-short');
const brainErr = document.getElementById('brain-error');
const gw = state.gateway || {};
if (brainDot) brainDot.className = `dot ${gw.status === 'connected' ? 'connected' : 'disconnected'}`;
if (brainShort) brainShort.textContent = gw.status === 'disabled' ? 'aria-core entfernt — Brain-Loop' : (STATUS_LABELS[gw.status] || gw.status || '-');
if (brainErr) brainErr.textContent = gw.lastError || '';
// Brain-Card holt ihre Daten via loadBrainStatus() (fetch /api/brain/health).
// state.gateway ist Reststruktur aus OpenClaw-Zeit — wir ignorieren das hier.
// RVS
const rvs = state.rvs || {};
@@ -2870,14 +2895,15 @@
// Conversation-Stats (separater Endpoint)
const conv = document.getElementById('conversation-status');
if (!conv) return;
const infoBtn = `<button class="info-btn-small" onclick="showInfo('conversation')" title="Konversation — wie funktioniert das?"></button>`;
try {
const r2 = await fetch('/api/brain/conversation/stats');
if (!r2.ok) throw new Error('HTTP ' + r2.status);
const d2 = await r2.json();
const distillIcon = d2.needs_distill ? ' ⚠ Destillat bald fällig' : '';
conv.innerHTML = `Konversation: <strong>${d2.turns}</strong> Turns · Window: ${d2.max_window} · Schwelle: ${d2.distill_threshold}${distillIcon}`;
conv.innerHTML = `Konversation: <strong>${d2.turns}</strong> Turns · Window: ${d2.max_window} · Schwelle: ${d2.distill_threshold}${distillIcon} ${infoBtn}`;
} catch (e) {
conv.innerHTML = `Konversation: <span style="color:#555570;">${e.message}</span>`;
conv.innerHTML = `Konversation: <span style="color:#555570;">${e.message}</span> ${infoBtn}`;
}
}
@@ -2895,14 +2921,23 @@
}
async function resetConversation() {
if (!confirm('Konversation leeren?\n\nDer Rolling-Window-Verlauf wird komplett verworfen. Destillierte Facts bleiben in der DB.')) return;
if (!confirm('Konversation komplett zurücksetzen?\n\n• ARIAs Rolling-Window (Brain) wird geleert — sie "vergisst" die letzten Turns\n• Chat-Anzeige in der Diagnostic wird geleert\n\nDestillierte Facts + andere Memories bleiben in der Vector-DB.')) return;
try {
const r = await fetch('/api/brain/conversation/reset', { method: 'POST' });
const d = await r.json();
if (d.ok) {
// Beides parallel — Brain Window + Diagnostic chat_backup
const [brainR, histR] = await Promise.all([
fetch('/api/brain/conversation/reset', { method: 'POST' }),
fetch('/api/chat-history-clear', { method: 'POST' }),
]);
const brainOk = brainR.ok;
const histOk = histR.ok;
if (brainOk && histOk) {
// Chat-View leeren (Server broadcasted das eh, aber sicherheitshalber)
if (chatBox) chatBox.innerHTML = '';
const fsBox = document.getElementById('chat-box-fs');
if (fsBox) fsBox.innerHTML = '';
loadBrainStatus();
} else {
alert('Reset fehlgeschlagen');
alert(`Reset teilweise fehlgeschlagen — Brain: ${brainOk ? 'OK' : 'fail'}, History: ${histOk ? 'OK' : 'fail'}`);
}
} catch (e) {
alert('Reset fehlgeschlagen: ' + e.message);
@@ -3136,6 +3171,78 @@
return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
}
// ── Generisches Info-Modal — Aufruf: openInfoModal('Titel', '<p>HTML...</p>') ──
function openInfoModal(title, html) {
const t = document.getElementById('info-modal-title');
const b = document.getElementById('info-modal-body');
const m = document.getElementById('info-modal');
if (!t || !b || !m) return;
t.textContent = title;
b.innerHTML = html;
m.classList.add('open');
}
function closeInfoModal() {
const m = document.getElementById('info-modal');
if (m) m.classList.remove('open');
}
// Vor-definierte Info-Blocks
const INFO_TEXTS = {
'brain-status': {
title: 'Gehirn — Status',
html: `
<p><strong>online / offline</strong> — ob der <code>aria-brain</code> Container erreichbar ist (HTTP GET /health).</p>
<p><strong>N Memories</strong> — Anzahl der Punkte in der Vector-DB. Beinhaltet alle Typen: identity, rule, preference, tool, skill, fact, conversation, reminder.</p>
<p><strong>Qdrant: aria-qdrant:6333</strong> — Hostname + Port des Vector-DB-Containers. Der Brain spricht intern dorthin.</p>
`,
},
'conversation': {
title: 'Konversation — wie funktioniert das?',
html: `
<p><strong>Rolling Window:</strong> ARIA "sieht" pro Anfrage nur die letzten N Turns einer einzelnen, durchgehenden Konversation. Kein Sessions, kein Multi-Thread.</p>
<ul>
<li><strong>Turns</strong> — Anzahl aller Nachrichten (User + ARIA) seit dem letzten Destillat oder Reset.</li>
<li><strong>Window: 50</strong> — die letzten 50 Turns wandern in den Prompt. Aelteste fallen raus, sobald die Schwelle ueberschritten ist.</li>
<li><strong>Schwelle: 60</strong> — bei mehr als 60 Turns triggert Brain automatisch das Destillat (die 30 aeltesten werden zu fact-Memories verdichtet, Token-Budget bleibt konstant).</li>
</ul>
<p><strong>⚗ Jetzt destillieren:</strong> manueller Trigger fuer das Destillat (kostet einen Claude-Call). Verdichtet die aeltesten 30 Turns zu Fakten + entfernt sie aus dem Window.</p>
<p><strong>🧹 Konversation komplett zuruecksetzen:</strong> leert beides — ARIAs Rolling-Window (Brain) UND die Chat-Anzeige hier (chat_backup.jsonl). Destillierte Facts + alle anderen Memories in der Vector-DB <em>bleiben</em>.</p>
<p style="margin-top:8px;color:#FFD60A;font-size:12px;">⚠ Falls "Turns: 0" obwohl du oben Chat-Eintraege siehst: chat_backup.jsonl (Anzeige) und conversation.jsonl (Brain-Kontext) sind getrennte Stores. Alte chat_backup-Eintraege koennen aus OpenClaw-Zeit stammen. Reset-Button leert beides.</p>
`,
},
'memories': {
title: 'Memories — Hot vs. Cold',
html: `
<p><strong>Pinned (Hot Memory)</strong> 📌 — landet bei JEDER Anfrage im System-Prompt. Hier gehoeren rein: Identitaet, Sicherheitsregeln, Benutzer-Praeferenzen, Tool-Freigaben, Kern-Skills.</p>
<p><strong>Cold Memory</strong> — semantisch durchsucht. Pro Anfrage werden die 5 aehnlichsten Punkte zur User-Frage in den Prompt eingehaengt.</p>
<p><strong>Typen:</strong></p>
<ul>
<li><strong>identity</strong> — wer ARIA ist (Name, Persoenlichkeit)</li>
<li><strong>rule</strong> — Sicherheits-/Werte-Regeln</li>
<li><strong>preference</strong> — User-Profile</li>
<li><strong>tool</strong> — Tool-Freigaben + Infrastruktur</li>
<li><strong>skill</strong> — Faehigkeiten (verlinkt mit /data/skills/)</li>
<li><strong>fact</strong> — Wissens-Fakten (oft aus Destillaten)</li>
<li><strong>conversation</strong> — destillierte Konversations-Erkenntnisse</li>
<li><strong>reminder</strong> — Termine, Aufgaben</li>
</ul>
<p><strong>Such-Feld:</strong> semantische Suche via Embedder + Qdrant. Findet sinngemaess, nicht nur Stichworte.</p>
`,
},
'bootstrap': {
title: 'Bootstrap & Migration — die drei Wege',
html: `
<p><strong>1. Aus brain-import/ migrieren</strong> 🔵 — Parser fuer die <code>.md</code>-Dateien im Repo (AGENT.md, USER.md, TOOLING.md). Schreibt sie als atomare pinned Memories. Idempotent — Re-Run ersetzt nur die Migration-Punkte, eigene Memories bleiben.</p>
<p><strong>2. Bootstrap-Snapshot</strong> 🟡 — kleines JSON, NUR die pinned Memories. Export = aktueller Stand als Datei. Import = ALLE aktuell pinned werden ersetzt. Cold Memory bleibt unangetastet.</p>
<p><strong>3. Komplettes Gehirn</strong> 🔴 — tar.gz mit allem (Memories + Skills + Qdrant-DB). Backup + Restore. Import ueberschreibt ALLES.</p>
`,
},
};
function showInfo(key) {
const cfg = INFO_TEXTS[key];
if (cfg) openInfoModal(cfg.title, cfg.html);
}
function brainExport() {
// Browser folgt der Download-Header-Antwort automatisch
window.location.href = '/api/brain-export';
+17
View File
@@ -1448,6 +1448,23 @@ const server = http.createServer((req, res) => {
}
});
return;
} else if (req.url === "/api/chat-history-clear" && req.method === "POST") {
// Leert die Diagnostic-Anzeige-History (chat_backup.jsonl).
// Brain's Rolling-Window (conversation.jsonl) ist davon unabhaengig —
// der Caller sollte zusaetzlich /api/brain/conversation/reset triggern.
log("warn", "server", "HTTP /api/chat-history-clear");
try {
const file = "/shared/config/chat_backup.jsonl";
if (fs.existsSync(file)) fs.unlinkSync(file);
// Broadcast: alle Browser-Clients sollen ihre lokale Chat-View leeren
broadcast({ type: "chat_history", messages: [] });
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: true }));
} catch (err) {
res.writeHead(500, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: false, error: err.message }));
}
return;
} else if (req.url === "/api/wipe-all" && req.method === "POST") {
// Komplett-Reset — Gedaechtnis, Stimmen, Config alle weg. SSH-Keys
// und .env bleiben, RVS-Anbindung bleibt. Brain + Qdrant werden