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 class="modal-body" style="padding:16px;">
|
||||
<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;">
|
||||
<option value="identity">identity — Wer ARIA ist</option>
|
||||
<option value="rule">rule — Sicherheit / Werte / Normen</option>
|
||||
<option value="preference">preference — Benutzer-Praeferenzen</option>
|
||||
<option value="tool">tool — Tool-Freigaben</option>
|
||||
<option value="skill">skill — Faehigkeit / Workflow</option>
|
||||
<option value="fact" selected>fact — Wissens-Fakt</option>
|
||||
<option value="conversation">conversation — Aus Gespraech destilliert</option>
|
||||
<option value="reminder">reminder — Termin / Aufgabe</option>
|
||||
<option value="identity">identity — Wer ARIA ist (FEST im Prompt)</option>
|
||||
<option value="rule">rule — Sicherheit / Werte / Normen (FEST)</option>
|
||||
<option value="preference">preference — Benutzer-Praeferenzen (FEST)</option>
|
||||
<option value="tool">tool — Tool-Freigaben (FEST)</option>
|
||||
<option value="skill">skill — Faehigkeit / Workflow (FEST)</option>
|
||||
<option value="fact" selected>fact — Wissens-Fakt (Cold)</option>
|
||||
<option value="conversation">conversation — Aus Gespraech destilliert (Cold)</option>
|
||||
<option value="reminder">reminder — Termin / Aufgabe (Cold)</option>
|
||||
</select>
|
||||
<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">
|
||||
<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>
|
||||
<label style="display:block;font-size:11px;color:#8888AA;margin-bottom:4px;">Kategorie (frei, optional)</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">
|
||||
<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" 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>
|
||||
<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;">
|
||||
@@ -3466,6 +3470,56 @@
|
||||
};
|
||||
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) {
|
||||
const pin = m.pinned ? '📌 ' : '';
|
||||
const preview = (m.content || '').slice(0, 140).replace(/\n/g, ' ');
|
||||
@@ -3483,21 +3537,37 @@
|
||||
</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) {
|
||||
const el = document.getElementById('brain-memory-list');
|
||||
if (!el) return;
|
||||
|
||||
// Auto-Suggest-Datalist mit allen existierenden Categories aktualisieren
|
||||
_updateCategoryDatalist(items);
|
||||
|
||||
if (isSearchResult) {
|
||||
// Such-Treffer: in Aehnlichkeits-Reihenfolge, kein Type-Gruppieren
|
||||
const html = items.map(m => renderMemoryRow(m, true)).join('');
|
||||
el.innerHTML = html || '(Keine Treffer)';
|
||||
return;
|
||||
}
|
||||
// Normale Liste: nach Type gruppieren
|
||||
// Normale Liste: nach Type gruppieren, Header klappbar
|
||||
const byType = {};
|
||||
items.forEach(m => { (byType[m.type] = byType[m.type] || []).push(m); });
|
||||
const html = BRAIN_TYPE_ORDER.flatMap(t => {
|
||||
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('');
|
||||
return [heading, rows];
|
||||
}).join('');
|
||||
@@ -3505,12 +3575,28 @@
|
||||
const extraTypes = Object.keys(byType).filter(t => !BRAIN_TYPE_ORDER.includes(t));
|
||||
let extra = '';
|
||||
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 += byType[t].map(m => renderMemoryRow(m, false)).join('');
|
||||
extra += _brainTypeHeading(t, byType[t].length);
|
||||
if (!brainCollapsedTypes.has(t)) {
|
||||
extra += byType[t].map(m => renderMemoryRow(m, false)).join('');
|
||||
}
|
||||
}
|
||||
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 ───────────────────────────────────
|
||||
|
||||
function openMemoryModal(id) {
|
||||
|
||||
Reference in New Issue
Block a user