Improve AI batch progress controls

This commit is contained in:
2026-05-27 12:59:15 +08:00
parent 5d96c1de6d
commit b5f41c3dda
2 changed files with 229 additions and 23 deletions

View File

@@ -19,6 +19,8 @@ const state = {
aiJob: null,
aiJobOwned: false,
aiPolling: null,
bulkJob: null,
bulkPolling: null,
currentUser: null,
authenticated: false,
pdfZoom: Number(localStorage.getItem("frontPagePdfZoom")) || 110,
@@ -425,7 +427,7 @@ function renderFilterActions() {
const aiPassedCount = Number(state.status?.ai_passed || 0);
const shouldShow = ["review_all", "ai_passed"].includes(filter);
button.classList.toggle("is-hidden", !shouldShow);
button.disabled = !shouldShow || aiPassedCount <= 0;
button.disabled = !shouldShow || aiPassedCount <= 0 || Boolean(state.bulkJob?.running);
button.textContent = aiPassedCount > 0 ? `通过所有AI已通过${aiPassedCount}` : "通过所有AI已通过";
}
@@ -1414,22 +1416,39 @@ function setAiButtonsDisabled(disabled) {
});
}
function clearAiProgressPanel() {
const panel = $("aiProgressPanel");
if (!panel) return;
panel.classList.add("is-hidden");
$("aiProgressBar").style.width = "0%";
$("aiProgressText").textContent = "0/0";
$("aiProgressMeta").textContent = "等待开始";
$("aiCancelBtn").classList.remove("is-hidden");
$("aiCancelBtn").disabled = true;
}
function renderAiProgress(job = state.aiJob) {
const panel = $("aiProgressPanel");
if (!panel) return;
if (!job || (!job.running && !job.total && !job.message)) {
panel.classList.add("is-hidden");
if (!job || (!job.running && !job.total && !job.message) || (!job.running && job.cancel_requested)) {
clearAiProgressPanel();
return;
}
const total = Number(job.total || 0);
const processed = Number(job.processed || 0);
const percent = total ? Math.min(100, Math.round((processed / total) * 100)) : 0;
const isBulkApprove = job.kind === "approve_ai_passed";
panel.classList.remove("is-hidden");
$("aiProgressTitle").textContent = job.running ? "AI处理中" : displayText(job.message || "AI处理完成");
$("aiProgressTitle").textContent = isBulkApprove
? (job.running ? "批量通过中" : displayText(job.message || "批量通过完成"))
: (job.running ? "AI处理中" : displayText(job.message || "AI处理完成"));
$("aiProgressText").textContent = total ? `${processed}/${total}` : "--";
$("aiProgressBar").style.width = `${percent}%`;
$("aiProgressMeta").textContent = `OK ${job.ok || 0} · 不OK ${job.pending || 0} · 失败 ${job.failed || 0} · 并发 ${job.concurrency || 1}`;
$("aiCancelBtn").disabled = !job.running;
$("aiProgressMeta").textContent = isBulkApprove
? `已通过 ${job.updated || 0} · 剩余 ${Math.max(0, total - processed)} · 失败 ${job.failed || 0}`
: `OK ${job.ok || 0} · 不OK ${job.pending || 0} · 失败 ${job.failed || 0} · 并发 ${job.concurrency || 1}`;
$("aiCancelBtn").classList.toggle("is-hidden", isBulkApprove);
$("aiCancelBtn").disabled = isBulkApprove || !job.running;
}
async function cancelAiReview({ silent = false } = {}) {
@@ -1439,6 +1458,9 @@ async function cancelAiReview({ silent = false } = {}) {
renderAiProgress(job);
setAiButtonsDisabled(Boolean(job.running));
if (!silent) showMessage(displayText(job.message || "AI核验正在中断..."));
if (job.running && !state.aiPolling) {
state.aiPolling = setTimeout(() => pollAiReviewJob().catch((error) => showMessage(error.message, "error")), 1200);
}
return job;
}
@@ -1448,8 +1470,8 @@ async function pollAiReviewJob() {
renderAiProgress(job);
const total = job.total || 0;
const processed = job.processed || 0;
showMessage(`AI处理中${processed}/${total},并发 ${job.concurrency || 1}OK ${job.ok || 0}不OK ${job.pending || 0},失败 ${job.failed || 0}`, "");
if (job.running) {
showMessage(`AI处理中${processed}/${total},并发 ${job.concurrency || 1}OK ${job.ok || 0}不OK ${job.pending || 0},失败 ${job.failed || 0}`, "");
if (state.aiJobOwned && !job.cancel_requested && state.activePage !== "review") switchPage("review");
setAiButtonsDisabled(true);
state.aiPolling = setTimeout(() => pollAiReviewJob().catch((error) => showMessage(error.message, "error")), 2000);
@@ -1458,6 +1480,15 @@ async function pollAiReviewJob() {
state.aiPolling = null;
state.aiJobOwned = false;
setAiButtonsDisabled(false);
if (job.cancel_requested) {
clearAiProgressPanel();
showMessage(displayText(job.message || "AI核验已中断"), "ok");
await loadStatus().catch(() => null);
await loadOverview().catch(() => null);
await loadRecords();
await refreshSelectedRecord();
return;
}
showMessage(displayText(`${job.message || "AI处理完成"}OK ${job.ok || 0}不OK ${job.pending || 0},失败 ${job.failed || 0}`), job.failed ? "error" : "ok");
await loadStatus().catch(() => null);
await loadOverview().catch(() => null);
@@ -1502,19 +1533,37 @@ async function approveAiNoIssueRecords() {
button.disabled = true;
showMessage("正在批量确认历史 AI 已通过记录...");
try {
const data = await api("/api/ai/review/approve-no-issue", { method: "POST" });
state.status = data.status_snapshot || state.status;
showMessage(`已批量确认 ${data.updated || 0} 条历史 AI 已通过记录`, "ok");
await loadStatus().catch(() => null);
await loadOverview().catch(() => null);
await loadRecords();
const job = await api("/api/ai/review/approve-no-issue", { method: "POST" });
state.bulkJob = job;
renderAiProgress(job);
await pollApproveAiNoIssueJob();
} catch (error) {
showMessage(error.message, "error");
} finally {
button.disabled = false;
renderFilterActions();
}
}
async function pollApproveAiNoIssueJob() {
const job = await api("/api/ai/review/approve-no-issue/status");
state.bulkJob = job;
renderAiProgress(job);
const total = Number(job.total || 0);
const processed = Number(job.processed || 0);
if (job.running) {
showMessage(`批量通过AI已通过${processed}/${total},已更新 ${job.updated || 0}`, "");
renderFilterActions();
state.bulkPolling = setTimeout(() => pollApproveAiNoIssueJob().catch((error) => showMessage(error.message, "error")), 1200);
return;
}
state.bulkPolling = null;
showMessage(displayText(job.message || `批量通过完成,已更新 ${job.updated || 0}`), job.failed ? "error" : "ok");
await loadStatus().catch(() => null);
await loadOverview().catch(() => null);
await loadRecords();
renderFilterActions();
}
async function submitReviewedRecords() {
if (!window.confirm("确认提交所有已人工复核项目?提交后这些项目将不再出现在复核工作台列表中。")) return;
const button = $("submitReviewedBtn");
@@ -1857,9 +1906,15 @@ async function loadWorkspace() {
state.aiJobOwned = false;
renderAiProgress(job);
}
const bulkJob = await api("/api/ai/review/approve-no-issue/status").catch(() => null);
if (bulkJob?.running) {
state.bulkJob = bulkJob;
renderAiProgress(bulkJob);
}
const page = canOpenPage(state.activePage) ? state.activePage : firstAllowedPage();
switchPage(job?.running && state.aiJobOwned && canOpenPage("review") ? "review" : page);
if (job?.running) pollAiReviewJob().catch((error) => showMessage(error.message, "error"));
if (bulkJob?.running) pollApproveAiNoIssueJob().catch((error) => showMessage(error.message, "error"));
}
boot();