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>
258 lines
5.2 KiB
Svelte
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>
|