fix(diag): Pinned-/Type-Filter wirkt jetzt auch bei aktiver Suche

Bug: runBrainSearch und runAdvancedSearch ignorierten den
brain-filter-pinned Dropdown — egal ob "Nur Pinned" oder "Nur Cold"
gewaehlt war, kam immer alles was die Such-Kriterien erfuellte.
Plus: Dropdown-onchange rief loadBrainMemoryList und brach damit
die Suche ab statt sie mit dem neuen Filter neu auszufuehren.

Fix:
- Neue Helfer brainSearchActive() (erkennt single/advanced/none) und
  applyPinnedFilter() (client-side Filter nach 'all'/'pinned'/'cold').
- runBrainSearch + runAdvancedSearch wenden applyPinnedFilter nach
  dem Backend-Hit an. Info-Box zeigt zusaetzlich an wenn
  Pinned-Filter aktiv war ("... · 📌 nur pinned"), bei 0 Treffern
  auch der unfiltered Count fuer Debug ("X Treffer ohne Pinned-Filter").
- Type+Pinned-Dropdowns onchange → onBrainFiltersChanged: bei
  aktiver Suche re-search, sonst loadBrainMemoryList.

Backend bleibt unveraendert (include_pinned all-or-none reicht —
Feinheit "nur pinned" macht der Client).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-13 01:31:17 +02:00
parent 5f96ace469
commit 24cf40293a
+56 -18
View File
@@ -834,7 +834,7 @@
<option value="semantic">🧠 Semantisch</option> <option value="semantic">🧠 Semantisch</option>
</select> </select>
<button class="btn secondary" onclick="runBrainSearch()" style="padding:4px 12px;font-size:11px;">Suchen</button> <button class="btn secondary" onclick="runBrainSearch()" style="padding:4px 12px;font-size:11px;">Suchen</button>
<select id="brain-filter-type" onchange="loadBrainMemoryList()" <select id="brain-filter-type" onchange="onBrainFiltersChanged()"
style="background:#080810;color:#E0E0F0;border:1px solid #1E1E2E;padding:6px;border-radius:4px;font-family:inherit;font-size:11px;"> style="background:#080810;color:#E0E0F0;border:1px solid #1E1E2E;padding:6px;border-radius:4px;font-family:inherit;font-size:11px;">
<option value="">Alle Typen</option> <option value="">Alle Typen</option>
<option value="identity">Identität</option> <option value="identity">Identität</option>
@@ -846,7 +846,7 @@
<option value="conversation">Konversation</option> <option value="conversation">Konversation</option>
<option value="reminder">Reminder</option> <option value="reminder">Reminder</option>
</select> </select>
<select id="brain-filter-pinned" onchange="loadBrainMemoryList()" <select id="brain-filter-pinned" onchange="onBrainFiltersChanged()"
style="background:#080810;color:#E0E0F0;border:1px solid #1E1E2E;padding:6px;border-radius:4px;font-family:inherit;font-size:11px;"> style="background:#080810;color:#E0E0F0;border:1px solid #1E1E2E;padding:6px;border-radius:4px;font-family:inherit;font-size:11px;">
<option value="all">Pinned + Cold</option> <option value="all">Pinned + Cold</option>
<option value="pinned">📌 Nur Pinned</option> <option value="pinned">📌 Nur Pinned</option>
@@ -3639,19 +3639,23 @@
} }
} }
const hits = Array.from(combined).map(id => brainMemoryCache[id]).filter(Boolean); let hits = Array.from(combined).map(id => brainMemoryCache[id]).filter(Boolean);
const totalHits = hits.length;
hits = applyPinnedFilter(hits);
brainSearchIds = hits.map(m => m.id); brainSearchIds = hits.map(m => m.id);
const desc = active.map((a, i) => i === 0 ? `"${a.term}"` : ` ${a.op} "${a.term}"`).join(''); const desc = active.map((a, i) => i === 0 ? `"${a.term}"` : ` ${a.op} "${a.term}"`).join('');
const pinnedFilter = document.getElementById('brain-filter-pinned')?.value || 'all';
const pinnedLabel = pinnedFilter === 'pinned' ? ' · 📌 nur pinned'
: pinnedFilter === 'cold' ? ' · nur cold'
: '';
if (info) { if (info) {
info.style.display = 'block'; info.style.display = 'block';
const filterDesc = (typeFilter ? ` · Typ=${escapeHtml(typeFilter)}` : '') + pinnedLabel;
if (hits.length === 0) { if (hits.length === 0) {
info.innerHTML = `🔍 Keine Treffer fuer ${escapeHtml(desc)}` + const extra = totalHits > 0 ? ` (${totalHits} Treffer ohne Pinned-Filter)` : '';
(typeFilter ? ` · Typ=${escapeHtml(typeFilter)}` : '') + info.innerHTML = `🔍 Keine Treffer fuer ${escapeHtml(desc)}${filterDesc}${extra} · 📝 wortlich, Boolean-Kombi`;
` · 📝 wortlich, Boolean-Kombi`;
} else { } else {
info.innerHTML = `🔍 ${hits.length} Treffer fuer ${escapeHtml(desc)}` + info.innerHTML = `🔍 ${hits.length} Treffer fuer ${escapeHtml(desc)}${filterDesc} · 📝 wortlich, Boolean-Kombi`;
(typeFilter ? ` · Typ=${escapeHtml(typeFilter)}` : '') +
` · 📝 wortlich, Boolean-Kombi`;
} }
} }
renderBrainList(hits, true); renderBrainList(hits, true);
@@ -3663,6 +3667,34 @@
} }
} }
/** True wenn aktuell eine Search-Ansicht aktiv ist (Single oder Advanced).
* Wird vom Pinned/Type-Filter-onchange genutzt um statt loadBrainMemoryList
* die Suche neu auszufuehren — damit Filter auch bei aktiver Suche greifen. */
function brainSearchActive() {
const q = (document.getElementById('brain-search')?.value || '').trim();
if (q) return 'single';
const hasAdv = (advRows || []).some(r => (r.term || '').trim());
return hasAdv ? 'advanced' : null;
}
/** Wird vom Type+Pinned-Dropdown onchange gerufen. Bei aktiver Suche
* re-search ausfuehren, sonst Liste neu laden. */
function onBrainFiltersChanged() {
const which = brainSearchActive();
if (which === 'single') runBrainSearch();
else if (which === 'advanced') runAdvancedSearch();
else loadBrainMemoryList();
}
/** Filtert eine Liste von Memories nach dem pinned-Dropdown-Wert.
* 'all' = alles durchlassen, 'pinned' = nur pinned, 'cold' = nur cold. */
function applyPinnedFilter(items) {
const v = document.getElementById('brain-filter-pinned')?.value || 'all';
if (v === 'pinned') return items.filter(m => m.pinned);
if (v === 'cold') return items.filter(m => !m.pinned);
return items;
}
async function runBrainSearch() { async function runBrainSearch() {
const q = (document.getElementById('brain-search').value || '').trim(); const q = (document.getElementById('brain-search').value || '').trim();
const info = document.getElementById('brain-search-info'); const info = document.getElementById('brain-search-info');
@@ -3673,17 +3705,18 @@
return; return;
} }
const typeFilter = document.getElementById('brain-filter-type').value; const typeFilter = document.getElementById('brain-filter-type').value;
const pinnedFilter = document.getElementById('brain-filter-pinned')?.value || 'all';
const mode = (document.getElementById('brain-search-mode')?.value) || 'text'; const mode = (document.getElementById('brain-search-mode')?.value) || 'text';
let url, modeLabel; let url, modeLabel;
if (mode === 'semantic') { if (mode === 'semantic') {
// Embedder-basiert, mit Score-Threshold gegen Rauschen // Embedder-basiert, mit Score-Threshold gegen Rauschen
const params = new URLSearchParams({ q, k: '10', include_pinned: 'true', score_threshold: '0.30' }); const params = new URLSearchParams({ q, k: '20', include_pinned: 'true', score_threshold: '0.30' });
if (typeFilter) params.set('type', typeFilter); if (typeFilter) params.set('type', typeFilter);
url = '/api/brain/memory/search?' + params.toString(); url = '/api/brain/memory/search?' + params.toString();
modeLabel = '🧠 semantisch (Score ≥ 0.30)'; modeLabel = '🧠 semantisch (Score ≥ 0.30)';
} else { } else {
// Volltext-Substring (case-insensitive) — findet exakte Begriffe // Volltext-Substring (case-insensitive) — findet exakte Begriffe
const params = new URLSearchParams({ q, k: '50', include_pinned: 'true' }); const params = new URLSearchParams({ q, k: '100', include_pinned: 'true' });
if (typeFilter) params.set('type', typeFilter); if (typeFilter) params.set('type', typeFilter);
url = '/api/brain/memory/search-text?' + params.toString(); url = '/api/brain/memory/search-text?' + params.toString();
modeLabel = '📝 wortlich (Substring)'; modeLabel = '📝 wortlich (Substring)';
@@ -3691,19 +3724,24 @@
try { try {
const r = await fetch(url); const r = await fetch(url);
if (!r.ok) throw new Error('HTTP ' + r.status); if (!r.ok) throw new Error('HTTP ' + r.status);
const hits = await r.json(); let hits = await r.json();
hits.forEach(m => { brainMemoryCache[m.id] = m; }); hits.forEach(m => { brainMemoryCache[m.id] = m; });
// Pinned-Filter clientseitig anwenden — Backend kennt nur include_pinned
// (all-or-none), wir brauchen aber feiner "nur pinned" / "nur cold".
const totalHits = hits.length;
hits = applyPinnedFilter(hits);
brainSearchIds = hits.map(m => m.id); brainSearchIds = hits.map(m => m.id);
const pinnedLabel = pinnedFilter === 'pinned' ? ' · 📌 nur pinned'
: pinnedFilter === 'cold' ? ' · nur cold'
: '';
if (info) { if (info) {
info.style.display = 'block'; info.style.display = 'block';
const filterDesc = (typeFilter ? ` · Typ=${escapeHtml(typeFilter)}` : '') + pinnedLabel;
if (hits.length === 0) { if (hits.length === 0) {
info.innerHTML = `🔍 Keine Treffer für "${escapeHtml(q)}"` + const extra = totalHits > 0 ? ` (${totalHits} Treffer ohne Pinned-Filter)` : '';
(typeFilter ? ` · Typ=${escapeHtml(typeFilter)}` : '') + info.innerHTML = `🔍 Keine Treffer für "${escapeHtml(q)}"${filterDesc}${extra} · ${modeLabel}.`;
` · ${modeLabel}. Anderen Begriff probieren oder ✕ rechts um Suche zu schliessen.`;
} else { } else {
info.innerHTML = `🔍 ${hits.length} Treffer für "${escapeHtml(q)}"` + info.innerHTML = `🔍 ${hits.length} Treffer für "${escapeHtml(q)}"${filterDesc} · ${modeLabel}`;
(typeFilter ? ` · Typ=${escapeHtml(typeFilter)}` : '') +
` · ${modeLabel}`;
} }
} }
renderBrainList(hits, true); renderBrainList(hits, true);