Files
ClawdDotNet/Mockup/extracted/ClawdDotNet UI Redesign.dc.html
T
RichardandClaude Opus 5 33d95a6c3f UI-Entwurf umgesetzt, Rocket.Chat-Tool, Deploymentcenter 2.4
Sicherungspunkt vor dem Aufraeumen. Buendelt die Arbeit, die seit dem
Abschluss der Avalonia-Portierung im Arbeitsverzeichnis lag.

Oberflaeche
- Entwurf aus Mockup/ umgesetzt: Theme.axaml (Farben je Thema, Barlow als
  mitgelieferte Schrift), Icons.axaml (Symbolgeometrien), Shell.axaml
  (eigene ControlThemes statt Fluent umzufaerben).
- Neue Steuerelemente StrokeIcon und BlueprintFrame, Seiten fuer
  Token-Verbrauch und Agenten-Chats, Werkzeug-Einstellungen als Seite
  statt eigenem Fenster, Texteditor-Fenster.
- ThemeManager mit hellem und dunklem Thema; die beiden Pinsel-Konverter
  entfallen, weil ein fester Farbwert den Themenwechsel nicht ueberlebt.

Rocket.Chat
- Neues Tool-Projekt (Client, Konfiguration, Workspace-Dateien) nach der
  Bauform des Telegram-Tools: rocketchat_poll als Tool-Job, geweckt wird
  nur, wenn wirklich etwas anliegt.
- send_file ist freigabepflichtig, send_message bewusst nicht: Der Raum
  ist Arbeitsraum, der Schutz sitzt an der Raum-Allowlist.
- Konzept-Doc um die Messung gegen die echte Instanz 8.7 ergaenzt; drei
  Annahmen waren falsch und sind korrigiert.

Deploymentcenter
- DC6 (Update anwenden) und DC7 (Erstinstallation ueber setup.json)
  erledigt, DC3 fuer win-x64/dev; deploy/publish.py als Release-Strecke.
- AppHost.DisposeAsync gegen doppeltes Herunterfahren gesperrt - sonst
  ueberschreibt eine zweite Abmeldung den Wartungszustand am Watchdog.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 12:16:05 +02:00

1010 lines
71 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="stylesheet" href="_ds/industry-a0ac2064-12b6-4f32-aeb8-51c9f8ab1636/styles.css">
<script src="_ds/industry-a0ac2064-12b6-4f32-aeb8-51c9f8ab1636/_ds_bundle.js"></script>
<style>
body { margin: 0; }
a { color: var(--color-accent); }
a:hover { color: var(--color-accent-700); }
</style>
</helmet>
<div style="display:flex;flex-direction:column;gap:14px;align-items:center;padding:28px 0;font-family:Barlow, system-ui, sans-serif;">
<div style="width:1920px;font-size:13px;color:#6b6b6e;line-height:1.5;">
Update: <strong style="color:#1d1f20;">(1)</strong> Zielauflösung jetzt 1920×1080. <strong style="color:#1d1f20;">(2)</strong> Sidebar per Pfeil zuklappbar (nur Icons). <strong style="color:#1d1f20;">(3)</strong> Agenten-Seite folgt wieder der WinForms-Aufteilung: oben Agentenliste + Einstellungen, unten Werkzeugliste + Werkzeug-Konfiguration, in zwei horizontal geteilten Bereichen — aber mit klaren flachen Feldern statt PropertyGrid. <strong style="color:#1d1f20;">(4)</strong> Werkzeugleisten bei Aufgaben (inkl. Job Aktivieren/Deaktivieren), Diensten und Logs ergänzt; Sicherung um Automatik und Zugangsdaten-Optionen erweitert.
</div>
<div id="app-window" style="{{ rootStyle }}">
<!-- Sidebar -->
<div style="{{ sidebarStyle }}">
<div style="display:flex;align-items:center;gap:8px;padding:14px;border-bottom:1px solid var(--color-divider);">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--color-accent)" stroke-width="1.5" style="flex:none;"><path d="M12 9c-1.5-2.5-4-3-5.5-1.5S5 11 7 12c-1 1-1.5 2-1.5 3.5S7 18 8.5 17c1-1 1.5-2 1.5-3"/><path d="M12 9c1.5-2.5 4-3 5.5-1.5S19 11 17 12c1 1 1.5 2 1.5 3.5S15 18 13.5 17c-1-1-1.5-2-1.5-3"/><path d="M12 9v3"/></svg>
<sc-if value="{{ sidebarExpanded }}" hint-placeholder-val="{{ true }}">
<div style="font-family:var(--font-heading);font-weight:600;font-size:17px;white-space:nowrap;">ClawdDotNet</div>
</sc-if>
<button title="Sidebar ein-/ausklappen" onClick="{{ toggleSidebar }}" style="{{ collapseButtonStyle }}">
<sc-if value="{{ sidebarExpanded }}" hint-placeholder-val="{{ true }}">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="m15 18-6-6 6-6"/></svg>
</sc-if>
<sc-if value="{{ sidebarCollapsed }}" hint-placeholder-val="{{ false }}">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="m9 18 6-6-6-6"/></svg>
</sc-if>
</button>
</div>
<button title="Instanz wechseln" style="{{ instanceButtonStyle }}" onClick="{{ openInstanceDialog }}">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="flex:none;"><path d="M12 9c-1.5-2.5-4-3-5.5-1.5S5 11 7 12c-1 1-1.5 2-1.5 3.5S7 18 8.5 17c1-1 1.5-2 1.5-3"/><path d="M12 9c1.5-2.5 4-3 5.5-1.5S19 11 17 12c1 1 1.5 2 1.5 3.5S15 18 13.5 17c-1-1-1.5-2-1.5-3"/><path d="M12 9v3"/></svg>
<sc-if value="{{ sidebarExpanded }}" hint-placeholder-val="{{ true }}">
<div style="display:flex;flex-direction:column;align-items:flex-start;gap:1px;min-width:0;">
<span style="font-size:9px;letter-spacing:0.08em;text-transform:uppercase;color:var(--color-accent);">Instanz</span>
<span style="font-size:13px;font-weight:600;font-family:var(--font-heading);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:150px;">{{ currentInstance.name }}</span>
</div>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="margin-left:auto;flex:none;"><path d="m6 9 6 6 6-6"/></svg>
</sc-if>
</button>
<div style="flex:1;overflow:auto;padding:14px 10px;display:flex;flex-direction:column;gap:18px;">
<div style="display:flex;flex-direction:column;gap:2px;">
<sc-if value="{{ sidebarExpanded }}" hint-placeholder-val="{{ true }}">
<div style="font-size:10px;letter-spacing:0.1em;text-transform:uppercase;color:color-mix(in srgb, var(--color-text) 50%, transparent);padding:0 8px 6px;">Arbeiten</div>
</sc-if>
<button title="Chat" style="{{ navStyles.chat }}" onClick="{{ goChat }}">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="flex:none;"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/></svg>
<sc-if value="{{ sidebarExpanded }}" hint-placeholder-val="{{ true }}"><span>Chat</span></sc-if>
</button>
<button title="Agenten" style="{{ navStyles.agents }}" onClick="{{ goAgents }}">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="flex:none;"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
<sc-if value="{{ sidebarExpanded }}" hint-placeholder-val="{{ true }}"><span>Agenten</span></sc-if>
</button>
<button title="Aufgaben" style="{{ navStyles.tasks }}" onClick="{{ goTasks }}">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="flex:none;"><path d="m3 17 2 2 4-4"/><path d="m3 7 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/></svg>
<sc-if value="{{ sidebarExpanded }}" hint-placeholder-val="{{ true }}"><span>Aufgaben</span></sc-if>
</button>
</div>
<div style="display:flex;flex-direction:column;gap:2px;">
<sc-if value="{{ sidebarExpanded }}" hint-placeholder-val="{{ true }}">
<div style="font-size:10px;letter-spacing:0.1em;text-transform:uppercase;color:color-mix(in srgb, var(--color-text) 50%, transparent);padding:0 8px 6px;">Analyse</div>
</sc-if>
<button title="Tokennutzung" style="{{ navStyles.tokenusage }}" onClick="{{ goTokenUsage }}">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="flex:none;"><path d="M3 3v18h18"/><path d="M7 15v4"/><path d="M11 11v8"/><path d="M15 7v12"/><path d="M19 12v7"/></svg>
<sc-if value="{{ sidebarExpanded }}" hint-placeholder-val="{{ true }}"><span>Tokennutzung</span></sc-if>
</button>
<button title="Agenten-Chats" style="{{ navStyles.agentchats }}" onClick="{{ goAgentChats }}">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="flex:none;"><path d="M8 12h.01M12 12h.01M16 12h.01"/><path d="M21 12c0 4.4-4 8-9 8-1.3 0-2.6-.2-3.7-.7L3 20l1-4.2C3.4 14.6 3 13.3 3 12c0-4.4 4-8 9-8s9 3.6 9 8Z"/></svg>
<sc-if value="{{ sidebarExpanded }}" hint-placeholder-val="{{ true }}"><span>Agenten-Chats</span></sc-if>
</button>
</div>
<div style="display:flex;flex-direction:column;gap:2px;">
<sc-if value="{{ sidebarExpanded }}" hint-placeholder-val="{{ true }}">
<div style="font-size:10px;letter-spacing:0.1em;text-transform:uppercase;color:color-mix(in srgb, var(--color-text) 50%, transparent);padding:0 8px 6px;">System</div>
</sc-if>
<button title="Sicherung" style="{{ navStyles.backup }}" onClick="{{ goBackup }}">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="flex:none;"><rect x="2" y="4" width="20" height="5" rx="1"/><path d="M4 9v9a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9"/><path d="M10 13h4"/></svg>
<sc-if value="{{ sidebarExpanded }}" hint-placeholder-val="{{ true }}"><span>Sicherung</span></sc-if>
</button>
<button title="Logs" style="{{ navStyles.logs }}" onClick="{{ goLogs }}">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="flex:none;"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M9 13h6"/><path d="M9 17h6"/></svg>
<sc-if value="{{ sidebarExpanded }}" hint-placeholder-val="{{ true }}"><span>Logs</span></sc-if>
</button>
<button title="Einstellungen" style="{{ navStyles.settings }}" onClick="{{ goSettings }}">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="flex:none;"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.6 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9c.13-.32.19-.66.18-1a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06c.51.5 1.24.66 1.82.33H9c.32-.13.66-.19 1-.18A1.65 1.65 0 0 0 11.51 3H12a2 2 0 0 1 4 0v.09c0 .34.06.68.18 1a1.65 1.65 0 0 0 1.82.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06c-.51.5-.67 1.24-.33 1.82V9c.13.32.19.66.18 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
<sc-if value="{{ sidebarExpanded }}" hint-placeholder-val="{{ true }}"><span>Einstellungen</span></sc-if>
</button>
<button title="Info" style="{{ navStyles.info }}" onClick="{{ goInfo }}">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="flex:none;"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
<sc-if value="{{ sidebarExpanded }}" hint-placeholder-val="{{ true }}"><span>Info</span></sc-if>
</button>
</div>
</div>
<sc-if value="{{ sidebarExpanded }}" hint-placeholder-val="{{ true }}">
<div style="padding:10px 16px;border-top:1px solid var(--color-divider);font-size:11px;color:color-mix(in srgb, var(--color-text) 55%, transparent);white-space:nowrap;">
ClawdDotNet 2.4 · Avalonia
</div>
</sc-if>
</div>
<!-- Main -->
<div style="flex:1;display:flex;flex-direction:column;min-width:0;">
<!-- Top bar -->
<div style="display:flex;align-items:center;gap:12px;padding:14px 24px;border-bottom:1px solid var(--color-divider);flex:none;">
<div style="font-family:var(--font-heading);font-weight:600;font-size:20px;">{{ pageTitle }}</div>
<div style="margin-left:auto;display:flex;align-items:center;gap:10px;">
<span style="font-size:12px;color:color-mix(in srgb, var(--color-text) 55%, transparent);">{{ themeLabel }}</span>
<button aria-label="Dunkelmodus umschalten" onClick="{{ toggleTheme }}" style="{{ themeToggleStyle }}">
<sc-if value="{{ isDark }}" hint-placeholder-val="{{ false }}">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>
</sc-if>
<sc-if value="{{ isLight }}" hint-placeholder-val="{{ true }}">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
</sc-if>
</button>
</div>
</div>
<!-- Notification bar -->
<sc-if value="{{ hasNotifications }}" hint-placeholder-val="{{ true }}">
<div style="display:flex;flex-direction:column;flex:none;border-bottom:1px solid var(--color-divider);">
<sc-for list="{{ notifications }}" as="note" hint-placeholder-count="1">
<div style="display:flex;align-items:center;gap:10px;padding:8px 24px;border-left:3px solid {{ note.barColor }};background:color-mix(in srgb, {{ note.barColor }} 10%, transparent);">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="{{ note.barColor }}" stroke-width="1.5" style="flex:none;"><path d="M12 9v4"/><path d="M12 17h.01"/><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z"/></svg>
<div style="font-size:12.5px;flex:1;"><strong>{{ note.instance }}</strong>: {{ note.text }}</div>
<button onClick="{{ note.dismiss }}" style="background:transparent;border:none;cursor:pointer;color:inherit;opacity:0.6;padding:2px;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M18 6 6 18"/><path d="M6 6l12 12"/></svg></button>
</div>
</sc-for>
</div>
</sc-if>
<!-- Content -->
<div style="flex:1;overflow:hidden;padding:20px;min-height:0;">
<!-- AGENTS PAGE -->
<sc-if value="{{ isAgentsPage }}" hint-placeholder-val="{{ true }}">
<div style="display:flex;flex-direction:column;height:100%;min-height:0;">
<!-- Top: agent list + agent settings -->
<div style="{{ topPaneStyle }}">
<div style="{{ topListStyle }}">
<div style="{{ toolbarBarStyle }}">
<button style="{{ toolbarBtnStyle }}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 5v14M5 12h14"/></svg>Agent</button>
<button style="{{ toolbarBtnStyle }}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M5 12h14"/></svg>Agent</button>
<div style="{{ sepStyle }}"></div>
<button style="{{ toolbarBtnStyle }}"><svg width="12" height="12" viewBox="0 0 24 24"><path d="M8 5v14l11-7z" fill="currentColor"/></svg>Jetzt ausf.</button>
<div style="{{ sepStyle }}"></div>
<button style="{{ toolbarBtnStyle }}" onClick="{{ openIdentityEditor }}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>Identity</button>
<button style="{{ toolbarBtnStyle }}" onClick="{{ openSoulEditor }}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>Soul</button>
<div style="{{ sepStyle }}"></div>
<button style="{{ toolbarBtnStyle }}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2Z"/></svg>Ordner</button>
</div>
<div style="flex:1;overflow:auto;">
<table class="table">
<thead><tr><th>Anzeigename</th><th>Modell</th><th>Kurzinfo</th></tr></thead>
<tbody>
<sc-for list="{{ agentsUi }}" as="agent" hint-placeholder-count="2">
<tr style="{{ agent.rowStyle }}" onClick="{{ agent.select }}">
<td style="font-weight:600;">{{ agent.displayName }}</td>
<td style="font-size:12px;">{{ agent.model }}</td>
<td style="font-size:12px;color:color-mix(in srgb, var(--color-text) 60%, transparent);">{{ agent.description }}</td>
</tr>
</sc-for>
</tbody>
</table>
</div>
</div>
<div onMouseDown="{{ startDragTopList }}" style="{{ splitterHandleStyle }}"></div>
<div style="flex:1;min-width:0;overflow:auto;padding-left:18px;">
<div style="display:flex;flex-direction:column;gap:20px;max-width:640px;">
<div>
<div style="font-size:10px;letter-spacing:0.1em;text-transform:uppercase;color:var(--color-accent);margin-bottom:4px;">Allgemein &amp; Modell</div>
<h4 style="margin:0 0 12px;">{{ selectedAgent.displayName }}</h4>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div class="field"><label>Agent-ID</label><input class="input" value="{{ selectedAgent.agentId }}" readonly="{{ true }}" /></div>
<div class="field"><label>Anzeigename</label><input class="input" value="{{ selectedAgent.displayName }}" /></div>
<div class="field" style="grid-column:1 / -1;"><label>Modell</label><input class="input" value="{{ selectedAgent.model }}" /></div>
<div class="field" style="grid-column:1 / -1;"><label>Rolle / Beschreibung</label><textarea class="input" style="height:50px;" value="{{ selectedAgent.description }}"></textarea></div>
<div class="field" style="grid-column:1 / -1;"><label>System-Prompt (zusätzliche Anweisungen)</label><textarea class="input" style="height:60px;" value="{{ selectedAgent.systemPrompt }}"></textarea></div>
</div>
</div>
<div class="hr" style="margin:0;"></div>
<div>
<div style="font-size:10px;letter-spacing:0.1em;text-transform:uppercase;color:var(--color-accent);margin-bottom:4px;">Grenzen</div>
<h4 style="margin:0 0 12px;">Schutzgrenzen (LoopGuard &amp; Budget)</h4>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div class="field"><label>Max. Schritte pro Run</label><input class="input" value="{{ selectedAgent.maxSteps }}" /></div>
<div class="field"><label>Timeout (Sekunden)</label><input class="input" value="{{ selectedAgent.timeoutSeconds }}" /></div>
<div class="field"><label>Max. Kontext-Tokens</label><input class="input" value="{{ selectedAgent.maxContextTokens }}" /></div>
<div class="field"><label>Max. Tool-Ergebnis (Zeichen)</label><input class="input" value="{{ selectedAgent.maxToolResultChars }}" /></div>
</div>
</div>
</div>
</div>
</div>
<!-- Splitter -->
<div onMouseDown="{{ startDragVertical }}" style="height:10px;flex:none;display:flex;align-items:center;justify-content:center;border-top:1px solid var(--color-divider);border-bottom:1px solid var(--color-divider);background:var(--color-surface);cursor:row-resize;">
<div style="width:36px;height:2px;background:var(--color-divider);"></div>
</div>
<!-- Bottom: tool list + tool settings -->
<div style="{{ bottomPaneStyle }}">
<div style="{{ bottomListStyle }}">
<div style="font-size:10px;letter-spacing:0.1em;text-transform:uppercase;color:var(--color-accent);padding:4px 0 6px;">Zugelassene Werkzeuge für {{ selectedAgent.displayName }}</div>
<div style="flex:1;overflow:auto;">
<table class="table">
<thead><tr><th style="width:36px;"></th><th>Werkzeug</th><th>Beschreibung</th></tr></thead>
<tbody>
<sc-for list="{{ selectedAgent.tools }}" as="tool" hint-placeholder-count="4">
<tr style="{{ tool.rowStyle }}" onClick="{{ tool.select }}">
<td><label class="radio"><input type="checkbox" checked="{{ tool.enabled }}" /><span class="dot" style="border-radius:3px;"></span></label></td>
<td style="font-weight:600;">{{ tool.name }}</td>
<td style="font-size:12px;color:color-mix(in srgb, var(--color-text) 60%, transparent);">{{ tool.description }}</td>
</tr>
</sc-for>
</tbody>
</table>
</div>
</div>
<div onMouseDown="{{ startDragBottomList }}" style="{{ splitterHandleStyle }}"></div>
<div style="flex:1;min-width:0;overflow:auto;padding-left:18px;">
<div style="font-size:10px;letter-spacing:0.1em;text-transform:uppercase;color:var(--color-accent);margin-bottom:4px;">Werkzeug-Einstellungen</div>
<h4 style="margin:0 0 12px;">{{ selectedToolName }}</h4>
<sc-if value="{{ isFileRwTool }}" hint-placeholder-val="{{ false }}">
<div style="display:flex;flex-direction:column;gap:12px;max-width:520px;">
<div class="field"><label>Erlaubte Dateiendungen (persönlich)</label><input class="input" value=".md, .txt, .json" /></div>
<div class="field"><label>Zugriffsebene (geteilt)</label><select class="input"><option>Lesen &amp; Schreiben</option><option>Nur Lesen</option></select></div>
<div class="field"><label>Geschützte Pfade</label><input class="input" value="Agents/SharedWorkspace/secrets" /></div>
</div>
</sc-if>
<sc-if value="{{ isDatabaseTool }}" hint-placeholder-val="{{ false }}">
<div style="display:flex;flex-direction:column;gap:12px;max-width:520px;">
<div class="field"><label>Typ</label><select class="input"><option>PostgreSQL</option><option>SQLite</option></select></div>
<div class="field"><label>Connection-String</label><input class="input" type="password" value="••••••••••" /></div>
<div class="field"><label>Zugriffsebene</label><select class="input"><option>Nur Lesen</option><option>Lesen &amp; Schreiben</option></select></div>
<div class="field"><label>Erlaubte Tabellen</label><input class="input" value="trades, positions" /></div>
</div>
</sc-if>
<sc-if value="{{ isWebFetchTool }}" hint-placeholder-val="{{ false }}">
<div style="display:flex;flex-direction:column;gap:12px;max-width:520px;">
<div class="field"><label>Erlaubte Domains</label><input class="input" value="reuters.com, coindesk.com" /></div>
<div class="field"><label>Max. Antwortgröße (KB)</label><input class="input" value="512" /></div>
</div>
</sc-if>
<sc-if value="{{ isNoConfigTool }}" hint-placeholder-val="{{ true }}">
<div style="font-style:italic;color:color-mix(in srgb, var(--color-text) 60%, transparent);">Dieses Werkzeug benötigt keine weiteren Einstellungen.</div>
</sc-if>
</div>
</div>
</div>
</sc-if>
<!-- CHAT PAGE -->
<sc-if value="{{ isChatPage }}" hint-placeholder-val="{{ false }}">
<div style="display:flex;flex-direction:column;height:100%;max-width:900px;">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:16px;flex:none;">
<span style="font-size:13px;font-weight:600;">Agent:</span>
<select class="input" style="width:220px;"><option>Dev Assistant</option></select>
</div>
<div style="flex:1;overflow:auto;display:flex;flex-direction:column;gap:12px;padding-bottom:12px;">
<div style="align-self:flex-start;max-width:70%;" class="card">
<div style="font-size:12px;font-weight:600;">Dev Assistant</div>
<div style="font-size:13.5px;">Deploy-Pipeline für crypto-team ist grün. Soll ich die Release-Notes zusammenfassen?</div>
</div>
<div style="align-self:flex-end;max-width:70%;background:color-mix(in srgb, var(--color-accent) 15%, transparent);" class="card">
<div style="font-size:12px;font-weight:600;">Du</div>
<div style="font-size:13.5px;">Ja, bitte kurz.</div>
</div>
</div>
<div style="display:flex;gap:8px;flex:none;">
<textarea class="input" style="flex:1;height:56px;" placeholder="Nachricht eingeben... (Strg+Eingabe zum Senden)"></textarea>
<button class="btn btn-primary">Senden</button>
</div>
</div>
</sc-if>
<!-- TASKS PAGE -->
<sc-if value="{{ isTasksPage }}" hint-placeholder-val="{{ false }}">
<div style="display:flex;flex-direction:column;height:100%;min-height:0;">
<div class="seg" style="align-self:flex-start;margin-bottom:12px;flex:none;">
<label class="seg-opt"><input type="radio" checked="{{ isJobsTab }}" onChange="{{ goJobsTab }}" />Geplante Jobs</label>
<label class="seg-opt"><input type="radio" checked="{{ isServicesTab }}" onChange="{{ goServicesTab }}" />Dienste</label>
<label class="seg-opt"><input type="radio" checked="{{ isHistoryTab }}" onChange="{{ goHistoryTab }}" />Verlauf</label>
</div>
<sc-if value="{{ isJobsTab }}" hint-placeholder-val="{{ true }}">
<div style="flex:1;min-height:0;display:flex;flex-direction:column;">
<div style="{{ toolbarBarStyle }}">
<button class="btn btn-primary" style="font-size:12.5px;padding:6px 10px;"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 5v14M5 12h14"/></svg>Job hinzufügen</button>
<button style="{{ toolbarBtnStyle }}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>Bearbeiten</button>
<button style="{{ toolbarBtnStyle }}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/></svg>Entfernen</button>
<button style="{{ toolbarBtnStyle }}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M18.36 6.64a9 9 0 1 1-12.73 0"/><path d="M12 2v10"/></svg>Aktivieren/Deaktivieren</button>
<div style="{{ sepStyle }}"></div>
<button style="{{ toolbarBtnStyle }}"><svg width="12" height="12" viewBox="0 0 24 24"><path d="M8 5v14l11-7z" fill="currentColor"/></svg>Jetzt ausf.</button>
<button style="{{ toolbarBtnStyle }}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M23 4v6h-6"/><path d="M1 20v-6h6"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10"/><path d="M20.49 15a9 9 0 0 1-14.85 3.36L1 14"/></svg>Aktualisieren</button>
</div>
<div style="flex:1;overflow:auto;">
<table class="table">
<thead><tr><th>Typ</th><th>Agent</th><th>Tool / Nachricht</th><th>Cron</th><th>Nächster Lauf</th><th>Letzter Lauf</th><th>Status</th></tr></thead>
<tbody>
<sc-for list="{{ jobs }}" as="job" hint-placeholder-count="3">
<tr><td>{{ job.jobType }}</td><td>{{ job.agentName }}</td><td>{{ job.taskMessage }}</td><td style="font-family:ui-monospace,monospace;font-size:12px;">{{ job.cronExpression }}</td><td>{{ job.nextRun }}</td><td>{{ job.lastRun }}</td><td><span class="tag" style="{{ job.tagStyle }}">{{ job.status }}</span></td></tr>
</sc-for>
</tbody>
</table>
</div>
</div>
</sc-if>
<sc-if value="{{ isServicesTab }}" hint-placeholder-val="{{ false }}">
<div style="flex:1;min-height:0;display:flex;flex-direction:column;">
<div style="{{ toolbarBarStyle }}">
<button class="btn btn-primary" style="font-size:12.5px;padding:6px 10px;"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 5v14M5 12h14"/></svg>Dienst hinzufügen</button>
<button style="{{ toolbarBtnStyle }}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/></svg>Entfernen</button>
<div style="{{ sepStyle }}"></div>
<button style="{{ toolbarBtnStyle }}"><svg width="12" height="12" viewBox="0 0 24 24"><path d="M8 5v14l11-7z" fill="currentColor"/></svg>Start</button>
<button style="{{ toolbarBtnStyle }}"><svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12"/></svg>Stop</button>
<div style="{{ sepStyle }}"></div>
<button style="{{ toolbarBtnStyle }}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M23 4v6h-6"/><path d="M1 20v-6h6"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10"/><path d="M20.49 15a9 9 0 0 1-14.85 3.36L1 14"/></svg>Aktualisieren</button>
</div>
<div style="flex:1;overflow:auto;">
<table class="table">
<thead><tr><th>Dienstname</th><th>Typ</th><th>Port</th><th>Status</th><th>Beschreibung</th></tr></thead>
<tbody>
<sc-for list="{{ services }}" as="svc" hint-placeholder-count="3">
<tr><td style="font-weight:600;">{{ svc.name }}</td><td>{{ svc.type }}</td><td>{{ svc.port }}</td><td><span class="tag" style="{{ svc.tagStyle }}">{{ svc.status }}</span></td><td style="font-size:12px;color:color-mix(in srgb, var(--color-text) 60%, transparent);">{{ svc.description }}</td></tr>
</sc-for>
</tbody>
</table>
</div>
</div>
</sc-if>
<sc-if value="{{ isHistoryTab }}" hint-placeholder-val="{{ false }}">
<div style="flex:1;min-height:0;display:flex;flex-direction:column;">
<div style="{{ toolbarBarStyle }}">
<button style="{{ toolbarBtnStyle }}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M23 4v6h-6"/><path d="M1 20v-6h6"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10"/><path d="M20.49 15a9 9 0 0 1-14.85 3.36L1 14"/></svg>Verlauf aktualisieren</button>
</div>
<div style="flex:1;overflow:auto;">
<table class="table">
<thead><tr><th>Zeitpunkt</th><th>Job</th><th>Agent</th><th>Beschreibung</th><th>Status</th><th>Details</th></tr></thead>
<tbody>
<sc-for list="{{ history }}" as="h" hint-placeholder-count="3">
<tr><td>{{ h.time }}</td><td>{{ h.jobName }}</td><td>{{ h.agent }}</td><td style="font-size:12px;">{{ h.description }}</td><td><span class="tag" style="{{ h.tagStyle }}">{{ h.status }}</span></td><td style="font-size:12px;color:color-mix(in srgb, var(--color-text) 60%, transparent);">{{ h.info }}</td></tr>
</sc-for>
</tbody>
</table>
</div>
</div>
</sc-if>
</div>
</sc-if>
<!-- BACKUP PAGE -->
<sc-if value="{{ isBackupPage }}" hint-placeholder-val="{{ false }}">
<div style="height:100%;overflow:auto;">
<div style="max-width:760px;display:flex;flex-direction:column;gap:24px;">
<div>
<div style="font-size:10px;letter-spacing:0.1em;text-transform:uppercase;color:var(--color-accent);margin-bottom:4px;">Manuell</div>
<h4 style="margin:0 0 12px;">Sicherung erstellen</h4>
<div style="display:flex;flex-direction:column;gap:12px;">
<div style="display:flex;gap:8px;align-items:flex-end;">
<div class="field" style="flex:1;"><label>Zielordner</label><input class="input" value="D:\Backups\ClawdDotNet" /></div>
<button class="btn btn-secondary">Durchsuchen…</button>
</div>
<div>
<label style="font-size:12px;color:color-mix(in srgb, var(--color-text) 70%, transparent);display:block;margin-bottom:6px;">Zugangsdaten</label>
<div style="display:flex;gap:18px;">
<label class="radio"><input type="radio" name="secrets" checked="{{ true }}" /><span class="dot"></span>nicht mitsichern</label>
<label class="radio"><input type="radio" name="secrets" /><span class="dot"></span>mit Passphrase schützen</label>
</div>
</div>
<div>
<label style="font-size:12px;color:color-mix(in srgb, var(--color-text) 70%, transparent);display:block;margin-bottom:6px;">Umfang</label>
<div style="display:flex;gap:18px;">
<label class="radio"><input type="checkbox" checked="{{ true }}" /><span class="dot" style="border-radius:3px;"></span>Chatverläufe</label>
<label class="radio"><input type="checkbox" /><span class="dot" style="border-radius:3px;"></span>Protokolle</label>
</div>
</div>
<button class="btn btn-primary" style="align-self:flex-start;"><svg width="12" height="12" viewBox="0 0 24 24"><path d="M8 5v14l11-7z" fill="currentColor"/></svg>Sicherung jetzt erstellen</button>
</div>
</div>
<div class="hr" style="margin:0;"></div>
<div>
<div style="font-size:10px;letter-spacing:0.1em;text-transform:uppercase;color:var(--color-accent);margin-bottom:4px;">Automatik</div>
<h4 style="margin:0 0 12px;">Automatische Sicherung</h4>
<div style="display:flex;flex-direction:column;gap:10px;">
<label class="radio"><input type="checkbox" checked="{{ true }}" /><span class="dot" style="border-radius:3px;"></span>täglich sichern um</label>
<div style="display:flex;gap:24px;align-items:center;padding-left:24px;">
<div class="field" style="width:100px;"><input class="input" value="03:00" /></div>
<div class="field" style="width:160px;"><label>Aufbewahren</label><input class="input" value="14" /></div>
</div>
<div style="font-size:12px;color:color-mix(in srgb, var(--color-text) 55%, transparent);padding-left:24px;">Die Automatik sichert ohne Zugangsdaten — eine gespeicherte Passphrase wäre wirkungslos.</div>
</div>
</div>
<div class="hr" style="margin:0;"></div>
<div>
<div style="font-size:10px;letter-spacing:0.1em;text-transform:uppercase;color:var(--color-accent);margin-bottom:4px;">Bestand</div>
<div style="display:flex;align-items:baseline;justify-content:space-between;margin-bottom:10px;">
<h4 style="margin:0;">Vorhandene Sicherungen</h4>
<div style="display:flex;gap:6px;">
<button style="{{ toolbarBtnStyle }}">Aktualisieren</button>
<button style="{{ toolbarBtnStyle }}">Ordner öffnen</button>
<button style="{{ toolbarBtnStyle }}">Löschen</button>
<button class="btn btn-primary" style="font-size:12.5px;padding:6px 10px;">Wiederherstellen…</button>
</div>
</div>
<table class="table">
<thead><tr><th>Dateiname</th><th>Erstellt am</th><th>Instanz</th><th>Größe</th><th>Zugangsdaten</th></tr></thead>
<tbody>
<tr><td>backup_2026-08-16_0300.zip</td><td>16.08.2026 03:00</td><td>crypto-team</td><td>214 MB</td><td>ausgeschlossen</td></tr>
<tr><td>backup_2026-08-15_0300.zip</td><td>15.08.2026 03:00</td><td>crypto-team</td><td>211 MB</td><td>ausgeschlossen</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</sc-if>
<!-- LOGS PAGE -->
<sc-if value="{{ isLogsPage }}" hint-placeholder-val="{{ false }}">
<div style="display:flex;flex-direction:column;height:100%;min-height:0;">
<div style="{{ toolbarBarStyle }}">
<span style="font-size:12.5px;">Modul:</span>
<select class="input" style="width:170px;height:30px;padding:2px 8px;"><option>Alle</option><option>AgentEngine</option><option>BackupScheduler</option></select>
<span style="font-size:12.5px;margin-left:6px;">Stufe:</span>
<select class="input" style="width:110px;height:30px;padding:2px 8px;"><option>Info</option><option>Warnung</option><option>Fehler</option></select>
<div style="{{ sepStyle }}"></div>
<button style="{{ toolbarBtnStyle }}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M23 4v6h-6"/><path d="M1 20v-6h6"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10"/><path d="M20.49 15a9 9 0 0 1-14.85 3.36L1 14"/></svg>Aktualisieren</button>
<button style="{{ toolbarBtnStyle }}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/></svg>Leeren</button>
<button style="{{ toolbarBtnStyle }}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2Z"/></svg>Ordner öffnen</button>
<label style="display:flex;align-items:center;gap:6px;font-size:12.5px;margin-left:8px;"><input type="checkbox" checked="{{ true }}" />Mitlaufen</label>
</div>
<div style="flex:1;overflow:auto;font-family:ui-monospace, monospace;font-size:12.5px;background:var(--color-surface);border:1px solid var(--color-divider);padding:14px;line-height:1.7;">
<div>09:12:03 INFO AgentEngine: Run gestartet (Dev Assistant)</div>
<div>09:12:05 INFO Tools.FileRW: Lesevorgang abgeschlossen</div>
<div style="color:var(--color-accent-700);">09:12:08 WARN BackupScheduler: Sicherung verzögert (Ziel belegt)</div>
<div>09:12:11 INFO Tools.Database: Query ausgeführt (42ms)</div>
</div>
</div>
</sc-if>
<!-- TOKEN USAGE PAGE -->
<sc-if value="{{ isTokenUsagePage }}" hint-placeholder-val="{{ false }}">
<div style="height:100%;overflow:auto;">
<div style="display:flex;gap:12px;margin-bottom:20px;">
<div class="card" style="min-width:150px;"><div class="card-kicker">Tokens (7 Tage)</div><div class="card-title">{{ totalTokens7d }}</div></div>
<div class="card" style="min-width:150px;"><div class="card-kicker">Kosten (7 Tage)</div><div class="card-title">{{ totalCost7d }} €</div></div>
<div class="card" style="min-width:150px;"><div class="card-kicker">Runs (7 Tage)</div><div class="card-title">{{ totalRuns7d }}</div></div>
</div>
<h4 style="margin:0 0 12px;">Tokens pro Tag</h4>
<div style="display:flex;align-items:flex-end;gap:16px;height:160px;margin-bottom:28px;border-bottom:1px solid var(--color-divider);padding-bottom:2px;">
<sc-for list="{{ tokenDaysUi }}" as="d" hint-placeholder-count="7">
<div style="display:flex;flex-direction:column;align-items:center;gap:6px;flex:1;">
<div style="font-size:11px;color:color-mix(in srgb, var(--color-text) 60%, transparent);">{{ d.tokensLabel }}</div>
<div style="width:28px;background:var(--color-accent);height:{{ d.barHeightPct }}%;"></div>
<div style="font-size:11.5px;">{{ d.day }}</div>
</div>
</sc-for>
</div>
<h4 style="margin:0 0 12px;">Aufschlüsselung nach Agent &amp; Modell</h4>
<table class="table" style="max-width:760px;">
<thead><tr><th>Agent</th><th>Modell</th><th>Tokens</th><th>Kosten</th><th>Runs</th></tr></thead>
<tbody>
<sc-for list="{{ tokenByAgent }}" as="row" hint-placeholder-count="2">
<tr><td style="font-weight:600;">{{ row.agent }}</td><td style="font-size:12px;">{{ row.model }}</td><td>{{ row.tokens }}</td><td>{{ row.cost }} €</td><td>{{ row.runs }}</td></tr>
</sc-for>
</tbody>
</table>
</div>
</sc-if>
<!-- AGENT CHATS PAGE -->
<sc-if value="{{ isAgentChatsPage }}" hint-placeholder-val="{{ false }}">
<div style="display:flex;flex-direction:column;height:100%;max-width:760px;">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:16px;flex:none;">
<span style="font-size:13px;font-weight:600;">Agent A:</span>
<select class="input" style="width:200px;" value="{{ chatAgentA }}" onChange="{{ setChatAgentA }}">
<sc-for list="{{ agentOptions }}" as="opt" hint-placeholder-count="2">
<option value="{{ opt.agentId }}">{{ opt.displayName }}</option>
</sc-for>
</select>
<span style="font-size:13px;font-weight:600;margin-left:8px;">Agent B:</span>
<select class="input" style="width:200px;" value="{{ chatAgentB }}" onChange="{{ setChatAgentB }}">
<sc-for list="{{ agentOptions }}" as="opt2" hint-placeholder-count="2">
<option value="{{ opt2.agentId }}">{{ opt2.displayName }}</option>
</sc-for>
</select>
</div>
<div style="flex:1;overflow:auto;display:flex;flex-direction:column;gap:10px;padding-bottom:12px;">
<sc-for list="{{ chatMessages }}" as="msg" hint-placeholder-count="3">
<div style="{{ msg.alignStyle }}" class="card">
<div style="display:flex;justify-content:space-between;gap:12px;">
<span style="font-size:12px;font-weight:600;">{{ msg.senderName }}</span>
<span style="font-size:11px;color:color-mix(in srgb, var(--color-text) 55%, transparent);">{{ msg.time }}</span>
</div>
<div style="font-size:13.5px;">{{ msg.text }}</div>
</div>
</sc-for>
</div>
</div>
</sc-if>
<!-- SETTINGS PAGE -->
<sc-if value="{{ isSettingsPage }}" hint-placeholder-val="{{ false }}">
<div style="height:100%;overflow:auto;">
<div style="max-width:640px;display:flex;flex-direction:column;gap:26px;">
<div>
<div style="font-size:10px;letter-spacing:0.1em;text-transform:uppercase;color:var(--color-accent);margin-bottom:4px;">Instanz</div>
<div style="display:flex;flex-direction:column;gap:14px;">
<div class="field"><label>Instanz-Name</label><input class="input" value="{{ currentInstance.name }}" /></div>
<div class="field"><label>OpenRouter API-Key</label><input class="input" type="password" value="••••••••" /></div>
</div>
</div>
<div class="hr" style="margin:0;"></div>
<div>
<div style="font-size:10px;letter-spacing:0.1em;text-transform:uppercase;color:var(--color-accent);margin-bottom:4px;">Allgemein</div>
<div style="display:flex;flex-direction:column;gap:14px;">
<div class="field"><label>Log-Verzeichnis</label><input class="input" value="./Logs" /></div>
<div class="field"><label>Minimaler Log-Level</label><select class="input"><option>Info</option></select></div>
</div>
</div>
</div>
</div>
</sc-if>
<!-- INFO PAGE -->
<sc-if value="{{ isInfoPage }}" hint-placeholder-val="{{ false }}">
<div class="card blueprint" style="max-width:420px;">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div class="card-title">ClawdDotNet</div>
<div class="card-body">Version 2.4.1 · Avalonia UI · net10.0</div>
</div>
</sc-if>
</div>
<!-- Statusbar -->
<div style="display:flex;align-items:center;gap:8px;padding:6px 24px;border-top:1px solid var(--color-divider);font-size:11.5px;color:color-mix(in srgb, var(--color-text) 55%, transparent);flex:none;">
<span>Instanz: {{ currentInstance.name }}</span>
<span>·</span>
<span style="display:inline-flex;align-items:center;gap:5px;"><span style="width:7px;height:7px;border-radius:50%;background:{{ apiStatusColor }};display:inline-block;"></span>API {{ apiStatusText }}</span>
<span>·</span>
<span title="{{ creditsTooltip }}">1h: {{ tokensLastHour }} Tok · 24h: {{ tokensLast24h }} Tok (~{{ costLast24h }} €)</span>
<span>·</span>
<span>Credits: {{ creditsRemaining }} von {{ creditsTotal }}</span>
<span style="margin-left:auto;">{{ themeLabel }}</span>
</div>
</div>
<!-- INSTANCE DIALOG -->
<sc-if value="{{ showInstanceDialog }}" hint-placeholder-val="{{ false }}">
<div class="dialog-backdrop" style="position:absolute;" onClick="{{ closeInstanceDialog }}">
<div class="dialog blueprint" style="width:600px;" onClick="{{ stopPropagation }}">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div style="display:flex;align-items:baseline;justify-content:space-between;">
<div class="dialog-title">Instanz wechseln</div>
<button class="btn btn-ghost" style="padding:4px;" onClick="{{ closeInstanceDialog }}"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M18 6 6 18"/><path d="M6 6l12 12"/></svg></button>
</div>
<div class="dialog-body">Jede Instanz läuft unabhängig mit eigenen Agenten und Daten. Wechseln startet die App mit der gewählten Instanz neu.</div>
<div style="display:flex;flex-direction:column;gap:6px;max-height:260px;overflow:auto;">
<sc-for list="{{ instancesUi }}" as="inst" hint-placeholder-count="3">
<div style="display:flex;align-items:center;gap:8px;padding:8px 10px;border:1px solid var(--color-divider);">
<div style="width:26px;height:26px;flex:none;border-radius:0;background:{{ inst.logoColor }};color:#fff;display:flex;align-items:center;justify-content:center;font-family:var(--font-heading);font-weight:600;font-size:10px;position:relative;">
{{ inst.logoInitials }}
<span style="position:absolute;bottom:-2px;right:-2px;width:8px;height:8px;border-radius:50%;background:{{ inst.statusColor }};border:2px solid var(--color-surface);"></span>
</div>
<div style="flex:1;min-width:0;overflow:hidden;">
<div style="font-family:var(--font-heading);font-weight:600;font-size:13.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">{{ inst.name }}</div>
<div style="font-size:11px;color:color-mix(in srgb, var(--color-text) 55%, transparent);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">{{ inst.agentCount }} Agenten · API-Key {{ inst.apiKeyStatus }}</div>
</div>
<span class="tag tag-accent" style="flex:none;{{ inst.activeTagStyle }}">Aktiv</span>
<button class="btn btn-secondary" style="font-size:12px;flex:none;" onClick="{{ inst.activate }}">Wechseln</button>
</div>
</sc-for>
</div>
<div class="hr" style="margin:0;"></div>
<div style="display:flex;gap:8px;">
<input class="input" placeholder="Name der neuen Instanz" value="{{ newInstanceName }}" onChange="{{ onNewInstanceNameChange }}" style="flex:1;" />
<button class="btn btn-primary" onClick="{{ createInstance }}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 5v14M5 12h14"/></svg>Anlegen &amp; wechseln</button>
</div>
</div>
</div>
</sc-if>
<!-- IDENTITY/SOUL EDITOR DIALOG -->
<sc-if value="{{ showFieldEditor }}" hint-placeholder-val="{{ false }}">
<div class="dialog-backdrop" style="position:absolute;" onClick="{{ closeFieldEditor }}">
<div class="dialog blueprint" style="width:560px;" onClick="{{ stopPropagation }}">
<i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
<div class="dialog-title">{{ fieldEditorTitle }}</div>
<textarea class="input" style="height:220px;" value="{{ fieldEditorValue }}"></textarea>
<div class="dialog-actions">
<button class="btn btn-secondary" onClick="{{ closeFieldEditor }}">Abbrechen</button>
<button class="btn btn-primary" onClick="{{ closeFieldEditor }}">Speichern</button>
</div>
</div>
</div>
</sc-if>
</div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{&quot;$preview&quot;:{&quot;width&quot;:1980,&quot;height&quot;:1160},&quot;defaultTheme&quot;:{&quot;editor&quot;:&quot;enum&quot;,&quot;default&quot;:&quot;light&quot;,&quot;options&quot;:[&quot;light&quot;,&quot;dark&quot;],&quot;tsType&quot;:&quot;string&quot;,&quot;section&quot;:&quot;Verhalten&quot;}}">
const LIGHT = {
'--color-bg': '#f2f2f3', '--color-surface': '#e9e9ea', '--color-text': '#1d1f20',
'--color-accent': '#5980a6', '--color-accent-2': '#728fab',
'--color-divider': 'color-mix(in srgb, #1d1f20 16%, transparent)',
'--color-neutral-100': '#f5f5f8', '--color-neutral-200': '#e7e7ea', '--color-neutral-300': '#d4d4d7',
'--color-neutral-400': '#b7b7ba', '--color-neutral-500': '#98989b', '--color-neutral-600': '#7a7a7d',
'--color-neutral-700': '#5d5d60', '--color-neutral-800': '#424244', '--color-neutral-900': '#2b2b2d',
'--color-accent-100': '#eef6ff', '--color-accent-200': '#d6ebff', '--color-accent-300': '#b5d9fd',
'--color-accent-400': '#94bce3', '--color-accent-500': '#749dc4', '--color-accent-600': '#597ea3',
'--color-accent-700': '#416180', '--color-accent-800': '#2c455d', '--color-accent-900': '#1d2d3d',
};
const DARK = {
'--color-bg': '#1a1c1e', '--color-surface': '#232527', '--color-text': '#eef0f1',
'--color-accent': '#7ea3c6', '--color-accent-2': '#8fa8c2',
'--color-divider': 'color-mix(in srgb, #eef0f1 16%, transparent)',
'--color-neutral-100': '#2b2b2d', '--color-neutral-200': '#424244', '--color-neutral-300': '#5d5d60',
'--color-neutral-400': '#7a7a7d', '--color-neutral-500': '#98989b', '--color-neutral-600': '#b7b7ba',
'--color-neutral-700': '#d4d4d7', '--color-neutral-800': '#e7e7ea', '--color-neutral-900': '#f5f5f8',
'--color-accent-100': '#1d2d3d', '--color-accent-200': '#2c455d', '--color-accent-300': '#416180',
'--color-accent-400': '#597ea3', '--color-accent-500': '#749dc4', '--color-accent-600': '#94bce3',
'--color-accent-700': '#b5d9fd', '--color-accent-800': '#d6ebff', '--color-accent-900': '#eef6ff',
};
const AGENTS = [
{ agentId: 'dev-assistant', displayName: 'Dev Assistant', model: 'anthropic/claude-sonnet-4-5',
description: 'Code-Reviews und Refactoring für die crypto-team Instanz.',
maxSteps: 20, timeoutSeconds: 300, maxContextTokens: 180000, maxToolResultChars: 20000,
systemPrompt: 'Antworte präzise auf Deutsch. Schlage konkrete Diffs vor.',
identity: 'Erfahrener Backend-Entwickler mit Fokus auf .NET.\n\n[Kontext, Team, Verantwortlichkeiten...]',
soul: 'Gründlich, dokumentiert Entscheidungen, fragt nach bei Unklarheit.\n\n[Persönlichkeit, Werte, Arbeitsweise...]',
tools: [
{ name: 'FileRW', enabled: true, description: 'Lesen/Schreiben im Workspace' },
{ name: 'Database', enabled: true, description: 'Zugriff auf Team-Datenbank' },
{ name: 'WebFetch', enabled: false, description: 'Abruf externer Webseiten' },
{ name: 'Mail', enabled: false, description: 'Versand/Empfang von E-Mails' },
{ name: 'Telegram', enabled: false, description: 'Telegram Bot-Nachrichten' },
] },
{ agentId: 'social-media', displayName: 'Social Media Manager', model: 'anthropic/claude-opus-4-1',
description: 'Plant und veröffentlicht Beiträge im Namen des Teams.',
maxSteps: 15, timeoutSeconds: 600, maxContextTokens: 120000, maxToolResultChars: 15000,
systemPrompt: 'Formuliere kurz, aktiv, ohne Emojis.',
identity: 'Community-Managerin mit Blick für Tonfall.',
soul: 'Konsistent, markenbewusst, holt bei heiklen Themen Rückfrage ein.',
tools: [
{ name: 'SocialMediaManager', enabled: true, description: 'Plant/veröffentlicht Beiträge' },
{ name: 'WebFetch', enabled: true, description: 'Abruf externer Webseiten' },
{ name: 'Mail', enabled: false, description: 'Versand/Empfang von E-Mails' },
] },
];
const INSTANCES = [
{ name: 'crypto-team', agentCount: 4, apiKeyStatus: 'konfiguriert', status: 'running', logoColor: '#5980a6' },
{ name: 'stock-team', agentCount: 2, apiKeyStatus: 'konfiguriert', status: 'error', logoColor: '#8a6d3b' },
{ name: 'sandbox', agentCount: 0, apiKeyStatus: 'fehlt', status: 'stopped', logoColor: '#6b6b6e' },
];
const NOTIFICATIONS = [
{ id: 'n1', severity: 'error', instance: 'sandbox', text: 'OpenRouter-Guthaben aufgebraucht — Agenten pausiert.' },
{ id: 'n2', severity: 'warn', instance: 'stock-team', text: '3 unbehandelte Exceptions in den letzten 15 Minuten.' },
];
const TOKEN_DAYS = [
{ day: 'Mo', tokens: 82000, cost: 0.31 },
{ day: 'Di', tokens: 95000, cost: 0.37 },
{ day: 'Mi', tokens: 61000, cost: 0.24 },
{ day: 'Do', tokens: 110000, cost: 0.44 },
{ day: 'Fr', tokens: 134000, cost: 0.53 },
{ day: 'Sa', tokens: 42000, cost: 0.16 },
{ day: 'So', tokens: 38000, cost: 0.15 },
];
const TOKEN_BY_AGENT = [
{ agent: 'Dev Assistant', model: 'claude-sonnet-4-5', tokens: 312000, cost: 1.22, runs: 41 },
{ agent: 'Social Media Manager', model: 'claude-opus-4-1', tokens: 198000, cost: 0.98, runs: 19 },
];
const AGENT_MESSAGES = [
{ from: 'dev-assistant', to: 'social-media', time: '15.08. 09:02', text: 'Kannst du den Deploy-Status im Wochenrückblick erwähnen?' },
{ from: 'social-media', to: 'dev-assistant', time: '15.08. 09:04', text: 'Ja, füge ich als letzten Punkt ein.' },
{ from: 'dev-assistant', to: 'social-media', time: '15.08. 09:05', text: 'Danke — Formulierung: "Stabiles Release, keine Vorfälle diese Woche."' },
];
const JOBS = [
{ jobType: 'Agent Wakeup', agentName: 'Dev Assistant', taskMessage: 'Nightly Code Review', cronExpression: '0 2 * * *', nextRun: '17.08. 02:00', lastRun: '16.08. 02:00', status: 'Aktiv' },
{ jobType: 'Tool Call', agentName: 'Social Media Manager', taskMessage: 'X: Wochenrückblick posten', cronExpression: '0 9 * * MON', nextRun: '24.08. 09:00', lastRun: '17.08. 09:00', status: 'Aktiv' },
{ jobType: 'Agent Wakeup', agentName: 'Dev Assistant', taskMessage: 'Dependency-Check', cronExpression: '0 6 * * *', nextRun: '—', lastRun: '15.08. 06:00', status: 'Pausiert' },
];
const SERVICES = [
{ name: 'Webhook-Empfänger', type: 'HTTP', port: 8043, status: 'Läuft', description: 'Nimmt eingehende Deploy-Hooks entgegen' },
{ name: 'Telegram-Bridge', type: 'Bot', port: 0, status: 'Läuft', description: 'Leitet Telegram-Nachrichten an Agenten weiter' },
{ name: 'Metrics-Export', type: 'HTTP', port: 9100, status: 'Gestoppt', description: 'Prometheus-Endpunkt für Token-Nutzung' },
];
const HISTORY = [
{ time: '16.08. 02:00', jobName: 'Nightly Code Review', agent: 'Dev Assistant', description: 'Geplanter Lauf', status: 'Erfolg', info: '3 Dateien geprüft, keine Findings' },
{ time: '15.08. 09:00', jobName: 'Wochenrückblick posten', agent: 'Social Media Manager', description: 'Geplanter Lauf', status: 'Erfolg', info: 'Beitrag veröffentlicht' },
{ time: '15.08. 06:00', jobName: 'Dependency-Check', agent: 'Dev Assistant', description: 'Geplanter Lauf', status: 'Fehler', info: 'Timeout nach 300s' },
];
function statusTagStyle(status, vars) {
const map = {
'Aktiv': { background: `color-mix(in srgb, ${vars['--color-accent']} 15%, transparent)`, color: vars['--color-accent-700'] },
'Läuft': { background: `color-mix(in srgb, ${vars['--color-accent']} 15%, transparent)`, color: vars['--color-accent-700'] },
'Erfolg': { background: `color-mix(in srgb, ${vars['--color-accent']} 15%, transparent)`, color: vars['--color-accent-700'] },
'Pausiert': { background: vars['--color-neutral-100'], color: vars['--color-neutral-800'] },
'Gestoppt': { background: vars['--color-neutral-100'], color: vars['--color-neutral-800'] },
'Fehler': { background: 'color-mix(in srgb, #b3453b 18%, transparent)', color: '#7a2c25' },
};
return map[status] ?? { background: vars['--color-neutral-100'], color: vars['--color-neutral-800'] };
}
class Component extends DCLogic {
dragging = null;
state = {
dark: (this.props.defaultTheme ?? 'light') === 'dark',
sidebarCollapsed: false,
page: 'agents',
selectedAgentId: AGENTS[0].agentId,
selectedToolName: AGENTS[0].tools[0].name,
tasksTab: 'jobs',
showInstanceDialog: false,
currentInstanceName: 'crypto-team',
newInstanceName: '',
fieldEditor: null,
dismissedNotifications: [],
topRatio: 0.55,
topListPct: 44,
bottomListPct: 44,
chatAgentA: 'dev-assistant',
chatAgentB: 'social-media',
};
componentDidMount() {
window.addEventListener('mousemove', this.onMouseMove);
window.addEventListener('mouseup', this.onMouseUp);
}
componentWillUnmount() {
window.removeEventListener('mousemove', this.onMouseMove);
window.removeEventListener('mouseup', this.onMouseUp);
}
startDragVertical = (e) => { this.dragging = 'vertical'; e.preventDefault(); };
startDragTopList = (e) => { this.dragging = 'topList'; e.preventDefault(); };
startDragBottomList = (e) => { this.dragging = 'bottomList'; e.preventDefault(); };
onMouseMove = (e) => {
if (!this.dragging) return;
const dx = e.movementX || 0, dy = e.movementY || 0;
if (this.dragging === 'vertical') {
this.setState(s => ({ topRatio: Math.min(0.8, Math.max(0.2, s.topRatio + dy / 700)) }));
} else if (this.dragging === 'topList') {
this.setState(s => ({ topListPct: Math.min(70, Math.max(20, s.topListPct + dx / 12)) }));
} else if (this.dragging === 'bottomList') {
this.setState(s => ({ bottomListPct: Math.min(70, Math.max(20, s.bottomListPct + dx / 12)) }));
}
};
onMouseUp = () => { this.dragging = null; };
dismissNotification = (id) => () => this.setState(s => ({ dismissedNotifications: [...s.dismissedNotifications, id] }));
setChatAgentA = (e) => this.setState({ chatAgentA: e.target.value });
setChatAgentB = (e) => this.setState({ chatAgentB: e.target.value });
toggle = () => this.setState(s => ({ dark: !s.dark }));
toggleSidebar = () => this.setState(s => ({ sidebarCollapsed: !s.sidebarCollapsed }));
setPage = (page) => () => this.setState({ page });
setTasksTab = (tasksTab) => () => this.setState({ tasksTab });
selectAgent = (id) => () => this.setState({ selectedAgentId: id, selectedToolName: (AGENTS.find(a => a.agentId === id)?.tools[0]?.name) ?? '' });
selectTool = (name) => () => this.setState({ selectedToolName: name });
openDialog = () => this.setState({ showInstanceDialog: true });
closeDialog = () => this.setState({ showInstanceDialog: false, newInstanceName: '' });
openIdentityEditor = () => this.setState({ fieldEditor: 'identity' });
openSoulEditor = () => this.setState({ fieldEditor: 'soul' });
closeFieldEditor = () => this.setState({ fieldEditor: null });
stop = (e) => e.stopPropagation();
activateInstance = (name) => () => this.setState({ currentInstanceName: name, showInstanceDialog: false });
onNameChange = (e) => this.setState({ newInstanceName: e.target.value });
createInstance = () => {
const name = this.state.newInstanceName.trim();
if (!name) return;
this.setState({ currentInstanceName: name, showInstanceDialog: false, newInstanceName: '' });
};
navBtnStyle(active, vars, collapsed) {
return {
display: 'flex', alignItems: 'center', gap: '10px', width: '100%',
padding: collapsed ? '9px' : '9px 10px', justifyContent: collapsed ? 'center' : 'flex-start',
fontSize: '13.5px', fontFamily: 'var(--font-body)',
textAlign: 'left', cursor: 'pointer', border: 'none',
background: active ? `color-mix(in srgb, ${vars['--color-accent']} 16%, transparent)` : 'transparent',
color: active ? vars['--color-accent'] : vars['--color-text'],
fontWeight: active ? 600 : 400,
};
}
renderVals() {
const vars = this.state.dark ? DARK : LIGHT;
const page = this.state.page;
const collapsed = this.state.sidebarCollapsed;
const selectedAgent = AGENTS.find(a => a.agentId === this.state.selectedAgentId) ?? AGENTS[0];
const selectedTool = selectedAgent.tools.find(t => t.name === this.state.selectedToolName) ?? selectedAgent.tools[0];
const selectedToolName = selectedTool ? selectedTool.name : '';
const rootStyle = {
...vars,
width: '1920px', height: '1080px', display: 'flex', overflow: 'hidden', position: 'relative',
background: vars['--color-bg'], color: vars['--color-text'],
border: `1px solid ${vars['--color-divider']}`,
fontFamily: 'var(--font-body)', boxShadow: '0 20px 60px rgba(0,0,0,0.25)',
};
const sidebarStyle = {
width: collapsed ? '64px' : '240px', flex: 'none', display: 'flex', flexDirection: 'column',
background: vars['--color-surface'], borderRight: `1px solid ${vars['--color-divider']}`,
transition: 'width 0.15s ease', overflow: 'hidden',
};
const collapseButtonStyle = {
marginLeft: 'auto', width: '22px', height: '22px', flex: 'none', display: 'flex', alignItems: 'center', justifyContent: 'center',
border: 'none', background: 'transparent', color: vars['--color-text'], cursor: 'pointer', opacity: 0.7,
};
const navKeys = ['chat', 'agents', 'tasks', 'tokenusage', 'agentchats', 'backup', 'logs', 'settings', 'info'];
const navStyles = {};
navKeys.forEach(k => { navStyles[k] = this.navBtnStyle(page === k, vars, collapsed); });
const titles = { chat: 'Chat', agents: 'Agenten', tasks: 'Aufgaben', tokenusage: 'Tokennutzung', agentchats: 'Agenten-Chats', backup: 'Sicherung', logs: 'Logs', settings: 'Einstellungen', info: 'Info' };
const agentsUi = AGENTS.map(a => ({
...a,
select: this.selectAgent(a.agentId),
rowStyle: {
cursor: 'pointer',
background: a.agentId === selectedAgent.agentId ? `color-mix(in srgb, ${vars['--color-accent']} 12%, transparent)` : 'transparent',
},
tools: a.agentId === selectedAgent.agentId ? a.tools.map(t => ({
...t,
select: this.selectTool(t.name),
rowStyle: { cursor: 'pointer', background: t.name === selectedToolName ? `color-mix(in srgb, ${vars['--color-accent']} 12%, transparent)` : 'transparent' },
})) : a.tools,
}));
const currentAgentUi = agentsUi.find(a => a.agentId === selectedAgent.agentId);
const statusColors = { running: '#3f8f5c', stopped: vars['--color-neutral-500'], error: '#c0392b' };
const instancesUi = INSTANCES.map(inst => ({
...inst,
activate: this.activateInstance(inst.name),
activeTagStyle: { visibility: inst.name === this.state.currentInstanceName ? 'visible' : 'hidden' },
statusColor: statusColors[inst.status] ?? vars['--color-neutral-500'],
logoInitials: inst.name.slice(0, 2).toUpperCase(),
}));
const toolbarBarStyle = { display: 'flex', alignItems: 'center', gap: '6px', padding: '0 0 10px', flex: 'none', flexWrap: 'wrap' };
const toolbarBtnStyle = {
display: 'inline-flex', alignItems: 'center', gap: '6px', padding: '6px 10px', fontSize: '12.5px',
border: `1px solid ${vars['--color-divider']}`, background: 'transparent', color: vars['--color-text'], cursor: 'pointer',
};
const sepStyle = { width: '1px', height: '20px', background: vars['--color-divider'], margin: '0 4px' };
const jobs = JOBS.map(j => ({ ...j, tagStyle: statusTagStyle(j.status, vars) }));
const services = SERVICES.map(s => ({ ...s, tagStyle: statusTagStyle(s.status, vars) }));
const history = HISTORY.map(h => ({ ...h, tagStyle: statusTagStyle(h.status, vars) }));
const fieldEditor = this.state.fieldEditor;
const fieldEditorTitle = fieldEditor === 'identity' ? `Identity bearbeiten — ${selectedAgent.displayName}` : `Soul bearbeiten — ${selectedAgent.displayName}`;
const fieldEditorValue = fieldEditor === 'identity' ? selectedAgent.identity : (fieldEditor === 'soul' ? selectedAgent.soul : '');
const visibleNotifications = NOTIFICATIONS.filter(n => !this.state.dismissedNotifications.includes(n.id)).map(n => ({
...n,
dismiss: this.dismissNotification(n.id),
barColor: n.severity === 'error' ? '#c0392b' : '#b5822a',
}));
const topPaneStyle = { flex: `${this.state.topRatio} 1 0%`, display: 'flex', minHeight: 0, overflow: 'hidden' };
const bottomPaneStyle = { flex: `${1 - this.state.topRatio} 1 0%`, display: 'flex', minHeight: 0, overflow: 'hidden', marginTop: '8px' };
const topListStyle = { width: `${this.state.topListPct}%`, flex: 'none', display: 'flex', flexDirection: 'column', minHeight: 0, borderRight: `1px solid ${vars['--color-divider']}`, paddingRight: '14px' };
const bottomListStyle = { width: `${this.state.bottomListPct}%`, flex: 'none', display: 'flex', flexDirection: 'column', minHeight: 0, borderRight: `1px solid ${vars['--color-divider']}`, paddingRight: '14px' };
const splitterHandleStyle = { width: '6px', flex: 'none', cursor: 'col-resize', background: 'transparent' };
const maxTokenDay = Math.max(...TOKEN_DAYS.map(d => d.tokens));
const tokenDaysUi = TOKEN_DAYS.map(d => ({ ...d, barHeightPct: Math.round((d.tokens / maxTokenDay) * 100), tokensLabel: (d.tokens / 1000).toFixed(0) + 'k' }));
const totalTokens7d = TOKEN_DAYS.reduce((s, d) => s + d.tokens, 0).toLocaleString('de-DE');
const totalCost7d = TOKEN_DAYS.reduce((s, d) => s + d.cost, 0).toFixed(2);
const totalRuns7d = TOKEN_BY_AGENT.reduce((s, a) => s + a.runs, 0);
const tokenByAgent = TOKEN_BY_AGENT.map(r => ({ ...r, tokens: r.tokens.toLocaleString('de-DE'), cost: r.cost.toFixed(2) }));
const agentOptions = AGENTS.map(a => ({ agentId: a.agentId, displayName: a.displayName }));
const agentNameById = Object.fromEntries(AGENTS.map(a => [a.agentId, a.displayName]));
const chatMessages = AGENT_MESSAGES
.filter(m => (m.from === this.state.chatAgentA && m.to === this.state.chatAgentB) || (m.from === this.state.chatAgentB && m.to === this.state.chatAgentA))
.map(m => {
const isA = m.from === this.state.chatAgentA;
return {
...m,
senderName: agentNameById[m.from] ?? m.from,
alignStyle: { alignSelf: isA ? 'flex-start' : 'flex-end', maxWidth: '70%', background: isA ? 'transparent' : `color-mix(in srgb, ${vars['--color-accent']} 15%, transparent)` },
};
});
return {
hasNotifications: visibleNotifications.length > 0, notifications: visibleNotifications,
apiStatusColor: '#3f8f5c', apiStatusText: 'OK', creditsTooltip: 'Preise abgerufen: heute, 41 Modelle',
tokensLastHour: '3.240', tokensLast24h: '168.500', costLast24h: '0.41', creditsRemaining: '$41.86', creditsTotal: '$100.00',
topPaneStyle, bottomPaneStyle, topListStyle, bottomListStyle, splitterHandleStyle,
startDragVertical: this.startDragVertical, startDragTopList: this.startDragTopList, startDragBottomList: this.startDragBottomList,
isTokenUsagePage: page === 'tokenusage', isAgentChatsPage: page === 'agentchats',
goTokenUsage: this.setPage('tokenusage'), goAgentChats: this.setPage('agentchats'),
tokenDaysUi, totalTokens7d, totalCost7d, totalRuns7d, tokenByAgent,
agentOptions, chatAgentA: this.state.chatAgentA, chatAgentB: this.state.chatAgentB,
setChatAgentA: this.setChatAgentA, setChatAgentB: this.setChatAgentB, chatMessages,
rootStyle, sidebarStyle, collapseButtonStyle,
sidebarCollapsed: collapsed, sidebarExpanded: !collapsed,
toggleSidebar: this.toggleSidebar,
navStyles,
pageTitle: titles[page],
isAgentsPage: page === 'agents', isChatPage: page === 'chat', isTasksPage: page === 'tasks',
isBackupPage: page === 'backup', isLogsPage: page === 'logs', isSettingsPage: page === 'settings', isInfoPage: page === 'info',
goChat: this.setPage('chat'), goAgents: this.setPage('agents'), goTasks: this.setPage('tasks'),
goBackup: this.setPage('backup'), goLogs: this.setPage('logs'), goSettings: this.setPage('settings'), goInfo: this.setPage('info'),
isDark: this.state.dark, isLight: !this.state.dark,
themeLabel: this.state.dark ? 'Dunkelmodus' : 'Hellmodus',
toggleTheme: this.toggle,
themeToggleStyle: {
display: 'flex', alignItems: 'center', justifyContent: 'center', width: '32px', height: '32px',
border: `1px solid ${vars['--color-divider']}`, background: 'transparent', color: vars['--color-text'], cursor: 'pointer',
},
currentInstance: { name: this.state.currentInstanceName },
instanceButtonStyle: {
display: 'flex', alignItems: 'center', gap: '8px', margin: '12px 16px 0', padding: '9px 10px',
border: `1px solid ${vars['--color-accent']}`, background: 'transparent', color: vars['--color-text'],
cursor: 'pointer', textAlign: 'left', font: 'inherit', justifyContent: collapsed ? 'center' : 'flex-start',
},
selectedAgent: currentAgentUi ?? selectedAgent, agentsUi,
selectedToolName, isFileRwTool: selectedToolName === 'FileRW', isDatabaseTool: selectedToolName === 'Database',
isWebFetchTool: selectedToolName === 'WebFetch',
isNoConfigTool: !['FileRW', 'Database', 'WebFetch'].includes(selectedToolName),
toolbarBarStyle, toolbarBtnStyle, sepStyle,
openIdentityEditor: this.openIdentityEditor, openSoulEditor: this.openSoulEditor,
showFieldEditor: !!fieldEditor, fieldEditorTitle, fieldEditorValue, closeFieldEditor: this.closeFieldEditor,
tasksTab: this.state.tasksTab,
isJobsTab: this.state.tasksTab === 'jobs', isServicesTab: this.state.tasksTab === 'services', isHistoryTab: this.state.tasksTab === 'history',
goJobsTab: this.setTasksTab('jobs'), goServicesTab: this.setTasksTab('services'), goHistoryTab: this.setTasksTab('history'),
jobs, services, history,
showInstanceDialog: this.state.showInstanceDialog,
openInstanceDialog: this.openDialog, closeInstanceDialog: this.closeDialog, stopPropagation: this.stop,
instancesUi, newInstanceName: this.state.newInstanceName,
onNewInstanceNameChange: this.onNameChange, createInstance: this.createInstance,
};
}
}
</script>
</body>
</html>