feat: async score jobs — POST /api/score/async + 评分记录 page

Each async score job:
- Runs InlineScorer.score() in thread pool
- Writes standard run artifacts (metadata.json, scores.csv, summary.md)
- Runs optimization_advisor => optimization_advice.md
- Result appears in 运行列表 and 报告详情 with full report

New endpoints:
- POST /api/score/async  (202, job_id immediate)
- GET  /api/score/jobs   (list all jobs)
- GET  /api/score/jobs/{id} (single job status)

Frontend:
- 评分记录 nav page with card list
- 5s auto-polling for queued/running jobs
- 查看报告 button navigates to existing 报告详情 page

Dify: change /api/score -> /api/score/async, no response parsing needed

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-06-24 17:24:22 +08:00
parent abcd61ec8f
commit 4fd515d2d9
9 changed files with 706 additions and 11 deletions

View File

@@ -5,8 +5,8 @@
const App = {
currentRunId: null,
activeView: null,
views: ["runs", "new", "report", "profiles", "apidocs"],
titles: { runs: "运行列表", new: "新建评估", report: "报告详情", profiles: "LLM 配置", apidocs: "API 文档" },
views: ["runs", "new", "report", "profiles", "scorejobs", "apidocs"],
titles: { runs: "运行列表", new: "新建评估", report: "报告详情", profiles: "LLM 配置", scorejobs: "评分记录", apidocs: "API 文档" },
// 初始化:绑定导航、从 URL/sessionStorage 恢复上次位置、启动健康检查。
init() {
@@ -68,10 +68,11 @@ const App = {
sessionStorage.setItem("rag_view", view);
if (App.currentRunId) sessionStorage.setItem("rag_run_id", App.currentRunId);
if (view === "runs") App.loadRuns();
if (view === "new") Runner.loadScenarios();
if (view === "report") Report.render(App.currentRunId);
if (view === "profiles") Profiles.load();
if (view === "runs") App.loadRuns();
if (view === "new") Runner.loadScenarios();
if (view === "report") Report.render(App.currentRunId);
if (view === "profiles") Profiles.load();
if (view === "scorejobs") ScoreJobs.load();
},
// ----------------------------------------------------------------