fix(dashboard): heatmap cell click + 5th tab + model sync fixes\n\n- restore hmModal with 4 legacy tabs + new Performance Graph tab\n- fix event.target in research-dashboard.template.html switchTab\n- fix showCellDetail event.stopPropagation for modal persistence\n- update agent models + sync KILO_SPEC.md and AGENTS.md

This commit is contained in:
Deploy Bot
2026-05-27 13:46:55 +01:00
parent 36455ccf24
commit 7635cb62cd
6 changed files with 768 additions and 1363 deletions

View File

@@ -433,35 +433,40 @@ Provider availability depends on configuration. Common providers include:
| Agent | Role | Model |
|-------|------|-------|
| `@RequirementRefiner` | Converts vague ideas and bug reports into strict User Stories with acceptance criteria checklists. | ollama-cloud/kimi-k2-thinking |
| `@HistoryMiner` | Analyzes git history to find duplicates and past solutions, preventing regression and duplicate work. | ollama-cloud/nemotron-3-super |
| `@SystemAnalyst` | Designs technical specifications, data schemas, and API contracts before implementation. | ollama-cloud/nemotron-3-super |
| `@SdetEngineer` | Writes tests following TDD methodology. | ollama-cloud/qwen3-coder:480b |
| `@LeadDeveloper` | Primary code writer for backend and core logic. | ollama-cloud/nemotron-3-super |
| `@FrontendDeveloper` | Handles UI implementation with multimodal capabilities. | ollama-cloud/kimi-k2.5 |
| `@BackendDeveloper` | Backend specialist for Node. | ollama-cloud/deepseek-v3.2 |
| `@GoDeveloper` | Go backend specialist for Gin, Echo, APIs, and database integration. | ollama-cloud/qwen3-coder:480b |
| `@RequirementRefiner` | Converts vague ideas and bug reports into strict User Stories with acceptance criteria checklists. | ollama-cloud/kimi-k2.6 |
| `@HistoryMiner` | Analyzes git history to find duplicates and past solutions, preventing regression and duplicate work. | ollama-cloud/kimi-k2.6 |
| `@SystemAnalyst` | Designs technical specifications, data schemas, and API contracts before implementation. | ollama-cloud/glm-5.1 |
| `@SdetEngineer` | Writes tests following TDD methodology. | ollama-cloud/kimi-k2.6 |
| `@LeadDeveloper` | Primary code writer for backend and core logic. | ollama-cloud/kimi-k2.6 |
| `@FrontendDeveloper` | Handles UI implementation with multimodal capabilities. | ollama-cloud/kimi-k2.6 |
| `@BackendDeveloper` | Backend specialist for Node. | ollama-cloud/kimi-k2.6 |
| `@GoDeveloper` | Go backend specialist for Gin, Echo, APIs, and database integration. | ollama-cloud/deepseek-v4-pro-max |
| `@DevopsEngineer` | DevOps specialist for Docker, Kubernetes, CI/CD pipeline automation, and infrastructure management. | ollama-cloud/kimi-k2.6 |
| `@CodeSkeptic` | Adversarial code reviewer. | ollama-cloud/minimax-m2.5 |
| `@TheFixer` | Iteratively fixes bugs based on specific error reports and test failures. | ollama-cloud/minimax-m2.5 |
| `@PerformanceEngineer` | Reviews code for performance issues. | ollama-cloud/nemotron-3-super |
| `@SecurityAuditor` | Scans for security vulnerabilities, OWASP Top 10, dependency CVEs, and hardcoded secrets. | ollama-cloud/nemotron-3-super |
| `@VisualTester` | Visual regression testing agent that compares screenshots and detects UI differences using pixelmatch and image diff. | ollama-cloud/qwen3-coder:480b |
| `@CodeSkeptic` | Adversarial code reviewer. | ollama-cloud/kimi-k2.6 |
| `@TheFixer` | Iteratively fixes bugs based on specific error reports and test failures. | ollama-cloud/kimi-k2.6 |
| `@PerformanceEngineer` | Reviews code for performance issues. | ollama-cloud/deepseek-v4-pro-max |
| `@SecurityAuditor` | Scans for security vulnerabilities, OWASP Top 10, dependency CVEs, and hardcoded secrets. | ollama-cloud/deepseek-v4-pro-max |
| `@VisualTester` | Visual regression testing agent that compares screenshots and detects UI differences using pixelmatch and image diff. | ollama-cloud/kimi-k2.6 |
| `@Orchestrator` | Main dispatcher. | ollama-cloud/kimi-k2.6 |
| `@ReleaseManager` | Manages git operations, semantic versioning, branching, and deployments. | ollama-cloud/devstral-2:123b |
| `@Evaluator` | Scores agent effectiveness after task completion for continuous improvement. | ollama-cloud/nemotron-3-super |
| `@PromptOptimizer` | Improves agent system prompts based on performance failures. | ollama-cloud/glm-5.1 |
| `@ProductOwner` | Manages issue checklists, status labels, tracks progress and coordinates with human users. | ollama-cloud/glm-5.1 |
| `@ReleaseManager` | Manages git operations, semantic versioning, branching, and deployments. | ollama-cloud/glm-5.1 |
| `@Evaluator` | Scores agent effectiveness after task completion for continuous improvement. | ollama-cloud/kimi-k2.6 |
| `@PromptOptimizer` | Improves agent system prompts based on performance failures. | ollama-cloud/kimi-k2.6 |
| `@ProductOwner` | Manages issue checklists, status labels, tracks progress and coordinates with human users. | ollama-cloud/kimi-k2.6 |
| `@AgentArchitect` | Creates, modifies, and reviews new agents, workflows, and skills based on capability gap analysis. | ollama-cloud/kimi-k2.6 |
| `@CapabilityAnalyst` | Analyzes task requirements against available agents, workflows, and skills. | ollama-cloud/nemotron-3-super |
| `@WorkflowArchitect` | Creates and maintains workflow definitions with complete architecture, Gitea integration, and quality gates. | ollama-cloud/gpt-oss:120b |
| `@MarkdownValidator` | Validates and corrects Markdown descriptions for Gitea issues. | ollama-cloud/nemotron-3-nano:30b |
| `@CapabilityAnalyst` | Analyzes task requirements against available agents, workflows, and skills. | ollama-cloud/glm-5.1 |
| `@WorkflowArchitect` | Creates and maintains workflow definitions with complete architecture, Gitea integration, and quality gates. | ollama-cloud/kimi-k2.6 |
| `@MarkdownValidator` | Validates and corrects Markdown descriptions for Gitea issues. | ollama-cloud/kimi-k2.6 |
| `@BrowserAutomation` | Browser automation agent using Playwright MCP for E2E testing, form filling, navigation, and web interaction. | ollama-cloud/kimi-k2.6 |
| `@Planner` | Advanced task planner using Chain of Thought, Tree of Thoughts, and Plan-Execute-Reflect. | ollama-cloud/nemotron-3-super |
| `@Reflector` | Self-reflection agent using Reflexion pattern - learns from mistakes. | ollama-cloud/nemotron-3-super |
| `@MemoryManager` | Manages agent memory systems - short-term (context), long-term (vector store), and episodic (experiences). | ollama-cloud/nemotron-3-super |
| `@IncidentResponder` | Server incident response, live forensics, malware removal, hardening, and SSH-based cleanup. | ollama-cloud/kimi-k2.6 |
| `@WorkflowCrossChecker` | Pre-flight inter-agent conflict and architecture validation; asks uncomfortable questions before expensive work. | ollama-cloud/deepseek-v4-pro-max |
| `@Planner` | Advanced task planner using Chain of Thought, Tree of Thoughts, and Plan-Execute-Reflect. | ollama-cloud/deepseek-v4-pro-max |
| `@Reflector` | Self-reflection agent using Reflexion pattern - learns from mistakes. | ollama-cloud/deepseek-v4-pro-max |
| `@MemoryManager` | Manages agent memory systems - short-term (context), long-term (vector store), and episodic (experiences). | ollama-cloud/deepseek-v4-pro-max |
| `@ArchitectIndexer` | Indexes and maps project codebase architecture into . | ollama-cloud/kimi-k2.6 |
| `@FlutterDeveloper` | Flutter mobile specialist for cross-platform apps, state management, and UI components. | ollama-cloud/kimi-k2.6 |
| `@PhpDeveloper` | PHP specialist for Laravel, Symfony, WordPress, and modular architecture. | ollama-cloud/kimi-k2.6 |
| `@PipelineJudge` | Automated pipeline judge. | ollama-cloud/glm-5.1 |
| `@PythonDeveloper` | Python specialist for Django, FastAPI, data processing, and ML pipelines. | ollama-cloud/kimi-k2.6 |
| `@IncidentResponder` | Server incident response and system hardening specialist. | ollama-cloud/kimi-k2.6 |
| `@WorkflowCrossChecker` | Workflow cross-checker and process inspector. | ollama-cloud/kimi-k2.6 |

View File

@@ -52,6 +52,11 @@ These agents are invoked automatically by `/pipeline` or manually via `@mention`
| `@BackendDeveloper` | Backend specialist for Node | When backend needed |
| `@GoDeveloper` | Go backend specialist for Gin, Echo, APIs, and database integration | When Go backend needed |
| `@DevopsEngineer` | DevOps specialist for Docker, Kubernetes, CI/CD pipeline automation, and infrastructure management | When deployment/infra needed |
| `@ArchitectIndexer` | Indexes and maps project codebase architecture into | Manual invocation |
| `@FlutterDeveloper` | Flutter mobile specialist for cross-platform apps, state management, and UI components | Manual invocation |
| `@PhpDeveloper` | PHP specialist for Laravel, Symfony, WordPress, and modular architecture | Manual invocation |
| `@PythonDeveloper` | Python specialist for Django, FastAPI, data processing, and ML pipelines | Manual invocation |
| `@IncidentResponder` | Server incident response and system hardening specialist | Manual invocation |
### Quality Assurance
| Agent | Role | When Invoked |
@@ -86,8 +91,9 @@ These agents are invoked automatically by `/pipeline` or manually via `@mention`
| `@AgentArchitect` | Creates, modifies, and reviews new agents, workflows, and skills based on capability gap analysis | When gaps identified |
| `@CapabilityAnalyst` | Analyzes task requirements against available agents, workflows, and skills | When starting new task |
| `@WorkflowArchitect` | Creates and maintains workflow definitions with complete architecture, Gitea integration, and quality gates | New workflow needed |
| `@WorkflowCrossChecker` | Pre-flight inter-agent conflict and architecture validation; asks uncomfortable questions before expensive work | Before parallel spawn or state transitions |
| `@MarkdownValidator` | Validates and corrects Markdown descriptions for Gitea issues | Before issue creation |
| `@PipelineJudge` | Automated pipeline judge | Manual invocation |
| `@WorkflowCrossChecker` | Workflow cross-checker and process inspector | Manual invocation |
### Security & Incident Response
| Agent | Role | When Invoked |

View File

@@ -983,6 +983,7 @@
<button class="hm-tab-btn" onclick="switchHmTab('gitea')">Gitea History</button>
<button class="hm-tab-btn" onclick="switchHmTab('skills')">Skills</button>
<button class="hm-tab-btn" onclick="switchHmTab('models')">Model Timeline</button>
<button class="hm-tab-btn" onclick="switchHmTab('graph')">Performance Graph</button>
</div>
<div class="modal-body" id="hmModalBody">
<!-- Content injected by JS -->
@@ -1162,33 +1163,15 @@ function renderOverview() {
`).join('')
: '<p style="color: var(--text-muted);">No history yet</p>';
// Recommended agents (use inline recs if available)
let recAgents = [];
if (INLINE_RECOMMENDATIONS && INLINE_RECOMMENDATIONS.length > 0) {
recAgents = INLINE_RECOMMENDATIONS.slice(0, 6).map(r => ({ agent: r.agent, current: { recommendations: [{ priority: r.impact, target: r.source_of_truth_model || r.recommended_model, reason: r.rationale, score_before: r.score_before, score_after: r.score_after, score_delta: r.score_delta }], model: r.current_model_in_agent_versions || r.current_model, category: 'Core Dev', description: '', benchmark: { fit_score: r.score_after || 0 } } }));
} else {
recAgents = Object.entries(agentData.agents)
.filter(([_, a]) => a.current.recommendations && a.current.recommendations.length > 0)
.slice(0, 6);
}
// Recommended agents from live data
const recAgents = Object.entries(agentData.agents || {})
.filter(([_, a]) => (a.current?.recommendations || []).length > 0)
.slice(0, 6);
document.getElementById('recCount').textContent = recAgents.length;
if (INLINE_RECOMMENDATIONS && INLINE_RECOMMENDATIONS.length > 0) {
document.getElementById('recAgents').innerHTML = recAgents.map((r, idx) => renderRecCard({
agent: r.agent,
current_model: r.current?.model || '',
recommended_model: r.current?.recommendations?.[0]?.target || '',
impact: r.current?.recommendations?.[0]?.priority?.toLowerCase() || 'medium',
score_before: r.current?.recommendations?.[0]?.score_before || 0,
score_after: r.current?.recommendations?.[0]?.score_after || 0,
score_delta: r.current?.recommendations?.[0]?.score_delta || 0,
rationale: r.current?.recommendations?.[0]?.reason || ''
}, idx)).join('');
} else {
document.getElementById('recAgents').innerHTML = recAgents.map(([name, agent]) =>
renderAgentCard(name, agent, true)
).join('');
}
document.getElementById('recAgents').innerHTML = recAgents.map(([name, agent]) =>
renderAgentCard(name, agent, true)
).join('');
}
// Render All Agents
@@ -1327,24 +1310,19 @@ function renderTimeline() {
// Render Recommendations (v3 style with swap visuals)
function renderRecommendations() {
// Use inline recommendations or fall back to agent data
let recs = [];
if (INLINE_RECOMMENDATIONS && INLINE_RECOMMENDATIONS.length > 0) {
recs = INLINE_RECOMMENDATIONS;
} else {
recs = Object.entries(agentData.agents)
.filter(([_, a]) => a.current.recommendations && a.current.recommendations.length > 0)
.map(([name, agent]) => ({
agent: name,
current_model: agent.current.model,
recommended_model: agent.current.recommendations[0]?.target,
impact: agent.current.recommendations[0]?.priority?.toLowerCase() || 'medium',
score_before: agent.current.recommendations[0]?.score_before || 0,
score_after: agent.current.recommendations[0]?.score_after || 0,
score_delta: agent.current.recommendations[0]?.score_delta || 0,
rationale: agent.current.recommendations[0]?.reason || ''
}));
}
const recs = Object.entries(agentData.agents || {})
.filter(([_, a]) => (a.current?.recommendations || []).length > 0)
.map(([name, agent]) => ({
agent: name,
current_model: agent.current?.model,
recommended_model: agent.current?.recommendations?.[0]?.target,
impact: agent.current?.recommendations?.[0]?.priority?.toLowerCase() || 'medium',
score_before: agent.current?.recommendations?.[0]?.score_before || 0,
score_after: agent.current?.recommendations?.[0]?.score_after || 0,
score_delta: agent.current?.recommendations?.[0]?.score_delta || 0,
rationale: agent.current?.recommendations?.[0]?.reason || '',
current_model_in_agent_versions: agent.current?.model
}));
if (recs.length === 0) {
document.getElementById('allRecommendations').innerHTML = '<p style="color:var(--text-muted);text-align:center;padding:40px;">No recommendations available</p>';
@@ -1493,7 +1471,7 @@ function renderHeatmap() {
h += `<td style="background:${hmColor(s)};color:${hmText(s)};cursor:pointer" class="${cur ? 'hm-cur' : ''}" title="${ag.n} × ${hmModels[j].n}: ${s}"
onmouseover="showTT(event,'${ag.n}','${hmModels[j].n} (${hmModels[j].p})',${s},${best},${cur},${hmModels[j].if})"
onmouseout="hideTT()"
onclick="showCellDetail('${hmModels[j].full}', '${ag.n}')">${s}${marks}</td>`;
onclick="openHmModal(event, '${ag.n}', '${hmModels[j].n}', ${s}, ${hmModels[j].if})">${s}${marks}</td>`;
});
h += '</tr>';
});
@@ -1556,7 +1534,8 @@ function closeHmModal() {
}
// Show cell detail modal with Chart.js line chart and prompt history
function showCellDetail(modelName, agentName) {
function showCellDetail(event, modelName, agentName) {
event.stopPropagation();
const agent = agentData.agents[agentName];
if (!agent) {
console.error('Agent not found:', agentName);
@@ -1770,9 +1749,13 @@ function renderHmModalContent(tabName) {
case 'models':
content = renderModelsTab(agent);
break;
case 'graph':
content = renderGraphTab(agent);
break;
}
body.innerHTML = `<div class="hm-tab-content active" style="display:block">${content}</div>`;
if (tabName === 'graph') setTimeout(() => renderCellChart(hmCurrentAgent, agent.current?.model || ''), 50);
}
function renderPromptTab(agent) {
@@ -1911,6 +1894,46 @@ function renderModelsTab(agent) {
return html;
}
function renderGraphTab(agent) {
return `
<div style="margin-bottom:20px">
<h3 style="margin-bottom:10px;color:var(--accent-cyan)">Performance Over Time</h3>
<div style="position:relative;height:300px">
<canvas id="cellChartCanvas"></canvas>
</div>
</div>
<div>
<h3 style="margin-bottom:10px;color:var(--accent-cyan)">Prompt Change History</h3>
<div id="promptHistoryList" style="max-height:300px;overflow-y:auto">
${renderPromptHistory(agent)}
</div>
</div>
`;
}
function renderPromptHistory(agent) {
const promptChanges = (agent.history || []).filter(item => item.change_type === 'prompt_change');
if (promptChanges.length === 0) {
return '<p style="color:var(--text-muted);text-align:center;padding:20px">No prompt change history found</p>';
}
let html = '<ul style="list-style:none;padding:0">';
promptChanges.forEach(change => {
html += `
<li style="padding:10px;border-bottom:1px solid var(--border);margin-bottom:10px">
<div style="display:flex;justify-content:space-between;margin-bottom:5px">
<span style="font-family:'JetBrains Mono',monospace;font-size:.8em;color:var(--text-muted)">${formatDate(change.date)}</span>
<span style="font-family:'JetBrains Mono',monospace;font-size:.8em;color:var(--accent-cyan)">${change.commit ? change.commit.substring(0,7) : 'unknown'}</span>
</div>
<div style="font-size:.9em;color:var(--text-secondary)">${change.reason || 'No reason provided'}</div>
</li>
`;
});
html += '</ul>';
return html;
}
// Compute composite score for any model name
// Formula (v2): IF_score * 0.85 + context_window_bonus (SWE-bench removed — all values unverifiable)
function computeAgentScore(modelName) {
@@ -2344,7 +2367,7 @@ function simulateApply() {
progressStatus.textContent = 'Complete!';
progressResult.classList.add('show');
const recs = INLINE_RECOMMENDATIONS && INLINE_RECOMMENDATIONS.length > 0 ? INLINE_RECOMMENDATIONS : [];
const recs = Object.values(agentData.agents || {}).filter(a => (a.current?.recommendations || []).length > 0);
progressResultText.textContent = `${recs.length} recommendations applied. Run 'bun run sync:evolution' to update dashboard.`;
}
}
@@ -2395,11 +2418,11 @@ function showResearchModal() {
step.classList.add('done');
});
const recs = INLINE_RECOMMENDATIONS && INLINE_RECOMMENDATIONS.length > 0 ? INLINE_RECOMMENDATIONS : [];
const modelsCount = new Set(recs.map(r => r.current_model).concat(recs.map(r => r.source_of_truth_model || r.recommended_model))).size;
const recsCount = recs.filter(r => r.score_delta > 0).length;
document.getElementById('researchSummaryText').textContent =
const recs = Object.values(agentData.agents || {}).filter(a => (a.current?.recommendations || []).length > 0);
const modelsCount = new Set(recs.flatMap(a => [a.current?.model, a.current?.recommendations?.[0]?.target])).size;
const recsCount = recs.filter(a => (a.current?.recommendations?.[0]?.score_delta || 0) > 0).length;
document.getElementById('researchSummaryText').textContent =
`${modelsCount} models evaluated. ${recsCount} recommendations found. ${recs.length - recsCount} idle models detected.`;
researchSummary.classList.add('show');
}

View File

@@ -983,6 +983,7 @@
<button class="hm-tab-btn" onclick="switchHmTab('gitea')">Gitea History</button>
<button class="hm-tab-btn" onclick="switchHmTab('skills')">Skills</button>
<button class="hm-tab-btn" onclick="switchHmTab('models')">Model Timeline</button>
<button class="hm-tab-btn" onclick="switchHmTab('graph')">Performance Graph</button>
</div>
<div class="modal-body" id="hmModalBody">
<!-- Content injected by JS -->
@@ -1016,7 +1017,7 @@ Chart.defaults.borderColor = '#1e2d45';
Chart.defaults.font.family = "'Inter', sans-serif";
// Inline recommendation data fallback (from model-research-latest.json)
const INLINE_RECOMMENDATIONS = []; // REMOVED — data now comes from agentData, not hardcoded
const INLINE_RECOMMENDATIONS = []; // Deprecated — data now comes from agentData.agents[].current.recommendations
// Inline benchmark data (fallback when embedded data doesn't have model_benchmarks)
// SOURCE: agent-evolution/data/model-benchmarks-verified.json v2.0.0
@@ -5736,33 +5737,15 @@ function renderOverview() {
`).join('')
: '<p style="color: var(--text-muted);">No history yet</p>';
// Recommended agents (use inline recs if available)
let recAgents = [];
if (INLINE_RECOMMENDATIONS && INLINE_RECOMMENDATIONS.length > 0) {
recAgents = INLINE_RECOMMENDATIONS.slice(0, 6).map(r => ({ agent: r.agent, current: { recommendations: [{ priority: r.impact, target: r.source_of_truth_model || r.recommended_model, reason: r.rationale, score_before: r.score_before, score_after: r.score_after, score_delta: r.score_delta }], model: r.current_model_in_agent_versions || r.current_model, category: 'Core Dev', description: '', benchmark: { fit_score: r.score_after || 0 } } }));
} else {
recAgents = Object.entries(agentData.agents)
.filter(([_, a]) => a.current.recommendations && a.current.recommendations.length > 0)
.slice(0, 6);
}
// Recommended agents from live data
const recAgents = Object.entries(agentData.agents || {})
.filter(([_, a]) => (a.current?.recommendations || []).length > 0)
.slice(0, 6);
document.getElementById('recCount').textContent = recAgents.length;
if (INLINE_RECOMMENDATIONS && INLINE_RECOMMENDATIONS.length > 0) {
document.getElementById('recAgents').innerHTML = recAgents.map((r, idx) => renderRecCard({
agent: r.agent,
current_model: r.current?.model || '',
recommended_model: r.current?.recommendations?.[0]?.target || '',
impact: r.current?.recommendations?.[0]?.priority?.toLowerCase() || 'medium',
score_before: r.current?.recommendations?.[0]?.score_before || 0,
score_after: r.current?.recommendations?.[0]?.score_after || 0,
score_delta: r.current?.recommendations?.[0]?.score_delta || 0,
rationale: r.current?.recommendations?.[0]?.reason || ''
}, idx)).join('');
} else {
document.getElementById('recAgents').innerHTML = recAgents.map(([name, agent]) =>
renderAgentCard(name, agent, true)
).join('');
}
document.getElementById('recAgents').innerHTML = recAgents.map(([name, agent]) =>
renderAgentCard(name, agent, true)
).join('');
}
// Render All Agents
@@ -6068,7 +6051,7 @@ function renderHeatmap() {
h += `<td style="background:${hmColor(s)};color:${hmText(s)};cursor:pointer" class="${cur ? 'hm-cur' : ''}" title="${ag.n} × ${hmModels[j].n}: ${s}"
onmouseover="showTT(event,'${ag.n}','${hmModels[j].n} (${hmModels[j].p})',${s},${best},${cur},${hmModels[j].if})"
onmouseout="hideTT()"
onclick="showCellDetail('${hmModels[j].full}', '${ag.n}')">${s}${marks}</td>`;
onclick="openHmModal(event, '${ag.n}', '${hmModels[j].n}', ${s}, ${hmModels[j].if})">${s}${marks}</td>`;
});
h += '</tr>';
});
@@ -6131,7 +6114,8 @@ function closeHmModal() {
}
// Show cell detail modal with Chart.js line chart and prompt history
function showCellDetail(modelName, agentName) {
function showCellDetail(event, modelName, agentName) {
event.stopPropagation();
const agent = agentData.agents[agentName];
if (!agent) {
console.error('Agent not found:', agentName);
@@ -6345,9 +6329,13 @@ function renderHmModalContent(tabName) {
case 'models':
content = renderModelsTab(agent);
break;
case 'graph':
content = renderGraphTab(agent);
break;
}
body.innerHTML = `<div class="hm-tab-content active" style="display:block">${content}</div>`;
if (tabName === 'graph') setTimeout(() => renderCellChart(hmCurrentAgent, agent.current?.model || ''), 50);
}
function renderPromptTab(agent) {
@@ -6486,6 +6474,46 @@ function renderModelsTab(agent) {
return html;
}
function renderGraphTab(agent) {
return `
<div style="margin-bottom:20px">
<h3 style="margin-bottom:10px;color:var(--accent-cyan)">Performance Over Time</h3>
<div style="position:relative;height:300px">
<canvas id="cellChartCanvas"></canvas>
</div>
</div>
<div>
<h3 style="margin-bottom:10px;color:var(--accent-cyan)">Prompt Change History</h3>
<div id="promptHistoryList" style="max-height:300px;overflow-y:auto">
${renderPromptHistory(agent)}
</div>
</div>
`;
}
function renderPromptHistory(agent) {
const promptChanges = (agent.history || []).filter(item => item.change_type === 'prompt_change');
if (promptChanges.length === 0) {
return '<p style="color:var(--text-muted);text-align:center;padding:20px">No prompt change history found</p>';
}
let html = '<ul style="list-style:none;padding:0">';
promptChanges.forEach(change => {
html += `
<li style="padding:10px;border-bottom:1px solid var(--border);margin-bottom:10px">
<div style="display:flex;justify-content:space-between;margin-bottom:5px">
<span style="font-family:'JetBrains Mono',monospace;font-size:.8em;color:var(--text-muted)">${formatDate(change.date)}</span>
<span style="font-family:'JetBrains Mono',monospace;font-size:.8em;color:var(--accent-cyan)">${change.commit ? change.commit.substring(0,7) : 'unknown'}</span>
</div>
<div style="font-size:.9em;color:var(--text-secondary)">${change.reason || 'No reason provided'}</div>
</li>
`;
});
html += '</ul>';
return html;
}
// Compute composite score for any model name
// Formula (v2): IF_score * 0.85 + context_window_bonus (SWE-bench removed — all values unverifiable)
function computeAgentScore(modelName) {
@@ -6919,7 +6947,7 @@ function simulateApply() {
progressStatus.textContent = 'Complete!';
progressResult.classList.add('show');
const recs = INLINE_RECOMMENDATIONS && INLINE_RECOMMENDATIONS.length > 0 ? INLINE_RECOMMENDATIONS : [];
const recs = Object.values(agentData.agents || {}).filter(a => (a.current?.recommendations || []).length > 0);
progressResultText.textContent = `${recs.length} recommendations applied. Run 'bun run sync:evolution' to update dashboard.`;
}
}
@@ -6970,11 +6998,11 @@ function showResearchModal() {
step.classList.add('done');
});
const recs = INLINE_RECOMMENDATIONS && INLINE_RECOMMENDATIONS.length > 0 ? INLINE_RECOMMENDATIONS : [];
const modelsCount = new Set(recs.map(r => r.current_model).concat(recs.map(r => r.source_of_truth_model || r.recommended_model))).size;
const recsCount = recs.filter(r => r.score_delta > 0).length;
document.getElementById('researchSummaryText').textContent =
const recs = Object.values(agentData.agents || {}).filter(a => (a.current?.recommendations || []).length > 0);
const modelsCount = new Set(recs.flatMap(a => [a.current?.model, a.current?.recommendations?.[0]?.target])).size;
const recsCount = recs.filter(a => (a.current?.recommendations?.[0]?.score_delta || 0) > 0).length;
document.getElementById('researchSummaryText').textContent =
`${modelsCount} models evaluated. ${recsCount} recommendations found. ${recs.length - recsCount} idle models detected.`;
researchSummary.classList.add('show');
}

File diff suppressed because it is too large Load Diff

View File

@@ -259,12 +259,12 @@
</div>
<div class="tabs" id="tabBar">
<button class="tab-btn active" onclick="switchTab('overview')">Обзор</button>
<button class="tab-btn" onclick="switchTab('groq')">Groq Free Tier</button>
<button class="tab-btn" onclick="switchTab('models')">Все модели</button>
<button class="tab-btn" onclick="switchTab('heatmap')">Матрица</button>
<button class="tab-btn" onclick="switchTab('recs')">Рекомендации</button>
<button class="tab-btn" onclick="switchTab('impact')">Анализ профита</button>
<button class="tab-btn active" onclick="switchTab('overview', this)">Обзор</button>
<button class="tab-btn" onclick="switchTab('groq', this)">Groq Free Tier</button>
<button class="tab-btn" onclick="switchTab('models', this)">Все модели</button>
<button class="tab-btn" onclick="switchTab('heatmap', this)">Матрица</button>
<button class="tab-btn" onclick="switchTab('recs', this)">Рекомендации</button>
<button class="tab-btn" onclick="switchTab('impact', this)">Анализ профита</button>
</div>
<!-- ========== TAB: OVERVIEW ========== -->
@@ -477,11 +477,11 @@ const impactData = (EMBEDDED_DATA.impact_data || []).map(d => ({
}));
// ======================= RENDER =======================
function switchTab(id) {
function switchTab(id, el) {
document.querySelectorAll('.tab-panel').forEach(p=>p.classList.remove('active'));
document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));
document.getElementById('tab-'+id).classList.add('active');
event.target.classList.add('active');
(el || document.querySelector(`button[onclick*="switchTab('${id}')"]`)).classList.add('active');
if(id==='impact') requestAnimationFrame(()=>setTimeout(drawChart,50));
}