Files
APAW/agent-evolution/index.standalone.html
¨NW¨ 15a7b4b7a4 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
2026-04-05 19:58:59 +01:00

654 lines
40 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: #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>