diff --git a/mateclaw-ui/src/i18n/locales/en-US.ts b/mateclaw-ui/src/i18n/locales/en-US.ts index 9360cb81..c5dd725c 100644 --- a/mateclaw-ui/src/i18n/locales/en-US.ts +++ b/mateclaw-ui/src/i18n/locales/en-US.ts @@ -2206,6 +2206,60 @@ export 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.', + formMode: 'Form', + jsonMode: 'JSON', + fallbackType: 'Fallback type', + allowAdditionalFields: 'Keep undeclared fields', + versionLabel: 'Version', + addType: 'Add type', + removeType: 'Remove type', + removeTypeConfirm: 'Remove page type "{type}"?', + noTypes: 'No page types yet', + selectType: 'Select or create a page type on the left', + typeKey: 'Type key', + typeKeyHint: 'lowercase, unique, only letters/digits/_/-', + copyFrom: 'Copy from existing type', + copyFromNone: 'Don\'t copy (blank)', + moveUp: 'Move up', + moveDown: 'Move down', + label: 'Label', + description: 'Description', + layer: 'Knowledge layer', + layerFact: 'Fact', + layerExperience: 'Experience', + layerUnset: 'Unspecified', + fields: 'Fields', + fieldName: 'Field name', + fieldType: 'Type', + fieldRequired: 'Required', + enumValues: 'Enum values', + enumHint: 'comma-separated', + addField: 'Add field', + noFields: 'No fields yet', + advanced: 'Advanced (stage prompts / template)', + advancedIntro: 'These prompts are injected into the matching ingest-pipeline stage, appended after the system default; leave blank to use the default. Three stages: route = decide whether content belongs to this type, create = author a new page of this type, merge = fold new content into an existing page.', + stageRoute: 'Route stage instructions', + routeHint: 'Routing: extra guidance for the model when deciding whether and how a piece of raw content belongs to this page type.', + routeExample: 'e.g. Only route here when the content describes a specific person\'s life or role; passing mentions don\'t count.', + stageCreate: 'Create stage instructions', + createHint: 'Authoring: structure and style for the first-time generation of a page of this type (sections, tone, required elements).', + createExample: 'e.g. Write in third person; open with a one-sentence definition; then sections Background / Key attributes / Relations.', + stageCreateTemplate: 'Create template key', + createTemplateHint: 'The template key referenced at create time (matches "Template key" below); leave blank to use no template.', + createTemplateExample: 'e.g. concept-tpl', + stageMerge: 'Merge stage instructions', + mergeHint: 'Merging: rules for folding new content into an existing page (how to dedupe, which side wins on conflict).', + mergeExample: 'e.g. Keep the more recent version; on conflict annotate source and time rather than overwriting.', + templateKey: 'Template key', + templateKeyHint: 'Template identifier, referenced by the create stage via "Create template key" above.', + templateMarkdown: 'Template Markdown', + templateMarkdownHint: 'Body skeleton handed to the model: write the headings/sections you want. The create stage injects it verbatim into the prompt and the model fills in the prose. It is a structural example — there is no variable substitution, so parenthetical notes are hints to the model, not auto-filled field values.', + templateMarkdownExample: '## Overview\n(one-sentence definition of this entry)\n\n## Key attributes\n- \n\n## Relations\n- link related entries with [[wiki links]]', + errKeyFormat: 'Type key "{key}" is invalid: must be lowercase, non-empty, only letters/digits/_/-', + errKeyDup: 'Duplicate type key "{key}"', + errFieldDup: 'Duplicate field "{field}" in type "{type}"', + warnFallbackMissing: 'Fallback type "{type}" is not among the defined types', + errJsonParse: 'JSON parse failed — fix it before switching to the form', }, reclassify: { desc: 'Re-classify existing pages against the current profile: only the page type is updated, the body is untouched. Use it after editing the profile (adding/removing types) to migrate old pages to the new categories.', diff --git a/mateclaw-ui/src/i18n/locales/zh-CN.ts b/mateclaw-ui/src/i18n/locales/zh-CN.ts index 2117f14f..5d308c42 100644 --- a/mateclaw-ui/src/i18n/locales/zh-CN.ts +++ b/mateclaw-ui/src/i18n/locales/zh-CN.ts @@ -2218,6 +2218,60 @@ export default { placeholder: '在此粘贴 pageType profile 的 JSON…', reset: '重置为默认', resetConfirm: '确认重置为内置默认 profile?当前自定义配置会被清除。', + formMode: '表单', + jsonMode: 'JSON', + fallbackType: '兜底类型', + allowAdditionalFields: '保留未声明字段', + versionLabel: '版本', + addType: '添加类型', + removeType: '删除类型', + removeTypeConfirm: '确认删除页面类型「{type}」?', + noTypes: '暂无页面类型', + selectType: '从左侧选择或新建一个页面类型', + typeKey: '类型 Key', + typeKeyHint: '小写、唯一,仅含字母数字 _ -', + copyFrom: '从已有类型复制', + copyFromNone: '不复制(空白)', + moveUp: '上移', + moveDown: '下移', + label: '显示名', + description: '描述', + layer: '知识分层', + layerFact: '事实(fact)', + layerExperience: '经验(experience)', + layerUnset: '未指定', + fields: '字段', + fieldName: '字段名', + fieldType: '类型', + fieldRequired: '必填', + enumValues: '可选值', + enumHint: '逗号分隔', + addField: '添加字段', + noFields: '暂无字段', + advanced: '高级(阶段提示词 / 模板)', + advancedIntro: '以下提示词会注入到摄取流水线对应阶段,追加在系统默认提示词之后;留空即用默认。三个阶段:route=判断内容是否归入该类型,create=首次新建该类型页面,merge=把新内容并入已有页面。', + stageRoute: 'Route 阶段指令', + routeHint: '路由判定:模型在决定一段原始内容是否属于、以及如何归入该类型页面时的额外指引。', + routeExample: '例:仅当内容描述某个具体人物的生平或职责时,才归入本类型;泛泛提及不算。', + stageCreate: 'Create 阶段指令', + createHint: '创建写作:首次为该类型生成页面时的结构与风格要求(要素、语气、章节)。', + createExample: '例:用第三人称客观陈述;开头给一句话定义;随后分「背景 / 关键属性 / 关联」三节。', + stageCreateTemplate: 'Create 模板 Key', + createTemplateHint: '创建时引用的模板标识,对应下方「模板 Key」;留空则不套用模板。', + createTemplateExample: '例:concept-tpl', + stageMerge: 'Merge 阶段指令', + mergeHint: '合并取舍:把新增内容并入已有页面时的规则(如何去重、冲突时保留哪个)。', + mergeExample: '例:保留信息更新的版本;冲突处标注来源与时间,不直接覆盖原文。', + templateKey: '模板 Key', + templateKeyHint: '模板标识,供 create 阶段通过上面的「Create 模板 Key」引用。', + templateMarkdown: '模板 Markdown', + templateMarkdownHint: '交给模型的正文骨架:写你想要的标题/章节结构,create 阶段会原样拼进提示词,由模型据此填写正文。它是结构示例,不做变量替换——括号里的中文是给模型的提示,不会被自动套用字段值。', + templateMarkdownExample: '## 概要\n(用一句话定义本条目)\n\n## 关键属性\n- \n\n## 关联\n- 相关条目用 [[wiki 链接]] 指向', + errKeyFormat: '类型 Key「{key}」非法:需为小写、非空,仅含字母数字 _ -', + errKeyDup: '类型 Key「{key}」重复', + errFieldDup: '类型「{type}」中字段名「{field}」重复', + warnFallbackMissing: '兜底类型「{type}」未在已定义类型中', + errJsonParse: 'JSON 解析失败,请修正后再切换到表单', }, reclassify: { desc: '按当前 profile 重新分类已有页面:仅更新页面分类(pageType),不改动正文。改了 profile(新增/删除类型)后用它把旧页面迁移到新分类。', diff --git a/mateclaw-ui/src/views/Wiki/components/WikiAdvancedPanel.vue b/mateclaw-ui/src/views/Wiki/components/WikiAdvancedPanel.vue index 8469c43b..8f230891 100644 --- a/mateclaw-ui/src/views/Wiki/components/WikiAdvancedPanel.vue +++ b/mateclaw-ui/src/views/Wiki/components/WikiAdvancedPanel.vue @@ -10,39 +10,15 @@ -
-
-
-

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

-

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

-
- {{ t('wiki.adv.profile.builtin') }} - v{{ profile.version }} -
- -
-
⚠ {{ iss }}
-
-
- - - -
- -
-

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

- -
-
+ + -
+

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

@@ -241,6 +217,7 @@ import { wikiApi } from '@/api/index' import { mcToast } from '@/composables/useMcToast' import { mcConfirm } from '@/components/common/useConfirm' import AgentPickerDialog from '@/components/common/AgentPickerDialog.vue' +import WikiPageTypeProfileEditor from './WikiPageTypeProfileEditor.vue' const { t } = useI18n() const store = useWikiStore() @@ -273,56 +250,7 @@ function switchSection(key: typeof section.value) { function unwrap(res: any) { return res?.data ?? res } function errMsg(e: any, fallback: string) { return e?.response?.data?.message || fallback } -// ---- REQ-1 Profile ---- -const profile = reactive({ config: '', version: 0, builtinDefault: true, issues: [] as string[], busy: false }) -async function loadProfile() { - if (!kbId.value) return - try { - const d = unwrap(await wikiApi.getPageTypeProfile(kbId.value)) - profile.config = typeof d.config === 'string' ? d.config : JSON.stringify(d.config, null, 2) - profile.version = d.version - profile.builtinDefault = d.builtinDefault - } catch (e: any) { mcToast.error(errMsg(e, 'Load profile failed')) } -} -async function validateProfile() { - profile.busy = true - try { - const d = unwrap(await wikiApi.validatePageTypeProfile(kbId.value, profile.config)) - profile.issues = d.issues || [] - if (d.valid) mcToast.success(t('wiki.adv.validOk')) - } catch (e: any) { mcToast.error(errMsg(e, 'Validate failed')) } finally { profile.busy = false } -} -async function saveProfile() { - profile.busy = true - try { - await wikiApi.savePageTypeProfile(kbId.value, profile.config) - mcToast.success(t('common.saved')) - profile.issues = [] - await loadProfile() - } catch (e: any) { mcToast.error(errMsg(e, 'Save failed')) } finally { profile.busy = false } -} -async function resetProfile() { - if (!(await mcConfirm({ title: t('wiki.adv.profile.reset'), message: t('wiki.adv.profile.resetConfirm'), tone: 'danger' }))) return - profile.busy = true - try { - await wikiApi.resetPageTypeProfile(kbId.value) - mcToast.success(t('common.saved')) - await loadProfile() - } catch (e: any) { mcToast.error(errMsg(e, 'Reset failed')) } finally { profile.busy = false } -} -async function reclassify() { - if (!kbId.value) return - if (!(await mcConfirm({ - title: t('wiki.adv.reclassify.confirmTitle'), - message: t('wiki.adv.reclassify.confirmMsg'), - tone: 'danger', - }))) return - profile.busy = true - try { - await wikiApi.reclassifyKB(kbId.value) - mcToast.success(t('wiki.adv.reclassify.started')) - } catch (e: any) { mcToast.error(errMsg(e, 'Reclassify failed')) } finally { profile.busy = false } -} +// ---- REQ-1 Profile lives in WikiPageTypeProfileEditor (guided form + JSON) ---- // ---- REQ-2 Layers & Stale ---- const layers = reactive({ pages: [] as any[], busy: false }) @@ -459,7 +387,7 @@ function runClass(s?: string) { return 'badge-muted' } -onMounted(() => { loaded.profile = true; loadProfile() }) +onMounted(() => { loaded.profile = true })