dsh-data-domain-knowledge 0.1.1-alpha.2 → 0.1.1-alpha.4

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.
Files changed (252) hide show
  1. package/README.md +33 -16
  2. package/lib/client.js +1042 -2097
  3. package/lib/contract-refs.d.ts +0 -1
  4. package/lib/doc-page.js +1 -1
  5. package/lib/pages.js +2489 -1060
  6. package/lib/types/agent-behavior-rules.d.ts.map +1 -1
  7. package/lib/types/agent-behavior-rules.js +3 -2
  8. package/lib/types/agent-behavior-rules.js.map +1 -1
  9. package/lib/types/axis-contract.js +1 -1
  10. package/lib/types/axis-contract.js.map +1 -1
  11. package/lib/types/card-organize.d.ts +2 -1
  12. package/lib/types/card-organize.d.ts.map +1 -1
  13. package/lib/types/card-organize.js +5 -3
  14. package/lib/types/card-organize.js.map +1 -1
  15. package/lib/types/config-schema.d.ts +1 -1
  16. package/lib/types/config-schema.js +1 -1
  17. package/lib/types/convert/host.d.ts +2 -3
  18. package/lib/types/convert/host.d.ts.map +1 -1
  19. package/lib/types/convert/host.js +11 -2
  20. package/lib/types/convert/host.js.map +1 -1
  21. package/lib/types/convert/ingest.d.ts +14 -20
  22. package/lib/types/convert/ingest.d.ts.map +1 -1
  23. package/lib/types/convert/ingest.js +8 -5
  24. package/lib/types/convert/ingest.js.map +1 -1
  25. package/lib/types/diagnosis/store.d.ts +2 -2
  26. package/lib/types/diagnosis/store.d.ts.map +1 -1
  27. package/lib/types/diagnosis/store.js +10 -2
  28. package/lib/types/diagnosis/store.js.map +1 -1
  29. package/lib/types/diagnosis/types.d.ts +1 -1
  30. package/lib/types/diagnosis/types.js +1 -1
  31. package/lib/types/extract-model.d.ts +3 -0
  32. package/lib/types/extract-model.d.ts.map +1 -1
  33. package/lib/types/extract-model.js.map +1 -1
  34. package/lib/types/images.js +1 -1
  35. package/lib/types/index.d.ts +0 -4
  36. package/lib/types/index.d.ts.map +1 -1
  37. package/lib/types/index.js +99 -91
  38. package/lib/types/index.js.map +1 -1
  39. package/lib/types/knowledge-bases.d.ts +3 -0
  40. package/lib/types/knowledge-bases.d.ts.map +1 -1
  41. package/lib/types/knowledge-bases.js +22 -2
  42. package/lib/types/knowledge-bases.js.map +1 -1
  43. package/lib/types/knowledge-contract.d.ts +6 -0
  44. package/lib/types/knowledge-contract.d.ts.map +1 -1
  45. package/lib/types/knowledge-contract.js +6 -2
  46. package/lib/types/knowledge-contract.js.map +1 -1
  47. package/lib/types/layout.d.ts +9 -26
  48. package/lib/types/layout.d.ts.map +1 -1
  49. package/lib/types/layout.js +10 -28
  50. package/lib/types/layout.js.map +1 -1
  51. package/lib/types/materialize-snapshot.d.ts +5 -0
  52. package/lib/types/materialize-snapshot.d.ts.map +1 -1
  53. package/lib/types/migrate.d.ts +3 -1
  54. package/lib/types/migrate.d.ts.map +1 -1
  55. package/lib/types/migrate.js +3 -1
  56. package/lib/types/migrate.js.map +1 -1
  57. package/lib/types/model-text.d.ts +7 -0
  58. package/lib/types/model-text.d.ts.map +1 -1
  59. package/lib/types/model-text.js +31 -9
  60. package/lib/types/model-text.js.map +1 -1
  61. package/lib/types/organize-contract.d.ts.map +1 -1
  62. package/lib/types/organize-contract.js +2 -1
  63. package/lib/types/organize-contract.js.map +1 -1
  64. package/lib/types/pipeline/engine.d.ts +28 -4
  65. package/lib/types/pipeline/engine.d.ts.map +1 -1
  66. package/lib/types/pipeline/engine.js +68 -15
  67. package/lib/types/pipeline/engine.js.map +1 -1
  68. package/lib/types/pipeline/progress.d.ts +12 -0
  69. package/lib/types/pipeline/progress.d.ts.map +1 -0
  70. package/lib/types/pipeline/progress.js +37 -0
  71. package/lib/types/pipeline/progress.js.map +1 -0
  72. package/lib/types/pipeline/resumable-extract.d.ts +45 -0
  73. package/lib/types/pipeline/resumable-extract.d.ts.map +1 -0
  74. package/lib/types/pipeline/resumable-extract.js +262 -0
  75. package/lib/types/pipeline/resumable-extract.js.map +1 -0
  76. package/lib/types/pipeline/store.d.ts +8 -2
  77. package/lib/types/pipeline/store.d.ts.map +1 -1
  78. package/lib/types/pipeline/store.js +27 -5
  79. package/lib/types/pipeline/store.js.map +1 -1
  80. package/lib/types/pipeline/types.d.ts +56 -0
  81. package/lib/types/pipeline/types.d.ts.map +1 -1
  82. package/lib/types/pipeline/types.js +18 -1
  83. package/lib/types/pipeline/types.js.map +1 -1
  84. package/lib/types/present.d.ts +1 -1
  85. package/lib/types/present.js +1 -1
  86. package/lib/types/preset-ids.d.ts +0 -1
  87. package/lib/types/preset-ids.d.ts.map +1 -1
  88. package/lib/types/preset-ids.js +0 -3
  89. package/lib/types/preset-ids.js.map +1 -1
  90. package/lib/types/query-analysis.d.ts +0 -5
  91. package/lib/types/query-analysis.d.ts.map +1 -1
  92. package/lib/types/query-analysis.js +0 -16
  93. package/lib/types/query-analysis.js.map +1 -1
  94. package/lib/types/recall-delivery.d.ts +36 -0
  95. package/lib/types/recall-delivery.d.ts.map +1 -0
  96. package/lib/types/recall-delivery.js +16 -0
  97. package/lib/types/recall-delivery.js.map +1 -0
  98. package/lib/types/retrieval/scope.d.ts +17 -0
  99. package/lib/types/retrieval/scope.d.ts.map +1 -0
  100. package/lib/types/retrieval/scope.js +11 -0
  101. package/lib/types/retrieval/scope.js.map +1 -0
  102. package/lib/types/runtime.d.ts +75 -14
  103. package/lib/types/runtime.d.ts.map +1 -1
  104. package/lib/types/runtime.js +454 -198
  105. package/lib/types/runtime.js.map +1 -1
  106. package/lib/types/settings-volatile.d.ts +0 -7
  107. package/lib/types/settings-volatile.d.ts.map +1 -1
  108. package/lib/types/settings-volatile.js +0 -7
  109. package/lib/types/settings-volatile.js.map +1 -1
  110. package/lib/types/source-identity.d.ts +34 -0
  111. package/lib/types/source-identity.d.ts.map +1 -0
  112. package/lib/types/source-identity.js +85 -0
  113. package/lib/types/source-identity.js.map +1 -0
  114. package/lib/types/source-read.d.ts +18 -0
  115. package/lib/types/source-read.d.ts.map +1 -0
  116. package/lib/types/source-read.js +49 -0
  117. package/lib/types/source-read.js.map +1 -0
  118. package/lib/types/store/axis-references.d.ts.map +1 -1
  119. package/lib/types/store/axis-references.js +5 -3
  120. package/lib/types/store/axis-references.js.map +1 -1
  121. package/lib/types/store/knowledge-db.d.ts +5 -29
  122. package/lib/types/store/knowledge-db.d.ts.map +1 -1
  123. package/lib/types/store/knowledge-db.js +57 -2
  124. package/lib/types/store/knowledge-db.js.map +1 -1
  125. package/lib/types/store/source-identity.d.ts +8 -0
  126. package/lib/types/store/source-identity.d.ts.map +1 -0
  127. package/lib/types/store/source-identity.js +56 -0
  128. package/lib/types/store/source-identity.js.map +1 -0
  129. package/lib/types/types.d.ts +46 -5
  130. package/lib/types/types.d.ts.map +1 -1
  131. package/lib/types/types.js +6 -3
  132. package/lib/types/types.js.map +1 -1
  133. package/lib/types/wiki-availability.d.ts +11 -0
  134. package/lib/types/wiki-availability.d.ts.map +1 -0
  135. package/lib/types/wiki-availability.js +39 -0
  136. package/lib/types/wiki-availability.js.map +1 -0
  137. package/lib/types/wiki-finalize.d.ts +4 -2
  138. package/lib/types/wiki-finalize.d.ts.map +1 -1
  139. package/lib/types/wiki-finalize.js +9 -27
  140. package/lib/types/wiki-finalize.js.map +1 -1
  141. package/lib/types/wiki-links.d.ts +35 -0
  142. package/lib/types/wiki-links.d.ts.map +1 -0
  143. package/lib/types/wiki-links.js +129 -0
  144. package/lib/types/wiki-links.js.map +1 -0
  145. package/lib/types/wiki-trial-types.d.ts +3 -0
  146. package/lib/types/wiki-trial-types.d.ts.map +1 -1
  147. package/lib/types/wiki-trial.d.ts +3 -3
  148. package/lib/types/wiki-trial.d.ts.map +1 -1
  149. package/lib/types/wiki-trial.js +14 -3
  150. package/lib/types/wiki-trial.js.map +1 -1
  151. package/package.json +16 -16
  152. package/scripts/doctor.mjs +0 -7
  153. package/scripts/setup-embedding.sh +0 -0
  154. package/src/agent-behavior-rules.ts +3 -2
  155. package/src/axis-contract.ts +1 -1
  156. package/src/card-organize.ts +5 -3
  157. package/src/client/AttributionEventGuide.tsx +3 -3
  158. package/src/client/AxisMatrix.tsx +86 -19
  159. package/src/client/AxisRegistrySection.tsx +1 -1
  160. package/src/client/CompileSettingsView.tsx +473 -0
  161. package/src/client/DirectoryPicker.tsx +0 -2
  162. package/src/client/ExtractionTasksPage.tsx +557 -423
  163. package/src/client/GuideArchitectureCanvas.tsx +5 -12
  164. package/src/client/ImportFilesDialog.tsx +1 -0
  165. package/src/client/InfoHint.tsx +1 -1
  166. package/src/client/KnowledgeBaseTab.tsx +141 -148
  167. package/src/client/KnowledgePanel.tsx +16 -9
  168. package/src/client/OperationLogTab.tsx +1 -1
  169. package/src/client/PipelineRunView.tsx +173 -0
  170. package/src/client/RecallPanel.tsx +5 -16
  171. package/src/client/TabGuide.tsx +3 -7
  172. package/src/client/WikiFlowPanel.tsx +10 -11
  173. package/src/client/arch/NodeLogicPanel.tsx +3 -3
  174. package/src/client/arch/model.ts +5 -113
  175. package/src/client/architecture.css +1 -1
  176. package/src/client/architecture.tsx +13 -27
  177. package/src/client/attribution/AttributionTab.tsx +141 -10
  178. package/src/client/pages.css +305 -180
  179. package/src/client/pages.tsx +7 -12
  180. package/src/client/styles.css +15 -96
  181. package/src/config-schema.ts +1 -1
  182. package/src/convert/host.ts +13 -5
  183. package/src/convert/ingest.ts +20 -7
  184. package/src/diagnosis/store.ts +7 -2
  185. package/src/diagnosis/types.ts +1 -1
  186. package/src/extract-model.ts +3 -0
  187. package/src/images.ts +1 -1
  188. package/src/index.ts +92 -97
  189. package/src/knowledge-bases.ts +23 -3
  190. package/src/knowledge-contract.ts +7 -2
  191. package/src/layout.ts +10 -32
  192. package/src/materialize-snapshot.ts +1 -0
  193. package/src/migrate.ts +3 -1
  194. package/src/model-text.ts +33 -11
  195. package/src/organize-contract.ts +2 -1
  196. package/src/pipeline/engine.ts +85 -22
  197. package/src/pipeline/progress.ts +40 -0
  198. package/src/pipeline/resumable-extract.ts +233 -0
  199. package/src/pipeline/store.ts +28 -6
  200. package/src/pipeline/types.ts +47 -2
  201. package/src/present.ts +1 -1
  202. package/src/preset-ids.ts +0 -4
  203. package/src/query-analysis.ts +0 -20
  204. package/src/recall-delivery.ts +19 -0
  205. package/src/retrieval/scope.ts +10 -0
  206. package/src/runtime.ts +412 -206
  207. package/src/settings-volatile.ts +0 -7
  208. package/src/source-identity.ts +85 -0
  209. package/src/source-read.ts +47 -0
  210. package/src/store/axis-references.ts +5 -3
  211. package/src/store/knowledge-db.ts +54 -2
  212. package/src/store/source-identity.ts +42 -0
  213. package/src/types.ts +18 -5
  214. package/src/wiki-availability.ts +37 -0
  215. package/src/wiki-finalize.ts +9 -25
  216. package/src/wiki-links.ts +120 -0
  217. package/src/wiki-trial-types.ts +3 -2
  218. package/src/wiki-trial.ts +17 -6
  219. package/lib/types/intent/engine.d.ts +0 -122
  220. package/lib/types/intent/engine.d.ts.map +0 -1
  221. package/lib/types/intent/engine.js +0 -488
  222. package/lib/types/intent/engine.js.map +0 -1
  223. package/lib/types/intent/host.d.ts +0 -68
  224. package/lib/types/intent/host.d.ts.map +0 -1
  225. package/lib/types/intent/host.js +0 -249
  226. package/lib/types/intent/host.js.map +0 -1
  227. package/lib/types/intent/scope.d.ts +0 -25
  228. package/lib/types/intent/scope.d.ts.map +0 -1
  229. package/lib/types/intent/scope.js +0 -116
  230. package/lib/types/intent/scope.js.map +0 -1
  231. package/lib/types/intent/store.d.ts +0 -33
  232. package/lib/types/intent/store.d.ts.map +0 -1
  233. package/lib/types/intent/store.js +0 -239
  234. package/lib/types/intent/store.js.map +0 -1
  235. package/lib/types/intent/tools.d.ts +0 -62
  236. package/lib/types/intent/tools.d.ts.map +0 -1
  237. package/lib/types/intent/tools.js +0 -130
  238. package/lib/types/intent/tools.js.map +0 -1
  239. package/lib/types/intent/types.d.ts +0 -170
  240. package/lib/types/intent/types.d.ts.map +0 -1
  241. package/lib/types/intent/types.js +0 -56
  242. package/lib/types/intent/types.js.map +0 -1
  243. package/src/client/IntentLogic.tsx +0 -192
  244. package/src/client/IntentTab.tsx +0 -343
  245. package/src/client/TypeDefinitionDrawer.tsx +0 -195
  246. package/src/intent/README.md +0 -42
  247. package/src/intent/engine.ts +0 -515
  248. package/src/intent/host.ts +0 -315
  249. package/src/intent/scope.ts +0 -125
  250. package/src/intent/store.ts +0 -218
  251. package/src/intent/tools.ts +0 -154
  252. package/src/intent/types.ts +0 -229
@@ -15,8 +15,10 @@
15
15
  * 那一个设置抽屉,只是落到对应的分块上。
16
16
  * 2. **设置抽屉装方案级参数**(存储 / 收录闸门 / 构建 / 召回 / 重排 / 追加行为规则):
17
17
  * 一个 namespace,整个方案一份。
18
- * 3. **编译设置抽屉装本库坐标实体**:按知识库持久化,切库就换一套。
19
- * 它与设置是**两种作用域**,所以是两个容器而不是一个 —— 见 `TypeDefinitionDrawer.tsx`。
18
+ * 3. **编译设置是知识处理整页里的一个视图**:按知识库持久化,切库就换一套。
19
+ * 它与面板顶部的方案级设置是**两种作用域**,所以是两个容器而不是一个 ——
20
+ * 见 `CompileSettingsView.tsx`。2026-10-28 前它是工具栏上的一只抽屉;
21
+ * 搬进整页的理由是它改的参数**紧挨着**要用它们的那几段。
20
22
  *
21
23
  * 产品逻辑参考腾讯 WeKnora:知识库是一等对象(独立配置、独立内容)。每个知识库有自己的
22
24
  * 根目录与知识矩阵实体注册表;知识库下有文件列表,每个文件有详情页与版本
@@ -35,8 +37,8 @@
35
37
 
36
38
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type ChangeEvent, type ReactNode } from 'react'
37
39
  import {
38
- AlertCircle, Check, ChevronDown, Eye, ExternalLink, FileText, FileUp, FolderOpen,
39
- Layers3, RefreshCw, Sparkles, Tags, Trash2, TriangleAlert, Upload, X,
40
+ AlertCircle, Check, ChevronDown, Eye, ExternalLink, FileText, FolderOpen,
41
+ Layers3, RefreshCw, Sparkles, Trash2, TriangleAlert, Upload, X,
40
42
  } from '@tita-agent/ui/icons'
41
43
  import type { LucideIcon } from '@tita-agent/ui/icons'
42
44
  import {
@@ -44,7 +46,7 @@ import {
44
46
  } from '@tita-agent/ui/components'
45
47
  import {
46
48
  Dialog, DialogContent, DialogHeader, DialogTitle, Drawer, DrawerBody, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle,
47
- openAppPath, useFullPage,
49
+ useFullPage,
48
50
  } from '@tita-agent/ui/overlays'
49
51
  import { RestoreDialog, type VersionManagerEntry } from '@tita-agent/ui/versions'
50
52
  import { readRemembered, readRememberedId, remember } from './panel-state.ts'
@@ -77,7 +79,7 @@ import { WikiCardPreview } from './WikiCardPreview.tsx'
77
79
  import { ListPager, PAGE_SIZE, pageSlice, usePage } from './ListPager.tsx'
78
80
  import { COMPILE_STEP_TITLE, CompileConfirmDialog, CompileProgressDialog, type CompileProgressView } from './CompileDialogs.tsx'
79
81
  import { ImportFilesDialog, type ImportItemView, type ImportStage } from './ImportFilesDialog.tsx'
80
- import { TypeDefinitionDrawer, type CompileModelSelectionView } from './TypeDefinitionDrawer.tsx'
82
+ import { newSourceId } from '../source-identity.ts'
81
83
  import { DeleteDocumentDialog } from './DeleteDocumentDialog.tsx'
82
84
  import { InfoHint } from './InfoHint.tsx'
83
85
  import { PipelineNotice } from './PipelineNotice.tsx'
@@ -93,7 +95,6 @@ export interface KnowledgeBase {
93
95
  isDefault: boolean
94
96
  /** 这一库的坐标提取备注(0068)。逐字进每一次抽取的提示词。 */
95
97
  extractionNote?: string
96
- compileModel?: CompileModelSelectionView
97
98
  createdAt: string
98
99
  updatedAt: string
99
100
  sourceCount: number
@@ -442,12 +443,16 @@ export interface KnowledgeCardView {
442
443
  interface CardMatrixResult {
443
444
  view: MatrixView
444
445
  sceneOnly: number
446
+ factCount: number
447
+ wayCount: number
448
+ unclassifiedCount: number
445
449
  }
446
450
 
447
451
  interface CardMatrixFilter {
448
452
  objectId?: string
449
453
  actionId?: string
450
454
  sceneId?: string
455
+ wikiKind?: 'fact' | 'way' | 'unclassified'
451
456
  }
452
457
 
453
458
  /**
@@ -458,18 +463,37 @@ interface CardMatrixFilter {
458
463
  * 不再新增一条服务端聚合接口,避免两份统计在编译刚结束时短暂不同步。
459
464
  */
460
465
  function cardMatrixOf(cards: readonly KnowledgeCardView[], lookup: AxisLookup): CardMatrixResult {
461
- interface Bucket { object: string | null; action: string | null; scene: string | null; count: number }
466
+ interface Bucket { object: string | null; action: string | null; scene: string | null; count: number; factCount: number; wayCount: number; unclassifiedCount: number }
462
467
  const buckets = new Map<string, Bucket>()
463
468
  let noCoordinate = 0
464
469
  let sceneOnly = 0
465
- const add = (coordinate: Bucket): void => {
470
+ let factCount = 0
471
+ let wayCount = 0
472
+ let unclassifiedCount = 0
473
+ const add = (coordinate: Omit<Bucket, 'count' | 'factCount' | 'wayCount' | 'unclassifiedCount'>, wikiKind: 'fact' | 'way' | 'unclassified'): void => {
466
474
  const key = JSON.stringify([coordinate.object, coordinate.action, coordinate.scene])
467
475
  const previous = buckets.get(key)
468
- if (previous) previous.count += 1
469
- else buckets.set(key, { ...coordinate, count: 1 })
476
+ if (previous) {
477
+ previous.count += 1
478
+ if (wikiKind === 'fact') previous.factCount += 1
479
+ else if (wikiKind === 'way') previous.wayCount += 1
480
+ else previous.unclassifiedCount += 1
481
+ } else buckets.set(key, {
482
+ ...coordinate,
483
+ count: 1,
484
+ factCount: wikiKind === 'fact' ? 1 : 0,
485
+ wayCount: wikiKind === 'way' ? 1 : 0,
486
+ unclassifiedCount: wikiKind === 'unclassified' ? 1 : 0,
487
+ })
470
488
  }
471
489
 
472
490
  for (const card of cards) {
491
+ const wikiKind = card.metadata?.wiki_kind === 'fact' || card.metadata?.wiki_kind === 'way'
492
+ ? card.metadata.wiki_kind
493
+ : 'unclassified'
494
+ if (wikiKind === 'fact') factCount += 1
495
+ else if (wikiKind === 'way') wayCount += 1
496
+ else unclassifiedCount += 1
473
497
  const coordinate = card.coordinate
474
498
  if (coordinate === undefined) { noCoordinate += 1; continue }
475
499
  const object = coordinate.objectId === '' ? null : lookup.nameOf('object', coordinate.objectId)
@@ -477,7 +501,7 @@ function cardMatrixOf(cards: readonly KnowledgeCardView[], lookup: AxisLookup):
477
501
  const scene = coordinate.sceneId === '' ? null : lookup.nameOf('scene', coordinate.sceneId)
478
502
  if (object === null && action === null && scene === null) { noCoordinate += 1; continue }
479
503
  if (object === null && action === null) { sceneOnly += 1; continue }
480
- add({ object, action, scene, count: 1 })
504
+ add({ object, action, scene }, wikiKind)
481
505
  }
482
506
 
483
507
  const totals = (pick: (bucket: Bucket) => string | null): Map<string, number> => {
@@ -493,6 +517,11 @@ function cardMatrixOf(cards: readonly KnowledgeCardView[], lookup: AxisLookup):
493
517
  .map(([name]) => name)
494
518
  const objects = rank(totals(bucket => bucket.object))
495
519
  const actions = rank(totals(bucket => bucket.action))
520
+ const breakdownFor = (selected: readonly Bucket[]) => [
521
+ { id: 'fact', label: 'F', title: 'Wiki Fact', count: selected.reduce((sum, bucket) => sum + bucket.factCount, 0) },
522
+ { id: 'way', label: 'W', title: 'Wiki Way', count: selected.reduce((sum, bucket) => sum + bucket.wayCount, 0) },
523
+ { id: 'unclassified', label: '未', title: '未归类 Wiki', count: selected.reduce((sum, bucket) => sum + bucket.unclassifiedCount, 0) },
524
+ ].filter(item => item.count > 0)
496
525
  const groupsFor = (object: string | null, action: string | null) => [...buckets.values()]
497
526
  .filter(bucket => bucket.object === object && bucket.action === action)
498
527
  .sort((left, right) => (left.scene === null ? -1 : right.scene === null ? 1 : 0)
@@ -500,15 +529,19 @@ function cardMatrixOf(cards: readonly KnowledgeCardView[], lookup: AxisLookup):
500
529
  .map(bucket => ({ scene: bucket.scene ?? '', count: bucket.count }))
501
530
  const cells = objects.flatMap(object => actions.flatMap(action => {
502
531
  const groups = groupsFor(object, action)
503
- return groups.length > 0 ? [{ object, action, groups }] : []
532
+ if (groups.length === 0) return []
533
+ const cellBuckets = [...buckets.values()].filter(bucket => bucket.object === object && bucket.action === action)
534
+ return [{ object, action, groups, breakdown: breakdownFor(cellBuckets) }]
504
535
  }))
505
536
  const noAction = objects.flatMap(object => {
506
537
  const groups = groupsFor(object, null)
507
- return groups.length > 0 ? [{ name: object, groups }] : []
538
+ const looseBuckets = [...buckets.values()].filter(bucket => bucket.object === object && bucket.action === null)
539
+ return groups.length > 0 ? [{ name: object, groups, breakdown: breakdownFor(looseBuckets) }] : []
508
540
  })
509
541
  const noObject = actions.flatMap(action => {
510
542
  const groups = groupsFor(null, action)
511
- return groups.length > 0 ? [{ name: action, groups }] : []
543
+ const looseBuckets = [...buckets.values()].filter(bucket => bucket.object === null && bucket.action === action)
544
+ return groups.length > 0 ? [{ name: action, groups, breakdown: breakdownFor(looseBuckets) }] : []
512
545
  })
513
546
 
514
547
  return {
@@ -519,6 +552,9 @@ function cardMatrixOf(cards: readonly KnowledgeCardView[], lookup: AxisLookup):
519
552
  unitLabel: '卡片',
520
553
  },
521
554
  sceneOnly,
555
+ factCount,
556
+ wayCount,
557
+ unclassifiedCount,
522
558
  }
523
559
  }
524
560
 
@@ -582,11 +618,11 @@ interface Props {
582
618
  onActiveBaseChange?: (base: { id: string; name: string } | null) => void
583
619
  /** 知识库设置作用域:召回 / 重排面板读它算默认参数,收录闸门读它决定能不能上传。 */
584
620
  knowledge?: SettingsScopeLike
585
- /** 架构图节点跳转过来时要落到哪一栏。`nonce` 变化即重新应用。 */
586
- focus?: { tab: KnowledgeTab; nonce: number }
621
+ /** 外部跳转落到哪一栏;可选 Wiki ID 会在目标栏加载后打开原卡片。`nonce` 变化即重新应用。 */
622
+ focus?: { tab: KnowledgeTab; nonce: number; wikiEntityId?: string; wikiEntityType?: string }
587
623
  /**
588
624
  * 卡片头的「参数」chip 落到设置抽屉的哪一块(0034)。**设置不在这一层** —— 这里只
589
- * 转交一个「打开并定位」的意图,抽屉与它读写的 scope 都还在 `KnowledgePanel` 手里。
625
+ * 转交一个「打开并定位」的动作,抽屉与它读写的 scope 都还在 `KnowledgePanel` 手里。
590
626
  */
591
627
  onOpenSettings?: (target: SettingsNode) => void
592
628
  }
@@ -601,6 +637,8 @@ export function KnowledgeBaseTab({ visible, onChanged, onActiveBaseChange, knowl
601
637
  const [bases, setBases] = useState<KnowledgeBase[]>([])
602
638
  const [activeId, setActiveId] = useState('')
603
639
  const [documents, setDocuments] = useState<KnowledgeDocument[]>([])
640
+ /** Avoid opening a same-ID card from the previous base while a base switch is still loading. */
641
+ const [cardsBaseId, setCardsBaseId] = useState('')
604
642
  /**
605
643
  * 每份文档有几条抽取轨迹(0063)。它是**审计计数**,不是「这份文档是什么」,
606
644
  * 所以它不进 `KnowledgeDocument`,单独一张按 docId 查的表。
@@ -750,8 +788,6 @@ export function KnowledgeBaseTab({ visible, onChanged, onActiveBaseChange, knowl
750
788
  changeTab('knowledge')
751
789
  setKnowledgeContent('event')
752
790
  }, [changeTab])
753
- /** 编译设置抽屉:本库的编译模型、坐标实体与抽取备注。 */
754
- const [typeDef, setTypeDef] = useState(false)
755
791
  const [detail, setDetail] = useState<{ document: KnowledgeDocumentDetail; versions: DocumentVersion[] } | null>(null)
756
792
  const [detailLoading, setDetailLoading] = useState(false)
757
793
  const [restoreTarget, setRestoreTarget] = useState<VersionManagerEntry | null>(null)
@@ -892,7 +928,11 @@ export function KnowledgeBaseTab({ visible, onChanged, onActiveBaseChange, knowl
892
928
  try {
893
929
  const payload = await requestJson<{ cards: KnowledgeCardView[] }>(`/knowledge/cards?kb=${encodeURIComponent(baseId)}`)
894
930
  setCards(payload.data?.cards ?? [])
895
- } catch { setCards([]) }
931
+ setCardsBaseId(baseId)
932
+ } catch {
933
+ setCards([])
934
+ setCardsBaseId(baseId)
935
+ }
896
936
  }, [])
897
937
 
898
938
  /**
@@ -1006,65 +1046,18 @@ export function KnowledgeBaseTab({ visible, onChanged, onActiveBaseChange, knowl
1006
1046
 
1007
1047
  // ----------------------------------------------------------------- actions
1008
1048
 
1009
- /**
1010
- * 保存坐标提取备注(0068)。
1011
- *
1012
- * 与知识矩阵实体**同一条写路径**(PATCH 那个库),因为它就住在同一个注册表里 ——
1013
- * 同一份数据不因为多了一个入口就多一个写入口。
1014
- */
1015
- const saveExtractionNote = async (note: string): Promise<void> => {
1016
- const base = active
1017
- if (!base) return
1018
- setBusy('extraction-note')
1019
- try {
1020
- await requestJson(`/knowledge/bases/${encodeURIComponent(base.id)}`, {
1021
- method: 'PATCH',
1022
- headers: { 'content-type': 'application/json' },
1023
- body: JSON.stringify({ extractionNote: note }),
1024
- })
1025
- await loadBases(base.id)
1026
- say('success', note.trim() === ''
1027
- ? '备注已清空 —— 下一次抽取不再带这段约定'
1028
- : `备注已保存(${note.trim().length} 字),下一次抽取会逐字带上它`)
1029
- } catch (error) {
1030
- say('error', error instanceof Error ? error.message : String(error))
1031
- } finally {
1032
- setBusy(null)
1033
- }
1034
- }
1035
-
1036
- /** 保存本库的编译模型路由;配置落在知识库注册表,不复制 DSH 的模型目录。 */
1037
- const saveCompileModel = async (selection: CompileModelSelectionView): Promise<void> => {
1038
- const base = active
1039
- if (!base) return
1040
- setBusy('compile-model')
1041
- try {
1042
- await requestJson(`/knowledge/bases/${encodeURIComponent(base.id)}`, {
1043
- method: 'PATCH',
1044
- headers: { 'content-type': 'application/json' },
1045
- body: JSON.stringify({ compileModel: selection }),
1046
- })
1047
- await loadBases(base.id)
1048
- say('success', selection.mode === 'default' ? '编译模型已改为跟随 DSH 默认模型' : `编译模型已固定为 ${selection.provider} / ${selection.model}`)
1049
- } catch (error) {
1050
- say('error', error instanceof Error ? error.message : String(error))
1051
- } finally {
1052
- setBusy(null)
1053
- }
1054
- }
1055
-
1056
1049
  /**
1057
1050
  * @returns 写入时那些**必须让人看见**的话(图片一张都没进来、标题重复…)。
1058
1051
  * 调用方把它挂到这个文件的 `note` 上:一份 40 张图的文档只上传了 `.md`,后果要到
1059
1052
  * 编译之后才在卡片里显形 —— 那时人已经不知道是哪一步丢的了。
1060
1053
  */
1061
- const writeSource = async (title: string, content: string, externalId: string): Promise<string[]> => {
1054
+ const writeSource = async (title: string, content: string, sourceId: string): Promise<string[]> => {
1062
1055
  const base = active
1063
1056
  if (!base) throw new Error('请先选择一个知识库')
1064
1057
  const payload = await requestJson<{ assets?: { refs: number; missing: number } }>('/knowledge/sources', {
1065
1058
  method: 'POST',
1066
1059
  headers: { 'content-type': 'application/json' },
1067
- body: JSON.stringify({ kb: base.id, title, external_id: externalId, content }),
1060
+ body: JSON.stringify({ kb: base.id, source_id: sourceId, title, external_id: `upload:${sourceId}`, content, allow_duplicate_title: true, metadata: { original_filename: title } }),
1068
1061
  })
1069
1062
  const assets = payload.data?.assets
1070
1063
  /*
@@ -1072,13 +1065,12 @@ export function KnowledgeBaseTab({ visible, onChanged, onActiveBaseChange, knowl
1072
1065
  * 长的那一句(服务端的 warning)留给处理日志与 Agent 那条路 —— 同一件事在两处的
1073
1066
  * 篇幅本来就该不同。
1074
1067
  */
1075
- return assets !== undefined && assets.missing > 0 && assets.missing === assets.refs
1076
- ? [`正文引用的 ${assets.refs} 张图片一张都没进库`]
1077
- : []
1068
+ return [...(payload.warnings ?? []), ...(assets !== undefined && assets.missing > 0 && assets.missing === assets.refs
1069
+ ? [`正文引用的 ${assets.refs} 张图片一张都没进库`] : [])]
1078
1070
  }
1079
1071
 
1080
1072
  /** Convert one file with the kernel, then ingest the delivery into the active base. */
1081
- const convertAndIngest = async (file: File, onNote?: (note: string) => void): Promise<string[]> => {
1073
+ const convertAndIngest = async (file: File, sourceId: string, onNote?: (note: string) => void): Promise<string[]> => {
1082
1074
  const base = active
1083
1075
  if (!base) throw new Error('请先选择一个知识库')
1084
1076
  const form = new FormData()
@@ -1104,7 +1096,7 @@ export function KnowledgeBaseTab({ visible, onChanged, onActiveBaseChange, knowl
1104
1096
  const ingested = await requestJson('/convert/api/ingest', {
1105
1097
  method: 'POST',
1106
1098
  headers: { 'content-type': 'application/json' },
1107
- body: JSON.stringify({ task_id: taskId, kb: base.id, title: file.name }),
1099
+ body: JSON.stringify({ task_id: taskId, kb: base.id, title: file.name, source_id: sourceId, allow_duplicate_title: true }),
1108
1100
  })
1109
1101
  /*
1110
1102
  * 内核那条路的备注取自它的 warning:那一层已经会说出「N 个图片资产未复制」这类话,
@@ -1180,6 +1172,7 @@ export function KnowledgeBaseTab({ visible, onChanged, onActiveBaseChange, knowl
1180
1172
  if (files.length === 0 || importRunning || busy === 'compile') return
1181
1173
  setImportItems(current => [...current, ...files.map(file => ({
1182
1174
  id: `import-${importSeq.current++}`,
1175
+ sourceId: newSourceId(),
1183
1176
  file,
1184
1177
  name: file.name,
1185
1178
  ext: (file.name.slice(file.name.lastIndexOf('.') + 1) || '').toLowerCase(),
@@ -1237,14 +1230,10 @@ export function KnowledgeBaseTab({ visible, onChanged, onActiveBaseChange, knowl
1237
1230
  let notes: string[] = []
1238
1231
  if (DIRECT_READ_EXT.has(ext)) {
1239
1232
  const content = await directReadText(item.file)
1240
- /*
1241
- * 文件名是浏览器直读上传的稳定身份。lastModified 只能描述这一次
1242
- * 选择文件的时间,不能进入 external_id;否则同一文件更新后会被当成新来源,
1243
- * 随后又被同标题校验拒绝,永远进不了版本链。
1244
- */
1245
- notes = await writeSource(item.name, content, `upload:${item.name}`)
1233
+ // Queue identity is allocated before reading; filenames never select an existing source.
1234
+ notes = await writeSource(item.name, content, item.sourceId)
1246
1235
  } else {
1247
- notes = await convertAndIngest(item.file, note => patchImportItem(item.id, { note }))
1236
+ notes = await convertAndIngest(item.file, item.sourceId, note => patchImportItem(item.id, { note }))
1248
1237
  }
1249
1238
  const note = notes.length === 0 ? undefined : notes.join(';')
1250
1239
  patchImportItem(item.id, { stage: 'ingested', ...(note === undefined ? { note: undefined } : { note }) })
@@ -1630,6 +1619,19 @@ export function KnowledgeBaseTab({ visible, onChanged, onActiveBaseChange, knowl
1630
1619
  }
1631
1620
  }
1632
1621
 
1622
+ /** 智能归因中的召回条目可以跳回 Wiki,并在这里打开同一张卡片的原始预览。 */
1623
+ const openedWikiFocusRef = useRef<string | null>(null)
1624
+ useEffect(() => {
1625
+ if (!visible || focus?.tab !== 'wiki' || !focus.wikiEntityId || !activeId || cardsBaseId !== activeId) return
1626
+ const focusKey = `${focus.nonce}:${activeId}:${focus.wikiEntityId}`
1627
+ if (openedWikiFocusRef.current === focusKey) return
1628
+ const card = cards.find(item => item.entityId === focus.wikiEntityId
1629
+ && (focus.wikiEntityType === undefined || item.entityType === focus.wikiEntityType))
1630
+ if (!card) return
1631
+ openedWikiFocusRef.current = focusKey
1632
+ void openCard(card)
1633
+ }, [visible, focus?.tab, focus?.nonce, focus?.wikiEntityId, focus?.wikiEntityType, activeId, cardsBaseId, cards])
1634
+
1633
1635
  const compareCardVersion = async (entry: VersionManagerEntry): Promise<void> => {
1634
1636
  const baseId = activeIdRef.current
1635
1637
  const current = cardDetail
@@ -1964,6 +1966,7 @@ export function KnowledgeBaseTab({ visible, onChanged, onActiveBaseChange, knowl
1964
1966
  const cell = {
1965
1967
  object: cardMatrixFilter.objectId === '' ? null : axisLookup.nameOf('object', cardMatrixFilter.objectId!),
1966
1968
  action: cardMatrixFilter.actionId === '' ? null : axisLookup.nameOf('action', cardMatrixFilter.actionId!),
1969
+ ...(cardMatrixFilter.wikiKind ? { breakdownId: cardMatrixFilter.wikiKind } : {}),
1967
1970
  }
1968
1971
  return hasScene
1969
1972
  ? { cell, group: { ...cell, scene: cardMatrixFilter.sceneId === '' ? '' : axisLookup.nameOf('scene', cardMatrixFilter.sceneId!) } }
@@ -1987,6 +1990,20 @@ export function KnowledgeBaseTab({ visible, onChanged, onActiveBaseChange, knowl
1987
1990
  const same = cardMatrixFilter.objectId === next.objectId
1988
1991
  && cardMatrixFilter.actionId === next.actionId
1989
1992
  && cardMatrixFilter.sceneId === undefined
1993
+ && cardMatrixFilter.wikiKind === undefined
1994
+ setCardMatrixFilter(same ? {} : next)
1995
+ }
1996
+ const pickCardBreakdown = (pick: MatrixCellPick, breakdownId: string): void => {
1997
+ if (breakdownId !== 'fact' && breakdownId !== 'way' && breakdownId !== 'unclassified') return
1998
+ const next: CardMatrixFilter = {
1999
+ objectId: cardAxisId('object', pick.object),
2000
+ actionId: cardAxisId('action', pick.action),
2001
+ wikiKind: breakdownId,
2002
+ }
2003
+ const same = cardMatrixFilter.objectId === next.objectId
2004
+ && cardMatrixFilter.actionId === next.actionId
2005
+ && cardMatrixFilter.sceneId === undefined
2006
+ && cardMatrixFilter.wikiKind === next.wikiKind
1990
2007
  setCardMatrixFilter(same ? {} : next)
1991
2008
  }
1992
2009
  const pickCardGroup = (pick: MatrixPick): void => {
@@ -1998,13 +2015,14 @@ export function KnowledgeBaseTab({ visible, onChanged, onActiveBaseChange, knowl
1998
2015
  const same = cardMatrixFilter.objectId === next.objectId
1999
2016
  && cardMatrixFilter.actionId === next.actionId
2000
2017
  && cardMatrixFilter.sceneId === next.sceneId
2018
+ && cardMatrixFilter.wikiKind === undefined
2001
2019
  setCardMatrixFilter(same ? {} : next)
2002
2020
  }
2003
2021
  const pickCardAxis = (pick: MatrixAxisPick): void => {
2004
2022
  const id = cardAxisId(pick.axis, pick.name)
2005
2023
  const same = pick.axis === 'object'
2006
- ? cardMatrixFilter.objectId === id && cardMatrixFilter.actionId === undefined && cardMatrixFilter.sceneId === undefined
2007
- : cardMatrixFilter.actionId === id && cardMatrixFilter.objectId === undefined && cardMatrixFilter.sceneId === undefined
2024
+ ? cardMatrixFilter.objectId === id && cardMatrixFilter.actionId === undefined && cardMatrixFilter.sceneId === undefined && cardMatrixFilter.wikiKind === undefined
2025
+ : cardMatrixFilter.actionId === id && cardMatrixFilter.objectId === undefined && cardMatrixFilter.sceneId === undefined && cardMatrixFilter.wikiKind === undefined
2008
2026
  setCardMatrixFilter(same ? {} : pick.axis === 'object' ? { objectId: id } : { actionId: id })
2009
2027
  }
2010
2028
 
@@ -2017,6 +2035,10 @@ export function KnowledgeBaseTab({ visible, onChanged, onActiveBaseChange, knowl
2017
2035
  && (cardMatrixFilter.objectId === undefined || (card.coordinate?.objectId ?? '') === cardMatrixFilter.objectId)
2018
2036
  && (cardMatrixFilter.actionId === undefined || (card.coordinate?.actionId ?? '') === cardMatrixFilter.actionId)
2019
2037
  && (cardMatrixFilter.sceneId === undefined || (card.coordinate?.sceneId ?? '') === cardMatrixFilter.sceneId)
2038
+ && (cardMatrixFilter.wikiKind === undefined
2039
+ || (cardMatrixFilter.wikiKind === 'unclassified'
2040
+ ? card.metadata?.wiki_kind !== 'fact' && card.metadata?.wiki_kind !== 'way'
2041
+ : card.metadata?.wiki_kind === cardMatrixFilter.wikiKind))
2020
2042
  && (!needle || `${card.title}\n${card.entityId}\n${card.summary ?? ''}`.toLocaleLowerCase().includes(needle)))
2021
2043
  }, [cards, cardMatrixFilter, cardQuery, cardStatus])
2022
2044
 
@@ -2198,32 +2220,34 @@ export function KnowledgeBaseTab({ visible, onChanged, onActiveBaseChange, knowl
2198
2220
  <AxisMatrix
2199
2221
  view={cardMatrixView}
2200
2222
  selection={cardMatrixSelection}
2201
- ariaLabel="WikiCard 矩阵:对象 × 动作 × 场景"
2223
+ ariaLabel="Wiki 矩阵:对象 × 动作,显示 Wiki Fact 与 Wiki Way 数量"
2202
2224
  frame={false}
2203
2225
  bounded
2204
2226
  onPickCell={pickCardCell}
2227
+ onPickBreakdown={pickCardBreakdown}
2205
2228
  onPickGroup={pickCardGroup}
2206
2229
  onPickAxis={pickCardAxis}
2207
2230
  onExpand={() => setCardMatrixFull(true)}
2208
- title={<>卡片矩阵<span className={styles.blockSub}>(对象 × 动作 × 场景)</span></>}
2209
- note="格内 = 卡片数 · 选中后筛选卡片"
2210
- meta={<>{cardMatrixView.withKnowledge} / {cardMatrixView.slots} 格有卡片</>}
2231
+ title={<>Wiki 矩阵<span className={styles.blockSub}>(对象 × 动作 × 场景)</span></>}
2232
+ note="总数 + 类型数:F = Wiki Fact · W = Wiki Way;点类型数筛选"
2233
+ meta={<>Fact {cardMatrix.factCount} · Way {cardMatrix.wayCount}</>}
2211
2234
  footer={<>
2212
2235
  {cardMatrix.sceneOnly > 0 && <p className={styles.trialMore}>另有 {cardMatrix.sceneOnly} 张卡片只有场景,两个主轴为空。</p>}
2213
2236
  {cardMatrixView.noCoordinate > 0 && <p className={styles.trialMore}>另有 {cardMatrixView.noCoordinate} 张卡片没有坐标。</p>}
2237
+ {cardMatrix.unclassifiedCount > 0 && <p className={styles.trialMore}>其中有 {cardMatrix.unclassifiedCount} 张 Wiki 尚未归类为 Fact 或 Way;点击格内“未”可筛选有坐标的未归类 Wiki。</p>}
2214
2238
  </>}
2215
2239
  />
2216
2240
  </section> : null}
2217
2241
  <Card className={styles.kbCard}>
2218
2242
  <div className={styles.kbCardHead}>
2219
- <div><h3><FileText size={13} />卡片</h3></div>
2243
+ <div><h3><FileText size={13} />Wiki</h3></div>
2220
2244
  {/* 构建设置的入口落在这里(2026-10-05 随「知识图谱」分栏一起搬):构建把这批
2221
2245
  卡片编成可召回的索引(索引版本写在步骤轨「物化」那一步的第二行),所以参数
2222
2246
  留在它加工的东西旁边 —— 与召回、重排把参数放在自己卡头上是同一条规矩。
2223
2247
  打开的还是那**一个**设置抽屉,只是落到「构建」分块。 */}
2224
2248
  <span className={styles.kbCardActions}>
2225
2249
  <span className={styles.kbCount}>
2226
- {visibleCards.length === cards.length ? `${cards.length} 张` : `筛出 ${visibleCards.length} / ${cards.length} 张`}
2250
+ {visibleCards.length === cards.length ? `${cards.length} 张 Wiki` : `筛出 ${visibleCards.length} / ${cards.length} 张 Wiki`}
2227
2251
  </span>
2228
2252
  {onOpenSettings && <SettingsChip target="build" onOpen={onOpenSettings} />}
2229
2253
  </span>
@@ -2261,20 +2285,21 @@ export function KnowledgeBaseTab({ visible, onChanged, onActiveBaseChange, knowl
2261
2285
  <Dialog open={cardMatrixFull} onOpenChange={setCardMatrixFull}>
2262
2286
  <DialogContent className={styles.matrixDialog}>
2263
2287
  <DialogHeader>
2264
- <DialogTitle className={styles.matrixDialogTitle}>卡片矩阵<span className={styles.blockSub}>(对象 × 动作 × 场景)</span></DialogTitle>
2288
+ <DialogTitle className={styles.matrixDialogTitle}>Wiki 矩阵<span className={styles.blockSub}>(对象 × 动作 × 场景)</span></DialogTitle>
2265
2289
  </DialogHeader>
2266
2290
  <div className={styles.matrixDialogBody}>
2267
2291
  <AxisMatrix
2268
2292
  view={cardMatrixView}
2269
2293
  selection={cardMatrixSelection}
2270
- ariaLabel="WikiCard 矩阵:对象 × 动作 × 场景(全屏)"
2294
+ ariaLabel="Wiki 矩阵:对象 × 动作,显示 Wiki Fact 与 Wiki Way 数量(全屏)"
2271
2295
  frame={false}
2272
2296
  onPickCell={pickCardCell}
2297
+ onPickBreakdown={pickCardBreakdown}
2273
2298
  onPickGroup={pickCardGroup}
2274
2299
  onPickAxis={pickCardAxis}
2275
- title={<>卡片矩阵<span className={styles.blockSub}>(对象 × 动作 × 场景)</span></>}
2276
- note="格内 = 卡片数 · 选中后筛选卡片"
2277
- meta={<>{cardMatrixView.withKnowledge} / {cardMatrixView.slots} 格有卡片</>}
2300
+ title={<>Wiki 矩阵<span className={styles.blockSub}>(对象 × 动作 × 场景)</span></>}
2301
+ note="总数 + 类型数:F = Wiki Fact · W = Wiki Way;点类型数筛选"
2302
+ meta={<>Fact {cardMatrix.factCount} · Way {cardMatrix.wayCount}</>}
2278
2303
  />
2279
2304
  </div>
2280
2305
  </DialogContent>
@@ -2302,10 +2327,13 @@ export function KnowledgeBaseTab({ visible, onChanged, onActiveBaseChange, knowl
2302
2327
  * 还是在讲卡片?现在它自己一栏,Wiki 只回答后半个问题。
2303
2328
  */
2304
2329
  const renderKnowledge = (): ReactNode => <Tabs value={knowledgeContent} onValueChange={value => setKnowledgeContent(value as 'knowledge' | 'procedure' | 'event')}>
2305
- <TabsList className={cn(styles.tabsList, styles.kbViewTabs)} aria-label="Fact、Procedure 与 Case 分类">
2306
- <TabsTrigger value="knowledge">Fact</TabsTrigger>
2307
- <TabsTrigger value="procedure">Procedure</TabsTrigger>
2308
- <TabsTrigger value="event">Case · {events.length}</TabsTrigger>
2330
+ {/* 三个页签同时给中文名与英文术语:英文是工具与 Wiki 里的正式名(Fact / Procedure /
2331
+ Case),中文是读它的人先认得的词。**页签不挂计数** —— 一栏里已经有分面计数与
2332
+ 块头计数,页签再写一个就会变成第三个「有多少」。 */}
2333
+ <TabsList className={cn(styles.tabsList, styles.kbViewTabs)} aria-label="事实、方法与案例分类">
2334
+ <TabsTrigger value="knowledge">事实 <span className="text-[9.5px] font-normal text-tita-ink-faint">Fact</span></TabsTrigger>
2335
+ <TabsTrigger value="procedure">方法 <span className="text-[9.5px] font-normal text-tita-ink-faint">Procedure</span></TabsTrigger>
2336
+ <TabsTrigger value="event">案例 <span className="text-[9.5px] font-normal text-tita-ink-faint">Case</span></TabsTrigger>
2309
2337
  </TabsList>
2310
2338
  <TabsContent value="knowledge" className={styles.viewStack}>
2311
2339
  {orphanNotice()}
@@ -2327,8 +2355,7 @@ export function KnowledgeBaseTab({ visible, onChanged, onActiveBaseChange, knowl
2327
2355
  <TabsContent value="event" className={styles.viewStack}>
2328
2356
  <div className="flex min-w-0 items-center justify-between gap-2">
2329
2357
  <div className="flex min-w-0 items-center gap-2">
2330
- <h3 className="text-[12px] font-semibold text-tita-ink">Case</h3>
2331
- <Badge tone="neutral">{events.length}</Badge>
2358
+ <h3 className="text-[12px] font-semibold text-tita-ink">案例 <span className="text-[10px] font-normal text-tita-ink-faint">Case</span></h3>
2332
2359
  </div>
2333
2360
  <Button variant="ghost" size="icon" aria-label="刷新 Case" title="刷新 Case" disabled={eventsLoading || !activeId} onClick={() => void loadEvents(activeId)}>
2334
2361
  <RefreshCw size={13} className={eventsLoading ? 'animate-spin' : undefined} />
@@ -2366,8 +2393,9 @@ export function KnowledgeBaseTab({ visible, onChanged, onActiveBaseChange, knowl
2366
2393
  还和库选择器里那行菜单重复了一遍 —— 同一件事两个入口,就会有人改完一处
2367
2394
  以为另一处也跟着变了。库路径也不在这里了:它是「存储位置」设置,
2368
2395
  入口在面板顶部的齿轮里。
2369
- 0034:「编译设置」挂在这一行 —— 它是**按库**的动作;而它**不是**第六个步骤:
2370
- 步骤轨是一条流水线,定义不属于任何一段(理由见 `TypeDefinitionDrawer.tsx`)。
2396
+ 0034:「编译设置」曾挂在这一行 —— 它是**按库**的动作,而它**不是**第六个步骤:
2397
+ 步骤轨是一条流水线,定义不属于任何一段。2026-10-28 起它搬进知识处理整页,
2398
+ 与「解析内核」一起,只在那一个页面上留入口。
2371
2399
  0035:**计数撤出这一行**,动作左对齐。计数没有丢 —— 步骤轨每一步第二行写的就是
2372
2400
  「这一段现在有多少东西」(`stepFacts`),而这一行只回答「能对它做什么」。
2373
2401
  2026-10-05:**知识库入口整条撤掉**(用户要求)。它原先在最左边,显示库名或选择器,
@@ -2376,41 +2404,18 @@ export function KnowledgeBaseTab({ visible, onChanged, onActiveBaseChange, knowl
2376
2404
  去改它,等于把一个部署级的事实伪装成一个可以随手切换的视图。默认库由
2377
2405
  `seedDefault()` 保证存在,所以撤掉「新建」也不会留下一个建不出库的空部署。 */}
2378
2406
  <div className={styles.kbToolbar}>
2379
- {/* 三个动作同形:图标 + 文字的 outline 按钮,同一个 30px 高。
2380
- 「解析内核」此前是一枚 `<a target="_blank">`,靠 `Button asChild` 拿外观。
2381
- 看起来对,但桌面壳里跑不通:应用地址是 `dsh-app://app/...`,而壳的
2382
- `setWindowOpenHandler` 只放行 http/https —— `/convert/` 被解析成应用内地址、
2383
- 直接 deny,表现就是「点了没有任何反应,也不报错」。
2384
- 现在走 `openAppPath()`:浏览器态开新标签页,桌面壳里换成 Host 的绝对地址再交给
2385
- 系统浏览器。**同一枚按钮,两种外壳做各自成立的那件事。**
2386
- 0042:「编译」原本在 wiki 栏的统计卡里(增量 + 全量两枚),现在它是**工具栏的
2387
- 一个动作** —— 编译是「对这个库做什么」,它和「选哪个库」「编译设置」同层,
2388
- 不属于任何一栏的内容。 */}
2407
+ {/* 一行一个动作:outline + 图标,同一个 30px 高。
2408
+ 0042:「编译」原本在 wiki 栏的统计卡里,后来是工具栏的一个动作。
2409
+ 2026-10-28:编译设置与解析内核的入口都搬进知识处理整页 —— 面板不再同时
2410
+ 兼任「处理现场」与「参数 / 上游工具」两种角色。 */}
2389
2411
  <span className={styles.kbToolbarActions}>
2390
- <Button
2391
- variant="outline"
2392
- size="sm"
2393
- disabled={!active}
2394
- title={active ? `编辑 ${active.name} 的编译设置` : '先选择一个知识库'}
2395
- onClick={() => setTypeDef(true)}
2396
- ><Tags size={13} />编译设置</Button>
2397
- {/* 一行顶栏里**一个入口**。此前这里是两枚(「知识处理」与「任务历史」),
2398
- 打开的却是同一个地址的两个位置 —— 同一样东西两个入口,人就会以为它们是两件事,
2399
- 点完一个再点另一个来确认「历史是不是同一份」。整合后只剩这一枚:
2400
- 页面上半段是四段阶段,下半段右侧就是运行记录,一屏之内都读得到。 */}
2401
- <Button variant="outline" size="sm" disabled={!active} title="运行阶段、处理范围与运行记录" onClick={() => {
2402
- if (active) void fullPage.open({ src: `/knowledge/pages/tasks?kb=${encodeURIComponent(active.id)}`, title: '知识处理与运行记录' })
2412
+ {/* 一行顶栏里**一个入口**。此前这里是三枚(编译设置 / 知识处理 / 解析内核),
2413
+ 打开的是同一件事的三个位置 —— 编译设置改的就是这条流水线的模型与坐标,
2414
+ 解析内核是它的上游。2026-10-28 起它们都住在知识处理整页里:
2415
+ 面板这一行只回答「去哪处理这个库」,不再兼任设置与上游工具。 */}
2416
+ <Button variant="outline" size="sm" disabled={!active} title="运行阶段、处理范围、编译设置与轨迹" onClick={() => {
2417
+ if (active) void fullPage.open({ src: `/knowledge/pages/tasks?kb=${encodeURIComponent(active.id)}`, title: '知识处理' })
2403
2418
  }}><Layers3 size={14} />知识处理</Button>
2404
- <Button
2405
- variant="outline"
2406
- size="sm"
2407
- title="打开解析内核控制台"
2408
- onClick={() => { void openAppPath('/convert/').then(opened => {
2409
- // 打不开就如实说。桌面壳拿不到 Host 地址时 `window.open` 会被静默拒绝 ——
2410
- // 那种「点了没反应」正是这次要修掉的东西,不能换个地方再犯一次。
2411
- if (!opened) setNotice({ tone: 'error', text: '打不开解析内核控制台:当前外壳下拿不到它的地址。' })
2412
- }) }}
2413
- ><FileUp size={14} />解析内核</Button>
2414
2419
  </span>
2415
2420
  {/* 说明靠右、动作靠左:这一行的主语是一排按钮,`?` 是这一排的脚注,
2416
2421
  所以它自己站在最右侧,而不是挤进按钮之间(挤进去会被读成某一枚按钮的附件)。 */}
@@ -2494,18 +2499,6 @@ export function KnowledgeBaseTab({ visible, onChanged, onActiveBaseChange, knowl
2494
2499
  onRetryFailed={() => void runImportQueue()}
2495
2500
  />
2496
2501
 
2497
- {/* 本库的坐标实体与抽取备注:按库持久化,和方案级设置分开。 */}
2498
- {active && <TypeDefinitionDrawer
2499
- open={typeDef}
2500
- onOpenChange={setTypeDef}
2501
- kbId={active.id}
2502
- busy={busy}
2503
- extractionNote={active.extractionNote ?? ''}
2504
- onSaveExtractionNote={saveExtractionNote}
2505
- compileModel={active.compileModel ?? { mode: 'default' }}
2506
- onSaveCompileModel={saveCompileModel}
2507
- />}
2508
-
2509
2502
  <RestoreDialog
2510
2503
  open={restoreTarget !== null}
2511
2504
  onOpenChange={open => { if (!open) setRestoreTarget(null) }}