- load(): normalize ollama-cloud/* names to short form, deduplicate with Set - Prevents double entries when cache adds short names alongside API full names
474 lines
23 KiB
HTML
474 lines
23 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Real-Fit Matrix — Agent × Model Performance</title>
|
||
<style>
|
||
:root{--bg:#0a0f1a;--bg2:#0f1525;--bg3:#141c2e;--bdr:#1e2d45;--txt:#e8f1ff;--txt2:#8ba3c0;--cyan:#00d4ff;--green:#00ff94;--red:#ff4757;--orange:#ff9f43;--purple:#a855f7;}
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
body{font-family:system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--txt);min-height:100vh;padding:24px}
|
||
h1{font-size:1.6rem;background:linear-gradient(90deg,var(--cyan),var(--green));-webkit-background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:8px}
|
||
.sub{color:var(--txt2);font-size:.85rem;margin-bottom:20px}
|
||
table{width:100%;border-collapse:collapse;font-size:.82rem}
|
||
th,td{padding:8px 10px;border:1px solid var(--bdr);text-align:center}
|
||
th{background:var(--bg2);color:var(--txt2);font-size:.72rem;text-transform:uppercase;letter-spacing:.5px;position:sticky;top:0}
|
||
td:first-child{text-align:left;font-weight:700;white-space:nowrap}
|
||
td.score{font-weight:700;font-family:monospace}
|
||
.hm-cur{box-shadow:inset 0 0 0 2px var(--cyan)}
|
||
.high{background:rgba(0,255,148,.18);color:var(--green)}
|
||
.good{background:rgba(0,212,255,.14);color:var(--cyan)}
|
||
.med{background:rgba(168,85,247,.15);color:var(--purple)}
|
||
.low{background:rgba(255,71,87,.1);color:var(--red)}
|
||
.na{background:transparent;color:var(--txt2);font-size:.9rem;cursor:pointer}
|
||
.na:hover{background:rgba(0,212,255,.08)}
|
||
.legend{display:flex;gap:12px;flex-wrap:wrap;margin-top:16px;font-size:.78rem;color:var(--txt2)}
|
||
.legend span{display:flex;align-items:center;gap:4px}
|
||
.dot{width:14px;height:14px;border-radius:3px}
|
||
.meta{font-size:.72rem;color:var(--txt2);margin-top:12px}
|
||
a{color:var(--cyan);text-decoration:none}
|
||
.btn-research{font-size:.9rem;background:none;border:none;cursor:pointer;margin-left:4px;opacity:.7}
|
||
.btn-research:hover{opacity:1}
|
||
.modal{position:fixed;inset:0;background:rgba(0,0,0,.7);display:flex;align-items:center;justify-content:center;z-index:1000;padding:16px}
|
||
.modal.hidden{display:none}
|
||
.modal-panel{background:var(--bg2);border:1px solid var(--cyan);border-radius:12px;max-width:90vw;width:480px;max-height:90vh;overflow-y:auto;padding:20px;position:relative}
|
||
.modal-panel.wide{width:640px}
|
||
.modal-title{font-size:1.1rem;margin-bottom:12px}
|
||
.modal-list{text-align:left;margin:12px 0;max-height:40vh;overflow-y:auto}
|
||
.modal-list label{display:flex;align-items:center;gap:8px;padding:6px 0;cursor:pointer;border-bottom:1px solid var(--bdr)}
|
||
.modal-list input{margin:0}
|
||
.modal-actions{display:flex;gap:8px;justify-content:flex-end;margin-top:16px}
|
||
.btn{padding:6px 14px;border-radius:6px;border:1px solid var(--bdr);background:var(--bg3);color:var(--txt);cursor:pointer;font-size:.85rem}
|
||
.btn.primary{background:linear-gradient(90deg,var(--green),var(--cyan));color:#000;border:none;font-weight:700}
|
||
.progress{margin-top:12px}
|
||
.progress-bar{height:8px;background:var(--bg3);border-radius:4px;overflow:hidden}
|
||
.progress-fill{height:100%;width:0%;background:linear-gradient(90deg,var(--green),var(--cyan));transition:width .3s}
|
||
.progress-text{font-size:.8rem;color:var(--txt2);margin-top:6px;text-align:center}
|
||
.result-table{width:100%;margin-top:12px;font-size:.82rem;border-collapse:collapse}
|
||
.result-table th,.result-table td{padding:6px;border:1px solid var(--bdr)}
|
||
.result-table .best{background:rgba(0,255,148,.25);color:var(--green);font-weight:700}
|
||
.result-table tbody tr{cursor:pointer}
|
||
.result-table tbody tr:hover{background:rgba(0,212,255,.06)}
|
||
.detail-row{margin-bottom:12px}
|
||
.detail-label{font-size:.72rem;color:var(--txt2);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px}
|
||
.detail-val{font-size:.85rem;white-space:pre-wrap;word-break:break-word}
|
||
.detail-pills{display:flex;flex-wrap:wrap;gap:6px;margin-top:4px}
|
||
.pill{font-size:.72rem;padding:2px 8px;border-radius:4px;background:var(--bg3);border:1px solid var(--bdr);color:var(--txt2)}
|
||
.score-big{font-size:2rem;font-weight:700;margin:4px 0}
|
||
.toggle{color:var(--cyan);cursor:pointer;font-size:.78rem}
|
||
.toggle:hover{text-decoration:underline}
|
||
.dim-bar{display:flex;align-items:center;gap:8px;margin:4px 0}
|
||
.dim-bar>span:first-child{width:120px;font-size:.75rem;color:var(--txt2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||
.dim-track{flex:1;height:8px;background:var(--bg3);border-radius:4px;overflow:hidden}
|
||
.dim-fill{height:100%;border-radius:4px}
|
||
.dim-num{width:30px;text-align:right;font-size:.78rem;font-weight:700}
|
||
.v-pass{color:var(--green)}
|
||
.v-marginal{color:var(--orange)}
|
||
.v-fail{color:var(--red)}
|
||
.commentary{font-size:.85rem;padding:10px 12px;background:rgba(0,212,255,.08);border-left:3px solid var(--cyan);border-radius:0 6px 6px 0;color:var(--txt);white-space:pre-wrap;word-break:break-word}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1>Real-Fit Matrix</h1>
|
||
<div class="sub">Real agent × model evaluation scores via live Ollama API (28 calls, 4 models, 7 agents)</div>
|
||
<div id="matrix"></div>
|
||
<div class="legend">
|
||
<span><span class="dot high"></span> 90+ Excellent</span>
|
||
<span><span class="dot good"></span> 75–89 Good</span>
|
||
<span><span class="dot med"></span> 50–74 Average</span>
|
||
<span><span class="dot low"></span> <50 Weak</span>
|
||
<span style="margin-left:auto">● = assigned model</span>
|
||
</div>
|
||
<div class="meta">Data source: <a href="data/real-fit-report.json" target="_blank">real-fit-report.json</a> | Updated: <span id="updated"></span></div>
|
||
|
||
<div id="researchAgentModal" class="modal hidden">
|
||
<div class="modal-panel">
|
||
<div class="modal-title" id="agentModalTitle">Research models</div>
|
||
<div class="modal-list" id="agentModalList"></div>
|
||
<div class="progress hidden" id="agentProgress">
|
||
<div class="progress-bar"><div class="progress-fill" id="agentProgressFill"></div></div>
|
||
<div class="progress-text" id="agentProgressText"></div>
|
||
</div>
|
||
<div class="modal-actions">
|
||
<button class="btn" onclick="closeModal('researchAgentModal')">Close</button>
|
||
<button class="btn" id="evolveAgentBtn" onclick="startEvolveAgent()">Run Role-Fit Test</button>
|
||
<button class="btn primary" id="agentStartBtn" onclick="startAgentResearch()">Start Research</button>
|
||
</div>
|
||
<div id="agentResults"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="researchCellModal" class="modal hidden">
|
||
<div class="modal-panel">
|
||
<div class="modal-title" id="cellModalTitle">Evaluate cell</div>
|
||
<div class="modal-list" id="cellModalList"></div>
|
||
<div class="progress hidden" id="cellProgress">
|
||
<div class="progress-bar"><div class="progress-fill" id="cellProgressFill"></div></div>
|
||
<div class="progress-text" id="cellProgressText"></div>
|
||
</div>
|
||
<div class="modal-actions">
|
||
<button class="btn" onclick="closeModal('researchCellModal')">Close</button>
|
||
<button class="btn primary" id="cellStartBtn" onclick="startCellResearch()">Evaluate</button>
|
||
</div>
|
||
<div id="cellResults"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="detailModal" class="modal hidden">
|
||
<div class="modal-panel wide">
|
||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
|
||
<span style="font-size:1rem;font-weight:700" id="detailTitle"></span>
|
||
<button class="btn" onclick="closeModal('detailModal')">Close</button>
|
||
</div>
|
||
<div id="detailContent"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
let reportData, evoData, allModels=[], allAvailableModels=[];
|
||
const API_BASE='http://localhost:3004';
|
||
const $=id=>document.getElementById(id);
|
||
|
||
const MODEL_BENCHMARKS={
|
||
"qwen3.5-122b":91,"qwen3-coder-480b":88,"deepseek-v4-pro":89,"deepseek-v4-pro-max":89,
|
||
"deepseek-v4-flash":86,"kimi-k2.6":91,"kimi-k2.5":90,
|
||
"minimax-m2.5":82,"minimax-m2.7":80,"glm-5.1":90,
|
||
"glm-5":90,"nemotron-3-super":78,"nemotron-3-nano":68,
|
||
"gemma4-27b":85,"devstral-2":80,"devstral-small-2":75
|
||
};
|
||
function clsFor(s){if(s>=90)return'score high';if(s>=75)return'score good';if(s>=50)return'score med';return'score low';}
|
||
function scoreColor(s){if(s>=90)return'var(--green)';if(s>=75)return'var(--cyan)';if(s>=50)return'var(--purple)';return'var(--red)';}
|
||
function closeModal(id){$(id).classList.add('hidden');}
|
||
|
||
async function load(){
|
||
let mRes;
|
||
try{ mRes=await fetch(`${API_BASE}/api/models`); allAvailableModels=[...new Set((await mRes.json()).models.map(modelShort))].sort(); }
|
||
catch(e){ allAvailableModels=Object.keys(MODEL_BENCHMARKS); }
|
||
try{
|
||
const rRes=await fetch(`${API_BASE}/api/real-fit-report`);
|
||
reportData=await rRes.json();
|
||
}catch(e){
|
||
const rRes=await fetch('data/real-fit-report.json');
|
||
reportData=await rRes.json();
|
||
}
|
||
const eRes=await fetch('data/evolution.json');
|
||
evoData=await eRes.json();
|
||
reportData.generated = reportData.generated || new Date().toISOString();
|
||
$('updated').textContent=new Date(reportData.generated).toLocaleString('ru-RU');
|
||
|
||
const agents=Object.values(reportData.agents).filter(a=>Object.values(a.evaluations).some(s=>s>0));
|
||
const modelScores={};
|
||
agents.forEach(a=>{for(const[m,s] of Object.entries(a.evaluations)){modelScores[m]=(modelScores[m]||0)+s;}});
|
||
allModels=Object.keys(modelScores).filter(m=>modelScores[m]>0).sort();
|
||
|
||
mergeCachedResults();
|
||
renderTable();
|
||
}
|
||
|
||
function currentModel(agentName){
|
||
const info=reportData.agents[agentName]?.info||[];
|
||
return (info[2]||'').split('/').pop();
|
||
}
|
||
|
||
function modelShort(full){return full.replace('ollama-cloud/','');}
|
||
|
||
function openAgentModal(agent){
|
||
$('agentModalTitle').textContent='Research models for '+agent;
|
||
const cur=currentModel(agent);
|
||
let html='';
|
||
allAvailableModels.forEach(full=>{
|
||
const m=modelShort(full);
|
||
const checked=m===cur||cur.replace(':','-')===m||m.replace('-',':')===cur?'checked':'';
|
||
const ifs=MODEL_BENCHMARKS[m]||'—';
|
||
html+=`<label><input type="checkbox" value="${m}" ${checked}> <span>${m}</span> <span style="color:var(--txt2);margin-left:auto">IF ${ifs}</span></label>`;
|
||
});
|
||
$('agentModalList').innerHTML=html||'<p style="color:var(--txt2)">No model data</p>';
|
||
$('agentResults').innerHTML='';
|
||
$('agentProgress').classList.add('hidden');
|
||
$('agentStartBtn').disabled=false;
|
||
$('evolveAgentBtn').disabled=false;
|
||
$('researchAgentModal').classList.remove('hidden');
|
||
window.__activeAgent=agent;
|
||
}
|
||
|
||
function openCellModal(agent,model){
|
||
$('cellModalTitle').textContent='Evaluate '+agent+' × '+model;
|
||
$('cellModalList').innerHTML=`<label><input type="checkbox" value="${model}" checked> <span>${model}</span></label>`;
|
||
$('cellResults').innerHTML='';
|
||
$('cellProgress').classList.add('hidden');
|
||
$('cellStartBtn').disabled=false;
|
||
$('researchCellModal').classList.remove('hidden');
|
||
window.__activeAgent=agent; window.__activeModel=model;
|
||
}
|
||
|
||
async function openDetail(agent,model){
|
||
$('detailTitle').textContent=agent+' × '+model;
|
||
$('detailContent').innerHTML='<p style="color:var(--txt2)">Loading...</p>';
|
||
$('detailModal').classList.remove('hidden');
|
||
let data;
|
||
try{
|
||
const res=await fetch(`${API_BASE}/api/evaluation/${encodeURIComponent(agent)}/${encodeURIComponent(model)}`);
|
||
if(!res.ok) throw new Error(res.status);
|
||
data=await res.json();
|
||
}catch(e){
|
||
$('detailContent').innerHTML='<p style="color:var(--red);margin-top:12px">No detailed evaluation data available for this combination. Run research first.</p>';
|
||
return;
|
||
}
|
||
const s=data.total_score??data.score??0;
|
||
|
||
const verdict=(data.verdict||'').toUpperCase();
|
||
let vClass='';
|
||
if(verdict==='PASS') vClass='v-pass';
|
||
else if(verdict==='MARGINAL') vClass='v-marginal';
|
||
else if(verdict==='FAIL') vClass='v-fail';
|
||
const verdictHtml=verdict?`<span class="${vClass}" style="font-size:.85rem;font-weight:700;border:1px solid currentColor;padding:2px 8px;border-radius:4px;margin-left:8px">${verdict}</span>`:'';
|
||
|
||
let scoresHtml='';
|
||
if(data.scores){
|
||
scoresHtml='<div class="detail-row"><div class="detail-label">Score Breakdown</div>';
|
||
for(const [k,v] of Object.entries(data.scores)){
|
||
const num=typeof v==='number'?v:Number(v)||0;
|
||
scoresHtml+=`<div class="dim-bar"><span>${k}</span><div class="dim-track"><div class="dim-fill" style="width:${num}%;background:${scoreColor(num)}"></div></div><span class="dim-num">${Math.round(num)}</span></div>`;
|
||
}
|
||
scoresHtml+='</div>';
|
||
}
|
||
|
||
let commentaryHtml='';
|
||
if(data.explanation){
|
||
commentaryHtml=`<div class="detail-row"><div class="detail-label">Evaluator Commentary</div><div class="commentary">${data.explanation}</div></div>`;
|
||
}
|
||
|
||
let rubricHtml='';
|
||
if(data.rubric){
|
||
rubricHtml='<div class="detail-row"><div class="detail-label">Rubric Weights</div><div class="detail-pills">';
|
||
for(const [k,v] of Object.entries(data.rubric)){
|
||
rubricHtml+=`<span class="pill">${k}: ${v}</span>`;
|
||
}
|
||
rubricHtml+='</div></div>';
|
||
}
|
||
|
||
let kwHtml='';
|
||
if(data.expected_keywords?.length){ kwHtml='<div class="detail-pills">'+data.expected_keywords.map(k=>`<span class="pill">${k}</span>`).join('')+'</div>'; }
|
||
const resp=(data.response||'').toString();
|
||
const trunc=resp.length>500?resp.slice(0,500)+'...':resp;
|
||
const more=resp.length>500;
|
||
const rid='r'+Math.random().toString(36).slice(2);
|
||
window.__respCache=window.__respCache||{};
|
||
window.__respCache[rid]={full:resp,trunc:trunc};
|
||
let respHtml=`<div class="detail-val" id="${rid}">${trunc}</div>`;
|
||
if(more) respHtml+=`<span class="toggle" onclick="const c=window.__respCache['${rid}'];const el=$('${rid}');const isFull=el.dataset.f==='1';el.textContent=isFull?c.trunc:c.full;el.dataset.f=isFull?'0':'1';this.textContent=isFull?'Show more':'Show less'">Show more</span>`;
|
||
const lat=data.latency_ms;
|
||
const latTxt=typeof lat==='number'?(lat>=1000?(lat/1000).toFixed(1)+'s':lat+'ms'):'—';
|
||
|
||
$('detailContent').innerHTML=`
|
||
<div class="detail-row"><div class="detail-label">Agent × Model</div><div class="detail-val">${agent} × ${model}${verdictHtml}</div></div>
|
||
<div class="detail-row"><div class="detail-label">Total Score</div><div class="score-big" style="color:${scoreColor(s)}">${Math.round(s)}</div></div>
|
||
${scoresHtml}
|
||
<div class="detail-row"><div class="detail-label">Task</div><div class="detail-val">${data.user_prompt||'—'}</div></div>
|
||
<div class="detail-row"><div class="detail-label">System Role</div><div class="detail-val">${data.system_prompt||'—'}</div></div>
|
||
<div class="detail-row"><div class="detail-label">Model Response</div>${respHtml}</div>
|
||
${commentaryHtml}
|
||
${rubricHtml}
|
||
<div class="detail-row"><div class="detail-label">Evaluator</div><div class="detail-val">${data.evaluator||'—'}</div></div>
|
||
<div class="detail-row"><div class="detail-label">Latency</div><div class="detail-val">${latTxt}</div></div>
|
||
<div class="detail-row"><div class="detail-label">Tokens</div><div class="detail-val">Prompt: ${data.tokens_prompt??0} / Response: ${data.tokens_response??0}</div></div>
|
||
<div class="detail-row"><div class="detail-label">Expected Keywords</div>${kwHtml||'<div class="detail-val">—</div>'}</div>
|
||
<div class="detail-row"><div class="detail-label">Evaluated At</div><div class="detail-val">${data.evaluated_at?new Date(data.evaluated_at).toLocaleString('ru-RU'):'—'}</div></div>
|
||
`;
|
||
}
|
||
|
||
async function animateProgress(pid,label,ms){
|
||
const bar=$(pid+'Fill'),txt=$(pid+'Text'),wrap=$(pid);
|
||
wrap.classList.remove('hidden'); txt.textContent=label; bar.style.width='0%';
|
||
await new Promise(r=>setTimeout(r,50)); bar.style.transition=`width ${ms}ms linear`;
|
||
await new Promise(r=>setTimeout(r,50)); bar.style.width='100%';
|
||
await new Promise(r=>setTimeout(r,ms));
|
||
bar.style.transition='width .3s';
|
||
}
|
||
|
||
function setProgress(pid,percent,label){
|
||
const bar=$(pid+'Fill'),txt=$(pid+'Text'),wrap=$(pid);
|
||
wrap.classList.remove('hidden'); txt.textContent=label; bar.style.width=percent+'%';
|
||
}
|
||
|
||
function mergeCachedResults(){
|
||
try{
|
||
const store=JSON.parse(localStorage.getItem('__researchResults')||'{}');
|
||
for(const[agent,rec] of Object.entries(store)){
|
||
if(!reportData.agents[agent]){
|
||
reportData.agents[agent]={name:agent,evaluations:{},info:[],best_model:'',best_score:0};
|
||
}
|
||
for(const r of (rec.models||[])){
|
||
const shortM=modelShort(r.model);
|
||
// Only fill gaps — never override existing DB scores
|
||
const existing=reportData.agents[agent].evaluations[shortM];
|
||
if(existing===undefined||existing===0){
|
||
reportData.agents[agent].evaluations[shortM]=r.score;
|
||
}
|
||
if(r.score>reportData.agents[agent].best_score){
|
||
reportData.agents[agent].best_model=shortM;
|
||
reportData.agents[agent].best_score=r.score;
|
||
}
|
||
if(!allModels.includes(shortM)) allModels.push(shortM);
|
||
if(!allAvailableModels.includes(shortM)) allAvailableModels.push(shortM);
|
||
}
|
||
}
|
||
allModels.sort();
|
||
}catch(e){}
|
||
}
|
||
|
||
function renderTable(){
|
||
const agents=Object.values(reportData.agents).filter(a=>Object.values(a.evaluations).some(s=>s>0));
|
||
let html='<table><thead><tr><th>Agent</th>';
|
||
allModels.forEach(m=>html+=`<th>${m}</th>`);
|
||
html+='<th>Best</th><th>Score</th></tr></thead><tbody>';
|
||
agents.forEach(a=>{
|
||
html+=`<tr><td>${a.name} <button class="btn-research" onclick="openAgentModal('${a.name}')" title="Research models">🔬</button></td>`;
|
||
allModels.forEach(m=>{
|
||
const score=a.evaluations[m];
|
||
const isCur=a.info&&a.info[2]&&a.info[2].includes(m);
|
||
let cls='na',text='—',click=`onclick="openCellModal('${a.name}','${m}')"`;
|
||
if(score!==undefined&&score>0){cls=clsFor(score);text=Math.round(score);click=`onclick="openDetail('${a.name}','${m}')"`;}
|
||
const curCls=isCur?' hm-cur':'';
|
||
html+=`<td class="${cls}${curCls}" data-model="${m}" ${click}>${text}${isCur?' ●':''}</td>`;
|
||
});
|
||
html+=`<td>${a.best_model}</td><td style="font-weight:700">${Math.round(a.best_score)}</td></tr>`;
|
||
});
|
||
html+='</tbody></table>';
|
||
$('matrix').innerHTML=html;
|
||
}
|
||
|
||
function updateCell(agent,model,score){
|
||
const short=modelShort(model);
|
||
if(!reportData.agents[agent]){
|
||
reportData.agents[agent]={name:agent,evaluations:{},info:[],best_model:model,best_score:0};
|
||
}
|
||
reportData.agents[agent].evaluations[short]=score;
|
||
reportData.agents[agent].best_model=short;
|
||
reportData.agents[agent].best_score=score;
|
||
if(!allModels.includes(short)){
|
||
allModels.push(short);
|
||
allModels.sort();
|
||
}
|
||
if(!allAvailableModels.includes(short)) allAvailableModels.push(short);
|
||
renderTable();
|
||
}
|
||
|
||
async function pollJob(jobId,pid){
|
||
for(let i=0;i<60;i++){
|
||
await new Promise(r=>setTimeout(r,2000));
|
||
try{
|
||
const res=await fetch(`${API_BASE}/api/research/${jobId}`);
|
||
if(!res.ok) continue;
|
||
const job=await res.json();
|
||
if(job.status==='pending') setProgress(pid,25,'Waiting in queue...');
|
||
else if(job.status==='running') setProgress(pid,75,'Running evaluation...');
|
||
else if(job.status==='done'){ setProgress(pid,100,'Done!'); return job; }
|
||
else if(job.status==='error'){ setProgress(pid,100,'Error!'); return job; }
|
||
}catch(e){ console.warn('poll error',e); }
|
||
}
|
||
setProgress(pid,100,'Timeout'); return {status:'timeout'};
|
||
}
|
||
|
||
async function startAgentResearch(){
|
||
const agent=window.__activeAgent;
|
||
const models=[...$('agentModalList').querySelectorAll('input:checked')].map(i=>i.value);
|
||
if(!models.length)return;
|
||
$('agentStartBtn').disabled=true;
|
||
$('evolveAgentBtn').disabled=true;
|
||
setProgress('agentProgress',10,'Submitting job...');
|
||
let job;
|
||
try{
|
||
const res=await fetch(`${API_BASE}/api/research`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({agent,models})});
|
||
if(!res.ok) throw new Error('API error '+res.status);
|
||
job=await res.json();
|
||
job=await pollJob(job.job_id,'agentProgress');
|
||
}catch(e){
|
||
$('agentResults').innerHTML='<p style="color:var(--red);margin-top:12px">API unavailable — run real-fit-engine.py to evaluate '+agent+'</p>';
|
||
$('agentProgressText').textContent='Error: API unavailable';
|
||
$('agentStartBtn').disabled=false;
|
||
$('evolveAgentBtn').disabled=false;
|
||
return;
|
||
}
|
||
const results=job.models_scored||[];
|
||
let html='<table class="result-table"><thead><tr><th>Model</th><th>Score</th></tr></thead><tbody>';
|
||
let best=-1;
|
||
results.forEach(r=>{if(r.score>best)best=r.score;});
|
||
results.forEach(r=>{
|
||
const b=r.score>=best-0.1?'best':'';
|
||
html+=`<tr class="${b}" onclick="openDetail('${agent}','${r.model}')"><td>${r.model}</td><td>${Math.round(r.score)}</td></tr>`;
|
||
updateCell(agent,r.model,r.score);
|
||
});
|
||
html+='</tbody></table>';
|
||
$('agentResults').innerHTML=html;
|
||
$('agentProgressText').textContent='Done! Best score: '+Math.round(best);
|
||
const store=JSON.parse(localStorage.getItem('__researchResults')||'{}');
|
||
store[agent]={models:results,ts:Date.now()};
|
||
localStorage.setItem('__researchResults',JSON.stringify(store));
|
||
$('agentStartBtn').disabled=false;
|
||
$('evolveAgentBtn').disabled=false;
|
||
}
|
||
|
||
async function startEvolveAgent(){
|
||
const agent=window.__activeAgent;
|
||
const models=[...$('agentModalList').querySelectorAll('input:checked')].map(i=>i.value);
|
||
if(!models.length) return;
|
||
$('evolveAgentBtn').disabled=true;
|
||
$('agentStartBtn').disabled=true;
|
||
setProgress('agentProgress',10,'Submitting evolve-agent job...');
|
||
try{
|
||
const res=await fetch(`${API_BASE}/api/evolve-agent/start`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({agent,models})});
|
||
if(!res.ok) throw new Error('API error '+res.status);
|
||
setProgress('agentProgress',50,'Running role-fit test...');
|
||
const result=await res.json();
|
||
if(result.job_id){
|
||
await pollJob(result.job_id,'agentProgress');
|
||
}else{
|
||
await animateProgress('agentProgress','Processing...',2000);
|
||
}
|
||
setProgress('agentProgress',100,'Done!');
|
||
}catch(e){
|
||
console.error('evolve-agent error',e);
|
||
setProgress('agentProgress',100,'Error: '+e.message);
|
||
$('evolveAgentBtn').disabled=false;
|
||
$('agentStartBtn').disabled=false;
|
||
return;
|
||
}
|
||
await load();
|
||
closeModal('researchAgentModal');
|
||
$('evolveAgentBtn').disabled=false;
|
||
$('agentStartBtn').disabled=false;
|
||
}
|
||
|
||
async function startCellResearch(){
|
||
const agent=window.__activeAgent, model=window.__activeModel;
|
||
$('cellStartBtn').disabled=true;
|
||
setProgress('cellProgress',10,'Submitting...');
|
||
let job;
|
||
try{
|
||
const res=await fetch(`${API_BASE}/api/research/cell`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({agent,model})});
|
||
if(!res.ok) throw new Error('API error '+res.status);
|
||
job=await res.json();
|
||
job=await pollJob(job.job_id,'cellProgress');
|
||
}catch(e){
|
||
$('cellResults').innerHTML='<p style="color:var(--red);margin-top:12px">API unavailable — run real-fit-engine.py to evaluate '+agent+'</p>';
|
||
$('cellProgressText').textContent='Error: API unavailable';
|
||
$('cellStartBtn').disabled=false;
|
||
return;
|
||
}
|
||
const result=(job.models_scored||[])[0]||{model,score:0};
|
||
updateCell(agent,result.model,result.score);
|
||
$('cellResults').innerHTML='<table class="result-table"><tbody><tr onclick="openDetail(\''+agent+'\',\''+result.model+'\')"><td>'+result.model+'</td><td>'+Math.round(result.score)+'</td></tr></tbody></table>';
|
||
$('cellProgressText').textContent='Done! Score: '+Math.round(result.score);
|
||
const store=JSON.parse(localStorage.getItem('__researchResults')||'{}');
|
||
if(!store[agent]) store[agent]={models:[],ts:Date.now()};
|
||
store[agent].models=store[agent].models.filter(m=>m.model!==result.model);
|
||
store[agent].models.push(result);
|
||
localStorage.setItem('__researchResults',JSON.stringify(store));
|
||
}
|
||
|
||
load().catch(e=>$('matrix').innerHTML='Error: '+e);
|
||
</script>
|
||
</body>
|
||
</html>
|