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>
1010 lines
71 KiB
HTML
1010 lines
71 KiB
HTML
<!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 & 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 & 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 & 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 & 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 & 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 & 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="{"$preview":{"width":1980,"height":1160},"defaultTheme":{"editor":"enum","default":"light","options":["light","dark"],"tsType":"string","section":"Verhalten"}}">
|
||
|
||
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>
|