Some checks failed
Build AppImage / build (push) Has been cancelled
Phase 8 (VS-Code-Look Chatbereich): - Linksbuendige Messages mit Avatar-Spalte, Hover-Actions - Inline Tool-Karten (Read/Edit/Bash/Generic) in Assistant-Messages - Edit-Karten zeigen Diff direkt mit Accept/Reject - Tool-Calls werden via events.ts an letzte Assistant-Message gebunden - Smart-Sticky-Scroll (Auto-Scroll stoppt wenn User selbst scrollt) - OOM-Bug durch MutationObserver mit subtree:true behoben Phase 9 (Komplettes UI-Redesign): - Design-System in app.css: 4 Graustufen, 1 Akzent (#007acc), 4 Status-Farben, 5 Schriftgroessen (11/12/13/14/16), 4-Punkt-Spacing, 2 Radius-Werte - vscode.css als Aliase auf das neue System - UI-Library src/lib/ui/: Button, Card, Icon, Badge, StatusDot, Tooltip, Drawer, Tabs - Lucide-svelte fuer SVG-Icons (ersetzt Emojis im Chrome) - StatusBar (22px) ersetzt ueberfuellten Footer mit 6+ Stats - Titlebar entruempelt: ✱-Logo + Stop + Schulungsmodus + Version - 2-spaltiges Layout (Sidebar 240px + Hauptbereich) statt 4-Pane-Zerstueckelung - ToolDrawer: 13 Panels in 4 Gruppen (Aktivitaet/Speicher/Werkzeuge/Einstellungen), jede Gruppe mit internen Tabs, Esc schliesst - Cmd+K global oeffnet QuickActions als zentrale Navigation - StatusDot-Komponente ersetzt Emoji-Status (🟢🟡⚪🔴) in AgentView - Hardgecodete Farben (#ef4444, #22c55e, #eab308 ...) in 9 Komponenten durch CSS-Variablen ersetzt Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
897 lines
23 KiB
Svelte
897 lines
23 KiB
Svelte
<script lang="ts">
|
||
import { onMount } from 'svelte';
|
||
import { invoke } from '@tauri-apps/api/core';
|
||
import { currentModel, agentMode, type AgentMode } from '$lib/stores/app';
|
||
import { updateCheckManual } from '$lib/stores/updateTrigger';
|
||
|
||
// === 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;
|
||
pattern: string;
|
||
tool: string | null;
|
||
path_pattern: string | null;
|
||
permission_type: string;
|
||
action: string;
|
||
}
|
||
|
||
// === Zustand ===
|
||
let searchQuery = $state('');
|
||
let activeCategory = $state('general');
|
||
let loading = $state(true);
|
||
|
||
// Settings-Daten
|
||
let availableModels: ModelInfo[] = $state([]);
|
||
let selectedModel = $state('');
|
||
let saving = $state(false);
|
||
let appVersion = $state('');
|
||
|
||
// Commands & Skills
|
||
let slashCommands: SlashCommand[] = $state([]);
|
||
|
||
// Hooks
|
||
let hooks: HookInfo[] = $state([]);
|
||
|
||
// Permissions
|
||
let permissions: Permission[] = $state([]);
|
||
|
||
// Alle Settings aus DB
|
||
let allSettings: Record<string, string> = $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<string, string> = { haiku: '🐦', sonnet: '📝', opus: '🎭' };
|
||
const modelPrices: Record<string, { input: number; output: number }> = {
|
||
haiku: { input: 0.25, output: 1.25 },
|
||
sonnet: { input: 3, output: 15 },
|
||
opus: { input: 15, output: 75 },
|
||
};
|
||
|
||
// Agent-Modi
|
||
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' },
|
||
];
|
||
|
||
// === 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 {
|
||
const [models, model, mode, version, cmds, allHooks, perms, settings] = await Promise.all([
|
||
invoke<ModelInfo[]>('get_available_models'),
|
||
invoke<string>('get_current_model'),
|
||
invoke<string>('get_agent_mode'),
|
||
invoke<string>('get_current_version'),
|
||
invoke<SlashCommand[]>('get_slash_commands'),
|
||
invoke<HookInfo[]>('list_hooks'),
|
||
invoke<Permission[]>('get_permissions').catch(() => []),
|
||
invoke<Record<string, string>>('get_all_settings').catch(() => ({})),
|
||
]);
|
||
|
||
availableModels = models;
|
||
selectedModel = model;
|
||
$currentModel = model;
|
||
$agentMode = mode as AgentMode;
|
||
appVersion = version;
|
||
slashCommands = cmds;
|
||
hooks = allHooks;
|
||
permissions = perms;
|
||
allSettings = settings;
|
||
} catch (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);
|
||
}
|
||
|
||
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;
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<div class="settings-panel">
|
||
<!-- Suchfeld -->
|
||
<div class="search-bar">
|
||
<span class="search-icon">🔍</span>
|
||
<input
|
||
type="text"
|
||
class="search-input"
|
||
placeholder="Einstellungen durchsuchen..."
|
||
bind:value={searchQuery}
|
||
/>
|
||
{#if searchQuery}
|
||
<button class="search-clear" onclick={() => searchQuery = ''}>✕</button>
|
||
{/if}
|
||
</div>
|
||
|
||
<div class="settings-layout">
|
||
<!-- Kategorie-Sidebar -->
|
||
{#if !searchQuery}
|
||
<nav class="category-nav">
|
||
{#each categories as cat}
|
||
<button
|
||
class="category-btn"
|
||
class:active={activeCategory === cat.id}
|
||
onclick={() => activeCategory = cat.id}
|
||
>
|
||
<span class="cat-icon">{cat.icon}</span>
|
||
<span class="cat-label">{cat.label}</span>
|
||
</button>
|
||
{/each}
|
||
</nav>
|
||
{/if}
|
||
|
||
<!-- Content -->
|
||
<div class="settings-content" class:full-width={!!searchQuery}>
|
||
{#if loading}
|
||
<div class="loading">Lade Einstellungen...</div>
|
||
{:else}
|
||
|
||
<!-- ALLGEMEIN -->
|
||
{#if activeCategory === 'general' || searchQuery}
|
||
{#if !searchQuery || agentModes.some(m => m.name.toLowerCase().includes(searchQuery.toLowerCase()))}
|
||
<section class="section">
|
||
<h3>🤝 Agent-Modus</h3>
|
||
<div class="mode-grid">
|
||
{#each agentModes as mode}
|
||
<button
|
||
class="mode-card"
|
||
class:selected={$agentMode === mode.id}
|
||
onclick={() => changeMode(mode.id)}
|
||
>
|
||
<span class="mode-icon">{mode.icon}</span>
|
||
<div class="mode-text">
|
||
<span class="mode-name">{mode.name}</span>
|
||
{#if $agentMode === mode.id}
|
||
<span class="active-badge">Aktiv</span>
|
||
{/if}
|
||
</div>
|
||
<span class="mode-desc">{mode.desc}</span>
|
||
</button>
|
||
{/each}
|
||
</div>
|
||
</section>
|
||
{/if}
|
||
|
||
{#if !searchQuery}
|
||
<section class="section">
|
||
<h3>🎨 Darstellung</h3>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<span class="setting-key">Theme</span>
|
||
<span class="setting-desc">Farbschema der Anwendung</span>
|
||
</div>
|
||
<span class="setting-val">AWL Dark</span>
|
||
</div>
|
||
</section>
|
||
{/if}
|
||
{/if}
|
||
|
||
<!-- MODELL -->
|
||
{#if activeCategory === 'model' || (searchQuery && availableModels.some(m => m.name.toLowerCase().includes(searchQuery.toLowerCase())))}
|
||
<section class="section">
|
||
<h3>🤖 Claude-Modell</h3>
|
||
<div class="model-list">
|
||
{#each availableModels as model}
|
||
<button
|
||
class="model-card"
|
||
class:selected={selectedModel === model.id}
|
||
onclick={() => changeModel(model.id)}
|
||
disabled={saving}
|
||
>
|
||
<div class="model-head">
|
||
<span class="model-icon">{modelIcons[model.id] || '🤖'}</span>
|
||
<span class="model-name">{model.name}</span>
|
||
{#if selectedModel === model.id}
|
||
<span class="active-badge">Aktiv</span>
|
||
{/if}
|
||
</div>
|
||
<span class="model-desc">{model.description}</span>
|
||
{#if modelPrices[model.id]}
|
||
<div class="model-price">
|
||
<span>In: {formatPrice(modelPrices[model.id].input)}/1M</span>
|
||
<span>Out: {formatPrice(modelPrices[model.id].output)}/1M</span>
|
||
</div>
|
||
{/if}
|
||
</button>
|
||
{/each}
|
||
</div>
|
||
</section>
|
||
{/if}
|
||
|
||
<!-- COMMANDS & SKILLS -->
|
||
{#if activeCategory === 'commands' || (searchQuery && filteredCommands.length > 0)}
|
||
<section class="section">
|
||
<h3>⌨️ Commands & Skills</h3>
|
||
<p class="section-sub">Slash-Commands aus ~/.claude/commands/ und Skills aus ~/.claude/skills/</p>
|
||
|
||
<div class="command-list">
|
||
{#each filteredCommands as cmd}
|
||
<div class="command-row">
|
||
<div class="command-info">
|
||
<span class="command-name">/{cmd.name}</span>
|
||
<span class="command-badge" class:builtin={cmd.category === 'builtin'} class:skill={cmd.category === 'skill'} class:custom={cmd.category === 'custom'}>
|
||
{getCategoryIcon(cmd.category)} {getCategoryLabel(cmd.category)}
|
||
</span>
|
||
</div>
|
||
<span class="command-desc">{cmd.description}</span>
|
||
{#if cmd.source !== 'builtin'}
|
||
<span class="command-source" title={cmd.source}>
|
||
{cmd.source.split('/').pop()}
|
||
</span>
|
||
{/if}
|
||
</div>
|
||
{/each}
|
||
</div>
|
||
|
||
{#if filteredCommands.length === 0}
|
||
<div class="empty-state">Keine Commands gefunden</div>
|
||
{/if}
|
||
|
||
<div class="section-footer">
|
||
<span class="count">{slashCommands.filter(c => c.category === 'builtin').length} Eingebaut</span>
|
||
<span class="count">{slashCommands.filter(c => c.category === 'custom').length} Custom</span>
|
||
<span class="count">{slashCommands.filter(c => c.category === 'skill').length} Skills</span>
|
||
</div>
|
||
</section>
|
||
{/if}
|
||
|
||
<!-- HOOKS -->
|
||
{#if activeCategory === 'hooks' || (searchQuery && filteredHooks.length > 0)}
|
||
<section class="section">
|
||
<h3>🪝 Hooks</h3>
|
||
<p class="section-sub">Event-basierte Automatisierungen</p>
|
||
|
||
<div class="hook-list">
|
||
{#each filteredHooks as hook}
|
||
<div class="hook-row">
|
||
<div class="hook-info">
|
||
<button
|
||
class="hook-toggle"
|
||
class:enabled={hook.enabled}
|
||
onclick={() => toggleHook(hook)}
|
||
title={hook.enabled ? 'Deaktivieren' : 'Aktivieren'}
|
||
>
|
||
{hook.enabled ? '●' : '○'}
|
||
</button>
|
||
<div class="hook-text">
|
||
<span class="hook-name">{hook.name}</span>
|
||
<span class="hook-event">{hook.event}</span>
|
||
</div>
|
||
</div>
|
||
{#if hook.description}
|
||
<span class="hook-desc">{hook.description}</span>
|
||
{/if}
|
||
</div>
|
||
{/each}
|
||
</div>
|
||
|
||
{#if filteredHooks.length === 0}
|
||
<div class="empty-state">Keine Hooks gefunden</div>
|
||
{/if}
|
||
</section>
|
||
{/if}
|
||
|
||
<!-- BERECHTIGUNGEN -->
|
||
{#if activeCategory === 'permissions' || (searchQuery && 'berechtigungen permissions'.includes(searchQuery.toLowerCase()))}
|
||
<section class="section">
|
||
<h3>🛡️ Berechtigungen</h3>
|
||
<p class="section-sub">Guard-Rails für Tool-Zugriffe und Aktionen</p>
|
||
|
||
{#if permissions.length > 0}
|
||
<div class="perm-list">
|
||
{#each permissions as perm}
|
||
<div class="perm-row">
|
||
<span class="perm-pattern">{perm.pattern}</span>
|
||
{#if perm.tool}
|
||
<span class="perm-tool">{perm.tool}</span>
|
||
{/if}
|
||
<span class="perm-status" class:allowed={perm.action === 'Allow'} class:denied={perm.action !== 'Allow'}>
|
||
{perm.action === 'Allow' ? '✓ Erlaubt' : '✗ ' + perm.action}
|
||
</span>
|
||
</div>
|
||
{/each}
|
||
</div>
|
||
{:else}
|
||
<div class="empty-state">
|
||
Keine expliziten Berechtigungen gesetzt.<br>
|
||
<span class="hint">Berechtigungen werden bei der ersten Tool-Nutzung vergeben.</span>
|
||
</div>
|
||
{/if}
|
||
</section>
|
||
{/if}
|
||
|
||
<!-- ÜBER -->
|
||
{#if activeCategory === 'about' || (searchQuery && 'version update über about'.includes(searchQuery.toLowerCase()))}
|
||
<section class="section">
|
||
<h3>ℹ️ Über Claude Desktop</h3>
|
||
|
||
<div class="about-grid">
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<span class="setting-key">Version</span>
|
||
</div>
|
||
<span class="setting-val">{appVersion || '—'}</span>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<span class="setting-key">Framework</span>
|
||
</div>
|
||
<span class="setting-val">Tauri 2.0 + SvelteKit 5</span>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<span class="setting-key">Theme</span>
|
||
</div>
|
||
<span class="setting-val">AWL Dark</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="update-section">
|
||
<button class="update-btn" onclick={triggerUpdateCheck}>
|
||
🔄 Nach Updates suchen
|
||
</button>
|
||
{#if appVersion === 'dev'}
|
||
<p class="dev-hint">
|
||
Entwicklungs-Build — Auto-Update deaktiviert.
|
||
</p>
|
||
{/if}
|
||
</div>
|
||
|
||
<!-- Alle DB-Settings (Debug) -->
|
||
{#if Object.keys(allSettings).length > 0}
|
||
<details class="debug-settings">
|
||
<summary>Alle Settings ({Object.keys(allSettings).length})</summary>
|
||
<div class="settings-dump">
|
||
{#each Object.entries(allSettings) as [key, value]}
|
||
<div class="dump-row">
|
||
<span class="dump-key">{key}</span>
|
||
<span class="dump-val">{value}</span>
|
||
</div>
|
||
{/each}
|
||
</div>
|
||
</details>
|
||
{/if}
|
||
</section>
|
||
{/if}
|
||
|
||
{/if}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<style>
|
||
.settings-panel {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* Suchfeld */
|
||
.search-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 6px 10px;
|
||
background: var(--bg-secondary);
|
||
border-bottom: 1px solid var(--bg-tertiary);
|
||
}
|
||
|
||
.search-icon {
|
||
font-size: 0.8rem;
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.search-input {
|
||
flex: 1;
|
||
background: var(--bg-primary);
|
||
border: 1px solid var(--bg-tertiary);
|
||
border-radius: var(--radius-sm);
|
||
padding: 5px 8px;
|
||
font-size: 0.8rem;
|
||
color: var(--text-primary);
|
||
outline: none;
|
||
transition: border-color 0.15s;
|
||
}
|
||
|
||
.search-input:focus {
|
||
border-color: var(--accent);
|
||
}
|
||
|
||
.search-input::placeholder {
|
||
color: var(--text-secondary);
|
||
opacity: 0.6;
|
||
}
|
||
|
||
.search-clear {
|
||
background: none;
|
||
border: none;
|
||
color: var(--text-secondary);
|
||
cursor: pointer;
|
||
font-size: 0.75rem;
|
||
padding: 2px 4px;
|
||
}
|
||
|
||
.search-clear:hover {
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
/* Layout */
|
||
.settings-layout {
|
||
display: flex;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* Kategorie-Nav */
|
||
.category-nav {
|
||
display: flex;
|
||
flex-direction: column;
|
||
width: 110px;
|
||
flex-shrink: 0;
|
||
background: var(--bg-secondary);
|
||
border-right: 1px solid var(--bg-tertiary);
|
||
padding: 4px;
|
||
gap: 2px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.category-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 6px 8px;
|
||
background: transparent;
|
||
border: none;
|
||
border-radius: var(--radius-sm);
|
||
cursor: pointer;
|
||
color: var(--text-secondary);
|
||
font-size: 0.72rem;
|
||
text-align: left;
|
||
transition: all 0.12s;
|
||
}
|
||
|
||
.category-btn:hover {
|
||
background: var(--bg-tertiary);
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.category-btn.active {
|
||
background: rgba(233, 69, 96, 0.12);
|
||
color: var(--accent);
|
||
}
|
||
|
||
.cat-icon { font-size: 0.85rem; }
|
||
.cat-label { white-space: nowrap; }
|
||
|
||
/* Content */
|
||
.settings-content {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: var(--spacing-md);
|
||
}
|
||
|
||
.settings-content.full-width {
|
||
/* Kein Sidebar bei Suche */
|
||
}
|
||
|
||
.loading {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100%;
|
||
color: var(--text-secondary);
|
||
font-size: 0.85rem;
|
||
}
|
||
|
||
/* Sektionen */
|
||
.section {
|
||
margin-bottom: var(--spacing-lg);
|
||
}
|
||
|
||
.section h3 {
|
||
font-size: 0.85rem;
|
||
font-weight: 600;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.section-sub {
|
||
font-size: 0.7rem;
|
||
color: var(--text-secondary);
|
||
margin-bottom: var(--spacing-sm);
|
||
}
|
||
|
||
/* Agent-Modi Grid */
|
||
.mode-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 6px;
|
||
}
|
||
|
||
.mode-card {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
padding: 8px;
|
||
background: var(--bg-secondary);
|
||
border: 2px solid var(--bg-tertiary);
|
||
border-radius: var(--radius-md);
|
||
cursor: pointer;
|
||
transition: all 0.15s;
|
||
text-align: left;
|
||
}
|
||
|
||
.mode-card:hover { border-color: var(--text-secondary); }
|
||
.mode-card.selected { border-color: var(--accent); background: rgba(233, 69, 96, 0.08); }
|
||
.mode-icon { font-size: 1rem; }
|
||
.mode-text { display: flex; align-items: center; gap: 4px; }
|
||
.mode-name { font-weight: 600; font-size: 0.8rem; }
|
||
.mode-desc { font-size: 0.65rem; color: var(--text-secondary); line-height: 1.3; }
|
||
|
||
/* Modell-Liste */
|
||
.model-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
|
||
.model-card {
|
||
text-align: left;
|
||
padding: 10px;
|
||
background: var(--bg-secondary);
|
||
border: 2px solid var(--bg-tertiary);
|
||
border-radius: var(--radius-md);
|
||
cursor: pointer;
|
||
transition: all 0.15s;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 3px;
|
||
}
|
||
|
||
.model-card:hover:not(:disabled) { border-color: var(--text-secondary); }
|
||
.model-card.selected { border-color: var(--accent); background: rgba(233, 69, 96, 0.08); }
|
||
.model-card:disabled { opacity: 0.5; cursor: wait; }
|
||
|
||
.model-head { display: flex; align-items: center; gap: 6px; }
|
||
.model-icon { font-size: 1rem; }
|
||
.model-name { font-weight: 600; font-size: 0.85rem; flex: 1; }
|
||
.model-desc { font-size: 0.72rem; color: var(--text-secondary); }
|
||
|
||
.model-price {
|
||
display: flex;
|
||
gap: var(--spacing-md);
|
||
font-size: 0.65rem;
|
||
color: var(--text-secondary);
|
||
font-family: var(--font-mono);
|
||
}
|
||
|
||
/* Active-Badge (wiederverwendbar) */
|
||
.active-badge {
|
||
font-size: 0.6rem;
|
||
padding: 1px 5px;
|
||
background: var(--accent);
|
||
color: white;
|
||
border-radius: var(--radius-sm);
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* Setting-Row */
|
||
.setting-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 6px 0;
|
||
border-bottom: 1px solid var(--bg-tertiary);
|
||
}
|
||
|
||
.setting-info { display: flex; flex-direction: column; gap: 1px; }
|
||
.setting-key { font-size: 0.8rem; }
|
||
.setting-desc { font-size: 0.65rem; color: var(--text-secondary); }
|
||
.setting-val { font-size: 0.8rem; color: var(--text-secondary); font-family: var(--font-mono); }
|
||
|
||
/* Commands */
|
||
.command-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
|
||
.command-row {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
padding: 6px 8px;
|
||
background: var(--bg-secondary);
|
||
border-radius: var(--radius-sm);
|
||
transition: background 0.1s;
|
||
}
|
||
|
||
.command-row:hover { background: var(--bg-tertiary); }
|
||
|
||
.command-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.command-name {
|
||
font-family: var(--font-mono);
|
||
font-size: 0.8rem;
|
||
font-weight: 600;
|
||
color: var(--accent);
|
||
}
|
||
|
||
.command-badge {
|
||
font-size: 0.6rem;
|
||
padding: 1px 5px;
|
||
border-radius: var(--radius-sm);
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.command-badge.builtin { background: rgba(99, 102, 241, 0.15); color: #818cf8; }
|
||
.command-badge.skill { background: rgba(234, 179, 8, 0.15); color: var(--status-warning); }
|
||
.command-badge.custom { background: rgba(34, 197, 94, 0.15); color: var(--status-success); }
|
||
|
||
.command-desc { font-size: 0.7rem; color: var(--text-secondary); }
|
||
.command-source { font-size: 0.6rem; color: var(--text-secondary); opacity: 0.5; font-family: var(--font-mono); }
|
||
|
||
.section-footer {
|
||
display: flex;
|
||
gap: var(--spacing-md);
|
||
margin-top: var(--spacing-sm);
|
||
padding-top: var(--spacing-sm);
|
||
border-top: 1px solid var(--bg-tertiary);
|
||
}
|
||
|
||
.count {
|
||
font-size: 0.65rem;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
/* Hooks */
|
||
.hook-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
|
||
.hook-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 6px 8px;
|
||
background: var(--bg-secondary);
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
|
||
.hook-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.hook-toggle {
|
||
background: none;
|
||
border: none;
|
||
font-size: 1rem;
|
||
cursor: pointer;
|
||
color: var(--text-secondary);
|
||
padding: 0;
|
||
line-height: 1;
|
||
transition: color 0.15s;
|
||
}
|
||
|
||
.hook-toggle.enabled { color: var(--status-success); }
|
||
|
||
.hook-text { display: flex; flex-direction: column; gap: 1px; }
|
||
.hook-name { font-size: 0.78rem; font-weight: 500; }
|
||
.hook-event { font-size: 0.62rem; color: var(--text-secondary); font-family: var(--font-mono); }
|
||
|
||
.hook-desc {
|
||
font-size: 0.62rem;
|
||
color: var(--text-secondary);
|
||
opacity: 0.7;
|
||
max-width: 140px;
|
||
text-align: right;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
/* Permissions */
|
||
.perm-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
|
||
.perm-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 6px 8px;
|
||
background: var(--bg-secondary);
|
||
border-radius: var(--radius-sm);
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
.perm-pattern { font-weight: 500; flex: 1; font-family: var(--font-mono); font-size: 0.72rem; }
|
||
.perm-tool { color: var(--text-secondary); font-family: var(--font-mono); font-size: 0.65rem; padding: 1px 4px; background: var(--bg-tertiary); border-radius: 2px; }
|
||
.perm-status { font-size: 0.65rem; font-weight: 500; }
|
||
.perm-status.allowed { color: var(--status-success); }
|
||
.perm-status.denied { color: var(--status-error); }
|
||
|
||
/* Empty State */
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: var(--spacing-md);
|
||
color: var(--text-secondary);
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
.empty-state .hint {
|
||
font-size: 0.7rem;
|
||
opacity: 0.7;
|
||
}
|
||
|
||
/* About / Updates */
|
||
.about-grid {
|
||
margin-bottom: var(--spacing-md);
|
||
}
|
||
|
||
.update-section {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--spacing-sm);
|
||
}
|
||
|
||
.update-btn {
|
||
align-self: flex-start;
|
||
padding: 6px 14px;
|
||
background: var(--accent);
|
||
color: white;
|
||
border: none;
|
||
border-radius: var(--radius-md);
|
||
font-size: 0.8rem;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: all 0.15s;
|
||
}
|
||
|
||
.update-btn:hover { filter: brightness(1.1); }
|
||
|
||
.dev-hint {
|
||
margin: 0;
|
||
padding: 6px 10px;
|
||
font-size: 0.7rem;
|
||
color: var(--text-secondary);
|
||
background: rgba(234, 179, 8, 0.08);
|
||
border: 1px solid rgba(234, 179, 8, 0.2);
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
|
||
/* Debug Settings */
|
||
.debug-settings {
|
||
margin-top: var(--spacing-md);
|
||
font-size: 0.72rem;
|
||
}
|
||
|
||
.debug-settings summary {
|
||
cursor: pointer;
|
||
color: var(--text-secondary);
|
||
padding: 4px 0;
|
||
}
|
||
|
||
.settings-dump {
|
||
margin-top: 4px;
|
||
background: var(--bg-secondary);
|
||
border-radius: var(--radius-sm);
|
||
padding: 6px;
|
||
}
|
||
|
||
.dump-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 2px 4px;
|
||
border-bottom: 1px solid var(--bg-tertiary);
|
||
}
|
||
|
||
.dump-key { font-family: var(--font-mono); color: var(--accent); }
|
||
.dump-val { font-family: var(--font-mono); color: var(--text-secondary); }
|
||
</style>
|