From 2a2f862257de2b0a9dbe32231fed44f000bc42ca Mon Sep 17 00:00:00 2001 From: matevip Date: Tue, 14 Apr 2026 18:56:45 +0800 Subject: [PATCH] feat(wiki): per-raw progress bar in raw-material card (RFC-012 M2 v2 UI) --- .../wiki/model/WikiRawMaterialEntity.java | 12 ++ .../wiki/service/WikiProcessingService.java | 50 +++++++ .../wiki/service/WikiRawMaterialService.java | 25 ++++ .../db/migration/h2/V8__wiki_raw_progress.sql | 6 + .../migration/mysql/V8__wiki_raw_progress.sql | 6 + mateclaw-ui/src/i18n/locales/en-US.ts | 3 + mateclaw-ui/src/i18n/locales/zh-CN.ts | 3 + mateclaw-ui/src/stores/useWikiStore.ts | 4 + .../Wiki/components/RawMaterialPanel.vue | 124 ++++++++++++++---- 9 files changed, 204 insertions(+), 29 deletions(-) create mode 100644 mateclaw-server/src/main/resources/db/migration/h2/V8__wiki_raw_progress.sql create mode 100644 mateclaw-server/src/main/resources/db/migration/mysql/V8__wiki_raw_progress.sql diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/model/WikiRawMaterialEntity.java b/mateclaw-server/src/main/java/vip/mate/wiki/model/WikiRawMaterialEntity.java index 5010712b..351d8c14 100644 --- a/mateclaw-server/src/main/java/vip/mate/wiki/model/WikiRawMaterialEntity.java +++ b/mateclaw-server/src/main/java/vip/mate/wiki/model/WikiRawMaterialEntity.java @@ -55,6 +55,18 @@ public class WikiRawMaterialEntity { /** 错误信息 */ private String errorMessage; + /** + * RFC-012 M2 v2 UI:当前处理阶段(null 未开始 / "route" / "phase-b" / "done")。 + * 供前端决定是否显示进度条以及显示"准备中"还是具体进度。 + */ + private String progressPhase; + + /** RFC-012 M2 v2 UI:本次处理计划的总页数(route 阶段确定后写入)。 */ + private Integer progressTotal; + + /** RFC-012 M2 v2 UI:已完成的页数(每个 phase B 页成功后 +1)。 */ + private Integer progressDone; + @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiProcessingService.java b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiProcessingService.java index 3dfb92a6..b4550487 100644 --- a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiProcessingService.java +++ b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiProcessingService.java @@ -22,9 +22,11 @@ import vip.mate.wiki.model.WikiRawMaterialEntity; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; /** @@ -50,6 +52,20 @@ public class WikiProcessingService { /** 并行 chunk / 材料处理执行器(JDK 21 虚拟线程);Listener 跨包需要引用,故 public */ public static final ExecutorService WIKI_EXECUTOR = Executors.newVirtualThreadPerTaskExecutor(); + /** + * RFC-012 M2 v2 UI v2:单 raw 的进度计数器,多个并行 chunk 的 {@code processChunkTwoPhase} + * 共享同一份 atomic 计数,避免 6 个 chunk 各写各的 progress 字段时互相覆盖(导致 UI 永远 preparing)。 + *

+ * 生命周期:{@code processRawMaterial} 入口 put,try/finally 出口 remove。 + */ + private static final class ProgressCounter { + final AtomicInteger total = new AtomicInteger(0); + final AtomicInteger done = new AtomicInteger(0); + final AtomicBoolean phaseBStarted = new AtomicBoolean(false); + } + + private final ConcurrentHashMap progressCounters = new ConcurrentHashMap<>(); + /** * 处理单个原始材料 */ @@ -93,6 +109,10 @@ public class WikiProcessingService { kbService.updateStatus(kb.getId(), "processing"); + // RFC-012 M2 v2 UI v2:为本次 raw 处理创建共享进度计数器(多 chunk 共享,避免 race) + progressCounters.put(rawId, new ProgressCounter()); + rawService.updateProgress(rawId, "route", 0, 0); // UI 立即看到 indeterminate 滑条 + try { // Phase 1: 获取文本内容 String textContent = rawService.getTextContent(raw); @@ -152,6 +172,12 @@ 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"); + } finally { + // RFC-012 M2 v2 UI v2:写入最终进度并清理共享计数器 + ProgressCounter pc = progressCounters.remove(rawId); + if (pc != null) { + rawService.updateProgress(rawId, "done", pc.done.get(), pc.total.get()); + } } } @@ -371,6 +397,10 @@ public class WikiProcessingService { String configContent = kb.getConfigContent() != null ? kb.getConfigContent() : ""; String rawTitle = raw.getTitle(); + // RFC-012 M2 v2 UI v2:取共享进度计数器(processRawMaterial 入口已 put)。 + // 多 chunk 并行时所有 chunk 共享同一份 atomic 计数,避免互相覆盖把 UI 拉回 preparing。 + ProgressCounter pc = progressCounters.get(rawId); + // ─── 阶段 A:路由 ─── String routeSystem = PromptLoader.loadPrompt("wiki/route-system"); String routeUserTemplate = PromptLoader.loadPrompt("wiki/route-user"); @@ -414,9 +444,19 @@ public class WikiProcessingService { if (!slug.isBlank()) updateSlugs.add(slug); } } + int totalPlanned = createMetas.size() + updateSlugs.size(); log.info("[Wiki] Route phase: kbId={}, rawId={}, planned create={}, planned update={}", kbId, rawId, createMetas.size(), updateSlugs.size()); + // RFC-012 M2 v2 UI v2:把本 chunk 的计划数累加到共享 total;切换到 phase-b(仅首次切换需 log) + if (pc != null) { + pc.total.addAndGet(totalPlanned); + if (pc.phaseBStarted.compareAndSet(false, true)) { + log.info("[Wiki] Progress: switching to phase-b for raw={}", rawId); + } + rawService.updateProgress(rawId, "phase-b", pc.done.get(), pc.total.get()); + } + // ─── 阶段 B-1:逐页 create(每页一次单独 LLM call,输入/输出都是单页规模) ─── for (JsonNode meta : createMetas) { try { @@ -427,6 +467,11 @@ public class WikiProcessingService { log.warn("[Wiki] Phase B create page slug='{}' failed: {}", meta.path("slug").asText(""), e.getMessage()); } + // 无论成功失败都推进 done 计数,避免失败页卡死 UI 进度 + if (pc != null) { + int d = pc.done.incrementAndGet(); + rawService.updateProgress(rawId, "phase-b", d, pc.total.get()); + } } // ─── 阶段 B-2:逐页 merge(每页一次单独 LLM call) ─── @@ -438,7 +483,12 @@ public class WikiProcessingService { } catch (RuntimeException e) { log.warn("[Wiki] Phase B merge page slug='{}' failed: {}", slug, e.getMessage()); } + if (pc != null) { + int d = pc.done.incrementAndGet(); + rawService.updateProgress(rawId, "phase-b", d, pc.total.get()); + } } + // 单 chunk 完成时不写"done"——多 chunk 还在跑;最终"done"由 processRawMaterial 的 finally 写入 log.info("[Wiki] Two-phase digest applied: kbId={}, rawId={}, created={}, updated={}", kbId, rawId, created, updated); diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiRawMaterialService.java b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiRawMaterialService.java index 274eaa18..063bc851 100644 --- a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiRawMaterialService.java +++ b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiRawMaterialService.java @@ -165,10 +165,35 @@ public class WikiRawMaterialService { } entity.setProcessingStatus("processing"); entity.setErrorMessage(null); + // RFC-012 M2 v2 UI:新一轮处理开始,清掉上次遗留的进度显示 + entity.setProgressPhase(null); + entity.setProgressTotal(0); + entity.setProgressDone(0); rawMapper.updateById(entity); return true; } + /** + * RFC-012 M2 v2 UI:更新 wiki 两阶段消化的进度字段。 + *

+ * 在 {@code WikiProcessingService.processChunkTwoPhase} 的四个节点被调用: + *

+ */ + @Transactional + public void updateProgress(Long id, String phase, int done, int total) { + WikiRawMaterialEntity entity = rawMapper.selectById(id); + if (entity == null) return; + entity.setProgressPhase(phase); + entity.setProgressDone(done); + entity.setProgressTotal(total); + rawMapper.updateById(entity); + } + @Transactional public void updateProcessingStatus(Long id, String status, String errorMessage) { WikiRawMaterialEntity entity = rawMapper.selectById(id); diff --git a/mateclaw-server/src/main/resources/db/migration/h2/V8__wiki_raw_progress.sql b/mateclaw-server/src/main/resources/db/migration/h2/V8__wiki_raw_progress.sql new file mode 100644 index 00000000..d5c583aa --- /dev/null +++ b/mateclaw-server/src/main/resources/db/migration/h2/V8__wiki_raw_progress.sql @@ -0,0 +1,6 @@ +-- V8: wiki raw material two-phase digest progress fields, for UI progress bar +-- RFC-012 M2 v2 UI follow-up: expose per-raw progress (current phase + pages done / total planned) +-- so the frontend can render a determinate progress bar instead of an opaque "处理中" badge. +ALTER TABLE mate_wiki_raw_material ADD COLUMN IF NOT EXISTS progress_phase VARCHAR(32) DEFAULT NULL; +ALTER TABLE mate_wiki_raw_material ADD COLUMN IF NOT EXISTS progress_total INT DEFAULT 0; +ALTER TABLE mate_wiki_raw_material ADD COLUMN IF NOT EXISTS progress_done INT DEFAULT 0; diff --git a/mateclaw-server/src/main/resources/db/migration/mysql/V8__wiki_raw_progress.sql b/mateclaw-server/src/main/resources/db/migration/mysql/V8__wiki_raw_progress.sql new file mode 100644 index 00000000..d5c583aa --- /dev/null +++ b/mateclaw-server/src/main/resources/db/migration/mysql/V8__wiki_raw_progress.sql @@ -0,0 +1,6 @@ +-- V8: wiki raw material two-phase digest progress fields, for UI progress bar +-- RFC-012 M2 v2 UI follow-up: expose per-raw progress (current phase + pages done / total planned) +-- so the frontend can render a determinate progress bar instead of an opaque "处理中" badge. +ALTER TABLE mate_wiki_raw_material ADD COLUMN IF NOT EXISTS progress_phase VARCHAR(32) DEFAULT NULL; +ALTER TABLE mate_wiki_raw_material ADD COLUMN IF NOT EXISTS progress_total INT DEFAULT 0; +ALTER TABLE mate_wiki_raw_material ADD COLUMN IF NOT EXISTS progress_done INT DEFAULT 0; diff --git a/mateclaw-ui/src/i18n/locales/en-US.ts b/mateclaw-ui/src/i18n/locales/en-US.ts index 12b84e6c..847f5479 100644 --- a/mateclaw-ui/src/i18n/locales/en-US.ts +++ b/mateclaw-ui/src/i18n/locales/en-US.ts @@ -1196,6 +1196,9 @@ export default { partial: 'PARTIAL', failed: 'FAILED', }, + progress: { + preparing: 'Preparing…', + }, }, cronJobs: { kicker: 'Automation', diff --git a/mateclaw-ui/src/i18n/locales/zh-CN.ts b/mateclaw-ui/src/i18n/locales/zh-CN.ts index 5c957f89..96b9adc6 100644 --- a/mateclaw-ui/src/i18n/locales/zh-CN.ts +++ b/mateclaw-ui/src/i18n/locales/zh-CN.ts @@ -1206,6 +1206,9 @@ export default { partial: '部分完成', failed: '失败', }, + progress: { + preparing: '准备中…', + }, }, cronJobs: { kicker: '自动执行', diff --git a/mateclaw-ui/src/stores/useWikiStore.ts b/mateclaw-ui/src/stores/useWikiStore.ts index bdd6e903..afd37f30 100644 --- a/mateclaw-ui/src/stores/useWikiStore.ts +++ b/mateclaw-ui/src/stores/useWikiStore.ts @@ -26,6 +26,10 @@ export interface WikiRawMaterial { lastProcessedAt: string | null errorMessage: string | null createTime: string + // RFC-012 M2 v2 UI:两阶段消化进度字段(后端在 route 后写 total,每页完成后 +1 done) + progressPhase: string | null + progressTotal: number + progressDone: number } export interface WikiPage { diff --git a/mateclaw-ui/src/views/Wiki/components/RawMaterialPanel.vue b/mateclaw-ui/src/views/Wiki/components/RawMaterialPanel.vue index 273ad122..e13ddc6a 100644 --- a/mateclaw-ui/src/views/Wiki/components/RawMaterialPanel.vue +++ b/mateclaw-ui/src/views/Wiki/components/RawMaterialPanel.vue @@ -56,35 +56,56 @@ {{ t('wiki.noRawMaterials') }}
-
- {{ raw.title }} - {{ raw.sourceType }} +
+
+ {{ raw.title }} + {{ raw.sourceType }} +
+
+ + {{ t(`wiki.status.${raw.processingStatus}`) }} + + + {{ raw.errorMessage }} + +
+
+ + +
-
- - {{ t(`wiki.status.${raw.processingStatus}`) }} +
+
+
+
+ + {{ raw.progressTotal + ? `${raw.progressDone} / ${raw.progressTotal}` + : t('wiki.progress.preparing') }} - - {{ raw.errorMessage }} - -
-
- -
@@ -122,7 +143,7 @@