// runner.js — 新建评估视图:列出场景、LLM角色配置、权重配置、触发评估、轮询任务状态。 const Runner = { selectedScenario: null, selectedScenarioInfo: null, pollTimer: null, lastRunId: null, init() { document.getElementById("run-btn").addEventListener("click", () => Runner.trigger()); document.getElementById("view-report-btn").addEventListener("click", () => { if (Runner.lastRunId) { App.enableReportNav(); App.navigate("report", Runner.lastRunId); } }); document.getElementById("add-doc-weight-btn").addEventListener("click", () => Runner._addDocWeightRow()); }, async loadScenarios() { const list = document.getElementById("scenario-list"); list.innerHTML = '

加载中…

'; try { const data = await API.scenarios(); const scenarios = data.scenarios || []; if (scenarios.length === 0) { list.innerHTML = '

未在 scenarios/ 下找到场景文件。

'; return; } list.innerHTML = ""; scenarios.forEach((sc) => list.appendChild(Runner.renderScenarioItem(sc))); } catch (err) { list.innerHTML = `

加载失败:${App.escape(err.message)}

`; } Runner._populateProfileSelects(); }, async _populateProfileSelects() { const cached = Profiles.getAll(); const profiles = cached.length > 0 ? cached : (await API.profiles().catch(() => ({ profiles: [] }))).profiles; ["role-judge", "role-answer", "role-dataset"].forEach(id => { const sel = document.getElementById(id); sel.innerHTML = ''; profiles.forEach(p => { const opt = document.createElement("option"); opt.value = p.profile_id; opt.textContent = `${p.name} (${p.model})`; sel.appendChild(opt); }); }); }, renderScenarioItem(sc) { const item = document.createElement("div"); const invalid = !!sc.error; item.className = "scenario-item" + (invalid ? " invalid" : ""); const modeTag = sc.mode ? `${App.escape(sc.mode)}` : ""; const metricCount = (sc.metrics || []).length; item.innerHTML = `
${App.escape(sc.scenario_name || sc.path)}
${App.escape(sc.path)}
${sc.error ? `
${App.escape(sc.error)}
` : ""}
${modeTag} ${metricCount} 指标
`; if (!invalid) { item.addEventListener("click", () => { document.querySelectorAll(".scenario-item").forEach((el) => el.classList.remove("selected")); item.classList.add("selected"); Runner.selectedScenario = sc.path; Runner.selectedScenarioInfo = sc; document.getElementById("selected-scenario").textContent = sc.path; document.getElementById("run-btn").disabled = false; document.getElementById("llm-assignment-panel").hidden = false; Runner._renderWeightPanel(sc); document.getElementById("weight-config-panel").hidden = false; }); } return item; }, // 根据选中场景渲染指标权重行(动态生成,按场景 metrics 列表) _renderWeightPanel(sc) { const metricRows = document.getElementById("metric-weight-rows"); metricRows.innerHTML = ""; const metrics = sc.metrics || []; const existingWeights = sc.metric_weights || {}; metrics.forEach(metric => { const row = document.createElement("div"); row.className = "weight-row"; const currentVal = existingWeights[metric] != null ? existingWeights[metric] : 1.0; row.innerHTML = ` ${App.escape(metric)} `; metricRows.appendChild(row); }); // 填充已有文档权重 const docRows = document.getElementById("doc-weight-rows"); docRows.innerHTML = ""; const existingDocWeights = sc.doc_weights || {}; Object.entries(existingDocWeights).forEach(([docName, w]) => { Runner._addDocWeightRow(docName, w); }); }, // 添加一行文档权重输入 _addDocWeightRow(docName, weight) { const name = docName !== undefined ? docName : ""; const w = weight !== undefined ? weight : 1.0; const container = document.getElementById("doc-weight-rows"); const row = document.createElement("div"); row.className = "weight-row"; row.innerHTML = ` `; row.querySelector(".weight-row-remove").addEventListener("click", () => row.remove()); container.appendChild(row); }, // 收集权重面板当前值;全等权时返回 null(不发送) _collectWeights() { const metricWeights = {}; document.querySelectorAll("#metric-weight-rows .weight-row-input").forEach(input => { const metric = input.dataset.metric; const val = parseFloat(input.value); if (metric && !isNaN(val)) metricWeights[metric] = val; }); const docWeights = {}; document.querySelectorAll("#doc-weight-rows .weight-row").forEach(row => { const nameInput = row.querySelector(".doc-weight-name"); const valInput = row.querySelector(".weight-row-input"); if (!nameInput || !valInput) return; const name = nameInput.value.trim(); const val = parseFloat(valInput.value); if (name && !isNaN(val)) docWeights[name] = val; }); const allMetricDefault = Object.values(metricWeights).every(v => Math.abs(v - 1.0) < 1e-9); const noDocWeights = Object.keys(docWeights).length === 0; if (allMetricDefault && noDocWeights) return { metricWeights: null, docWeights: null }; return { metricWeights, docWeights }; }, async trigger() { if (!Runner.selectedScenario) return; const runBtn = document.getElementById("run-btn"); runBtn.disabled = true; const panel = document.getElementById("task-panel"); const logBox = document.getElementById("task-log"); const statusBadge = document.getElementById("task-status"); const reportBtn = document.getElementById("view-report-btn"); panel.hidden = false; reportBtn.hidden = true; logBox.textContent = ""; Runner._setStatus(statusBadge, "queued"); try { await Runner._applyProfilesIfNeeded(logBox); const resp = await API.triggerEvaluation(Runner.selectedScenario); Runner.poll(resp.task_id); } catch (err) { Runner._setStatus(statusBadge, "failed"); logBox.textContent = (logBox.textContent ? logBox.textContent + "\n" : "") + `触发失败:${err.message}`; runBtn.disabled = false; } }, async _applyProfilesIfNeeded(logBox) { const judgeId = document.getElementById("role-judge").value; const answerId = document.getElementById("role-answer").value; const datasetId = document.getElementById("role-dataset").value; const { metricWeights, docWeights } = Runner._collectWeights(); if (!judgeId && !answerId && !datasetId && !metricWeights && !docWeights) return; logBox.textContent = "正在将 LLM 配置和权重写入场景文件…\n"; const body = { scenario_path: Runner.selectedScenario, judge_profile_id: judgeId || null, answer_profile_id: answerId || null, dataset_profile_id: datasetId || null, metric_weights: metricWeights, doc_weights: docWeights, }; const result = await API.applyProfiles(body); const fields = (result.patched_fields || []).join(", "); logBox.textContent += fields ? `✓ 已更新字段:${fields}\n` : "(未找到可更新的字段,继续运行)\n"; }, poll(taskId) { const logBox = document.getElementById("task-log"); const statusBadge = document.getElementById("task-status"); const reportBtn = document.getElementById("view-report-btn"); const runBtn = document.getElementById("run-btn"); if (Runner.pollTimer) clearInterval(Runner.pollTimer); Runner.pollTimer = setInterval(async () => { try { const status = await API.taskStatus(taskId); logBox.textContent = (status.logs || []).join("\n"); logBox.scrollTop = logBox.scrollHeight; Runner._setStatus(statusBadge, status.status); if (status.status === "completed" || status.status === "failed") { clearInterval(Runner.pollTimer); runBtn.disabled = false; if (status.status === "completed" && status.run_id) { Runner.lastRunId = status.run_id; sessionStorage.setItem("rag_run_id", status.run_id); reportBtn.hidden = false; } } } catch (err) { clearInterval(Runner.pollTimer); logBox.textContent += `\n轮询失败:${err.message}`; runBtn.disabled = false; } }, 1200); }, _setStatus(badge, status) { badge.textContent = status; badge.className = "badge " + status; }, };