dsh-data-domain-knowledge 0.1.0-alpha.2 → 0.1.0-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.
- package/README.md +147 -19
- package/cordis.patch.yml +25 -10
- package/lib/client.js +6400 -3326
- package/lib/doc-page.js +7761 -0
- package/lib/types/convert/host.d.ts.map +1 -1
- package/lib/types/convert/host.js +63 -11
- package/lib/types/convert/host.js.map +1 -1
- package/lib/types/convert/ingest.d.ts +7 -4
- package/lib/types/convert/ingest.d.ts.map +1 -1
- package/lib/types/convert/ingest.js +52 -14
- package/lib/types/convert/ingest.js.map +1 -1
- package/lib/types/convert/types.d.ts +2 -2
- package/lib/types/convert/types.d.ts.map +1 -1
- package/lib/types/convert/types.js +2 -2
- package/lib/types/convert/types.js.map +1 -1
- package/lib/types/diagnosis/cards.d.ts +8 -7
- package/lib/types/diagnosis/cards.d.ts.map +1 -1
- package/lib/types/diagnosis/cards.js +119 -49
- package/lib/types/diagnosis/cards.js.map +1 -1
- package/lib/types/diagnosis/run.d.ts +57 -0
- package/lib/types/diagnosis/run.d.ts.map +1 -0
- package/lib/types/diagnosis/run.js +245 -0
- package/lib/types/diagnosis/run.js.map +1 -0
- package/lib/types/diagnosis/schema.d.ts +25 -2
- package/lib/types/diagnosis/schema.d.ts.map +1 -1
- package/lib/types/diagnosis/schema.js +64 -25
- package/lib/types/diagnosis/schema.js.map +1 -1
- package/lib/types/diagnosis/seed-source.d.ts +17 -2
- package/lib/types/diagnosis/seed-source.d.ts.map +1 -1
- package/lib/types/diagnosis/seed-source.js +19 -2
- package/lib/types/diagnosis/seed-source.js.map +1 -1
- package/lib/types/diagnosis/seed.d.ts +32 -42
- package/lib/types/diagnosis/seed.d.ts.map +1 -1
- package/lib/types/diagnosis/seed.js +187 -51
- package/lib/types/diagnosis/seed.js.map +1 -1
- package/lib/types/diagnosis/store.d.ts +95 -25
- package/lib/types/diagnosis/store.d.ts.map +1 -1
- package/lib/types/diagnosis/store.js +297 -166
- package/lib/types/diagnosis/store.js.map +1 -1
- package/lib/types/diagnosis/types.d.ts +168 -16
- package/lib/types/diagnosis/types.d.ts.map +1 -1
- package/lib/types/diagnosis/types.js +1 -1
- package/lib/types/diagnosis/types.js.map +1 -1
- package/lib/types/document-page.d.ts +39 -0
- package/lib/types/document-page.d.ts.map +1 -0
- package/lib/types/document-page.js +43 -0
- package/lib/types/document-page.js.map +1 -0
- package/lib/types/extract-model.d.ts +67 -27
- package/lib/types/extract-model.d.ts.map +1 -1
- package/lib/types/extract-model.js +225 -97
- package/lib/types/extract-model.js.map +1 -1
- package/lib/types/extract.d.ts +16 -0
- package/lib/types/extract.d.ts.map +1 -1
- package/lib/types/extract.js +19 -2
- package/lib/types/extract.js.map +1 -1
- package/lib/types/fs-atomic.d.ts +11 -0
- package/lib/types/fs-atomic.d.ts.map +1 -0
- package/lib/types/fs-atomic.js +41 -0
- package/lib/types/fs-atomic.js.map +1 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +466 -63
- package/lib/types/index.js.map +1 -1
- package/lib/types/intent/types.js +1 -1
- package/lib/types/intent/types.js.map +1 -1
- package/lib/types/knowledge-bases.d.ts +23 -12
- package/lib/types/knowledge-bases.d.ts.map +1 -1
- package/lib/types/knowledge-bases.js +37 -44
- package/lib/types/knowledge-bases.js.map +1 -1
- package/lib/types/layout.d.ts +3 -3
- package/lib/types/layout.d.ts.map +1 -1
- package/lib/types/layout.js +4 -4
- package/lib/types/layout.js.map +1 -1
- package/lib/types/migrate.d.ts +1 -1
- package/lib/types/migrate.js +2 -2
- package/lib/types/migrate.js.map +1 -1
- package/lib/types/model-text.d.ts +99 -0
- package/lib/types/model-text.d.ts.map +1 -0
- package/lib/types/model-text.js +103 -0
- package/lib/types/model-text.js.map +1 -0
- package/lib/types/present.d.ts +239 -0
- package/lib/types/present.d.ts.map +1 -0
- package/lib/types/present.js +255 -0
- package/lib/types/present.js.map +1 -0
- package/lib/types/recall-rank.d.ts +37 -0
- package/lib/types/recall-rank.d.ts.map +1 -1
- package/lib/types/recall-rank.js +41 -1
- package/lib/types/recall-rank.js.map +1 -1
- package/lib/types/runtime.d.ts +564 -31
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/runtime.js +2645 -292
- package/lib/types/runtime.js.map +1 -1
- package/lib/types/settings-schema.d.ts +4 -2
- package/lib/types/settings-schema.d.ts.map +1 -1
- package/lib/types/settings-schema.js +2 -0
- package/lib/types/settings-schema.js.map +1 -1
- package/lib/types/source-images.d.ts +118 -0
- package/lib/types/source-images.d.ts.map +1 -0
- package/lib/types/source-images.js +288 -0
- package/lib/types/source-images.js.map +1 -0
- package/lib/types/types.d.ts +356 -28
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js +112 -18
- package/lib/types/types.js.map +1 -1
- package/lib/types/wiki-finalize.d.ts +140 -0
- package/lib/types/wiki-finalize.d.ts.map +1 -0
- package/lib/types/wiki-finalize.js +269 -0
- package/lib/types/wiki-finalize.js.map +1 -0
- package/lib/types/wiki-types.d.ts +10 -2
- package/lib/types/wiki-types.d.ts.map +1 -1
- package/lib/types/wiki-types.js +27 -30
- package/lib/types/wiki-types.js.map +1 -1
- package/package.json +4 -3
- package/preset/tita-data-agent/agent.cordis.yml +25 -10
- package/src/client/DirectoryPicker.tsx +12 -12
- package/src/client/DrawerSection.tsx +18 -0
- package/src/client/InfoHint.tsx +21 -8
- package/src/client/IntentTab.tsx +20 -26
- package/src/client/KnowledgeBaseTab.tsx +458 -158
- package/src/client/KnowledgePanel.tsx +44 -10
- package/src/client/KnowledgeStepRail.tsx +3 -0
- package/src/client/KnowledgeToolRow.tsx +331 -0
- package/src/client/NodeSettings.tsx +20 -22
- package/src/client/PreviewSections.tsx +178 -43
- package/src/client/RecallPanel.tsx +418 -127
- package/src/client/TypeDefinitionDrawer.tsx +154 -54
- package/src/client/WikiCardReview.tsx +266 -0
- package/src/client/WikiFlowCard.tsx +57 -95
- package/src/client/WikiTypeEditor.tsx +197 -95
- package/src/client/arch/AgentArchitectureGraph.tsx +30 -14
- package/src/client/arch/NodeLogicPanel.tsx +6 -3
- package/src/client/arch/model.ts +164 -26
- package/src/client/architecture.css +25 -10
- package/src/client/architecture.tsx +66 -41
- package/src/client/asset-images.ts +125 -0
- package/src/client/attribution/AttributionGraph.tsx +481 -34
- package/src/client/attribution/AttributionTab.tsx +501 -106
- package/src/client/attribution/CaseView.tsx +9 -14
- package/src/client/attribution/GraphStage.tsx +25 -18
- package/src/client/attribution/RunView.tsx +27 -9
- package/src/client/attribution/StageChrome.tsx +280 -140
- package/src/client/attribution/graph-options.ts +611 -47
- package/src/client/attribution/model.ts +559 -30
- package/src/client/convert/ConvertPanel.tsx +36 -4
- package/src/client/doc-page.css +134 -0
- package/src/client/doc-page.tsx +242 -0
- package/src/client/index.ts +18 -0
- package/src/client/panel-state.ts +105 -0
- package/src/client/problem-domains.ts +1 -1
- package/src/client/request.ts +36 -0
- package/src/client/styles.css +397 -54
- package/src/client/useFitScale.ts +15 -2
- package/src/client/wiki-type-groups.ts +86 -0
- package/src/convert/host.ts +68 -9
- package/src/convert/ingest.ts +61 -18
- package/src/convert/types.ts +2 -2
- package/src/diagnosis/cards.ts +120 -50
- package/src/diagnosis/run.ts +251 -0
- package/src/diagnosis/schema.ts +67 -26
- package/src/diagnosis/seed-source.ts +29 -4
- package/src/diagnosis/seed.ts +187 -85
- package/src/diagnosis/store.ts +310 -173
- package/src/diagnosis/types.ts +181 -16
- package/src/document-page.ts +51 -0
- package/src/extract-model.ts +254 -99
- package/src/extract.ts +35 -3
- package/src/fs-atomic.ts +41 -0
- package/src/index.ts +499 -62
- package/src/intent/types.ts +1 -1
- package/src/knowledge-bases.ts +50 -49
- package/src/layout.ts +4 -4
- package/src/migrate.ts +2 -2
- package/src/model-text.ts +126 -0
- package/src/present.ts +367 -0
- package/src/recall-rank.ts +57 -2
- package/src/runtime.ts +2802 -293
- package/src/settings-schema.ts +2 -0
- package/src/source-images.ts +326 -0
- package/src/types.ts +446 -45
- package/src/wiki-finalize.ts +310 -0
- package/src/wiki-types.ts +28 -32
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
ChevronDown,
|
|
2
|
+
CircleHelp, ChevronDown, ChevronRight, Maximize2, Minimize2, RotateCcw, Search,
|
|
3
|
+
SlidersHorizontal, X,
|
|
3
4
|
} from '@tita-data-agent/ui/icons'
|
|
4
5
|
import type { ReactNode } from 'react'
|
|
5
6
|
import { Badge, Button, Input, cn } from '@tita-data-agent/ui/components'
|
|
@@ -7,9 +8,10 @@ import {
|
|
|
7
8
|
EDGE_LEGEND, type GraphLayout,
|
|
8
9
|
} from './graph-options.ts'
|
|
9
10
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
type AttributionNode, type AttributionNodeKind, type AttributionStats, type
|
|
11
|
+
CHECK_COST_BANDS, FAILURE_REASON_STYLE, FILTERABLE_KINDS, LANES, NODE_KIND_LABEL,
|
|
12
|
+
PROBLEM_CLASSES, PROBLEM_CLASS_LABEL,
|
|
13
|
+
type AttributionNode, type AttributionNodeKind, type AttributionStats, type FailureReason,
|
|
14
|
+
type LaneId, type ProblemClassId,
|
|
13
15
|
} from './model.ts'
|
|
14
16
|
|
|
15
17
|
/**
|
|
@@ -19,6 +21,12 @@ import {
|
|
|
19
21
|
* 不读数据、不算分数、不改状态。这样「浮层放在哪个角」这种纯排版决定可以随时改,
|
|
20
22
|
* 而不会牵动任何业务逻辑 —— 而这恰恰是这一轮改动的主要内容。
|
|
21
23
|
*
|
|
24
|
+
* **四角分区(0021 §7.1)**,一条视线:
|
|
25
|
+
* - 画布左上「改这张图」:四组按动线顺序 —— ① 布局 → ② 问题定义 → ③ 问题假设 → ④ 检查点;
|
|
26
|
+
* - 画布右上「怎么看这张图」:图例 · 全景 / 聚焦 · 保留 1 跳邻居 · 全屏,**四个同排**;
|
|
27
|
+
* - 画布左下「这张图现在多大」:统计 + 数据来源;
|
|
28
|
+
* - 画布底中「在图里找东西」:只剩文本搜索与命中数。
|
|
29
|
+
*
|
|
22
30
|
* 每条浮层都走同一个面板原语 `ChromePanel`:半透明白底 + 细描边 + 轻阴影。
|
|
23
31
|
* 之所以不各写一套边框,是因为它们都盖在画布上,**任何一条比别的更重都会把注意力
|
|
24
32
|
* 从图上偷走**。统一是这里唯一正确的风格决定。
|
|
@@ -40,7 +48,7 @@ export function ChromePanel({ className, children }: { className?: string; child
|
|
|
40
48
|
/** 浮层里的小按钮:比标准 `sm` 再矮 4px,否则一条 380px 宽的浮层会被按钮撑到两行。 */
|
|
41
49
|
const MINI = 'h-[26px] min-h-[26px] px-2 text-[11px] font-[600]'
|
|
42
50
|
|
|
43
|
-
/** 可多选的切面 chip
|
|
51
|
+
/** 可多选的切面 chip。选中态用描边而不是填色:填色会把失败原因的语义色盖掉。 */
|
|
44
52
|
export function Chip({ active, color, onClick, children, title }: {
|
|
45
53
|
active: boolean
|
|
46
54
|
color?: string
|
|
@@ -65,68 +73,172 @@ export function Chip({ active, color, onClick, children, title }: {
|
|
|
65
73
|
>{children}</button>
|
|
66
74
|
}
|
|
67
75
|
|
|
68
|
-
|
|
76
|
+
/** 组标题。四组各有一行 —— 顺序本身就是读法,所以标题不能被省掉。 */
|
|
77
|
+
function GroupLabel({ children, index }: { children: ReactNode; index: string }): ReactNode {
|
|
78
|
+
return <span className="flex shrink-0 items-center gap-1 pt-0.5 text-[9.5px] font-[650] text-tita-ink-faint">
|
|
79
|
+
<span className="font-mono">{index}</span>{children}
|
|
80
|
+
</span>
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function Group({ index, label, children }: { index: string; label: string; children: ReactNode }): ReactNode {
|
|
84
|
+
return <div className="flex items-start gap-1.5 text-[10px] text-tita-ink-muted">
|
|
85
|
+
<GroupLabel index={index}>{label}</GroupLabel>
|
|
86
|
+
<div className="flex min-w-0 flex-wrap gap-1">{children}</div>
|
|
87
|
+
</div>
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* ─────────────────────────────────────────────── 画布左上:操作(四组切面) ── */
|
|
91
|
+
|
|
92
|
+
/** 布局的两种读法。**只有两个** —— 「分层」已撤销(§7.2)。 */
|
|
93
|
+
const LAYOUT_OPTIONS: Array<[GraphLayout, string, string]> = [
|
|
94
|
+
['line', '动线', '动线:五段泳道 + 点亮一条链,回答「下一步查什么、这条链断在哪」'],
|
|
95
|
+
['force', '关系', '关系:浮动节点、可拖拽,关系靠聚簇与距离表达,回答「哪块密、哪里空」'],
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
export const LAYOUT_LABEL: Record<GraphLayout, string> = { line: '动线', force: '关系' }
|
|
69
99
|
|
|
70
100
|
/**
|
|
71
|
-
*
|
|
101
|
+
* 画布左上角工具条:**四组按动线顺序的切面**。
|
|
102
|
+
*
|
|
103
|
+
* 顺序不是排版偏好,是动线本身:先决定这张图怎么画(① 布局),再按
|
|
104
|
+
* 「问题 → 假设 → 检查点」逐段收窄(② ③ ④)。§7.1 把这条顺序写进了规格,
|
|
105
|
+
* 所以组标题带序号,而且**不提供另一种排列**。
|
|
72
106
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
107
|
+
* 「类型切面」被这三组吸收(§7.3):节点可见性由它所属的那一段承担,
|
|
108
|
+
* 关掉「检查点」= 第三段整段不画。同时存在两套说法只会让人犹豫点哪个。
|
|
75
109
|
*
|
|
76
110
|
* 宽度钉在 240px:它是**这一行左侧能拿到的全部预算**(380px 侧栏减内边距、
|
|
77
|
-
*
|
|
78
|
-
*
|
|
111
|
+
* 再减右上角那排读法开关)。不写成自适应是因为自适应在窄容器里会先压扁
|
|
112
|
+
* 最不该被压的那两个布局按钮。
|
|
79
113
|
*/
|
|
80
|
-
export function
|
|
114
|
+
export function OperationsRail({
|
|
115
|
+
layout, onLayout, onReset, kinds, onToggleKind, problems, onToggleProblem,
|
|
116
|
+
subclasses, onToggleSubclass, symptomCounts, failureReasons, onToggleFailureReason,
|
|
117
|
+
gapOnly, onGapOnly, costBands, onToggleCostBand, automations, onToggleAutomation,
|
|
118
|
+
laneCounts, lanesOff, onToggleLane, hint, onCollapse,
|
|
119
|
+
}: {
|
|
81
120
|
layout: GraphLayout
|
|
82
121
|
onLayout: (layout: GraphLayout) => void
|
|
83
122
|
onReset: () => void
|
|
123
|
+
/** 生效的段(= 节点种类)切面。 */
|
|
84
124
|
kinds: ReadonlySet<AttributionNodeKind>
|
|
85
125
|
onToggleKind: (kind: AttributionNodeKind) => void
|
|
86
|
-
/** 问题大类切面。它排在类型之前 —— 「哪类问题」比「哪些节点」更靠前。 */
|
|
87
126
|
problems: ReadonlySet<ProblemClassId>
|
|
88
127
|
onToggleProblem: (id: ProblemClassId) => void
|
|
128
|
+
subclasses: ReadonlySet<string>
|
|
129
|
+
onToggleSubclass: (id: string) => void
|
|
89
130
|
symptomCounts: Record<ProblemClassId, number>
|
|
131
|
+
failureReasons: ReadonlySet<FailureReason>
|
|
132
|
+
onToggleFailureReason: (id: FailureReason) => void
|
|
133
|
+
gapOnly: boolean
|
|
134
|
+
onGapOnly: () => void
|
|
135
|
+
costBands: ReadonlySet<string>
|
|
136
|
+
onToggleCostBand: (id: string) => void
|
|
137
|
+
automations: ReadonlySet<string>
|
|
138
|
+
onToggleAutomation: (id: string) => void
|
|
139
|
+
/** 每段当前画了几个 / 一共几个。段头读数。 */
|
|
140
|
+
laneCounts: Record<LaneId, { shown: number; total: number }>
|
|
141
|
+
/** 被关掉的段。它是 `kinds` 的投影,用来给段 chip 标选中态。 */
|
|
142
|
+
lanesOff: ReadonlySet<LaneId>
|
|
143
|
+
onToggleLane: (id: LaneId) => void
|
|
90
144
|
hint: string
|
|
91
145
|
onCollapse: () => void
|
|
92
146
|
}): ReactNode {
|
|
147
|
+
const activeProblems = PROBLEM_CLASSES.filter(item => problems.has(item.id))
|
|
93
148
|
return <ChromePanel className="flex w-[min(240px,100%)] flex-col gap-1.5 p-2">
|
|
94
149
|
<div className="flex items-center gap-1">
|
|
95
|
-
<span className="mr-0.5 text-[10px] font-[650] text-tita-ink-faint">布局</span>
|
|
96
|
-
{([['force', '力导向'], ['layered', '分层']] as Array<[GraphLayout, string]>).map(([id, label]) =>
|
|
97
|
-
<Button
|
|
98
|
-
key={id}
|
|
99
|
-
size="sm"
|
|
100
|
-
variant={layout === id ? 'default' : 'outline'}
|
|
101
|
-
className={MINI}
|
|
102
|
-
onClick={() => onLayout(id)}
|
|
103
|
-
>{label}</Button>)}
|
|
104
|
-
<Button size="icon" variant="outline" className="ml-auto h-[26px] w-[26px] min-w-[26px]" title="重新布局" aria-label="重新布局" onClick={onReset}>
|
|
105
|
-
<RotateCcw size={12} />
|
|
106
|
-
</Button>
|
|
107
150
|
<Button size="icon" variant="ghost" className="h-[26px] w-[26px] min-w-[26px]" title="收起工具条" aria-label="收起工具条" onClick={onCollapse}>
|
|
108
151
|
<ChevronDown size={12} />
|
|
109
152
|
</Button>
|
|
153
|
+
<span className="text-[9.5px] font-[650] text-tita-ink-faint">按动线顺序</span>
|
|
154
|
+
<Button size="icon" variant="outline" className="ml-auto h-[26px] w-[26px] min-w-[26px]" title="重新布局" aria-label="重新布局" onClick={onReset}>
|
|
155
|
+
<RotateCcw size={12} />
|
|
156
|
+
</Button>
|
|
110
157
|
</div>
|
|
111
158
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
{
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
159
|
+
{/* ① 布局 */}
|
|
160
|
+
<Group index="①" label="布局">
|
|
161
|
+
{LAYOUT_OPTIONS.map(([id, label, title]) => <Button
|
|
162
|
+
key={id}
|
|
163
|
+
size="sm"
|
|
164
|
+
variant={layout === id ? 'default' : 'outline'}
|
|
165
|
+
className={MINI}
|
|
166
|
+
title={title}
|
|
167
|
+
onClick={() => onLayout(id)}
|
|
168
|
+
>{label}</Button>)}
|
|
169
|
+
</Group>
|
|
170
|
+
|
|
171
|
+
{/* ② 问题定义:大类(子图选择器)→ 子类(只在选中的大类内出现) */}
|
|
172
|
+
<Group index="②" label="问题定义">
|
|
173
|
+
{PROBLEM_CLASSES.map(item => <Chip
|
|
174
|
+
key={item.id}
|
|
175
|
+
active={problems.has(item.id)}
|
|
176
|
+
title={`${item.cue} · ${symptomCounts[item.id]} 个现象`}
|
|
177
|
+
onClick={() => onToggleProblem(item.id)}
|
|
178
|
+
>{item.label}<span className="ml-1 font-mono text-[9.5px] opacity-70">{symptomCounts[item.id]}</span></Chip>)}
|
|
179
|
+
</Group>
|
|
180
|
+
{/* 子类行只在选了大类之后出现:没选大类时摊开 15 个子类,是把「两级切面」
|
|
181
|
+
又变回了一张目录。选了大类才收窄,这才是 §4.5 说的随大类收窄。 */}
|
|
182
|
+
{activeProblems.length > 0 && <div className="flex items-start gap-1.5 pl-[13px] text-[10px] text-tita-ink-muted">
|
|
183
|
+
<div className="flex min-w-0 flex-wrap gap-1">
|
|
184
|
+
{activeProblems.flatMap(item => item.subclasses).map(sub => <Chip
|
|
185
|
+
key={sub.id}
|
|
186
|
+
active={subclasses.has(sub.id)}
|
|
187
|
+
title={`${sub.label} · 形态 ${sub.shape}${sub.timePattern ? ` · ${sub.timePattern}` : ''}`}
|
|
188
|
+
onClick={() => onToggleSubclass(sub.id)}
|
|
189
|
+
>{sub.label}</Chip>)}
|
|
121
190
|
</div>
|
|
122
|
-
</div>
|
|
191
|
+
</div>}
|
|
123
192
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
193
|
+
{/* ③ 问题假设:失败原因 + 空区 */}
|
|
194
|
+
<Group index="③" label="问题假设">
|
|
195
|
+
{Object.entries(FAILURE_REASON_STYLE).map(([key, tone]) => <Chip
|
|
196
|
+
key={key}
|
|
197
|
+
active={failureReasons.has(key as FailureReason)}
|
|
198
|
+
color={tone.stroke}
|
|
199
|
+
onClick={() => onToggleFailureReason(key as FailureReason)}
|
|
200
|
+
>{tone.label}</Chip>)}
|
|
201
|
+
<Chip
|
|
202
|
+
active={gapOnly}
|
|
203
|
+
color="var(--tita-danger)"
|
|
204
|
+
title="只留空区:有假设、一条检查点都没有 —— 它是「这张图哪里还没建完」的读数"
|
|
205
|
+
onClick={onGapOnly}
|
|
206
|
+
>空区</Chip>
|
|
207
|
+
</Group>
|
|
208
|
+
|
|
209
|
+
{/* ④ 检查点:成本 + 自动 / 人工 */}
|
|
210
|
+
<Group index="④" label="检查点">
|
|
211
|
+
{CHECK_COST_BANDS.map(band => <Chip
|
|
212
|
+
key={band.id}
|
|
213
|
+
active={costBands.has(band.id)}
|
|
214
|
+
onClick={() => onToggleCostBand(band.id)}
|
|
215
|
+
>{band.label}</Chip>)}
|
|
216
|
+
{([['auto', '自动'], ['manual', '人工']] as Array<[string, string]>).map(([id, label]) => <Chip
|
|
217
|
+
key={id}
|
|
218
|
+
active={automations.has(id)}
|
|
219
|
+
onClick={() => onToggleAutomation(id)}
|
|
220
|
+
>{label}</Chip>)}
|
|
221
|
+
</Group>
|
|
222
|
+
|
|
223
|
+
{/* 段开关:它就是「类型切面」的归位处(§7.3)。段名 + 实时计数,
|
|
224
|
+
空的或不画的那一段要能一眼看出来,而不是点进去才发现是 0。 */}
|
|
225
|
+
<div className="flex items-start gap-1.5 border-t border-tita-line pt-1.5 text-[10px] text-tita-ink-muted">
|
|
226
|
+
<span className="shrink-0 pt-0.5 text-[9.5px] font-[650] text-tita-ink-faint">段</span>
|
|
227
|
+
<div className="flex min-w-0 flex-wrap gap-1">
|
|
228
|
+
{LANES.map(lane => {
|
|
229
|
+
const counts = laneCounts[lane.id]
|
|
230
|
+
const off = lanesOff.has(lane.id)
|
|
231
|
+
return <Chip
|
|
232
|
+
key={lane.id}
|
|
233
|
+
active={!off}
|
|
234
|
+
title={off ? `已关掉:这${lane.instance ? '一' : '一'}段整段不画` : `${lane.label} · ${lane.orderHint}`}
|
|
235
|
+
onClick={() => onToggleLane(lane.id)}
|
|
236
|
+
>
|
|
237
|
+
<span className={cn(off && 'line-through')}>{lane.ordinal} {lane.label}</span>
|
|
238
|
+
<span className="ml-1 font-mono text-[9.5px] opacity-70">{counts?.shown ?? 0}</span>
|
|
239
|
+
{counts && counts.total !== counts.shown && <span className="ml-0.5 font-mono text-[9.5px] opacity-60">/{counts.total}</span>}
|
|
240
|
+
</Chip>
|
|
241
|
+
})}
|
|
130
242
|
</div>
|
|
131
243
|
</div>
|
|
132
244
|
|
|
@@ -134,63 +246,99 @@ export function ToolRail({ layout, onLayout, onReset, kinds, onToggleKind, probl
|
|
|
134
246
|
</ChromePanel>
|
|
135
247
|
}
|
|
136
248
|
|
|
137
|
-
/** 收起来之后留下的那颗药丸。收起不等于隐藏 ——
|
|
138
|
-
export function ToolPill({ layout,
|
|
249
|
+
/** 收起来之后留下的那颗药丸。收起不等于隐藏 —— 布局与生效筛选数还在上面。 */
|
|
250
|
+
export function ToolPill({ layout, filterCount, laneCount, onClick }: {
|
|
139
251
|
layout: GraphLayout
|
|
140
|
-
|
|
141
|
-
|
|
252
|
+
/** 生效的切面数(不含文本)。 */
|
|
253
|
+
filterCount: number
|
|
254
|
+
/** 会被画出来的节点数。 */
|
|
255
|
+
laneCount: number
|
|
142
256
|
onClick: () => void
|
|
143
257
|
}): ReactNode {
|
|
144
|
-
|
|
258
|
+
/**
|
|
259
|
+
* 0044:原来这里挂着一枚「N 条待确认的卡片提议」角标,现在没有它了。
|
|
260
|
+
*
|
|
261
|
+
* 那枚角标的紧迫感来自**确认门**:未确认的提议不落 `02_wiki/`,没人点头,
|
|
262
|
+
* 一次排查就等于白跑。门撤掉之后卡片直接入库、已经能召回,review 只是给它抬分 ——
|
|
263
|
+
* 「有 N 张还没人看过」不再是需要从归因图谱上催的事,它属于「知识库 → Wiki」那一屏。
|
|
264
|
+
* 留一枚不再紧急的角标,只会教人忽略角标。
|
|
265
|
+
*/
|
|
145
266
|
return <ChromePanel className="p-[3px]">
|
|
146
|
-
<
|
|
267
|
+
<span className="flex items-center gap-1">
|
|
268
|
+
<Button
|
|
269
|
+
size="sm"
|
|
270
|
+
variant="ghost"
|
|
271
|
+
className={MINI}
|
|
272
|
+
onClick={onClick}
|
|
273
|
+
title="展开四组切面:布局 → 问题定义 → 问题假设 → 检查点"
|
|
274
|
+
>
|
|
147
275
|
<SlidersHorizontal size={12} />
|
|
148
|
-
{layout
|
|
149
|
-
{
|
|
276
|
+
{LAYOUT_LABEL[layout]}
|
|
277
|
+
{filterCount > 0 && <span className="rounded-[4px] bg-tita-primary-soft px-1 font-mono text-[9.5px] text-tita-primary">{filterCount} 项</span>}
|
|
278
|
+
<span className="font-mono text-[9.5px] text-tita-ink-faint">{laneCount}</span>
|
|
150
279
|
</Button>
|
|
280
|
+
</span>
|
|
151
281
|
</ChromePanel>
|
|
152
282
|
}
|
|
153
283
|
|
|
154
|
-
/*
|
|
284
|
+
/* ─────────────────────────────────────────────── 画布右上:读法(四个同排) ── */
|
|
155
285
|
|
|
156
286
|
/**
|
|
157
|
-
*
|
|
287
|
+
* 右上角:**四个读法开关同排**(§7.1 的 v1.4 调整)。
|
|
158
288
|
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
* 那里是整行宽度,放得下 9 种边和 4 条说明。
|
|
289
|
+
* 图例 · 全景 / 聚焦 · 保留 1 跳邻居 · 全屏 —— 这四个都是「怎么看这张图」,
|
|
290
|
+
* 既不是筛选也不是内容,所以集中在一处。原来「全景 / 聚焦」在右侧中部、
|
|
291
|
+
* 「保留 1 跳邻居」在底中,四件事散在三个角上,扫一遍要来回跳。
|
|
163
292
|
*
|
|
164
|
-
*
|
|
165
|
-
* 而不是把正文钉死在角上、盖住半张图。
|
|
293
|
+
* 全屏按钮**任何子视图都留着**:换个视图就丢掉出口,是这一轮最不该犯的错。
|
|
166
294
|
*/
|
|
167
|
-
export function
|
|
168
|
-
|
|
169
|
-
|
|
295
|
+
export function ReadBar({ legendOpen, onToggleLegend, showLegend, overview, onOverview,
|
|
296
|
+
includeNeighbors, onIncludeNeighbors, full, onToggleFull, showScope = true }: {
|
|
297
|
+
legendOpen: boolean
|
|
298
|
+
onToggleLegend: () => void
|
|
299
|
+
/** 案例 / 排查视图里没有图可读,图例、全景与邻居开关就没有对象。 */
|
|
300
|
+
showLegend: boolean
|
|
301
|
+
overview: boolean
|
|
302
|
+
onOverview: (value: boolean) => void
|
|
303
|
+
includeNeighbors: boolean
|
|
304
|
+
onIncludeNeighbors: (value: boolean) => void
|
|
170
305
|
full: boolean
|
|
171
306
|
onToggleFull: () => void
|
|
172
|
-
|
|
173
|
-
* 案例 / 排查视图里没有图可读,图例就没有对象。
|
|
174
|
-
*
|
|
175
|
-
* 但**全屏按钮必须留下来**:全屏是「这一整块(含三个视图)占满视口」,
|
|
176
|
-
* 换个视图就丢掉出口,是这一轮最不该犯的错。
|
|
177
|
-
*/
|
|
178
|
-
showLegend?: boolean
|
|
307
|
+
showScope?: boolean
|
|
179
308
|
}): ReactNode {
|
|
180
|
-
return <ChromePanel className="flex items-center gap-
|
|
309
|
+
return <ChromePanel className="flex items-center gap-[3px] p-[3px]">
|
|
181
310
|
{showLegend && <Button
|
|
182
311
|
size="sm"
|
|
183
|
-
variant={
|
|
184
|
-
className="h-[
|
|
185
|
-
aria-label={
|
|
186
|
-
aria-expanded={
|
|
187
|
-
title={
|
|
188
|
-
onClick={
|
|
312
|
+
variant={legendOpen ? 'default' : 'ghost'}
|
|
313
|
+
className="h-[24px] min-h-[24px] gap-1 px-1.5 text-[10.5px]"
|
|
314
|
+
aria-label={legendOpen ? '隐藏图例' : '显示图例'}
|
|
315
|
+
aria-expanded={legendOpen}
|
|
316
|
+
title={legendOpen ? '隐藏图例' : '显示图例:配色、十种边、空区与僵尸边'}
|
|
317
|
+
onClick={onToggleLegend}
|
|
189
318
|
><CircleHelp size={12} />图例</Button>}
|
|
319
|
+
|
|
320
|
+
{showLegend && showScope && <Button
|
|
321
|
+
size="sm"
|
|
322
|
+
variant="ghost"
|
|
323
|
+
className="h-[24px] min-h-[24px] px-1.5 text-[10.5px]"
|
|
324
|
+
aria-pressed={!overview}
|
|
325
|
+
title={overview ? '聚焦:只看选中节点的 1 跳邻域' : '全景:看整张图'}
|
|
326
|
+
onClick={() => onOverview(!overview)}
|
|
327
|
+
>{overview ? '全景' : '聚焦'}</Button>}
|
|
328
|
+
|
|
329
|
+
{showLegend && showScope && <Button
|
|
330
|
+
size="sm"
|
|
331
|
+
variant="ghost"
|
|
332
|
+
className="h-[24px] min-h-[24px] px-1.5 text-[10.5px]"
|
|
333
|
+
aria-pressed={includeNeighbors}
|
|
334
|
+
title="保留 1 跳邻居:只留命中节点会让图散成孤点,边的语义一起丢"
|
|
335
|
+
onClick={() => onIncludeNeighbors(!includeNeighbors)}
|
|
336
|
+
>1 跳邻居</Button>}
|
|
337
|
+
|
|
190
338
|
<Button
|
|
191
339
|
size="icon"
|
|
192
|
-
variant=
|
|
193
|
-
className="h-[
|
|
340
|
+
variant={full ? 'default' : 'outline'}
|
|
341
|
+
className="h-[24px] w-[24px] min-w-[24px]"
|
|
194
342
|
aria-label={full ? '退出全屏' : '全屏查看'}
|
|
195
343
|
title={full ? '退出全屏(Esc)' : '全屏查看:整个视口都用来放这块内容'}
|
|
196
344
|
onClick={onToggleFull}
|
|
@@ -205,18 +353,18 @@ export function LegendToggle({ open, onToggle, full, onToggleFull, showLegend =
|
|
|
205
353
|
* 用户会以为「图例已经开着」,而真正说明含义的那半仍然是藏着的。要么整块在,
|
|
206
354
|
* 要么整块不在,状态才可读。
|
|
207
355
|
*
|
|
208
|
-
* **两个分组必须分开写。**
|
|
356
|
+
* **两个分组必须分开写。** 这里的五种颜色**全部是原因侧**(失败原因,挂在假设上),
|
|
209
357
|
* 一份只画颜色的图例会让人以为它在说「问题有哪几类」—— 这正是最常被看错的一处。
|
|
210
|
-
*
|
|
358
|
+
* 所以原因侧明写「失败原因 · 原因侧」,问题侧(现象)单独一段、并且把
|
|
211
359
|
* 「现象节点有几个、有没有分类」如实报出来:**分类还没建的地方要看得见,而不是留白。**
|
|
212
360
|
*/
|
|
213
361
|
export function LegendBody({ stats }: { stats: AttributionStats }): ReactNode {
|
|
214
362
|
return <ChromePanel className="flex flex-col gap-1.5 p-2">
|
|
215
363
|
<div className="text-[9.5px] leading-[14px] text-tita-ink-faint">
|
|
216
|
-
<span className="font-[650] text-tita-ink-muted"
|
|
364
|
+
<span className="font-[650] text-tita-ink-muted">失败原因</span> · 原因侧(挂在假设上)
|
|
217
365
|
</div>
|
|
218
366
|
<div className="flex flex-wrap gap-1">
|
|
219
|
-
{Object.entries(
|
|
367
|
+
{Object.entries(FAILURE_REASON_STYLE).map(([key, tone]) => <span
|
|
220
368
|
key={key}
|
|
221
369
|
className="flex items-center gap-1 rounded-md border px-1.5 py-0.5 text-[10px] leading-[15px]"
|
|
222
370
|
style={{ borderColor: tone.stroke, background: tone.fill, color: tone.text }}
|
|
@@ -237,8 +385,8 @@ export function LegendBody({ stats }: { stats: AttributionStats }): ReactNode {
|
|
|
237
385
|
>{item.label}</span>)}
|
|
238
386
|
</div>
|
|
239
387
|
<p className="text-[9.5px] leading-relaxed text-tita-ink-muted">
|
|
240
|
-
|
|
241
|
-
|
|
388
|
+
问题大类刻意不给颜色:颜色通道已经被失败原因占用。问题侧走文字 —— 现象节点下方的
|
|
389
|
+
徽标写着它属于哪个大类,左上的「问题定义」切面按大类收窄,并把它的检查一起带出来。
|
|
242
390
|
</p>
|
|
243
391
|
</div>
|
|
244
392
|
|
|
@@ -253,78 +401,59 @@ export function LegendBody({ stats }: { stats: AttributionStats }): ReactNode {
|
|
|
253
401
|
</span>)}
|
|
254
402
|
</div>
|
|
255
403
|
<ul className="flex flex-col gap-0.5 text-[9.5px] leading-relaxed text-tita-ink-muted">
|
|
256
|
-
<li>圆的大小 =
|
|
257
|
-
<li><span className="text-tita-danger">红色虚环 + 空区徽标</span> =
|
|
404
|
+
<li>圆的大小 = 复用次数(关系视图);动线视图里每个节点都读得出名字</li>
|
|
405
|
+
<li><span className="text-tita-danger">红色虚环 + 空区徽标</span> = 有假设但一条检查点都没有(当前 {stats.gaps} 个)。动线视图里它就是「② 段右边没有线」</li>
|
|
258
406
|
<li><span className="text-tita-ink-faint">灰色虚线</span> = 僵尸边,90 天未走(当前 {stats.zombies} 条)</li>
|
|
259
|
-
<li
|
|
407
|
+
<li>⑤ 段(实际查询与结果)默认折叠 —— 它随每次排查线性增长;被点亮的那条链例外</li>
|
|
260
408
|
</ul>
|
|
261
409
|
</div>
|
|
262
410
|
</ChromePanel>
|
|
263
411
|
}
|
|
264
412
|
|
|
265
|
-
/*
|
|
413
|
+
/* ─────────────────────────────────────────────── 画布底中:只剩搜索 ── */
|
|
266
414
|
|
|
267
415
|
/**
|
|
268
|
-
*
|
|
416
|
+
* 底中:**只剩文本搜索 + 命中数**(§7.1 的 v1.4 调整)。
|
|
269
417
|
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
418
|
+
* 失败原因切面上移到了左栏第 ③ 组,「保留 1 跳邻居」上移到了右上读法排。
|
|
419
|
+
* 搜索留在底中,因为它是**跨全图**的动作,不属于任何一段。
|
|
272
420
|
*/
|
|
273
|
-
export function FilterBar({ query, onQuery,
|
|
421
|
+
export function FilterBar({ query, onQuery, filtering, onClear, hit, total }: {
|
|
274
422
|
query: string
|
|
275
423
|
onQuery: (value: string) => void
|
|
276
|
-
modes: ReadonlySet<FailureMode>
|
|
277
|
-
onToggleMode: (mode: FailureMode) => void
|
|
278
|
-
includeNeighbors: boolean
|
|
279
|
-
onIncludeNeighbors: (value: boolean) => void
|
|
280
424
|
filtering: boolean
|
|
281
425
|
onClear: () => void
|
|
282
426
|
hit: number
|
|
283
427
|
total: number
|
|
284
428
|
}): ReactNode {
|
|
285
|
-
return <ChromePanel className="flex w-[min(520px,100%)]
|
|
286
|
-
<div className="
|
|
287
|
-
<
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
/>
|
|
295
|
-
</div>
|
|
296
|
-
<span className="shrink-0 text-[10px] tabular-nums text-tita-ink-faint">命中 {hit}/{total}</span>
|
|
297
|
-
{filtering && <Button size="sm" variant="ghost" className={MINI} onClick={onClear}><X size={11} />清除</Button>}
|
|
298
|
-
</div>
|
|
299
|
-
|
|
300
|
-
<div className="flex items-start gap-1.5 text-[10px] text-tita-ink-muted">
|
|
301
|
-
<span className="shrink-0 pt-0.5 font-[650] text-tita-ink-faint">切面</span>
|
|
302
|
-
<div className="flex flex-wrap gap-1">
|
|
303
|
-
{Object.entries(FAILURE_MODE_STYLE).map(([key, tone]) => <Chip
|
|
304
|
-
key={key}
|
|
305
|
-
active={modes.has(key as FailureMode)}
|
|
306
|
-
color={tone.stroke}
|
|
307
|
-
onClick={() => onToggleMode(key as FailureMode)}
|
|
308
|
-
>{tone.label}</Chip>)}
|
|
309
|
-
</div>
|
|
310
|
-
<Chip active={includeNeighbors} onClick={() => onIncludeNeighbors(!includeNeighbors)} title="只留命中节点会让图散成孤点">
|
|
311
|
-
保留 1 跳邻居
|
|
312
|
-
</Chip>
|
|
429
|
+
return <ChromePanel className="flex w-[min(520px,100%)] items-center gap-1.5 p-2">
|
|
430
|
+
<div className="relative min-w-0 flex-1">
|
|
431
|
+
<Search size={12} className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 text-tita-ink-faint" />
|
|
432
|
+
<Input
|
|
433
|
+
value={query}
|
|
434
|
+
onChange={event => onQuery(event.target.value)}
|
|
435
|
+
placeholder="筛选节点:id / 标题 / 判据…"
|
|
436
|
+
className="h-[26px] min-h-[26px] pl-6 text-[11px]"
|
|
437
|
+
/>
|
|
313
438
|
</div>
|
|
439
|
+
<span className="shrink-0 text-[10px] tabular-nums text-tita-ink-faint">命中 {hit}/{total}</span>
|
|
440
|
+
{filtering && <Button size="sm" variant="ghost" className={MINI} onClick={onClear}><X size={11} />清除</Button>}
|
|
314
441
|
</ChromePanel>
|
|
315
442
|
}
|
|
316
443
|
|
|
317
|
-
/*
|
|
444
|
+
/* ─────────────────────────────────────────────── 画布左下:读数 ── */
|
|
318
445
|
|
|
319
446
|
/**
|
|
320
|
-
*
|
|
447
|
+
* 左下角统计:**从右下搬来**(§7.1 的 v1.4 调整)。
|
|
448
|
+
*
|
|
449
|
+
* 搬家的理由是左列因此形成「上面改、下面读」的一列,而右下让给图例 / 详情的展开正文。
|
|
321
450
|
*
|
|
322
451
|
* 只放**随筛选变化**的数:它回答的是「我现在看的这张图有多大、缺多少」。
|
|
323
|
-
*
|
|
452
|
+
* 图例里已经写了空区 / 僵尸边的含义,这里只给数,不重复解释。
|
|
324
453
|
*
|
|
325
|
-
* 数据来源徽标(`
|
|
326
|
-
*
|
|
327
|
-
*
|
|
454
|
+
* 数据来源徽标(`02_wiki/diagnosis` / `05_diagnosis(旧格式)`)也挂在这里:它和这些数
|
|
455
|
+
* 一样是「这份内容从哪来」的事实,而且它必须始终可见 —— 看到的是卡片还是旧格式的
|
|
456
|
+
* jsonl,不能靠猜。
|
|
328
457
|
*/
|
|
329
458
|
export function StatsStrip({ stats, source, problems }: {
|
|
330
459
|
stats: AttributionStats
|
|
@@ -332,7 +461,7 @@ export function StatsStrip({ stats, source, problems }: {
|
|
|
332
461
|
/** 编译 / 迁移过程中发现的问题。空数组表示一切正常。 */
|
|
333
462
|
problems: string[]
|
|
334
463
|
}): ReactNode {
|
|
335
|
-
return <ChromePanel className="flex flex-wrap items-center gap-x-2 gap-y-0.5 px-2 py-1">
|
|
464
|
+
return <ChromePanel className="flex w-[min(200px,100%)] flex-wrap items-center gap-x-2 gap-y-0.5 px-2 py-1">
|
|
336
465
|
<Stat label="类型" value={stats.typeNodes} />
|
|
337
466
|
<Divider />
|
|
338
467
|
<Stat label="引用" value={stats.referenceNodes} />
|
|
@@ -348,24 +477,23 @@ export function StatsStrip({ stats, source, problems }: {
|
|
|
348
477
|
title={problems.join('\n')}
|
|
349
478
|
>{problems.length} 个问题</span>}
|
|
350
479
|
<span
|
|
351
|
-
className="
|
|
352
|
-
title={SOURCE_HINT[source] ??
|
|
353
|
-
|
|
480
|
+
className="w-full shrink-0 truncate font-mono text-[9.5px] text-tita-ink-faint"
|
|
481
|
+
title={SOURCE_HINT[source] ?? source}
|
|
482
|
+
>来源 {source}</span>
|
|
354
483
|
</ChromePanel>
|
|
355
484
|
}
|
|
356
485
|
|
|
357
486
|
/**
|
|
358
487
|
* 数据来源的说明。
|
|
359
488
|
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
489
|
+
* 这里必须能分清两种形态,因为它们意味着不同的可信度:`02_wiki/diagnosis` 是当前真相
|
|
490
|
+
* (卡片),`05_diagnosis(旧格式)` 意味着迁移没能无损生效。一句笼统的「05_diagnosis」
|
|
491
|
+
* 会把它们混成一种,而这正是最不该让人猜的地方。
|
|
363
492
|
*/
|
|
364
493
|
const SOURCE_HINT: Record<string, string> = {
|
|
365
494
|
'02_wiki/diagnosis': '来自 wiki 卡片(诊断卡片是当前唯一的真相)',
|
|
366
495
|
'05_diagnosis(旧格式)': '旧格式 jsonl:迁移会丢知识,因此仍按原样读',
|
|
367
|
-
'
|
|
368
|
-
'seed 数据': '宿主未就绪,当前展示的是内置示例',
|
|
496
|
+
'还没有排查知识': '这个知识库还没有排查知识',
|
|
369
497
|
}
|
|
370
498
|
|
|
371
499
|
function Divider(): ReactNode {
|
|
@@ -382,9 +510,9 @@ function Stat({ label, value, tone }: { label: string; value: number; tone?: 'wa
|
|
|
382
510
|
</span>
|
|
383
511
|
}
|
|
384
512
|
|
|
385
|
-
/*
|
|
513
|
+
/* ─────────────────────────────────────────────── 画布右侧:选中节点详情 ── */
|
|
386
514
|
|
|
387
|
-
export function NodeDetailCard({ node }: { node: AttributionNode }): ReactNode {
|
|
515
|
+
export function NodeDetailCard({ node, onClear }: { node: AttributionNode; onClear?: () => void }): ReactNode {
|
|
388
516
|
return <ChromePanel className="flex flex-col gap-1.5 p-2">
|
|
389
517
|
<div className="flex items-start justify-between gap-2">
|
|
390
518
|
<span className="text-[11.5px] font-semibold leading-snug">{node.title}</span>
|
|
@@ -393,11 +521,23 @@ export function NodeDetailCard({ node }: { node: AttributionNode }): ReactNode {
|
|
|
393
521
|
{node.summary && <p className="text-[10.5px] leading-relaxed text-tita-ink-muted">{node.summary}</p>}
|
|
394
522
|
<div className="flex flex-wrap gap-1">
|
|
395
523
|
<Badge tone="neutral">{NODE_KIND_LABEL[node.kind]}</Badge>
|
|
396
|
-
{node.
|
|
524
|
+
{node.failureReason && <Badge tone="violet">{FAILURE_REASON_STYLE[node.failureReason].label}</Badge>}
|
|
525
|
+
{node.problemClass && <Badge tone="neutral">{PROBLEM_CLASS_LABEL[node.problemClass]}</Badge>}
|
|
397
526
|
{node.reuseCount !== undefined && <Badge tone="neutral">复用 {node.reuseCount}</Badge>}
|
|
398
527
|
{node.costMinutes !== undefined && <Badge tone={node.automation === 'auto' ? 'success' : 'warning'}>
|
|
399
528
|
{node.costMinutes} min · {node.automation === 'auto' ? '自动' : '人工'}
|
|
400
529
|
</Badge>}
|
|
401
530
|
</div>
|
|
531
|
+
{/* 检查点的查询依据:它落在 `queries` 边上,从节点本身读不出来。 */}
|
|
532
|
+
{node.assets && node.assets.length > 0 && <p className="text-[10px] leading-snug text-tita-ink-faint">
|
|
533
|
+
要去查:{node.assets.join(' · ')}
|
|
534
|
+
</p>}
|
|
535
|
+
{node.discriminator && <p className="text-[10px] leading-snug text-tita-ink-muted">判据:{node.discriminator}</p>}
|
|
536
|
+
{node.why && <p className="text-[10px] leading-snug text-tita-ink-muted">为什么算数:{node.why}</p>}
|
|
537
|
+
{onClear && <button
|
|
538
|
+
type="button"
|
|
539
|
+
onClick={onClear}
|
|
540
|
+
className="mt-0.5 flex items-center gap-1 self-start rounded-md px-1 py-0.5 text-[10px] text-tita-ink-faint hover:text-tita-ink focus-visible:outline-[3px] focus-visible:outline-offset-1 focus-visible:outline-tita-focus-ring"
|
|
541
|
+
><ChevronRight size={10} />取消点亮,回到全亮</button>}
|
|
402
542
|
</ChromePanel>
|
|
403
543
|
}
|