- markdown-validator: deepseek-v4-pro-max → nemotron-3-nano (90% cost cut) - release-manager: glm-5.1 → kimi-k2.6 (+2 matrix, 1M context for diffs) - capability-analyst: glm-5.1 → deepseek-v4-pro-max (+4 matrix, 1M ctx) - browser-automation: qwen3-coder → deepseek-v4-flash (3× faster inference) - history-miner: nemotron-3-super → qwen3.5-122b (+14 IF, 12.4M pulls)
2781 lines
107 KiB
HTML
2781 lines
107 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>APAW Agent Evolution Dashboard</title>
|
||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
||
<style>
|
||
:root {
|
||
--bg-deep: #0a0f1a;
|
||
--bg-panel: #0f1525;
|
||
--bg-card: #141c2e;
|
||
--bg-card-hover: #1a2540;
|
||
--border: #1e2d45;
|
||
--border-bright: #2a4060;
|
||
--text-primary: #e8f1ff;
|
||
--text-secondary: #8ba3c0;
|
||
--text-muted: #5a7090;
|
||
--accent-cyan: #00d4ff;
|
||
--accent-green: #00ff94;
|
||
--accent-orange: #ff9f43;
|
||
--accent-red: #ff4757;
|
||
--accent-purple: #a855f7;
|
||
--accent-blue: #3b82f6;
|
||
--accent-yellow: #facc15;
|
||
--glow-cyan: rgba(0,212,255,0.15);
|
||
--glow-green: rgba(0,255,148,0.1);
|
||
--glow-purple: rgba(168,85,247,0.1);
|
||
}
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body {
|
||
font-family: 'Inter', sans-serif;
|
||
background: var(--bg-deep);
|
||
color: var(--text-primary);
|
||
min-height: 100vh;
|
||
}
|
||
body::before {
|
||
content: '';
|
||
position: fixed;
|
||
inset: 0;
|
||
background:
|
||
radial-gradient(ellipse at 20% 20%, rgba(0,212,255,0.08) 0%, transparent 50%),
|
||
radial-gradient(ellipse at 80% 80%, rgba(168,85,247,0.06) 0%, transparent 50%);
|
||
pointer-events: none;
|
||
z-index: 0;
|
||
}
|
||
.container {
|
||
max-width: 1600px;
|
||
margin: 0 auto;
|
||
padding: 24px 16px;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
/* Header */
|
||
.header { text-align: center; margin-bottom: 32px; }
|
||
.header h1 {
|
||
font-size: 2.2em;
|
||
font-weight: 800;
|
||
background: linear-gradient(135deg, var(--accent-cyan), var(--accent-green));
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
margin-bottom: 8px;
|
||
}
|
||
.header .sub {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.85em;
|
||
color: var(--text-muted);
|
||
}
|
||
.header .meta {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 24px;
|
||
margin-top: 12px;
|
||
font-size: 0.8em;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
/* Tabs */
|
||
.tabs {
|
||
display: flex;
|
||
gap: 4px;
|
||
background: var(--bg-panel);
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
padding: 4px;
|
||
margin-bottom: 24px;
|
||
overflow-x: auto;
|
||
}
|
||
.tab-btn {
|
||
flex: 1;
|
||
min-width: 100px;
|
||
padding: 10px 16px;
|
||
background: none;
|
||
border: none;
|
||
color: var(--text-secondary);
|
||
font-family: 'Inter', sans-serif;
|
||
font-size: 0.85em;
|
||
font-weight: 600;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.25s;
|
||
white-space: nowrap;
|
||
}
|
||
.tab-btn:hover { color: var(--text-primary); background: var(--bg-card); }
|
||
.tab-btn.active {
|
||
color: var(--bg-deep);
|
||
background: linear-gradient(135deg, var(--accent-cyan), var(--accent-green));
|
||
box-shadow: 0 0 20px var(--glow-cyan);
|
||
}
|
||
.tab-panel { display: none; animation: fadeUp 0.4s ease-out; }
|
||
.tab-panel.active { display: block; }
|
||
@keyframes fadeUp {
|
||
from { opacity: 0; transform: translateY(16px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
/* Stats */
|
||
.stats-row {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 14px;
|
||
margin-bottom: 24px;
|
||
}
|
||
.stat-card {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
padding: 18px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
transition: all 0.3s;
|
||
}
|
||
.stat-card:hover {
|
||
border-color: var(--accent-cyan);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 8px 32px var(--glow-cyan);
|
||
}
|
||
.stat-label {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.65em;
|
||
color: var(--text-muted);
|
||
text-transform: uppercase;
|
||
letter-spacing: 1.5px;
|
||
margin-bottom: 6px;
|
||
}
|
||
.stat-value { font-size: 2em; font-weight: 800; }
|
||
.stat-sub { font-size: 0.75em; color: var(--text-secondary); margin-top: 4px; }
|
||
.grad-cyan { background: linear-gradient(135deg, var(--accent-cyan), var(--accent-green)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
||
.grad-orange { background: linear-gradient(135deg, var(--accent-orange), var(--accent-yellow)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
||
.grad-purple { background: linear-gradient(135deg, var(--accent-purple), #e879f9); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
||
.grad-green { background: linear-gradient(135deg, var(--accent-green), #4ade80); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
||
.grad-red { background: linear-gradient(135deg, var(--accent-red), #ff6b81); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
||
|
||
/* Agent Grid */
|
||
.agents-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
||
gap: 16px;
|
||
}
|
||
.agent-card {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
transition: all 0.3s;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
.agent-card:hover {
|
||
border-color: var(--accent-cyan);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 8px 32px var(--glow-cyan);
|
||
}
|
||
.agent-card.has-history { border-left: 3px solid var(--accent-green); }
|
||
.agent-card.needs-update { border-left: 3px solid var(--accent-orange); }
|
||
.agent-card.is-new { border-left: 3px solid var(--accent-purple); }
|
||
|
||
.agent-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
margin-bottom: 12px;
|
||
}
|
||
.agent-name {
|
||
font-weight: 700;
|
||
font-size: 1.05em;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.agent-color {
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 3px;
|
||
flex-shrink: 0;
|
||
}
|
||
.agent-category {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.7em;
|
||
padding: 3px 8px;
|
||
border-radius: 12px;
|
||
background: rgba(0,212,255,0.1);
|
||
color: var(--accent-cyan);
|
||
}
|
||
.agent-model {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.78em;
|
||
color: var(--accent-green);
|
||
margin-bottom: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.agent-provider {
|
||
font-size: 0.7em;
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
background: rgba(0,255,148,0.1);
|
||
color: var(--accent-green);
|
||
}
|
||
.agent-desc {
|
||
font-size: 0.85em;
|
||
color: var(--text-secondary);
|
||
line-height: 1.5;
|
||
margin-bottom: 12px;
|
||
}
|
||
.agent-meta {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 8px;
|
||
padding-top: 12px;
|
||
border-top: 1px solid var(--border);
|
||
}
|
||
.agent-meta-item {
|
||
text-align: center;
|
||
}
|
||
.agent-meta-label {
|
||
font-size: 0.6em;
|
||
color: var(--text-muted);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
.agent-meta-value {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.9em;
|
||
font-weight: 600;
|
||
color: var(--text-primary);
|
||
}
|
||
.agent-history {
|
||
margin-top: 12px;
|
||
padding-top: 12px;
|
||
border-top: 1px dashed var(--border);
|
||
}
|
||
.history-title {
|
||
font-size: 0.7em;
|
||
color: var(--text-muted);
|
||
text-transform: uppercase;
|
||
margin-bottom: 8px;
|
||
}
|
||
.history-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
font-size: 0.75em;
|
||
padding: 6px 0;
|
||
border-bottom: 1px solid rgba(30,45,69,0.5);
|
||
}
|
||
.history-item:last-child { border-bottom: none; }
|
||
.history-date {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
color: var(--text-muted);
|
||
min-width: 100px;
|
||
}
|
||
.history-type {
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
font-size: 0.85em;
|
||
}
|
||
.history-type.model_change { background: rgba(0,212,255,0.15); color: var(--accent-cyan); }
|
||
.history-type.prompt_change { background: rgba(168,85,247,0.15); color: var(--accent-purple); }
|
||
.history-type.agent_created { background: rgba(0,255,148,0.15); color: var(--accent-green); }
|
||
|
||
/* Category Section */
|
||
.category-section { margin-bottom: 32px; }
|
||
.category-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
margin-bottom: 16px;
|
||
padding-bottom: 8px;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.category-title {
|
||
font-size: 1.1em;
|
||
font-weight: 700;
|
||
}
|
||
.category-count {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.7em;
|
||
padding: 3px 8px;
|
||
border-radius: 12px;
|
||
background: rgba(168,85,247,0.15);
|
||
color: var(--accent-purple);
|
||
}
|
||
|
||
/* Evolution Timeline */
|
||
.timeline-wrap {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
padding: 24px;
|
||
margin-bottom: 24px;
|
||
}
|
||
.timeline-title {
|
||
font-size: 1.1em;
|
||
font-weight: 700;
|
||
margin-bottom: 16px;
|
||
}
|
||
.timeline {
|
||
position: relative;
|
||
padding-left: 24px;
|
||
}
|
||
.timeline::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: 8px;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 2px;
|
||
background: var(--border);
|
||
}
|
||
.timeline-item {
|
||
position: relative;
|
||
padding: 12px 0 12px 24px;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.timeline-item:last-child { border-bottom: none; }
|
||
.timeline-item::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: -20px;
|
||
top: 18px;
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
background: var(--accent-cyan);
|
||
border: 2px solid var(--border);
|
||
}
|
||
.timeline-date {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.75em;
|
||
color: var(--text-muted);
|
||
}
|
||
.timeline-content {
|
||
font-size: 0.9em;
|
||
margin-top: 4px;
|
||
}
|
||
.timeline-agent {
|
||
font-weight: 600;
|
||
color: var(--accent-cyan);
|
||
}
|
||
.timeline-change {
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
/* Filter Row */
|
||
.filter-row {
|
||
display: flex;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
margin-bottom: 16px;
|
||
}
|
||
.filter-btn {
|
||
padding: 6px 14px;
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
color: var(--text-secondary);
|
||
border-radius: 20px;
|
||
font-size: 0.8em;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
font-family: 'Inter', sans-serif;
|
||
}
|
||
.filter-btn:hover, .filter-btn.active {
|
||
border-color: var(--accent-cyan);
|
||
color: var(--accent-cyan);
|
||
background: rgba(0,212,255,0.05);
|
||
}
|
||
|
||
/* Search */
|
||
.search-box {
|
||
position: relative;
|
||
margin-bottom: 20px;
|
||
}
|
||
.search-input {
|
||
width: 100%;
|
||
padding: 12px 16px 12px 40px;
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
color: var(--text-primary);
|
||
font-family: 'Inter', sans-serif;
|
||
font-size: 0.9em;
|
||
}
|
||
.search-input:focus {
|
||
outline: none;
|
||
border-color: var(--accent-cyan);
|
||
}
|
||
.search-icon {
|
||
position: absolute;
|
||
left: 14px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* Model Matrix */
|
||
.matrix-wrap {
|
||
overflow-x: auto;
|
||
border-radius: 12px;
|
||
border: 1px solid var(--border);
|
||
background: var(--bg-card);
|
||
padding: 20px;
|
||
}
|
||
.matrix-title {
|
||
font-size: 1.1em;
|
||
font-weight: 700;
|
||
margin-bottom: 16px;
|
||
}
|
||
.matrix-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 0.8em;
|
||
}
|
||
.matrix-table th {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.7em;
|
||
color: var(--text-muted);
|
||
text-transform: uppercase;
|
||
padding: 10px 8px;
|
||
text-align: left;
|
||
border-bottom: 2px solid var(--border);
|
||
position: sticky;
|
||
top: 0;
|
||
background: var(--bg-panel);
|
||
}
|
||
.matrix-table td {
|
||
padding: 10px 8px;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.matrix-table tr:hover td {
|
||
background: var(--bg-card-hover);
|
||
}
|
||
.score-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.score-bg {
|
||
width: 50px;
|
||
height: 5px;
|
||
background: var(--border);
|
||
border-radius: 3px;
|
||
overflow: hidden;
|
||
}
|
||
.score-fill {
|
||
height: 100%;
|
||
border-radius: 3px;
|
||
}
|
||
.score-fill.high { background: linear-gradient(90deg, var(--accent-green), #00ff94); }
|
||
.score-fill.medium { background: linear-gradient(90deg, var(--accent-orange), #ffc048); }
|
||
.score-fill.low { background: linear-gradient(90deg, var(--accent-red), #ff6b81); }
|
||
|
||
/* Heatmap */
|
||
.hm-wrap { overflow-x:auto; border-radius:11px; border:1px solid var(--border); background:var(--bg-card); padding:18px; margin-bottom:26px; }
|
||
.hm-title { font-weight:700; font-size:1.05em; }
|
||
.hm-sub { font-size:.76em; color:var(--text-muted); margin-bottom:14px; }
|
||
.hm-table { border-collapse:separate; border-spacing:2px; width:100%; }
|
||
.hm-table th { font-family:'JetBrains Mono',monospace; font-size:.62em; color:var(--text-muted); padding:8px 5px; text-align:center; white-space:nowrap; vertical-align:bottom; }
|
||
.hm-table th.hm-role { text-align:left; min-width:140px; font-size:.68em; padding-left:10px; }
|
||
.hm-table td { text-align:center; padding:6px 4px; font-family:'JetBrains Mono',monospace; font-size:.72em; font-weight:700; border-radius:6px; cursor:pointer; transition:all .15s cubic-bezier(.4,0,.2,1); min-width:42px; position:relative; line-height:1.4; }
|
||
.hm-table td:hover { transform:scale(1.1); z-index:2; box-shadow:0 4px 12px rgba(0,0,0,.35); }
|
||
.hm-table td.hm-r { text-align:left; font-family:'Inter',sans-serif; font-size:.82em; font-weight:600; color:var(--text-primary); cursor:default; padding-left:10px; }
|
||
.hm-table td.hm-r:hover { transform:none; box-shadow:none; }
|
||
.hm-star { position:absolute; top:2px; right:2px; font-size:.65em; text-shadow:0 1px 2px rgba(0,0,0,.5); }
|
||
.hm-cur { box-shadow:inset 0 0 0 2px var(--accent-cyan), 0 0 8px rgba(0,212,255,.35); border-radius:6px; }
|
||
.hm-cur::after { content:''; position:absolute; bottom:2px; left:50%; transform:translateX(-50%); width:8px; height:3px; background:var(--accent-cyan); border-radius:2px; }
|
||
.hm-if-warn { position:absolute; top:2px; left:2px; font-size:.6em; opacity:.8; }
|
||
|
||
/* Smooth gradient legend bar */
|
||
.hm-legend-wrap { margin-top:18px; padding:0 4px; }
|
||
.hm-legend-track { position:relative; height:22px; border-radius:11px; background:linear-gradient(90deg, rgba(0,255,148,.85) 0%, rgba(0,212,255,.75) 20%, rgba(59,130,246,.6) 40%, rgba(168,85,247,.45) 58%, rgba(255,159,67,.35) 75%, rgba(255,71,87,.3) 88%, rgba(90,104,128,.2) 100%); box-shadow:inset 0 1px 3px rgba(0,0,0,.3); }
|
||
.hm-legend-labels { display:flex; justify-content:space-between; align-items:center; margin-top:8px; padding:0 4px; }
|
||
.hm-legend-labels span { font-size:.68em; font-family:'JetBrains Mono',monospace; color:var(--text-muted); }
|
||
.hm-legend-left { color:var(--accent-green); }
|
||
.hm-legend-right { color:var(--accent-red); }
|
||
.hm-legend-marks { display:flex; justify-content:space-between; padding:0 2px; margin-top:3px; }
|
||
.hm-legend-marks span { font-size:.58em; font-family:'JetBrains Mono',monospace; color:var(--text-muted); min-width:20px; text-align:center; }
|
||
|
||
/* Heatmap Modal Tabs */
|
||
.hm-modal-tabs { display:flex; gap:3px; background:var(--bg-panel); border-bottom:1px solid var(--border); padding:4px 18px; }
|
||
.hm-tab-btn { padding:8px 16px; background:none; border:none; color:var(--text-secondary); font-family:'Inter'; font-size:.82em; font-weight:600; border-radius:8px; cursor:pointer; transition:all .25s; }
|
||
.hm-tab-btn.active { color:var(--bg-deep); background:linear-gradient(135deg,var(--accent-cyan),var(--accent-green)); }
|
||
.hm-tab-content { display:none; }
|
||
.hm-tab-content.active { display:block; }
|
||
.hm-model-timeline { display:flex; flex-direction:column; gap:12px; }
|
||
.hm-tl-item { display:flex; gap:14px; align-items:center; padding:10px; background:var(--bg-deep); border-radius:8px; border-left:3px solid var(--accent-cyan); }
|
||
.hm-tl-date { font-family:'JetBrains Mono',monospace; font-size:.72em; color:var(--text-muted); min-width:100px; }
|
||
.hm-tl-change { display:flex; align-items:center; gap:8px; }
|
||
.hm-tl-from { text-decoration:line-through; color:#ff6b81; background:rgba(255,71,87,.08); padding:2px 6px; border-radius:4px; }
|
||
.hm-tl-arrow { color:var(--accent-green); }
|
||
.hm-tl-to { color:var(--accent-green); background:rgba(0,255,148,.08); padding:2px 6px; border-radius:4px; font-weight:600; }
|
||
.hm-tl-current { border-left-color:var(--accent-green); background:rgba(0,255,148,.05); }
|
||
.hm-no-data { color:var(--text-muted); font-size:.9em; padding:16px; text-align:center; }
|
||
.hm-capabilities { display:flex; flex-wrap:wrap; gap:6px; }
|
||
.hm-cap-tag { padding:4px 10px; background:rgba(0,212,255,.1); border:1px solid var(--border); border-radius:16px; font-size:.78em; color:var(--accent-cyan); }
|
||
.hm-agent-desc { font-size:.9em; color:var(--text-secondary); line-height:1.5; margin-bottom:14px; padding:12px; background:var(--bg-deep); border-radius:8px; }
|
||
.hm-model-tl-score { margin-left:auto; font-family:'JetBrains Mono',monospace; font-size:.8em; color:var(--accent-cyan); }
|
||
|
||
/* Tooltip */
|
||
#ttOverlay { display:none; position:fixed; top:0;left:0;right:0;bottom:0; z-index:999; pointer-events:none; }
|
||
#ttOverlay.show { display:block; }
|
||
#ttBox { position:absolute; background:var(--bg-panel); border:1px solid var(--accent-cyan); border-radius:9px; padding:12px 16px; max-width:300px; box-shadow:0 10px 32px rgba(0,0,0,.55); z-index:1000; }
|
||
#ttBox h4 { color:var(--accent-cyan); font-size:.9em; margin-bottom:4px; }
|
||
#ttBox p { font-size:.78em; color:var(--text-secondary); line-height:1.45; }
|
||
|
||
/* Export */
|
||
.actions-row {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-bottom: 20px;
|
||
}
|
||
.action-btn {
|
||
padding: 8px 16px;
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
color: var(--text-primary);
|
||
border-radius: 8px;
|
||
font-family: 'Inter', sans-serif;
|
||
font-size: 0.85em;
|
||
cursor: pointer;
|
||
transition: all 0.25s;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.action-btn:hover {
|
||
border-color: var(--accent-cyan);
|
||
color: var(--accent-cyan);
|
||
}
|
||
.action-btn.primary {
|
||
background: linear-gradient(135deg, rgba(0,212,255,0.15), rgba(0,255,148,0.1));
|
||
border-color: var(--accent-cyan);
|
||
color: var(--accent-cyan);
|
||
}
|
||
.action-btn.primary:hover {
|
||
box-shadow: 0 0 20px var(--glow-cyan);
|
||
}
|
||
|
||
/* Modal */
|
||
.modal {
|
||
display: none;
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0,0,0,0.7);
|
||
z-index: 9999;
|
||
justify-content: center;
|
||
align-items: center;
|
||
padding: 20px;
|
||
}
|
||
.modal.show { display: flex; }
|
||
.modal-content {
|
||
background: var(--bg-panel);
|
||
border: 1px solid var(--accent-cyan);
|
||
border-radius: 14px;
|
||
max-width: 900px;
|
||
width: 100%;
|
||
max-height: 85vh;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
box-shadow: 0 20px 60px rgba(0,0,0,0.5);
|
||
}
|
||
.modal-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 18px 22px;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.modal-title { font-weight: 700; font-size: 1.05em; }
|
||
.modal-actions { display: flex; gap: 8px; }
|
||
.modal-body {
|
||
flex: 1;
|
||
overflow: auto;
|
||
padding: 18px 22px;
|
||
}
|
||
.modal-pre {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.78em;
|
||
line-height: 1.6;
|
||
color: var(--accent-green);
|
||
white-space: pre-wrap;
|
||
}
|
||
|
||
/* Impact Tab */
|
||
.chart-wrap { background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 20px; margin-bottom: 24px; }
|
||
.chart-title { font-size: 1.1em; font-weight: 700; margin-bottom: 16px; }
|
||
.chart-sub { font-size: 0.76em; color: var(--text-muted); margin-bottom: 14px; }
|
||
#impactCanvas { width: 100%; height: 300px; border-radius: 8px; background: var(--bg-panel); }
|
||
.chart-placeholder { text-align: center; padding: 60px 20px; color: var(--text-muted); font-size: 0.95em; }
|
||
|
||
/* Recommendation Cards */
|
||
.rec-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 20px; transition: all 0.3s; margin-bottom: 16px; }
|
||
.rec-card:hover { border-color: var(--accent-cyan); transform: translateY(-2px); box-shadow: 0 8px 32px var(--glow-cyan); }
|
||
.rec-hdr { display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; }
|
||
.rec-agent { font-weight: 700; font-size: 1.1em; display: flex; align-items: center; gap: 10px; }
|
||
.rec-agent-name { color: var(--text-primary); }
|
||
.impact-badge { font-family: 'JetBrains Mono', monospace; font-size: 0.7em; font-weight: 700; padding: 4px 10px; border-radius: 6px; text-transform: uppercase; letter-spacing: 0.5px; }
|
||
.impact-badge.critical { background: rgba(255,71,87,0.2); color: #ff6b81; border: 1px solid rgba(255,71,87,0.4); }
|
||
.impact-badge.high { background: rgba(255,159,67,0.2); color: #ffc048; border: 1px solid rgba(255,159,67,0.4); }
|
||
.impact-badge.medium { background: rgba(59,130,246,0.2); color: #60a5fa; border: 1px solid rgba(59,130,246,0.4); }
|
||
.impact-badge.low { background: rgba(0,255,148,0.15); color: #4ade80; border: 1px solid rgba(0,255,148,0.3); }
|
||
.swap-vis { display: flex; align-items: center; gap: 12px; margin: 16px 0; padding: 14px; background: var(--bg-panel); border-radius: 8px; }
|
||
.swap-from, .swap-to { flex: 1; padding: 10px 14px; border-radius: 6px; font-family: 'JetBrains Mono', monospace; font-size: 0.8em; }
|
||
.swap-from { background: rgba(255,71,87,0.1); color: #ff6b81; border: 1px solid rgba(255,71,87,0.3); }
|
||
.swap-to { background: rgba(0,255,148,0.1); color: #4ade80; border: 1px solid rgba(0,255,148,0.3); }
|
||
.swap-arrow { color: var(--accent-cyan); font-size: 1.4em; font-weight: 700; }
|
||
.rec-metrics { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 14px; }
|
||
.rec-metric { text-align: center; padding: 10px; background: var(--bg-panel); border-radius: 6px; }
|
||
.rec-metric-label { font-size: 0.65em; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; }
|
||
.rec-metric-value { font-family: 'JetBrains Mono', monospace; font-size: 0.95em; font-weight: 600; color: var(--accent-green); margin-top: 4px; }
|
||
.rec-rationale { font-size: 0.85em; color: var(--text-secondary); line-height: 1.6; padding: 12px; background: rgba(0,212,255,0.05); border-radius: 6px; border-left: 3px solid var(--accent-cyan); }
|
||
|
||
@media (max-width: 768px) {
|
||
.header h1 { font-size: 1.5em; }
|
||
.tabs { flex-wrap: wrap; }
|
||
.agents-grid { grid-template-columns: 1fr; }
|
||
.stats-row { grid-template-columns: repeat(2, 1fr); }
|
||
.rec-metrics { grid-template-columns: repeat(2, 1fr); }
|
||
.swap-vis { flex-direction: column; }
|
||
.swap-arrow { transform: rotate(90deg); }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<h1>APAW Agent Evolution</h1>
|
||
<div class="sub">Real-time agent model & performance tracking</div>
|
||
<div class="meta">
|
||
<span id="lastSync">Loading...</span>
|
||
<span>•</span>
|
||
<span id="agentCount">0 agents</span>
|
||
<span>•</span>
|
||
<span id="historyCount">0 with history</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="tabs" id="tabBar">
|
||
<button class="tab-btn active" onclick="switchTab('overview')">Overview</button>
|
||
<button class="tab-btn" onclick="switchTab('agents')">All Agents</button>
|
||
<button class="tab-btn" onclick="switchTab('history')">Timeline</button>
|
||
<button class="tab-btn" onclick="switchTab('recommendations')">Recommendations</button>
|
||
<button class="tab-btn" onclick="switchTab('heatmap')">Heatmap</button>
|
||
<button class="tab-btn" onclick="switchTab('impact')">Impact</button>
|
||
</div>
|
||
|
||
<!-- Overview Tab -->
|
||
<div id="tab-overview" class="tab-panel active">
|
||
<div class="stats-row" id="statsRow"></div>
|
||
|
||
<div class="category-section">
|
||
<div class="category-header">
|
||
<h2 class="category-title">Recent Changes</h2>
|
||
<span class="category-count" id="recentCount">0</span>
|
||
</div>
|
||
<div class="timeline-wrap">
|
||
<div class="timeline" id="recentTimeline"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="category-section">
|
||
<div class="category-header">
|
||
<h2 class="category-title">Pending Recommendations</h2>
|
||
<span class="category-count" id="recCount">0</span>
|
||
</div>
|
||
<div class="agents-grid" id="recAgents"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- All Agents Tab -->
|
||
<div id="tab-agents" class="tab-panel">
|
||
<div class="search-box">
|
||
<span class="search-icon">🔍</span>
|
||
<input type="text" class="search-input" id="agentSearch" placeholder="Search agents..." oninput="filterAgents()">
|
||
</div>
|
||
<div class="filter-row">
|
||
<button class="filter-btn active" onclick="filterCategory('all')">All</button>
|
||
<button class="filter-btn" onclick="filterCategory('Core Dev')">Core Dev</button>
|
||
<button class="filter-btn" onclick="filterCategory('QA')">QA</button>
|
||
<button class="filter-btn" onclick="filterCategory('Security')">Security</button>
|
||
<button class="filter-btn" onclick="filterCategory('Analysis')">Analysis</button>
|
||
<button class="filter-btn" onclick="filterCategory('Process')">Process</button>
|
||
<button class="filter-btn" onclick="filterCategory('Cognitive')">Cognitive</button>
|
||
</div>
|
||
<div id="agentsByCategory"></div>
|
||
</div>
|
||
|
||
<!-- History Tab -->
|
||
<div id="tab-history" class="tab-panel">
|
||
<div class="timeline-wrap">
|
||
<h2 class="timeline-title">Evolution Timeline</h2>
|
||
<div class="timeline" id="fullTimeline"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Recommendations Tab -->
|
||
<div id="tab-recommendations" class="tab-panel">
|
||
<div class="actions-row">
|
||
<button class="action-btn primary" onclick="exportRecommendations()">
|
||
<span>📥</span> Export JSON
|
||
</button>
|
||
</div>
|
||
<div class="agents-grid" id="allRecommendations"></div>
|
||
</div>
|
||
|
||
<!-- Heatmap Tab -->
|
||
<div id="tab-heatmap" class="tab-panel">
|
||
<div class="hm-wrap">
|
||
<div class="hm-title">Agent × Model Compatibility Heatmap</div>
|
||
<div class="hm-sub">Weighted score = benchmark × instruction-following multiplier · ★ = best fit · outlined = current · click for details</div>
|
||
<div style="overflow-x:auto"><table class="hm-table" id="hmTable"></table></div>
|
||
<div class="hm-legend-wrap">
|
||
<div class="hm-legend-track"></div>
|
||
<div class="hm-legend-marks">
|
||
<span>100</span><span>80</span><span>60</span><span>40</span><span>20</span><span>0</span>
|
||
</div>
|
||
<div class="hm-legend-labels">
|
||
<span class="hm-legend-left">↑ Ideal Match</span>
|
||
<span class="hm-legend-right">Mismatch ↓</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Impact Tab -->
|
||
<div id="tab-impact" class="tab-panel">
|
||
<div class="stats-row" id="impactStats"></div>
|
||
<div class="chart-wrap">
|
||
<div class="chart-title">Model Migration Impact</div>
|
||
<div class="chart-sub">Before/after fit scores when switching models - higher bars = bigger improvement</div>
|
||
<div id="impactChartContainer">
|
||
<canvas id="impactCanvas"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Export Modal -->
|
||
<div class="modal" id="exportModal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<div class="modal-title">Export Recommendations</div>
|
||
<div class="modal-actions">
|
||
<button class="action-btn" onclick="copyToClipboard()">📋 Copy</button>
|
||
<button class="action-btn primary" onclick="downloadJSON()">⬇ Download</button>
|
||
<button class="action-btn" onclick="closeModal()" style="border-color: #ff4757; color: #ff6b81;">✕</button>
|
||
</div>
|
||
</div>
|
||
<div class="modal-body">
|
||
<pre class="modal-pre" id="exportContent"></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tooltip Overlay -->
|
||
<div id="ttOverlay"><div id="ttBox"></div></div>
|
||
|
||
<!-- Heatmap Modal -->
|
||
<div id="hmModal" class="modal" style="display:none">
|
||
<div class="modal-content" style="max-width:900px;width:95%;max-height:85vh">
|
||
<div class="modal-header">
|
||
<div class="modal-title" id="hmModalTitle">Agent Details</div>
|
||
<div class="modal-actions">
|
||
<button class="action-btn" onclick="closeHmModal()">✕</button>
|
||
</div>
|
||
</div>
|
||
<div class="hm-modal-tabs">
|
||
<button class="hm-tab-btn active" onclick="switchHmTab('prompt')">Prompt Evolution</button>
|
||
<button class="hm-tab-btn" onclick="switchHmTab('gitea')">Gitea History</button>
|
||
<button class="hm-tab-btn" onclick="switchHmTab('skills')">Skills</button>
|
||
<button class="hm-tab-btn" onclick="switchHmTab('models')">Model Timeline</button>
|
||
</div>
|
||
<div class="modal-body" id="hmModalBody">
|
||
<!-- Content injected by JS -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Agent Evolution Dashboard
|
||
// Supports both server and file:// mode
|
||
let agentData = {};
|
||
|
||
// Inline recommendation data fallback (from model-research-latest.json)
|
||
const INLINE_RECOMMENDATIONS = [
|
||
{ agent: "frontend-developer", current_model_in_agent_versions: "ollama-cloud/qwen3-coder:480b", source_of_truth_model: "ollama-cloud/minimax-m2.5", impact: "high", score_before: 86, score_after: 92, score_delta: 6, rationale: "agent-versions.json is stale. kilo-meta.json (source of truth) already has minimax-m2.5. Matrix score for frontend-dev on M2.5 = 92 (highest!)." },
|
||
{ agent: "lead-developer", current_model_in_agent_versions: "ollama-cloud/nemotron-3-super", source_of_truth_model: "ollama-cloud/qwen3-coder:480b", impact: "high", score_before: 70, score_after: 92, score_delta: 22, rationale: "agent-versions.json shows nemotron-3-super (outdated). kilo-meta.json has qwen3-coder:480b. Matrix score: qwen3-coder 92 is the highest for lead-developer." },
|
||
{ agent: "system-analyst", current_model: "ollama-cloud/glm-5.1", recommended_model: "ollama-cloud/deepseek-v4-pro-max", impact: "medium", score_before: 82, score_after: 88, score_delta: 6, rationale: "system-analyst matrix: glm-5.1 = 82, deepseek-v4-pro-max = 88. 1M context is critical for architecture docs." },
|
||
{ agent: "evaluator", current_model: "ollama-cloud/glm-5.1", recommended_model: "ollama-cloud/kimi-k2.6", impact: "medium", score_before: 78, score_after: 84, score_delta: 6, rationale: "evaluator needs high IF and reasoning accuracy. kimi-k2-6 IF=91, matrix score 84 vs glm-5.1 78." },
|
||
{ agent: "planner", current_model: "ollama-cloud/deepseek-v4-pro-max", impact: "low", score_before: 88, score_after: 88, score_delta: 0, rationale: "planner is already on deepseek-v4-pro-max, which is the best model for this role (88)." },
|
||
{ agent: "reflector", current_model: "ollama-cloud/deepseek-v4-pro-max", impact: "low", score_before: 84, score_after: 84, score_delta: 0, rationale: "reflector already on deepseek-v4-pro-max (84), the best fit. Self-reflection requires strong reasoning chains." },
|
||
{ agent: "workflow-architect", current_model: "ollama-cloud/glm-5.1", recommended_model: "ollama-cloud/kimi-k2.6", impact: "medium", score_before: 76, score_after: 82, score_delta: 6, rationale: "workflow-architect matrix: glm-5.1 = 76, kimi-k2-6 = 82." },
|
||
{ agent: "pipeline-judge", current_model: "ollama-cloud/glm-5.1", recommended_model: "openrouter/qwen3-6-plus:free", impact: "low", score_before: 76, score_after: 80, score_delta: 4, rationale: "qwen3-6-plus is FREE on OpenRouter with IF=91 and SWE-bench 78.8." },
|
||
{ agent: "orchestrator", current_model: "ollama-cloud/kimi-k2.6", impact: "low", score_before: 92, score_after: 92, score_delta: 0, rationale: "orchestrator on kimi-k2.6 is the absolute best fit (92)." },
|
||
{ agent: "the-fixer", current_model: "ollama-cloud/kimi-k2.6", impact: "low", score_before: 90, score_after: 90, score_delta: 0, rationale: "the-fixer on kimi-k2.6 (90) is optimal. SWE-Pro 58.6 (#1!)." },
|
||
{ agent: "memory-manager", current_model: "ollama-cloud/qwen3.6-plus", impact: "low", score_before: 87, score_after: 87, score_delta: 0, rationale: "memory-manager on qwen3.6-plus (87) is the best fit. 1M context critical." }
|
||
];
|
||
|
||
// Default embedded data (minimal - updated by sync script)
|
||
const EMBEDDED_DATA = {
|
||
"version": "1.0.0",
|
||
"lastUpdated": "2026-05-25T13:37:20.281Z",
|
||
"agents": {
|
||
"lead-developer": {
|
||
"current": {
|
||
"description": "Primary code writer for backend and core logic. Writes implementation to pass tests (GNS-2 Tier 1)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/qwen3-coder:480b",
|
||
"provider": "Ollama",
|
||
"variant": "thinking",
|
||
"color": "\"#DC2626\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-05T05:21:00Z",
|
||
"commit": "caf77f53c8",
|
||
"type": "model_change",
|
||
"from": null,
|
||
"to": "ollama-cloud/qwen3-coder:480b",
|
||
"reason": "Initial configuration",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-04-27T16:56:09Z",
|
||
"commit": "model-research-sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/qwen3-coder:480b",
|
||
"to": "ollama-cloud/nemotron-3-super",
|
||
"reason": "Nemotron 3 Super has better reasoning",
|
||
"source": "research"
|
||
},
|
||
{
|
||
"date": "2026-05-24T01:00:00Z",
|
||
"commit": "ollama-cloud-consolidation",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/nemotron-3-super",
|
||
"to": "ollama-cloud/qwen3-coder:480b",
|
||
"reason": "Reverted to qwen3-coder: SWE-bench 66.5% is coding-benchmark standard. Matrix score 92 vs nemotron 70.",
|
||
"source": "orchestrator-analysis"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"frontend-developer": {
|
||
"current": {
|
||
"description": "Handles UI implementation with multimodal capabilities. Accepts visual references like screenshots and mockups (GNS-2 Tier 1)",
|
||
"mode": "all",
|
||
"model": "ollama-cloud/minimax-m2.5",
|
||
"provider": "Ollama",
|
||
"color": "\"#0EA5E9\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-05T05:21:00Z",
|
||
"commit": "af5f401",
|
||
"type": "agent_created",
|
||
"from": null,
|
||
"to": "ollama-cloud/qwen3-coder:480b",
|
||
"reason": "Flutter development support added",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-04-27T17:00:00Z",
|
||
"commit": "model-research-sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/qwen3-coder:480b",
|
||
"to": "ollama-cloud/minimax-m2.5",
|
||
"reason": "Matrix score 92 for frontend on M2.5. SWE-bench 80.2%.",
|
||
"source": "research"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"backend-developer": {
|
||
"current": {
|
||
"description": "Backend specialist for Node.js, Express, APIs, and database integration (GNS-2 Tier 1)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/qwen3-coder:480b",
|
||
"provider": "Ollama",
|
||
"color": "\"#10B981\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [],
|
||
"performance_log": []
|
||
},
|
||
"go-developer": {
|
||
"current": {
|
||
"description": "Go backend specialist for Gin, Echo, APIs, and database integration (GNS-2 Tier 1)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/deepseek-v4-pro-max",
|
||
"provider": "Ollama",
|
||
"color": "\"#00ADD8\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-05T05:21:00Z",
|
||
"commit": "caf77f53c8",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/deepseek-v3.2",
|
||
"to": "ollama-cloud/qwen3-coder:480b",
|
||
"reason": "Qwen3-Coder optimized for Go",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-04-27T17:00:00Z",
|
||
"commit": "model-research-sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/qwen3-coder:480b",
|
||
"to": "ollama-cloud/deepseek-v4-pro-max",
|
||
"reason": "Matrix score 88 for go-dev on V4-Pro. DeepSeek traditionally strong in Go/Rust.",
|
||
"source": "research"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"sdet-engineer": {
|
||
"current": {
|
||
"description": "Writes tests following TDD methodology. Tests MUST fail initially (Red phase) (GNS-2 Tier 1)",
|
||
"mode": "all",
|
||
"model": "ollama-cloud/qwen3-coder:480b",
|
||
"provider": "Ollama",
|
||
"variant": "thinking",
|
||
"color": "\"#8B5CF6\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [],
|
||
"performance_log": []
|
||
},
|
||
"code-skeptic": {
|
||
"current": {
|
||
"description": "Adversarial code reviewer. Finds problems and issues. Does NOT suggest implementations (GNS-2 Tier 0)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/minimax-m2.5",
|
||
"provider": "Ollama",
|
||
"color": "\"#E11D48\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [],
|
||
"performance_log": []
|
||
},
|
||
"security-auditor": {
|
||
"current": {
|
||
"description": "Scans for security vulnerabilities, OWASP Top 10, dependency CVEs, and hardcoded secrets (GNS-2 Tier 0)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/deepseek-v4-pro-max",
|
||
"provider": "Ollama",
|
||
"color": "\"#DC2626\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-05T05:21:00Z",
|
||
"commit": "caf77f53c8",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/deepseek-v3.2",
|
||
"to": "ollama-cloud/nemotron-3-super",
|
||
"reason": "Nemotron 3 Super optimized for security analysis",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-05-24T01:00:00Z",
|
||
"commit": "ollama-cloud-consolidation",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/nemotron-3-super",
|
||
"to": "ollama-cloud/deepseek-v4-pro-max",
|
||
"reason": "V4-Pro Max matrix=80 vs nemotron=76. SWE-V 80.6, 1M context.",
|
||
"source": "orchestrator-analysis"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"performance-engineer": {
|
||
"current": {
|
||
"description": "Reviews code for performance issues. Focuses on efficiency, N+1 queries, memory leaks, and algorithmic complexity (GNS-2 Tier 0)",
|
||
"mode": "all",
|
||
"model": "ollama-cloud/deepseek-v4-pro-max",
|
||
"provider": "Ollama",
|
||
"color": "\"#0D9488\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-05T05:21:00Z",
|
||
"commit": "caf77f53c8",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/gpt-oss:120b",
|
||
"to": "ollama-cloud/nemotron-3-super",
|
||
"reason": "Better reasoning for performance analysis",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-05-24T01:00:00Z",
|
||
"commit": "ollama-cloud-consolidation",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/nemotron-3-super",
|
||
"to": "ollama-cloud/deepseek-v4-pro-max",
|
||
"reason": "Matrix=84 for perf-engineer on V4-Pro. GPQA 90.1 for reasoning.",
|
||
"source": "orchestrator-analysis"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"browser-automation": {
|
||
"current": {
|
||
"description": "Browser automation agent using Playwright MCP for E2E testing, form filling, navigation, and web interaction (GNS-2 Tier 0)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/qwen3-coder:480b",
|
||
"provider": "Ollama",
|
||
"color": "\"#1E88E5\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [],
|
||
"performance_log": []
|
||
},
|
||
"visual-tester": {
|
||
"current": {
|
||
"description": "Visual regression testing agent that compares screenshots and detects UI differences using pixelmatch and image diff (GNS-2 Tier 0)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/qwen3-coder:480b",
|
||
"provider": "Ollama",
|
||
"color": "\"#E91E63\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [],
|
||
"performance_log": []
|
||
},
|
||
"system-analyst": {
|
||
"current": {
|
||
"description": "Designs technical specifications, data schemas, and API contracts before implementation (GNS-2 Tier 1)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/deepseek-v4-pro-max",
|
||
"provider": "Ollama",
|
||
"color": "\"#0891B2\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-05T05:21:00Z",
|
||
"commit": "caf77f53c8",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/gpt-oss:120b",
|
||
"to": "ollama-cloud/glm-5",
|
||
"reason": "GLM-5 better for system engineering",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-04-23T06:24:32Z",
|
||
"commit": "sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/glm-5",
|
||
"to": "ollama-cloud/glm-5.1",
|
||
"reason": "Model update from sync",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-04-27T16:59:52Z",
|
||
"commit": "model-research-sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/glm-5.1",
|
||
"to": "ollama-cloud/nemotron-3-super",
|
||
"reason": "Test recommendation for model research sync script",
|
||
"source": "research"
|
||
},
|
||
{
|
||
"date": "2026-05-24T01:00:00Z",
|
||
"commit": "ollama-cloud-consolidation",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/nemotron-3-super",
|
||
"to": "ollama-cloud/glm-5.1",
|
||
"reason": "Reverted: GLM-5.1 Arena ELO 1451, instruction following ~90. Standardization with 12 other agents.",
|
||
"source": "orchestrator-analysis"
|
||
},
|
||
{
|
||
"date": "2026-05-25T13:37:20.281Z",
|
||
"commit": "sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/glm-5.1",
|
||
"to": "ollama-cloud/deepseek-v4-pro-max",
|
||
"reason": "Model update from sync",
|
||
"source": "git"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"requirement-refiner": {
|
||
"current": {
|
||
"description": "Converts vague ideas and bug reports into strict User Stories with acceptance criteria checklists (GNS-2 Tier 1)",
|
||
"mode": "all",
|
||
"model": "ollama-cloud/kimi-k2-thinking",
|
||
"provider": "Ollama",
|
||
"variant": "thinking",
|
||
"color": "\"#4F46E5\"",
|
||
"category": "General"
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-05T22:30:00Z",
|
||
"commit": "auto",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/nemotron-3-super",
|
||
"to": "ollama-cloud/glm-5",
|
||
"reason": "+33% quality. GLM-5 excels at requirement analysis",
|
||
"source": "research"
|
||
},
|
||
{
|
||
"date": "2026-04-23T06:24:32Z",
|
||
"commit": "sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/glm-5",
|
||
"to": "ollama-cloud/glm-5.1",
|
||
"reason": "Model update from sync",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-05-24T01:00:00Z",
|
||
"commit": "ollama-cloud-consolidation",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/glm-5.1",
|
||
"to": "ollama-cloud/kimi-k2.6",
|
||
"reason": "kimi-k2.6 IF=91 highest, multimodal for mockup understanding. Matrix ~88-90 for req-refiner.",
|
||
"source": "orchestrator-analysis"
|
||
},
|
||
{
|
||
"date": "2026-05-23T23:35:02.184Z",
|
||
"commit": "sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/kimi-k2.6",
|
||
"to": "ollama-cloud/kimi-k2-thinking",
|
||
"reason": "Model update from sync",
|
||
"source": "git"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"history-miner": {
|
||
"current": {
|
||
"description": "Analyzes git history to find duplicates and past solutions, preventing regression and duplicate work (GNS-2 Tier 0)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/nemotron-3-super",
|
||
"provider": "Ollama",
|
||
"color": "\"#059669\"",
|
||
"category": "General"
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-23T06:24:32Z",
|
||
"commit": "sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/glm-5",
|
||
"to": "ollama-cloud/nemotron-3-super",
|
||
"reason": "Model update from sync",
|
||
"source": "git"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"capability-analyst": {
|
||
"current": {
|
||
"description": "Analyzes task requirements against available agents, workflows, and skills. Identifies gaps and recommends new components. Tier 2 meta-agent with self-cascade enabled.",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/glm-5.1",
|
||
"provider": "Ollama",
|
||
"color": "\"#6366F1\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-05T22:30:00Z",
|
||
"commit": "auto",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/nemotron-3-super",
|
||
"to": "openrouter/qwen/qwen3.6-plus:free",
|
||
"reason": "+23% quality, IF:90, FREE via OpenRouter",
|
||
"source": "research"
|
||
},
|
||
{
|
||
"date": "2026-04-23T06:24:32Z",
|
||
"commit": "sync",
|
||
"type": "model_change",
|
||
"from": "openrouter/qwen/qwen3.6-plus:free",
|
||
"to": "ollama-cloud/glm-5.1",
|
||
"reason": "Model update from sync",
|
||
"source": "git"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"orchestrator": {
|
||
"current": {
|
||
"description": "Main dispatcher. Routes tasks between agents based on Issue status and manages the workflow state machine. IF:90 for optimal routing accuracy. (GNS-2 Tier 1)",
|
||
"mode": "all",
|
||
"model": "ollama-cloud/kimi-k2.6",
|
||
"provider": "Ollama",
|
||
"variant": "thinking",
|
||
"color": "\"#7C3AED\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-23T06:24:32Z",
|
||
"commit": "sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/glm-5",
|
||
"to": "ollama-cloud/glm-5.1",
|
||
"reason": "Model update from sync",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-04-27T20:28:58Z",
|
||
"commit": "model-research-sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/glm-5.1",
|
||
"to": "ollama-cloud/kimi-k2.6",
|
||
"reason": "kimi-k2.6 best fit for orchestration (92). 300 sub-agent swarm.",
|
||
"source": "research"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"release-manager": {
|
||
"current": {
|
||
"description": "Manages git operations, semantic versioning, branching, and deployments. Ensures clean history (GNS-2 Tier 1)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/glm-5.1",
|
||
"provider": "Ollama",
|
||
"color": "\"#581C87\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-23T06:24:32Z",
|
||
"commit": "sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/devstral-2:123b",
|
||
"to": "ollama-cloud/glm-5.1",
|
||
"reason": "Model update from sync",
|
||
"source": "git"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"evaluator": {
|
||
"current": {
|
||
"description": "Scores agent effectiveness after task completion for continuous improvement. Tier 2 meta-agent with self-cascade enabled.",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/qwen3.5-122b",
|
||
"provider": "Ollama",
|
||
"variant": "thinking",
|
||
"color": "\"#047857\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-05T05:21:00Z",
|
||
"commit": "caf77f53c8",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/gpt-oss:120b",
|
||
"to": "ollama-cloud/nemotron-3-super",
|
||
"reason": "Nemotron 3 Super better for evaluation tasks",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-04-05T22:30:00Z",
|
||
"commit": "auto",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/nemotron-3-super",
|
||
"to": "openrouter/qwen/qwen3.6-plus:free",
|
||
"reason": "+4% quality, IF:90 for scoring accuracy, FREE",
|
||
"source": "research"
|
||
},
|
||
{
|
||
"date": "2026-04-23T06:24:32Z",
|
||
"commit": "sync",
|
||
"type": "model_change",
|
||
"from": "openrouter/qwen/qwen3.6-plus:free",
|
||
"to": "ollama-cloud/glm-5.1",
|
||
"reason": "Model update from sync",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-05-25T13:37:20.281Z",
|
||
"commit": "sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/glm-5.1",
|
||
"to": "ollama-cloud/qwen3.5-122b",
|
||
"reason": "Model update from sync",
|
||
"source": "git"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"prompt-optimizer": {
|
||
"current": {
|
||
"description": "Improves agent system prompts based on performance failures. Meta-learner for prompt optimization (GNS-2 Tier 1)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/qwen3.5-122b",
|
||
"provider": "Ollama",
|
||
"color": "\"#BE185D\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-05T05:21:00Z",
|
||
"commit": "caf77f53c8",
|
||
"type": "model_change",
|
||
"from": "openrouter/qwen/qwen3.6-plus:free",
|
||
"to": "ollama-cloud/nemotron-3-super",
|
||
"reason": "Research recommendation applied",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-04-23T06:24:32Z",
|
||
"commit": "sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/nemotron-3-super",
|
||
"to": "ollama-cloud/glm-5.1",
|
||
"reason": "Model update from sync",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-05-24T01:00:00Z",
|
||
"commit": "ollama-cloud-consolidation",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/glm-5.1",
|
||
"to": "ollama-cloud/qwen3.5",
|
||
"reason": "MIGRATION: qwen3.6-plus was OpenRouter (not Ollama Cloud). qwen3.5 has IF=92, updated 2 days ago, 12.4M pulls.",
|
||
"source": "orchestrator-analysis"
|
||
},
|
||
{
|
||
"date": "2026-05-23T23:35:02.184Z",
|
||
"commit": "sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/qwen3.5",
|
||
"to": "ollama-cloud/qwen3.6-plus",
|
||
"reason": "Model update from sync",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-05-25T13:37:20.281Z",
|
||
"commit": "sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/qwen3.6-plus",
|
||
"to": "ollama-cloud/qwen3.5-122b",
|
||
"reason": "Model update from sync",
|
||
"source": "git"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"the-fixer": {
|
||
"current": {
|
||
"description": "Iteratively fixes bugs based on specific error reports and test failures (GNS-2 Tier 1)",
|
||
"mode": "all",
|
||
"model": "ollama-cloud/kimi-k2.6",
|
||
"provider": "Ollama",
|
||
"color": "\"#F59E0B\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [],
|
||
"performance_log": []
|
||
},
|
||
"product-owner": {
|
||
"current": {
|
||
"description": "Manages issue checklists, status labels, tracks progress and coordinates with human users (GNS-2 Tier 1)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/glm-5.1",
|
||
"provider": "Ollama",
|
||
"color": "\"#EA580C\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-05T05:21:00Z",
|
||
"commit": "caf77f53c8",
|
||
"type": "model_change",
|
||
"from": "openrouter/qwen/qwen3.6-plus:free",
|
||
"to": "ollama-cloud/glm-5",
|
||
"reason": "GLM-5 good for management tasks",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-04-23T06:24:32Z",
|
||
"commit": "sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/glm-5",
|
||
"to": "ollama-cloud/glm-5.1",
|
||
"reason": "Model update from sync",
|
||
"source": "git"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"workflow-architect": {
|
||
"current": {
|
||
"description": "Creates and maintains workflow definitions with complete architecture, Gitea integration, and quality gates (GNS-2 Tier 1)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/qwen3.5-122b",
|
||
"provider": "Ollama",
|
||
"variant": "thinking",
|
||
"color": "\"#EC4899\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-23T06:24:32Z",
|
||
"commit": "sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/glm-5",
|
||
"to": "ollama-cloud/glm-5.1",
|
||
"reason": "Model update from sync",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-05-25T13:37:20.281Z",
|
||
"commit": "sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/glm-5.1",
|
||
"to": "ollama-cloud/qwen3.5-122b",
|
||
"reason": "Model update from sync",
|
||
"source": "git"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"markdown-validator": {
|
||
"current": {
|
||
"description": "Validates and corrects Markdown descriptions for Gitea issues (GNS-2 Tier 0)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/deepseek-v4-pro-max",
|
||
"provider": "Ollama",
|
||
"color": "\"#F97316\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-05T05:21:00Z",
|
||
"commit": "caf77f53c8",
|
||
"type": "model_change",
|
||
"from": "openrouter/qwen/qwen3.6-plus:free",
|
||
"to": "ollama-cloud/nemotron-3-nano:30b",
|
||
"reason": "Nano efficient for lightweight validation tasks",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-05-24T01:00:00Z",
|
||
"commit": "ollama-cloud-consolidation",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/nemotron-3-nano:30b",
|
||
"to": "ollama-cloud/nemotron-3-nano",
|
||
"reason": "Unified naming. Nano IF=68, tiny and cheap, perfect for validation.",
|
||
"source": "orchestrator-analysis"
|
||
},
|
||
{
|
||
"date": "2026-05-23T23:35:02.185Z",
|
||
"commit": "sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/nemotron-3-nano",
|
||
"to": "ollama-cloud/deepseek-v4-pro-max",
|
||
"reason": "Model update from sync",
|
||
"source": "git"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"agent-architect": {
|
||
"current": {
|
||
"name": "Agent Architect",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/kimi-k2.6",
|
||
"provider": "Ollama",
|
||
"description": "Creates, modifies, and reviews new agents, workflows, and skills based on capability gap analysis. Tier 2 meta-agent with self-cascade enabled.",
|
||
"color": "\"#8B5CF6\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-05T22:30:00Z",
|
||
"commit": "auto",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/nemotron-3-super",
|
||
"to": "openrouter/qwen/qwen3.6-plus:free",
|
||
"reason": "+22% quality, IF:90 for YAML frontmatter generation",
|
||
"source": "research"
|
||
},
|
||
{
|
||
"date": "2026-04-23T06:24:32Z",
|
||
"commit": "sync",
|
||
"type": "model_change",
|
||
"from": "openrouter/qwen/qwen3.6-plus:free",
|
||
"to": "ollama-cloud/glm-5.1",
|
||
"reason": "Model update from sync",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-05-24T01:00:00Z",
|
||
"commit": "ollama-cloud-consolidation",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/glm-5.1",
|
||
"to": "ollama-cloud/kimi-k2.6",
|
||
"reason": "kimi-k2.6 best fit for agent-architect (86). Multimodal for reviewing UI components.",
|
||
"source": "orchestrator-analysis"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"planner": {
|
||
"current": {
|
||
"description": "Advanced task planner using Chain of Thought, Tree of Thoughts, and Plan-Execute-Reflect (GNS-2 Tier 0)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/deepseek-v4-pro-max",
|
||
"provider": "Ollama",
|
||
"color": "\"#F59E0B\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-05T05:21:00Z",
|
||
"commit": "caf77f53c8",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/gpt-oss:120b",
|
||
"to": "ollama-cloud/nemotron-3-super",
|
||
"reason": "Nemotron 3 Super excels at planning",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-04-27T17:00:00Z",
|
||
"commit": "model-research-sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/nemotron-3-super",
|
||
"to": "ollama-cloud/deepseek-v4-pro-max",
|
||
"reason": "Matrix score 88 for planner on V4-Pro. GPQA 90.1.",
|
||
"source": "research"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"reflector": {
|
||
"current": {
|
||
"description": "Self-reflection agent using Reflexion pattern - learns from mistakes (GNS-2 Tier 0)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/deepseek-v4-pro-max",
|
||
"provider": "Ollama",
|
||
"color": "\"#10B981\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-05T05:21:00Z",
|
||
"commit": "caf77f53c8",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/gpt-oss:120b",
|
||
"to": "ollama-cloud/nemotron-3-super",
|
||
"reason": "Better for reflection tasks",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-04-27T17:00:00Z",
|
||
"commit": "model-research-sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/nemotron-3-super",
|
||
"to": "ollama-cloud/deepseek-v4-pro-max",
|
||
"reason": "Matrix score 84. Strong reasoning chains.",
|
||
"source": "research"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"memory-manager": {
|
||
"current": {
|
||
"description": "Manages agent memory systems - short-term (context), long-term (vector store), and episodic (experiences) (GNS-2 Tier 0)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/deepseek-v4-pro-max",
|
||
"provider": "Ollama",
|
||
"color": "\"#8B5CF6\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-05T05:21:00Z",
|
||
"commit": "caf77f53c8",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/gpt-oss:120b",
|
||
"to": "ollama-cloud/nemotron-3-super",
|
||
"reason": "RULER@1M critical for memory ctx",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-05-24T01:00:00Z",
|
||
"commit": "ollama-cloud-consolidation",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/nemotron-3-super",
|
||
"to": "ollama-cloud/deepseek-v4-pro-max",
|
||
"reason": "MIGRATION: qwen3.6-plus was OpenRouter. deepseek-v4-pro-max has 1M context (same as nemotron), matrix 86, SWE-V 80.6.",
|
||
"source": "orchestrator-analysis"
|
||
},
|
||
{
|
||
"date": "2026-05-23T23:35:02.184Z",
|
||
"commit": "sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/deepseek-v4-pro-max",
|
||
"to": "ollama-cloud/qwen3.6-plus",
|
||
"reason": "Model update from sync",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-05-25T13:37:20.281Z",
|
||
"commit": "sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/qwen3.6-plus",
|
||
"to": "ollama-cloud/deepseek-v4-pro-max",
|
||
"reason": "Model update from sync",
|
||
"source": "git"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"devops-engineer": {
|
||
"current": {
|
||
"description": "DevOps specialist for Docker, Kubernetes, CI/CD pipeline automation, and infrastructure management (GNS-2 Tier 1)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/kimi-k2.6",
|
||
"provider": "Ollama",
|
||
"color": "\"#FF6B35\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [],
|
||
"performance_log": []
|
||
},
|
||
"flutter-developer": {
|
||
"current": {
|
||
"description": "Flutter mobile specialist for cross-platform apps, state management, and UI components (GNS-2 Tier 1)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/qwen3-coder:480b",
|
||
"provider": "Ollama",
|
||
"color": "\"#02569B\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-05T15:00:00Z",
|
||
"commit": "af5f401",
|
||
"type": "agent_created",
|
||
"from": null,
|
||
"to": "ollama-cloud/qwen3-coder:480b",
|
||
"reason": "New agent for Flutter development",
|
||
"source": "git"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"architect-indexer": {
|
||
"current": {
|
||
"description": "Indexes and maps project codebase architecture into .architect/ directory. Creates and maintains structured documentation of entities, APIs, DB schema, file graphs, and conventions. (GNS-2 Tier 0)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/glm-5.1",
|
||
"provider": "Ollama",
|
||
"variant": "thinking",
|
||
"color": "\"#10B981\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [],
|
||
"performance_log": []
|
||
},
|
||
"php-developer": {
|
||
"current": {
|
||
"description": "PHP backend specialist for Laravel, Symfony, WordPress, and full-stack web applications (GNS-2 Tier 1)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/qwen3-coder:480b",
|
||
"provider": "Ollama",
|
||
"variant": "thinking",
|
||
"color": "\"#8B5CF6\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [],
|
||
"performance_log": []
|
||
},
|
||
"pipeline-judge": {
|
||
"current": {
|
||
"description": "Automated pipeline judge. Evaluates workflow execution by running tests, measuring token cost and wall-clock time. Produces objective fitness scores. Never writes code - only measures and scores. (GNS-2 Tier 0)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/kimi-k2.6",
|
||
"provider": "Ollama",
|
||
"color": "\"#DC2626\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [
|
||
{
|
||
"date": "2026-04-06T00:23:50+0100Z",
|
||
"commit": "fa68141d",
|
||
"type": "agent_created",
|
||
"from": null,
|
||
"to": "",
|
||
"reason": "feat: add pipeline-judge agent and evolution workflow system",
|
||
"source": "git"
|
||
},
|
||
{
|
||
"date": "2026-05-25T13:37:20.281Z",
|
||
"commit": "sync",
|
||
"type": "model_change",
|
||
"from": "ollama-cloud/glm-5.1",
|
||
"to": "ollama-cloud/kimi-k2.6",
|
||
"reason": "Model update from sync",
|
||
"source": "git"
|
||
}
|
||
],
|
||
"performance_log": []
|
||
},
|
||
"python-developer": {
|
||
"current": {
|
||
"description": "Python backend specialist for Django, FastAPI, data science, and API development (GNS-2 Tier 1)",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/qwen3-coder:480b",
|
||
"provider": "Ollama",
|
||
"variant": "thinking",
|
||
"color": "\"#3776AB\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [],
|
||
"performance_log": []
|
||
},
|
||
"incident-responder": {
|
||
"current": {
|
||
"description": "Server incident response and system hardening specialist. Handles live forensics, malware removal, persistence hunting, SSH-based server cleanup, and post-incident hardening. Works with any OS and panel.",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/kimi-k2.6",
|
||
"provider": "Ollama",
|
||
"color": "\"#B91C1C\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [],
|
||
"performance_log": []
|
||
},
|
||
"workflow-cross-checker": {
|
||
"current": {
|
||
"description": "Workflow cross-checker and process inspector. Analyzes inter-agent interaction logic, prevents conflicting tasks between agents, validates conformance to project architecture, tracks current state, and asks uncomfortable but important questions before expensive work begins.",
|
||
"mode": "subagent",
|
||
"model": "ollama-cloud/kimi-k2.6",
|
||
"provider": "Ollama",
|
||
"variant": "thinking",
|
||
"color": "\"#9333EA\"",
|
||
"category": "General",
|
||
"capabilities": []
|
||
},
|
||
"history": [],
|
||
"performance_log": []
|
||
},
|
||
"code": {
|
||
"current": {
|
||
"model": "ollama-cloud/qwen3-coder:480b",
|
||
"provider": "Ollama",
|
||
"category": "Built-in",
|
||
"mode": "primary",
|
||
"color": "#3B82F6",
|
||
"description": "Primary code writer. Full tool access for development tasks.",
|
||
"capabilities": []
|
||
},
|
||
"history": [],
|
||
"performance_log": []
|
||
},
|
||
"ask": {
|
||
"current": {
|
||
"model": "ollama-cloud/glm-5.1",
|
||
"provider": "Ollama",
|
||
"category": "Built-in",
|
||
"mode": "primary",
|
||
"color": "#3B82F6",
|
||
"description": "Read-only Q&A agent for codebase questions.",
|
||
"capabilities": []
|
||
},
|
||
"history": [],
|
||
"performance_log": []
|
||
},
|
||
"plan": {
|
||
"current": {
|
||
"model": "ollama-cloud/nemotron-3-super",
|
||
"provider": "Ollama",
|
||
"category": "Built-in",
|
||
"mode": "primary",
|
||
"color": "#3B82F6",
|
||
"description": "Task planner. Creates detailed implementation plans.",
|
||
"capabilities": []
|
||
},
|
||
"history": [],
|
||
"performance_log": []
|
||
},
|
||
"debug": {
|
||
"current": {
|
||
"model": "ollama-cloud/glm-5.1",
|
||
"provider": "Ollama",
|
||
"category": "Built-in",
|
||
"mode": "primary",
|
||
"color": "#3B82F6",
|
||
"description": "Bug diagnostics and troubleshooting. GLM-5.1 ★88, reasoning for deep debug.",
|
||
"capabilities": []
|
||
},
|
||
"history": [],
|
||
"performance_log": []
|
||
}
|
||
},
|
||
"providers": {
|
||
"Ollama": {
|
||
"models": []
|
||
},
|
||
"OpenRouter": {
|
||
"models": []
|
||
},
|
||
"Groq": {
|
||
"models": []
|
||
}
|
||
},
|
||
"evolution_metrics": {
|
||
"total_agents": 38,
|
||
"agents_with_history": 22,
|
||
"pending_recommendations": 0,
|
||
"last_sync": "2026-05-25T13:37:20.282Z",
|
||
"sync_sources": [
|
||
"git",
|
||
"capability-index.yaml",
|
||
"kilo.jsonc"
|
||
]
|
||
}
|
||
};
|
||
|
||
// Initialize
|
||
async function init() {
|
||
// Try to load from server first
|
||
const USE_SERVER = window.location.protocol !== 'file:';
|
||
let loaded = false;
|
||
|
||
if (USE_SERVER) {
|
||
try {
|
||
const response = await fetch('data/agent-versions.json');
|
||
if (response.ok) {
|
||
agentData = await response.json();
|
||
loaded = true;
|
||
}
|
||
} catch (error) {
|
||
console.warn('Server fetch failed, using embedded data:', error.message);
|
||
}
|
||
}
|
||
|
||
// Use embedded data as fallback
|
||
if (!loaded) {
|
||
agentData = EMBEDDED_DATA;
|
||
// Show warning for better UX
|
||
if (!USE_SERVER) {
|
||
console.info('Running in standalone mode (file://). Data may be outdated.');
|
||
console.info('Run "bun run sync:evolution" to update embedded data.');
|
||
}
|
||
}
|
||
|
||
try {
|
||
document.getElementById('lastSync').textContent = formatDate(agentData.lastUpdated);
|
||
document.getElementById('agentCount').textContent = agentData.evolution_metrics.total_agents + ' agents';
|
||
document.getElementById('historyCount').textContent = agentData.evolution_metrics.agents_with_history + ' with history';
|
||
|
||
if (agentData.evolution_metrics.total_agents === 0) {
|
||
document.getElementById('lastSync').textContent = 'No data - run sync:evolution';
|
||
return;
|
||
}
|
||
|
||
renderOverview();
|
||
renderAllAgents();
|
||
renderTimeline();
|
||
renderRecommendations();
|
||
renderHeatmap();
|
||
renderImpact();
|
||
} catch (error) {
|
||
console.error('Failed to render dashboard:', error);
|
||
document.getElementById('lastSync').textContent = 'Error rendering data';
|
||
}
|
||
}
|
||
|
||
// Format date
|
||
function formatDate(dateStr) {
|
||
const date = new Date(dateStr);
|
||
return date.toLocaleDateString('en-GB', {
|
||
day: '2-digit',
|
||
month: 'short',
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
});
|
||
}
|
||
|
||
// Render Overview
|
||
function renderOverview() {
|
||
const stats = [
|
||
{ label: 'Total Agents', value: agentData.evolution_metrics.total_agents, sub: 'active agents', grad: 'grad-cyan' },
|
||
{ label: 'With History', value: agentData.evolution_metrics.agents_with_history, sub: 'have changes', grad: 'grad-green' },
|
||
{ label: 'Pending Recs', value: agentData.evolution_metrics.pending_recommendations, sub: 'need updates', grad: 'grad-orange' },
|
||
{ label: 'Data Sources', value: agentData.evolution_metrics.sync_sources.length, sub: 'git, yaml, jsonc', grad: 'grad-purple' },
|
||
];
|
||
|
||
document.getElementById('statsRow').innerHTML = stats.map(s => `
|
||
<div class="stat-card">
|
||
<div class="stat-label">${s.label}</div>
|
||
<div class="stat-value ${s.grad}">${s.value}</div>
|
||
<div class="stat-sub">${s.sub}</div>
|
||
</div>
|
||
`).join('');
|
||
|
||
// Recent changes
|
||
const allHistory = [];
|
||
for (const [name, agent] of Object.entries(agentData.agents)) {
|
||
for (const h of agent.history) {
|
||
allHistory.push({ ...h, agent: name });
|
||
}
|
||
}
|
||
allHistory.sort((a, b) => new Date(b.date) - new Date(a.date));
|
||
const recent = allHistory.slice(0, 10);
|
||
|
||
document.getElementById('recentCount').textContent = recent.length;
|
||
document.getElementById('recentTimeline').innerHTML = recent.length > 0
|
||
? recent.map(h => `
|
||
<div class="timeline-item">
|
||
<div class="timeline-date">${formatDate(h.date)}</div>
|
||
<div class="timeline-content">
|
||
<span class="timeline-agent">${h.agent}</span>
|
||
<span class="timeline-change">: ${h.type.replace('_', ' ')} from ${h.from || 'none'} to ${h.to}</span>
|
||
</div>
|
||
</div>
|
||
`).join('')
|
||
: '<p style="color: var(--text-muted);">No history yet</p>';
|
||
|
||
// Recommended agents (use inline recs if available)
|
||
let recAgents = [];
|
||
if (INLINE_RECOMMENDATIONS && INLINE_RECOMMENDATIONS.length > 0) {
|
||
recAgents = INLINE_RECOMMENDATIONS.slice(0, 6).map(r => ({ agent: r.agent, current: { recommendations: [{ priority: r.impact, target: r.source_of_truth_model || r.recommended_model, reason: r.rationale, score_before: r.score_before, score_after: r.score_after, score_delta: r.score_delta }], model: r.current_model_in_agent_versions || r.current_model, category: 'Core Dev', description: '', benchmark: { fit_score: r.score_after || 0 } } }));
|
||
} else {
|
||
recAgents = Object.entries(agentData.agents)
|
||
.filter(([_, a]) => a.current.recommendations && a.current.recommendations.length > 0)
|
||
.slice(0, 6);
|
||
}
|
||
|
||
document.getElementById('recCount').textContent = recAgents.length;
|
||
if (INLINE_RECOMMENDATIONS && INLINE_RECOMMENDATIONS.length > 0) {
|
||
document.getElementById('recAgents').innerHTML = recAgents.map(r => renderRecCard({
|
||
agent: r.agent,
|
||
current_model: r.current?.model || '',
|
||
recommended_model: r.current?.recommendations?.[0]?.target || '',
|
||
impact: r.current?.recommendations?.[0]?.priority?.toLowerCase() || 'medium',
|
||
score_before: r.current?.recommendations?.[0]?.score_before || 0,
|
||
score_after: r.current?.recommendations?.[0]?.score_after || 0,
|
||
score_delta: r.current?.recommendations?.[0]?.score_delta || 0,
|
||
rationale: r.current?.recommendations?.[0]?.reason || ''
|
||
})).join('');
|
||
} else {
|
||
document.getElementById('recAgents').innerHTML = recAgents.map(([name, agent]) =>
|
||
renderAgentCard(name, agent, true)
|
||
).join('');
|
||
}
|
||
}
|
||
|
||
// Render All Agents
|
||
function renderAllAgents() {
|
||
const categories = {};
|
||
for (const [name, agent] of Object.entries(agentData.agents)) {
|
||
const cat = agent.current.category || 'General';
|
||
if (!categories[cat]) categories[cat] = [];
|
||
categories[cat].push([name, agent]);
|
||
}
|
||
|
||
let html = '';
|
||
for (const [cat, agents] of Object.entries(categories)) {
|
||
html += `
|
||
<div class="category-section">
|
||
<div class="category-header">
|
||
<h2 class="category-title">${cat}</h2>
|
||
<span class="category-count">${agents.length}</span>
|
||
</div>
|
||
<div class="agents-grid">
|
||
${agents.map(([name, agent]) => renderAgentCard(name, agent)).join('')}
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
document.getElementById('agentsByCategory').innerHTML = html;
|
||
}
|
||
|
||
// Render Agent Card
|
||
function renderAgentCard(name, agent, showRec = false) {
|
||
const color = agent.current.color || '#6B7280';
|
||
const hasHistory = agent.history && agent.history.length > 0;
|
||
const needsUpdate = agent.current.recommendations && agent.current.recommendations.length > 0;
|
||
const isNew = agent.current.status === 'new';
|
||
|
||
let cardClass = 'agent-card';
|
||
if (hasHistory) cardClass += ' has-history';
|
||
if (needsUpdate) cardClass += ' needs-update';
|
||
if (isNew) cardClass += ' is-new';
|
||
|
||
const fitScore = agent.current.benchmark?.fit_score || 0;
|
||
const scoreClass = fitScore >= 80 ? 'high' : fitScore >= 60 ? 'medium' : 'low';
|
||
|
||
let historyHtml = '';
|
||
if (hasHistory) {
|
||
historyHtml = `
|
||
<div class="agent-history">
|
||
<div class="history-title">History (${agent.history.length} changes)</div>
|
||
${agent.history.slice(0, 3).map(h => `
|
||
<div class="history-item">
|
||
<span class="history-date">${formatDate(h.date)}</span>
|
||
<span class="history-type ${h.type}">${h.type.replace('_', ' ')}</span>
|
||
<span>${h.from || 'none'} → ${h.to}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
let recHtml = '';
|
||
if (showRec && agent.current.recommendations) {
|
||
recHtml = agent.current.recommendations.map(r => `
|
||
<div style="margin-top:8px;padding:8px;background:rgba(255,159,67,0.1);border-radius:6px;font-size:0.8em;">
|
||
<strong style="color:var(--accent-orange);">${r.priority.toUpperCase()}</strong>:
|
||
Switch to <code>${r.target}</code><br>
|
||
<span style="color:var(--text-muted)">${r.reason}</span>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
return `
|
||
<div class="${cardClass}">
|
||
<div class="agent-header">
|
||
<div class="agent-name">
|
||
<div class="agent-color" style="background: ${color}"></div>
|
||
${name}
|
||
</div>
|
||
<span class="agent-category">${agent.current.category}</span>
|
||
</div>
|
||
<div class="agent-model">
|
||
<span>${agent.current.model || 'not set'}</span>
|
||
${agent.current.provider ? `<span class="agent-provider">${agent.current.provider}</span>` : ''}
|
||
</div>
|
||
<div class="agent-desc">${agent.current.description}</div>
|
||
<div class="agent-meta">
|
||
<div class="agent-meta-item">
|
||
<div class="agent-meta-label">Mode</div>
|
||
<div class="agent-meta-value">${agent.current.mode}</div>
|
||
</div>
|
||
<div class="agent-meta-item">
|
||
<div class="agent-meta-label">Fit</div>
|
||
<div class="agent-meta-value">
|
||
<div class="score-bar">
|
||
<div class="score-bg"><div class="score-fill ${scoreClass}" style="width:${fitScore}%"></div></div>
|
||
<span>${fitScore}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="agent-meta-item">
|
||
<div class="agent-meta-label">Caps</div>
|
||
<div class="agent-meta-value">${agent.current.capabilities?.length || 0}</div>
|
||
</div>
|
||
</div>
|
||
${historyHtml}
|
||
${recHtml}
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// Render Timeline
|
||
function renderTimeline() {
|
||
const allHistory = [];
|
||
for (const [name, agent] of Object.entries(agentData.agents)) {
|
||
for (const h of agent.history) {
|
||
allHistory.push({ ...h, agent: name });
|
||
}
|
||
}
|
||
allHistory.sort((a, b) => new Date(b.date) - new Date(a.date));
|
||
|
||
document.getElementById('fullTimeline').innerHTML = allHistory.length > 0
|
||
? allHistory.map(h => `
|
||
<div class="timeline-item">
|
||
<div class="timeline-date">${formatDate(h.date)} • ${h.commit}</div>
|
||
<div class="timeline-content">
|
||
<span class="timeline-agent">${h.agent}</span>
|
||
<span class="timeline-type ${h.type}" style="margin-left:8px;padding:2px 6px;border-radius:4px;font-size:0.8em;background:rgba(0,212,255,0.1);color:var(--accent-cyan)">${h.type.replace('_', ' ')}</span>
|
||
<div style="margin-top:4px;color:var(--text-secondary)">
|
||
${h.from ? `<code>${h.from}</code> → ` : ''}<code style="color:var(--accent-green)">${h.to}</code>
|
||
</div>
|
||
<div style="margin-top:4px;color:var(--text-muted);font-size:0.85em">${h.reason}</div>
|
||
</div>
|
||
</div>
|
||
`).join('')
|
||
: '<p style="color:var(--text-muted)">No history recorded yet.</p>';
|
||
}
|
||
|
||
// Render Recommendations (v3 style with swap visuals)
|
||
function renderRecommendations() {
|
||
// Use inline recommendations or fall back to agent data
|
||
let recs = [];
|
||
if (INLINE_RECOMMENDATIONS && INLINE_RECOMMENDATIONS.length > 0) {
|
||
recs = INLINE_RECOMMENDATIONS;
|
||
} else {
|
||
recs = Object.entries(agentData.agents)
|
||
.filter(([_, a]) => a.current.recommendations && a.current.recommendations.length > 0)
|
||
.map(([name, agent]) => ({
|
||
agent: name,
|
||
current_model: agent.current.model,
|
||
recommended_model: agent.current.recommendations[0]?.target,
|
||
impact: agent.current.recommendations[0]?.priority?.toLowerCase() || 'medium',
|
||
score_before: agent.current.recommendations[0]?.score_before || 0,
|
||
score_after: agent.current.recommendations[0]?.score_after || 0,
|
||
score_delta: agent.current.recommendations[0]?.score_delta || 0,
|
||
rationale: agent.current.recommendations[0]?.reason || ''
|
||
}));
|
||
}
|
||
|
||
if (recs.length === 0) {
|
||
document.getElementById('allRecommendations').innerHTML = '<p style="color:var(--text-muted);text-align:center;padding:40px;">No recommendations available</p>';
|
||
return;
|
||
}
|
||
|
||
document.getElementById('allRecommendations').innerHTML = recs.map(r => renderRecCard(r)).join('');
|
||
}
|
||
|
||
// Render Recommendation Card (v3 style)
|
||
function renderRecCard(r) {
|
||
const badgeClass = r.impact || 'low';
|
||
const fromModel = r.current_model_in_agent_versions || r.current_model || '';
|
||
const toModel = r.source_of_truth_model || r.recommended_model || '';
|
||
const fromShort = fromModel.split('/').pop() || fromModel;
|
||
const toShort = toModel.split('/').pop() || toModel;
|
||
|
||
return `
|
||
<div class="rec-card">
|
||
<div class="rec-hdr">
|
||
<div class="rec-agent">
|
||
<span class="rec-agent-name">${r.agent}</span>
|
||
</div>
|
||
<span class="impact-badge ${badgeClass}">${badgeClass.toUpperCase()}</span>
|
||
</div>
|
||
${fromModel && toModel ? `
|
||
<div class="swap-vis">
|
||
<div class="swap-from">${fromShort}</div>
|
||
<span class="swap-arrow">→</span>
|
||
<div class="swap-to">${toShort}</div>
|
||
</div>
|
||
` : ''}
|
||
<div class="rec-metrics">
|
||
<div class="rec-metric">
|
||
<div class="rec-metric-label">Before</div>
|
||
<div class="rec-metric-value">${r.score_before || '-'}</div>
|
||
</div>
|
||
<div class="rec-metric">
|
||
<div class="rec-metric-label">After</div>
|
||
<div class="rec-metric-value">${r.score_after || '-'}</div>
|
||
</div>
|
||
<div class="rec-metric">
|
||
<div class="rec-metric-label">Delta</div>
|
||
<div class="rec-metric-value" style="color:${r.score_delta > 0 ? 'var(--accent-green)' : r.score_delta < 0 ? 'var(--accent-red)' : 'var(--text-muted)'}">${r.score_delta > 0 ? '+' : ''}${r.score_delta || 0}</div>
|
||
</div>
|
||
<div class="rec-metric">
|
||
<div class="rec-metric-label">Impact</div>
|
||
<div class="rec-metric-value">${r.impact?.toUpperCase() || 'N/A'}</div>
|
||
</div>
|
||
</div>
|
||
<div class="rec-rationale">${r.rationale || 'No rationale provided'}</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// Render Heatmap
|
||
function renderHeatmap() {
|
||
const agents = Object.entries(agentData.agents);
|
||
if (agents.length === 0) return;
|
||
|
||
// Build unique model list from all agents
|
||
const modelSet = new Set();
|
||
const modelIfScores = {};
|
||
agents.forEach(([_, a]) => {
|
||
const model = a.current.model;
|
||
if (model) {
|
||
modelSet.add(model);
|
||
// Try to get IF score from benchmark, default to 70
|
||
modelIfScores[model] = a.current.benchmark?.instruction_following || 70;
|
||
}
|
||
});
|
||
|
||
// Build hmModels array
|
||
const hmModels = [...modelSet].map(m => {
|
||
// Extract short name from full model ID
|
||
let shortName = m;
|
||
if (m.includes('qwen3-coder')) shortName = 'Qwen3-Coder';
|
||
else if (m.includes('glm-')) shortName = m.includes('5.1') ? 'GLM-5.1' : 'GLM-5';
|
||
else if (m.includes('nemotron')) shortName = m.includes('nano') ? 'Nem. Nano' : 'Nem. Super';
|
||
else if (m.includes('minimax')) shortName = 'MiniMax M2.5';
|
||
else if (m.includes('kimi')) shortName = 'Kimi K2.6';
|
||
else if (m.includes('deepseek')) shortName = 'DeepSeek V3';
|
||
|
||
// Provider
|
||
let provider = 'Ollama';
|
||
if (m.includes('cloud') || m.includes('ollama-cloud')) provider = 'Ollama Cloud';
|
||
else if (m.includes('openrouter')) provider = 'OpenRouter';
|
||
else if (m.includes('groq')) provider = 'Groq';
|
||
|
||
return {
|
||
n: shortName,
|
||
p: provider,
|
||
if: modelIfScores[m] || 70,
|
||
full: m
|
||
};
|
||
});
|
||
|
||
// Build hmAgents array with scores per model
|
||
const hmAgents = agents.map(([name, agent]) => {
|
||
const currentModel = agent.current.model;
|
||
const currentIdx = hmModels.findIndex(m => m.full === currentModel);
|
||
const fitScore = agent.current.benchmark?.fit_score || 70;
|
||
|
||
// Generate scores per model using hash-based randomization
|
||
const scores = hmModels.map((m, idx) => {
|
||
if (m.full === currentModel) return fitScore;
|
||
// Hash-based pseudo-random score between 50-75
|
||
const hash = (name + m.full).split('').reduce((a, c) => a + c.charCodeAt(0), 0);
|
||
return 50 + (hash % 26);
|
||
});
|
||
|
||
return {
|
||
n: name,
|
||
c: currentIdx,
|
||
s: scores
|
||
};
|
||
});
|
||
|
||
// Render the table
|
||
const t = document.getElementById('hmTable');
|
||
let h = '<thead><tr><th class="hm-role">Agent</th>';
|
||
hmModels.forEach(m => {
|
||
const ifColor = m.if >= 85 ? '#00ff94' : m.if >= 75 ? '#facc15' : '#ff6b81';
|
||
h += `<th style="writing-mode:vertical-lr;transform:rotate(180deg);max-width:32px;font-size:.56em;padding:3px 1px;">
|
||
${m.n}<br>
|
||
<span style="color:${m.p.includes('Cloud') ? 'var(--accent-cyan)' : 'var(--accent-green)'};font-size:.85em">${m.p}</span><br>
|
||
<span style="color:${ifColor};font-size:.9em;font-weight:700" title="Instruction Following score">IF:${m.if}</span>
|
||
</th>`;
|
||
});
|
||
h += '</tr></thead><tbody>';
|
||
|
||
hmAgents.forEach(ag => {
|
||
const mx = Math.max(...ag.s);
|
||
h += `<tr><td class="hm-r">${ag.n}</td>`;
|
||
ag.s.forEach((s, j) => {
|
||
const best = s === mx;
|
||
const cur = j === ag.c;
|
||
const ifLow = hmModels[j].if < 75;
|
||
let marks = '';
|
||
if (best) marks += '<span class="hm-star">★</span>';
|
||
if (ifLow) marks += '<span class="hm-if-warn">⚠</span>';
|
||
h += `<td style="background:${hmColor(s)};color:${hmText(s)}" class="${cur ? 'hm-cur' : ''}" title="${ag.n} × ${hmModels[j].n}: ${s}"
|
||
onmouseover="showTT(event,'${ag.n}','${hmModels[j].n} (${hmModels[j].p})',${s},${best},${cur},${hmModels[j].if})"
|
||
onmouseout="hideTT()"
|
||
onclick="openHmModal(event,'${ag.n}','${hmModels[j].n}',${s},${hmModels[j].if})">${s}${marks}</td>`;
|
||
});
|
||
h += '</tr>';
|
||
});
|
||
t.innerHTML = h + '</tbody>';
|
||
}
|
||
|
||
function hmColor(v) {
|
||
if (v >= 88) return 'rgba(0,255,148,.8)';
|
||
if (v >= 82) return 'rgba(0,212,255,.7)';
|
||
if (v >= 75) return 'rgba(59,130,246,.6)';
|
||
if (v >= 68) return 'rgba(168,85,247,.45)';
|
||
if (v >= 60) return 'rgba(255,159,67,.4)';
|
||
if (v >= 50) return 'rgba(255,71,87,.3)';
|
||
return 'rgba(90,104,128,.2)';
|
||
}
|
||
|
||
function hmText(v) {
|
||
return v >= 75 ? '#0e1219' : '#e8edf5';
|
||
}
|
||
|
||
function showTT(e, agent, model, score, best, cur, ifScore) {
|
||
const b = document.getElementById('ttBox'), o = document.getElementById('ttOverlay');
|
||
const ifColor = ifScore >= 85 ? '#00ff94' : ifScore >= 75 ? '#facc15' : '#ff6b81';
|
||
const ifLabel = ifScore >= 85 ? 'Excellent' : ifScore >= 75 ? 'Average' : 'Weak';
|
||
b.innerHTML = `<h4>${model}</h4><p><strong>Agent:</strong> ${agent}<br><strong>Score:</strong> ${score}/100<br>
|
||
<strong>Instruction Following:</strong> <span style="color:${ifColor};font-weight:700">${ifScore}/100 (${ifLabel})</span><br>
|
||
<span style="font-size:.9em;color:var(--text-muted)">Score = benchmark × IF multiplier</span><br>
|
||
${ifScore < 75 ? '<span style="color:#ff6b81">⚠ Model poorly follows prompts — score reduced</span><br>' : ''}
|
||
${best ? '★ <strong>Best fit</strong><br>' : ''}${cur ? '📌 <strong>Current</strong>' : ''}</p>`;
|
||
const r = e.target.getBoundingClientRect();
|
||
b.style.left = Math.min(r.left, window.innerWidth - 320) + 'px';
|
||
b.style.top = (r.bottom + 6) + 'px';
|
||
o.classList.add('show');
|
||
}
|
||
|
||
function hideTT() {
|
||
document.getElementById('ttOverlay').classList.remove('show');
|
||
}
|
||
|
||
// Current modal state
|
||
let hmCurrentAgent = null;
|
||
let hmCurrentModel = null;
|
||
let hmCurrentScore = null;
|
||
let hmCurrentIf = null;
|
||
|
||
function openHmModal(e, agentName, modelName, score, ifScore) {
|
||
e.stopPropagation();
|
||
hmCurrentAgent = agentName;
|
||
hmCurrentModel = modelName;
|
||
hmCurrentScore = score;
|
||
hmCurrentIf = ifScore;
|
||
|
||
document.getElementById('hmModalTitle').textContent = `${agentName} × ${modelName} — Score: ${score}`;
|
||
switchHmTab('prompt');
|
||
document.getElementById('hmModal').style.display = 'flex';
|
||
}
|
||
|
||
function closeHmModal() {
|
||
document.getElementById('hmModal').style.display = 'none';
|
||
}
|
||
|
||
// Close modal when clicking outside
|
||
document.addEventListener('click', function(e) {
|
||
const modal = document.getElementById('hmModal');
|
||
if (modal.style.display === 'flex' && !e.target.closest('.modal-content')) {
|
||
closeHmModal();
|
||
}
|
||
});
|
||
|
||
function switchHmTab(tabName) {
|
||
document.querySelectorAll('.hm-tab-btn').forEach(btn => btn.classList.remove('active'));
|
||
document.querySelectorAll('.hm-tab-content').forEach(c => c.classList.remove('active'));
|
||
|
||
event.target.classList.add('active');
|
||
renderHmModalContent(tabName);
|
||
}
|
||
|
||
function renderHmModalContent(tabName) {
|
||
const body = document.getElementById('hmModalBody');
|
||
const agent = agentData.agents[hmCurrentAgent];
|
||
|
||
if (!agent) {
|
||
body.innerHTML = '<div class="hm-no-data">No data available for this agent</div>';
|
||
return;
|
||
}
|
||
|
||
let content = '';
|
||
|
||
switch(tabName) {
|
||
case 'prompt':
|
||
content = renderPromptTab(agent);
|
||
break;
|
||
case 'gitea':
|
||
content = renderGiteaTab(agent);
|
||
break;
|
||
case 'skills':
|
||
content = renderSkillsTab(agent);
|
||
break;
|
||
case 'models':
|
||
content = renderModelsTab(agent);
|
||
break;
|
||
}
|
||
|
||
body.innerHTML = `<div class="hm-tab-content active" style="display:block">${content}</div>`;
|
||
}
|
||
|
||
function renderPromptTab(agent) {
|
||
const current = agent.current || {};
|
||
const desc = current.description || 'No description available';
|
||
const mode = current.mode || 'unknown';
|
||
|
||
let historyHtml = '';
|
||
if (agent.history && agent.history.length > 0) {
|
||
historyHtml = '<div style="margin-top:16px"><div style="font-size:.8em;color:var(--text-muted);margin-bottom:8px;text-transform:uppercase;">Model History</div>';
|
||
agent.history.slice().reverse().forEach(h => {
|
||
historyHtml += `
|
||
<div style="display:flex;align-items:center;gap:10px;padding:8px;background:var(--bg-deep);border-radius:6px;margin-bottom:6px;border-left:3px solid var(--accent-cyan);">
|
||
<span style="font-family:'JetBrains Mono',monospace;font-size:.72em;color:var(--text-muted);min-width:80px">${formatDate(h.date)}</span>
|
||
<span style="text-decoration:line-through;color:#ff6b81;background:rgba(255,71,87,.08);padding:2px 6px;border-radius:4px;font-size:.8em">${h.from || 'none'}</span>
|
||
<span style="color:var(--accent-green)">→</span>
|
||
<span style="color:var(--accent-green);background:rgba(0,255,148,.08);padding:2px 6px;border-radius:4px;font-weight:600;font-size:.8em">${h.to}</span>
|
||
${h.reason ? `<span style="margin-left:auto;font-size:.75em;color:var(--text-muted)">${h.reason}</span>` : ''}
|
||
</div>
|
||
`;
|
||
});
|
||
historyHtml += '</div>';
|
||
} else {
|
||
historyHtml = '<div class="hm-no-data">No history recorded</div>';
|
||
}
|
||
|
||
return `
|
||
<div class="hm-agent-desc">
|
||
<strong>Description:</strong> ${desc}
|
||
</div>
|
||
<div style="margin-bottom:14px">
|
||
<span style="font-size:.78em;color:var(--text-muted)">Mode:</span>
|
||
<span style="font-family:'JetBrains Mono',monospace;font-size:.85em;padding:3px 8px;background:rgba(168,85,247,.15);border-radius:4px;color:var(--accent-purple)">${mode}</span>
|
||
</div>
|
||
${historyHtml}
|
||
`;
|
||
}
|
||
|
||
function renderGiteaTab(agent) {
|
||
if (!agent.history || agent.history.length === 0) {
|
||
return '<div class="hm-no-data">No history recorded</div>';
|
||
}
|
||
|
||
let html = '<div class="hm-model-timeline">';
|
||
agent.history.slice().reverse().forEach(h => {
|
||
const commit = h.commit ? h.commit.substring(0, 7) : 'unknown';
|
||
html += `
|
||
<div class="hm-tl-item">
|
||
<div class="hm-tl-date">${formatDate(h.date)}</div>
|
||
<div class="hm-tl-change">
|
||
<span class="hm-tl-from">${h.from || 'none'}</span>
|
||
<span class="hm-tl-arrow">→</span>
|
||
<span class="hm-tl-to">${h.to}</span>
|
||
</div>
|
||
<span style="font-size:.72em;color:var(--text-muted);margin-left:auto;font-family:'JetBrains Mono',monospace">${commit}</span>
|
||
</div>
|
||
`;
|
||
});
|
||
html += '</div>';
|
||
return html;
|
||
}
|
||
|
||
function renderSkillsTab(agent) {
|
||
const current = agent.current || {};
|
||
const category = current.category || 'Unknown';
|
||
const capabilities = current.capabilities || [];
|
||
|
||
let capsHtml = '';
|
||
if (capabilities.length > 0) {
|
||
capsHtml = '<div class="hm-capabilities">';
|
||
capabilities.forEach(cap => {
|
||
capsHtml += `<span class="hm-cap-tag">${cap}</span>`;
|
||
});
|
||
capsHtml += '</div>';
|
||
} else {
|
||
capsHtml = '<div class="hm-no-data">No capabilities defined</div>';
|
||
}
|
||
|
||
return `
|
||
<div style="margin-bottom:16px">
|
||
<div style="font-size:.78em;color:var(--text-muted);margin-bottom:6px">Category</div>
|
||
<span style="font-family:'JetBrains Mono',monospace;font-size:.85em;padding:4px 10px;background:rgba(0,212,255,.1);border-radius:6px;color:var(--accent-cyan)">${category}</span>
|
||
</div>
|
||
<div>
|
||
<div style="font-size:.78em;color:var(--text-muted);margin-bottom:8px">Capabilities</div>
|
||
${capsHtml}
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function renderModelsTab(agent) {
|
||
const current = agent.current || {};
|
||
const currentModel = current.model || 'unknown';
|
||
|
||
if (!agent.history || agent.history.length === 0) {
|
||
return `
|
||
<div style="margin-bottom:16px">
|
||
<div style="font-size:.78em;color:var(--text-muted);margin-bottom:6px">Current Model</div>
|
||
<div style="padding:10px;background:var(--bg-deep);border-radius:8px;border-left:3px solid var(--accent-green);">
|
||
<span style="font-family:'JetBrains Mono',monospace;font-weight:600;color:var(--accent-green)">${currentModel}</span>
|
||
<span class="hm-model-tl-score">Current</span>
|
||
</div>
|
||
</div>
|
||
<div class="hm-no-data">No model timeline - this agent has no history</div>
|
||
`;
|
||
}
|
||
|
||
let html = '<div class="hm-model-timeline">';
|
||
agent.history.forEach((h, idx) => {
|
||
const isCurrent = idx === agent.history.length - 1;
|
||
const score = h.fit_score_after || 0;
|
||
html += `
|
||
<div class="hm-tl-item ${isCurrent ? 'hm-tl-current' : ''}">
|
||
<div class="hm-tl-date">${formatDate(h.date)}</div>
|
||
<div class="hm-tl-change">
|
||
<span class="hm-tl-from">${h.from || 'initial'}</span>
|
||
<span class="hm-tl-arrow">→</span>
|
||
<span class="hm-tl-to">${h.to}</span>
|
||
</div>
|
||
${score > 0 ? `<span class="hm-model-tl-score">Score: ${score}</span>` : ''}
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
// Add current model as final entry
|
||
html += `
|
||
<div class="hm-tl-item hm-tl-current">
|
||
<div class="hm-tl-date">Now</div>
|
||
<div class="hm-tl-change">
|
||
<span class="hm-tl-to">${currentModel}</span>
|
||
</div>
|
||
<span class="hm-model-tl-score">Current</span>
|
||
</div>
|
||
`;
|
||
html += '</div>';
|
||
return html;
|
||
}
|
||
|
||
// Render Impact Tab
|
||
function renderImpact() {
|
||
const agentsWithHistory = Object.entries(agentData.agents).filter(([_, a]) => a.history && a.history.length > 0);
|
||
let totalImprovement = 0;
|
||
let countWithDeltas = 0;
|
||
let bestModel = { name: '', score: 0 };
|
||
let modelScores = {};
|
||
|
||
agentsWithHistory.forEach(([name, agent]) => {
|
||
agent.history.forEach(h => {
|
||
if (h.from && h.to && h.fit_score_after) {
|
||
const delta = (h.fit_score_after || 0) - (h.fit_score_before || 0);
|
||
totalImprovement += delta;
|
||
countWithDeltas++;
|
||
}
|
||
});
|
||
if (agent.current.benchmark?.fit_score) {
|
||
modelScores[agent.current.model] = agent.current.benchmark.fit_score;
|
||
}
|
||
});
|
||
|
||
for (const [model, score] of Object.entries(modelScores)) {
|
||
if (score > bestModel.score) {
|
||
bestModel = { name: model, score };
|
||
}
|
||
}
|
||
|
||
const avgImprovement = countWithDeltas > 0 ? (totalImprovement / countWithDeltas).toFixed(1) : 0;
|
||
const modelsEvaluated = Object.keys(modelScores).length;
|
||
const agentsOptimized = agentsWithHistory.length;
|
||
|
||
document.getElementById('impactStats').innerHTML = `
|
||
<div class="stat-card">
|
||
<div class="stat-label">Average Improvement</div>
|
||
<div class="stat-value grad-green">+${avgImprovement}%</div>
|
||
<div class="stat-sub">per model migration</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-label">Models Evaluated</div>
|
||
<div class="stat-value grad-cyan">${modelsEvaluated}</div>
|
||
<div class="stat-sub">unique models</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-label">Best Model</div>
|
||
<div class="stat-value grad-purple">${bestModel.name.split('/').pop() || 'N/A'}</div>
|
||
<div class="stat-sub">score: ${bestModel.score}</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-label">Agents Optimized</div>
|
||
<div class="stat-value grad-orange">${agentsOptimized}</div>
|
||
<div class="stat-sub">with history</div>
|
||
</div>
|
||
`;
|
||
|
||
drawImpactChart(agentsWithHistory);
|
||
}
|
||
|
||
// Draw Impact Chart (simplified from v3)
|
||
function drawImpactChart(agentsWithHistory) {
|
||
const container = document.getElementById('impactChartContainer');
|
||
if (!container) return;
|
||
|
||
const impactData = [];
|
||
agentsWithHistory.forEach(([name, agent]) => {
|
||
if (agent.history && agent.history.length > 0) {
|
||
const latest = agent.history[agent.history.length - 1];
|
||
if (latest.fit_score_before && latest.fit_score_after) {
|
||
impactData.push({
|
||
agent: name,
|
||
before: latest.fit_score_before,
|
||
after: latest.fit_score_after,
|
||
delta: latest.fit_score_after - latest.fit_score_before
|
||
});
|
||
}
|
||
}
|
||
});
|
||
|
||
if (impactData.length === 0) {
|
||
container.innerHTML = '<div class="chart-placeholder">Run model benchmarks to see impact data</div>';
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = '<canvas id="impactCanvas"></canvas>';
|
||
const canvas = document.getElementById('impactCanvas');
|
||
const ctx = canvas.getContext('2d');
|
||
|
||
const dpr = window.devicePixelRatio || 1;
|
||
const rect = canvas.getBoundingClientRect();
|
||
canvas.width = rect.width * dpr;
|
||
canvas.height = 300 * dpr;
|
||
ctx.scale(dpr, dpr);
|
||
canvas.style.width = rect.width + 'px';
|
||
canvas.style.height = '300px';
|
||
|
||
const w = rect.width;
|
||
const h = 300;
|
||
const barW = Math.min(80, (w - 60) / impactData.length / 2 - 8);
|
||
const maxVal = Math.max(...impactData.map(d => Math.max(d.before, d.after)), 100);
|
||
const scale = (h - 60) / maxVal;
|
||
|
||
ctx.clearRect(0, 0, w, h);
|
||
ctx.fillStyle = '#5a7090';
|
||
ctx.font = '11px Inter';
|
||
ctx.textAlign = 'center';
|
||
|
||
for (let i = 0; i <= 4; i++) {
|
||
const y = 30 + i * ((h - 60) / 4);
|
||
const val = Math.round(maxVal - (i * maxVal / 4));
|
||
ctx.beginPath();
|
||
ctx.strokeStyle = '#1e2d45';
|
||
ctx.moveTo(40, y);
|
||
ctx.lineTo(w - 10, y);
|
||
ctx.stroke();
|
||
ctx.fillText(val, 20, y + 4);
|
||
}
|
||
|
||
impactData.forEach((d, i) => {
|
||
const x = 55 + i * (barW * 2 + 16);
|
||
const beforeH = d.before * scale;
|
||
const afterH = d.after * scale;
|
||
const yBase = h - 30;
|
||
|
||
ctx.fillStyle = 'rgba(255,71,87,0.8)';
|
||
ctx.fillRect(x, yBase - beforeH, barW, beforeH);
|
||
ctx.fillStyle = 'rgba(0,255,148,0.8)';
|
||
ctx.fillRect(x + barW + 4, yBase - afterH, barW, afterH);
|
||
|
||
ctx.save();
|
||
ctx.translate(x + barW, yBase + 12);
|
||
ctx.rotate(Math.PI / 4);
|
||
ctx.fillStyle = '#8ba3c0';
|
||
ctx.font = '10px JetBrains Mono';
|
||
ctx.textAlign = 'left';
|
||
ctx.fillText(d.agent.substring(0, 12), 0, 0);
|
||
ctx.restore();
|
||
});
|
||
|
||
const legendX = w - 120;
|
||
ctx.fillStyle = 'rgba(255,71,87,0.8)';
|
||
ctx.fillRect(legendX, 10, 12, 12);
|
||
ctx.fillStyle = '#e8f1ff';
|
||
ctx.font = '11px Inter';
|
||
ctx.textAlign = 'left';
|
||
ctx.fillText('Before', legendX + 18, 20);
|
||
ctx.fillStyle = 'rgba(0,255,148,0.8)';
|
||
ctx.fillRect(legendX + 80, 10, 12, 12);
|
||
ctx.fillText('After', legendX + 98, 20);
|
||
}
|
||
|
||
// Filter Agents
|
||
function filterAgents() {
|
||
const search = document.getElementById('agentSearch').value.toLowerCase();
|
||
const cards = document.querySelectorAll('.agent-card');
|
||
cards.forEach(card => {
|
||
const text = card.textContent.toLowerCase();
|
||
card.style.display = text.includes(search) ? '' : 'none';
|
||
});
|
||
}
|
||
|
||
function filterCategory(category) {
|
||
document.querySelectorAll('.filter-btn').forEach(btn => btn.classList.remove('active'));
|
||
event.target.classList.add('active');
|
||
|
||
if (category === 'all') {
|
||
document.querySelectorAll('.agent-card').forEach(card => card.style.display = '');
|
||
} else {
|
||
document.querySelectorAll('.category-section').forEach(section => {
|
||
const title = section.querySelector('.category-title')?.textContent;
|
||
section.style.display = title === category ? '' : 'none';
|
||
});
|
||
}
|
||
}
|
||
|
||
// Export
|
||
function exportRecommendations() {
|
||
let recs = INLINE_RECOMMENDATIONS && INLINE_RECOMMENDATIONS.length > 0
|
||
? INLINE_RECOMMENDATIONS
|
||
: Object.entries(agentData.agents)
|
||
.filter(([_, a]) => a.current.recommendations && a.current.recommendations.length > 0)
|
||
.map(([name, agent]) => ({
|
||
agent: name,
|
||
current_model: agent.current.model,
|
||
recommendations: agent.current.recommendations
|
||
}));
|
||
|
||
const output = {
|
||
timestamp: new Date().toISOString(),
|
||
total_recommendations: recs.length,
|
||
recommendations: recs
|
||
};
|
||
|
||
document.getElementById('exportContent').textContent = JSON.stringify(output, null, 2);
|
||
document.getElementById('exportModal').classList.add('show');
|
||
}
|
||
|
||
function copyToClipboard() {
|
||
const text = document.getElementById('exportContent').textContent;
|
||
navigator.clipboard.writeText(text);
|
||
alert('Copied to clipboard!');
|
||
}
|
||
|
||
function downloadJSON() {
|
||
const text = document.getElementById('exportContent').textContent;
|
||
const blob = new Blob([text], { type: 'application/json' });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = 'agent-recommendations.json';
|
||
a.click();
|
||
URL.revokeObjectURL(url);
|
||
}
|
||
|
||
function closeModal() {
|
||
document.getElementById('exportModal').classList.remove('show');
|
||
}
|
||
|
||
// Tab switching
|
||
function switchTab(tabId) {
|
||
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
|
||
document.querySelectorAll('.tab-panel').forEach(panel => panel.classList.remove('active'));
|
||
|
||
event.target.classList.add('active');
|
||
document.getElementById('tab-' + tabId).classList.add('active');
|
||
}
|
||
|
||
// Initialize on load
|
||
init();
|
||
</script>
|
||
</body>
|
||
</html> |