dsh-data-domain-knowledge 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +360 -0
- package/cordis.patch.yml +410 -0
- package/eval/README.md +76 -0
- package/eval/evaluate-retrieval.mjs +121 -0
- package/eval/queries.example.jsonl +2 -0
- package/lib/client.js +111415 -0
- package/lib/types/config-schema.d.ts +35 -0
- package/lib/types/config-schema.d.ts.map +1 -0
- package/lib/types/config-schema.js +34 -0
- package/lib/types/config-schema.js.map +1 -0
- package/lib/types/convert/core-loader.d.ts +32 -0
- package/lib/types/convert/core-loader.d.ts.map +1 -0
- package/lib/types/convert/core-loader.js +60 -0
- package/lib/types/convert/core-loader.js.map +1 -0
- package/lib/types/convert/host.d.ts +88 -0
- package/lib/types/convert/host.d.ts.map +1 -0
- package/lib/types/convert/host.js +479 -0
- package/lib/types/convert/host.js.map +1 -0
- package/lib/types/convert/ingest.d.ts +125 -0
- package/lib/types/convert/ingest.d.ts.map +1 -0
- package/lib/types/convert/ingest.js +228 -0
- package/lib/types/convert/ingest.js.map +1 -0
- package/lib/types/convert/settings-schema.d.ts +18 -0
- package/lib/types/convert/settings-schema.d.ts.map +1 -0
- package/lib/types/convert/settings-schema.js +26 -0
- package/lib/types/convert/settings-schema.js.map +1 -0
- package/lib/types/convert/types.d.ts +46 -0
- package/lib/types/convert/types.d.ts.map +1 -0
- package/lib/types/convert/types.js +41 -0
- package/lib/types/convert/types.js.map +1 -0
- package/lib/types/diagnosis/cards.d.ts +71 -0
- package/lib/types/diagnosis/cards.d.ts.map +1 -0
- package/lib/types/diagnosis/cards.js +482 -0
- package/lib/types/diagnosis/cards.js.map +1 -0
- package/lib/types/diagnosis/problem-classes.d.ts +51 -0
- package/lib/types/diagnosis/problem-classes.d.ts.map +1 -0
- package/lib/types/diagnosis/problem-classes.js +66 -0
- package/lib/types/diagnosis/problem-classes.js.map +1 -0
- package/lib/types/diagnosis/schema.d.ts +65 -0
- package/lib/types/diagnosis/schema.d.ts.map +1 -0
- package/lib/types/diagnosis/schema.js +208 -0
- package/lib/types/diagnosis/schema.js.map +1 -0
- package/lib/types/diagnosis/seed-source.d.ts +20 -0
- package/lib/types/diagnosis/seed-source.d.ts.map +1 -0
- package/lib/types/diagnosis/seed-source.js +96 -0
- package/lib/types/diagnosis/seed-source.js.map +1 -0
- package/lib/types/diagnosis/seed.d.ts +50 -0
- package/lib/types/diagnosis/seed.d.ts.map +1 -0
- package/lib/types/diagnosis/seed.js +242 -0
- package/lib/types/diagnosis/seed.js.map +1 -0
- package/lib/types/diagnosis/store.d.ts +81 -0
- package/lib/types/diagnosis/store.d.ts.map +1 -0
- package/lib/types/diagnosis/store.js +409 -0
- package/lib/types/diagnosis/store.js.map +1 -0
- package/lib/types/diagnosis/types.d.ts +124 -0
- package/lib/types/diagnosis/types.d.ts.map +1 -0
- package/lib/types/diagnosis/types.js +13 -0
- package/lib/types/diagnosis/types.js.map +1 -0
- package/lib/types/directory-picker.d.ts +94 -0
- package/lib/types/directory-picker.d.ts.map +1 -0
- package/lib/types/directory-picker.js +110 -0
- package/lib/types/directory-picker.js.map +1 -0
- package/lib/types/extract-model.d.ts +76 -0
- package/lib/types/extract-model.d.ts.map +1 -0
- package/lib/types/extract-model.js +193 -0
- package/lib/types/extract-model.js.map +1 -0
- package/lib/types/extract.d.ts +121 -0
- package/lib/types/extract.d.ts.map +1 -0
- package/lib/types/extract.js +549 -0
- package/lib/types/extract.js.map +1 -0
- package/lib/types/fts.d.ts +96 -0
- package/lib/types/fts.d.ts.map +1 -0
- package/lib/types/fts.js +184 -0
- package/lib/types/fts.js.map +1 -0
- package/lib/types/index.d.ts +39 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +1112 -0
- package/lib/types/index.js.map +1 -0
- package/lib/types/intent/engine.d.ts +108 -0
- package/lib/types/intent/engine.d.ts.map +1 -0
- package/lib/types/intent/engine.js +501 -0
- package/lib/types/intent/engine.js.map +1 -0
- package/lib/types/intent/host.d.ts +88 -0
- package/lib/types/intent/host.d.ts.map +1 -0
- package/lib/types/intent/host.js +389 -0
- package/lib/types/intent/host.js.map +1 -0
- package/lib/types/intent/preset-tools.d.ts +37 -0
- package/lib/types/intent/preset-tools.d.ts.map +1 -0
- package/lib/types/intent/preset-tools.js +45 -0
- package/lib/types/intent/preset-tools.js.map +1 -0
- package/lib/types/intent/settings-schema.d.ts +59 -0
- package/lib/types/intent/settings-schema.d.ts.map +1 -0
- package/lib/types/intent/settings-schema.js +57 -0
- package/lib/types/intent/settings-schema.js.map +1 -0
- package/lib/types/intent/store.d.ts +75 -0
- package/lib/types/intent/store.d.ts.map +1 -0
- package/lib/types/intent/store.js +139 -0
- package/lib/types/intent/store.js.map +1 -0
- package/lib/types/intent/tools.d.ts +63 -0
- package/lib/types/intent/tools.d.ts.map +1 -0
- package/lib/types/intent/tools.js +108 -0
- package/lib/types/intent/tools.js.map +1 -0
- package/lib/types/intent/types.d.ts +189 -0
- package/lib/types/intent/types.d.ts.map +1 -0
- package/lib/types/intent/types.js +62 -0
- package/lib/types/intent/types.js.map +1 -0
- package/lib/types/knowledge-bases.d.ts +266 -0
- package/lib/types/knowledge-bases.d.ts.map +1 -0
- package/lib/types/knowledge-bases.js +1107 -0
- package/lib/types/knowledge-bases.js.map +1 -0
- package/lib/types/layout.d.ts +262 -0
- package/lib/types/layout.d.ts.map +1 -0
- package/lib/types/layout.js +312 -0
- package/lib/types/layout.js.map +1 -0
- package/lib/types/migrate.d.ts +110 -0
- package/lib/types/migrate.d.ts.map +1 -0
- package/lib/types/migrate.js +280 -0
- package/lib/types/migrate.js.map +1 -0
- package/lib/types/recall-rank.d.ts +158 -0
- package/lib/types/recall-rank.d.ts.map +1 -0
- package/lib/types/recall-rank.js +261 -0
- package/lib/types/recall-rank.js.map +1 -0
- package/lib/types/runtime.d.ts +568 -0
- package/lib/types/runtime.d.ts.map +1 -0
- package/lib/types/runtime.js +3045 -0
- package/lib/types/runtime.js.map +1 -0
- package/lib/types/settings-schema.d.ts +117 -0
- package/lib/types/settings-schema.d.ts.map +1 -0
- package/lib/types/settings-schema.js +92 -0
- package/lib/types/settings-schema.js.map +1 -0
- package/lib/types/settings-volatile.d.ts +102 -0
- package/lib/types/settings-volatile.d.ts.map +1 -0
- package/lib/types/settings-volatile.js +114 -0
- package/lib/types/settings-volatile.js.map +1 -0
- package/lib/types/types.d.ts +799 -0
- package/lib/types/types.d.ts.map +1 -0
- package/lib/types/types.js +387 -0
- package/lib/types/types.js.map +1 -0
- package/lib/types/wiki-types.d.ts +68 -0
- package/lib/types/wiki-types.d.ts.map +1 -0
- package/lib/types/wiki-types.js +322 -0
- package/lib/types/wiki-types.js.map +1 -0
- package/package.json +101 -0
- package/preset/tita-data-agent/agent.cordis.yml +376 -0
- package/preset/tita-data-agent/preset.yml +3 -0
- package/python/requirements.txt +8 -0
- package/scripts/doctor.mjs +70 -0
- package/scripts/e2e-pipeline.mjs +355 -0
- package/scripts/install-agent-preset.mjs +92 -0
- package/scripts/setup-embedding.sh +34 -0
- package/scripts/sync-agent-preset.mjs +188 -0
- package/src/client/AgentPresetCard.tsx +247 -0
- package/src/client/DirectoryPicker.tsx +357 -0
- package/src/client/InfoHint.tsx +65 -0
- package/src/client/IntentLogicTab.tsx +185 -0
- package/src/client/IntentTab.tsx +715 -0
- package/src/client/KnowledgeBaseTab.tsx +1454 -0
- package/src/client/KnowledgeGraphView.tsx +306 -0
- package/src/client/KnowledgePanel.tsx +381 -0
- package/src/client/KnowledgeStepRail.tsx +60 -0
- package/src/client/NodeSettings.tsx +392 -0
- package/src/client/PreviewSections.tsx +116 -0
- package/src/client/RecallPanel.tsx +399 -0
- package/src/client/TypeDefinitionDrawer.tsx +215 -0
- package/src/client/WikiFlowCard.tsx +226 -0
- package/src/client/WikiTypeEditor.tsx +332 -0
- package/src/client/agent-preset.ts +121 -0
- package/src/client/arch/AgentArchitectureGraph.tsx +219 -0
- package/src/client/arch/NodeLogicPanel.tsx +133 -0
- package/src/client/arch/ProblemDetailPanel.tsx +88 -0
- package/src/client/arch/model.ts +691 -0
- package/src/client/architecture.css +191 -0
- package/src/client/architecture.tsx +530 -0
- package/src/client/attribution/AttributionGraph.tsx +104 -0
- package/src/client/attribution/AttributionTab.tsx +332 -0
- package/src/client/attribution/CaseView.tsx +102 -0
- package/src/client/attribution/GraphStage.tsx +87 -0
- package/src/client/attribution/RunView.tsx +184 -0
- package/src/client/attribution/StageChrome.tsx +403 -0
- package/src/client/attribution/ViewSwitch.tsx +107 -0
- package/src/client/attribution/graph-options.ts +381 -0
- package/src/client/attribution/model.ts +462 -0
- package/src/client/config-scope.ts +119 -0
- package/src/client/convert/ConvertPanel.tsx +594 -0
- package/src/client/convert/styles.css +138 -0
- package/src/client/convert/styles.ts +49 -0
- package/src/client/format.ts +39 -0
- package/src/client/graph/layout.ts +181 -0
- package/src/client/graph.css +113 -0
- package/src/client/index.ts +98 -0
- package/src/client/problem-domains.ts +114 -0
- package/src/client/styles.css +940 -0
- package/src/client/styles.ts +11 -0
- package/src/client/useFitScale.ts +68 -0
- package/src/config-schema.ts +45 -0
- package/src/convert/core-loader.ts +68 -0
- package/src/convert/host.ts +579 -0
- package/src/convert/ingest.ts +367 -0
- package/src/convert/settings-schema.ts +27 -0
- package/src/convert/types.ts +65 -0
- package/src/diagnosis/cards.ts +498 -0
- package/src/diagnosis/problem-classes.ts +117 -0
- package/src/diagnosis/schema.ts +260 -0
- package/src/diagnosis/seed-source.ts +107 -0
- package/src/diagnosis/seed.ts +291 -0
- package/src/diagnosis/store.ts +432 -0
- package/src/diagnosis/types.ts +152 -0
- package/src/directory-picker.ts +164 -0
- package/src/extract-model.ts +227 -0
- package/src/extract.ts +632 -0
- package/src/fts.ts +203 -0
- package/src/index.ts +1091 -0
- package/src/intent/README.md +63 -0
- package/src/intent/engine.ts +527 -0
- package/src/intent/host.ts +462 -0
- package/src/intent/preset-tools.ts +48 -0
- package/src/intent/settings-schema.ts +63 -0
- package/src/intent/store.ts +143 -0
- package/src/intent/tools.ts +139 -0
- package/src/intent/types.ts +245 -0
- package/src/knowledge-bases.ts +1160 -0
- package/src/layout.ts +350 -0
- package/src/migrate.ts +262 -0
- package/src/python/embedding_worker.py +471 -0
- package/src/recall-rank.ts +299 -0
- package/src/runtime.ts +3069 -0
- package/src/settings-schema.ts +94 -0
- package/src/settings-volatile.ts +122 -0
- package/src/types.ts +1140 -0
- package/src/wiki-types.ts +357 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 「Wiki 抽取流程」卡片(issue 0024 的流程说明)。
|
|
3
|
+
*
|
|
4
|
+
* 它把"原文件 → Wiki 卡片"这段**单文档抽取**画成四个阶段,并明确标出每一段的
|
|
5
|
+
* 实现状态——**没有实现的不画成已实现**:这张图是给人看的契约,不是宣传。
|
|
6
|
+
*
|
|
7
|
+
* 与腾讯 WeKnora 四阶段 Map-Reduce 的对应关系写在每一段的"对应 WeKnora"一行里:
|
|
8
|
+
* 我们是**单文档**抽取,所以它的「批内 Taxonomy 规划」退化成「按库复用已有类型」,
|
|
9
|
+
* 「Pass 1..N 分块引文」在文档不长时与候选抽取合并成一次调用。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ReactNode } from 'react'
|
|
13
|
+
import {
|
|
14
|
+
ArrowDown, Box, Check, FileText, GitBranch, Layers3, RefreshCw, ShieldCheck, Sparkles, Wrench,
|
|
15
|
+
} from '@tita-data-agent/ui/icons'
|
|
16
|
+
import { Badge, Card, cn } from '@tita-data-agent/ui/components'
|
|
17
|
+
import { styles } from './styles.ts'
|
|
18
|
+
|
|
19
|
+
type StageStatus = 'done' | 'partial' | 'todo'
|
|
20
|
+
|
|
21
|
+
interface Stage {
|
|
22
|
+
id: string
|
|
23
|
+
icon: typeof FileText
|
|
24
|
+
tone: 'blue' | 'violet' | 'green' | 'amber'
|
|
25
|
+
pass: string
|
|
26
|
+
title: string
|
|
27
|
+
/** 这一段的输入 → 输出。 */
|
|
28
|
+
io: string
|
|
29
|
+
/** 这一段做什么(要点,`**…**` 会被渲染成粗体)。 */
|
|
30
|
+
body: string[]
|
|
31
|
+
/** 是否调用模型。 */
|
|
32
|
+
llm: boolean
|
|
33
|
+
/** 与 WeKnora 四阶段的对应关系。 */
|
|
34
|
+
weknora: string
|
|
35
|
+
status: StageStatus
|
|
36
|
+
statusNote: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const STAGES: Stage[] = [
|
|
40
|
+
{
|
|
41
|
+
id: 'candidates',
|
|
42
|
+
icon: Sparkles,
|
|
43
|
+
tone: 'violet',
|
|
44
|
+
pass: 'Pass 0',
|
|
45
|
+
title: '候选抽取:实体 / 概念 / 关系',
|
|
46
|
+
io: '一个 SourceRaw → 候选项列表',
|
|
47
|
+
body: [
|
|
48
|
+
'每个候选项的身份是**三件事一起定**:类型 + 名称(slug)+ 证据分块。类型与名称必须同时决定,因为 slug 的命名空间就是 `<类型>/<名称>`。',
|
|
49
|
+
'关系抽两处:卡片之间的 wiki 链接(正文里的 `[[类型/名称|标题]]`),以及实体之间的**图谱边**(`03_compiled/knowledge_graph.json`,目前只有 `supported_by` 溯源边)。',
|
|
50
|
+
'**slug 稳定性**:文档里提到的实体如果已有对应页面,复用它的 slug,不要另起一个近义 slug。',
|
|
51
|
+
],
|
|
52
|
+
llm: false,
|
|
53
|
+
weknora: '对应它的 Pass 0(抽 entities + concepts,强制复用已有 slug)。',
|
|
54
|
+
status: 'partial',
|
|
55
|
+
statusNote: '已实现**规则抽取**(正文标题、[[链接]]、frontmatter 的 candidates / entities / concepts / relations),编译时自动跑、增量跳过没变的来源;模型驱动的候选工具(propose_wiki_cards)还没有 —— 所以「抽什么」目前由文档结构决定,不是由模型判断。',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'normalize',
|
|
59
|
+
icon: GitBranch,
|
|
60
|
+
tone: 'blue',
|
|
61
|
+
pass: '归一',
|
|
62
|
+
title: '与已有 Wiki 合并:同指一物就复用',
|
|
63
|
+
io: '候选项列表 + 库内已有卡片 → 去重后的落点',
|
|
64
|
+
body: [
|
|
65
|
+
'**判重按 slug 与别名,不按标题字符串**:标题可以改,slug 是稳定标识。',
|
|
66
|
+
'核心原则是「**相关 ≠ 相同**」——相关的话题不要合并,只有同指一物才合并。',
|
|
67
|
+
'命中已有 → 记下"增量合并到该页";没命中 → 记下"新建"。',
|
|
68
|
+
'WeKnora 的「批内 Taxonomy 规划」在单文档场景退化成**库内复用**:优先沿用已有的类型与命名,不为了一个新页面另起一套。',
|
|
69
|
+
],
|
|
70
|
+
llm: false,
|
|
71
|
+
weknora: '对应它的 Taxonomy 规划 + WikiDeduplicationPrompt(related ≠ same);批内统筹改为库内复用。',
|
|
72
|
+
status: 'done',
|
|
73
|
+
statusNote: '已实现:按 entityId / title / metadata.aliases 建别名索引判重,同指一物就合并 source_refs(重复的出处不会写第二遍),关系候选不参与合并。',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'write',
|
|
77
|
+
icon: FileText,
|
|
78
|
+
tone: 'green',
|
|
79
|
+
pass: '落卡',
|
|
80
|
+
title: '写卡片:命中的增量合并,没命中的按模板新建',
|
|
81
|
+
io: '落点 + 证据 → 02_wiki/<类型>/<id>.md',
|
|
82
|
+
body: [
|
|
83
|
+
'必须带 `source_refs`,且每个来源都要真实存在(写了不存在的来源会被直接拒绝)。',
|
|
84
|
+
'必填小节与必填字段按**类型 schema** 填;缺了要么被拒(严格模式)要么标成待补(report 模式)。',
|
|
85
|
+
'新建的卡片是 `unreviewed`,不自动当成已审核的知识。',
|
|
86
|
+
'正文必须**严格接地**:只写证据里有的东西,去重,不写自链接。',
|
|
87
|
+
],
|
|
88
|
+
llm: true,
|
|
89
|
+
weknora: '对应它的 Reduce:按 slug 增量合并页面,提示词硬约束接地 / 禁幻觉 / 去重 / 禁自链接。',
|
|
90
|
+
status: 'done',
|
|
91
|
+
statusNote: '已实现:write_knowledge_card + 类型 schema 与三层校验(0024)',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'finalize',
|
|
95
|
+
icon: Wrench,
|
|
96
|
+
tone: 'amber',
|
|
97
|
+
pass: 'Finalize',
|
|
98
|
+
title: '收尾:索引、死链、交叉链接、质检',
|
|
99
|
+
io: '02_wiki → wikiNotes / 图谱 / 待补清单',
|
|
100
|
+
body: [
|
|
101
|
+
'重建索引页、清理死链、补交叉链接——这几步**不调用模型**,纯靠规则与图算法。',
|
|
102
|
+
'跑三层校验(format / reference / semantic),产出"待补 N 张"清单,人可以按清单补。',
|
|
103
|
+
'之后才轮到编译:卡片 → wikiNotes → 向量 + 图谱。',
|
|
104
|
+
],
|
|
105
|
+
llm: false,
|
|
106
|
+
weknora: '对应它的 Finalize(重建索引 / 清死链 / 补交叉链接,纯 SQL + 图算法)。',
|
|
107
|
+
status: 'partial',
|
|
108
|
+
statusNote: '部分实现:三层质检已实现;索引页 / 死链 / 交叉链接待实现',
|
|
109
|
+
},
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
const STATUS_META: Record<StageStatus, { label: string; tone: 'success' | 'warning' | 'neutral' }> = {
|
|
113
|
+
done: { label: '已实现', tone: 'success' },
|
|
114
|
+
partial: { label: '部分实现', tone: 'warning' },
|
|
115
|
+
todo: { label: '待实现', tone: 'neutral' },
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const TONE_CLASS: Record<Stage['tone'], string> = {
|
|
119
|
+
blue: styles.wikiFlowBlue,
|
|
120
|
+
violet: styles.wikiFlowViolet,
|
|
121
|
+
green: styles.wikiFlowGreen,
|
|
122
|
+
amber: styles.wikiFlowAmber,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** 三条判定规则 —— 对应"抽取 → 判类型 → 看是否已存在"这三步,但顺序与判据都被钉死。 */
|
|
126
|
+
const RULES: Array<{ order: string; title: string; body: string }> = [
|
|
127
|
+
{
|
|
128
|
+
order: '1',
|
|
129
|
+
title: '类型在抽取时就要定,不能留到最后再判',
|
|
130
|
+
body: '候选项的身份是「类型 + 名称 + 证据」。slug 的命名空间是 `<类型>/<名称>`,类型晚定一步,slug 就得重算一次。',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
order: '2',
|
|
134
|
+
title: '类型只能来自注册表,**不命中不要自创**',
|
|
135
|
+
body: '自创一个类型等于这张卡片没有 schema:没有必填小节、没有模板、校验不了。都不命中时落到 `concept`,或先在「Wiki 类型」里把它登记成正式类型再抽。',
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
order: '3',
|
|
139
|
+
title: '「是否已存在」按 slug 与别名判,不按标题',
|
|
140
|
+
body: '命中的走**增量合并**(旧版本先快照,回滚本身也可回滚),没命中的才新建 `draft`。相关不等于相同:别把相近的话题并成一条。',
|
|
141
|
+
},
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
/** 把文案里的 `**强调**` 渲染成粗体,避免在 JSX 里写 Markdown 却显示成星号。 */
|
|
145
|
+
function rich(text: string): ReactNode {
|
|
146
|
+
return text.split(/\*\*([^*]+)\*\*/g).map((part, index) => (index % 2 === 1 ? <b key={index}>{part}</b> : <span key={index}>{part}</span>))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function WikiFlowCard(): ReactNode {
|
|
150
|
+
return <Card className={styles.kbCard}>
|
|
151
|
+
<div className={styles.kbCardHead}>
|
|
152
|
+
<div><h3><RefreshCw size={13} />Wiki 抽取流程(原文件 → Wiki)</h3></div>
|
|
153
|
+
<span className={styles.kbCount}>4 段 · 目前没有一段由管线自己调模型</span>
|
|
154
|
+
</div>
|
|
155
|
+
<p className={styles.kbHint}>
|
|
156
|
+
单文档抽取的完整链路。<b>与已落库的卡片合并是这条链路的中心</b>——它决定这次是"新建一页"还是"补进已有页面",
|
|
157
|
+
也决定 slug 能不能稳定下来。状态徽标按代码现状标注:没实现的不画成已实现。
|
|
158
|
+
前两段现在由编译管线用**规则**完成(编译时自动跑,正文没变的来源按指纹跳过);
|
|
159
|
+
设计里那两次模型调用(候选抽取工具、归一提示词)还没有,所以这两段标的是「部分实现」。
|
|
160
|
+
</p>
|
|
161
|
+
|
|
162
|
+
<div className={styles.wikiFlow}>
|
|
163
|
+
<div className={styles.wikiFlowSource}>
|
|
164
|
+
<FileText size={14} />
|
|
165
|
+
<span>
|
|
166
|
+
<strong>SourceRaw</strong>
|
|
167
|
+
<small>01_source_truth/*.md — 原始可追溯文档,本身不进召回</small>
|
|
168
|
+
</span>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
{STAGES.map(stage => {
|
|
172
|
+
const Icon = stage.icon
|
|
173
|
+
const status = STATUS_META[stage.status]
|
|
174
|
+
return <div className={styles.wikiFlowStageWrap} key={stage.id}>
|
|
175
|
+
<span className={styles.wikiFlowArrow} aria-hidden="true"><ArrowDown size={14} /></span>
|
|
176
|
+
<div className={cn(styles.wikiFlowStage, TONE_CLASS[stage.tone])}>
|
|
177
|
+
<div className={styles.wikiFlowStageHead}>
|
|
178
|
+
<span className={styles.wikiFlowPass}><Icon size={13} />{stage.pass}</span>
|
|
179
|
+
<strong>{stage.title}</strong>
|
|
180
|
+
<Badge tone={status.tone}>{status.label}</Badge>
|
|
181
|
+
<span className={styles.wikiFlowLlm}>{stage.llm ? '调用模型' : '不调用模型'}</span>
|
|
182
|
+
</div>
|
|
183
|
+
<p className={styles.wikiFlowIo}>{stage.io}</p>
|
|
184
|
+
<ul className={styles.wikiFlowBody}>
|
|
185
|
+
{stage.body.map(line => <li key={line}>{rich(line)}</li>)}
|
|
186
|
+
</ul>
|
|
187
|
+
<p className={styles.wikiFlowNote}><b>对应 WeKnora:</b>{stage.weknora}</p>
|
|
188
|
+
<p className={styles.wikiFlowNote}>{stage.statusNote}</p>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
})}
|
|
192
|
+
|
|
193
|
+
<span className={styles.wikiFlowArrow} aria-hidden="true"><ArrowDown size={14} /></span>
|
|
194
|
+
<div className={styles.wikiFlowSink}>
|
|
195
|
+
<Layers3 size={14} />
|
|
196
|
+
<span>
|
|
197
|
+
<strong>Wiki 卡片 → wikiNotes → 向量 / 图谱</strong>
|
|
198
|
+
<small>02_wiki/<类型>/<id>.md → 03_compiled/* — 召回单元在编译段才产生</small>
|
|
199
|
+
</span>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
<div className={styles.kbSubHead}><h4><ShieldCheck size={13} />三条判定规则(内置,只读)</h4></div>
|
|
204
|
+
<ul className={styles.wikiFlowRules}>
|
|
205
|
+
{RULES.map(rule => <li key={rule.order}>
|
|
206
|
+
<span className={styles.wikiFlowRuleOrder}><Check size={11} />{rule.order}</span>
|
|
207
|
+
<span>
|
|
208
|
+
<strong>{rich(rule.title)}</strong>
|
|
209
|
+
<small>{rich(rule.body)}</small>
|
|
210
|
+
</span>
|
|
211
|
+
</li>)}
|
|
212
|
+
</ul>
|
|
213
|
+
|
|
214
|
+
<div className={styles.wikiFlowDiff}>
|
|
215
|
+
<Box size={13} />
|
|
216
|
+
<span>
|
|
217
|
+
WeKnora 是<b>知识库级后台管道</b>(Redis 队列 + 每 KB 并发治理 + Neo4j),本仓库不照抄:
|
|
218
|
+
模型调用一律经 Agent,所以前两段由工具驱动、由人复核。<b>抽取粒度</b>
|
|
219
|
+
(focused / standard / exhaustive)这个旋钮值得抄——它把"抽多少"变成用户能理解的东西。
|
|
220
|
+
</span>
|
|
221
|
+
</div>
|
|
222
|
+
</Card>
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** 流程内的三条判定规则(与卡片同源,供文档与工具描述复用)。 */
|
|
226
|
+
export const WIKI_FLOW_RULES = RULES
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 「Wiki 类型」配置区块(issue 0024)——**这张界面就是"SourceRaw → Wiki"的约束入口**。
|
|
3
|
+
*
|
|
4
|
+
* 它决定三件事:
|
|
5
|
+
* 1. 能生成哪些类型的卡片(`entities` 清单,停用的类型模型不该再用);
|
|
6
|
+
* 2. 每类卡片**必须**有哪些小节与字段(缺了要么被拒、要么标成待补,看生效方式);
|
|
7
|
+
* 3. 模板卡长什么样(由小节派生,新建卡片时照抄)。
|
|
8
|
+
*
|
|
9
|
+
* 组件自包含:列表与编辑弹窗在这里,数据由父级(知识库面板)以 props 传入,
|
|
10
|
+
* 写入统一走 `onApply`(整表替换)。它另外自己拉一次 `/knowledge/wiki-types`
|
|
11
|
+
* 取"待补"清单与内置只读判定规则——那是展示信息,不影响写入契约。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { useCallback, useEffect, useState, type ChangeEvent, type ReactNode } from 'react'
|
|
15
|
+
import {
|
|
16
|
+
AlertCircle, Check, ClipboardCheck, Eye, Pencil, Plus, RotateCcw, ShieldCheck, Trash2, X,
|
|
17
|
+
} from '@tita-data-agent/ui/icons'
|
|
18
|
+
import { Badge, Button, Input, Label, SelectMenu, Switch, cn } from '@tita-data-agent/ui/components'
|
|
19
|
+
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@tita-data-agent/ui/overlays'
|
|
20
|
+
import { styles } from './styles.ts'
|
|
21
|
+
|
|
22
|
+
export interface WikiSectionSpec { label: string; required: boolean }
|
|
23
|
+
export interface WikiFieldSpec { name: string; required: boolean; note: string }
|
|
24
|
+
|
|
25
|
+
export interface WikiTypeView {
|
|
26
|
+
id: string
|
|
27
|
+
label: string
|
|
28
|
+
description: string
|
|
29
|
+
layer: 'type' | 'instance'
|
|
30
|
+
sections: WikiSectionSpec[]
|
|
31
|
+
fields: WikiFieldSpec[]
|
|
32
|
+
sourceTypes: string[]
|
|
33
|
+
examples: string[]
|
|
34
|
+
template: string
|
|
35
|
+
enabled: boolean
|
|
36
|
+
builtin: boolean
|
|
37
|
+
cardCount: number
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface WikiTypeIssue {
|
|
41
|
+
entityId: string
|
|
42
|
+
entityType: string
|
|
43
|
+
level: string
|
|
44
|
+
field?: string
|
|
45
|
+
message: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface WikiTypeDraft {
|
|
49
|
+
open: boolean
|
|
50
|
+
mode: 'create' | 'edit'
|
|
51
|
+
id: string
|
|
52
|
+
label: string
|
|
53
|
+
description: string
|
|
54
|
+
layer: 'type' | 'instance'
|
|
55
|
+
/** 每行一条;结尾加 `?` 表示可选小节。 */
|
|
56
|
+
sections: string
|
|
57
|
+
/** 每行一条,`name|说明`;`name|说明|可选` 表示非必填。 */
|
|
58
|
+
fields: string
|
|
59
|
+
/** 每行一个来源分类 id;留空表示不限制。 */
|
|
60
|
+
sourceTypes: string
|
|
61
|
+
examples: string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const EMPTY_DRAFT: WikiTypeDraft = { open: false, mode: 'create', id: '', label: '', description: '', layer: 'type', sections: '', fields: '', sourceTypes: '', examples: '' }
|
|
65
|
+
|
|
66
|
+
const TYPE_ID_PATTERN = /^[a-z][a-z0-9_/-]{1,60}$/
|
|
67
|
+
|
|
68
|
+
interface Props {
|
|
69
|
+
kbId: string
|
|
70
|
+
types: WikiTypeView[]
|
|
71
|
+
mode: 'strict' | 'report'
|
|
72
|
+
/** 用户是否改过(决定「恢复默认」是否可用)。 */
|
|
73
|
+
customized: boolean
|
|
74
|
+
canEdit: boolean
|
|
75
|
+
busy: boolean
|
|
76
|
+
/** 整表替换 wikiTypes。 */
|
|
77
|
+
onApply: (next: WikiTypeView[], describe: string) => Promise<void>
|
|
78
|
+
onModeChange: (mode: 'strict' | 'report') => Promise<void>
|
|
79
|
+
onReset: () => Promise<void>
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** 由小节派生模板卡 —— 与服务端 `templateCardFor` 同一规则,避免两处漂移。 */
|
|
83
|
+
function templatePreview(sections: WikiSectionSpec[], fields: WikiFieldSpec[]): string {
|
|
84
|
+
const head = fields.filter(field => field.required).map(field => `> 必填字段:\`${field.name}\`(${field.note})`)
|
|
85
|
+
const body = sections.map(section => `## ${section.label}\n\n(${section.required ? '必填' : '可选'}:写什么)\n`).join('\n')
|
|
86
|
+
const required = sections.filter(section => section.required).map(section => section.label)
|
|
87
|
+
return [required.length > 0 ? `<!-- 必填小节:${required.join(' / ')} -->` : '', ...head, '', body].filter(Boolean).join('\n')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function parseSections(text: string): WikiSectionSpec[] {
|
|
91
|
+
return text.split('\n').map(line => line.trim()).filter(Boolean).map(line => {
|
|
92
|
+
const optional = line.endsWith('?')
|
|
93
|
+
return { label: optional ? line.slice(0, -1).trim() : line, required: !optional }
|
|
94
|
+
}).filter(section => section.label.length > 0)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function parseFields(text: string): WikiFieldSpec[] {
|
|
98
|
+
return text.split('\n').map(line => line.trim()).filter(Boolean).map(line => {
|
|
99
|
+
const [name = '', note = '', optional = ''] = line.split('|').map(part => part.trim())
|
|
100
|
+
return { name, note, required: optional !== '可选' && optional !== 'optional' }
|
|
101
|
+
}).filter(field => field.name.length > 0)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const LAYER_LABEL: Record<WikiTypeView['layer'], string> = { type: '类型层', instance: '实例层' }
|
|
105
|
+
|
|
106
|
+
export function WikiTypeSection({ kbId, types, mode, customized, canEdit, busy, onApply, onModeChange, onReset }: Props): ReactNode {
|
|
107
|
+
const [draft, setDraft] = useState<WikiTypeDraft>(EMPTY_DRAFT)
|
|
108
|
+
const [notice, setNotice] = useState<{ tone: 'success' | 'error'; text: string } | null>(null)
|
|
109
|
+
const [rules, setRules] = useState('')
|
|
110
|
+
const [issues, setIssues] = useState<WikiTypeIssue[]>([])
|
|
111
|
+
const [issueCount, setIssueCount] = useState(0)
|
|
112
|
+
|
|
113
|
+
const load = useCallback(async (): Promise<void> => {
|
|
114
|
+
try {
|
|
115
|
+
const response = await fetch(`/knowledge/wiki-types?kb=${encodeURIComponent(kbId)}`)
|
|
116
|
+
const payload = await response.json() as { data?: { rules?: string; issues?: WikiTypeIssue[]; issueCount?: number } }
|
|
117
|
+
setRules(payload.data?.rules ?? '')
|
|
118
|
+
setIssues(payload.data?.issues ?? [])
|
|
119
|
+
setIssueCount(payload.data?.issueCount ?? 0)
|
|
120
|
+
} catch { /* 待补清单是展示信息,取不到不影响编辑 */ }
|
|
121
|
+
}, [kbId])
|
|
122
|
+
|
|
123
|
+
useEffect(() => { if (canEdit || kbId) void load() }, [load, canEdit])
|
|
124
|
+
|
|
125
|
+
const openEditor = (type?: WikiTypeView): void => {
|
|
126
|
+
setNotice(null)
|
|
127
|
+
setDraft(type
|
|
128
|
+
? {
|
|
129
|
+
open: true, mode: 'edit', id: type.id, label: type.label, description: type.description, layer: type.layer,
|
|
130
|
+
sections: type.sections.map(section => section.required ? section.label : `${section.label}?`).join('\n'),
|
|
131
|
+
fields: type.fields.map(field => [field.name, field.note, field.required ? '' : '可选'].filter(Boolean).join('|')).join('\n'),
|
|
132
|
+
sourceTypes: type.sourceTypes.join('\n'),
|
|
133
|
+
examples: type.examples.join('\n'),
|
|
134
|
+
}
|
|
135
|
+
: { ...EMPTY_DRAFT, open: true })
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const save = async (): Promise<void> => {
|
|
139
|
+
const id = draft.id.trim()
|
|
140
|
+
const label = draft.label.trim()
|
|
141
|
+
const description = draft.description.trim()
|
|
142
|
+
const sections = parseSections(draft.sections)
|
|
143
|
+
if (!TYPE_ID_PATTERN.test(id)) { setNotice({ tone: 'error', text: '类型 id 必须是小写字母开头,可含数字 / 下划线 / 连字符 / 斜杠,例如 metric、diagnosis/symptom' }); return }
|
|
144
|
+
if (draft.mode === 'create' && types.some(type => type.id === id)) { setNotice({ tone: 'error', text: `类型 id 已存在:${id}` }); return }
|
|
145
|
+
if (!label) { setNotice({ tone: 'error', text: '显示名不能为空' }); return }
|
|
146
|
+
if (description.length < 8) { setNotice({ tone: 'error', text: '定义必填:写清这个类型「是什么」,模型靠它判断卡片属于哪一类' }); return }
|
|
147
|
+
if (sections.length === 0) { setNotice({ tone: 'error', text: '至少写一个小节' }); return }
|
|
148
|
+
if (!sections.some(section => section.required)) { setNotice({ tone: 'error', text: '至少要有一个必填小节(不加 `?` 的即为必填)' }); return }
|
|
149
|
+
const fields = parseFields(draft.fields)
|
|
150
|
+
const next: WikiTypeView = {
|
|
151
|
+
id,
|
|
152
|
+
label,
|
|
153
|
+
description,
|
|
154
|
+
layer: draft.layer,
|
|
155
|
+
sections,
|
|
156
|
+
fields,
|
|
157
|
+
sourceTypes: draft.sourceTypes.split('\n').map(line => line.trim()).filter(Boolean),
|
|
158
|
+
examples: draft.examples.split('\n').map(line => line.trim()).filter(Boolean),
|
|
159
|
+
template: templatePreview(sections, fields),
|
|
160
|
+
enabled: types.find(type => type.id === id)?.enabled ?? true,
|
|
161
|
+
builtin: types.find(type => type.id === id)?.builtin ?? false,
|
|
162
|
+
cardCount: types.find(type => type.id === id)?.cardCount ?? 0,
|
|
163
|
+
}
|
|
164
|
+
const list = draft.mode === 'create' ? [...types, next] : types.map(type => type.id === id ? next : type)
|
|
165
|
+
setDraft(EMPTY_DRAFT)
|
|
166
|
+
await onApply(list, draft.mode === 'create' ? `已新增卡片类型「${label}」` : `卡片类型「${label}」已更新`)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const toggle = (type: WikiTypeView, enabled: boolean): Promise<void> =>
|
|
170
|
+
onApply(types.map(item => item.id === type.id ? { ...item, enabled } : item), `${type.label} 已${enabled ? '启用' : '停用'}`)
|
|
171
|
+
|
|
172
|
+
const remove = (type: WikiTypeView): Promise<void> =>
|
|
173
|
+
onApply(types.filter(item => item.id !== type.id), `已删除卡片类型「${type.label}」`)
|
|
174
|
+
|
|
175
|
+
const enabledCount = types.filter(type => type.enabled).length
|
|
176
|
+
|
|
177
|
+
return <>
|
|
178
|
+
<div className={styles.kbSubHead}>
|
|
179
|
+
<h4><ClipboardCheck size={13} />Wiki 类型(卡片 schema)</h4>
|
|
180
|
+
<span>{enabledCount} / {types.length} 启用{customized ? ' · 已自定义' : ''}</span>
|
|
181
|
+
</div>
|
|
182
|
+
<p className={styles.kbHint}>
|
|
183
|
+
从原文件生成 Wiki 卡片时,<b>类型决定这张卡片该长什么样</b>:先按「定义 + 例如」判断属于哪一类,
|
|
184
|
+
再按<b>必填小节</b>写正文、按<b>必填字段</b>填 front matter。模板卡由小节派生,新建卡片时照抄即可。
|
|
185
|
+
</p>
|
|
186
|
+
|
|
187
|
+
<div className={styles.wikiTypeBar}>
|
|
188
|
+
<label className={styles.wikiTypeMode}>
|
|
189
|
+
<Switch
|
|
190
|
+
checked={mode === 'strict'}
|
|
191
|
+
disabled={!canEdit || busy}
|
|
192
|
+
onCheckedChange={checked => void onModeChange(checked ? 'strict' : 'report')}
|
|
193
|
+
/>
|
|
194
|
+
<span>
|
|
195
|
+
<strong>严格模式</strong>
|
|
196
|
+
<small>{mode === 'strict' ? '缺必填小节 / 字段直接拒绝写入' : '只把问题标在卡片上,不拦截写入'}</small>
|
|
197
|
+
</span>
|
|
198
|
+
</label>
|
|
199
|
+
<span className={styles.wikiTypeIssueCount} title="对该库已落库卡片跑三层校验的结果">
|
|
200
|
+
{issueCount > 0 ? `${issueCount} 张卡片待补` : '卡片均符合 schema'}
|
|
201
|
+
</span>
|
|
202
|
+
<span className={styles.kbCardActions}>
|
|
203
|
+
{canEdit && <Button variant="ghost" size="sm" disabled={busy || !customized} title={customized ? '清除自定义,回到出厂类型' : '当前就是出厂类型'} onClick={() => void onReset()}><RotateCcw size={13} />恢复默认</Button>}
|
|
204
|
+
<Button variant="outline" size="sm" disabled={!canEdit || busy} onClick={() => openEditor()}><Plus size={13} />新增类型</Button>
|
|
205
|
+
</span>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
{notice && <p className={cn(styles.kbNotice, notice.tone === 'error' && styles.kbNoticeError)} role="status">
|
|
209
|
+
<AlertCircle size={13} /><span>{notice.text}</span>
|
|
210
|
+
<button type="button" className={styles.kbNoticeClose} aria-label="关闭提示" onClick={() => setNotice(null)}><X size={12} /></button>
|
|
211
|
+
</p>}
|
|
212
|
+
|
|
213
|
+
<ul className={styles.wikiTypeList}>
|
|
214
|
+
{types.map(type => <li key={type.id} className={styles.wikiTypeRow}>
|
|
215
|
+
<span className={styles.wikiTypeName}>
|
|
216
|
+
<span className={styles.wikiTypeLabel}>{type.label}</span>
|
|
217
|
+
<code className={styles.kbSourceId}>{type.id}</code>
|
|
218
|
+
<Badge tone={type.layer === 'instance' ? 'warning' : 'neutral'}>{LAYER_LABEL[type.layer]}</Badge>
|
|
219
|
+
{type.builtin ? '' : <span className={styles.wikiTypeTag}>自定义</span>}
|
|
220
|
+
</span>
|
|
221
|
+
<span className={styles.wikiTypeMeta}>
|
|
222
|
+
{type.description || '(没有定义:模型只能靠猜)'}
|
|
223
|
+
<br />
|
|
224
|
+
必填小节:{type.sections.filter(section => section.required).map(section => section.label).join(' / ') || '(无)'}
|
|
225
|
+
{type.fields.filter(field => field.required).length > 0 ? `;必填字段:${type.fields.filter(field => field.required).map(field => field.name).join(' / ')}` : ''}
|
|
226
|
+
</span>
|
|
227
|
+
<span className={styles.kbTypeCount}>{type.cardCount} 张</span>
|
|
228
|
+
<Switch checked={type.enabled} disabled={!canEdit || busy} onCheckedChange={checked => void toggle(type, checked)} />
|
|
229
|
+
<Button variant="ghost" size="icon" title={`编辑 ${type.label}`} aria-label={`编辑类型 ${type.label}`} disabled={!canEdit || busy} onClick={() => openEditor(type)}><Pencil size={13} /></Button>
|
|
230
|
+
<Button
|
|
231
|
+
variant="ghost" size="icon"
|
|
232
|
+
disabled={!canEdit || busy || type.builtin || type.cardCount > 0}
|
|
233
|
+
title={type.builtin ? '内置类型不可删除,只能停用' : type.cardCount > 0 ? `该类型下还有 ${type.cardCount} 张卡片,先改类型再删除` : `删除类型 ${type.label}`}
|
|
234
|
+
aria-label={`删除类型 ${type.label}`}
|
|
235
|
+
onClick={() => void remove(type)}
|
|
236
|
+
><Trash2 size={13} /></Button>
|
|
237
|
+
</li>)}
|
|
238
|
+
</ul>
|
|
239
|
+
|
|
240
|
+
{issueCount > 0 && <details className={styles.kbPrompt}>
|
|
241
|
+
<summary><AlertCircle size={12} /> 待补清单(前 {issues.length} / {issueCount} 条)</summary>
|
|
242
|
+
<ul className={styles.wikiTypeIssues}>
|
|
243
|
+
{issues.map((issue, index) => <li key={`${issue.entityId}-${index}`}>
|
|
244
|
+
<code>{issue.entityId}</code>
|
|
245
|
+
<span className={styles.wikiTypeIssueLevel}>{issue.level}</span>
|
|
246
|
+
<span>{issue.message}</span>
|
|
247
|
+
</li>)}
|
|
248
|
+
</ul>
|
|
249
|
+
</details>}
|
|
250
|
+
|
|
251
|
+
{rules && <details className={styles.kbPrompt}>
|
|
252
|
+
<summary><ShieldCheck size={12} /> 判定规则(内置,只读)</summary>
|
|
253
|
+
<p className={styles.kbPromptNote}>{rules.split('\n').map((line, index) => <span key={index}>{line}<br /></span>)}</p>
|
|
254
|
+
<p className={styles.kbPromptNote}>这段规则由代码内置(`WIKI_TYPE_RULES`),不随知识库变化,也不允许修改;可编辑的只有每个类型的定义、小节、字段与示例。</p>
|
|
255
|
+
</details>}
|
|
256
|
+
|
|
257
|
+
<Dialog open={draft.open} onOpenChange={open => { if (!open) setDraft(EMPTY_DRAFT) }}>
|
|
258
|
+
{draft.open && <DialogContent>
|
|
259
|
+
<DialogHeader><DialogTitle>{draft.mode === 'create' ? '新增 Wiki 卡片类型' : `编辑类型 · ${draft.label}`}</DialogTitle></DialogHeader>
|
|
260
|
+
<div className={styles.kbForm}>
|
|
261
|
+
<label className={styles.kbField}><Label>类型 id</Label>
|
|
262
|
+
<Input value={draft.id} disabled={draft.mode === 'edit'} spellCheck={false} placeholder="metric"
|
|
263
|
+
onChange={(event: ChangeEvent<HTMLInputElement>) => setDraft({ ...draft, id: event.target.value })} />
|
|
264
|
+
<span className={styles.kbHint}>小写字母开头,可含数字 / 下划线 / 连字符 / 斜杠(如 <code>diagnosis/symptom</code>);创建后不可修改——它是卡片的 <code>entity_type</code>。</span>
|
|
265
|
+
</label>
|
|
266
|
+
<label className={styles.kbField}><Label>显示名</Label>
|
|
267
|
+
<Input value={draft.label} placeholder="指标"
|
|
268
|
+
onChange={(event: ChangeEvent<HTMLInputElement>) => setDraft({ ...draft, label: event.target.value })} />
|
|
269
|
+
</label>
|
|
270
|
+
<label className={styles.kbField}><Label>定义(必填)</Label>
|
|
271
|
+
<textarea className={styles.kbTextarea} value={draft.description} spellCheck={false}
|
|
272
|
+
placeholder="一个可计算、可归属的量化口径,回答「这个数是怎么算出来的」。"
|
|
273
|
+
onChange={(event: ChangeEvent<HTMLTextAreaElement>) => setDraft({ ...draft, description: event.target.value })} />
|
|
274
|
+
<span className={styles.kbHint}>写清这一类「是什么」——它是模型判断卡片属于哪一类的第一依据。</span>
|
|
275
|
+
</label>
|
|
276
|
+
<div className={styles.kbField}><Label>层级</Label>
|
|
277
|
+
<SelectMenu
|
|
278
|
+
value={draft.layer}
|
|
279
|
+
onValueChange={next => setDraft({ ...draft, layer: next as 'type' | 'instance' })}
|
|
280
|
+
aria-label="层级"
|
|
281
|
+
options={[
|
|
282
|
+
{ value: 'type', label: '类型层(可复用,如口径、假设)' },
|
|
283
|
+
{ value: 'instance', label: '实例层(只发生过一次,如案例)' },
|
|
284
|
+
]}
|
|
285
|
+
/>
|
|
286
|
+
</div>
|
|
287
|
+
<label className={styles.kbField}><Label>必填小节(每行一条,结尾加 ? 表示可选)</Label>
|
|
288
|
+
<textarea className={styles.kbTextarea} value={draft.sections} spellCheck={false}
|
|
289
|
+
placeholder={'口径定义\n计算逻辑\n负责人?'}
|
|
290
|
+
onChange={(event: ChangeEvent<HTMLTextAreaElement>) => setDraft({ ...draft, sections: event.target.value })} />
|
|
291
|
+
<span className={styles.kbHint}>小节就是正文的 Markdown 标题,也是模板卡与校验的依据。</span>
|
|
292
|
+
</label>
|
|
293
|
+
<label className={styles.kbField}><Label>字段(每行一条:<code>名称|说明</code>,说明后加 <code>|可选</code> 表示非必填)</Label>
|
|
294
|
+
<textarea className={styles.kbTextarea} value={draft.fields} spellCheck={false}
|
|
295
|
+
placeholder={'formula|可执行的表达式或伪代码\nowner|谁对这个口径负责'}
|
|
296
|
+
onChange={(event: ChangeEvent<HTMLTextAreaElement>) => setDraft({ ...draft, fields: event.target.value })} />
|
|
297
|
+
<span className={styles.kbHint}>字段落在 front matter / metadata 上,是机器读的部分。</span>
|
|
298
|
+
</label>
|
|
299
|
+
<label className={styles.kbField}><Label>期望来源分类(每行一个 id,留空表示不限制)</Label>
|
|
300
|
+
<textarea className={styles.kbTextarea} value={draft.sourceTypes} spellCheck={false}
|
|
301
|
+
placeholder={'document\nprd'}
|
|
302
|
+
onChange={(event: ChangeEvent<HTMLTextAreaElement>) => setDraft({ ...draft, sourceTypes: event.target.value })} />
|
|
303
|
+
<span className={styles.kbHint}>来自「来源类型」的 id。来源不匹配时只提示,不阻断写作。</span>
|
|
304
|
+
</label>
|
|
305
|
+
<label className={styles.kbField}><Label>例如 · 用户会怎么提到它(每行一条)</Label>
|
|
306
|
+
<textarea className={styles.kbTextarea} value={draft.examples} spellCheck={false}
|
|
307
|
+
placeholder={'上个月的 GMV 怎么算\n转化率的口径是什么'}
|
|
308
|
+
onChange={(event: ChangeEvent<HTMLTextAreaElement>) => setDraft({ ...draft, examples: event.target.value })} />
|
|
309
|
+
<span className={styles.kbHint}>按用户的真实说法写,模型靠它判断该生成哪一类卡片。</span>
|
|
310
|
+
</label>
|
|
311
|
+
{(draft.sections.trim() || draft.fields.trim()) && <div className={styles.kbField}>
|
|
312
|
+
<Label>模板卡预览(由小节派生,只读)</Label>
|
|
313
|
+
<pre className={styles.kbPre}>{templatePreview(parseSections(draft.sections), parseFields(draft.fields))}</pre>
|
|
314
|
+
</div>}
|
|
315
|
+
</div>
|
|
316
|
+
<DialogFooter>
|
|
317
|
+
<Button variant="ghost" onClick={() => setDraft(EMPTY_DRAFT)}>取消</Button>
|
|
318
|
+
<Button variant="default" disabled={busy || !draft.id.trim() || !draft.label.trim()} onClick={() => void save()}><Check size={13} />保存</Button>
|
|
319
|
+
</DialogFooter>
|
|
320
|
+
</DialogContent>}
|
|
321
|
+
</Dialog>
|
|
322
|
+
</>
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** 卡片的模板卡查看入口:编辑弹窗外也让人能直接照抄。 */
|
|
326
|
+
export function WikiTemplateHint({ template }: { template: string }): ReactNode {
|
|
327
|
+
if (!template) return null
|
|
328
|
+
return <details className={styles.kbPrompt}>
|
|
329
|
+
<summary><Eye size={12} /> 模板卡</summary>
|
|
330
|
+
<pre className={styles.kbPre}>{template}</pre>
|
|
331
|
+
</details>
|
|
332
|
+
}
|