mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(wiki): JobStageBar stuck at queued — add job stage transitions
Root cause: processRawMaterial() created a job record at queued stage but never called jobService.transition() during processing. The job row stayed at queued forever, so the stage bar never advanced. Backend (WikiProcessingService): - Transition job to ROUTING immediately after creation - Transition to PHASE_A_RUNNING before chunk processing begins - Transition to COMPLETED/PARTIAL/FAILED at the end based on finalStatus - Transition to FAILED in the catch block on unhandled exceptions Backend (WikiProcessingJobService.transition): - Handle FAILED, PARTIAL terminal stages (set finishedAt + status) - Handle non-terminal intermediate stages (set status to running) Frontend (JobStageBar.vue): - Add stageMapping for backend stages not shown as dots: phase_a_done → phase_b_running, failed/partial/cancelled → completed position - Guard stageIndex() against -1 (unknown stages default to all-pending) - Terminal failure states show red failed dot instead of pulsing active
This commit is contained in:
parent
af8f712986
commit
0301d5628e
@ -95,6 +95,15 @@ public class WikiProcessingJobService {
|
||||
} else if (newStage == WikiJobStage.COMPLETED) {
|
||||
job.setFinishedAt(LocalDateTime.now());
|
||||
job.setStatus(WikiJobStatus.COMPLETED.name().toLowerCase());
|
||||
} else if (newStage == WikiJobStage.FAILED) {
|
||||
job.setFinishedAt(LocalDateTime.now());
|
||||
job.setStatus(WikiJobStatus.FAILED.name().toLowerCase());
|
||||
} else if (newStage == WikiJobStage.PARTIAL) {
|
||||
job.setFinishedAt(LocalDateTime.now());
|
||||
job.setStatus(WikiJobStatus.PARTIAL.name().toLowerCase());
|
||||
} else if (!newStage.isTerminal()) {
|
||||
// Non-terminal intermediate stage: mark as running
|
||||
job.setStatus(WikiJobStatus.RUNNING.name().toLowerCase());
|
||||
}
|
||||
jobMapper.updateById(job);
|
||||
return job;
|
||||
|
||||
@ -137,10 +137,13 @@ public class WikiProcessingService {
|
||||
|
||||
kbService.updateStatus(kb.getId(), "processing");
|
||||
|
||||
// RFC-030 §9.1: create a processing job record before starting
|
||||
// RFC-030 §9.1: create a processing job record and track its ID for stage transitions
|
||||
Long jobId = null;
|
||||
if (wikiJobService != null) {
|
||||
try {
|
||||
wikiJobService.createHeavyIngest(kb.getId(), rawId);
|
||||
var job = wikiJobService.createHeavyIngest(kb.getId(), rawId);
|
||||
jobId = job.getId();
|
||||
wikiJobService.transition(jobId, vip.mate.wiki.job.WikiJobStage.ROUTING);
|
||||
} catch (Exception e) {
|
||||
log.warn("[Wiki] Failed to create heavy ingest job record for raw={}: {}", rawId, e.getMessage());
|
||||
}
|
||||
@ -182,6 +185,11 @@ public class WikiProcessingService {
|
||||
// Phase 3: 构建已有页面索引(一次构建,所有 chunk 共用)
|
||||
String existingPagesIndex = buildExistingPagesIndex(kb.getId());
|
||||
|
||||
// Transition job to phase_a (chunk processing begins)
|
||||
if (wikiJobService != null && jobId != null) {
|
||||
try { wikiJobService.transition(jobId, vip.mate.wiki.job.WikiJobStage.PHASE_A_RUNNING); } catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
// Phase 3: LLM 消化
|
||||
// result[0] = totalPages, result[1] = failedChunks, result[2] = totalChunks
|
||||
int[] result;
|
||||
@ -256,6 +264,18 @@ public class WikiProcessingService {
|
||||
"kbPageCount", pageCount));
|
||||
}
|
||||
|
||||
// Transition job to terminal stage
|
||||
if (wikiJobService != null && jobId != null) {
|
||||
try {
|
||||
var terminalStage = switch (finalStatus) {
|
||||
case "failed" -> vip.mate.wiki.job.WikiJobStage.FAILED;
|
||||
case "partial" -> vip.mate.wiki.job.WikiJobStage.PARTIAL;
|
||||
default -> vip.mate.wiki.job.WikiJobStage.COMPLETED;
|
||||
};
|
||||
wikiJobService.transition(jobId, terminalStage);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
log.info("[Wiki] Processing completed for raw={}, kbId={}, generatedPages={}, totalPages={}",
|
||||
rawId, kb.getId(), totalPages, pageCount);
|
||||
|
||||
@ -282,6 +302,10 @@ public class WikiProcessingService {
|
||||
log.error("[Wiki] Processing failed for raw={}: {}", rawId, e.getMessage(), e);
|
||||
rawService.updateProcessingStatus(rawId, "failed", e.getMessage());
|
||||
kbService.updateStatus(kb.getId(), "active");
|
||||
// Transition job to failed
|
||||
if (wikiJobService != null && jobId != null) {
|
||||
try { wikiJobService.transition(jobId, vip.mate.wiki.job.WikiJobStage.FAILED); } catch (Exception ignored) {}
|
||||
}
|
||||
// RFC-012 M3:广播异常终态
|
||||
progressBus.broadcast(kb.getId(), WikiProgressBus.EVENT_RAW_FAILED,
|
||||
java.util.Map.of("rawId", rawId, "error", e.getMessage() == null ? "unknown" : e.getMessage()));
|
||||
|
||||
@ -81,7 +81,25 @@ const stages = [
|
||||
]
|
||||
|
||||
const stageOrder = stages.map(s => s.key)
|
||||
const currentStage = computed(() => props.stage)
|
||||
|
||||
// Map backend stage values (including intermediates/terminals not shown as dots)
|
||||
// to their logical position in the visible stage list.
|
||||
const stageMapping: Record<string, string> = {
|
||||
phase_a_done: 'phase_b_running', // between phase_a and phase_b → show as phase_b active
|
||||
failed: 'completed', // terminal → all dots done up to failure point
|
||||
partial: 'completed',
|
||||
cancelled: 'completed',
|
||||
}
|
||||
|
||||
const currentStage = computed(() => {
|
||||
const raw = props.stage
|
||||
return stageMapping[raw] ?? raw
|
||||
})
|
||||
|
||||
// True when backend status indicates a terminal failure (dots should not pulse)
|
||||
const isTerminalFailure = computed(() =>
|
||||
props.status === 'failed' || props.status === 'partial' || props.status === 'cancelled'
|
||||
)
|
||||
|
||||
function stageIndex(key: string): number {
|
||||
return stageOrder.indexOf(key)
|
||||
@ -90,13 +108,20 @@ function stageIndex(key: string): number {
|
||||
function isStageComplete(key: string): boolean {
|
||||
const cur = stageIndex(currentStage.value)
|
||||
const target = stageIndex(key)
|
||||
if (cur < 0) return false
|
||||
return target < cur
|
||||
}
|
||||
|
||||
function dotClass(key: string) {
|
||||
const cur = stageIndex(currentStage.value)
|
||||
const target = stageIndex(key)
|
||||
if (props.status === 'failed' && key === currentStage.value) return 'failed'
|
||||
if (cur < 0) return 'pending' // unknown stage → all pending
|
||||
if (isTerminalFailure.value) {
|
||||
// Terminal failure: all dots before the failure point are done, the failure point is failed
|
||||
if (target < cur) return 'done'
|
||||
if (target === cur) return 'failed'
|
||||
return 'pending'
|
||||
}
|
||||
if (target < cur) return 'done'
|
||||
if (target === cur) return 'active'
|
||||
return 'pending'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user