feat: add Agent Evolution Dashboard

- Create agent-evolution/ directory with standalone dashboard
- Add interactive HTML dashboard with agent/model matrix
- Add heatmap view for agent-model compatibility scores
- Add recommendations tab with optimization suggestions
- Add Gitea integration preparation (history timeline)
- Add Docker configuration for deployment
- Add build scripts for standalone HTML generation
- Add sync scripts for agent data synchronization
- Add milestone and issues documentation
- Add skills and rules for evolution sync
- Update AGENTS.md with dashboard documentation
- Update package.json with evolution scripts

Features:
- 28 agents with model assignments and fit scores
- 8 models with benchmarks (SWE-bench, RULER, Terminal)
- 11 recommendations for model optimization
- History timeline with agent changes
- Interactive modal windows for model details
- Filter and search functionality
- Russian language interface
- Works offline (file://) with embedded data

Docker:
- Dockerfile for standalone deployment
- docker-compose.evolution.yml
- docker-run.sh/docker-run.bat scripts

NPM scripts:
- sync:evolution - sync and build dashboard
- evolution:open - open in browser
- evolution:dashboard - start dev server

Status: PAUSED - foundation complete, Gitea integration pending
This commit is contained in:
¨NW¨
2026-04-05 19:58:59 +01:00
parent b899119d21
commit 15a7b4b7a4
17 changed files with 4934 additions and 2 deletions

View File

@@ -0,0 +1,654 @@
<!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: #080b12;
--bg-panel: #0e1219;
--bg-card: #141922;
--bg-card-hover: #1a2130;
--border: #1e2736;
--border-bright: #2a3650;
--text-primary: #e8edf5;
--text-secondary: #8896aa;
--text-muted: #5a6880;
--accent-cyan: #00d4ff;
--accent-green: #00ff94;
--accent-orange: #ff9f43;
--accent-red: #ff4757;
--accent-purple: #a855f7;
--glow-cyan: rgba(0,212,255,0.15);
--glow-green: rgba(0,255,148,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;
overflow-x:hidden;
}
body::before {
content:'';
position:fixed; inset:0;
background:linear-gradient(90deg,rgba(0,212,255,0.02) 1px,transparent 1px),
linear-gradient(rgba(0,212,255,0.02) 1px,transparent 1px);
background-size:60px 60px;
pointer-events:none; z-index:0;
}
.container { max-width:1540px; margin:0 auto; padding:24px 16px; position:relative; z-index:1; }
.header { text-align:center; margin-bottom:32px; }
.header h1 {
font-size:2.4em; font-weight:900;
background:linear-gradient(135deg,var(--accent-cyan),var(--accent-green));
-webkit-background-clip:text; -webkit-text-fill-color:transparent;
}
.header .sub { font-family:'JetBrains Mono',monospace; color:var(--text-muted); font-size:.8em; margin-top:6px; }
.tabs { display:flex; gap:3px; 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 12px; background:none; border:none; color:var(--text-secondary);
font-family:'Inter',sans-serif; font-size:.85em; font-weight:600; border-radius:9px; cursor:pointer; transition:all .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)); }
.tab-panel { display:none; }
.tab-panel.active { display:block; }
.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;
transition:all .3s;
}
.stat-card:hover { border-color:var(--accent-cyan); transform:translateY(-2px); }
.stat-label { font-family:'JetBrains Mono',monospace; font-size:.65em; color:var(--text-muted); text-transform:uppercase; letter-spacing:1px; }
.stat-value { font-size:2em; font-weight:800; margin:4px 0; }
.stat-sub { font-size:.75em; color:var(--text-secondary); }
.grad-cyan { background:linear-gradient(135deg,var(--accent-cyan),var(--accent-green)); -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-orange { background:linear-gradient(135deg,var(--accent-orange),#facc15); -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; }
.sec-hdr { display:flex; align-items:center; gap:10px; margin-bottom:16px; padding-bottom:8px; border-bottom:1px solid var(--border); }
.sec-hdr h2 { font-size:1.1em; font-weight:700; }
.badge { font-family:'JetBrains Mono',monospace; font-size:.65em; padding:3px 9px; border-radius:16px; }
.badge-cyan { background:var(--glow-cyan); color:var(--accent-cyan); border:1px solid rgba(0,212,255,.2); }
.badge-green { background:var(--glow-green); color:var(--accent-green); border:1px solid rgba(0,255,148,.2); }
.badge-orange { background:rgba(255,159,67,.1); color:var(--accent-orange); border:1px solid rgba(255,159,67,.2); }
.tbl-wrap { overflow-x:auto; border-radius:10px; border:1px solid var(--border); background:var(--bg-card); margin-bottom:24px; }
table.dt { width:100%; border-collapse:collapse; font-size:.84em; }
table.dt th { font-family:'JetBrains Mono',monospace; font-size:.7em; color:var(--text-muted); text-transform:uppercase; padding:12px 14px; background:var(--bg-panel); border-bottom:2px solid var(--border); text-align:left; }
table.dt td { padding:10px 14px; border-bottom:1px solid var(--border); }
table.dt tr:hover td { background:var(--bg-card-hover); }
table.dt tr { cursor:pointer; transition:background .15s; }
.mbadge { display:inline-block; padding:3px 8px; border-radius:5px; font-family:'JetBrains Mono',monospace; font-size:.78em; font-weight:500; cursor:pointer; transition:all .2s; }
.mbadge:hover { transform:scale(1.05); }
.mbadge.qwen { background:rgba(59,130,246,.12); color:#60a5fa; border:1px solid rgba(59,130,246,.25); }
.mbadge.minimax { background:rgba(255,159,67,.12); color:#ff9f43; border:1px solid rgba(255,159,67,.25); }
.mbadge.nemotron { background:rgba(34,197,94,.12); color:#4ade80; border:1px solid rgba(34,197,94,.25); }
.mbadge.glm { background:rgba(0,255,148,.08); color:#00ff94; border:1px solid rgba(0,255,148,.2); }
.mbadge.gptoss { background:rgba(168,85,247,.12); color:#c084fc; border:1px solid rgba(168,85,247,.25); }
.mbadge.devstral { background:rgba(0,212,255,.12); color:#00d4ff; border:1px solid rgba(0,212,255,.25); }
.prov-tag { display:inline-block; padding:1px 6px; border-radius:3px; font-size:.62em; font-family:'JetBrains Mono',monospace; }
.prov-tag.ollama { background:rgba(0,212,255,.1); color:var(--accent-cyan); }
.prov-tag.groq { background:rgba(255,71,87,.1); color:#ff6b81; }
.prov-tag.openrouter { background:rgba(168,85,247,.1); color:#c084fc; }
.sbar { display:flex; align-items:center; gap:6px; }
.sbar-bg { width:60px; height:5px; background:var(--border); border-radius:3px; overflow:hidden; }
.sbar-fill { height:100%; border-radius:3px; }
.sbar-fill.h { background:linear-gradient(90deg,var(--accent-green),#00ff94); }
.sbar-fill.m { background:linear-gradient(90deg,var(--accent-orange),#ffc048); }
.sbar-fill.l { background:linear-gradient(90deg,var(--accent-red),#ff6b81); }
.snum { font-family:'JetBrains Mono',monospace; font-weight:600; font-size:.85em; min-width:28px; }
.rec-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(380px,1fr)); gap:14px; margin-bottom:24px; }
.rec-card {
background:var(--bg-card); border:1px solid var(--border); border-radius:10px; padding:16px;
transition:all .3s; border-left:3px solid var(--border);
}
.rec-card:hover { border-color:var(--accent-green); transform:translateY(-2px); }
.rec-card.critical { border-left-color:var(--accent-red); }
.rec-card.high { border-left-color:var(--accent-orange); }
.rec-card.medium { border-left-color:var(--accent-orange); }
.rec-card.optimal { border-left-color:var(--accent-green); }
.rec-hdr { display:flex; justify-content:space-between; align-items:center; margin-bottom:10px; }
.rec-agent { font-weight:700; font-size:1em; color:var(--accent-cyan); }
.imp-badge { padding:2px 8px; border-radius:16px; font-family:'JetBrains Mono',monospace; font-size:.68em; font-weight:600; }
.imp-badge.critical { background:rgba(255,71,87,.18); color:var(--accent-red); }
.imp-badge.high { background:rgba(255,159,67,.18); color:var(--accent-orange); }
.imp-badge.medium { background:rgba(250,204,21,.18); color:var(--accent-yellow); }
.imp-badge.optimal { background:rgba(0,255,148,.18); color:var(--accent-green); }
.swap-vis { display:flex; align-items:center; gap:8px; margin:10px 0; padding:10px; background:var(--bg-panel); border-radius:6px; }
.swap-from { font-family:'JetBrains Mono',monospace; font-size:.75em; padding:3px 8px; border-radius:4px; background:rgba(255,71,87,.08); color:#ff6b81; border:1px solid rgba(255,71,87,.15); text-decoration:line-through; opacity:.65; }
.swap-to { font-family:'JetBrains Mono',monospace; font-size:.75em; padding:3px 8px; border-radius:4px; background:rgba(0,255,148,.08); color:#00ff94; border:1px solid rgba(0,255,148,.2); font-weight:600; }
.swap-arrow { color:var(--accent-green); font-size:1.2em; }
.rec-reason { font-size:.82em; color:var(--text-secondary); line-height:1.5; margin-top:10px; padding-top:10px; border-top:1px solid var(--border); }
.hm-wrap { overflow-x:auto; border-radius:10px; border:1px solid var(--border); background:var(--bg-card); padding:16px; margin-bottom:24px; }
.hm-title { font-weight:700; font-size:1.05em; margin-bottom:6px; }
.hm-sub { font-size:.76em; color:var(--text-muted); margin-bottom:12px; }
.hm-table { border-collapse:collapse; width:100%; }
.hm-table th { font-family:'JetBrains Mono',monospace; font-size:.62em; color:var(--text-muted); padding:8px 6px; text-align:center; white-space:nowrap; }
.hm-table th.hm-role { text-align:left; min-width:140px; font-size:.68em; }
.hm-table td { text-align:center; padding:6px 4px; font-family:'JetBrains Mono',monospace; font-size:.74em; font-weight:600; border-radius:3px; cursor:pointer; transition:all .12s; min-width:36px; }
.hm-table td:hover { transform:scale(1.1); z-index:2; }
.hm-table td.hm-r { text-align:left; font-family:'Inter',sans-serif; font-size:.78em; font-weight:500; color:var(--text-secondary); cursor:default; }
.hm-table td.hm-r:hover { transform:none; }
.hm-cur { outline:2px solid var(--accent-cyan); outline-offset:-2px; }
.modal { display:none; position:fixed; inset:0; background:rgba(0,0,0,.85); 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:800px; width:100%; max-height:85vh; overflow-y:auto; }
.modal-header { display:flex; justify-content:space-between; align-items:center; padding:20px; border-bottom:1px solid var(--border); position:sticky; top:0; background:var(--bg-panel); z-index:1; }
.modal-title { font-weight:700; font-size:1.2em; display:flex; align-items:center; gap:10px; }
.modal-close { background:none; border:none; color:var(--text-muted); font-size:1.5em; cursor:pointer; }
.modal-close:hover { color:var(--accent-red); }
.modal-body { padding:20px; }
.model-info { display:grid; grid-template-columns:repeat(2,1fr); gap:12px; margin-bottom:16px; }
.model-info-item { background:var(--bg-card); padding:12px; border-radius:6px; }
.model-info-label { font-size:.7em; color:var(--text-muted); text-transform:uppercase; }
.model-info-value { font-size:1.1em; font-weight:600; margin-top:2px; }
.model-tags { display:flex; flex-wrap:wrap; gap:6px; margin-top:12px; }
.model-tag { padding:4px 10px; background:rgba(0,212,255,.1); border:1px solid rgba(0,212,255,.2); border-radius:16px; font-size:.75em; color:var(--accent-cyan); }
.gitea-timeline { position:relative; padding-left:24px; }
.gitea-timeline::before { content:''; position:absolute; left:8px; top:0; bottom:0; width:2px; background:var(--border); }
.gitea-item { position:relative; padding:12px 0 12px 24px; border-bottom:1px solid var(--border); }
.gitea-item:last-child { border-bottom:none; }
.gitea-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); }
.gitea-date { font-family:'JetBrains Mono',monospace; font-size:.75em; color:var(--text-muted); }
.gitea-content { font-size:.9em; margin-top:4px; }
.gitea-agent { font-weight:600; color:var(--accent-cyan); }
.gitea-change { color:var(--text-secondary); }
.frow { display:flex; gap:6px; margin-bottom:16px; flex-wrap:wrap; }
.fbtn { padding:6px 14px; background:var(--bg-card); border:1px solid var(--border); color:var(--text-secondary); border-radius:20px; font-size:.8em; cursor:pointer; transition:all .2s; }
.fbtn:hover,.fbtn.active { border-color:var(--accent-cyan); color:var(--accent-cyan); background:rgba(0,212,255,.06); }
.models-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(300px,1fr)); gap:12px; }
.mc { background:var(--bg-card); border:1px solid var(--border); border-radius:10px; padding:16px; cursor:pointer; transition:all .25s; }
.mc:hover { border-color:var(--accent-cyan); transform:translateY(-2px); box-shadow:0 6px 20px var(--glow-cyan); }
@media(max-width:768px) {
.header h1 { font-size:1.5em; }
.tabs { flex-wrap:wrap; }
.rec-grid { grid-template-columns:1fr; }
.stats-row { grid-template-columns:repeat(2,1fr); }
.model-info { grid-template-columns:1fr; }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>.Agent Evolution</h1>
<div class="sub">Эволюция агентной системы APAW • Модели и рекомендации</div>
</div>
<div class="tabs">
<button class="tab-btn active" onclick="switchTab('overview')">Обзор</button>
<button class="tab-btn" onclick="switchTab('matrix')">Матрица</button>
<button class="tab-btn" onclick="switchTab('recs')">Рекомендации</button>
<button class="tab-btn" onclick="switchTab('history')">История</button>
<button class="tab-btn" onclick="switchTab('models')">Модели</button>
</div>
<div id="tab-overview" class="tab-panel active">
<div class="stats-row" id="statsRow"></div>
<div class="sec-hdr">
<h2>Конфигурация агентов</h2>
<span class="badge badge-cyan" id="agentsCount">0 агентов</span>
</div>
<div class="tbl-wrap">
<table class="dt">
<thead><tr>
<th>Агент</th>
<th>Модель</th>
<th>Провайдер</th>
<th>Fit</th>
<th>Статус</th>
</tr></thead>
<tbody id="agentsTable"></tbody>
</table>
</div>
</div>
<div id="tab-matrix" class="tab-panel">
<div class="hm-wrap">
<div class="hm-title">Матрица «Агент × Модель»</div>
<div class="hm-sub">Кликните на ячейку для подробностей • ★ = текущая модель</div>
<table class="hm-table" id="heatmapTable"></table>
</div>
</div>
<div id="tab-recs" class="tab-panel">
<div class="sec-hdr">
<h2>Рекомендации по оптимизации</h2>
<span class="badge badge-orange" id="recsCount">0 рекомен-й</span>
</div>
<div class="frow">
<button class="fbtn active" onclick="filterRecs('all',this)">Все</button>
<button class="fbtn" onclick="filterRecs('critical',this)">Критичные</button>
<button class="fbtn" onclick="filterRecs('high',this)">Высокие</button>
<button class="fbtn" onclick="filterRecs('medium',this)">Средние</button>
<button class="fbtn" onclick="filterRecs('optimal',this)">Оптимальные</button>
</div>
<div class="rec-grid" id="recsGrid"></div>
</div>
<div id="tab-history" class="tab-panel">
<div class="sec-hdr">
<h2>История изменений</h2>
<span class="badge badge-green" id="historyCount">0 изменений</span>
</div>
<div class="gitea-timeline" id="historyTimeline"></div>
</div>
<div id="tab-models" class="tab-panel">
<div class="sec-hdr">
<h2>Доступные модели</h2>
<span class="badge badge-cyan">Ollama + Groq + OpenRouter</span>
</div>
<div class="models-grid" id="modelsGrid"></div>
</div>
</div>
<div class="modal" id="modelModal">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">
<span id="modalTitle">Модель</span>
<span class="prov-tag" id="modalProvider">Ollama</span>
</div>
<button class="modal-close" onclick="closeModal()">&times;</button>
</div>
<div class="modal-body">
<div class="model-info" id="modalInfo"></div>
<div class="model-tags" id="modalTags"></div>
<div style="margin-top:16px">
<h3 style="font-size:.95em;margin-bottom:10px">Агенты на этой модели</h3>
<div id="modalAgents" style="display:flex;flex-wrap:wrap;gap:8px"></div>
</div>
</div>
</div>
</div>
<script>
// ======================= EMBEDDED DATA =======================
const EMBEDDED_DATA = {
agents: {
"lead-developer": {current:{model:"ollama-cloud/qwen3-coder:480b",provider:"Ollama",category:"Core Dev",fit:92,desc:"Primary code writer",status:"optimal"}},
"frontend-developer": {current:{model:"ollama-cloud/qwen3-coder:480b",provider:"Ollama",category:"Core Dev",fit:90,desc:"UI implementation",status:"optimal"}},
"backend-developer": {current:{model:"ollama-cloud/qwen3-coder:480b",provider:"Ollama",category:"Core Dev",fit:91,desc:"Node.js/APIs",status:"optimal"}},
"go-developer": {current:{model:"ollama-cloud/qwen3-coder:480b",provider:"Ollama",category:"Core Dev",fit:85,desc:"Go backend",status:"optimal"}},
"sdet-engineer": {current:{model:"ollama-cloud/qwen3-coder:480b",provider:"Ollama",category:"QA",fit:88,desc:"TDD tests",status:"optimal"}},
"code-skeptic": {current:{model:"ollama-cloud/minimax-m2.5",provider:"Ollama",category:"QA",fit:85,desc:"Adversarial review",status:"good"}},
"security-auditor": {current:{model:"ollama-cloud/nemotron-3-super",provider:"Ollama",category:"Security",fit:80,desc:"OWASP scanner",status:"good"}},
"performance-engineer": {current:{model:"ollama-cloud/nemotron-3-super",provider:"Ollama",category:"Performance",fit:82,desc:"N+1 detection",status:"good"}},
"system-analyst": {current:{model:"ollama-cloud/glm-5",provider:"Ollama",category:"Analysis",fit:82,desc:"Architecture design",status:"good"}},
"requirement-refiner": {current:{model:"ollama-cloud/gpt-oss:120b",provider:"Ollama",category:"Analysis",fit:62,desc:"User Stories",status:"needs-update"}},
"history-miner": {current:{model:"ollama-cloud/glm-5",provider:"Ollama",category:"Analysis",fit:78,desc:"Git search",status:"good"}},
"capability-analyst": {current:{model:"ollama-cloud/gpt-oss:120b",provider:"Ollama",category:"Analysis",fit:66,desc:"Gap analysis",status:"needs-update"}},
"orchestrator": {current:{model:"ollama-cloud/glm-5",provider:"Ollama",category:"Process",fit:80,desc:"Task routing",status:"good"}},
"release-manager": {current:{model:"ollama-cloud/devstral-2:123b",provider:"Ollama",category:"Process",fit:75,desc:"Git ops",status:"good"}},
"evaluator": {current:{model:"ollama-cloud/nemotron-3-super",provider:"Ollama",category:"Process",fit:82,desc:"Scoring",status:"good"}},
"prompt-optimizer": {current:{model:"ollama-cloud/nemotron-3-super",provider:"Ollama",category:"Process",fit:80,desc:"Prompt improvement",status:"good"}},
"the-fixer": {current:{model:"ollama-cloud/minimax-m2.5",provider:"Ollama",category:"Fixes",fit:88,desc:"Bug fixing",status:"optimal"}},
"product-owner": {current:{model:"ollama-cloud/glm-5",provider:"Ollama",category:"Management",fit:76,desc:"Backlog",status:"good"}},
"workflow-architect": {current:{model:"ollama-cloud/glm-5",provider:"Ollama",category:"Process",fit:74,desc:"Workflow design",status:"good"}},
"markdown-validator": {current:{model:"ollama-cloud/nemotron-3-nano:30b",provider:"Ollama",category:"Validation",fit:72,desc:"Markdown check",status:"good"}},
"agent-architect": {current:{model:"ollama-cloud/gpt-oss:120b",provider:"Ollama",category:"Meta",fit:69,desc:"Agent design",status:"needs-update"}},
"planner": {current:{model:"ollama-cloud/nemotron-3-super",provider:"Ollama",category:"Cognitive",fit:84,desc:"Task planning",status:"good"}},
"reflector": {current:{model:"ollama-cloud/nemotron-3-super",provider:"Ollama",category:"Cognitive",fit:82,desc:"Self-reflection",status:"good"}},
"memory-manager": {current:{model:"ollama-cloud/nemotron-3-super",provider:"Ollama",category:"Cognitive",fit:90,desc:"Memory systems",status:"optimal"}},
"devops-engineer": {current:{model:null,provider:null,category:"DevOps",fit:0,desc:"Docker/K8s/CI",status:"new"}},
"flutter-developer": {current:{model:"ollama-cloud/qwen3-coder:480b",provider:"Ollama",category:"Core Dev",fit:86,desc:"Flutter mobile",status:"optimal"}}
},
models: {
"qwen3-coder:480b":{name:"Qwen3-Coder 480B",org:"Qwen",swe:66.5,ctx:"256K→1M",desc:"SOTA кодинг. Сравним с Claude Sonnet 4.",tags:["coding","agent","tools"]},
"minimax-m2.5":{name:"MiniMax M2.5",org:"MiniMax",swe:80.2,ctx:"128K",desc:"Лидер SWE-bench 80.2%",tags:["coding","agent"]},
"nemotron-3-super":{name:"Nemotron 3 Super",org:"NVIDIA",swe:60.5,ctx:"1M",ruler:91.75,desc:"RULER@1M 91.75%! PinchBench 85.6%",tags:["agent","reasoning","1M-ctx"]},
"nemotron-3-nano:30b":{name:"Nemotron 3 Nano",org:"NVIDIA",ctx:"128K",desc:"Ультра-компактная. Thinking mode.",tags:["efficient","thinking"]},
"glm-5":{name:"GLM-5",org:"Z.ai",ctx:"128K",desc:"Мощный reasoning",tags:["reasoning","agent"]},
"gpt-oss:120b":{name:"GPT-OSS 120B",org:"OpenAI",swe:62.4,ctx:"130K",desc:"O4-mini уровень. Apache 2.0.",tags:["reasoning","tools"]},
"devstral-2:123b":{name:"Devstral 2",org:"Mistral",ctx:"128K",desc:"Multi-file editing. Vision.",tags:["coding","vision"]}
},
recommendations: [
{agent:"requirement-refiner",from:"gpt-oss:120b",to:"nemotron-3-super",priority:"critical",quality:"+22%",context:"130K→1M",reason:"Nemotron с RULER@1M 91.75% значительно лучше для спецификаций."},
{agent:"capability-analyst",from:"gpt-oss:120b",to:"nemotron-3-super",priority:"critical",quality:"+21%",context:"130K→1M",reason:"Gap analysis требует агентских способностей. Nemotron (80 vs 66)."},
{agent:"agent-architect",from:"gpt-oss:120b",to:"nemotron-3-super",priority:"high",quality:"+19%",context:"130K→1M",reason:"Agent design с длинным контекстом. Nemotron (82 vs 69)."},
{agent:"history-miner",from:"glm-5",to:"nemotron-3-super",priority:"high",quality:"+13%",context:"128K→1M",reason:"Git history требует 1M контекст. Nemotron (88 vs 78)."},
{agent:"devops-engineer",from:"(не назначена)",to:"nemotron-3-super",priority:"critical",reason:"Новый агент. Nemotron 1M для docker-compose + k8s manifests."},
{agent:"prompt-optimizer",from:"nemotron-3-super",to:"qwen3.6-plus:free",priority:"high",quality:"+2%",reason:"FREE на OpenRouter. Terminal-Bench 61.6%"},
{agent:"memory-manager",from:"gpt-oss:120b",to:"nemotron-3-super",priority:"applied",quality:"+30%",context:"130K→1M",reason:"Уже применено. RULER@1M критичен для памяти."},
{agent:"evaluator",from:"gpt-oss:120b",to:"nemotron-3-super",priority:"applied",quality:"+15%",reason:"Уже применено. Nemotron оптимален для оценки."},
{agent:"the-fixer",from:"minimax-m2.5",to:"minimax-m2.5",priority:"optimal",reason:"MiniMax M2.5 (SWE 80.2%) уже оптимален для фиксов."},
{agent:"lead-developer",from:"qwen3-coder:480b",to:"qwen3-coder:480b",priority:"optimal",reason:"Qwen3-Coder (SWE 66.5%) оптимален для кодинга."}
],
history: [
{date:"2026-04-05T05:21:00Z",agent:"security-auditor",from:"deepseek-v3.2",to:"nemotron-3-super",reason:"RULER@1M для security"},
{date:"2026-04-05T05:21:00Z",agent:"performance-engineer",from:"gpt-oss:120b",to:"nemotron-3-super",reason:"Лучший reasoning"},
{date:"2026-04-05T05:21:00Z",agent:"memory-manager",from:"gpt-oss:120b",to:"nemotron-3-super",reason:"1M контекст критичен"},
{date:"2026-04-05T05:21:00Z",agent:"evaluator",from:"gpt-oss:120b",to:"nemotron-3-super",reason:"Оценка качества"},
{date:"2026-04-05T05:21:00Z",agent:"planner",from:"gpt-oss:120b",to:"nemotron-3-super",reason:"CoT/ToT планирование"},
{date:"2026-04-05T05:21:00Z",agent:"reflector",from:"gpt-oss:120b",to:"nemotron-3-super",reason:"Рефлексия"},
{date:"2026-04-05T05:21:00Z",agent:"system-analyst",from:"gpt-oss:120b",to:"glm-5",reason:"GLM-5 для архитектуры"},
{date:"2026-04-05T05:21:00Z",agent:"go-developer",from:"deepseek-v3.2",to:"qwen3-coder:480b",reason:"Qwen оптимален для Go"},
{date:"2026-04-05T05:21:00Z",agent:"markdown-validator",from:"qwen3.6-plus:free",to:"nemotron-3-nano:30b",reason:"Nano для лёгких задач"},
{date:"2026-04-05T05:21:00Z",agent:"prompt-optimizer",from:"qwen3.6-plus:free",to:"nemotron-3-super",reason:"Анализ промптов"},
{date:"2026-04-05T05:21:00Z",agent:"product-owner",from:"qwen3.6-plus:free",to:"glm-5",reason:"Управление backlog"}
],
lastUpdated:"2026-04-05T18:00:00Z"
};
// ======================= INITIALIZATION =======================
const agentData = EMBEDDED_DATA;
const modelData = EMBEDDED_DATA.models;
const recommendations = EMBEDDED_DATA.recommendations;
const historyData = EMBEDDED_DATA.history;
function init() {
renderStats();
renderAgentsTable();
renderHeatmap();
renderRecommendations();
renderHistory();
renderModels();
}
// ======================= RENDER FUNCTIONS =======================
function renderStats() {
const agents = Object.values(agentData.agents);
const total = agents.length;
const optimal = agents.filter(a => a.current.status === 'optimal').length;
const needsUpdate = agents.filter(a => a.current.status === 'needs-update').length;
const critical = recommendations.filter(r => r.priority === 'critical').length;
document.getElementById('statsRow').innerHTML = `
<div class="stat-card">
<div class="stat-label">Всего агентов</div>
<div class="stat-value grad-cyan">${total}</div>
<div class="stat-sub">${Object.keys(agentData.agents).filter(a => agentData.agents[a].current.status === 'optimal').length} оптимально</div>
</div>
<div class="stat-card">
<div class="stat-label">Требуют внимания</div>
<div class="stat-value grad-orange">${needsUpdate + critical}</div>
<div class="stat-sub">${critical} критичных</div>
</div>
<div class="stat-card">
<div class="stat-label">Провайдеров</div>
<div class="stat-value grad-green">3</div>
<div class="stat-sub">Ollama, Groq, OR</div>
</div>
<div class="stat-card">
<div class="stat-label">История</div>
<div class="stat-value grad-purple">${historyData.length}</div>
<div class="stat-sub">изменений записано</div>
</div>
`;
document.getElementById('agentsCount').textContent = total + ' агентов';
}
function renderAgentsTable() {
const rows = Object.entries(agentData.agents).map(([name, data]) => {
const model = data.current.model || 'не назначена';
const provider = data.current.provider || '—';
const fit = data.current.fit || 0;
const status = data.current.status || 'good';
const statusIcon = status === 'new' ? '🆕' :
status === 'needs-update' ? '⚠️' :
status === 'optimal' ? '✅' : '🟡';
const statusText = status === 'new' ? 'Новый' :
status === 'needs-update' ? 'Улучшить' :
status === 'optimal' ? 'Оптимально' : 'Хорошо';
const modelClass = model.includes('qwen') ? 'qwen' :
model.includes('minimax') ? 'minimax' :
model.includes('nemotron') ? 'nemotron' :
model.includes('glm') ? 'glm' :
model.includes('gpt-oss') ? 'gptoss' :
model.includes('devstral') ? 'devstral' : '';
return `
<tr onclick="showAgentModal('${name}')" style="cursor:pointer" onmouseover="this.style.background='var(--bg-card-hover)'" onmouseout="this.style.background=''">
<td style="font-weight:600">${name}</td>
<td><span class="mbadge ${modelClass}">${model}</span></td>
<td><span class="prov-tag ${provider?.toLowerCase()||''}">${provider}</span></td>
<td><div class="sbar"><div class="sbar-bg"><div class="sbar-fill ${getScoreClass(fit)}" style="width:${fit}%"></div></div><span class="snum">${fit}</span></div></td>
<td>${statusIcon} ${statusText}</td>
</tr>
`;
}).join('');
document.getElementById('agentsTable').innerHTML = rows;
}
function renderHeatmap() {
const agents = ['Core Dev', 'QA', 'Security', 'Analysis', 'Process', 'Cognitive', 'DevOps'];
const models = ['Qwen3-Coder', 'MiniMax M2.5', 'Nemotron', 'GLM-5', 'GPT-OSS'];
// Score matrix
const scores = [
[92, 82, 72, 68, 65], // Core Dev
[88, 85, 76, 72, 70], // QA
[75, 72, 90, 68, 65], // Security
[72, 68, 88, 82, 62], // Analysis
[78, 72, 85, 80, 65], // Process
[75, 70, 92, 78, 66], // Cognitive
[82, 68, 85, 75, 70], // DevOps
];
let html = '<thead><tr><th class="hm-role">Категория</th>';
models.forEach(m => html += `<th>${m}</th>`);
html += '</tr></thead><tbody>';
agents.forEach((cat, i) => {
html += `<tr><td class="hm-r">${cat}</td>`;
models.forEach((m, j) => {
const score = scores[i][j];
const isCurrent = (i === 0 && j === 0) || (i === 2 && j === 2) || (i === 3 && j === 3) || (i === 4 && j === 3) || (i === 5 && j === 2);
const style = `background:${getScoreColor(score)}15;color:${getScoreColor(score)}${isCurrent ? ';outline:2px solid var(--accent-cyan);outline-offset:-2px' : ''}`;
html += `<td style="${style}" onclick="showModelFromHeatmap('${m}')">${score}${isCurrent ? '<span style="color:#FFD700;font-size:.75em">★</span>' : ''}</td>`;
});
html += '</tr>';
});
html += '</tbody>';
document.getElementById('heatmapTable').innerHTML = html;
}
function renderRecommendations() {
document.getElementById('recsCount').textContent = recommendations.length + ' рекомендаций';
const html = recommendations.map(r => {
const priorityClass = r.priority === 'critical' ? 'critical' : r.priority === 'high' ? 'high' : r.priority === 'medium' ? 'medium' : 'optimal';
const priorityText = r.priority === 'critical' ? '🔴 Критично' :
r.priority === 'high' ? '🟠 Высокий' :
r.priority === 'medium' ? '🟡 Средний' : '✅ Оптимально';
return `
<div class="rec-card ${priorityClass}" data-priority="${r.priority}">
<div class="rec-hdr">
<span class="rec-agent">${r.agent}</span>
<span class="imp-badge ${priorityClass}">${priorityText}</span>
</div>
<div class="swap-vis">
<span class="swap-from">${r.from}</span>
<span class="swap-arrow">→</span>
<span class="swap-to">${r.to}</span>
</div>
<div class="rec-reason">${r.reason}</div>
</div>
`;
}).join('');
document.getElementById('recsGrid').innerHTML = html;
}
function renderHistory() {
document.getElementById('historyCount').textContent = historyData.length + ' изменений';
const html = historyData.map(h => `
<div class="gitea-item">
<div class="gitea-date">${formatDate(h.date)}</div>
<div class="gitea-content">
<span class="gitea-agent">${h.agent}</span>
<span class="gitea-change">: ${h.from}${h.to}</span>
</div>
<div style="font-size:.8em;color:var(--text-muted)">${h.reason}</div>
</div>
`).join('');
document.getElementById('historyTimeline').innerHTML = html;
}
function renderModels() {
const models = Object.values(modelData);
const html = models.map(m => `
<div class="mc" onclick="showModelModal('${m.name}')">
<div style="font-weight:700;font-size:1.05em">${m.name}</div>
<div style="font-size:.75em;color:var(--text-muted);margin:4px 0">${m.org} • Контекст: ${m.ctx}</div>
${m.swe ? `<div style="font-size:.8em"><span style="color:var(--text-muted)">SWE-bench:</span> <span style="color:var(--accent-green);font-weight:600">${m.swe}%</span></div>` : ''}
${m.ruler ? `<div style="font-size:.8em"><span style="color:var(--text-muted)">RULER@1M:</span> <span style="color:var(--accent-cyan);font-weight:600">${m.ruler}%</span></div>` : ''}
<div style="font-size:.78em;color:var(--text-secondary);margin-top:8px;line-height:1.4">${m.desc}</div>
<div style="margin-top:8px">${m.tags.map(t => `<span style="font-size:.68em;padding:2px 6px;background:rgba(0,212,255,.1);border-radius:12px;color:var(--accent-cyan);margin-right:4px">${t}</span>`).join('')}</div>
</div>
`).join('');
document.getElementById('modelsGrid').innerHTML = html;
}
// ======================= MODAL FUNCTIONS =======================
function showModelModal(modelName) {
const m = Object.values(modelData).find(m => m.name === modelName);
if (!m) return;
document.getElementById('modalTitle').textContent = m.name;
document.getElementById('modalProvider').textContent = m.org;
document.getElementById('modalInfo').innerHTML = `
<div class="model-info-item">
<div class="model-info-label">Организация</div>
<div class="model-info-value">${m.org}</div>
</div>
<div class="model-info-item">
<div class="model-info-label">Контекст</div>
<div class="model-info-value">${m.ctx}</div>
</div>
${m.swe ? `<div class="model-info-item">
<div class="model-info-label">SWE-bench</div>
<div class="model-info-value" style="color:var(--accent-green)">${m.swe}%</div>
</div>` : ''}
${m.ruler ? `<div class="model-info-item">
<div class="model-info-label">RULER@1M</div>
<div class="model-info-value" style="color:var(--accent-cyan)">${m.ruler}%</div>
</div>` : ''}
`;
document.getElementById('modalTags').innerHTML = m.tags.map(t => `<span class="model-tag">${t}</span>`).join('');
// Find agents using this model
const agentsUsing = Object.entries(agentData.agents)
.filter(([_, d]) => d.current.model?.includes(m.name.toLowerCase().split(' ')[0].toLowerCase()))
.map(([name, _]) => name);
document.getElementById('modalAgents').innerHTML = agentsUsing.length > 0
? agentsUsing.map(a => `<span class="mbadge">${a}</span>`).join('')
: '<span style="color:var(--text-muted)">Нет агентов на этой модели</span>';
document.getElementById('modelModal').classList.add('show');
}
function showAgentModal(agentName) {
const a = agentData.agents[agentName];
if (!a) return;
document.getElementById('modalTitle').textContent = agentName;
document.getElementById('modalProvider').textContent = a.current.provider || '—';
document.getElementById('modalInfo').innerHTML = `
<div class="model-info-item">
<div class="model-info-label">Модель</div>
<div class="model-info-value">${a.current.model || 'не назначена'}</div>
</div>
<div class="model-info-item">
<div class="model-info-label">Категория</div>
<div class="model-info-value">${a.current.category}</div>
</div>
<div class="model-info-item">
<div class="model-info-label">Fit Score</div>
<div class="model-info-value" style="color:${getScoreColor(a.current.fit)}">${a.current.fit || '—'}</div>
</div>
<div class="model-info-item">
<div class="model-info-label">Статус</div>
<div class="model-info-value">${a.current.status || '—'}</div>
</div>
`;
document.getElementById('modalTags').innerHTML = '';
document.getElementById('modalAgents').innerHTML = `<div style="color:var(--text-secondary);font-size:.9em">${a.current.desc}</div>`;
document.getElementById('modelModal').classList.add('show');
}
function showModelFromHeatmap(modelName) {
showModelModal(modelName);
}
function closeModal() {
document.getElementById('modelModal').classList.remove('show');
}
function filterRecs(filter, btn) {
document.querySelectorAll('.frow .fbtn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
if (filter === 'all') {
document.querySelectorAll('.rec-card').forEach(c => c.style.display = '');
} else {
document.querySelectorAll('.rec-card').forEach(c => {
c.style.display = c.dataset.priority === filter ? '' : 'none';
});
}
}
// ======================= UTILITIES =======================
function getScoreColor(score) {
if (score >= 85) return '#00ff94';
if (score >= 70) return '#ffc048';
return '#ff6b81';
}
function getScoreClass(score) {
if (score >= 85) return 'h';
if (score >= 70) return 'm';
return 'l';
}
function formatDate(dateStr) {
const date = new Date(dateStr);
return date.toLocaleDateString('ru-RU', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' });
}
function switchTab(tabId) {
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.getElementById('tab-' + tabId).classList.add('active');
event.target.classList.add('active');
}
document.getElementById('modelModal').addEventListener('click', (e) => {
if (e.target.id === 'modelModal') closeModal();
});
// Initialize
init();
</script>
</body>
</html>