From 3d84feab6f671ac022743ab75af46a49249cba3b Mon Sep 17 00:00:00 2001 From: Eddy Date: Mon, 20 Apr 2026 22:02:23 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Phase=203=20Performance=20=E2=80=94=20K?= =?UTF-8?q?B-Cache,=20Bridge-Warmstart,=20maxTurns=20200?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - KB-Cache im RAM: Suchergebnisse 60s gecacht, kein MySQL-Roundtrip pro Nachricht - Bridge wird beim App-Start sofort gestartet (kein Cold-Start bei erster Nachricht) - Bridge-Start-Wait von 500ms auf 200ms reduziert - maxTurns von 25 auf 200 erhoeht (verhindert "maximum turns reached" bei komplexen Tasks) - invalidate_kb_cache Command fuer manuelles Cache-Leeren Co-Authored-By: Claude Opus 4.6 --- scripts/claude-bridge.js | 4 +- src-tauri/src/claude.rs | 8 ++-- src-tauri/src/knowledge.rs | 95 +++++++++++++++++++++++++++++++++++++- src-tauri/src/lib.rs | 11 +++++ 4 files changed, 111 insertions(+), 7 deletions(-) diff --git a/scripts/claude-bridge.js b/scripts/claude-bridge.js index 7233556..5336461 100644 --- a/scripts/claude-bridge.js +++ b/scripts/claude-bridge.js @@ -332,7 +332,7 @@ async function sendMessage(message, requestId, model = null, contextOverride = n model: useModel, promptLength: message.length, contextLength: useContext?.length || 0, - maxTurns: 25, + maxTurns: 200, resumeSessionId: resumeSessionId || null, }); @@ -373,7 +373,7 @@ async function sendMessage(message, requestId, model = null, contextOverride = n // Query-Optionen zusammenstellen const queryOptions = { model: useModel, - maxTurns: 25, + maxTurns: 200, abortController: activeAbort, }; diff --git a/src-tauri/src/claude.rs b/src-tauri/src/claude.rs index 44d5df4..240e1b8 100644 --- a/src-tauri/src/claude.rs +++ b/src-tauri/src/claude.rs @@ -390,8 +390,8 @@ pub async fn send_message(app: AppHandle, message: String) -> Result Result if needs_start { start_bridge(&app)?; - tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + tokio::time::sleep(tokio::time::Duration::from_millis(200)).await; } // Modell an Bridge senden @@ -603,7 +603,7 @@ pub async fn set_agent_mode(app: AppHandle, mode: String) -> Result, + ttl: Duration, +} + +impl KbCache { + fn new(ttl_secs: u64) -> Self { + Self { + entries: HashMap::new(), + ttl: Duration::from_secs(ttl_secs), + } + } + + fn get(&self, key: &str) -> Option<&str> { + if let Some(entry) = self.entries.get(key) { + if entry.created_at.elapsed() < self.ttl { + return Some(&entry.result); + } + } + None + } + + fn insert(&mut self, key: String, result: String) { + // Max 100 Eintraege — aelteste raus wenn voll + if self.entries.len() >= 100 { + self.evict_oldest(); + } + self.entries.insert(key, CacheEntry { result, created_at: Instant::now() }); + } + + fn evict_oldest(&mut self) { + if let Some(oldest_key) = self.entries.iter() + .min_by_key(|(_, v)| v.created_at) + .map(|(k, _)| k.clone()) + { + self.entries.remove(&oldest_key); + } + } + + fn invalidate_expired(&mut self) { + self.entries.retain(|_, v| v.created_at.elapsed() < self.ttl); + } +} + +// Globaler Cache — 60 Sekunden TTL +static KB_CACHE: std::sync::LazyLock> = + std::sync::LazyLock::new(|| Mutex::new(KbCache::new(60))); + /// Verbindungskonfiguration — aus ENV-Variablen, Fallback auf Defaults const MYSQL_PORT: u16 = 3306; @@ -168,7 +228,20 @@ pub async fn search_knowledge_internal(query: &str, limit: i32) -> Result Result { + // Cache-Key: query + limit + let cache_key = format!("{}:{}", search_query, limit); + + // Cache-Hit prüfen + { + let cache = KB_CACHE.lock().unwrap(); + if let Some(cached) = cache.get(&cache_key) { + println!("⚡ KB-Cache HIT für '{}'", &search_query[..search_query.len().min(40)]); + return Ok(cached.to_string()); + } + } + let pool = create_pool(); // Verbindung mit Timeout — DB nicht erreichbar soll nicht blockieren @@ -209,6 +282,9 @@ pub async fn search_knowledge_by_query(search_query: &str, limit: i32) -> Result if results.is_empty() { println!("🔍 KB-Hints für '{}': keine Treffer", &search_query[..search_query.len().min(40)]); + // Leere Ergebnisse auch cachen — verhindert wiederholte DB-Abfragen + let mut cache = KB_CACHE.lock().unwrap(); + cache.insert(cache_key, String::new()); return Ok(String::new()); } @@ -230,9 +306,15 @@ pub async fn search_knowledge_by_query(search_query: &str, limit: i32) -> Result hints.push("".to_string()); let block = hints.join("\n"); - println!("🔍 KB-Hints für '{}': {} Treffer, {} Bytes", + println!("🔍 KB-Hints für '{}': {} Treffer, {} Bytes (cached)", &search_query[..search_query.len().min(40)], results.len(), block.len()); + // In Cache speichern + { + let mut cache = KB_CACHE.lock().unwrap(); + cache.insert(cache_key, block.clone()); + } + Ok(block) } @@ -718,3 +800,14 @@ pub async fn auto_save_error_pattern( ) -> Result { save_error_pattern_to_kb(&error_hash, &error_message, &tool, occurrence_count).await } + +/// Phase 3: KB-Cache invalidieren (alle Eintraege loeschen) +/// Aufrufen wenn neue KB-Eintraege gespeichert wurden +#[tauri::command] +pub async fn invalidate_kb_cache() -> Result { + let mut cache = KB_CACHE.lock().unwrap(); + let count = cache.entries.len(); + cache.entries.clear(); + println!("🗑️ KB-Cache invalidiert ({} Eintraege geloescht)", count); + Ok(format!("{} Cache-Eintraege geloescht", count)) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index bf5a914..eb0c4e2 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -105,6 +105,8 @@ pub fn run() { knowledge::extract_message_keywords, knowledge::get_session_hints, knowledge::auto_save_error_pattern, + // Phase 3: KB-Cache + knowledge::invalidate_kb_cache, // Context-Management context::get_sticky_context, context::set_sticky_context, @@ -240,6 +242,15 @@ pub fn run() { // Lock-Datei erstellen (Instanz-Schutz + Update-Safety) update::create_lock_file(); + // Phase 3: Bridge sofort beim App-Start starten (kein Cold-Start bei erster Nachricht) + let bridge_handle = app.handle().clone(); + tauri::async_runtime::spawn(async move { + match claude::start_bridge(&bridge_handle) { + Ok(()) => println!("🔌 Bridge beim Start initialisiert (warm)"), + Err(e) => println!("⚠️ Bridge-Start beim App-Start fehlgeschlagen: {} (wird bei erster Nachricht erneut versucht)", e), + } + }); + Ok(()) }) .build(tauri::generate_context!())