Download sidebyside/app.js from smlflg/HarnessEcosystem: direct link, hf CLI and curl.
- Browser
- Download file 30.4 kB
-
https://huggingface.co/smlflg/HarnessEcosystem/resolve/main/sidebyside/app.js
- Command line
-
hf download hf://smlflg/HarnessEcosystem/sidebyside/app.js
-
curl -L -o app.js https://huggingface.co/smlflg/HarnessEcosystem/resolve/main/sidebyside/app.js
30.4 kB
| const state = { | |
| snapshot: null, | |
| view: "overview", | |
| query: "", | |
| type: "all", | |
| harness: "all", | |
| selectedItem: null, | |
| lastFocused: null, | |
| }; | |
| const ui = {}; | |
| document.addEventListener("DOMContentLoaded", init); | |
| function init() { | |
| cacheUi(); | |
| bindControls(); | |
| loadSnapshot(); | |
| } | |
| function cacheUi() { | |
| ui.subtitle = document.getElementById("snapshot-subtitle"); | |
| ui.meta = document.getElementById("topbar-meta"); | |
| ui.content = document.getElementById("content"); | |
| ui.viewTitle = document.getElementById("view-title"); | |
| ui.viewNav = document.getElementById("view-nav"); | |
| ui.search = document.getElementById("search-input"); | |
| ui.type = document.getElementById("type-filter"); | |
| ui.harness = document.getElementById("harness-filter"); | |
| ui.reset = document.getElementById("reset-filters"); | |
| ui.dialog = document.getElementById("detail-dialog"); | |
| ui.detailKind = document.getElementById("detail-kind"); | |
| ui.detailTitle = document.getElementById("detail-title"); | |
| ui.detailSummary = document.getElementById("detail-summary"); | |
| ui.detailMeta = document.getElementById("detail-meta"); | |
| ui.detailRaw = document.getElementById("detail-raw"); | |
| ui.closeDialog = document.getElementById("close-dialog"); | |
| } | |
| function bindControls() { | |
| ui.viewNav.addEventListener("click", (event) => { | |
| const button = event.target.closest("[data-view]"); | |
| if (!button) return; | |
| state.view = button.dataset.view; | |
| render(); | |
| }); | |
| ui.search.addEventListener("input", (event) => { | |
| state.query = event.target.value.trim().toLowerCase(); | |
| render(); | |
| }); | |
| ui.type.addEventListener("change", (event) => { | |
| state.type = event.target.value; | |
| render(); | |
| }); | |
| ui.harness.addEventListener("change", (event) => { | |
| state.harness = event.target.value; | |
| render(); | |
| }); | |
| ui.reset.addEventListener("click", () => { | |
| state.query = ""; | |
| state.type = "all"; | |
| state.harness = "all"; | |
| ui.search.value = ""; | |
| ui.type.value = "all"; | |
| ui.harness.value = "all"; | |
| render(); | |
| }); | |
| ui.closeDialog.addEventListener("click", closeDialog); | |
| ui.dialog.addEventListener("click", (event) => { | |
| if (event.target === ui.dialog) closeDialog(); | |
| }); | |
| ui.dialog.addEventListener("cancel", closeDialog); | |
| } | |
| async function loadSnapshot() { | |
| ui.content.replaceChildren(emptyState("Daten werden geladen.")); | |
| try { | |
| const response = await fetch("data/harness-snapshot.json", { cache: "no-store" }); | |
| if (!response.ok) throw new Error(`HTTP ${response.status}`); | |
| state.snapshot = normalizeSnapshot(await response.json()); | |
| populateHarnessFilter(); | |
| render(); | |
| } catch (error) { | |
| renderMissingSnapshot(error); | |
| } | |
| } | |
| function renderMissingSnapshot(error) { | |
| ui.subtitle.textContent = "Kein lesbarer Snapshot."; | |
| ui.meta.replaceChildren(); | |
| ui.content.replaceChildren( | |
| emptyState( | |
| "Snapshot fehlt oder ist nicht lesbar.", | |
| "Führe im Ordner `sidebyside` den Befehl `python3 scripts/build_snapshot.py` aus und lade die Seite über einen lokalen HTTP-Server neu.", | |
| error ? `Technischer Hinweis: ${error.message}` : "", | |
| ), | |
| ); | |
| } | |
| function render() { | |
| if (!state.snapshot) return; | |
| const snapshot = state.snapshot; | |
| const harnesses = filterHarnesses(snapshot.harnesses); | |
| const items = filterItems(snapshot.items); | |
| const discoveries = filterDiscoveries(snapshot.discoveries); | |
| ui.subtitle.textContent = snapshot.meta.generated_at | |
| ? `Stand ${formatDate(snapshot.meta.generated_at)}` | |
| : "Stand unbekannt"; | |
| ui.meta.replaceChildren( | |
| statPill("Harnesses", snapshot.harnesses.length), | |
| statPill("Inventar", snapshot.items.length), | |
| statPill("Funde", snapshot.discoveries.length), | |
| statPill("Privat", snapshot.meta.privacy_mode || "lokal"), | |
| ); | |
| updateNav(); | |
| const titleMap = { | |
| overview: "Vergleich", | |
| usage: "Meine Nutzung", | |
| inventory: "Skills & Commands", | |
| discoveries: "Unentdeckt", | |
| sources: "Quellen", | |
| }; | |
| ui.viewTitle.textContent = titleMap[state.view] || "Vergleich"; | |
| const node = { | |
| overview: () => renderOverview(snapshot, harnesses, discoveries), | |
| usage: () => renderUsage(harnesses), | |
| inventory: () => renderInventory(items, snapshot.items.length), | |
| discoveries: () => renderDiscoveries(discoveries), | |
| sources: () => renderSources(harnesses, snapshot), | |
| }[state.view](); | |
| ui.content.replaceChildren(node); | |
| } | |
| function updateNav() { | |
| ui.viewNav.querySelectorAll("[data-view]").forEach((button) => { | |
| button.classList.toggle("active", button.dataset.view === state.view); | |
| }); | |
| } | |
| function populateHarnessFilter() { | |
| ui.harness.replaceChildren(option("all", "Alle Harnesses")); | |
| for (const harness of state.snapshot.harnesses) { | |
| ui.harness.append(option(harness.id, harness.name)); | |
| } | |
| } | |
| function normalizeSnapshot(raw) { | |
| const meta = asObject(raw.meta); | |
| const harnesses = asArray(raw.harnesses).map(normalizeHarness); | |
| const comparisonRows = asArray(raw.comparison).map(normalizeComparisonRow); | |
| const discoveries = dedupeDiscoveries([ | |
| ...asArray(raw.discoveries).map((entry) => normalizeDiscovery(entry, null)), | |
| ...harnesses.flatMap((harness) => harness.discoveries.map((entry) => normalizeDiscovery(entry, harness))), | |
| ]); | |
| const items = harnesses.flatMap(collectDetailItems); | |
| return { meta, harnesses, comparisonRows, discoveries, items }; | |
| } | |
| function normalizeHarness(raw) { | |
| const base = asObject(raw); | |
| const extensionObject = asObject(base.extensions); | |
| const skills = bucketEntries(extensionObject, "skills"); | |
| const commands = bucketEntries(extensionObject, "commands"); | |
| const agents = bucketEntries(extensionObject, "agents"); | |
| const extensions = flattenExtensionEntries(extensionObject).filter((item) => !["skills", "commands", "agents"].includes(item.bucket)); | |
| const harness = { | |
| id: textValue(base.id), | |
| name: textValue(base.name || base.id || "Unbenannt"), | |
| status: statusText(base.status), | |
| rawStatus: asObject(base.status), | |
| config: asObject(base.config), | |
| rawUsage: asObject(base.usage), | |
| instructionLayers: collectArrayLike(base.instruction_layers), | |
| discoveries: collectArrayLike(base.discoveries), | |
| sources: collectArrayLike(base.sources), | |
| warnings: collectArrayLike(base.warnings), | |
| skills, | |
| commands, | |
| agents, | |
| extensions, | |
| }; | |
| harness.metrics = metricsForHarness(harness); | |
| return harness; | |
| } | |
| function metricsForHarness(harness) { | |
| const status = harness.rawStatus; | |
| const usage = harness.rawUsage; | |
| const counts = asObject(status.counts); | |
| return { | |
| model: modelLabel(harness.config), | |
| sessions: firstNumber(status.thread_count, status.session_count, counts.project_transcripts, counts.sessions, usage.sessions?.db_count), | |
| tools: firstNumber(status.log_rows, usage.tools?.observed_count, counts.tools, usage.tools?.db_count), | |
| extensions: harness.skills.length + harness.commands.length + harness.agents.length + harness.extensions.length, | |
| layers: harness.instructionLayers.length, | |
| discoveries: harness.discoveries.length, | |
| sources: harness.sources.length, | |
| }; | |
| } | |
| function normalizeComparisonRow(raw) { | |
| const base = asObject(raw); | |
| return { | |
| label: textValue(base.label || base.metric || "Vergleich"), | |
| note: textValue(base.note || ""), | |
| values: asObject(base.values), | |
| }; | |
| } | |
| function normalizeDiscovery(raw, harness) { | |
| const base = asObject(raw); | |
| const ownerId = textValue(base.harness_id || base.harnessId || harness?.id || ""); | |
| const ownerName = textValue(base.harness || harness?.name || "Global"); | |
| const title = discoveryTitle(base); | |
| return { | |
| kind: "discovery", | |
| id: `${ownerId}:${title}:${textValue(base.path || base.table || "")}`, | |
| name: title, | |
| category: textValue(base.kind || base.category || "discovery"), | |
| path: textValue(base.path || base.table || ""), | |
| summary: textValue(base.summary || base.reason || base.candidate || base.message || ""), | |
| confidence: confidenceValue(base.confidence), | |
| score: numberValue(base.score), | |
| harnessId: ownerId, | |
| harnessName: ownerName, | |
| raw: base, | |
| }; | |
| } | |
| function discoveryTitle(base) { | |
| const kind = textValue(base.kind); | |
| const name = textValue(base.name); | |
| const table = textValue(base.table); | |
| if (kind === "memory-store") return "Codex Memories sind leer"; | |
| if (kind === "hooks") return "Codex Hooks sind leer"; | |
| if (kind === "sessions") return "Session Store ist leer"; | |
| if (kind === "plugin" && name === "github") return "Nur GitHub ist als Codex-Plugin aktiv"; | |
| if (kind === "plugin") return "Plugin-Marktplatz ist kaum genutzt"; | |
| if (kind === "database" && table) return `${table} ist leer`; | |
| if (kind === "dormant_surface") return "Plugin-System wirkt ungenutzt"; | |
| if (kind === "feature_gap") return "Changelog-Features ohne Nutzungsspur"; | |
| if (kind === "lightly_used_surface") return "Große Oberfläche, wenig belegte Nutzung"; | |
| if (kind === "sparse_history") return "Session-History ist dünn"; | |
| if (kind === "empty_channels") return "Hermes Channels sind leer"; | |
| if (kind === "gateway_startup_failed") return "Hermes Gateway startet nicht sauber"; | |
| if (kind.endsWith("_disabled")) return `${kind.replaceAll("_", " ")} ist deaktiviert`; | |
| if (kind === "many_skills_low_usage") return "Viele Hermes-Skills kaum genutzt"; | |
| return textValue(base.name || base.title || base.message || base.candidate || base.kind || "Fund"); | |
| } | |
| function collectDetailItems(harness) { | |
| return [ | |
| ...harness.skills.map((item) => detailItem(item, harness, "skill")), | |
| ...harness.commands.map((item) => detailItem(item, harness, "command")), | |
| ...harness.agents.map((item) => detailItem(item, harness, "agent")), | |
| ...harness.instructionLayers.map((item) => detailItem(item, harness, "layer")), | |
| ...harness.extensions.map((item) => detailItem(item, harness, "extension")), | |
| ...usageEntries(harness.rawUsage).map((item) => detailItem(item, harness, "usage")), | |
| ]; | |
| } | |
| function detailItem(raw, harness, kind) { | |
| const base = asObject(raw); | |
| return { | |
| kind, | |
| harnessId: harness.id, | |
| harnessName: harness.name, | |
| id: `${harness.id}:${kind}:${textValue(base.path || base.name || base.label || Math.random())}`, | |
| name: textValue(base.name || base.label || base.id || base.layer || base.kind || kind), | |
| category: textValue(base.category || base.group || base.bucket || base.kind || kind), | |
| path: textValue(base.path || base.file || base.location || ""), | |
| summary: textValue(base.summary || base.description || base.preview || base.detail || summarizeValue(base.value)), | |
| usage: base.usage || base.count || base.value || "", | |
| confidence: confidenceValue(base.confidence), | |
| raw: base, | |
| }; | |
| } | |
| function renderOverview(snapshot, harnesses, discoveries) { | |
| const wrapper = viewStack(); | |
| wrapper.append( | |
| sectionHeader("Direkter Vergleich", "Die drei Harnesses als Arbeitswerkzeuge, nicht als Rohdaten-Dump."), | |
| harnessOverviewGrid(harnesses), | |
| focusPanel(discoveries), | |
| comparisonTable(snapshot.comparisonRows, harnesses), | |
| ); | |
| return wrapper; | |
| } | |
| function harnessOverviewGrid(harnesses) { | |
| const grid = el("div", "harness-overview"); | |
| for (const harness of harnesses) { | |
| const card = el("article", `harness-tile ${harness.id}`); | |
| card.append( | |
| row( | |
| stack(title(harness.name, "h3"), muted(harness.metrics.model || "Modell unbekannt")), | |
| statusBadge(harness.status), | |
| ), | |
| metricStrip([ | |
| ["Sessions", harness.metrics.sessions], | |
| ["Tools", harness.metrics.tools], | |
| ["Inventar", harness.metrics.extensions], | |
| ["Funde", harness.metrics.discoveries], | |
| ]), | |
| conciseInsight(harness), | |
| ); | |
| grid.append(card); | |
| } | |
| return grid; | |
| } | |
| function conciseInsight(harness) { | |
| const firstDiscovery = normalizeDiscovery(harness.discoveries[0] || {}, harness); | |
| const box = el("div", "insight-line"); | |
| box.append( | |
| tag(harness.id), | |
| textSpan(firstDiscovery.name || "Keine priorisierte Lücke im Snapshot."), | |
| ); | |
| return box; | |
| } | |
| function focusPanel(discoveries) { | |
| const panel = el("section", "focus-panel"); | |
| panel.append(sectionHeader("Fokus zuerst", "Die wichtigsten unentdeckten oder untergenutzten Features.")); | |
| const list = el("div", "focus-list"); | |
| balancedFocus(discoveries).forEach((item, index) => { | |
| const button = el("button", "focus-item"); | |
| button.type = "button"; | |
| button.addEventListener("click", () => openDetail(item)); | |
| button.append( | |
| textSpan(String(index + 1), "rank"), | |
| stack(strong(item.name), muted(`${item.harnessName} · Score ${formatNumber(item.score)} · ${confidenceLabel(item.confidence)}`)), | |
| ); | |
| list.append(button); | |
| }); | |
| panel.append(list); | |
| return panel; | |
| } | |
| function balancedFocus(discoveries) { | |
| const byHarness = discoveries.reduce((acc, item) => { | |
| const key = item.harnessId || item.harnessName || "global"; | |
| acc[key] = acc[key] || []; | |
| acc[key].push(item); | |
| return acc; | |
| }, {}); | |
| const firstPass = Object.values(byHarness).map((items) => items[0]).filter(Boolean); | |
| const rest = discoveries.filter((item) => !firstPass.includes(item)); | |
| return [...firstPass, ...rest].slice(0, 5); | |
| } | |
| function comparisonTable(rows, harnesses) { | |
| const section = el("section", "panel-section"); | |
| section.append(sectionHeader("Matrix", "Normierte Kennzahlen aus dem lokalen Snapshot.")); | |
| const wrap = el("div", "table-wrap"); | |
| const table = el("table"); | |
| const head = el("tr"); | |
| head.append(th("Metrik"), ...harnesses.map((harness) => th(harness.name))); | |
| table.append(el("thead", "", head)); | |
| const body = el("tbody"); | |
| for (const rowData of rows) { | |
| const tr = el("tr"); | |
| const label = el("td"); | |
| label.append(strong(rowData.label), rowData.note ? muted(rowData.note) : ""); | |
| tr.append(label); | |
| harnesses.forEach((harness) => tr.append(td(formatComparisonValue(rowData.values[harness.id] ?? rowData.values[harness.name])))); | |
| body.append(tr); | |
| } | |
| table.append(body); | |
| wrap.append(table); | |
| section.append(wrap); | |
| return section; | |
| } | |
| function renderUsage(harnesses) { | |
| const wrapper = viewStack(); | |
| wrapper.append(sectionHeader("Meine Nutzung", "Was deine lokalen Spuren über deinen tatsächlichen Einsatz zeigen.")); | |
| const grid = el("div", "usage-grid"); | |
| harnesses.forEach((harness) => grid.append(usagePanel(harness))); | |
| wrapper.append(grid); | |
| return wrapper; | |
| } | |
| function usagePanel(harness) { | |
| const panel = el("article", `usage-panel ${harness.id}`); | |
| panel.append(row(stack(title(harness.name, "h3"), muted(harness.metrics.model)), statusBadge(harness.status))); | |
| panel.append(metricStrip([ | |
| ["Sessions", harness.metrics.sessions], | |
| ["Toolcalls", harness.metrics.tools], | |
| ["Layer", harness.metrics.layers], | |
| ["Quellen", harness.metrics.sources], | |
| ])); | |
| panel.append(toolBlock(harness), compactConfig(harness)); | |
| return panel; | |
| } | |
| function toolBlock(harness) { | |
| const tools = topTools(harness); | |
| const block = el("div", "data-block"); | |
| block.append(strong("Top-Tools")); | |
| if (!tools.length) { | |
| block.append(muted("Keine Tool-Nutzung im Snapshot gefunden.")); | |
| return block; | |
| } | |
| const max = Math.max(...tools.map((item) => numberValue(item.count)), 1); | |
| tools.slice(0, 8).forEach((item) => { | |
| const bar = el("div", "bar-row"); | |
| bar.append(textSpan(item.name), textSpan(formatNumber(item.count), "bar-value")); | |
| const meter = el("div", "bar-meter"); | |
| const fill = el("span"); | |
| fill.style.width = `${Math.max(6, (numberValue(item.count) / max) * 100)}%`; | |
| meter.append(fill); | |
| block.append(bar, meter); | |
| }); | |
| return block; | |
| } | |
| function compactConfig(harness) { | |
| const rows = [ | |
| ["Modell", harness.metrics.model], | |
| ["Status", harness.status], | |
| ["Skills", harness.skills.length], | |
| ["Commands", harness.commands.length], | |
| ["Agents", harness.agents.length], | |
| ]; | |
| const block = el("div", "data-block"); | |
| block.append(strong("Profil")); | |
| rows.forEach(([label, value]) => block.append(keyValue(label, formatAny(value)))); | |
| return block; | |
| } | |
| function renderInventory(items, total) { | |
| const wrapper = viewStack(); | |
| wrapper.append(sectionHeader("Skills & Commands", `${items.length} von ${total} Einträgen nach aktuellen Filtern.`)); | |
| const typeCounts = countBy(items, "kind"); | |
| wrapper.append(metricStrip([ | |
| ["Skills", typeCounts.skill || 0], | |
| ["Commands", typeCounts.command || 0], | |
| ["Agents", typeCounts.agent || 0], | |
| ["Layer", typeCounts.layer || 0], | |
| ["Erweiterungen", typeCounts.extension || 0], | |
| ])); | |
| const list = el("div", "inventory-list"); | |
| items.slice(0, 160).forEach((item) => list.append(inventoryRow(item))); | |
| if (items.length > 160) list.append(emptyState(`${items.length - 160} weitere Treffer ausgeblendet. Nutze Suche oder Filter.`)); | |
| wrapper.append(list); | |
| return wrapper; | |
| } | |
| function inventoryRow(item) { | |
| const button = el("button", "inventory-row"); | |
| button.type = "button"; | |
| button.addEventListener("click", () => openDetail(item)); | |
| button.append( | |
| tag(kindLabel(item.kind)), | |
| stack(strong(item.name), muted([item.harnessName, item.category, item.path].filter(Boolean).join(" · "))), | |
| textSpan(item.summary || "Keine Kurzbeschreibung.", "row-summary"), | |
| ); | |
| return button; | |
| } | |
| function renderDiscoveries(discoveries) { | |
| const wrapper = viewStack(); | |
| wrapper.append(sectionHeader("Unentdeckt", `${discoveries.length} priorisierte Hinweise aus deinen lokalen Harness-Spuren.`)); | |
| const list = el("div", "discovery-list"); | |
| discoveries.forEach((item, index) => { | |
| const card = el("button", "discovery-row"); | |
| card.type = "button"; | |
| card.addEventListener("click", () => openDetail(item)); | |
| card.append( | |
| textSpan(String(index + 1), "rank"), | |
| stack(strong(item.name), muted(item.summary || "Keine Beschreibung.")), | |
| stack(tag(item.harnessName), muted(`Score ${formatNumber(item.score)} · ${confidenceLabel(item.confidence)}`)), | |
| ); | |
| list.append(card); | |
| }); | |
| wrapper.append(list); | |
| return wrapper; | |
| } | |
| function renderSources(harnesses, snapshot) { | |
| const wrapper = viewStack(); | |
| wrapper.append( | |
| sectionHeader("Quellen & Datenschutz", "Gruppierte Quellen statt endloser Dateiliste."), | |
| privacyPanel(snapshot), | |
| ); | |
| harnesses.forEach((harness) => wrapper.append(sourceGroup(harness))); | |
| return wrapper; | |
| } | |
| function privacyPanel(snapshot) { | |
| const panel = el("section", "privacy-panel"); | |
| panel.append( | |
| keyValue("Privacy Mode", snapshot.meta.privacy_mode || "summaries-only"), | |
| keyValue("Ausgeschlossen", asArray(snapshot.meta.excluded_patterns).join(", ")), | |
| keyValue("Warnungen", asArray(snapshot.meta.warnings).length || "keine"), | |
| ); | |
| return panel; | |
| } | |
| function sourceGroup(harness) { | |
| const section = el("section", "source-group"); | |
| section.append(row(stack(title(harness.name, "h3"), muted(`${harness.sources.length} Quellen`)), statusBadge(harness.status))); | |
| const kinds = countBy(harness.sources.map(normalizeSource), "kind"); | |
| section.append(metricStrip(Object.entries(kinds).slice(0, 5).map(([kind, count]) => [kind, count]))); | |
| const list = el("div", "source-list"); | |
| harness.sources.slice(0, 22).forEach((source) => { | |
| const item = normalizeSource(source); | |
| list.append(keyValue(item.kind || "Quelle", item.path || item.name || "")); | |
| }); | |
| if (harness.sources.length > 22) list.append(muted(`${harness.sources.length - 22} weitere Quellen im Snapshot.`)); | |
| section.append(list); | |
| return section; | |
| } | |
| function filterHarnesses(harnesses) { | |
| return harnesses.filter((harness) => state.harness === "all" || harness.id === state.harness); | |
| } | |
| function filterItems(items) { | |
| return items.filter((item) => { | |
| if (state.harness !== "all" && item.harnessId !== state.harness) return false; | |
| if (state.type !== "all" && item.kind !== state.type) return false; | |
| return matchesQuery(item); | |
| }).sort((a, b) => `${a.harnessName}:${a.kind}:${a.name}`.localeCompare(`${b.harnessName}:${b.kind}:${b.name}`, "de")); | |
| } | |
| function filterDiscoveries(discoveries) { | |
| return discoveries | |
| .filter((item) => { | |
| if (state.harness !== "all" && item.harnessId !== state.harness) return false; | |
| if (state.type !== "all" && state.type !== "discovery") return false; | |
| return matchesQuery(item); | |
| }) | |
| .sort((a, b) => numberValue(b.score) - numberValue(a.score) || confidenceValue(b.confidence) - confidenceValue(a.confidence)); | |
| } | |
| function matchesQuery(item) { | |
| if (!state.query) return true; | |
| return [item.name, item.kind, item.category, item.path, item.summary, item.harnessName] | |
| .map((value) => textValue(value).toLowerCase()) | |
| .join(" ") | |
| .includes(state.query); | |
| } | |
| function openDetail(item) { | |
| state.selectedItem = item; | |
| state.lastFocused = document.activeElement; | |
| ui.detailKind.textContent = `${item.harnessName || "Global"} · ${kindLabel(item.kind)}`; | |
| ui.detailTitle.textContent = item.name; | |
| ui.detailSummary.textContent = item.summary || "Keine Kurzbeschreibung im Snapshot."; | |
| ui.detailMeta.replaceChildren( | |
| ...metaPair("Kategorie", item.category || "unbekannt"), | |
| ...metaPair("Pfad", item.path || "kein Pfad"), | |
| ...metaPair("Nutzung", formatAny(item.usage)), | |
| ...metaPair("Confidence", confidenceLabel(item.confidence)), | |
| ); | |
| ui.detailRaw.textContent = clippedJson(item.raw || item); | |
| ui.dialog.showModal ? ui.dialog.showModal() : ui.dialog.setAttribute("open", ""); | |
| } | |
| function closeDialog() { | |
| state.selectedItem = null; | |
| if (ui.dialog.open) ui.dialog.close(); | |
| if (state.lastFocused && state.lastFocused.focus) state.lastFocused.focus(); | |
| } | |
| function viewStack() { | |
| return el("div", "view-stack"); | |
| } | |
| function sectionHeader(titleText, copy) { | |
| const header = el("header", "section-header"); | |
| header.append(stack(title(titleText, "h2"), muted(copy))); | |
| return header; | |
| } | |
| function metricStrip(entries) { | |
| const strip = el("div", "metric-strip"); | |
| entries.forEach(([label, value]) => { | |
| const metric = el("div", "metric"); | |
| metric.append(textSpan(label, "metric-label"), textSpan(formatAny(value), "metric-value")); | |
| strip.append(metric); | |
| }); | |
| return strip; | |
| } | |
| function keyValue(key, value) { | |
| const rowNode = el("div", "kv-row"); | |
| rowNode.append(textSpan(key, "kv-key"), textSpan(formatAny(value), "kv-value")); | |
| return rowNode; | |
| } | |
| function statusBadge(status) { | |
| const badge = el("span", `status-badge ${statusState(status)}`); | |
| badge.textContent = status; | |
| return badge; | |
| } | |
| function statPill(label, value) { | |
| const pill = el("span", "stat-pill"); | |
| pill.append(textSpan(label), strong(formatAny(value))); | |
| return pill; | |
| } | |
| function tag(text) { | |
| const node = el("span", "tag"); | |
| node.textContent = text || "n/a"; | |
| return node; | |
| } | |
| function option(value, label) { | |
| const node = document.createElement("option"); | |
| node.value = value; | |
| node.textContent = label; | |
| return node; | |
| } | |
| function th(text) { | |
| const node = document.createElement("th"); | |
| node.textContent = text; | |
| return node; | |
| } | |
| function td(text) { | |
| const node = document.createElement("td"); | |
| node.textContent = text; | |
| return node; | |
| } | |
| function title(text, level = "h3") { | |
| const node = document.createElement(level); | |
| node.textContent = text; | |
| return node; | |
| } | |
| function strong(text) { | |
| const node = document.createElement("strong"); | |
| node.textContent = text; | |
| return node; | |
| } | |
| function muted(text) { | |
| const node = el("p", "muted"); | |
| node.textContent = text; | |
| return node; | |
| } | |
| function textSpan(text, className = "") { | |
| const node = el("span", className); | |
| node.textContent = text; | |
| return node; | |
| } | |
| function stack(...children) { | |
| return el("div", "stack", ...children.filter(Boolean)); | |
| } | |
| function row(...children) { | |
| return el("div", "row", ...children.filter(Boolean)); | |
| } | |
| function el(tagName, className = "", ...children) { | |
| const node = document.createElement(tagName); | |
| if (className) node.className = className; | |
| children.flat().forEach((child) => { | |
| if (child === null || child === undefined || child === "") return; | |
| node.append(child); | |
| }); | |
| return node; | |
| } | |
| function emptyState(titleText, copy = "", detail = "") { | |
| const node = el("div", "empty-state"); | |
| node.append(strong(titleText)); | |
| if (copy) node.append(muted(copy)); | |
| if (detail) node.append(muted(detail)); | |
| return node; | |
| } | |
| function bucketEntries(extensionObject, bucket) { | |
| return collectArrayLike(extensionObject[bucket]).map((entry) => { | |
| const base = asObject(entry); | |
| return { ...base, bucket, category: base.category || base.group || base.kind || bucket }; | |
| }); | |
| } | |
| function flattenExtensionEntries(extensionObject) { | |
| return Object.entries(extensionObject).flatMap(([bucket, value]) => ( | |
| collectArrayLike(value).map((entry) => ({ ...asObject(entry), bucket, category: asObject(entry).category || bucket })) | |
| )); | |
| } | |
| function usageEntries(usage) { | |
| return Object.entries(asObject(usage)).map(([key, value]) => ({ | |
| name: key, | |
| category: "usage", | |
| value, | |
| summary: summarizeValue(value), | |
| })); | |
| } | |
| function topTools(harness) { | |
| const usage = harness.rawUsage; | |
| if (Array.isArray(usage.tools?.top)) return usage.tools.top.map((item) => ({ name: item.name || item.target || "tool", count: item.count || item.n || 0 })); | |
| if (Array.isArray(usage.log_target_counts)) return usage.log_target_counts.map((item) => ({ name: item.target || item.name || "tool", count: item.count || item.n || 0 })); | |
| return []; | |
| } | |
| function modelLabel(config) { | |
| const model = config.model; | |
| if (model && typeof model === "object") { | |
| return [model.default || model.model, model.provider].filter(Boolean).join(" · "); | |
| } | |
| return textValue(model || config.default_model || config.provider || "unbekannt"); | |
| } | |
| function firstNumber(...values) { | |
| for (const value of values) { | |
| const number = numberValue(value); | |
| if (number > 0) return number; | |
| } | |
| return 0; | |
| } | |
| function countBy(items, key) { | |
| return items.reduce((acc, item) => { | |
| const value = textValue(item[key] || "unbekannt"); | |
| acc[value] = (acc[value] || 0) + 1; | |
| return acc; | |
| }, {}); | |
| } | |
| function dedupeDiscoveries(discoveries) { | |
| const seen = new Set(); | |
| return discoveries.filter((item) => { | |
| const key = `${item.harnessId}:${item.name}:${item.summary}`; | |
| if (seen.has(key)) return false; | |
| seen.add(key); | |
| return true; | |
| }); | |
| } | |
| function normalizeSource(source) { | |
| return typeof source === "string" ? { kind: "Quelle", path: source } : asObject(source); | |
| } | |
| function statusText(value) { | |
| if (typeof value === "string") return value || "unknown"; | |
| const base = asObject(value); | |
| if (base.gateway?.startup_failed || base.flags?.gateway_startup_failed) return "partial"; | |
| if (base.exists && Object.values(base.exists).some((entry) => entry === false)) return "partial"; | |
| if (Object.keys(base).length) return "active"; | |
| return "unknown"; | |
| } | |
| function statusState(status) { | |
| const text = textValue(status).toLowerCase(); | |
| if (text.includes("partial") || text.includes("warn")) return "warn"; | |
| if (text.includes("fail") || text.includes("error") || text.includes("missing")) return "bad"; | |
| return "ok"; | |
| } | |
| function kindLabel(kind) { | |
| return { | |
| skill: "Skill", | |
| command: "Befehl", | |
| agent: "Agent", | |
| layer: "Instruktion", | |
| extension: "Erweiterung", | |
| usage: "Nutzung", | |
| discovery: "Fund", | |
| }[kind] || kind; | |
| } | |
| function formatComparisonValue(value) { | |
| if (value === undefined || value === null || value === "") return "n/a"; | |
| if (typeof value === "number") return formatNumber(value); | |
| if (typeof value === "string" && /^\d+$/.test(value)) return formatNumber(value); | |
| return String(value); | |
| } | |
| function formatDate(value) { | |
| const date = new Date(value); | |
| if (Number.isNaN(date.getTime())) return textValue(value); | |
| return new Intl.DateTimeFormat("de-DE", { dateStyle: "medium", timeStyle: "short" }).format(date); | |
| } | |
| function formatNumber(value) { | |
| const number = Number(value); | |
| if (!Number.isFinite(number)) return "0"; | |
| return new Intl.NumberFormat("de-DE").format(number); | |
| } | |
| function formatAny(value) { | |
| if (value === null || value === undefined || value === "") return "n/a"; | |
| if (typeof value === "number") return formatNumber(value); | |
| if (typeof value === "boolean") return value ? "Ja" : "Nein"; | |
| if (typeof value === "string") return value; | |
| if (Array.isArray(value)) return `${value.length} Einträge`; | |
| if (typeof value === "object") return summarizeValue(value); | |
| return String(value); | |
| } | |
| function summarizeValue(value) { | |
| if (value === null || value === undefined) return ""; | |
| if (Array.isArray(value)) return `${value.length} Einträge`; | |
| if (typeof value === "object") { | |
| const keys = Object.keys(value).slice(0, 6); | |
| return keys.length ? `Felder: ${keys.join(", ")}` : "Leeres Objekt"; | |
| } | |
| return textValue(value); | |
| } | |
| function confidenceLabel(value) { | |
| const confidence = confidenceValue(value); | |
| return confidence ? `${Math.round(confidence * 100)}%` : "n/a"; | |
| } | |
| function confidenceValue(value) { | |
| if (typeof value === "number") return value > 1 ? value / 100 : value; | |
| const text = textValue(value).toLowerCase(); | |
| if (text.startsWith("high")) return 1; | |
| if (text.startsWith("medium")) return 0.72; | |
| if (text.startsWith("low")) return 0.45; | |
| return numberValue(value); | |
| } | |
| function numberValue(value) { | |
| const number = Number(value); | |
| return Number.isFinite(number) ? number : 0; | |
| } | |
| function textValue(value) { | |
| if (value === null || value === undefined) return ""; | |
| if (typeof value === "string") return value; | |
| if (typeof value === "number" || typeof value === "boolean") return String(value); | |
| if (Array.isArray(value)) return value.map(textValue).filter(Boolean).join(", "); | |
| if (typeof value === "object") return textValue(value.name || value.label || value.id || value.path || value.summary || value.description || ""); | |
| return String(value); | |
| } | |
| function clippedJson(value) { | |
| let text = ""; | |
| try { | |
| text = JSON.stringify(value, null, 2); | |
| } catch { | |
| text = String(value); | |
| } | |
| return text.length > 5000 ? `${text.slice(0, 5000)}\n... gekürzt ...` : text; | |
| } | |
| function collectArrayLike(value) { | |
| if (!value) return []; | |
| if (Array.isArray(value)) return value; | |
| if (typeof value === "object") return Object.values(value); | |
| return [value]; | |
| } | |
| function asArray(value) { | |
| return Array.isArray(value) ? value : []; | |
| } | |
| function asObject(value) { | |
| return value && typeof value === "object" && !Array.isArray(value) ? value : {}; | |
| } | |
| function metaPair(label, value) { | |
| const dt = document.createElement("dt"); | |
| dt.textContent = label; | |
| const dd = document.createElement("dd"); | |
| dd.textContent = formatAny(value); | |
| return [dt, dd]; | |
| } | |