const questionSelect = document.getElementById("question-select");
const questionDetail = document.getElementById("question-detail");
const runBtn = document.getElementById("run-btn");
const progress = document.getElementById("progress");
const progressFill = document.getElementById("progress-fill");
const progressText = document.getElementById("progress-text");
const resultsGrid = document.getElementById("results-grid");
const flowCard = document.getElementById("flow-card");
const flowSteps = document.getElementById("flow-steps");
const summaryCard = document.getElementById("summary-card");
const runtimeInfo = document.getElementById("runtime-info");
let questions = [];
let currentSource = null;
const MODE_COLORS = {
direct: "模式 A",
zero_shot: "模式 B",
few_shot: "模式 C",
};
async function init() {
const [qRes, rRes] = await Promise.all([
fetch("/api/questions"),
fetch("/api/runtime"),
]);
const qData = await qRes.json();
const runtime = await rRes.json();
questions = qData.questions;
questionSelect.innerHTML = questions
.map(
(q) =>
`<option value="${q.index}">题目${q.index}:${q.title}</option>`
)
.join("");
renderRuntime(runtime);
renderQuestionDetail(questions[0]);
renderPlaceholders();
questionSelect.addEventListener("change", () => {
const q = questions.find((item) => item.index === Number(questionSelect.value));
renderQuestionDetail(q);
resetUI();
});
runBtn.addEventListener("click", startDemo);
}
function renderRuntime(runtime) {
const items = [
["调用方式", runtime.call_path],
["模型", runtime.model],
["API Key", runtime.api_configured ? runtime.api_key_status : "未配置"],
["参数", `temperature=${runtime.temperature}, seed=${runtime.seed}`],
["是否真实运行", "是 · 每次回复均来自百炼实时 API"],
];
runtimeInfo.innerHTML = items
.map(([label, value]) => `<div class="runtime-item"><span>${label}</span><strong>${value}</strong></div>`)
.join("");
}
function renderQuestionDetail(q) {
if (!q) return;
questionDetail.innerHTML = `
<p><strong>题目:</strong>${escapeHtml(q.question)}</p>
<p><strong>参考答案:</strong>${escapeHtml(q.expected_answer)}</p>
<p><strong>为何适合演示 CoT:</strong>${escapeHtml(q.why_hard)}</p>
`;
}
function renderPlaceholders() {
const ids = ["direct", "zero_shot", "few_shot"];
resultsGrid.innerHTML = ids
.map(
(id) => `
<article class="mode-card placeholder" data-mode="${id}" id="card-${id}">
<div class="mode-head">
<h3>${MODE_COLORS[id] || id}</h3>
<p>等待运行…</p>
</div>
<div class="mode-body">选择题目后点击「开始对比演示」</div>
</article>`
)
.join("");
}
function resetUI() {
summaryCard.hidden = true;
flowCard.hidden = true;
progress.hidden = true;
progressFill.style.width = "0%";
flowSteps.querySelectorAll("li").forEach((li) => {
li.className = "";
li.querySelector("em").textContent = "等待";
});
renderPlaceholders();
}
function escapeHtml(text) {
return String(text)
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """);
}
function renderMessages(messages) {
return messages
.map(
(msg) => `
<details class="message-item" open>
<summary>消息 ${msg.index} · ${escapeHtml(msg.role_label)}</summary>
<pre>${escapeHtml(msg.content)}</pre>
</details>`
)
.join("");
}
function renderModeCard(result) {
const card = document.getElementById(`card-${result.id}`);
if (!card) return;
card.className = "mode-card";
card.innerHTML = `
<div class="mode-head">
<h3>${escapeHtml(result.title)} · ${escapeHtml(result.subtitle)}</h3>
<p>${escapeHtml(result.badge)} · ${result.message_count} 条消息</p>
<div class="badges">
<span class="badge ${result.correct ? "ok" : "bad"}">${result.correct ? "答案正确" : "答案错误"}</span>
<span class="badge info">${result.has_steps ? "含分步推理" : "无分步推理"}</span>
</div>
</div>
<div class="mode-body">
<div>
<div class="section-title">发送给模型的提示词</div>
<div class="prompt-summary">${escapeHtml(result.prompt_summary)}</div>
<div class="message-list">${renderMessages(result.messages)}</div>
</div>
<div>
<div class="section-title">模型回复(百炼实时返回)</div>
<div class="reply-box">${escapeHtml(result.reply)}</div>
<p><strong>最终答案:</strong>${escapeHtml(result.final_answer)}</p>
</div>
</div>
`;
}
function setFlowStep(step, status, text) {
const li = flowSteps.querySelector(`li[data-step="${step}"]`);
if (!li) return;
li.className = status;
li.querySelector("em").textContent = text;
}
function renderSummary(summary) {
summaryCard.hidden = false;
const promptBody = summary.prompt_diff
.map(
(row) =>
`<tr><td>${escapeHtml(row.title)}</td><td>${row.message_count}</td><td>${escapeHtml(row.prompt_summary)}</td></tr>`
)
.join("");
const resultBody = summary.result_diff
.map(
(row) =>
`<tr>
<td>${escapeHtml(row.title)}</td>
<td>${escapeHtml(row.final_answer)}</td>
<td>${row.correct ? "正确" : "错误"}</td>
<td>${row.has_steps ? "有" : "无"}</td>
</tr>`
)
.join("");
document.querySelector("#prompt-table tbody").innerHTML = promptBody;
document.querySelector("#result-table tbody").innerHTML = resultBody;
}
function startDemo() {
if (currentSource) {
currentSource.close();
currentSource = null;
}
const questionIndex = Number(questionSelect.value);
resetUI();
runBtn.disabled = true;
progress.hidden = false;
flowCard.hidden = false;
progressText.textContent = "连接演示服务…";
progressFill.style.width = "5%";
["direct", "zero_shot", "few_shot"].forEach((id) => {
const card = document.getElementById(`card-${id}`);
if (card) {
card.className = "mode-card loading placeholder";
card.querySelector(".mode-body").textContent = "等待调用…";
}
});
currentSource = new EventSource(`/api/stream/${questionIndex}`);
currentSource.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === "error") {
progressText.textContent = `错误:${data.message}`;
runBtn.disabled = false;
currentSource.close();
return;
}
if (data.type === "init") {
renderQuestionDetail(data.question);
renderRuntime(data.runtime);
progressText.textContent = `已加载题目:${data.question.title}`;
progressFill.style.width = "10%";
}
if (data.type === "mode_start") {
setFlowStep(data.step, "running", "正在调用百炼 API…");
progressText.textContent = `步骤 ${data.step}/${data.total}:${data.title} · ${data.subtitle}`;
progressFill.style.width = `${10 + ((data.step - 1) / data.total) * 70}%`;
const card = document.getElementById(`card-${data.mode_id}`);
if (card) {
card.className = "mode-card loading";
card.innerHTML = `
<div class="mode-head"><h3>${escapeHtml(data.title)}</h3><p>${escapeHtml(data.subtitle)}</p></div>
<div class="mode-body">正在调用百炼 API,请稍候…</div>`;
}
}
if (data.type === "mode_done") {
setFlowStep(data.step, "done", "完成");
renderModeCard(data.result);
progressFill.style.width = `${10 + (data.step / data.total) * 80}%`;
}
if (data.type === "summary") {
renderSummary(data);
progressFill.style.width = "95%";
}
if (data.type === "done") {
progressText.textContent = `演示完成,共发起 ${data.api_calls} 次百炼 API 真实调用`;
progressFill.style.width = "100%";
runBtn.disabled = false;
currentSource.close();
currentSource = null;
}
};
currentSource.onerror = () => {
progressText.textContent = "连接中断,请确认 web_app.py 正在运行";
runBtn.disabled = false;
if (currentSource) currentSource.close();
};
}
init();