feat(diag): Gehirn-Tab — klappbare Type-Header + Category-AutoSuggest + Info-Modal
UX im Memory-Browser geschaerft, Stefan-Wunsch: 1. Klappbare Type-Gruppen: Jeder Type-Header (Identität, Regeln, ...) hat jetzt einen ▼/▶ Indikator und reagiert auf Click. Eingeklappte Sektionen werden in localStorage gemerkt — bleiben ueber Reloads stabil. 2. Category-AutoSuggest: Das Kategorie-Feld im Neu/Edit-Modal hat jetzt ein <datalist> mit allen schon in der DB existierenden Categories als Vorschlag. Neue Categories sind weiterhin frei eintippbar. Liste wird bei jedem renderBrainList-Aufruf aus dem Cache aktualisiert. 3. Info-Button (ℹ) neben dem Typ-Dropdown: Erklaert welche Types FEST im System-Prompt eine eigene Sektion bekommen (identity/rule/preference/tool/skill — Hot Memory) und welche nur via semantischer Cold-Search reinkommen (fact/ conversation/reminder). Konsistent mit prompts.py:TYPE_HEADINGS. Auch dokumentiert dass Category ein freier Tag ist und den Prompt nicht direkt beeinflusst. Type-Dropdown-Labels selbst zeigen jetzt (FEST) / (Cold) als Hinweis. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+101
-15
@@ -988,23 +988,27 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body" style="padding:16px;">
|
<div class="modal-body" style="padding:16px;">
|
||||||
<input type="hidden" id="memory-edit-id" value="">
|
<input type="hidden" id="memory-edit-id" value="">
|
||||||
<label style="display:block;font-size:11px;color:#8888AA;margin-bottom:4px;">Typ</label>
|
<label style="display:flex;align-items:center;gap:6px;font-size:11px;color:#8888AA;margin-bottom:4px;">
|
||||||
|
<span>Typ</span>
|
||||||
|
<button type="button" onclick="showBrainTypeInfo()" title="Was bedeuten die Typen?" style="background:none;border:1px solid #0096FF;color:#0096FF;border-radius:50%;width:16px;height:16px;font-size:10px;line-height:14px;padding:0;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;">ℹ</button>
|
||||||
|
</label>
|
||||||
<select id="memory-type" style="width:100%;background:#0D0D1A;color:#E0E0F0;border:1px solid #1E1E2E;padding:6px;border-radius:4px;font-family:inherit;margin-bottom:10px;">
|
<select id="memory-type" style="width:100%;background:#0D0D1A;color:#E0E0F0;border:1px solid #1E1E2E;padding:6px;border-radius:4px;font-family:inherit;margin-bottom:10px;">
|
||||||
<option value="identity">identity — Wer ARIA ist</option>
|
<option value="identity">identity — Wer ARIA ist (FEST im Prompt)</option>
|
||||||
<option value="rule">rule — Sicherheit / Werte / Normen</option>
|
<option value="rule">rule — Sicherheit / Werte / Normen (FEST)</option>
|
||||||
<option value="preference">preference — Benutzer-Praeferenzen</option>
|
<option value="preference">preference — Benutzer-Praeferenzen (FEST)</option>
|
||||||
<option value="tool">tool — Tool-Freigaben</option>
|
<option value="tool">tool — Tool-Freigaben (FEST)</option>
|
||||||
<option value="skill">skill — Faehigkeit / Workflow</option>
|
<option value="skill">skill — Faehigkeit / Workflow (FEST)</option>
|
||||||
<option value="fact" selected>fact — Wissens-Fakt</option>
|
<option value="fact" selected>fact — Wissens-Fakt (Cold)</option>
|
||||||
<option value="conversation">conversation — Aus Gespraech destilliert</option>
|
<option value="conversation">conversation — Aus Gespraech destilliert (Cold)</option>
|
||||||
<option value="reminder">reminder — Termin / Aufgabe</option>
|
<option value="reminder">reminder — Termin / Aufgabe (Cold)</option>
|
||||||
</select>
|
</select>
|
||||||
<label style="display:block;font-size:11px;color:#8888AA;margin-bottom:4px;">Titel</label>
|
<label style="display:block;font-size:11px;color:#8888AA;margin-bottom:4px;">Titel</label>
|
||||||
<input type="text" id="memory-title" style="width:100%;background:#0D0D1A;color:#E0E0F0;border:1px solid #1E1E2E;padding:6px;border-radius:4px;font-family:inherit;margin-bottom:10px;" placeholder="Kurze Ueberschrift">
|
<input type="text" id="memory-title" style="width:100%;background:#0D0D1A;color:#E0E0F0;border:1px solid #1E1E2E;padding:6px;border-radius:4px;font-family:inherit;margin-bottom:10px;" placeholder="Kurze Ueberschrift">
|
||||||
<label style="display:block;font-size:11px;color:#8888AA;margin-bottom:4px;">Inhalt</label>
|
<label style="display:block;font-size:11px;color:#8888AA;margin-bottom:4px;">Inhalt</label>
|
||||||
<textarea id="memory-content" rows="8" style="width:100%;background:#0D0D1A;color:#E0E0F0;border:1px solid #1E1E2E;padding:6px;border-radius:4px;font-family:inherit;resize:vertical;margin-bottom:10px;" placeholder="Der eigentliche Text — das wird embedded und durchsucht."></textarea>
|
<textarea id="memory-content" rows="8" style="width:100%;background:#0D0D1A;color:#E0E0F0;border:1px solid #1E1E2E;padding:6px;border-radius:4px;font-family:inherit;resize:vertical;margin-bottom:10px;" placeholder="Der eigentliche Text — das wird embedded und durchsucht."></textarea>
|
||||||
<label style="display:block;font-size:11px;color:#8888AA;margin-bottom:4px;">Kategorie (frei, optional)</label>
|
<label style="display:block;font-size:11px;color:#8888AA;margin-bottom:4px;">Kategorie (frei, optional — vorhandene werden vorgeschlagen)</label>
|
||||||
<input type="text" id="memory-category" style="width:100%;background:#0D0D1A;color:#E0E0F0;border:1px solid #1E1E2E;padding:6px;border-radius:4px;font-family:inherit;margin-bottom:10px;" placeholder="z.B. persoenlichkeit, sicherheit, infrastruktur">
|
<input type="text" id="memory-category" list="memory-category-suggestions" autocomplete="off" style="width:100%;background:#0D0D1A;color:#E0E0F0;border:1px solid #1E1E2E;padding:6px;border-radius:4px;font-family:inherit;margin-bottom:10px;" placeholder="z.B. persoenlichkeit, sicherheit, infrastruktur">
|
||||||
|
<datalist id="memory-category-suggestions"></datalist>
|
||||||
<label style="display:block;font-size:11px;color:#8888AA;margin-bottom:4px;">Tags (komma-getrennt)</label>
|
<label style="display:block;font-size:11px;color:#8888AA;margin-bottom:4px;">Tags (komma-getrennt)</label>
|
||||||
<input type="text" id="memory-tags" style="width:100%;background:#0D0D1A;color:#E0E0F0;border:1px solid #1E1E2E;padding:6px;border-radius:4px;font-family:inherit;margin-bottom:10px;" placeholder="rvs, voice, bug">
|
<input type="text" id="memory-tags" style="width:100%;background:#0D0D1A;color:#E0E0F0;border:1px solid #1E1E2E;padding:6px;border-radius:4px;font-family:inherit;margin-bottom:10px;" placeholder="rvs, voice, bug">
|
||||||
<label style="display:flex;align-items:center;gap:8px;color:#E0E0F0;font-size:13px;cursor:pointer;">
|
<label style="display:flex;align-items:center;gap:8px;color:#E0E0F0;font-size:13px;cursor:pointer;">
|
||||||
@@ -3466,6 +3470,56 @@
|
|||||||
};
|
};
|
||||||
const BRAIN_TYPE_ORDER = ['identity','rule','preference','tool','skill','fact','conversation','reminder'];
|
const BRAIN_TYPE_ORDER = ['identity','rule','preference','tool','skill','fact','conversation','reminder'];
|
||||||
|
|
||||||
|
// Welche Types sind FEST verdrahtet im System-Prompt-Build (prompts.py
|
||||||
|
// → TYPE_HEADINGS) — die anderen sind frei wachsende Memories die per
|
||||||
|
// semantischer Cold-Search reinkommen.
|
||||||
|
const BRAIN_TYPE_INFO = {
|
||||||
|
identity: { fixed: true, use: 'Pinned-Punkte landen unter "## Wer du bist" im System-Prompt — Selbstbild von ARIA, was sie als Wesen ausmacht.' },
|
||||||
|
rule: { fixed: true, use: 'Pinned-Punkte landen unter "## Sicherheitsregeln & Prinzipien" — harte Regeln (niemals X, immer Y).' },
|
||||||
|
preference: { fixed: true, use: 'Pinned-Punkte landen unter "## Benutzer-Praeferenzen" — wie Stefan kommunizieren / arbeiten will.' },
|
||||||
|
tool: { fixed: true, use: 'Pinned-Punkte landen unter "## Tool-Freigaben" — was ARIA selbst entscheiden / ausfuehren darf.' },
|
||||||
|
skill: { fixed: true, use: 'Pinned-Punkte landen unter "## Deine Skills" als Memory — getrennt von der echten Skills-Liste die aus /data/skills/ kommt.' },
|
||||||
|
fact: { fixed: false, use: 'Allgemeine Wissens-Fakten. Nicht in fester Sektion — kommen via semantischer Suche (Cold Memory) rein wenn relevant.' },
|
||||||
|
conversation: { fixed: false, use: 'Aus dem Konversations-Destillat automatisch entstandene Punkte (alte Turns → fact-aehnliche Memories). Cold Memory.' },
|
||||||
|
reminder: { fixed: false, use: 'Termine, Aufgaben, To-Dos die ARIA wissen soll. Cold Memory — fuer aktive Erinnerungen lieber einen Trigger anlegen.' },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Welche Type-Headings sind eingeklappt? Persistiert in localStorage.
|
||||||
|
let brainCollapsedTypes = (() => {
|
||||||
|
try { return new Set(JSON.parse(localStorage.getItem('aria_brain_collapsed_types') || '[]')); }
|
||||||
|
catch { return new Set(); }
|
||||||
|
})();
|
||||||
|
function persistCollapsedTypes() {
|
||||||
|
try { localStorage.setItem('aria_brain_collapsed_types', JSON.stringify(Array.from(brainCollapsedTypes))); } catch {}
|
||||||
|
}
|
||||||
|
function toggleBrainType(t) {
|
||||||
|
if (brainCollapsedTypes.has(t)) brainCollapsedTypes.delete(t);
|
||||||
|
else brainCollapsedTypes.add(t);
|
||||||
|
persistCollapsedTypes();
|
||||||
|
loadBrainMemoryList();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showBrainTypeInfo() {
|
||||||
|
const fixedItems = BRAIN_TYPE_ORDER
|
||||||
|
.filter(t => BRAIN_TYPE_INFO[t]?.fixed)
|
||||||
|
.map(t => `<li><strong>${BRAIN_TYPE_LABELS[t] || t}</strong> (<code>${t}</code>) — ${escapeHtml(BRAIN_TYPE_INFO[t].use)}</li>`)
|
||||||
|
.join('');
|
||||||
|
const freeItems = BRAIN_TYPE_ORDER
|
||||||
|
.filter(t => !BRAIN_TYPE_INFO[t]?.fixed)
|
||||||
|
.map(t => `<li><strong>${BRAIN_TYPE_LABELS[t] || t}</strong> (<code>${t}</code>) — ${escapeHtml(BRAIN_TYPE_INFO[t].use)}</li>`)
|
||||||
|
.join('');
|
||||||
|
openInfoModal('Memory-Typen', `
|
||||||
|
<p style="margin-top:0;">ARIA's Gedaechtnis ist nach <strong>Typ</strong> sortiert.
|
||||||
|
Pinned Punkte mit einem festen Typ landen direkt im System-Prompt (Hot Memory).
|
||||||
|
Alle anderen kommen via semantischer Suche rein wenn sie zum aktuellen Turn passen (Cold Memory, Top-5).</p>
|
||||||
|
<p style="margin-top:12px;color:#0096FF;"><strong>Feste Typen</strong> (haben eine eigene Sektion im System-Prompt)</p>
|
||||||
|
<ul style="margin:6px 0;padding-left:20px;">${fixedItems}</ul>
|
||||||
|
<p style="margin-top:12px;color:#0096FF;"><strong>Freie Typen</strong> (gehen nur als Cold Memory rein)</p>
|
||||||
|
<ul style="margin:6px 0;padding-left:20px;">${freeItems}</ul>
|
||||||
|
<p style="margin-top:12px;">Die <strong>Kategorie</strong> ist ein freier Tag und beeinflusst den Prompt nicht direkt — sie dient nur zum Filtern in der Diagnostic-Liste. Vorschlaege im Eingabefeld kommen aus existierenden Eintraegen, neue Namen sind erlaubt.</p>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
function renderMemoryRow(m, withScore) {
|
function renderMemoryRow(m, withScore) {
|
||||||
const pin = m.pinned ? '📌 ' : '';
|
const pin = m.pinned ? '📌 ' : '';
|
||||||
const preview = (m.content || '').slice(0, 140).replace(/\n/g, ' ');
|
const preview = (m.content || '').slice(0, 140).replace(/\n/g, ' ');
|
||||||
@@ -3483,21 +3537,37 @@
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _brainTypeHeading(t, count) {
|
||||||
|
const collapsed = brainCollapsedTypes.has(t);
|
||||||
|
const arrow = collapsed ? '▶' : '▼';
|
||||||
|
const label = BRAIN_TYPE_LABELS[t] || t;
|
||||||
|
// onclick wirft das Klappen-Event; user-select:none damit das Toggle nicht Text markiert
|
||||||
|
return `<div onclick="toggleBrainType('${t}')" style="margin-top:14px;color:#0096FF;font-weight:bold;font-size:11px;text-transform:uppercase;letter-spacing:0.5px;cursor:pointer;user-select:none;display:flex;align-items:center;gap:6px;padding:4px 0;">
|
||||||
|
<span style="font-size:9px;width:12px;">${arrow}</span>
|
||||||
|
<span>${escapeHtml(label)} (${count})</span>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
function renderBrainList(items, isSearchResult) {
|
function renderBrainList(items, isSearchResult) {
|
||||||
const el = document.getElementById('brain-memory-list');
|
const el = document.getElementById('brain-memory-list');
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|
||||||
|
// Auto-Suggest-Datalist mit allen existierenden Categories aktualisieren
|
||||||
|
_updateCategoryDatalist(items);
|
||||||
|
|
||||||
if (isSearchResult) {
|
if (isSearchResult) {
|
||||||
// Such-Treffer: in Aehnlichkeits-Reihenfolge, kein Type-Gruppieren
|
// Such-Treffer: in Aehnlichkeits-Reihenfolge, kein Type-Gruppieren
|
||||||
const html = items.map(m => renderMemoryRow(m, true)).join('');
|
const html = items.map(m => renderMemoryRow(m, true)).join('');
|
||||||
el.innerHTML = html || '(Keine Treffer)';
|
el.innerHTML = html || '(Keine Treffer)';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Normale Liste: nach Type gruppieren
|
// Normale Liste: nach Type gruppieren, Header klappbar
|
||||||
const byType = {};
|
const byType = {};
|
||||||
items.forEach(m => { (byType[m.type] = byType[m.type] || []).push(m); });
|
items.forEach(m => { (byType[m.type] = byType[m.type] || []).push(m); });
|
||||||
const html = BRAIN_TYPE_ORDER.flatMap(t => {
|
const html = BRAIN_TYPE_ORDER.flatMap(t => {
|
||||||
if (!byType[t]) return [];
|
if (!byType[t]) return [];
|
||||||
const heading = `<div style="margin-top:14px;color:#0096FF;font-weight:bold;font-size:11px;text-transform:uppercase;letter-spacing:0.5px;">${BRAIN_TYPE_LABELS[t] || t} (${byType[t].length})</div>`;
|
const heading = _brainTypeHeading(t, byType[t].length);
|
||||||
|
if (brainCollapsedTypes.has(t)) return [heading];
|
||||||
const rows = byType[t].map(m => renderMemoryRow(m, false)).join('');
|
const rows = byType[t].map(m => renderMemoryRow(m, false)).join('');
|
||||||
return [heading, rows];
|
return [heading, rows];
|
||||||
}).join('');
|
}).join('');
|
||||||
@@ -3505,12 +3575,28 @@
|
|||||||
const extraTypes = Object.keys(byType).filter(t => !BRAIN_TYPE_ORDER.includes(t));
|
const extraTypes = Object.keys(byType).filter(t => !BRAIN_TYPE_ORDER.includes(t));
|
||||||
let extra = '';
|
let extra = '';
|
||||||
for (const t of extraTypes) {
|
for (const t of extraTypes) {
|
||||||
extra += `<div style="margin-top:14px;color:#0096FF;font-weight:bold;font-size:11px;text-transform:uppercase;">${escapeHtml(t)} (${byType[t].length})</div>`;
|
extra += _brainTypeHeading(t, byType[t].length);
|
||||||
extra += byType[t].map(m => renderMemoryRow(m, false)).join('');
|
if (!brainCollapsedTypes.has(t)) {
|
||||||
|
extra += byType[t].map(m => renderMemoryRow(m, false)).join('');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
el.innerHTML = (html + extra) || '(Keine bekannten Typen gefunden)';
|
el.innerHTML = (html + extra) || '(Keine bekannten Typen gefunden)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _updateCategoryDatalist(items) {
|
||||||
|
const dl = document.getElementById('memory-category-suggestions');
|
||||||
|
if (!dl) return;
|
||||||
|
const set = new Set();
|
||||||
|
// Aus dem Cache UND aus den uebergebenen items beziehen — der Cache
|
||||||
|
// kann Such-Treffer enthalten, items kann ein gefilteter View sein.
|
||||||
|
Object.values(brainMemoryCache).concat(items || []).forEach(m => {
|
||||||
|
if (m && m.category && typeof m.category === 'string') set.add(m.category.trim());
|
||||||
|
});
|
||||||
|
const opts = Array.from(set).filter(Boolean).sort().map(c =>
|
||||||
|
`<option value="${escapeHtml(c)}">`).join('');
|
||||||
|
dl.innerHTML = opts;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Memory CRUD ───────────────────────────────────
|
// ── Memory CRUD ───────────────────────────────────
|
||||||
|
|
||||||
function openMemoryModal(id) {
|
function openMemoryModal(id) {
|
||||||
|
|||||||
Reference in New Issue
Block a user