213 lines
10 KiB
JavaScript
213 lines
10 KiB
JavaScript
|
|
/* CorrectBench Results Browser */
|
||
|
|
|
||
|
|
const ResultsApp = {
|
||
|
|
data: [],
|
||
|
|
currentDetail: null,
|
||
|
|
|
||
|
|
init() {
|
||
|
|
var self = this;
|
||
|
|
document.querySelectorAll(".tab-btn").forEach(function(btn) {
|
||
|
|
btn.addEventListener("click", function(e) {
|
||
|
|
self.switchTab(e.target.dataset.tab);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
document.getElementById("results-search").addEventListener("input", function(e) {
|
||
|
|
self.filterResults(e.target.value);
|
||
|
|
});
|
||
|
|
document.getElementById("btn-refresh-results").addEventListener("click", function() {
|
||
|
|
self.loadResults();
|
||
|
|
});
|
||
|
|
document.getElementById("btn-back-results").addEventListener("click", function() {
|
||
|
|
self.showResultsList();
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
switchTab(tabName) {
|
||
|
|
document.querySelectorAll(".tab-btn").forEach(function(btn) {
|
||
|
|
btn.classList.toggle("active", btn.dataset.tab === tabName);
|
||
|
|
});
|
||
|
|
document.querySelectorAll(".tab-content").forEach(function(el) {
|
||
|
|
el.classList.toggle("active", el.id === "tab-" + tabName);
|
||
|
|
});
|
||
|
|
if (tabName === "results" && ResultsApp.data.length === 0) {
|
||
|
|
ResultsApp.loadResults();
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
loadResults() {
|
||
|
|
var container = document.getElementById("results-list");
|
||
|
|
container.innerHTML = "";
|
||
|
|
fetch("/api/results/runs")
|
||
|
|
.then(function(resp) { return resp.json(); })
|
||
|
|
.then(function(data) {
|
||
|
|
ResultsApp.data = data;
|
||
|
|
ResultsApp.renderResults(data);
|
||
|
|
})
|
||
|
|
.catch(function() {
|
||
|
|
container.innerHTML = '<div class="no-results">加载失败</div>';
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
renderResults(data) {
|
||
|
|
var container = document.getElementById("results-list");
|
||
|
|
var countEl = document.getElementById("results-count");
|
||
|
|
countEl.textContent = data.length + " 条记录";
|
||
|
|
if (data.length === 0) {
|
||
|
|
container.innerHTML = '<div class="no-results">暂无运行记录</div>';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var html = "";
|
||
|
|
for (var i = 0; i < data.length; i++) {
|
||
|
|
var run = data[i];
|
||
|
|
var cov = run.coverage;
|
||
|
|
var covClass = "low";
|
||
|
|
if (cov !== null && cov !== undefined) {
|
||
|
|
if (cov >= 80) covClass = "high";
|
||
|
|
else if (cov >= 50) covClass = "medium";
|
||
|
|
}
|
||
|
|
var covText = (cov !== null && cov !== undefined) ? cov + "%" : "-";
|
||
|
|
var passClass = run.full_pass ? "pass" : "fail";
|
||
|
|
var passText = run.full_pass ? "✓ PASS" : "✗ FAIL";
|
||
|
|
var timeText = run.time ? run.time.toFixed(1) + "s" : "";
|
||
|
|
var costText = run.token_cost ? "$" + run.token_cost.toFixed(3) : "";
|
||
|
|
html += '<div class="run-card" onclick="ResultsApp.showTaskDetail('' + run.path + '')">' ;
|
||
|
|
html += '<span class="task-id">' + run.task_id + '</span>';
|
||
|
|
html += '<span class="coverage ' + covClass + '">' + covText + '</span>';
|
||
|
|
html += '<span class="pass-badge ' + passClass + '">' + passText + '</span>';
|
||
|
|
html += '<span class="run-path">' + run.path + '</span>';
|
||
|
|
html += '<span class="run-meta">' + timeText + " " + costText + '</span>';
|
||
|
|
html += "</div>";
|
||
|
|
}
|
||
|
|
container.innerHTML = html;
|
||
|
|
},
|
||
|
|
|
||
|
|
filterResults(query) {
|
||
|
|
var q = query.toLowerCase().trim();
|
||
|
|
if (!q) { this.renderResults(this.data); return; }
|
||
|
|
var filtered = this.data.filter(function(r) {
|
||
|
|
return r.task_id.toLowerCase().indexOf(q) >= 0 || r.path.toLowerCase().indexOf(q) >= 0;
|
||
|
|
});
|
||
|
|
this.renderResults(filtered);
|
||
|
|
},
|
||
|
|
|
||
|
|
showResultsList() {
|
||
|
|
document.getElementById("results-list-view").style.display = "";
|
||
|
|
document.getElementById("task-detail-view").style.display = "none";
|
||
|
|
this.currentDetail = null;
|
||
|
|
},
|
||
|
|
|
||
|
|
showTaskDetail(taskPath) {
|
||
|
|
var self = this;
|
||
|
|
document.getElementById("results-list-view").style.display = "none";
|
||
|
|
document.getElementById("task-detail-view").style.display = "";
|
||
|
|
document.getElementById("detail-task-title").textContent = "加载中...";
|
||
|
|
document.getElementById("detail-info").innerHTML = "";
|
||
|
|
document.getElementById("code-tabs").innerHTML = "";
|
||
|
|
document.getElementById("code-viewer").textContent = "";
|
||
|
|
fetch("/api/results/task/" + encodeURIComponent(taskPath))
|
||
|
|
.then(function(resp) { return resp.json(); })
|
||
|
|
.then(function(data) {
|
||
|
|
self.currentDetail = data;
|
||
|
|
self.renderTaskDetail(data);
|
||
|
|
})
|
||
|
|
.catch(function() {
|
||
|
|
document.getElementById("detail-task-title").textContent = "加载失败";
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
renderTaskDetail(data) {
|
||
|
|
document.getElementById("detail-task-title").textContent = data.task_id;
|
||
|
|
var info = data.run_info || {};
|
||
|
|
var infoContainer = document.getElementById("detail-info");
|
||
|
|
var rows = [
|
||
|
|
["Task ID", info.task_id || data.task_id, ""],
|
||
|
|
["Coverage", info.coverage !== undefined ? info.coverage + "%" : "-", info.coverage >= 80 ? "pass" : (info.coverage >= 50 ? "" : "fail")],
|
||
|
|
["Full Pass", info.full_pass ? "✓ Yes" : "✗ No", info.full_pass ? "pass" : "fail"],
|
||
|
|
["Circuit Type", info.circuit_type || "-", ""],
|
||
|
|
["Time", info.time ? info.time.toFixed(1) + "s" : "-", ""],
|
||
|
|
["Token Cost", info.token_cost ? "$" + info.token_cost.toFixed(3) : "-", ""],
|
||
|
|
["Prompt Tokens", info.prompt_tokens || "-", ""],
|
||
|
|
["Completion Tokens", info.completion_tokens || "-", ""],
|
||
|
|
["Max Iter", info.max_iter || "-", ""],
|
||
|
|
["Scenario Num", info.scenario_num || "-", ""],
|
||
|
|
["Reboot Times", info.reboot_times || "0", ""],
|
||
|
|
["Eval0 Pass", info.Eval0_pass ? "✓" : "✗", info.Eval0_pass ? "pass" : "fail"],
|
||
|
|
["Eval1 Pass", info.Eval1_pass ? "✓" : "✗", info.Eval1_pass ? "pass" : "fail"],
|
||
|
|
["Eval2 Pass", info.Eval2_pass ? "✓" : "✗", info.Eval2_pass ? "pass" : "fail"],
|
||
|
|
["Eval2 Ratio", info.Eval2_ratio || "-", ""],
|
||
|
|
["TB Corrected", info.TB_corrected ? "Yes" : "No", ""],
|
||
|
|
["Incomplete", info["ERROR(incomplete)"] ? "Yes" : "No", ""]
|
||
|
|
];
|
||
|
|
var infoHtml = "";
|
||
|
|
for (var i = 0; i < rows.length; i++) {
|
||
|
|
var r = rows[i];
|
||
|
|
infoHtml += '<div class="detail-info-row"><span class="label">' + r[0] + '</span><span class="value ' + (r[2] || "") + '">' + r[1] + '</span></div>';
|
||
|
|
}
|
||
|
|
infoContainer.innerHTML = infoHtml;
|
||
|
|
|
||
|
|
var tabs = [];
|
||
|
|
if (data.final_tb) tabs.push({id:"final_tb", label:"final_TB.v", code:data.final_tb, lang:"verilog"});
|
||
|
|
if (data.dut) tabs.push({id:"dut", label:"DUT.v", code:data.dut, lang:"verilog"});
|
||
|
|
if (data.final_tb_py) tabs.push({id:"final_tb_py", label:"final_TB.py", code:data.final_tb_py, lang:"python"});
|
||
|
|
|
||
|
|
var tabsContainer = document.getElementById("code-tabs");
|
||
|
|
var tabsHtml = "";
|
||
|
|
for (var j = 0; j < tabs.length; j++) {
|
||
|
|
tabsHtml += '<button class="code-tab ' + (j === 0 ? "active" : "") + '" data-code-id="' + tabs[j].id + '">' + tabs[j].label + '</button>';
|
||
|
|
}
|
||
|
|
tabsContainer.innerHTML = tabsHtml;
|
||
|
|
|
||
|
|
var self = this;
|
||
|
|
tabsContainer.querySelectorAll(".code-tab").forEach(function(tab) {
|
||
|
|
tab.addEventListener("click", function(e) {
|
||
|
|
tabsContainer.querySelectorAll(".code-tab").forEach(function(t) { t.classList.remove("active"); });
|
||
|
|
e.target.classList.add("active");
|
||
|
|
var codeId = e.target.dataset.codeId;
|
||
|
|
var tabData = tabs.find(function(t) { return t.id === codeId; });
|
||
|
|
if (tabData) self.renderCode(tabData.code, tabData.lang);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
if (tabs.length > 0) {
|
||
|
|
this.renderCode(tabs[0].code, tabs[0].lang);
|
||
|
|
} else {
|
||
|
|
document.getElementById("code-viewer").textContent = "// 无代码文件";
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
renderCode(code, lang) {
|
||
|
|
var viewer = document.getElementById("code-viewer");
|
||
|
|
if (lang === "verilog") viewer.innerHTML = this.highlightVerilog(code);
|
||
|
|
else if (lang === "python") viewer.innerHTML = this.highlightPython(code);
|
||
|
|
else viewer.textContent = code;
|
||
|
|
},
|
||
|
|
|
||
|
|
escapeHtml(text) {
|
||
|
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/\>/g, ">");
|
||
|
|
},
|
||
|
|
|
||
|
|
highlightVerilog(code) {
|
||
|
|
var html = this.escapeHtml(code);
|
||
|
|
html = html.replace(/(\\/\\.*$)/gm, '<span class="cm">$1</span>');
|
||
|
|
html = html.replace(/(\`\w+)/g, '<span class="dir">$1</span>');
|
||
|
|
html = html.replace(/(".*?")/g, '<span class="str">$1</span>');
|
||
|
|
html = html.replace(/\b(\d+'[bBhHdDoO]\w+|\d+)\b/g, '<span class="num">$1</span>');
|
||
|
|
var kw = ["module","endmodule","input","output","reg","wire","integer","always","begin","end","if","else","case","endcase","for","while","initial","final","assign","parameter","localparam","function","endfunction","task","endtask","posedge","negedge","or","and","not","xor","forever","repeat","disable","wait","fork","join"];
|
||
|
|
html = html.replace(new RegExp(\b( + kw.join("|") + )\b, "g"), '<span class="kw">$1</span>');
|
||
|
|
html = html.replace(/(\$\w+)/g, '<span class="fn">$1</span>');
|
||
|
|
return html;
|
||
|
|
},
|
||
|
|
|
||
|
|
highlightPython(code) {
|
||
|
|
var html = this.escapeHtml(code);
|
||
|
|
html = html.replace(/(#.*$)/gm, '<span class="cm">$1</span>');
|
||
|
|
html = html.replace(/("""[\s\S]*?"""|'''[\s\S]*?''')/g, '<span class="str">$1</span>');
|
||
|
|
html = html.replace(/("[^"\n]*"|'[^'\n]*')/g, '<span class="str">$1</span>');
|
||
|
|
var kw = ["def","class","import","from","return","if","elif","else","for","while","try","except","finally","with","as","yield","lambda","pass","break","continue","raise","assert","global","nonlocal","del","in","not","and","or","is","True","False","None"];
|
||
|
|
html = html.replace(new RegExp(\b( + kw.join("|") + )\b, "g"), '<span class="kw">$1</span>');
|
||
|
|
html = html.replace(/\b(\d+\.\?\d*)\b/g, '<span class="num">$1</span>');
|
||
|
|
html = html.replace(/(@\w+)/g, '<span class="dir">$1</span>');
|
||
|
|
return html;
|
||
|
|
}
|
||
|
|
};
|