diff --git a/mateclaw-ui/src/api/index.ts b/mateclaw-ui/src/api/index.ts
index 0f7076f4..b57354f1 100644
--- a/mateclaw-ui/src/api/index.ts
+++ b/mateclaw-ui/src/api/index.ts
@@ -758,7 +758,7 @@ export const wikiApi = {
http.put(`/wiki/knowledge-bases/${id}/config`, { content }),
// Directory Scan
- setSourceDirectory: (id: number, path: string) =>
+ setSourceDirectory: (id: string | number, path: string) =>
http.put(`/wiki/knowledge-bases/${id}/source-directory`, { path }),
scanDirectory: (id: number) => http.post(`/wiki/knowledge-bases/${id}/scan`),
@@ -903,6 +903,51 @@ export const wikiApi = {
http.post(`/wiki/transformations/runs/${runId}/save-as-page`),
cancelTransformationRun: (runId: number) =>
http.post(`/wiki/transformations/runs/${runId}/cancel`),
+
+ // ---- PageType Profile (REQ-1) ----
+ getPageTypeProfile: (kbId: string | number) =>
+ http.get(`/wiki/knowledge-bases/${kbId}/page-type-profile`),
+ savePageTypeProfile: (kbId: string | number, config: string, name?: string) =>
+ http.put(`/wiki/knowledge-bases/${kbId}/page-type-profile`, { config, name }),
+ validatePageTypeProfile: (kbId: string | number, config: string) =>
+ http.post(`/wiki/knowledge-bases/${kbId}/page-type-profile/validate`, { config }),
+ resetPageTypeProfile: (kbId: string | number) =>
+ http.post(`/wiki/knowledge-bases/${kbId}/page-type-profile/reset-default`),
+
+ // ---- Agent pageType permissions (REQ-3) ----
+ listPageTypePermissions: (kbId: string | number, agentId: string | number) =>
+ http.get(`/wiki/knowledge-bases/${kbId}/agents/${agentId}/page-type-permissions`),
+ savePageTypePermission: (kbId: string | number, agentId: string | number, row: {
+ pageType: string
+ canRead?: number
+ canCreate?: number
+ canUpdate?: number
+ canDelete?: number
+ writePolicy?: 'allow' | 'deny' | 'approval_required'
+ }) =>
+ http.post(`/wiki/knowledge-bases/${kbId}/agents/${agentId}/page-type-permissions`, row),
+ deletePageTypePermission: (kbId: string | number, agentId: string | number, id: string | number) =>
+ http.delete(`/wiki/knowledge-bases/${kbId}/agents/${agentId}/page-type-permissions/${id}`),
+
+ // ---- Source watcher (REQ-4) ----
+ getSourceWatcher: (kbId: string | number) =>
+ http.get(`/wiki/knowledge-bases/${kbId}/source-watcher`),
+ triggerSourceWatcher: (kbId: string | number) =>
+ http.post(`/wiki/knowledge-bases/${kbId}/source-watcher/scan`),
+
+ // ---- Pipelines (REQ-5) ----
+ listPipelines: (kbId: string | number) =>
+ http.get(`/wiki/knowledge-bases/${kbId}/pipelines`),
+ savePipeline: (kbId: string | number, config: string, format: 'yaml' | 'json' = 'yaml') =>
+ http.post(`/wiki/knowledge-bases/${kbId}/pipelines`, { config, format }),
+ validatePipeline: (kbId: string | number, config: string, format: 'yaml' | 'json' = 'yaml') =>
+ http.post(`/wiki/knowledge-bases/${kbId}/pipelines/validate`, { config, format }),
+ deletePipeline: (kbId: string | number, id: string | number) =>
+ http.delete(`/wiki/knowledge-bases/${kbId}/pipelines/${id}`),
+ listPipelineRuns: (kbId: string | number, id: string | number) =>
+ http.get(`/wiki/knowledge-bases/${kbId}/pipelines/${id}/runs`),
+ getPipelineRun: (kbId: string | number, runId: string | number) =>
+ http.get(`/wiki/knowledge-bases/${kbId}/pipeline-runs/${runId}`),
}
// ==================== Workspace (Team) ====================
diff --git a/mateclaw-ui/src/i18n/locales/en-US.ts b/mateclaw-ui/src/i18n/locales/en-US.ts
index f383e704..a9b92949 100644
--- a/mateclaw-ui/src/i18n/locales/en-US.ts
+++ b/mateclaw-ui/src/i18n/locales/en-US.ts
@@ -2113,6 +2113,81 @@ export default {
lastError: 'Last error',
footer: 'Toggling wiki.hot_cache.enabled off short-circuits the injection path. The "Rebuild now" button bypasses the debounce window for easier debugging.',
},
+ adv: {
+ tab: 'Advanced',
+ validate: 'Validate',
+ validOk: 'Validation passed',
+ profile: {
+ tab: 'Page Types',
+ title: 'Page Type Profile',
+ desc: 'Define this KB\'s page types, their knowledge layer, route/create/merge stage instructions and content templates. Injected into the ingest pipeline prompts.',
+ builtin: 'Built-in default',
+ placeholder: 'Paste the pageType profile JSON here…',
+ reset: 'Reset to default',
+ resetConfirm: 'Reset to the built-in default profile? Your custom config will be cleared.',
+ },
+ layers: {
+ tab: 'Layers & Stale',
+ title: 'Knowledge Layers & Stale State',
+ desc: 'When a fact-layer page is updated, experience pages that depend on it are marked stale to flag they need review.',
+ fact: 'Fact',
+ experience: 'Experience',
+ other: 'Other',
+ stale: 'Stale',
+ empty: 'This knowledge base has no pages yet.',
+ page: 'Page',
+ layer: 'Layer',
+ status: 'Status',
+ staleTag: 'Needs review',
+ fresh: 'Fresh',
+ },
+ perm: {
+ tab: 'Permissions',
+ title: 'Agent Page Type Permissions',
+ desc: 'Configure per-pageType read/create/update/delete access for an agent in this KB; writes can require approval. No rules = allow all; once any rule exists, uncovered types are fail-safe denied.',
+ agent: 'Agent',
+ selectAgent: 'Select an agent…',
+ pageType: 'Page Type',
+ policy: 'Write policy',
+ noRows: 'This agent has no permission rules (allow all).',
+ pageTypeHint: 'page type, * for default',
+ addRule: 'Add rule',
+ deleteConfirm: 'Delete the permission rule for "{type}"?',
+ },
+ watcher: {
+ tab: 'Watcher',
+ title: 'Source Directory Watcher',
+ desc: 'Bind a local directory as a knowledge source; the watcher scans for new/changed files at an interval and ingests them automatically. You can also trigger a one-off scan.',
+ enabled: 'Watcher enabled',
+ active: 'Active',
+ interval: 'Scan interval',
+ sourceType: 'Source type',
+ directory: 'Source directory',
+ dirHint: 'absolute path, e.g. /data/docs',
+ availableTypes: 'Available source types',
+ scanNow: 'Scan now',
+ scanDone: 'Scan complete',
+ scanResult: 'Scanned {scanned}, added {added}, skipped {skipped}, errors {errors}',
+ },
+ pipeline: {
+ tab: 'Pipelines',
+ title: 'Custom Pipelines',
+ desc: 'Define triggers (e.g. a page type reaching a count threshold, page created) and multi-step processing (llm / skill) in YAML. Once saved, the backend runs them on the trigger condition.',
+ name: 'Name',
+ trigger: 'Trigger',
+ enabled: 'Enabled',
+ runs: 'Runs',
+ empty: 'No pipeline definitions yet.',
+ editor: 'YAML definition',
+ editorHint: 'name: my-pipeline\ntrigger:\n type: page_type_count\n page_type: episode\n threshold: 10\nsteps:\n - executor: llm\n prompt: …',
+ saveDef: 'Save definition',
+ deleteConfirm: 'Delete pipeline "{name}"?',
+ runsFor: 'Runs for "{name}"',
+ status: 'Status',
+ startedAt: 'Started at',
+ noRuns: 'No runs yet.',
+ },
+ },
searchPages: 'Search pages...',
dropFiles: 'Drop files here or click to upload',
dropToUpload: 'Release to upload',
diff --git a/mateclaw-ui/src/i18n/locales/zh-CN.ts b/mateclaw-ui/src/i18n/locales/zh-CN.ts
index 4739f9d8..fde0e7d8 100644
--- a/mateclaw-ui/src/i18n/locales/zh-CN.ts
+++ b/mateclaw-ui/src/i18n/locales/zh-CN.ts
@@ -2125,6 +2125,81 @@ export default {
lastError: '最近一次错误',
footer: '关闭 wiki.hot_cache.enabled 后注入路径短路;该面板的"立即重建"会绕过去抖,方便调试。',
},
+ adv: {
+ tab: '高级管理',
+ validate: '校验',
+ validOk: '校验通过',
+ profile: {
+ tab: '页面类型',
+ title: '页面类型 Profile',
+ desc: '定义本知识库的页面类型(pageType)、所属知识分层、路由/创建/合并阶段指令与内容模板。会注入到摄取流水线的各阶段提示词。',
+ builtin: '内置默认',
+ placeholder: '在此粘贴 pageType profile 的 JSON…',
+ reset: '重置为默认',
+ resetConfirm: '确认重置为内置默认 profile?当前自定义配置会被清除。',
+ },
+ layers: {
+ tab: '分层 & 失效',
+ title: '知识分层与失效状态',
+ desc: 'fact(事实)层更新后会把依赖它的 experience(经验)页面标记为 stale,提示需要复核。',
+ fact: '事实层',
+ experience: '经验层',
+ other: '其它',
+ stale: '已失效',
+ empty: '该知识库暂无页面。',
+ page: '页面',
+ layer: '分层',
+ status: '状态',
+ staleTag: '待复核',
+ fresh: '正常',
+ },
+ perm: {
+ tab: '权限 & 审批',
+ title: 'Agent 页面类型权限',
+ desc: '为指定 Agent 配置在本知识库下按 pageType 的读/创建/更新/删除权限;写操作可设为需审批。无规则时默认放行,一旦存在规则则未覆盖的类型按失败安全(拒绝)处理。',
+ agent: 'Agent',
+ selectAgent: '选择一个 Agent…',
+ pageType: '页面类型',
+ policy: '写策略',
+ noRows: '该 Agent 暂无权限规则(默认放行)。',
+ pageTypeHint: '页面类型,* 表示默认',
+ addRule: '新增规则',
+ deleteConfirm: '删除 "{type}" 的权限规则?',
+ },
+ watcher: {
+ tab: '变更监测',
+ title: '源目录变更监测',
+ desc: '关联一个本地目录作为知识来源,监测器会按间隔扫描新增/变更文件并自动摄取。也可手动触发一次扫描。',
+ enabled: '监测开关',
+ active: '已激活',
+ interval: '扫描间隔',
+ sourceType: '源类型',
+ directory: '源目录',
+ dirHint: '绝对路径,例如 /data/docs',
+ availableTypes: '可用源类型',
+ scanNow: '立即扫描',
+ scanDone: '扫描完成',
+ scanResult: '扫描 {scanned} 个,新增 {added},跳过 {skipped},错误 {errors}',
+ },
+ pipeline: {
+ tab: '流水线',
+ title: '自定义流水线',
+ desc: '用 YAML 定义触发器(如某类型页面累计到阈值、页面创建)与多步处理(llm / skill)。保存后由后台按触发条件自动执行。',
+ name: '名称',
+ trigger: '触发器',
+ enabled: '启用',
+ runs: '运行记录',
+ empty: '暂无流水线定义。',
+ editor: 'YAML 定义',
+ editorHint: 'name: my-pipeline\ntrigger:\n type: page_type_count\n page_type: episode\n threshold: 10\nsteps:\n - executor: llm\n prompt: …',
+ saveDef: '保存定义',
+ deleteConfirm: '删除流水线 "{name}"?',
+ runsFor: '"{name}" 的运行记录',
+ status: '状态',
+ startedAt: '开始时间',
+ noRuns: '暂无运行记录。',
+ },
+ },
searchPages: '搜索页面...',
dropFiles: '拖拽文件到此处或点击上传',
dropToUpload: '松开即开始上传',
diff --git a/mateclaw-ui/src/views/Wiki/components/WikiAdvancedPanel.vue b/mateclaw-ui/src/views/Wiki/components/WikiAdvancedPanel.vue
new file mode 100644
index 00000000..eb8f9c6a
--- /dev/null
+++ b/mateclaw-ui/src/views/Wiki/components/WikiAdvancedPanel.vue
@@ -0,0 +1,507 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ layerGroups.fact.length }} {{ t('wiki.adv.layers.fact') }}
+
{{ layerGroups.experience.length }} {{ t('wiki.adv.layers.experience') }}
+
{{ layerGroups.other.length }} {{ t('wiki.adv.layers.other') }}
+
{{ staleCount }} {{ t('wiki.adv.layers.stale') }}
+
+ {{ t('wiki.adv.layers.empty') }}
+
+
+ | {{ t('wiki.adv.layers.page') }} |
+ {{ t('wiki.adv.layers.layer') }} |
+ {{ t('wiki.adv.layers.status') }} |
+
+
+
+ | {{ p.title || p.slug }} |
+ {{ p.knowledgeLayer || '—' }} |
+
+ {{ t('wiki.adv.layers.staleTag') }}
+ {{ t('wiki.adv.layers.fresh') }}
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ 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 }) }}
+
+
+
+
+
+
+
+
+ | {{ t('wiki.adv.pipeline.name') }} |
+ {{ t('wiki.adv.pipeline.trigger') }} |
+ {{ t('wiki.adv.pipeline.enabled') }} |
+ |
+
+
+
+ | {{ d.name }} |
+ {{ d.triggerType }} |
+ {{ d.enabled ? '✓' : '—' }} |
+
+
+
+ |
+
+ | {{ t('wiki.adv.pipeline.empty') }} |
+
+
+
+
+
+ {{ t('wiki.adv.pipeline.runsFor', { name: pipeline.runsFor.name }) }}
+
+
+
+ | # | {{ t('wiki.adv.pipeline.status') }} | {{ t('wiki.adv.pipeline.startedAt') }} |
+
+
+ | {{ r.id }} |
+ {{ r.status }} |
+ {{ r.createTime }} |
+
+ | {{ t('wiki.adv.pipeline.noRuns') }} |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mateclaw-ui/src/views/Wiki/components/WikiWorkspace.vue b/mateclaw-ui/src/views/Wiki/components/WikiWorkspace.vue
index f2385224..dcef5e98 100644
--- a/mateclaw-ui/src/views/Wiki/components/WikiWorkspace.vue
+++ b/mateclaw-ui/src/views/Wiki/components/WikiWorkspace.vue
@@ -55,6 +55,10 @@
+
+
+
+
@@ -72,6 +76,7 @@ import WikiConfig from './WikiConfig.vue'
import WikiGraphView from './WikiGraphView.vue'
import HotCachePanel from './HotCachePanel.vue'
import TransformationsPanel from './TransformationsPanel.vue'
+import WikiAdvancedPanel from './WikiAdvancedPanel.vue'
import WikiWorkspaceHeader from './WikiWorkspaceHeader.vue'
import WikiPageSidebar from './WikiPageSidebar.vue'
import WikiBrokenLinksBanner from './WikiBrokenLinksBanner.vue'
@@ -107,6 +112,7 @@ const tabs = computed(() => {
if (canManageWiki.value) {
list.push({ key: 'config', label: t('wiki.config') })
list.push({ key: 'transformations', label: t('wiki.transformations.tab') })
+ list.push({ key: 'advanced', label: t('wiki.adv.tab') })
}
list.push({ key: 'hotCache', label: t('wiki.hotCache.tab') })
return list