dsh-data-domain-knowledge 0.1.0-alpha.2 → 0.1.0-alpha.3
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 +31 -7
- package/cordis.patch.yml +11 -5
- package/lib/client.js +5557 -3056
- package/lib/doc-page.js +7740 -0
- package/lib/types/convert/host.d.ts.map +1 -1
- package/lib/types/convert/host.js +36 -9
- package/lib/types/convert/host.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 +51 -0
- package/lib/types/diagnosis/run.d.ts.map +1 -0
- package/lib/types/diagnosis/run.js +239 -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 +18 -1
- 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 +114 -26
- package/lib/types/diagnosis/store.d.ts.map +1 -1
- package/lib/types/diagnosis/store.js +336 -167
- 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/fs-atomic.d.ts +12 -0
- package/lib/types/fs-atomic.d.ts.map +1 -0
- package/lib/types/fs-atomic.js +42 -0
- package/lib/types/fs-atomic.js.map +1 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +214 -24
- package/lib/types/index.js.map +1 -1
- package/lib/types/knowledge-bases.d.ts +10 -2
- package/lib/types/knowledge-bases.d.ts.map +1 -1
- package/lib/types/knowledge-bases.js +20 -18
- package/lib/types/knowledge-bases.js.map +1 -1
- package/lib/types/proposals.d.ts +166 -0
- package/lib/types/proposals.d.ts.map +1 -0
- package/lib/types/proposals.js +238 -0
- package/lib/types/proposals.js.map +1 -0
- package/lib/types/runtime.d.ts +199 -7
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/runtime.js +728 -145
- package/lib/types/runtime.js.map +1 -1
- package/lib/types/settings-schema.d.ts +2 -2
- package/lib/types/types.d.ts +61 -10
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js +56 -8
- package/lib/types/types.js.map +1 -1
- package/lib/types/wiki-types.d.ts +8 -1
- package/lib/types/wiki-types.d.ts.map +1 -1
- package/lib/types/wiki-types.js +16 -25
- package/lib/types/wiki-types.js.map +1 -1
- package/package.json +4 -3
- package/preset/tita-data-agent/agent.cordis.yml +11 -5
- package/src/client/DirectoryPicker.tsx +12 -12
- package/src/client/InfoHint.tsx +21 -8
- package/src/client/IntentTab.tsx +20 -26
- package/src/client/KnowledgeBaseTab.tsx +190 -80
- package/src/client/KnowledgePanel.tsx +42 -9
- package/src/client/KnowledgeStepRail.tsx +3 -0
- package/src/client/NodeSettings.tsx +20 -22
- package/src/client/PreviewSections.tsx +87 -33
- package/src/client/ProposalReview.tsx +313 -0
- package/src/client/TypeDefinitionDrawer.tsx +72 -38
- package/src/client/WikiTypeEditor.tsx +193 -77
- package/src/client/arch/AgentArchitectureGraph.tsx +143 -14
- package/src/client/arch/NodeLogicPanel.tsx +6 -3
- package/src/client/arch/model.ts +164 -26
- package/src/client/architecture.css +29 -9
- package/src/client/architecture.tsx +54 -29
- package/src/client/attribution/AttributionGraph.tsx +481 -34
- package/src/client/attribution/AttributionTab.tsx +507 -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 +296 -140
- package/src/client/attribution/graph-options.ts +611 -47
- package/src/client/attribution/model.ts +559 -30
- package/src/client/convert/ConvertPanel.tsx +6 -1
- package/src/client/doc-page.css +134 -0
- package/src/client/doc-page.tsx +263 -0
- package/src/client/panel-state.ts +105 -0
- package/src/client/request.ts +36 -0
- package/src/client/styles.css +317 -43
- package/src/client/useFitScale.ts +15 -2
- package/src/client/wiki-type-groups.ts +86 -0
- package/src/convert/host.ts +41 -7
- package/src/diagnosis/cards.ts +120 -50
- package/src/diagnosis/run.ts +245 -0
- package/src/diagnosis/schema.ts +67 -26
- package/src/diagnosis/seed-source.ts +28 -3
- package/src/diagnosis/seed.ts +187 -85
- package/src/diagnosis/store.ts +358 -174
- package/src/diagnosis/types.ts +181 -16
- package/src/document-page.ts +51 -0
- package/src/fs-atomic.ts +42 -0
- package/src/index.ts +209 -22
- package/src/knowledge-bases.ts +20 -18
- package/src/proposals.ts +337 -0
- package/src/runtime.ts +736 -135
- package/src/types.ts +113 -19
- package/src/wiki-types.ts +17 -27
|
@@ -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,115 @@ 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, pendingProposals, onOpenProposals, onClick }: {
|
|
139
251
|
layout: GraphLayout
|
|
140
|
-
|
|
141
|
-
|
|
252
|
+
/** 生效的切面数(不含文本)。 */
|
|
253
|
+
filterCount: number
|
|
254
|
+
/** 会被画出来的节点数。 */
|
|
255
|
+
laneCount: number
|
|
256
|
+
/**
|
|
257
|
+
* 待人确认的卡片提议条数(§7.1)。
|
|
258
|
+
*
|
|
259
|
+
* 它挂在这枚收起态的 pill 上,而不是另起一个浮层:提议多半就是从排查里长出来的
|
|
260
|
+
* (跑完一次排查,案例卡片的草案就等在队列里),而**这个数字需要被看见**——
|
|
261
|
+
* 未确认的提议不落 `02_wiki/`,一直没人点头,这次排查就等于白跑。
|
|
262
|
+
* 点它跳到「知识库 → Wiki」的待办区;review 界面只有一处,这里只挂角标。
|
|
263
|
+
*/
|
|
264
|
+
pendingProposals?: number
|
|
265
|
+
onOpenProposals?: () => void
|
|
142
266
|
onClick: () => void
|
|
143
267
|
}): ReactNode {
|
|
144
|
-
const
|
|
268
|
+
const waiting = pendingProposals ?? 0
|
|
145
269
|
return <ChromePanel className="p-[3px]">
|
|
146
|
-
<
|
|
270
|
+
<span className="flex items-center gap-1">
|
|
271
|
+
<Button
|
|
272
|
+
size="sm"
|
|
273
|
+
variant="ghost"
|
|
274
|
+
className={MINI}
|
|
275
|
+
onClick={onClick}
|
|
276
|
+
title="展开四组切面:布局 → 问题定义 → 问题假设 → 检查点"
|
|
277
|
+
>
|
|
147
278
|
<SlidersHorizontal size={12} />
|
|
148
|
-
{layout
|
|
149
|
-
{
|
|
279
|
+
{LAYOUT_LABEL[layout]}
|
|
280
|
+
{filterCount > 0 && <span className="rounded-[4px] bg-tita-primary-soft px-1 font-mono text-[9.5px] text-tita-primary">{filterCount} 项</span>}
|
|
281
|
+
<span className="font-mono text-[9.5px] text-tita-ink-faint">{laneCount}</span>
|
|
150
282
|
</Button>
|
|
283
|
+
{waiting > 0 && <button
|
|
284
|
+
type="button"
|
|
285
|
+
onClick={onOpenProposals}
|
|
286
|
+
title={`${waiting} 条卡片提议待人确认 —— 未确认的提议不落 02_wiki/,不进编译、不参与召回、不在图上出现。点进「知识库 → Wiki」的待办区逐条 review。`}
|
|
287
|
+
className="rounded-[6px] border px-1.5 py-0.5 text-[10px] leading-[15px] font-medium"
|
|
288
|
+
style={{
|
|
289
|
+
// 用 warning 令牌而不是 danger:待办不是错误,它是**还没做**。
|
|
290
|
+
// 颜色只做辅助,文字自己说清了是什么(不依赖颜色单独传达状态)。
|
|
291
|
+
borderColor: 'var(--tita-warning)',
|
|
292
|
+
color: 'var(--tita-warning)',
|
|
293
|
+
background: 'color-mix(in srgb, var(--tita-warning) 8%, transparent)',
|
|
294
|
+
}}
|
|
295
|
+
>{waiting} 条待确认</button>}
|
|
296
|
+
</span>
|
|
151
297
|
</ChromePanel>
|
|
152
298
|
}
|
|
153
299
|
|
|
154
|
-
/*
|
|
300
|
+
/* ─────────────────────────────────────────────── 画布右上:读法(四个同排) ── */
|
|
155
301
|
|
|
156
302
|
/**
|
|
157
|
-
*
|
|
303
|
+
* 右上角:**四个读法开关同排**(§7.1 的 v1.4 调整)。
|
|
158
304
|
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
* 那里是整行宽度,放得下 9 种边和 4 条说明。
|
|
305
|
+
* 图例 · 全景 / 聚焦 · 保留 1 跳邻居 · 全屏 —— 这四个都是「怎么看这张图」,
|
|
306
|
+
* 既不是筛选也不是内容,所以集中在一处。原来「全景 / 聚焦」在右侧中部、
|
|
307
|
+
* 「保留 1 跳邻居」在底中,四件事散在三个角上,扫一遍要来回跳。
|
|
163
308
|
*
|
|
164
|
-
*
|
|
165
|
-
* 而不是把正文钉死在角上、盖住半张图。
|
|
309
|
+
* 全屏按钮**任何子视图都留着**:换个视图就丢掉出口,是这一轮最不该犯的错。
|
|
166
310
|
*/
|
|
167
|
-
export function
|
|
168
|
-
|
|
169
|
-
|
|
311
|
+
export function ReadBar({ legendOpen, onToggleLegend, showLegend, overview, onOverview,
|
|
312
|
+
includeNeighbors, onIncludeNeighbors, full, onToggleFull, showScope = true }: {
|
|
313
|
+
legendOpen: boolean
|
|
314
|
+
onToggleLegend: () => void
|
|
315
|
+
/** 案例 / 排查视图里没有图可读,图例、全景与邻居开关就没有对象。 */
|
|
316
|
+
showLegend: boolean
|
|
317
|
+
overview: boolean
|
|
318
|
+
onOverview: (value: boolean) => void
|
|
319
|
+
includeNeighbors: boolean
|
|
320
|
+
onIncludeNeighbors: (value: boolean) => void
|
|
170
321
|
full: boolean
|
|
171
322
|
onToggleFull: () => void
|
|
172
|
-
|
|
173
|
-
* 案例 / 排查视图里没有图可读,图例就没有对象。
|
|
174
|
-
*
|
|
175
|
-
* 但**全屏按钮必须留下来**:全屏是「这一整块(含三个视图)占满视口」,
|
|
176
|
-
* 换个视图就丢掉出口,是这一轮最不该犯的错。
|
|
177
|
-
*/
|
|
178
|
-
showLegend?: boolean
|
|
323
|
+
showScope?: boolean
|
|
179
324
|
}): ReactNode {
|
|
180
|
-
return <ChromePanel className="flex items-center gap-
|
|
325
|
+
return <ChromePanel className="flex items-center gap-[3px] p-[3px]">
|
|
181
326
|
{showLegend && <Button
|
|
182
327
|
size="sm"
|
|
183
|
-
variant={
|
|
184
|
-
className="h-[
|
|
185
|
-
aria-label={
|
|
186
|
-
aria-expanded={
|
|
187
|
-
title={
|
|
188
|
-
onClick={
|
|
328
|
+
variant={legendOpen ? 'default' : 'ghost'}
|
|
329
|
+
className="h-[24px] min-h-[24px] gap-1 px-1.5 text-[10.5px]"
|
|
330
|
+
aria-label={legendOpen ? '隐藏图例' : '显示图例'}
|
|
331
|
+
aria-expanded={legendOpen}
|
|
332
|
+
title={legendOpen ? '隐藏图例' : '显示图例:配色、十种边、空区与僵尸边'}
|
|
333
|
+
onClick={onToggleLegend}
|
|
189
334
|
><CircleHelp size={12} />图例</Button>}
|
|
335
|
+
|
|
336
|
+
{showLegend && showScope && <Button
|
|
337
|
+
size="sm"
|
|
338
|
+
variant="ghost"
|
|
339
|
+
className="h-[24px] min-h-[24px] px-1.5 text-[10.5px]"
|
|
340
|
+
aria-pressed={!overview}
|
|
341
|
+
title={overview ? '聚焦:只看选中节点的 1 跳邻域' : '全景:看整张图'}
|
|
342
|
+
onClick={() => onOverview(!overview)}
|
|
343
|
+
>{overview ? '全景' : '聚焦'}</Button>}
|
|
344
|
+
|
|
345
|
+
{showLegend && showScope && <Button
|
|
346
|
+
size="sm"
|
|
347
|
+
variant="ghost"
|
|
348
|
+
className="h-[24px] min-h-[24px] px-1.5 text-[10.5px]"
|
|
349
|
+
aria-pressed={includeNeighbors}
|
|
350
|
+
title="保留 1 跳邻居:只留命中节点会让图散成孤点,边的语义一起丢"
|
|
351
|
+
onClick={() => onIncludeNeighbors(!includeNeighbors)}
|
|
352
|
+
>1 跳邻居</Button>}
|
|
353
|
+
|
|
190
354
|
<Button
|
|
191
355
|
size="icon"
|
|
192
|
-
variant=
|
|
193
|
-
className="h-[
|
|
356
|
+
variant={full ? 'default' : 'outline'}
|
|
357
|
+
className="h-[24px] w-[24px] min-w-[24px]"
|
|
194
358
|
aria-label={full ? '退出全屏' : '全屏查看'}
|
|
195
359
|
title={full ? '退出全屏(Esc)' : '全屏查看:整个视口都用来放这块内容'}
|
|
196
360
|
onClick={onToggleFull}
|
|
@@ -205,18 +369,18 @@ export function LegendToggle({ open, onToggle, full, onToggleFull, showLegend =
|
|
|
205
369
|
* 用户会以为「图例已经开着」,而真正说明含义的那半仍然是藏着的。要么整块在,
|
|
206
370
|
* 要么整块不在,状态才可读。
|
|
207
371
|
*
|
|
208
|
-
* **两个分组必须分开写。**
|
|
372
|
+
* **两个分组必须分开写。** 这里的五种颜色**全部是原因侧**(失败原因,挂在假设上),
|
|
209
373
|
* 一份只画颜色的图例会让人以为它在说「问题有哪几类」—— 这正是最常被看错的一处。
|
|
210
|
-
*
|
|
374
|
+
* 所以原因侧明写「失败原因 · 原因侧」,问题侧(现象)单独一段、并且把
|
|
211
375
|
* 「现象节点有几个、有没有分类」如实报出来:**分类还没建的地方要看得见,而不是留白。**
|
|
212
376
|
*/
|
|
213
377
|
export function LegendBody({ stats }: { stats: AttributionStats }): ReactNode {
|
|
214
378
|
return <ChromePanel className="flex flex-col gap-1.5 p-2">
|
|
215
379
|
<div className="text-[9.5px] leading-[14px] text-tita-ink-faint">
|
|
216
|
-
<span className="font-[650] text-tita-ink-muted"
|
|
380
|
+
<span className="font-[650] text-tita-ink-muted">失败原因</span> · 原因侧(挂在假设上)
|
|
217
381
|
</div>
|
|
218
382
|
<div className="flex flex-wrap gap-1">
|
|
219
|
-
{Object.entries(
|
|
383
|
+
{Object.entries(FAILURE_REASON_STYLE).map(([key, tone]) => <span
|
|
220
384
|
key={key}
|
|
221
385
|
className="flex items-center gap-1 rounded-md border px-1.5 py-0.5 text-[10px] leading-[15px]"
|
|
222
386
|
style={{ borderColor: tone.stroke, background: tone.fill, color: tone.text }}
|
|
@@ -237,8 +401,8 @@ export function LegendBody({ stats }: { stats: AttributionStats }): ReactNode {
|
|
|
237
401
|
>{item.label}</span>)}
|
|
238
402
|
</div>
|
|
239
403
|
<p className="text-[9.5px] leading-relaxed text-tita-ink-muted">
|
|
240
|
-
|
|
241
|
-
|
|
404
|
+
问题大类刻意不给颜色:颜色通道已经被失败原因占用。问题侧走文字 —— 现象节点下方的
|
|
405
|
+
徽标写着它属于哪个大类,左上的「问题定义」切面按大类收窄,并把它的检查一起带出来。
|
|
242
406
|
</p>
|
|
243
407
|
</div>
|
|
244
408
|
|
|
@@ -253,78 +417,59 @@ export function LegendBody({ stats }: { stats: AttributionStats }): ReactNode {
|
|
|
253
417
|
</span>)}
|
|
254
418
|
</div>
|
|
255
419
|
<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> =
|
|
420
|
+
<li>圆的大小 = 复用次数(关系视图);动线视图里每个节点都读得出名字</li>
|
|
421
|
+
<li><span className="text-tita-danger">红色虚环 + 空区徽标</span> = 有假设但一条检查点都没有(当前 {stats.gaps} 个)。动线视图里它就是「② 段右边没有线」</li>
|
|
258
422
|
<li><span className="text-tita-ink-faint">灰色虚线</span> = 僵尸边,90 天未走(当前 {stats.zombies} 条)</li>
|
|
259
|
-
<li
|
|
423
|
+
<li>⑤ 段(实际查询与结果)默认折叠 —— 它随每次排查线性增长;被点亮的那条链例外</li>
|
|
260
424
|
</ul>
|
|
261
425
|
</div>
|
|
262
426
|
</ChromePanel>
|
|
263
427
|
}
|
|
264
428
|
|
|
265
|
-
/*
|
|
429
|
+
/* ─────────────────────────────────────────────── 画布底中:只剩搜索 ── */
|
|
266
430
|
|
|
267
431
|
/**
|
|
268
|
-
*
|
|
432
|
+
* 底中:**只剩文本搜索 + 命中数**(§7.1 的 v1.4 调整)。
|
|
269
433
|
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
434
|
+
* 失败原因切面上移到了左栏第 ③ 组,「保留 1 跳邻居」上移到了右上读法排。
|
|
435
|
+
* 搜索留在底中,因为它是**跨全图**的动作,不属于任何一段。
|
|
272
436
|
*/
|
|
273
|
-
export function FilterBar({ query, onQuery,
|
|
437
|
+
export function FilterBar({ query, onQuery, filtering, onClear, hit, total }: {
|
|
274
438
|
query: string
|
|
275
439
|
onQuery: (value: string) => void
|
|
276
|
-
modes: ReadonlySet<FailureMode>
|
|
277
|
-
onToggleMode: (mode: FailureMode) => void
|
|
278
|
-
includeNeighbors: boolean
|
|
279
|
-
onIncludeNeighbors: (value: boolean) => void
|
|
280
440
|
filtering: boolean
|
|
281
441
|
onClear: () => void
|
|
282
442
|
hit: number
|
|
283
443
|
total: number
|
|
284
444
|
}): 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>
|
|
445
|
+
return <ChromePanel className="flex w-[min(520px,100%)] items-center gap-1.5 p-2">
|
|
446
|
+
<div className="relative min-w-0 flex-1">
|
|
447
|
+
<Search size={12} className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 text-tita-ink-faint" />
|
|
448
|
+
<Input
|
|
449
|
+
value={query}
|
|
450
|
+
onChange={event => onQuery(event.target.value)}
|
|
451
|
+
placeholder="筛选节点:id / 标题 / 判据…"
|
|
452
|
+
className="h-[26px] min-h-[26px] pl-6 text-[11px]"
|
|
453
|
+
/>
|
|
313
454
|
</div>
|
|
455
|
+
<span className="shrink-0 text-[10px] tabular-nums text-tita-ink-faint">命中 {hit}/{total}</span>
|
|
456
|
+
{filtering && <Button size="sm" variant="ghost" className={MINI} onClick={onClear}><X size={11} />清除</Button>}
|
|
314
457
|
</ChromePanel>
|
|
315
458
|
}
|
|
316
459
|
|
|
317
|
-
/*
|
|
460
|
+
/* ─────────────────────────────────────────────── 画布左下:读数 ── */
|
|
318
461
|
|
|
319
462
|
/**
|
|
320
|
-
*
|
|
463
|
+
* 左下角统计:**从右下搬来**(§7.1 的 v1.4 调整)。
|
|
464
|
+
*
|
|
465
|
+
* 搬家的理由是左列因此形成「上面改、下面读」的一列,而右下让给图例 / 详情的展开正文。
|
|
321
466
|
*
|
|
322
467
|
* 只放**随筛选变化**的数:它回答的是「我现在看的这张图有多大、缺多少」。
|
|
323
|
-
*
|
|
468
|
+
* 图例里已经写了空区 / 僵尸边的含义,这里只给数,不重复解释。
|
|
324
469
|
*
|
|
325
|
-
* 数据来源徽标(`
|
|
326
|
-
*
|
|
327
|
-
*
|
|
470
|
+
* 数据来源徽标(`02_wiki/diagnosis` / `05_diagnosis(旧格式)`)也挂在这里:它和这些数
|
|
471
|
+
* 一样是「这份内容从哪来」的事实,而且它必须始终可见 —— 看到的是卡片还是旧格式的
|
|
472
|
+
* jsonl,不能靠猜。
|
|
328
473
|
*/
|
|
329
474
|
export function StatsStrip({ stats, source, problems }: {
|
|
330
475
|
stats: AttributionStats
|
|
@@ -332,7 +477,7 @@ export function StatsStrip({ stats, source, problems }: {
|
|
|
332
477
|
/** 编译 / 迁移过程中发现的问题。空数组表示一切正常。 */
|
|
333
478
|
problems: string[]
|
|
334
479
|
}): ReactNode {
|
|
335
|
-
return <ChromePanel className="flex flex-wrap items-center gap-x-2 gap-y-0.5 px-2 py-1">
|
|
480
|
+
return <ChromePanel className="flex w-[min(200px,100%)] flex-wrap items-center gap-x-2 gap-y-0.5 px-2 py-1">
|
|
336
481
|
<Stat label="类型" value={stats.typeNodes} />
|
|
337
482
|
<Divider />
|
|
338
483
|
<Stat label="引用" value={stats.referenceNodes} />
|
|
@@ -348,24 +493,23 @@ export function StatsStrip({ stats, source, problems }: {
|
|
|
348
493
|
title={problems.join('\n')}
|
|
349
494
|
>{problems.length} 个问题</span>}
|
|
350
495
|
<span
|
|
351
|
-
className="
|
|
352
|
-
title={SOURCE_HINT[source] ??
|
|
353
|
-
|
|
496
|
+
className="w-full shrink-0 truncate font-mono text-[9.5px] text-tita-ink-faint"
|
|
497
|
+
title={SOURCE_HINT[source] ?? source}
|
|
498
|
+
>来源 {source}</span>
|
|
354
499
|
</ChromePanel>
|
|
355
500
|
}
|
|
356
501
|
|
|
357
502
|
/**
|
|
358
503
|
* 数据来源的说明。
|
|
359
504
|
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
505
|
+
* 这里必须能分清两种形态,因为它们意味着不同的可信度:`02_wiki/diagnosis` 是当前真相
|
|
506
|
+
* (卡片),`05_diagnosis(旧格式)` 意味着迁移没能无损生效。一句笼统的「05_diagnosis」
|
|
507
|
+
* 会把它们混成一种,而这正是最不该让人猜的地方。
|
|
363
508
|
*/
|
|
364
509
|
const SOURCE_HINT: Record<string, string> = {
|
|
365
510
|
'02_wiki/diagnosis': '来自 wiki 卡片(诊断卡片是当前唯一的真相)',
|
|
366
511
|
'05_diagnosis(旧格式)': '旧格式 jsonl:迁移会丢知识,因此仍按原样读',
|
|
367
|
-
'
|
|
368
|
-
'seed 数据': '宿主未就绪,当前展示的是内置示例',
|
|
512
|
+
'还没有排查知识': '这个知识库还没有排查知识',
|
|
369
513
|
}
|
|
370
514
|
|
|
371
515
|
function Divider(): ReactNode {
|
|
@@ -382,9 +526,9 @@ function Stat({ label, value, tone }: { label: string; value: number; tone?: 'wa
|
|
|
382
526
|
</span>
|
|
383
527
|
}
|
|
384
528
|
|
|
385
|
-
/*
|
|
529
|
+
/* ─────────────────────────────────────────────── 画布右侧:选中节点详情 ── */
|
|
386
530
|
|
|
387
|
-
export function NodeDetailCard({ node }: { node: AttributionNode }): ReactNode {
|
|
531
|
+
export function NodeDetailCard({ node, onClear }: { node: AttributionNode; onClear?: () => void }): ReactNode {
|
|
388
532
|
return <ChromePanel className="flex flex-col gap-1.5 p-2">
|
|
389
533
|
<div className="flex items-start justify-between gap-2">
|
|
390
534
|
<span className="text-[11.5px] font-semibold leading-snug">{node.title}</span>
|
|
@@ -393,11 +537,23 @@ export function NodeDetailCard({ node }: { node: AttributionNode }): ReactNode {
|
|
|
393
537
|
{node.summary && <p className="text-[10.5px] leading-relaxed text-tita-ink-muted">{node.summary}</p>}
|
|
394
538
|
<div className="flex flex-wrap gap-1">
|
|
395
539
|
<Badge tone="neutral">{NODE_KIND_LABEL[node.kind]}</Badge>
|
|
396
|
-
{node.
|
|
540
|
+
{node.failureReason && <Badge tone="violet">{FAILURE_REASON_STYLE[node.failureReason].label}</Badge>}
|
|
541
|
+
{node.problemClass && <Badge tone="neutral">{PROBLEM_CLASS_LABEL[node.problemClass]}</Badge>}
|
|
397
542
|
{node.reuseCount !== undefined && <Badge tone="neutral">复用 {node.reuseCount}</Badge>}
|
|
398
543
|
{node.costMinutes !== undefined && <Badge tone={node.automation === 'auto' ? 'success' : 'warning'}>
|
|
399
544
|
{node.costMinutes} min · {node.automation === 'auto' ? '自动' : '人工'}
|
|
400
545
|
</Badge>}
|
|
401
546
|
</div>
|
|
547
|
+
{/* 检查点的查询依据:它落在 `queries` 边上,从节点本身读不出来。 */}
|
|
548
|
+
{node.assets && node.assets.length > 0 && <p className="text-[10px] leading-snug text-tita-ink-faint">
|
|
549
|
+
要去查:{node.assets.join(' · ')}
|
|
550
|
+
</p>}
|
|
551
|
+
{node.discriminator && <p className="text-[10px] leading-snug text-tita-ink-muted">判据:{node.discriminator}</p>}
|
|
552
|
+
{node.why && <p className="text-[10px] leading-snug text-tita-ink-muted">为什么算数:{node.why}</p>}
|
|
553
|
+
{onClear && <button
|
|
554
|
+
type="button"
|
|
555
|
+
onClick={onClear}
|
|
556
|
+
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"
|
|
557
|
+
><ChevronRight size={10} />取消点亮,回到全亮</button>}
|
|
402
558
|
</ChromePanel>
|
|
403
559
|
}
|