From 433e2de2b6d1ab2762553a0653f830a0c6e7f720 Mon Sep 17 00:00:00 2001 From: Eddy Date: Tue, 14 Apr 2026 09:32:26 +0200 Subject: [PATCH] Modell-Auswahl in Settings implementiert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Neues SettingsPanel mit Modell-Auswahl (Haiku/Sonnet/Opus) - Modell wird in SQLite persistiert (claude_model Setting) - Bridge unterstützt set-model und get-models Commands - Modell kann zur Laufzeit gewechselt werden - Preisanzeige pro Modell im Settings-Panel - Aktuelles Modell wird beim App-Start geladen Co-Authored-By: Claude Opus 4.5 --- scripts/claude-bridge.js | 51 ++++- src-tauri/src/claude.rs | 90 +++++++- src-tauri/src/db.rs | 33 +++ src-tauri/src/lib.rs | 7 + src/lib/components/SettingsPanel.svelte | 277 ++++++++++++++++++++++++ src/routes/+layout.svelte | 10 + src/routes/+page.svelte | 4 + 7 files changed, 459 insertions(+), 13 deletions(-) create mode 100644 src/lib/components/SettingsPanel.svelte diff --git a/scripts/claude-bridge.js b/scripts/claude-bridge.js index 3b653bc..6b12d4c 100644 --- a/scripts/claude-bridge.js +++ b/scripts/claude-bridge.js @@ -17,7 +17,14 @@ process.stdin.resume(); let activeAbort = null; let currentAgentId = null; -const MODEL = process.env.CLAUDE_MODEL || 'opus'; +let currentModel = process.env.CLAUDE_MODEL || 'opus'; + +// Verfügbare Modelle +const AVAILABLE_MODELS = [ + { id: 'haiku', name: 'Claude Haiku', description: 'Schnell & günstig' }, + { id: 'sonnet', name: 'Claude Sonnet', description: 'Ausgewogen' }, + { id: 'opus', name: 'Claude Opus', description: 'Leistungsstark' }, +]; // ============ Kommunikation mit Tauri ============ @@ -39,7 +46,10 @@ function sendError(id, error) { // ============ Claude Agent SDK ============ -async function sendMessage(message, requestId) { +async function sendMessage(message, requestId, model = null) { + // Modell für diese Anfrage (Parameter > State > Default) + const useModel = model || currentModel; + currentAgentId = randomUUID(); activeAbort = new AbortController(); @@ -47,19 +57,20 @@ async function sendMessage(message, requestId) { id: currentAgentId, type: 'Main', task: message.substring(0, 100), + model: useModel, }); - sendResponse(requestId, { agentId: currentAgentId, status: 'gestartet' }); + sendResponse(requestId, { agentId: currentAgentId, status: 'gestartet', model: useModel }); const startTime = Date.now(); let fullText = ''; - let usedModel = MODEL; + let usedModel = useModel; try { const conversation = query({ prompt: message, options: { - model: MODEL, + model: useModel, maxTurns: 25, abortController: activeAbort, }, @@ -140,7 +151,8 @@ function handleCommand(msg) { sendError(msg.id, 'Keine Nachricht angegeben'); return; } - sendMessage(msg.message, msg.id); + // Modell kann pro Anfrage überschrieben werden + sendMessage(msg.message, msg.id, msg.model); break; case 'stop': @@ -150,10 +162,33 @@ function handleCommand(msg) { sendResponse(msg.id, { status: 'gestoppt' }); break; + case 'set-model': + if (!msg.model) { + sendError(msg.id, 'Kein Modell angegeben'); + return; + } + const validModels = AVAILABLE_MODELS.map(m => m.id); + if (!validModels.includes(msg.model)) { + sendError(msg.id, `Ungültiges Modell: ${msg.model}. Verfügbar: ${validModels.join(', ')}`); + return; + } + currentModel = msg.model; + sendResponse(msg.id, { model: currentModel, status: 'Modell geändert' }); + sendEvent('model-changed', { model: currentModel }); + break; + + case 'get-models': + sendResponse(msg.id, { + current: currentModel, + available: AVAILABLE_MODELS, + }); + break; + case 'status': sendResponse(msg.id, { - model: MODEL, + model: currentModel, isProcessing: !!currentAgentId, + availableModels: AVAILABLE_MODELS, }); break; @@ -186,4 +221,4 @@ process.on('SIGTERM', () => { clearInterval(keepAlive); process.exit(0); }); process.on('SIGINT', () => { clearInterval(keepAlive); process.exit(0); }); // Bereit -sendEvent('ready', { version: '1.0.0', pid: process.pid, model: MODEL }); +sendEvent('ready', { version: '1.1.0', pid: process.pid, model: currentModel, availableModels: AVAILABLE_MODELS }); diff --git a/src-tauri/src/claude.rs b/src-tauri/src/claude.rs index f3e3166..93793fa 100644 --- a/src-tauri/src/claude.rs +++ b/src-tauri/src/claude.rs @@ -264,11 +264,19 @@ fn send_to_bridge(app: &AppHandle, command: &str, message: &str) -> Result serde_json::json!({ + "command": command, + "id": request_id, + "model": message + }), + _ => serde_json::json!({ + "command": command, + "id": request_id, + "message": message + }), + }; if let Some(stdin) = &mut state.bridge_stdin { writeln!(stdin, "{}", msg.to_string()).map_err(|e| e.to_string())?; @@ -323,3 +331,75 @@ pub async fn get_agent_status(app: AppHandle) -> Result, String let state = state.lock().unwrap(); Ok(state.agents.clone()) } + +/// Modell wechseln +#[tauri::command] +pub async fn set_model(app: AppHandle, model: String) -> Result { + println!("🔄 Modell wechseln zu: {}", model); + + // Modell in Settings speichern + if let Some(db_state) = app.try_state::>>() { + let db = db_state.lock().unwrap(); + let _ = db.set_setting("claude_model", &model); + } + + // Bridge starten falls nicht aktiv + let needs_start = { + let state = app.state::>>(); + let state_guard = state.lock().unwrap(); + state_guard.bridge_stdin.is_none() + }; + + if needs_start { + start_bridge(&app)?; + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + } + + // Modell an Bridge senden + send_to_bridge(&app, "set-model", &model)?; + + Ok(model) +} + +/// Verfügbare Modelle abrufen +#[tauri::command] +pub async fn get_available_models() -> Result, String> { + Ok(vec![ + ModelInfo { + id: "haiku".to_string(), + name: "Claude Haiku".to_string(), + description: "Schnell & günstig".to_string(), + }, + ModelInfo { + id: "sonnet".to_string(), + name: "Claude Sonnet".to_string(), + description: "Ausgewogen".to_string(), + }, + ModelInfo { + id: "opus".to_string(), + name: "Claude Opus".to_string(), + description: "Leistungsstark".to_string(), + }, + ]) +} + +/// Aktuelles Modell aus Settings laden +#[tauri::command] +pub async fn get_current_model(app: AppHandle) -> Result { + if let Some(db_state) = app.try_state::>>() { + let db = db_state.lock().unwrap(); + if let Ok(Some(model)) = db.get_setting("claude_model") { + return Ok(model); + } + } + // Default + Ok("opus".to_string()) +} + +/// Modell-Info Struct +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct ModelInfo { + pub id: String, + pub name: String, + pub description: String, +} diff --git a/src-tauri/src/db.rs b/src-tauri/src/db.rs index 2fe7483..f372a34 100644 --- a/src-tauri/src/db.rs +++ b/src-tauri/src/db.rs @@ -517,6 +517,15 @@ impl Database { } } + /// Lädt alle Einstellungen + pub fn get_all_settings(&self) -> SqlResult> { + let mut stmt = self.conn.prepare("SELECT key, value FROM settings")?; + let settings = stmt.query_map([], |row| { + Ok((row.get(0)?, row.get(1)?)) + })?.collect::>>()?; + Ok(settings) + } + // ============ Statistiken ============ /// DB-Statistiken @@ -604,3 +613,27 @@ pub async fn get_db_stats(app: AppHandle) -> Result { let db = state.lock().unwrap(); db.stats().map_err(|e| e.to_string()) } + +/// Einstellung lesen +#[tauri::command] +pub async fn get_setting(app: AppHandle, key: String) -> Result, String> { + let state = app.state::(); + let db = state.lock().unwrap(); + db.get_setting(&key).map_err(|e| e.to_string()) +} + +/// Einstellung speichern +#[tauri::command] +pub async fn set_setting(app: AppHandle, key: String, value: String) -> Result<(), String> { + let state = app.state::(); + let db = state.lock().unwrap(); + db.set_setting(&key, &value).map_err(|e| e.to_string()) +} + +/// Alle Einstellungen laden +#[tauri::command] +pub async fn get_all_settings(app: AppHandle) -> Result, String> { + let state = app.state::(); + let db = state.lock().unwrap(); + db.get_all_settings().map_err(|e| e.to_string()) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 2a58a08..33dd42a 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -23,6 +23,9 @@ pub fn run() { claude::send_message, claude::stop_all_agents, claude::get_agent_status, + claude::set_model, + claude::get_available_models, + claude::get_current_model, // Gedächtnis-System memory::load_memory, memory::get_sticky_context, @@ -41,6 +44,10 @@ pub fn run() { // 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, diff --git a/src/lib/components/SettingsPanel.svelte b/src/lib/components/SettingsPanel.svelte new file mode 100644 index 0000000..f821bf9 --- /dev/null +++ b/src/lib/components/SettingsPanel.svelte @@ -0,0 +1,277 @@ + + +
+
+

⚙️ Einstellungen

+
+ + {#if loading} +
Lade Einstellungen...
+ {:else} +
+ +
+

🤖 Claude-Modell

+

Wähle das Modell für deine Anfragen

+ +
+ {#each availableModels as model} + + {/each} +
+
+ + +
+

🎨 Darstellung

+
+ Theme + AWL Dark (fest) +
+
+ +
+

📁 Pfade

+
+ Arbeitsverzeichnis + Wird aus Session geladen +
+
+
+ {/if} +
+ + diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 24023aa..83ddb6e 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -7,6 +7,16 @@ onMount(async () => { await initEventListeners(); + + // Aktuelles Modell aus Settings laden + try { + const model: string = await invoke('get_current_model'); + if (model) { + $currentModel = model; + } + } catch (err) { + console.warn('Modell konnte nicht geladen werden:', err); + } }); onDestroy(async () => { diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index b9255f2..bcf2c52 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -7,6 +7,7 @@ import MemoryPanel from '$lib/components/MemoryPanel.svelte'; import AuditLog from '$lib/components/AuditLog.svelte'; import GuardRailsPanel from '$lib/components/GuardRailsPanel.svelte'; + import SettingsPanel from '$lib/components/SettingsPanel.svelte'; let activeMiddleTab = 'activity'; let activeRightTab = 'agents'; @@ -20,6 +21,7 @@ const rightTabs = [ { id: 'agents', label: 'Agents', icon: '🤖' }, { id: 'guards', label: 'Guard-Rails', icon: '🛡️' }, + { id: 'settings', label: 'Settings', icon: '⚙️' }, ]; @@ -88,6 +90,8 @@ {:else if activeRightTab === 'guards'} + {:else if activeRightTab === 'settings'} + {/if}