diff --git a/src/lib/components/SettingsPanel.svelte b/src/lib/components/SettingsPanel.svelte index 3307670..b0f20fe 100644 --- a/src/lib/components/SettingsPanel.svelte +++ b/src/lib/components/SettingsPanel.svelte @@ -4,26 +4,54 @@ import { currentModel, agentMode, type AgentMode } from '$lib/stores/app'; import { updateCheckManual } from '$lib/stores/updateTrigger'; - interface ModelInfo { + // === Typen === + interface ModelInfo { id: string; name: string; description: string; } + interface SlashCommand { name: string; description: string; category: string; source: string; } + interface HookInfo { event: string; name: string; enabled: boolean; description: string; } + interface Permission { id: string; - name: string; - description: string; + pattern: string; + tool: string | null; + path_pattern: string | null; + permission_type: string; + action: string; } - let availableModels: ModelInfo[] = []; - let selectedModel = ''; - let loading = true; - let saving = false; - let appVersion = ''; + // === Zustand === + let searchQuery = $state(''); + let activeCategory = $state('general'); + let loading = $state(true); - // Modell-Icons - const modelIcons: Record = { - haiku: '🐦', - sonnet: '📝', - opus: '🎭', - }; + // Settings-Daten + let availableModels: ModelInfo[] = $state([]); + let selectedModel = $state(''); + let saving = $state(false); + let appVersion = $state(''); - // Preise pro 1M Token (ungefähre Werte) + // Commands & Skills + let slashCommands: SlashCommand[] = $state([]); + + // Hooks + let hooks: HookInfo[] = $state([]); + + // Permissions + let permissions: Permission[] = $state([]); + + // Alle Settings aus DB + let allSettings: Record = $state({}); + + // === Kategorien === + const categories = [ + { id: 'general', label: 'Allgemein', icon: '⚙️' }, + { id: 'model', label: 'Modell', icon: '🤖' }, + { id: 'commands', label: 'Commands', icon: '⌨️' }, + { id: 'hooks', label: 'Hooks', icon: '🪝' }, + { id: 'permissions', label: 'Berechtigungen', icon: '🛡️' }, + { id: 'about', label: 'Über', icon: 'ℹ️' }, + ]; + + // Modell-Icons und Preise + const modelIcons: Record = { haiku: '🐦', sonnet: '📝', opus: '🎭' }; const modelPrices: Record = { haiku: { input: 0.25, output: 1.25 }, sonnet: { input: 3, output: 15 }, @@ -31,214 +59,402 @@ }; // Agent-Modi - interface AgentModeInfo { - id: AgentMode; - name: string; - icon: string; - description: string; - } - - const agentModes: AgentModeInfo[] = [ - { - id: 'solo', - name: 'Solo', - icon: '🎯', - description: 'Main Agent macht alles selbst. Schnell für einfache Aufgaben.' - }, - { - id: 'handlanger', - name: 'Handlanger', - icon: '👷', - description: 'Main denkt, Sub-Agents führen exakt aus. Gut für koordinierte Aufgaben.' - }, - { - id: 'experten', - name: 'Experten', - icon: '🧠', - description: 'Jeder Agent denkt selbst. Ideal für komplexe, parallelisierbare Aufgaben.' - }, - { - id: 'auto', - name: 'Auto', - icon: '🔄', - description: 'Modus wird automatisch basierend auf Aufgaben-Komplexität gewählt.' - } + const agentModes: { id: AgentMode; name: string; icon: string; desc: string }[] = [ + { id: 'solo', name: 'Solo', icon: '🎯', desc: 'Main Agent macht alles selbst' }, + { id: 'handlanger', name: 'Handlanger', icon: '👷', desc: 'Main denkt, Sub-Agents führen aus' }, + { id: 'experten', name: 'Experten', icon: '🧠', desc: 'Jeder Agent denkt selbst' }, + { id: 'auto', name: 'Auto', icon: '🔄', desc: 'Automatisch basierend auf Aufgabe' }, ]; - async function loadSettings() { + // === Gefilterte Einträge === + let filteredCommands = $derived( + slashCommands.filter(c => + !searchQuery || c.name.toLowerCase().includes(searchQuery.toLowerCase()) || + c.description.toLowerCase().includes(searchQuery.toLowerCase()) + ) + ); + + let filteredHooks = $derived( + hooks.filter(h => + !searchQuery || h.name.toLowerCase().includes(searchQuery.toLowerCase()) || + h.event.toLowerCase().includes(searchQuery.toLowerCase()) + ) + ); + + // Suchfilter: Wenn Suche aktiv, zeige alle Kategorien die Treffer haben + let hasSearchResults = $derived(() => { + if (!searchQuery) return true; + const q = searchQuery.toLowerCase(); + // Prüfe ob irgendwas matcht + return filteredCommands.length > 0 || + filteredHooks.length > 0 || + agentModes.some(m => m.name.toLowerCase().includes(q) || m.desc.toLowerCase().includes(q)) || + availableModels.some(m => m.name.toLowerCase().includes(q)); + }); + + // === Laden === + onMount(async () => { + await loadAll(); + }); + + async function loadAll() { + loading = true; try { - availableModels = await invoke('get_available_models'); - const current: string = await invoke('get_current_model'); - selectedModel = current; - $currentModel = current; + const [models, model, mode, version, cmds, allHooks, perms, settings] = await Promise.all([ + invoke('get_available_models'), + invoke('get_current_model'), + invoke('get_agent_mode'), + invoke('get_current_version'), + invoke('get_slash_commands'), + invoke('list_hooks'), + invoke('get_permissions').catch(() => []), + invoke>('get_all_settings').catch(() => ({})), + ]); - // Agent-Modus laden - const currentMode: string = await invoke('get_agent_mode'); - $agentMode = currentMode as AgentMode; - - // Version laden - appVersion = await invoke('get_current_version'); + availableModels = models; + selectedModel = model; + $currentModel = model; + $agentMode = mode as AgentMode; + appVersion = version; + slashCommands = cmds; + hooks = allHooks; + permissions = perms; + allSettings = settings; } catch (err) { - console.error('Fehler beim Laden:', err); + console.error('Settings laden fehlgeschlagen:', err); } loading = false; } + // === Aktionen === + async function changeModel(modelId: string) { + if (modelId === selectedModel) return; + saving = true; + try { + await invoke('set_model', { model: modelId }); + selectedModel = modelId; + $currentModel = modelId; + } catch (err) { + console.error('Modell-Wechsel fehlgeschlagen:', err); + } + saving = false; + } + + async function changeMode(modeId: AgentMode) { + if (modeId === $agentMode) return; + try { + await invoke('set_agent_mode', { mode: modeId }); + $agentMode = modeId; + } catch (err) { + console.error('Modus-Wechsel fehlgeschlagen:', err); + } + } + + async function toggleHook(hook: HookInfo) { + try { + await invoke('set_hook_enabled', { hookName: hook.name, enabled: !hook.enabled }); + hooks = hooks.map(h => h.name === hook.name ? { ...h, enabled: !h.enabled } : h); + } catch (err) { + console.error('Hook-Toggle fehlgeschlagen:', err); + } + } + function triggerUpdateCheck() { updateCheckManual.set(true); } - async function changeMode(modeId: AgentMode) { - if (modeId === $agentMode) return; - - try { - await invoke('set_agent_mode', { mode: modeId }); - $agentMode = modeId; - } catch (err) { - console.error('Fehler beim Modus-Wechsel:', err); - } - } - - async function changeModel(modelId: string) { - if (modelId === selectedModel) return; - saving = true; - - try { - await invoke('set_model', { model: modelId }); - selectedModel = modelId; - $currentModel = modelId; - } catch (err) { - console.error('Fehler beim Speichern:', err); - } - - saving = false; - } - - onMount(() => { - loadSettings(); - }); - function formatPrice(price: number): string { return `$${price.toFixed(2)}`; } + + function getCategoryIcon(cat: string): string { + switch(cat) { + case 'builtin': return '📦'; + case 'custom': return '📝'; + case 'skill': return '⚡'; + default: return '📄'; + } + } + + function getCategoryLabel(cat: string): string { + switch(cat) { + case 'builtin': return 'Eingebaut'; + case 'custom': return 'Custom'; + case 'skill': return 'Skill'; + default: return cat; + } + }
-
-

⚙️ Einstellungen

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

🤖 Claude-Modell

-

Wähle das Modell für deine Anfragen

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

🤝 Agent-Modus

-

Wie sollen komplexe Aufgaben bearbeitet werden?

- -
- {#each agentModes as mode} - - {/each} -
- -
- {#if $agentMode === 'solo'} -

💡 Solo ist ideal für schnelle, einfache Aufgaben wie Typo-Fixes oder Code-Erklärungen.

- {:else if $agentMode === 'handlanger'} -

💡 Handlanger spart Context: Sub-Agents bekommen nur die nötigen Infos und liefern kompakte Zusammenfassungen.

- {:else if $agentMode === 'experten'} -

💡 Experten für komplexe Features: Research, Implement, Test und Review arbeiten parallel.

- {:else} -

💡 Auto analysiert die Aufgabe und wählt den passenden Modus automatisch.

- {/if} -
-
- - -
-

🎨 Darstellung

-
- Theme - AWL Dark (fest) -
-
- -
-

📁 Pfade

-
- Arbeitsverzeichnis - Wird aus Session geladen -
-
- -
-

🔄 Version & Updates

-
- Aktuelle Version - {appVersion || '—'} -
-
- - {#if appVersion === 'dev'} -

- Entwicklungs-Build — Auto-Update ist deaktiviert. - Nur Pipeline-Builds können aktualisiert werden. -

+ {/each} + + {/if} + + +
+ {#if loading} +
Lade Einstellungen...
+ {:else} + + + {#if activeCategory === 'general' || searchQuery} + {#if !searchQuery || agentModes.some(m => m.name.toLowerCase().includes(searchQuery.toLowerCase()))} +
+

🤝 Agent-Modus

+
+ {#each agentModes as mode} + + {/each} +
+
{/if} -
-
+ + {#if !searchQuery} +
+

🎨 Darstellung

+
+
+ Theme + Farbschema der Anwendung +
+ AWL Dark +
+
+ {/if} + {/if} + + + {#if activeCategory === 'model' || (searchQuery && availableModels.some(m => m.name.toLowerCase().includes(searchQuery.toLowerCase())))} +
+

🤖 Claude-Modell

+
+ {#each availableModels as model} + + {/each} +
+
+ {/if} + + + {#if activeCategory === 'commands' || (searchQuery && filteredCommands.length > 0)} +
+

⌨️ Commands & Skills

+

Slash-Commands aus ~/.claude/commands/ und Skills aus ~/.claude/skills/

+ +
+ {#each filteredCommands as cmd} +
+
+ /{cmd.name} + + {getCategoryIcon(cmd.category)} {getCategoryLabel(cmd.category)} + +
+ {cmd.description} + {#if cmd.source !== 'builtin'} + + {cmd.source.split('/').pop()} + + {/if} +
+ {/each} +
+ + {#if filteredCommands.length === 0} +
Keine Commands gefunden
+ {/if} + + +
+ {/if} + + + {#if activeCategory === 'hooks' || (searchQuery && filteredHooks.length > 0)} +
+

🪝 Hooks

+

Event-basierte Automatisierungen

+ +
+ {#each filteredHooks as hook} +
+
+ +
+ {hook.name} + {hook.event} +
+
+ {#if hook.description} + {hook.description} + {/if} +
+ {/each} +
+ + {#if filteredHooks.length === 0} +
Keine Hooks gefunden
+ {/if} +
+ {/if} + + + {#if activeCategory === 'permissions' || (searchQuery && 'berechtigungen permissions'.includes(searchQuery.toLowerCase()))} +
+

🛡️ Berechtigungen

+

Guard-Rails für Tool-Zugriffe und Aktionen

+ + {#if permissions.length > 0} +
+ {#each permissions as perm} +
+ {perm.pattern} + {#if perm.tool} + {perm.tool} + {/if} + + {perm.action === 'Allow' ? '✓ Erlaubt' : '✗ ' + perm.action} + +
+ {/each} +
+ {:else} +
+ Keine expliziten Berechtigungen gesetzt.
+ Berechtigungen werden bei der ersten Tool-Nutzung vergeben. +
+ {/if} +
+ {/if} + + + {#if activeCategory === 'about' || (searchQuery && 'version update über about'.includes(searchQuery.toLowerCase()))} +
+

ℹ️ Über Claude Desktop

+ +
+
+
+ Version +
+ {appVersion || '—'} +
+
+
+ Framework +
+ Tauri 2.0 + SvelteKit 5 +
+
+
+ Theme +
+ AWL Dark +
+
+ +
+ + {#if appVersion === 'dev'} +

+ Entwicklungs-Build — Auto-Update deaktiviert. +

+ {/if} +
+ + + {#if Object.keys(allSettings).length > 0} +
+ Alle Settings ({Object.keys(allSettings).length}) +
+ {#each Object.entries(allSettings) as [key, value]} +
+ {key} + {value} +
+ {/each} +
+
+ {/if} +
+ {/if} + + {/if}
- {/if} +