- 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
654 lines
40 KiB
HTML
654 lines
40 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>APAW Agent Evolution Dashboard</title>
|
||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
||
<style>
|
||
:root {
|
||
--bg-deep: #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()">×</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> |