Files
APAW/agent-evolution/index.standalone.html
Deploy Bot 047a87afb4 feat(agent-models): apply MEDIUM+LOW priority model migrations
- 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)
2026-05-25 15:07:17 +01:00

2781 lines
107 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html 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>