// Claude Desktop — Wissensbasis (claude-db) // Direkte MySQL-Anbindung zur zentralen Wissensdatenbank use mysql_async::{Pool, prelude::*}; use serde::{Deserialize, Serialize}; /// Verbindungskonfiguration const MYSQL_HOST: &str = "192.168.155.1"; const MYSQL_PORT: u16 = 3306; const MYSQL_USER: &str = "claude"; const MYSQL_PASS: &str = "claude"; const MYSQL_DB: &str = "claude"; /// Wissenseintrag aus der knowledge-Tabelle #[derive(Debug, Clone, Serialize, Deserialize)] pub struct KnowledgeEntry { pub id: i64, pub category: String, pub title: String, pub content: String, pub tags: Option, pub priority: i32, pub status: String, pub related_ids: Option, pub source: Option, pub created_at: String, pub updated_at: String, } /// Neuer Wissenseintrag (ohne id, timestamps) #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NewKnowledge { pub category: String, pub title: String, pub content: String, pub tags: Option, pub priority: Option, pub source: Option, } /// Suchergebnis mit Relevanz-Score #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SearchResult { pub entry: KnowledgeEntry, pub relevance: f64, } /// Verfügbare Kategorien #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CategoryInfo { pub name: String, pub count: i64, } // ============ Hilfsfunktionen ============ /// Erstellt einen MySQL Connection-Pool fn create_pool() -> Pool { let url = format!( "mysql://{}:{}@{}:{}/{}", MYSQL_USER, MYSQL_PASS, MYSQL_HOST, MYSQL_PORT, MYSQL_DB ); Pool::new(url.as_str()) } // ============ Tauri Commands ============ /// Wissensbasis durchsuchen (Volltext) #[tauri::command] pub async fn search_knowledge( query: String, category: Option, limit: Option, ) -> Result, String> { let pool = create_pool(); let mut conn = pool.get_conn().await.map_err(|e| e.to_string())?; let limit = limit.unwrap_or(20); // Volltext-Suche mit optionalem Kategorie-Filter let results: Vec = if let Some(cat) = category { conn.exec_map( r#"SELECT id, category, title, content, tags, priority, status, related_ids, source, created_at, updated_at, MATCH(title, content, tags) AGAINST(? IN NATURAL LANGUAGE MODE) as relevance FROM knowledge WHERE MATCH(title, content, tags) AGAINST(? IN NATURAL LANGUAGE MODE) AND category = ? ORDER BY relevance DESC LIMIT ?"#, (&query, &query, &cat, limit), |(id, category, title, content, tags, priority, status, related_ids, source, created_at, updated_at, relevance): (i64, String, String, String, Option, i32, String, Option, Option, String, String, f64)| { SearchResult { entry: KnowledgeEntry { id, category, title, content, tags, priority, status, related_ids, source, created_at, updated_at, }, relevance, } } ).await.map_err(|e| e.to_string())? } else { conn.exec_map( r#"SELECT id, category, title, content, tags, priority, status, related_ids, source, created_at, updated_at, MATCH(title, content, tags) AGAINST(? IN NATURAL LANGUAGE MODE) as relevance FROM knowledge WHERE MATCH(title, content, tags) AGAINST(? IN NATURAL LANGUAGE MODE) ORDER BY relevance DESC LIMIT ?"#, (&query, &query, limit), |(id, category, title, content, tags, priority, status, related_ids, source, created_at, updated_at, relevance): (i64, String, String, String, Option, i32, String, Option, Option, String, String, f64)| { SearchResult { entry: KnowledgeEntry { id, category, title, content, tags, priority, status, related_ids, source, created_at, updated_at, }, relevance, } } ).await.map_err(|e| e.to_string())? }; drop(conn); pool.disconnect().await.map_err(|e| e.to_string())?; println!("🔍 Wissensbasis-Suche '{}': {} Treffer", query, results.len()); Ok(results) } /// Wissenseintrag nach ID laden #[tauri::command] pub async fn get_knowledge(id: i64) -> Result, String> { let pool = create_pool(); let mut conn = pool.get_conn().await.map_err(|e| e.to_string())?; let result: Option = conn.exec_first( r#"SELECT id, category, title, content, tags, priority, status, related_ids, source, created_at, updated_at FROM knowledge WHERE id = ?"#, (id,) ).await.map_err(|e| e.to_string())? .map(|(id, category, title, content, tags, priority, status, related_ids, source, created_at, updated_at): (i64, String, String, String, Option, i32, String, Option, Option, String, String)| { KnowledgeEntry { id, category, title, content, tags, priority, status, related_ids, source, created_at, updated_at, } }); drop(conn); pool.disconnect().await.map_err(|e| e.to_string())?; Ok(result) } /// Neuen Wissenseintrag speichern ("Das merken") #[tauri::command] pub async fn save_knowledge(entry: NewKnowledge) -> Result { let pool = create_pool(); let mut conn = pool.get_conn().await.map_err(|e| e.to_string())?; let priority = entry.priority.unwrap_or(2); conn.exec_drop( r#"INSERT INTO knowledge (category, title, content, tags, priority, status, source, created_at, updated_at) VALUES (?, ?, ?, ?, ?, 'active', ?, NOW(), NOW())"#, (&entry.category, &entry.title, &entry.content, &entry.tags, priority, &entry.source) ).await.map_err(|e| e.to_string())?; let id: i64 = conn.last_insert_id().ok_or("Keine Insert-ID")? as i64; drop(conn); pool.disconnect().await.map_err(|e| e.to_string())?; println!("💾 Wissen gespeichert: {} (ID: {})", entry.title, id); Ok(id) } /// Alle Kategorien mit Anzahl der Einträge #[tauri::command] pub async fn get_knowledge_categories() -> Result, String> { let pool = create_pool(); let mut conn = pool.get_conn().await.map_err(|e| e.to_string())?; let categories: Vec = conn.query_map( r#"SELECT category, COUNT(*) as count FROM knowledge WHERE status = 'active' GROUP BY category ORDER BY count DESC"#, |(name, count): (String, i64)| CategoryInfo { name, count } ).await.map_err(|e| e.to_string())?; drop(conn); pool.disconnect().await.map_err(|e| e.to_string())?; Ok(categories) } /// Letzte N Wissenseinträge laden #[tauri::command] pub async fn get_recent_knowledge( limit: Option, category: Option, ) -> Result, String> { let pool = create_pool(); let mut conn = pool.get_conn().await.map_err(|e| e.to_string())?; let limit = limit.unwrap_or(10); let entries: Vec = if let Some(cat) = category { conn.exec_map( r#"SELECT id, category, title, content, tags, priority, status, related_ids, source, created_at, updated_at FROM knowledge WHERE status = 'active' AND category = ? ORDER BY updated_at DESC LIMIT ?"#, (&cat, limit), |(id, category, title, content, tags, priority, status, related_ids, source, created_at, updated_at): (i64, String, String, String, Option, i32, String, Option, Option, String, String)| { KnowledgeEntry { id, category, title, content, tags, priority, status, related_ids, source, created_at, updated_at, } } ).await.map_err(|e| e.to_string())? } else { conn.exec_map( r#"SELECT id, category, title, content, tags, priority, status, related_ids, source, created_at, updated_at FROM knowledge WHERE status = 'active' ORDER BY updated_at DESC LIMIT ?"#, (limit,), |(id, category, title, content, tags, priority, status, related_ids, source, created_at, updated_at): (i64, String, String, String, Option, i32, String, Option, Option, String, String)| { KnowledgeEntry { id, category, title, content, tags, priority, status, related_ids, source, created_at, updated_at, } } ).await.map_err(|e| e.to_string())? }; drop(conn); pool.disconnect().await.map_err(|e| e.to_string())?; Ok(entries) } /// Verbindung zur Wissensbasis testen #[tauri::command] pub async fn test_knowledge_connection() -> Result { let pool = create_pool(); let mut conn = pool.get_conn().await.map_err(|e| format!("Verbindungsfehler: {}", e))?; let count: i64 = conn.query_first("SELECT COUNT(*) FROM knowledge") .await .map_err(|e| e.to_string())? .unwrap_or(0); drop(conn); pool.disconnect().await.map_err(|e| e.to_string())?; println!("✅ Wissensbasis verbunden: {} Einträge", count); Ok(format!("Verbunden! {} Einträge in der Wissensbasis", count)) }