mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-16 04:18:17 +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) {
|
} else if (newStage == WikiJobStage.COMPLETED) {
|
||||||
job.setFinishedAt(LocalDateTime.now());
|
job.setFinishedAt(LocalDateTime.now());
|
||||||
job.setStatus(WikiJobStatus.COMPLETED.name().toLowerCase());
|
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);
|
jobMapper.updateById(job);
|
||||||
return job;
|
return job;
|
||||||
|
|||||||
@ -137,10 +137,13 @@ public class WikiProcessingService {
|
|||||||
|
|
||||||
kbService.updateStatus(kb.getId(), "processing");
|
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) {
|
if (wikiJobService != null) {
|
||||||
try {
|
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) {
|
} catch (Exception e) {
|
||||||
log.warn("[Wiki] Failed to create heavy ingest job record for raw={}: {}", rawId, e.getMessage());
|
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 共用)
|
// Phase 3: 构建已有页面索引(一次构建,所有 chunk 共用)
|
||||||
String existingPagesIndex = buildExistingPagesIndex(kb.getId());
|
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 消化
|
// Phase 3: LLM 消化
|
||||||
// result[0] = totalPages, result[1] = failedChunks, result[2] = totalChunks
|
// result[0] = totalPages, result[1] = failedChunks, result[2] = totalChunks
|
||||||
int[] result;
|
int[] result;
|
||||||
@ -256,6 +264,18 @@ public class WikiProcessingService {
|
|||||||
"kbPageCount", pageCount));
|
"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={}",
|
log.info("[Wiki] Processing completed for raw={}, kbId={}, generatedPages={}, totalPages={}",
|
||||||
rawId, kb.getId(), totalPages, pageCount);
|
rawId, kb.getId(), totalPages, pageCount);
|
||||||
|
|
||||||
@ -282,6 +302,10 @@ public class WikiProcessingService {
|
|||||||
log.error("[Wiki] Processing failed for raw={}: {}", rawId, e.getMessage(), e);
|
log.error("[Wiki] Processing failed for raw={}: {}", rawId, e.getMessage(), e);
|
||||||
rawService.updateProcessingStatus(rawId, "failed", e.getMessage());
|
rawService.updateProcessingStatus(rawId, "failed", e.getMessage());
|
||||||
kbService.updateStatus(kb.getId(), "active");
|
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:广播异常终态
|
// RFC-012 M3:广播异常终态
|
||||||
progressBus.broadcast(kb.getId(), WikiProgressBus.EVENT_RAW_FAILED,
|
progressBus.broadcast(kb.getId(), WikiProgressBus.EVENT_RAW_FAILED,
|
||||||
java.util.Map.of("rawId", rawId, "error", e.getMessage() == null ? "unknown" : e.getMessage()));
|
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 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 {
|
function stageIndex(key: string): number {
|
||||||
return stageOrder.indexOf(key)
|
return stageOrder.indexOf(key)
|
||||||
@ -90,13 +108,20 @@ function stageIndex(key: string): number {
|
|||||||
function isStageComplete(key: string): boolean {
|
function isStageComplete(key: string): boolean {
|
||||||
const cur = stageIndex(currentStage.value)
|
const cur = stageIndex(currentStage.value)
|
||||||
const target = stageIndex(key)
|
const target = stageIndex(key)
|
||||||
|
if (cur < 0) return false
|
||||||
return target < cur
|
return target < cur
|
||||||
}
|
}
|
||||||
|
|
||||||
function dotClass(key: string) {
|
function dotClass(key: string) {
|
||||||
const cur = stageIndex(currentStage.value)
|
const cur = stageIndex(currentStage.value)
|
||||||
const target = stageIndex(key)
|
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 'done'
|
||||||
if (target === cur) return 'active'
|
if (target === cur) return 'active'
|
||||||
return 'pending'
|
return 'pending'
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user