From 18daad79b202d576dc3cfa1e92c40c3697b3b60a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=80=AA=E7=A8=8B=E4=BC=9F?= Date: Thu, 11 Jun 2026 09:29:26 +0800 Subject: [PATCH] feat(wiki): unify raw materials & source watcher into a Sources tab with per-KB auto-sync (#316) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(wiki): unify raw materials & source watcher into a Sources tab with per-KB auto-sync The raw-material directory scan and the Advanced "source watcher" sub-tab were the same engine (same kb.sourceDirectory, same WikiDirectoryScanService) split across two surfaces with two editable directory inputs. Merge them into one "Sources" tab (upload / paste / directory manual scan + auto-sync toggle + the raw-material list) and drop the watcher sub-tab from Advanced. Auto-sync is now per-KB opt-in: a new watcher_enabled column (V146) gates the periodic scan per knowledge base. The server-global mate.wiki.watcher-enabled stays as an ops master switch — a KB is auto-scanned only when both are on (AND). Manual scans are unaffected. Scan interval stays global for now (tracked separately). Closes matevip/mateclaw#314 * docs(wiki): document source-watcher global switch env vars Expose MATE_WIKI_WATCHER_ENABLED / MATE_WIKI_WATCHER_INTERVAL_MS as explicit placeholders in application-mysql.yml, .env.example and docker-compose.yml, mirroring MATE_WIKI_ALLOWED_SOURCE_ROOTS. Notes the AND semantics (global ops gate + per-KB toggle) so operators know the global switch alone is not sufficient. --- .env.example | 8 +++ docker-compose.yml | 5 ++ .../mate/wiki/controller/WikiController.java | 17 +++++ .../wiki/model/WikiKnowledgeBaseEntity.java | 7 ++ .../service/WikiKnowledgeBaseService.java | 11 +++ .../service/WikiSourceWatcherService.java | 6 ++ .../src/main/resources/application-mysql.yml | 7 ++ .../h2/V146__wiki_kb_watcher_enabled.sql | 9 +++ .../mysql/V146__wiki_kb_watcher_enabled.sql | 12 ++++ .../WikiSourceWatcherServiceE2ETest.java | 23 ++++++ mateclaw-ui/src/api/index.ts | 2 + mateclaw-ui/src/i18n/locales/en-US.ts | 7 ++ mateclaw-ui/src/i18n/locales/zh-CN.ts | 7 ++ .../Wiki/components/RawMaterialPanel.vue | 70 +++++++++++++++++++ .../Wiki/components/WikiAdvancedPanel.vue | 66 +---------------- .../views/Wiki/components/WikiWorkspace.vue | 2 +- 16 files changed, 195 insertions(+), 64 deletions(-) create mode 100644 mateclaw-server/src/main/resources/db/migration/h2/V146__wiki_kb_watcher_enabled.sql create mode 100644 mateclaw-server/src/main/resources/db/migration/mysql/V146__wiki_kb_watcher_enabled.sql diff --git a/.env.example b/.env.example index 80b70f27..3983a0dd 100644 --- a/.env.example +++ b/.env.example @@ -87,6 +87,14 @@ MATECLAW_OAUTH_OPENAI_CALLBACK_BIND_HOST= # - /your/host/path:/data/wiki MATE_WIKI_ALLOWED_SOURCE_ROOTS= +# ── Wiki 知识源自动同步(变更监测)总开关 ──────────────────────── +# 定时扫描各知识库的源目录、自动消化新文件。默认关闭,运维主动开启。 +# AND 语义:全局这个开关开 *且* 某知识库自己的「自动同步」开关也开, +# 该库才会被定时扫描;手动「立即扫描」不受此开关影响。 +# 间隔单位毫秒,默认 5 分钟(目前为全局,暂不支持按库配置)。 +MATE_WIKI_WATCHER_ENABLED=false +MATE_WIKI_WATCHER_INTERVAL_MS=300000 + # ── Skill 工作区目录 ───────────────────────────────────────────── # 已安装的 skill、运行时积累的 LESSONS.md、skill 运行产物都落在这个目录。 # 默认(容器内)已指向 /app/data/skills,由 docker-compose 的 server_data 卷 diff --git a/docker-compose.yml b/docker-compose.yml index 060cc464..36fe8052 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -100,6 +100,11 @@ services: # 示例:MATE_WIKI_ALLOWED_SOURCE_ROOTS=/data/wiki,/opt/docs # 记得同步在 volumes 里把宿主机路径挂进容器。 MATE_WIKI_ALLOWED_SOURCE_ROOTS: ${MATE_WIKI_ALLOWED_SOURCE_ROOTS:-} + # Wiki 知识源自动同步总开关(运维总闸,默认关)。AND 语义:全局开关与 + # 每个知识库自己的「自动同步」开关都开,该库才会被定时扫描。 + # 间隔单位毫秒,默认 5 分钟。 + MATE_WIKI_WATCHER_ENABLED: ${MATE_WIKI_WATCHER_ENABLED:-false} + MATE_WIKI_WATCHER_INTERVAL_MS: ${MATE_WIKI_WATCHER_INTERVAL_MS:-300000} # Skill 工作区根目录。放在 /app/data 下,让现有的 server_data 卷一并持久化 # 已安装的 skill、运行时积累的 LESSONS.md 以及 skill 运行产物,容器重启不丢。 # 内置 skill 仍由 JAR classpath 每次启动现场释放,空卷不会丢内置文件。 diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/controller/WikiController.java b/mateclaw-server/src/main/java/vip/mate/wiki/controller/WikiController.java index 24f04666..86ee37a6 100644 --- a/mateclaw-server/src/main/java/vip/mate/wiki/controller/WikiController.java +++ b/mateclaw-server/src/main/java/vip/mate/wiki/controller/WikiController.java @@ -345,7 +345,10 @@ public class WikiController { if (kb == null) return R.fail(404, "Knowledge base not found"); vip.mate.wiki.source.WikiIngestSourceProvider provider = sourceWatcherService.providerFor(kb); Map out = new LinkedHashMap<>(); + // Global master switch (ops): gates the scheduler at all. out.put("watcherEnabled", properties.isWatcherEnabled()); + // Per-KB opt-in: auto-sync runs only when both are true (AND semantics). + out.put("kbWatcherEnabled", kb.getWatcherEnabled() != null && kb.getWatcherEnabled() == 1); out.put("intervalMs", properties.getWatcherIntervalMs()); out.put("sourceDirectory", kb.getSourceDirectory()); out.put("sourceType", provider != null ? provider.sourceType() : null); @@ -354,6 +357,20 @@ public class WikiController { return R.ok(out); } + @RequireWorkspaceRole("member") + @Operation(summary = "开关知识库的自动同步(每库)") + @PutMapping("/knowledge-bases/{id}/source-watcher/enabled") + public R setWatcherEnabled(@PathVariable Long id, @RequestBody Map body, + @RequestHeader(value = "X-Workspace-Id", required = false) Long workspaceId) { + verifyKBWorkspace(id, workspaceId); + WikiKnowledgeBaseEntity kb = kbService.getById(id); + if (kb == null) return R.fail(404, "Knowledge base not found"); + Object v = body.get("enabled"); + boolean enabled = (v instanceof Boolean b) ? b : Boolean.parseBoolean(String.valueOf(v)); + kbService.updateWatcherEnabled(id, enabled); + return R.ok(); + } + @RequireWorkspaceRole("member") @Operation(summary = "手动触发一次源监听扫描") @PostMapping("/knowledge-bases/{id}/source-watcher/scan") diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/model/WikiKnowledgeBaseEntity.java b/mateclaw-server/src/main/java/vip/mate/wiki/model/WikiKnowledgeBaseEntity.java index 3ced4646..756d5229 100644 --- a/mateclaw-server/src/main/java/vip/mate/wiki/model/WikiKnowledgeBaseEntity.java +++ b/mateclaw-server/src/main/java/vip/mate/wiki/model/WikiKnowledgeBaseEntity.java @@ -33,6 +33,13 @@ public class WikiKnowledgeBaseEntity { /** 关联的本地目录路径(可选,用于批量扫描导入) */ private String sourceDirectory; + /** + * 是否对该知识库启用自动同步(周期扫描 sourceDirectory)。1=开,0=关。 + * 自动扫描需"全局总闸 mate.wiki.watcher-enabled 开 且 本字段为 1"(AND 语义); + * 手动扫描不受此字段影响。 + */ + private Integer watcherEnabled; + /** 状态:active / processing / error */ private String status; diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiKnowledgeBaseService.java b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiKnowledgeBaseService.java index 74bc106b..0e4e34a2 100644 --- a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiKnowledgeBaseService.java +++ b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiKnowledgeBaseService.java @@ -383,6 +383,17 @@ public class WikiKnowledgeBaseService { kbMapper.updateById(entity); } + /** Toggle per-KB auto-sync (the periodic source-watcher scan). */ + @Transactional + public void updateWatcherEnabled(Long id, boolean enabled) { + WikiKnowledgeBaseEntity entity = kbMapper.selectById(id); + if (entity == null) { + throw new IllegalArgumentException("Knowledge base not found: " + id); + } + entity.setWatcherEnabled(enabled ? 1 : 0); + kbMapper.updateById(entity); + } + @Transactional public void decrementRawCount(Long kbId) { WikiKnowledgeBaseEntity entity = kbMapper.selectById(kbId); diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiSourceWatcherService.java b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiSourceWatcherService.java index 2101a130..52ac3355 100644 --- a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiSourceWatcherService.java +++ b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiSourceWatcherService.java @@ -63,6 +63,12 @@ public class WikiSourceWatcherService { public int runScanCycle() { int totalAdded = 0; for (WikiKnowledgeBaseEntity kb : kbService.listAll()) { + // Per-KB opt-in: the global master switch (checked in scheduledScan) + // gates the scheduler at all; this flag gates each KB. AND semantics — + // a KB is auto-scanned only when both are on. Manual scans bypass this. + if (kb.getWatcherEnabled() == null || kb.getWatcherEnabled() != 1) { + continue; + } vip.mate.wiki.source.WikiIngestSourceProvider provider = providerFor(kb); if (provider == null) { continue; diff --git a/mateclaw-server/src/main/resources/application-mysql.yml b/mateclaw-server/src/main/resources/application-mysql.yml index aaad80cc..134f5333 100644 --- a/mateclaw-server/src/main/resources/application-mysql.yml +++ b/mateclaw-server/src/main/resources/application-mysql.yml @@ -33,7 +33,14 @@ spring: # Set MATE_WIKI_ALLOWED_SOURCE_ROOTS in .env (comma-separated paths): # MATE_WIKI_ALLOWED_SOURCE_ROOTS=/data/wiki,/opt/docs # The default profile (H2 / desktop / single-tenant) leaves this off. +# Source watcher master switch (ops gate). Off by default; operators opt in. +# AND semantics: a KB is auto-scanned only when this global switch AND that +# KB's own auto-sync toggle are both on. Manual scans are unaffected. +# MATE_WIKI_WATCHER_ENABLED=true +# MATE_WIKI_WATCHER_INTERVAL_MS=300000 # scan interval, default 5 min mate: wiki: require-allowed-roots: true allowed-source-roots: ${MATE_WIKI_ALLOWED_SOURCE_ROOTS:} + watcher-enabled: ${MATE_WIKI_WATCHER_ENABLED:false} + watcher-interval-ms: ${MATE_WIKI_WATCHER_INTERVAL_MS:300000} diff --git a/mateclaw-server/src/main/resources/db/migration/h2/V146__wiki_kb_watcher_enabled.sql b/mateclaw-server/src/main/resources/db/migration/h2/V146__wiki_kb_watcher_enabled.sql new file mode 100644 index 00000000..bd9a1e36 --- /dev/null +++ b/mateclaw-server/src/main/resources/db/migration/h2/V146__wiki_kb_watcher_enabled.sql @@ -0,0 +1,9 @@ +-- Per-KB source-watcher toggle. Auto-sync (the periodic directory scan) was +-- previously gated only by the server-global `mate.wiki.watcher-enabled`. +-- This column makes the auto-sync opt-in per knowledge base: a KB is scanned +-- automatically only when the global master switch is on AND this flag is set +-- (AND semantics). Manual "scan now" is unaffected by this flag. Defaults to 0 +-- so existing KBs are not auto-scanned until explicitly enabled. + +ALTER TABLE mate_wiki_knowledge_base + ADD COLUMN IF NOT EXISTS watcher_enabled TINYINT(1) NOT NULL DEFAULT 0; diff --git a/mateclaw-server/src/main/resources/db/migration/mysql/V146__wiki_kb_watcher_enabled.sql b/mateclaw-server/src/main/resources/db/migration/mysql/V146__wiki_kb_watcher_enabled.sql new file mode 100644 index 00000000..d0c31420 --- /dev/null +++ b/mateclaw-server/src/main/resources/db/migration/mysql/V146__wiki_kb_watcher_enabled.sql @@ -0,0 +1,12 @@ +-- Per-KB source-watcher toggle. See the h2 sibling for the prose explanation. +-- MySQL lacks `ADD COLUMN IF NOT EXISTS`, so the column is guarded by an +-- INFORMATION_SCHEMA check + prepared statement (idempotent). + +SET @c := (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'mate_wiki_knowledge_base' + AND COLUMN_NAME = 'watcher_enabled'); +SET @s := IF(@c = 0, + 'ALTER TABLE mate_wiki_knowledge_base ADD COLUMN watcher_enabled TINYINT(1) NOT NULL DEFAULT 0', + 'SELECT 1'); +PREPARE stmt FROM @s; EXECUTE stmt; DEALLOCATE PREPARE stmt; diff --git a/mateclaw-server/src/test/java/vip/mate/wiki/service/WikiSourceWatcherServiceE2ETest.java b/mateclaw-server/src/test/java/vip/mate/wiki/service/WikiSourceWatcherServiceE2ETest.java index d669117b..bf7c99f7 100644 --- a/mateclaw-server/src/test/java/vip/mate/wiki/service/WikiSourceWatcherServiceE2ETest.java +++ b/mateclaw-server/src/test/java/vip/mate/wiki/service/WikiSourceWatcherServiceE2ETest.java @@ -35,6 +35,8 @@ class WikiSourceWatcherServiceE2ETest { private WikiKnowledgeBaseService kbService; @Autowired private WikiDirectoryScanService scanService; + @Autowired + private WikiRawMaterialService rawMaterialService; private static final java.util.concurrent.atomic.AtomicLong SEQ = new java.util.concurrent.atomic.AtomicLong(System.nanoTime()); @@ -47,6 +49,8 @@ class WikiSourceWatcherServiceE2ETest { WikiKnowledgeBaseEntity kb = kbService.create( "watcher-" + SEQ.incrementAndGet(), "test", null); kbService.updateSourceDirectory(kb.getId(), sourceDir.toString()); + // Auto-sync is per-KB opt-in; enable it so the cycle scans this KB. + kbService.updateWatcherEnabled(kb.getId(), true); // First cycle ingests both new files. int firstAdded = watcherService.runScanCycle(); @@ -109,4 +113,23 @@ class WikiSourceWatcherServiceE2ETest { // Should complete without throwing (count is non-negative). assertTrue(watcherService.runScanCycle() >= 0); } + + @Test + void disabledKb_isNotAutoScanned_untilEnabled(@TempDir Path sourceDir) throws IOException { + Files.writeString(sourceDir.resolve("note.md"), "# Note\n\ncontent"); + WikiKnowledgeBaseEntity kb = kbService.create( + "watcher-off-" + SEQ.incrementAndGet(), "test", null); + kbService.updateSourceDirectory(kb.getId(), sourceDir.toString()); + // watcher_enabled defaults to 0 → the auto cycle must skip this KB. + + watcherService.runScanCycle(); + assertEquals(0, rawMaterialService.listByKbId(kb.getId()).size(), + "a KB with auto-sync disabled must not be auto-scanned"); + + // Once enabled, the same cycle ingests its file. + kbService.updateWatcherEnabled(kb.getId(), true); + watcherService.runScanCycle(); + assertTrue(rawMaterialService.listByKbId(kb.getId()).size() >= 1, + "after enabling auto-sync the KB's file should be ingested"); + } } diff --git a/mateclaw-ui/src/api/index.ts b/mateclaw-ui/src/api/index.ts index c8e8d1d1..fe56f934 100644 --- a/mateclaw-ui/src/api/index.ts +++ b/mateclaw-ui/src/api/index.ts @@ -946,6 +946,8 @@ export const wikiApi = { http.get(`/wiki/knowledge-bases/${kbId}/source-watcher`), triggerSourceWatcher: (kbId: string | number) => http.post(`/wiki/knowledge-bases/${kbId}/source-watcher/scan`), + setWatcherEnabled: (kbId: string | number, enabled: boolean) => + http.put(`/wiki/knowledge-bases/${kbId}/source-watcher/enabled`, { enabled }), // ---- Pipelines (REQ-5) ---- listPipelines: (kbId: string | number) => diff --git a/mateclaw-ui/src/i18n/locales/en-US.ts b/mateclaw-ui/src/i18n/locales/en-US.ts index c5dd725c..e180df62 100644 --- a/mateclaw-ui/src/i18n/locales/en-US.ts +++ b/mateclaw-ui/src/i18n/locales/en-US.ts @@ -2091,6 +2091,13 @@ export default { kbDescription: 'Description', kbDescPlaceholder: 'Briefly describe the knowledge base purpose', rawMaterials: 'Raw Materials', + sources: { + tab: 'Sources', + autoSync: 'Auto-sync', + autoSyncInterval: 'every {sec}s', + autoSyncGlobalOffHint: 'Auto-sync needs the global switch enabled by ops', + toggleFailed: 'Failed to toggle auto-sync', + }, pages: 'Wiki Pages', config: 'Config', transformations: { diff --git a/mateclaw-ui/src/i18n/locales/zh-CN.ts b/mateclaw-ui/src/i18n/locales/zh-CN.ts index 5d308c42..9536ecce 100644 --- a/mateclaw-ui/src/i18n/locales/zh-CN.ts +++ b/mateclaw-ui/src/i18n/locales/zh-CN.ts @@ -2103,6 +2103,13 @@ export default { kbDescription: '描述', kbDescPlaceholder: '简要描述知识库用途', rawMaterials: '原始材料', + sources: { + tab: '来源', + autoSync: '自动同步', + autoSyncInterval: '每 {sec} 秒', + autoSyncGlobalOffHint: '自动同步需运维开启全局开关', + toggleFailed: '切换自动同步失败', + }, pages: 'Wiki 页面', config: '处理配置', transformations: { diff --git a/mateclaw-ui/src/views/Wiki/components/RawMaterialPanel.vue b/mateclaw-ui/src/views/Wiki/components/RawMaterialPanel.vue index 2fc90e07..2dba1300 100644 --- a/mateclaw-ui/src/views/Wiki/components/RawMaterialPanel.vue +++ b/mateclaw-ui/src/views/Wiki/components/RawMaterialPanel.vue @@ -72,6 +72,26 @@ + +
+ + + {{ t('wiki.sources.autoSyncInterval', { sec: Math.round(watcher.intervalMs / 1000) }) }} + + + + {{ t('wiki.sources.autoSyncGlobalOffHint') }} + +
+

@@ -502,6 +522,48 @@ const dirPath = ref(store.currentKB?.sourceDirectory || '') const scanning = ref(false) const scanResult = ref<{ scanned: number; added: number; skipped: number; errors?: string[] } | null>(null) +// ─── Per-KB auto-sync (source watcher) ──────────────────────────────────────── +// Auto-sync periodically scans the directory above. It runs only when the +// server-global master switch (watcher.globalEnabled, ops-controlled) AND this +// KB's toggle (watcher.kbEnabled) are both on. When the global switch is off the +// toggle is disabled with a hint — there's nothing a non-ops user can do here. +const watcher = reactive({ + globalEnabled: false, + kbEnabled: false, + intervalMs: 0, + sourceType: null as string | null, + busy: false, +}) +async function loadWatcher(kbId: number) { + try { + const res: any = await wikiApi.getSourceWatcher(kbId) + const d = res?.data ?? res + watcher.globalEnabled = !!d.watcherEnabled + watcher.kbEnabled = !!d.kbWatcherEnabled + watcher.intervalMs = d.intervalMs || 0 + watcher.sourceType = d.sourceType || null + } catch { /* leave defaults; auto-sync UI just shows disabled */ } +} +async function toggleWatcher(next: boolean) { + if (!store.currentKB) return + watcher.busy = true + try { + await wikiApi.setWatcherEnabled(store.currentKB.id, next) + watcher.kbEnabled = next + mcToast.success(t('common.saved')) + } catch (e: any) { + mcToast.error(e?.response?.data?.message || t('wiki.sources.toggleFailed')) + } finally { + watcher.busy = false + } +} +watch(() => store.currentKB?.id, (id) => { + if (id) { + dirPath.value = store.currentKB?.sourceDirectory || '' + void loadWatcher(id as number) + } +}, { immediate: true }) + // ─── Drag-over state ────────────────────────────────────────────────────────── const { isDragging, onDragEnter, onDragLeave, onDrop: handleDrop } = useFileDrop(uploadDroppedFiles) @@ -707,6 +769,14 @@ async function handleScanDir() { /* Directory scan */ .dir-scan-row { display: flex; gap: 10px; align-items: center; } + +/* Auto-sync (per-KB source watcher) */ +.auto-sync-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: -4px; } +.auto-sync-toggle { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--mc-text-secondary); cursor: pointer; } +.auto-sync-toggle.disabled { opacity: 0.55; cursor: not-allowed; } +.auto-sync-toggle input { cursor: inherit; } +.auto-sync-meta { font-size: 12px; color: var(--mc-text-tertiary); font-variant-numeric: tabular-nums; } +.auto-sync-hint { font-size: 12px; color: var(--mc-text-tertiary); } .dir-input-wrap { flex: 1; display: flex; align-items: center; gap: 8px; padding: 8px 12px; border: 1px solid var(--mc-border); border-radius: 12px; background: var(--mc-bg-elevated); color: var(--mc-text-tertiary); } .dir-input-wrap:focus-within { border-color: var(--mc-primary); box-shadow: 0 0 0 2px rgba(217,119,87,0.1); } .dir-input { flex: 1; border: none; background: transparent; font-size: 13px; color: var(--mc-text-primary); outline: none; } diff --git a/mateclaw-ui/src/views/Wiki/components/WikiAdvancedPanel.vue b/mateclaw-ui/src/views/Wiki/components/WikiAdvancedPanel.vue index 8f230891..f6cd7120 100644 --- a/mateclaw-ui/src/views/Wiki/components/WikiAdvancedPanel.vue +++ b/mateclaw-ui/src/views/Wiki/components/WikiAdvancedPanel.vue @@ -107,41 +107,7 @@ - -
-
-
-

{{ t('wiki.adv.watcher.title') }}

-

{{ t('wiki.adv.watcher.desc') }}

-
- -
-
-
{{ t('wiki.adv.watcher.enabled') }}{{ watcher.data.watcherEnabled ? t('common.yes') : t('common.no') }}
-
{{ t('wiki.adv.watcher.active') }}{{ watcher.data.active ? t('common.yes') : t('common.no') }}
-
{{ t('wiki.adv.watcher.interval') }}{{ watcher.data.intervalMs ? (watcher.data.intervalMs / 1000) + 's' : '—' }}
-
{{ t('wiki.adv.watcher.sourceType') }}{{ watcher.data.sourceType || '—' }}
-
- - -
- -
-

- {{ t('wiki.adv.watcher.availableTypes') }}: {{ watcher.data.availableSourceTypes.join(', ') }} -

-
- -
-
- {{ t('wiki.adv.watcher.scanResult', { scanned: watcher.lastScan.scanned, added: watcher.lastScan.added, skipped: watcher.lastScan.skipped, errors: watcher.lastScan.errors }) }} -
-
+
@@ -227,12 +193,11 @@ const agentStore = useAgentStore() const kbId = computed(() => (store.currentKB ? String(store.currentKB.id) : '')) const agents = computed(() => agentStore.agents) -const section = ref<'profile' | 'layers' | 'permissions' | 'watcher' | 'pipeline'>('profile') +const section = ref<'profile' | 'layers' | 'permissions' | 'pipeline'>('profile') const sections = computed(() => [ { key: 'profile' as const, label: t('wiki.adv.profile.tab') }, { key: 'layers' as const, label: t('wiki.adv.layers.tab') }, { key: 'permissions' as const, label: t('wiki.adv.perm.tab') }, - { key: 'watcher' as const, label: t('wiki.adv.watcher.tab') }, { key: 'pipeline' as const, label: t('wiki.adv.pipeline.tab') }, ]) @@ -243,7 +208,6 @@ function switchSection(key: typeof section.value) { loaded[key] = true if (key === 'layers') loadLayers() else if (key === 'permissions') { if (agents.value.length === 0) agentStore.fetchAgents() } - else if (key === 'watcher') loadWatcher() else if (key === 'pipeline') loadPipelines() } @@ -315,31 +279,7 @@ async function deletePermission(row: any) { function flag(v: any) { return v ? '✓' : '·' } function policyClass(p?: string) { return p === 'allow' ? 'badge-ok' : p === 'deny' ? 'badge-warn' : 'badge-muted' } -// ---- REQ-4 Watcher ---- -const watcher = reactive({ data: {} as any, dir: '', lastScan: null as any, busy: false }) -async function loadWatcher() { - if (!kbId.value) return - watcher.busy = true - try { - watcher.data = unwrap(await wikiApi.getSourceWatcher(kbId.value)) || {} - watcher.dir = watcher.data.sourceDirectory || '' - } catch (e: any) { mcToast.error(errMsg(e, 'Load watcher failed')) } finally { watcher.busy = false } -} -async function saveDirectory() { - watcher.busy = true - try { - await wikiApi.setSourceDirectory(kbId.value, watcher.dir) - mcToast.success(t('common.saved')) - await loadWatcher() - } catch (e: any) { mcToast.error(errMsg(e, 'Save failed')) } finally { watcher.busy = false } -} -async function triggerScan() { - watcher.busy = true - try { - watcher.lastScan = unwrap(await wikiApi.triggerSourceWatcher(kbId.value)) - mcToast.success(t('wiki.adv.watcher.scanDone')) - } catch (e: any) { mcToast.error(errMsg(e, 'Scan failed')) } finally { watcher.busy = false } -} +// REQ-4 Source-watcher moved to the unified Sources tab (RawMaterialPanel). // ---- REQ-5 Pipeline ---- const pipeline = reactive({ defs: [] as any[], config: '', issues: [] as string[], runsFor: null as any, runs: [] as any[], busy: false }) diff --git a/mateclaw-ui/src/views/Wiki/components/WikiWorkspace.vue b/mateclaw-ui/src/views/Wiki/components/WikiWorkspace.vue index 1155d051..e5d9be72 100644 --- a/mateclaw-ui/src/views/Wiki/components/WikiWorkspace.vue +++ b/mateclaw-ui/src/views/Wiki/components/WikiWorkspace.vue @@ -147,7 +147,7 @@ watch(() => store.currentPage, (page) => { const tabs = computed<{ key: WikiTab; label: string }[]>(() => { if (!canManageWiki.value) return [] return [ - { key: 'raw', label: t('wiki.rawMaterials') }, + { key: 'raw', label: t('wiki.sources.tab') }, { key: 'config', label: t('wiki.config') }, { key: 'transformations', label: t('wiki.transformations.tab') }, { key: 'advanced', label: t('wiki.adv.tab') },