claude-desktop/src/lib/components/HooksPanel.svelte
Eddy 120715982b Phasen 12-15: Hooks, VSCodium-Bridge, Programm-Steuerung, Schulungsmodus
Phase 12 Hook-System (hooks.rs + HooksPanel):
- HookManager mit Event-Registry + Ausfuehrungs-Log
- 5 Built-in Hooks (SessionStart, PreToolUse, PostToolUse,
  BeforeCompacting, AfterCompacting)
- Tauri-Commands: list_hooks, set_hook_enabled, get_hook_executions, fire_hook
- HooksPanel.svelte mit Live-Ausfuehrungs-Log

Phase 13 VSCodium-Integration:
- vscode-extension/: WebSocket-Server auf Port 7890
  (Commands: openFile, goToLine, formatDocument, findInFiles,
   openTerminal, getStatus, executeCommand, ping)
- src-tauri/src/ide.rs: WebSocket-Client via tokio-tungstenite
- IdePanel.svelte: Status, Port-Konfig, Ping-Test, Live-Anzeige aktive Datei

Phase 14 Programm-Steuerung (programs.rs + ProgramsPanel):
- D-Bus: dbus_call + dbus_list_services
- Xvfb: start/stop/status + screenshot (scrot)
- Playwright-Info (MCP-Verweis)
- ProgramsPanel mit 4 Sektionen (VSCodium, Playwright, D-Bus, Xvfb)

Phase 15 Schulungsmodus (teaching.rs + presentation/+page.svelte):
- Separates Tauri-Webview-Fenster
- MermaidDiagram.svelte (dynamic import mermaid)
- AnimatedCode.svelte mit WPM-Steuerung
- Tauri-Commands: presentation_open/close/send_slide/clear
- 🎓-Button in der Titelbar
- Capabilities um core:webview:allow-create-webview-window erweitert

Deps:
- Cargo: +tokio-tungstenite 0.23, +futures-util 0.3
- npm: +mermaid ^11.4.0 (npm install erforderlich)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 19:10:41 +02:00

258 lines
5.2 KiB
Svelte

<script lang="ts">
import { onMount } from 'svelte';
import { invoke } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';
interface HookConfig {
name: string;
event: string;
enabled: boolean;
description: string;
}
interface HookExecution {
timestamp: string;
event: string;
hook_name: string;
duration_ms: number;
success: boolean;
summary: string;
}
let hooks = $state<HookConfig[]>([]);
let executions = $state<HookExecution[]>([]);
let loading = $state(true);
async function loadHooks() {
try {
hooks = await invoke<HookConfig[]>('list_hooks');
executions = await invoke<HookExecution[]>('get_hook_executions', { limit: 50 });
} catch (err) {
console.error('Hooks laden fehlgeschlagen:', err);
} finally {
loading = false;
}
}
async function toggle(hook: HookConfig) {
try {
await invoke('set_hook_enabled', {
event: hook.event,
hookName: hook.name,
enabled: !hook.enabled
});
await loadHooks();
} catch (err) {
console.error('Hook toggle fehlgeschlagen:', err);
}
}
onMount(() => {
loadHooks();
// Live-Updates bei Hook-Ausführung
const unlisten = listen<{ event: string; hooks: string[]; summary: string }>('hook-fired', () => {
loadHooks();
});
return () => {
unlisten.then(fn => fn());
};
});
const eventLabels: Record<string, string> = {
SessionStart: '🚀 Session-Start',
PreToolUse: '🔧 Vor Tool-Aufruf',
PostToolUse: '✅ Nach Tool-Aufruf',
BeforeCompacting: '📦 Vor Compacting',
AfterCompacting: '📦 Nach Compacting',
ContextFailure: '❌ Context-Fehler',
AgentStarted: '🤖 Agent-Start'
};
function groupByEvent(items: HookConfig[]): Record<string, HookConfig[]> {
const out: Record<string, HookConfig[]> = {};
for (const h of items) {
(out[h.event] ||= []).push(h);
}
return out;
}
</script>
<div class="hooks-panel">
<h3>🪝 Hook-System</h3>
<p class="hint">
Hooks laufen automatisch bei bestimmten Events. Deaktiviere einzelne Hooks, wenn sie stören.
</p>
{#if loading}
<div class="loading">Lade Hooks...</div>
{:else}
<div class="hook-groups">
{#each Object.entries(groupByEvent(hooks)) as [event, hookList]}
<div class="hook-group">
<h4>{eventLabels[event] ?? event}</h4>
{#each hookList as hook}
<label class="hook-item">
<input
type="checkbox"
checked={hook.enabled}
onchange={() => toggle(hook)}
/>
<div class="hook-info">
<div class="hook-name">{hook.name}</div>
<div class="hook-desc">{hook.description}</div>
</div>
</label>
{/each}
</div>
{/each}
</div>
<div class="executions">
<h4>Letzte Ausführungen ({executions.length})</h4>
{#if executions.length === 0}
<div class="empty">Noch keine Hooks ausgeführt.</div>
{:else}
<ul class="execution-list">
{#each executions.slice().reverse() as exec}
<li class="execution" class:failure={!exec.success}>
<span class="exec-time">{new Date(exec.timestamp).toLocaleTimeString()}</span>
<span class="exec-event">{eventLabels[exec.event] ?? exec.event}</span>
<span class="exec-name">{exec.hook_name}</span>
<span class="exec-summary">{exec.summary}</span>
</li>
{/each}
</ul>
{/if}
</div>
{/if}
</div>
<style>
.hooks-panel {
padding: var(--spacing-md);
display: flex;
flex-direction: column;
gap: var(--spacing-md);
}
h3 {
margin: 0;
font-size: 1rem;
}
.hint {
font-size: 0.8rem;
color: var(--text-secondary);
margin: 0;
}
.hook-groups {
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
}
.hook-group {
background: var(--bg-secondary);
border-radius: var(--radius-sm);
padding: var(--spacing-sm);
}
.hook-group h4 {
margin: 0 0 var(--spacing-xs) 0;
font-size: 0.85rem;
color: var(--accent);
}
.hook-item {
display: flex;
align-items: flex-start;
gap: var(--spacing-sm);
padding: var(--spacing-xs);
cursor: pointer;
border-radius: var(--radius-sm);
}
.hook-item:hover {
background: var(--bg-tertiary);
}
.hook-info {
display: flex;
flex-direction: column;
gap: 2px;
}
.hook-name {
font-size: 0.82rem;
font-weight: 600;
}
.hook-desc {
font-size: 0.75rem;
color: var(--text-secondary);
}
.executions {
margin-top: var(--spacing-sm);
}
.executions h4 {
margin: 0 0 var(--spacing-xs) 0;
font-size: 0.85rem;
}
.empty {
color: var(--text-secondary);
font-size: 0.8rem;
font-style: italic;
}
.execution-list {
list-style: none;
padding: 0;
margin: 0;
max-height: 240px;
overflow-y: auto;
font-family: monospace;
font-size: 0.72rem;
}
.execution {
display: grid;
grid-template-columns: 70px 140px 160px 1fr;
gap: var(--spacing-sm);
padding: 2px var(--spacing-xs);
border-bottom: 1px solid var(--bg-tertiary);
}
.execution.failure {
color: #f87171;
}
.exec-time {
color: var(--text-secondary);
}
.exec-event {
color: var(--accent);
}
.exec-name {
font-weight: 600;
}
.exec-summary {
color: var(--text-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.loading {
color: var(--text-secondary);
font-style: italic;
}
</style>