diff --git a/src-tauri/src/knowledge.rs b/src-tauri/src/knowledge.rs index 2518538..0388191 100644 --- a/src-tauri/src/knowledge.rs +++ b/src-tauri/src/knowledge.rs @@ -193,6 +193,17 @@ const STOP_WORDS: &[&str] = &[ "schau", "guck", "check", "prüf", "teste", "versuch", "können", "müssen", "sollen", "wollen", "dürfen", "noch", "schon", "gerade", "gleich", "erstmal", "nochmal", + // Generische Verben/Adjektive die keine guten Suchbegriffe sind + "bleibt", "bleiben", "blieb", "kommt", "kommen", "kam", "macht", "machen", + "steht", "stehen", "stand", "liegt", "liegen", "lag", "nimmt", "nehmen", + "sieht", "sehen", "sah", "findet", "finden", "fand", "brauche", "braucht", + "offen", "öffnet", "öffnen", "geöffnet", "geschlossen", "schließen", "schließt", + "rechts", "links", "oben", "unten", "vorne", "hinten", + "neue", "neuer", "neues", "neuem", "neuen", "alte", "alter", "altes", + "ganze", "ganzen", "ganzer", "ganzes", "selbe", "selben", "selber", "selbes", + "gleiche", "gleichen", "gleicher", "gleiches", "andere", "anderen", "anderer", + "kaputt", "richtig", "fertig", "bereit", "leer", + "wenn", "weil", "damit", "dass", "sobald", ]; // ============ Konzept-Erkennung (wie Google/Facebook Textanalyse) ============ @@ -585,10 +596,23 @@ async fn search_knowledge_filtered(search_query: &str, limit: usize, project: &O return Ok(String::new()); } + // Relevanz-Schwelle: Ergebnisse mit zu niedrigem Score rausfiltern. + // MySQL FULLTEXT NATURAL LANGUAGE MODE gibt Scores > 0 zurück, + // aber generische Einzel-Wort-Treffer haben oft Score < 2. + let min_relevance = 1.5; + let relevant: Vec<_> = results.into_iter() + .filter(|(_, _, _, _, _, _, relevance)| *relevance >= min_relevance) + .collect(); + + if relevant.is_empty() { + println!("🔍 Keine Hints über Relevanz-Schwelle ({:.1})", min_relevance); + return Ok(String::new()); + } + // Bereits gezeigte IDs filtern let filtered: Vec<_> = { let topic = SESSION_TOPIC.lock().unwrap(); - results.into_iter() + relevant.into_iter() .filter(|(id, _, _, _, _, _, _)| !topic.shown_ids.contains(id)) .take(limit) .collect() diff --git a/src/lib/components/MessageList.svelte b/src/lib/components/MessageList.svelte index 4835edf..fb52527 100644 --- a/src/lib/components/MessageList.svelte +++ b/src/lib/components/MessageList.svelte @@ -4,14 +4,14 @@ // Auto-Scroll nicht mehr zum Ende. Stattdessen erscheint ein // Back-to-Bottom-Button. // - // Phase 9.1: ResizeObserver am Container — feuert auch bei - // Tool-Card-Slide-In, Diff-Aufklappen, Markdown-Code-Blocks. Tracker - // liest jetzt zusaetzlich die Anzahl Tool-Calls. + // Phase 9.2: MutationObserver statt ResizeObserver — feuert bei jeder + // DOM-Aenderung (neue Messages, WorkingIndicator, Tool-Cards, Markdown). + // $effect-Tracker scrollt nach tick() damit DOM schon gerendert ist. import { messages, isProcessing, type Message as ChatMessage } from '$lib/stores'; import MessageItem from './Message.svelte'; import WorkingIndicator from './WorkingIndicator.svelte'; - import { onMount, onDestroy } from 'svelte'; + import { onMount, onDestroy, tick } from 'svelte'; interface Props { sessionId?: string | null; @@ -34,7 +34,7 @@ let userScrolledUp = $state(false); let autoScrolling = false; // Guard: ignoriert onscroll waehrend wir selbst scrollen let autoScrollTimer: ReturnType | null = null; - let resizeObs: ResizeObserver | null = null; + let mutationObs: MutationObserver | null = null; // Working-Indicator: zeigen wenn Claude verarbeitet, aber noch keine // Assistant-Tokens da sind (vor erstem Stream + waehrend Tool-Calls). @@ -96,9 +96,9 @@ scrollToBottom(true); } - // Reactive-Tracker: deckt jetzt auch Tool-Calls ab. Sobald sich die - // Anzahl Tool-Calls in der letzten Message aendert (Slide-In, Status - // running→done), wird Auto-Scroll getriggert. + // Reactive-Tracker: deckt Messages, Tool-Calls, Parts und Processing ab. + // Nach tick() scrollen, damit DOM-Aenderungen (WorkingIndicator, neue + // Messages) schon gerendert sind bevor scrollHeight gelesen wird. $effect(() => { const last = $messages[$messages.length - 1]; const _trackers = [ @@ -106,7 +106,6 @@ $isProcessing, last?.content?.length ?? 0, last?.parts?.length ?? 0, - // Letzten Part tracken — bei Streaming waechst dessen content (last?.parts && last.parts.length > 0) ? last.parts[last.parts.length - 1]?.content?.length ?? 0 : 0, @@ -114,7 +113,7 @@ last?.toolCalls?.map((t) => t.status).join(',') ?? '', ]; void _trackers; - scrollToBottom(); + tick().then(() => scrollToBottom()); }); // Wenn der User eine neue Message schreibt und die Antwort ankommt, soll @@ -146,35 +145,27 @@ }); onMount(() => { - // ResizeObserver fuer den Container: feuert wenn sich die Hoehe - // aendert (Tool-Card aufklappen, Diff-Erweitern, Code-Block rendern). - // Ohne das wuerde der Stream "abgehaengt" weil $effect nur bei - // Content-Length-Aenderung greift. - if (container && typeof ResizeObserver !== 'undefined') { - resizeObs = new ResizeObserver(() => { + // MutationObserver: feuert bei JEDER DOM-Aenderung im Container — + // neue Messages, WorkingIndicator ein/aus, Tool-Card-Expansion, + // Markdown-Rendering, Diff-Aufklappen. Robuster als ResizeObserver + // (Container-Groesse ist bei overflow:auto konstant, nur scrollHeight + // waechst — ResizeObserver feuert da nicht). + if (container) { + mutationObs = new MutationObserver(() => { if (!userScrolledUp) scrollToBottom(); }); - // Den letzten Child beobachten, nicht den Container selbst — - // Container-Groesse ist konstant, sein Inhalt waechst. - const inner = container.firstElementChild; - if (inner) { - // Alle direkten Kinder beobachten ist zu teuer. Ein Wrapper - // reicht — der Container hat als Direct-Child die Message- - // Liste, und sein scrollHeight aendert sich passend. - // Wir beobachten den Container selbst — ResizeObserver - // feuert auch wenn der Inhalt waechst (clientHeight stays, - // scrollHeight grows → checkScroll triggert). - } - // Robusteste Variante: Container observed, plus Mutation-Observer - // fuer Content-Aenderungen. - resizeObs.observe(container); + mutationObs.observe(container, { + childList: true, + subtree: true, + characterData: true, + }); } }); onDestroy(() => { - if (resizeObs) { - resizeObs.disconnect(); - resizeObs = null; + if (mutationObs) { + mutationObs.disconnect(); + mutationObs = null; } releaseAutoScroll(); }); diff --git a/src/lib/ui/Drawer.svelte b/src/lib/ui/Drawer.svelte index 22a7352..933cc66 100644 --- a/src/lib/ui/Drawer.svelte +++ b/src/lib/ui/Drawer.svelte @@ -1,6 +1,6 @@ {#if open} -