All checks were successful
Build AppImage / build (push) Successful in 8m18s
- AnimatedFileEdit.svelte: neue Komponente fuer animierte Datei-Aenderungen im Praesentation-Fenster - Schulungsmodus: 5-Stufen-Speed-Regler (Lehrer 10cps bis Data-Modus instant+Glow) - Schulungsmodus: Live-Catchup-Button, Auto-Weiter nach Slide-Abschluss - ChatPanel: Permission-Mode-Toggle links vom Textfeld (default/acceptEdits/bypassPermissions) - ApprovalBar: Floating-Card mit blauem Glow, Buttons umbenannt (Anwenden/Ablehnen) - MessageList: Scroll-Guard mit scrollend-Event + 700ms-Fallback statt doppeltem rAF - MessageList: User-Nachrichten scrollen sofort nach unten (requestAnimationFrame + force) - Message.svelte: MessagePart[]-basiertes Rendering fuer chronologische Reihenfolge - events.ts: file-change sendet Slide an Praesentation-Fenster wenn offen - teaching.rs: presentation_send_slide_if_open Command - claude.rs: set/get_permission_mode Commands mit DB-Persistenz Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
370 lines
15 KiB
Rust
370 lines
15 KiB
Rust
// Claude Desktop — Tauri Backend
|
|
// Hauptmodul für die Rust-Seite der App
|
|
|
|
use std::sync::{Arc, Mutex};
|
|
use tauri::{
|
|
Emitter, Manager,
|
|
menu::{Menu, MenuItem},
|
|
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
|
|
};
|
|
|
|
#[cfg(target_os = "linux")]
|
|
use webkit2gtk::WebViewExt;
|
|
|
|
mod audit;
|
|
mod checkpoint;
|
|
mod claude;
|
|
mod clipboard;
|
|
mod commands;
|
|
mod context;
|
|
mod db;
|
|
mod guard;
|
|
mod hooks;
|
|
mod ide;
|
|
mod knowledge;
|
|
mod memory;
|
|
mod programs;
|
|
mod session;
|
|
mod strutil;
|
|
mod teaching;
|
|
mod update;
|
|
mod voice;
|
|
mod chat_window;
|
|
|
|
/// Initialisiert die App
|
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
pub fn run() {
|
|
tauri::Builder::default()
|
|
.plugin(tauri_plugin_shell::init())
|
|
.plugin(tauri_plugin_dialog::init())
|
|
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
|
.manage(Arc::new(Mutex::new(claude::ClaudeState::default())))
|
|
.manage(guard::GuardState::new(Mutex::new(guard::GuardRails::new())))
|
|
.manage::<hooks::HookState>(Arc::new(Mutex::new(hooks::HookManager::default())))
|
|
.manage::<clipboard::ClipboardState>(Arc::new(Mutex::new(clipboard::ClipboardWatcher::default())))
|
|
.manage::<ide::IdeState>(Arc::new(Mutex::new(ide::IdeConnector::default())))
|
|
// Phase 2.0: MySQL Pool als Managed State — wird einmal erstellt, von allen Knowledge-Commands geteilt
|
|
.manage::<knowledge::MysqlPoolState>(knowledge::create_managed_pool())
|
|
.invoke_handler(tauri::generate_handler![
|
|
// Claude SDK
|
|
claude::send_message,
|
|
claude::stop_all_agents,
|
|
claude::get_agent_status,
|
|
claude::set_model,
|
|
claude::get_available_models,
|
|
claude::get_current_model,
|
|
claude::set_agent_mode,
|
|
claude::get_agent_mode,
|
|
claude::set_permission_mode,
|
|
claude::get_permission_mode,
|
|
claude::init_sticky_context,
|
|
claude::get_bridge_status,
|
|
claude::stop_bridge_daemon,
|
|
claude::list_mcp_servers,
|
|
claude::add_mcp_server,
|
|
claude::remove_mcp_server,
|
|
claude::local_query,
|
|
claude::set_ollama_config,
|
|
// Gedächtnis-System
|
|
memory::load_memory,
|
|
memory::get_sticky_memory_entries,
|
|
memory::save_memory_entry,
|
|
memory::delete_memory_entry,
|
|
memory::list_memory_entries,
|
|
memory::get_autoload_memory,
|
|
memory::save_pattern,
|
|
memory::detect_issue,
|
|
// Audit-Log
|
|
audit::get_audit_log,
|
|
audit::add_audit_entry,
|
|
audit::get_audit_stats,
|
|
// Guard-Rails
|
|
guard::check_action,
|
|
guard::add_permission,
|
|
guard::remove_permission,
|
|
guard::get_permissions,
|
|
guard::get_blocked_patterns,
|
|
// Datenbank
|
|
db::init_database,
|
|
db::get_db_stats,
|
|
// Settings
|
|
db::get_setting,
|
|
db::set_setting,
|
|
db::get_all_settings,
|
|
// Sessions
|
|
session::create_session,
|
|
session::update_session,
|
|
session::list_sessions,
|
|
session::get_session,
|
|
session::delete_session,
|
|
session::resume_session,
|
|
session::get_active_session,
|
|
session::set_claude_session_id,
|
|
session::update_session_stats,
|
|
// Offline-Queue
|
|
session::queue_message,
|
|
session::list_queued_messages,
|
|
session::flush_offline_queue,
|
|
session::clear_offline_queue,
|
|
session::queue_count,
|
|
// Messages
|
|
db::save_message,
|
|
db::load_messages,
|
|
db::clear_messages,
|
|
db::compact_session,
|
|
db::search_past_messages,
|
|
db::rebuild_messages_fts,
|
|
// Monitor-Events
|
|
db::save_monitor_event,
|
|
db::load_monitor_events,
|
|
db::load_monitor_events_by_type,
|
|
db::clear_all_monitor_events,
|
|
db::get_monitor_stats,
|
|
// Phase 2.0: Fehler-Tracking
|
|
db::track_error,
|
|
db::set_error_kb_pattern,
|
|
db::get_error_stats,
|
|
// Projekte
|
|
db::list_projects,
|
|
db::save_project,
|
|
db::delete_project,
|
|
db::switch_project,
|
|
// Wissensbasis (claude-db)
|
|
knowledge::search_knowledge,
|
|
knowledge::get_knowledge,
|
|
knowledge::save_knowledge,
|
|
knowledge::get_knowledge_categories,
|
|
knowledge::get_recent_knowledge,
|
|
knowledge::test_knowledge_connection,
|
|
knowledge::get_tool_hints,
|
|
knowledge::format_tool_hints,
|
|
// Phase 2.0: Proaktive Intelligenz
|
|
knowledge::extract_message_keywords,
|
|
knowledge::get_session_hints,
|
|
knowledge::auto_save_error_pattern,
|
|
// Phase 3: KB-Cache
|
|
knowledge::invalidate_kb_cache,
|
|
// Phase 3.1: Smart Hints v2 — Session-Management
|
|
knowledge::reset_kb_session,
|
|
knowledge::get_kb_session_status,
|
|
// Block B: Usage-Tracking — markiert KB-Treffer als hilfreich
|
|
knowledge::track_knowledge_hit,
|
|
// Context-Management
|
|
context::get_sticky_context,
|
|
context::set_sticky_context,
|
|
context::remove_sticky_context,
|
|
context::get_project_context,
|
|
context::extract_context_before_compacting,
|
|
context::log_context_failure,
|
|
context::get_full_context,
|
|
context::list_sticky_context,
|
|
context::fuzzy_search_files,
|
|
context::resolve_file_path,
|
|
context::read_file_content,
|
|
// Voice-Interface
|
|
voice::transcribe_audio,
|
|
voice::text_to_speech,
|
|
voice::check_voice_availability,
|
|
voice::get_tts_voices,
|
|
// Hook-System
|
|
hooks::list_hooks,
|
|
hooks::set_hook_enabled,
|
|
hooks::get_hook_executions,
|
|
hooks::fire_hook,
|
|
// IDE-Connector (VSCodium)
|
|
ide::ide_connect,
|
|
ide::ide_disconnect,
|
|
ide::ide_status,
|
|
ide::ide_call,
|
|
// Programm-Steuerung (D-Bus, Xvfb, Playwright-Info)
|
|
programs::dbus_call,
|
|
programs::dbus_list_services,
|
|
programs::dbus_list_actions,
|
|
programs::dbus_run_action,
|
|
programs::capture_screenshot,
|
|
programs::xvfb_start,
|
|
programs::xvfb_stop,
|
|
programs::xvfb_status,
|
|
programs::xvfb_screenshot,
|
|
programs::playwright_info,
|
|
// Schulungsmodus (Phase 15)
|
|
teaching::presentation_open,
|
|
teaching::presentation_close,
|
|
teaching::presentation_send_slide,
|
|
teaching::presentation_send_slide_if_open,
|
|
teaching::presentation_clear,
|
|
// Chat-Fenster (herauslösen)
|
|
chat_window::chat_window_open,
|
|
chat_window::chat_window_close,
|
|
// Auto-Update
|
|
update::check_for_update,
|
|
update::download_update,
|
|
update::apply_update,
|
|
update::apply_bundle_update,
|
|
update::get_current_version,
|
|
// Clipboard-Watch
|
|
clipboard::clipboard_watch_start,
|
|
clipboard::clipboard_watch_stop,
|
|
clipboard::clipboard_watch_status,
|
|
// Slash-Command Registry
|
|
commands::get_slash_commands,
|
|
// Checkpoints (Accept/Reject + Rewind)
|
|
checkpoint::accept_change,
|
|
checkpoint::reject_change,
|
|
checkpoint::list_checkpoints,
|
|
checkpoint::rewind_to_checkpoint,
|
|
])
|
|
.setup(|app| {
|
|
let handle = app.handle().clone();
|
|
|
|
println!("🤖 Claude Desktop gestartet");
|
|
|
|
// Datenbank initialisieren
|
|
let app_dir = app.path().app_data_dir()
|
|
.expect("Konnte App-Datenverzeichnis nicht ermitteln");
|
|
std::fs::create_dir_all(&app_dir).ok();
|
|
let db_path = app_dir.join("claude-desktop.db");
|
|
println!("📦 Datenbank: {:?}", db_path);
|
|
|
|
let db = db::Database::open(&db_path)
|
|
.expect("Datenbank konnte nicht geöffnet werden");
|
|
app.manage(Arc::new(Mutex::new(db)));
|
|
|
|
// Guard-Rails: permanente Permissions aus DB laden
|
|
{
|
|
let db_state = handle.state::<Arc<Mutex<db::Database>>>();
|
|
let guard_state = handle.state::<guard::GuardState>();
|
|
let db_lock = db_state.lock().unwrap();
|
|
let mut guard_lock = guard_state.lock().unwrap();
|
|
if let Ok(perms) = db_lock.load_permissions() {
|
|
for p in perms {
|
|
guard_lock.add_permission(p);
|
|
}
|
|
println!("🛡️ {} permanente Permissions geladen", guard_lock.get_all_permissions().len());
|
|
}
|
|
}
|
|
|
|
// Gedächtnis-System laden
|
|
tauri::async_runtime::spawn(async move {
|
|
println!("🧠 Initialisiere Gedächtnis-System...");
|
|
});
|
|
|
|
// Tray-Icon einrichten
|
|
let show_item = MenuItem::with_id(app, "show", "Fenster zeigen", true, None::<&str>)?;
|
|
let hide_item = MenuItem::with_id(app, "hide", "Minimieren", true, None::<&str>)?;
|
|
let quit_item = MenuItem::with_id(app, "quit", "Beenden", true, None::<&str>)?;
|
|
|
|
let tray_menu = Menu::with_items(app, &[&show_item, &hide_item, &quit_item])?;
|
|
|
|
// App-Icon für Tray verwenden
|
|
let icon = app.default_window_icon()
|
|
.cloned()
|
|
.expect("Kein App-Icon konfiguriert");
|
|
|
|
let tray_icon = TrayIconBuilder::new()
|
|
.icon(icon)
|
|
.menu(&tray_menu)
|
|
.show_menu_on_left_click(false)
|
|
.tooltip("Claude Desktop")
|
|
.on_menu_event(|app, event| {
|
|
match event.id.as_ref() {
|
|
"show" => {
|
|
if let Some(window) = app.get_webview_window("main") {
|
|
let _ = window.show();
|
|
let _ = window.set_focus();
|
|
}
|
|
}
|
|
"hide" => {
|
|
if let Some(window) = app.get_webview_window("main") {
|
|
let _ = window.hide();
|
|
}
|
|
}
|
|
"quit" => {
|
|
app.exit(0);
|
|
}
|
|
_ => {}
|
|
}
|
|
})
|
|
.on_tray_icon_event(|tray, event| {
|
|
// Doppelklick auf Tray-Icon zeigt das Fenster
|
|
if let TrayIconEvent::Click { button: MouseButton::Left, button_state: MouseButtonState::Up, .. } = event {
|
|
if let Some(window) = tray.app_handle().get_webview_window("main") {
|
|
let _ = window.show();
|
|
let _ = window.set_focus();
|
|
}
|
|
}
|
|
})
|
|
.build(app)?;
|
|
|
|
// Tray-Icon Handle speichern (optional für späteren Zugriff)
|
|
app.manage(tray_icon);
|
|
println!("🔲 Tray-Icon eingerichtet");
|
|
|
|
// Lock-Datei erstellen (Instanz-Schutz + Update-Safety)
|
|
update::create_lock_file();
|
|
|
|
// Global Hotkey: Super+C öffnet Claude-Eingabe von überall
|
|
{
|
|
use tauri_plugin_global_shortcut::GlobalShortcutExt;
|
|
let hotkey_handle = app.handle().clone();
|
|
let _ = app.global_shortcut().on_shortcut("Super+C", move |_app, _shortcut, event| {
|
|
if event.state == tauri_plugin_global_shortcut::ShortcutState::Pressed {
|
|
if let Some(window) = hotkey_handle.get_webview_window("main") {
|
|
let _ = window.show();
|
|
let _ = window.unminimize();
|
|
let _ = window.set_focus();
|
|
let _ = hotkey_handle.emit("focus-chat-input", ());
|
|
}
|
|
}
|
|
});
|
|
println!("⌨️ Global Hotkey registriert: Super+C");
|
|
}
|
|
|
|
// WebKitGTK: Mikrofon-/Kamera-Permissions automatisch erlauben
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
if let Some(window) = app.get_webview_window("main") {
|
|
window.with_webview(|webview| {
|
|
use webkit2gtk::PermissionRequestExt;
|
|
|
|
let wv: webkit2gtk::WebView = webview.inner().clone();
|
|
wv.connect_permission_request(|_wv, request: &webkit2gtk::PermissionRequest| {
|
|
println!("🎤 WebKit Permission-Request → erlaubt");
|
|
request.allow();
|
|
true // Signal handled
|
|
});
|
|
println!("🎤 WebKit Permission-Handler registriert");
|
|
}).ok();
|
|
}
|
|
}
|
|
|
|
// 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!())
|
|
.expect("Fehler beim Erstellen der App")
|
|
.run(|_app_handle, event| {
|
|
// Lock-Datei bei App-Beendigung aufräumen
|
|
if let tauri::RunEvent::Exit = event {
|
|
update::remove_lock_file();
|
|
// Bridge-Daemon am Leben lassen! Er überlebt App-Neustarts.
|
|
// Nur die UDS-Verbindung wird geschlossen (Drop).
|
|
println!("🔒 Lock-Datei aufgeräumt, App beendet. Bridge-Daemon läuft weiter.");
|
|
}
|
|
// Bei Fenster-Schließen: Lock entfernen falls App komplett beendet wird
|
|
if let tauri::RunEvent::WindowEvent {
|
|
event: tauri::WindowEvent::CloseRequested { .. },
|
|
..
|
|
} = event {
|
|
// Lock wird beim tatsächlichen Exit entfernt (RunEvent::Exit oben)
|
|
}
|
|
});
|
|
}
|