255 lines
12 KiB
Python
255 lines
12 KiB
Python
|
|
import os
|
||
|
|
|
||
|
|
out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "web", "static", "results.js")
|
||
|
|
|
||
|
|
# Write JS using template with placeholders for regex patterns
|
||
|
|
# This avoids Python escape hell
|
||
|
|
|
||
|
|
template = """
|
||
|
|
/* 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 ? "\u2713 PASS" : "\u2717 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 = "\u52a0\u8f7d\u4e2d...";
|
||
|
|
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 = "\u52a0\u8f7d\u5931\u8d25";
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
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 ? "\u2713 Yes" : "\u2717 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 ? "\u2713" : "\u2717", info.Eval0_pass ? "pass" : "fail"],
|
||
|
|
["Eval1 Pass", info.Eval1_pass ? "\u2713" : "\u2717", info.Eval1_pass ? "pass" : "fail"],
|
||
|
|
["Eval2 Pass", info.Eval2_pass ? "\u2713" : "\u2717", 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 = "// \u65e0\u4ee3\u7801\u6587\u4ef6";
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
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(REGEX_AMP, "&").replace(REGEX_LT, "<").replace(REGEX_GT, ">");
|
||
|
|
},
|
||
|
|
|
||
|
|
highlightVerilog(code) {
|
||
|
|
var html = this.escapeHtml(code);
|
||
|
|
html = html.replace(REGEX_VCOMMENT, '<span class="cm">$1</span>');
|
||
|
|
html = html.replace(REGEX_VDIRECTIVE, '<span class="dir">$1</span>');
|
||
|
|
html = html.replace(REGEX_VSTRING, '<span class="str">$1</span>');
|
||
|
|
html = html.replace(REGEX_VNUM, '<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(REGEX_KW_PRE + kw.join("|") + REGEX_KW_SUF, "g"), '<span class="kw">$1</span>');
|
||
|
|
html = html.replace(REGEX_VSYS, '<span class="fn">$1</span>');
|
||
|
|
return html;
|
||
|
|
},
|
||
|
|
|
||
|
|
highlightPython(code) {
|
||
|
|
var html = this.escapeHtml(code);
|
||
|
|
html = html.replace(REGEX_PCOMMENT, '<span class="cm">$1</span>');
|
||
|
|
html = html.replace(REGEX_PDOCSTRING, '<span class="str">$1</span>');
|
||
|
|
html = html.replace(REGEX_PSTRING, '<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(REGEX_KW_PRE + kw.join("|") + REGEX_KW_SUF, "g"), '<span class="kw">$1</span>');
|
||
|
|
html = html.replace(REGEX_PNUM, '<span class="num">$1</span>');
|
||
|
|
html = html.replace(REGEX_PDECORATOR, '<span class="dir">$1</span>');
|
||
|
|
return html;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
"""
|
||
|
|
|
||
|
|
# Replace regex placeholders with actual patterns
|
||
|
|
# Use chr() to avoid quote conflicts in raw strings
|
||
|
|
DQ = chr(34) # double quote
|
||
|
|
SQ = chr(39) # single quote
|
||
|
|
BS = chr(92) # backslash
|
||
|
|
|
||
|
|
replacements = {
|
||
|
|
"REGEX_AMP": "/&/g",
|
||
|
|
"REGEX_LT": "/</g",
|
||
|
|
"REGEX_GT": "/"+BS+">/g",
|
||
|
|
"REGEX_VCOMMENT": "/("+BS+BS+"/"+BS+BS+".*$)/gm",
|
||
|
|
"REGEX_VDIRECTIVE": "/("+BS+"`"+BS+"w+)/g",
|
||
|
|
"REGEX_VSTRING": "/("+DQ+".*?"+DQ+")/g",
|
||
|
|
"REGEX_VNUM": "/"+BS+"b("+BS+"d+"+SQ+"[bBhHdDoO]"+BS+"w+|"+BS+"d+)"+BS+"b/g",
|
||
|
|
"REGEX_VSYS": "/("+BS+"$"+BS+"w+)/g",
|
||
|
|
"REGEX_PCOMMENT": "/(#.*$)/gm",
|
||
|
|
"REGEX_PDOCSTRING": "/("+DQ+DQ+DQ+"["+BS+"s"+BS+"S]*?"+DQ+DQ+DQ+"|"+SQ+SQ+SQ+"["+BS+"s"+BS+"S]*?"+SQ+SQ+SQ+")/g",
|
||
|
|
"REGEX_PSTRING": "/("+DQ+"[^"+DQ+BS+"n]*"+DQ+"|"+SQ+"[^"+SQ+BS+"n]*"+SQ+")/g",
|
||
|
|
"REGEX_PNUM": "/"+BS+"b("+BS+"d+"+BS+"."+BS+"?"+BS+"d*)"+BS+"b/g",
|
||
|
|
"REGEX_PDECORATOR": "/(@"+BS+"w+)/g",
|
||
|
|
"REGEX_KW_PRE": BS+"b(",
|
||
|
|
"REGEX_KW_SUF": ")"+BS+"b",
|
||
|
|
}
|
||
|
|
|
||
|
|
result = template
|
||
|
|
for key, val in replacements.items():
|
||
|
|
result = result.replace(key, val)
|
||
|
|
|
||
|
|
with open(out, "w", encoding="utf-8") as f:
|
||
|
|
f.write(result.strip() + "\n")
|
||
|
|
|
||
|
|
print("OK: " + out + " (" + str(len(result)) + " chars)")
|