// ClawdDotNet — Agenten-Verwaltung // // Übersicht, Anlegen, Löschen von Agenten, Werkzeug-Schalter und maskierte Einstellungen. import { api } from "../modules/api.js"; import { abonnieren } from "../modules/events.js"; import { erstelleElement, erstelleIcon, PFADE, erstellePlakette, erstelleKarte, erstelleModal, zeigeMeldung } from "../modules/dom.js"; let gewaehlterAgentId = null; let agenten = []; let entladeEreignisse = []; export async function rendereAgenten(parameter = {}) { const container = document.getElementById("ansicht-inhalt"); if (!container) return; container.replaceChildren(); entladeEreignisse.forEach((c) => c()); entladeEreignisse = []; const layout = erstelleElement("div", { klasse: "agenten-ansicht" }); const kopfZeile = erstelleElement("div", { klasse: "ansicht-kopf" }); kopfZeile.append( erstelleElement("h1", { text: "Agenten" }), erstelleElement( "button", { klasse: "schaltflaeche primaer", onclick: oeffneNeuerAgentDialog }, erstelleIcon(PFADE.plus, 16), erstelleElement("span", { text: " Neuer Agent" }) ) ); const raster = erstelleElement("div", { klasse: "agenten-raster" }); const listeContainer = erstelleElement("div", { klasse: "agenten-liste-bereich" }); const detailContainer = erstelleElement("div", { klasse: "agenten-detail-bereich" }); raster.append(listeContainer, detailContainer); layout.append(kopfZeile, raster); container.append(layout); await ladeUndRendereListe(parameter.agentId); entladeEreignisse.push( abonnieren("instance", (daten) => { if (daten.kind === "Agents") { ladeUndRendereListe(gewaehlterAgentId); } }) ); } async function ladeUndRendereListe(zielId = null) { const listeContainer = document.querySelector(".agenten-liste-bereich"); if (!listeContainer) return; listeContainer.replaceChildren( erstelleElement("div", { klasse: "hinweis-zentriert", text: "Lade Agenten…" }) ); try { agenten = await api("/agents"); listeContainer.replaceChildren(); if (!agenten || agenten.length === 0) { const leerKasten = erstelleElement("div", { klasse: "hinweis-zentriert" }); leerKasten.append( erstelleElement("p", { klasse: "gedaempft", text: "In dieser Instanz sind noch keine Agenten eingerichtet." }), erstelleElement("button", { klasse: "schaltflaeche primaer klein", onclick: oeffneNeuerAgentDialog, style: "margin-top: 10px;" }, erstelleIcon(PFADE.plus, 14), erstelleElement("span", { text: " Ersten Agenten anlegen" })) ); listeContainer.append(leerKasten); rendereDetails(null); return; } const auswahlId = zielId && agenten.some((a) => a.agentId === zielId) ? zielId : agenten[0].agentId; for (const a of agenten) { const eintrag = erstelleElement("div", { klasse: `agenten-karte ${a.agentId === auswahlId ? "aktiv" : ""}`, onclick: () => { document.querySelectorAll(".agenten-karte").forEach((k) => k.classList.remove("aktiv")); eintrag.classList.add("aktiv"); rendereDetails(a.agentId); } }); const top = erstelleElement("div", { klasse: "agent-karte-top" }); const name = erstelleElement("strong", { text: a.displayName }); const status = a.isRunning ? erstellePlakette("Beschäftigt", "warnung") : erstellePlakette("Bereit", "neutral"); top.append(name, status); const meta = erstelleElement("div", { klasse: "agent-karte-meta" }); meta.append( erstelleElement("span", { klasse: "gedaempft", text: `ID: ${a.agentId}` }), erstelleElement("span", { klasse: "gedaempft", text: ` · Modell: ${a.model}` }) ); const werkzeugeZaehler = a.tools ? a.tools.filter((t) => t.enabled).length : 0; const werkzeugeText = erstelleElement("div", { klasse: "agent-karte-werkzeuge gedaempft", text: `${werkzeugeZaehler} Werkzeuge aktiv` }); eintrag.append(top, meta, werkzeugeText); listeContainer.append(eintrag); } rendereDetails(auswahlId); } catch (err) { const text = err.instanzUnerreichbar ? "Die ausgewählte Instanz läuft derzeit nicht. Bitte starte sie zuerst im Kopfbereich oder unter 'Instanzen'." : (err.instanzKonflikt ? "Keine aktive Instanz gewählt. Bitte wähle oben eine Instanz aus." : err.message); listeContainer.replaceChildren( erstelleElement("div", { klasse: "fehler-zentriert", text }) ); } } async function rendereDetails(agentId) { gewaehlterAgentId = agentId; const detailContainer = document.querySelector(".agenten-detail-bereich"); if (!detailContainer) return; detailContainer.replaceChildren(); if (!agentId) { detailContainer.append( erstelleKarte("Agent-Details", [ erstelleElement("p", { klasse: "gedaempft", text: "Wähle links einen Agenten aus." }) ]) ); return; } const agent = agenten.find((a) => a.agentId === agentId); if (!agent) return; // Kopf-Aktionen (Chatten, Löschen) const aktionen = erstelleElement("div", { klasse: "gruppe-horizontal" }); const zumChatBtn = erstelleElement( "button", { klasse: "schaltflaeche flach", title: "Mit diesem Agenten chatten", onclick: () => { window.location.hash = `#/chat/${encodeURIComponent(agent.agentId)}`; } }, erstelleIcon(PFADE.chat, 14), erstelleElement("span", { text: " Chatten" }) ); const loeschenBtn = erstelleElement( "button", { klasse: "schaltflaeche gefahr flach", title: "Diesen Agenten löschen", onclick: () => loescheAgent(agent.agentId, agent.displayName) }, erstelleIcon(PFADE.muelleimer, 14), erstelleElement("span", { text: " Löschen" }) ); aktionen.append(zumChatBtn, loeschenBtn); // Stammdaten-Karte const stammdatenListe = erstelleElement("dl", { klasse: "datenliste" }); const felder = [ ["Name", agent.displayName], ["Kennung (ID)", agent.agentId], ["Modell", agent.model], ["Beschreibung", agent.description || "—"] ]; for (const [bez, wert] of felder) { stammdatenListe.append( erstelleElement("dt", { text: bez }), erstelleElement("dd", { text: wert }) ); } const basisKarte = erstelleKarte(agent.displayName, [stammdatenListe], { aktionen }); // Werkzeuge-Karte const werkzeugeListe = erstelleElement("div", { klasse: "werkzeuge-liste" }); if (!agent.tools || agent.tools.length === 0) { werkzeugeListe.append( erstelleElement("p", { klasse: "gedaempft", text: "Keine Werkzeuge für diesen Agenten konfiguriert." }) ); } else { for (const tool of agent.tools) { const zeile = erstelleElement("div", { klasse: "werkzeug-zeile" }); const links = erstelleElement("div", { klasse: "werkzeug-info" }); const toolTitel = erstelleElement("strong", { text: tool.name }); const toolDesc = erstelleElement("div", { klasse: "werkzeug-beschreibung gedaempft", text: tool.description || "" }); links.append(toolTitel, toolDesc); const rechts = erstelleElement("div", { klasse: "werkzeug-aktionen" }); const einstellungenBtn = erstelleElement( "button", { klasse: "schaltflaeche flach klein", title: "Werkzeug-Einstellungen einsehen", onclick: () => zeigeWerkzeugEinstellungen(agent.agentId, tool.name) }, erstelleIcon(PFADE.einstellungen, 14), erstelleElement("span", { text: " Konfiguration" }) ); const toggleLabel = erstelleElement("label", { klasse: "schalter" }); const toggleInput = erstelleElement("input", { type: "checkbox", checked: Boolean(tool.enabled), onchange: async (e) => { const aktiv = e.target.checked; try { await api( `/agents/${encodeURIComponent(agent.agentId)}/tools/${encodeURIComponent(tool.name)}?enabled=${aktiv}`, { method: "PUT" } ); tool.enabled = aktiv; zeigeMeldung(`Werkzeug '${tool.name}' ${aktiv ? "aktiviert" : "deaktiviert"}.`, "info"); } catch (err) { e.target.checked = !aktiv; zeigeMeldung(`Änderung fehlgeschlagen: ${err.message}`, "gefahr"); } } }); const toggleSpan = erstelleElement("span", { klasse: "schalter-regler" }); toggleLabel.append(toggleInput, toggleSpan); rechts.append(einstellungenBtn, toggleLabel); zeile.append(links, rechts); werkzeugeListe.append(zeile); } } const werkzeugeKarte = erstelleKarte("Verfügbare Werkzeuge", [werkzeugeListe]); detailContainer.append(basisKarte, werkzeugeKarte); } function oeffneNeuerAgentDialog() { const formular = erstelleElement("form", { klasse: "formular-raster", id: "neuer-agent-formular" }); const idFeld = erstelleElement("input", { type: "text", required: true, placeholder: "z.B. scout", id: "neu-agent-id" }); const nameFeld = erstelleElement("input", { type: "text", required: true, placeholder: "z.B. Scout Agent", id: "neu-agent-name" }); const modellFeld = erstelleElement("input", { type: "text", required: true, placeholder: "z.B. anthropic/claude-3.5-sonnet", value: "anthropic/claude-3.5-sonnet", id: "neu-agent-modell" }); const descFeld = erstelleElement("textarea", { rows: "3", placeholder: "Optionale Beschreibung des Agenten…", id: "neu-agent-desc" }); formular.append( erstelleElement("label", { text: "Agent-ID (eindeutiger Schlüssel):" }), idFeld, erstelleElement("label", { text: "Anzeigename:" }), nameFeld, erstelleElement("label", { text: "Modell (OpenRouter):" }), modellFeld, erstelleElement("label", { text: "Beschreibung:" }), descFeld ); const abbrechenBtn = erstelleElement( "button", { type: "button", klasse: "schaltflaeche flach", onclick: () => modal.schliessen() }, erstelleElement("span", { text: "Abbrechen" }) ); const speichernBtn = erstelleElement( "button", { type: "submit", klasse: "schaltflaeche primaer", form: "neuer-agent-formular", onclick: () => { formular.requestSubmit(); } }, erstelleElement("span", { text: "Agent anlegen" }) ); const modal = erstelleModal("Neuen Agenten anlegen", formular, [abbrechenBtn, speichernBtn]); formular.addEventListener("submit", async (e) => { e.preventDefault(); speichernBtn.disabled = true; try { await api("/agents", { method: "POST", body: { agentId: idFeld.value.trim(), displayName: nameFeld.value.trim(), model: modellFeld.value.trim(), description: descFeld.value.trim() } }); modal.schliessen(); zeigeMeldung(`Agent '${nameFeld.value.trim()}' wurde erfolgreich angelegt.`, "erfolg"); await ladeUndRendereListe(idFeld.value.trim()); } catch (err) { zeigeMeldung(`Fehler beim Anlegen: ${err.message}`, "gefahr"); } finally { speichernBtn.disabled = false; } }); modal.oeffnen(); } async function loescheAgent(agentId, name) { if (!confirm(`Soll der Agent '${name}' (${agentId}) wirklich gelöscht werden?`)) return; try { await api(`/agents/${encodeURIComponent(agentId)}`, { method: "DELETE" }); zeigeMeldung(`Agent '${name}' wurde gelöscht.`, "info"); await ladeUndRendereListe(); } catch (err) { zeigeMeldung(`Löschen fehlgeschlagen: ${err.message}`, "gefahr"); } } async function zeigeWerkzeugEinstellungen(agentId, toolName) { const box = erstelleElement("div", { klasse: "einstellungen-box" }); box.append(erstelleElement("p", { klasse: "gedaempft", text: "Lade Konfiguration…" })); const schliessenBtn = erstelleElement("button", { type: "button", klasse: "schaltflaeche flach", onclick: () => modal.schliessen(), text: "Schließen" }); const modal = erstelleModal( `Einstellungen: ${toolName} (${agentId})`, box, [schliessenBtn] ); modal.oeffnen(); try { const daten = await api( `/agents/${encodeURIComponent(agentId)}/tools/${encodeURIComponent(toolName)}/settings` ); box.replaceChildren(); const hinweis = erstelleElement("p", { klasse: "hinweis", text: "Geheime Werte (Passwörter, Schlüssel) werden vom Server maskiert ausgegeben." }); box.append(hinweis); if (!daten || Object.keys(daten).length === 0) { box.append( erstelleElement("p", { klasse: "gedaempft", text: "Keine individuellen Einstellungen hinterlegt." }) ); return; } const dl = erstelleElement("dl", { klasse: "datenliste" }); for (const [schluessel, wert] of Object.entries(daten)) { const dt = erstelleElement("dt", { text: schluessel }); const dd = erstelleElement("dd", { text: typeof wert === "object" ? JSON.stringify(wert) : String(wert) }); dl.append(dt, dd); } box.append(dl); } catch (err) { box.replaceChildren( erstelleElement("p", { klasse: "fehler", text: `Laden fehlgeschlagen: ${err.message}` }) ); } }