concept-atlas-dense-explain 0.1.0
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/bin/install.mjs +17 -0
- package/package.json +12 -0
- package/skill/SKILL.md +31 -0
- package/skill/assets/template/content/compile-runtime.mdx +158 -0
- package/skill/assets/template/index.html +16 -0
- package/skill/assets/template/package.json +21 -0
- package/skill/assets/template/scripts/build.mjs +37 -0
- package/skill/assets/template/scripts/clean-temp.mjs +27 -0
- package/skill/assets/template/src/app/App.jsx +239 -0
- package/skill/assets/template/src/components/MDXComponents.jsx +479 -0
- package/skill/assets/template/src/components/index.js +1 -0
- package/skill/assets/template/src/main.jsx +16 -0
- package/skill/assets/template/src/model/concept-schema.js +169 -0
- package/skill/assets/template/src/model/normalize-content.js +238 -0
- package/skill/assets/template/src/model/relation-types.js +90 -0
- package/skill/assets/template/src/styles/concept-explain.css +2425 -0
- package/skill/assets/template/src/views/NodeExplorer.jsx +629 -0
- package/skill/assets/template/src/views/RelationGraph.jsx +708 -0
- package/skill/assets/template/vite.config.js +21 -0
- package/skill/references/components.md +27 -0
- package/skill/references/prompting.md +38 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import mermaid from 'mermaid';
|
|
3
|
+
|
|
4
|
+
let mermaidReady = false;
|
|
5
|
+
|
|
6
|
+
function ensureMermaid() {
|
|
7
|
+
if (mermaidReady) return;
|
|
8
|
+
mermaid.initialize({
|
|
9
|
+
startOnLoad: false,
|
|
10
|
+
securityLevel: 'loose',
|
|
11
|
+
theme: 'base',
|
|
12
|
+
look: 'handDrawn',
|
|
13
|
+
themeVariables: {
|
|
14
|
+
primaryColor: '#172554',
|
|
15
|
+
primaryTextColor: '#e0f2fe',
|
|
16
|
+
primaryBorderColor: '#38bdf8',
|
|
17
|
+
lineColor: '#64748b',
|
|
18
|
+
secondaryColor: '#172554',
|
|
19
|
+
tertiaryColor: '#0f172a',
|
|
20
|
+
fontFamily: 'Plus Jakarta Sans, sans-serif',
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
mermaidReady = true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Data Layer Components
|
|
27
|
+
export function ExplainPage({ id, title, summary, layout = 'editorial', density = 'reading', children }) {
|
|
28
|
+
return <div data-component="ExplainPage" data-id={id} data-title={title} data-summary={summary} data-layout={layout} data-density={density}>{children}</div>;
|
|
29
|
+
}
|
|
30
|
+
ExplainPage.displayName = 'ExplainPage';
|
|
31
|
+
|
|
32
|
+
export function ConceptGraph({ root, children }) {
|
|
33
|
+
return <div data-component="ConceptGraph" data-root={root}>{children}</div>;
|
|
34
|
+
}
|
|
35
|
+
ConceptGraph.displayName = 'ConceptGraph';
|
|
36
|
+
|
|
37
|
+
export function ConceptNode({ id, title, level = 'L2', parent = null, children, input, output, summary }) {
|
|
38
|
+
return <div data-component="ConceptNode" data-id={id} data-title={title} data-level={level} data-parent={parent}>{children}</div>;
|
|
39
|
+
}
|
|
40
|
+
ConceptNode.displayName = 'ConceptNode';
|
|
41
|
+
|
|
42
|
+
export function ConceptRef({ id }) {
|
|
43
|
+
return <span data-component="ConceptRef" data-id={id} />;
|
|
44
|
+
}
|
|
45
|
+
ConceptRef.displayName = 'ConceptRef';
|
|
46
|
+
|
|
47
|
+
export function Children({ children }) {
|
|
48
|
+
return <div data-component="Children">{children}</div>;
|
|
49
|
+
}
|
|
50
|
+
Children.displayName = 'Children';
|
|
51
|
+
|
|
52
|
+
export function Relation({ from, to, type = 'depends-on', label, description, children }) {
|
|
53
|
+
return <div data-component="Relation" data-from={from} data-to={to} data-type={type} data-label={label}>{children || description}</div>;
|
|
54
|
+
}
|
|
55
|
+
Relation.displayName = 'Relation';
|
|
56
|
+
|
|
57
|
+
// Content Layer Components
|
|
58
|
+
export function Overview({ children }) {
|
|
59
|
+
return <div className="semantic-overview">{children}</div>;
|
|
60
|
+
}
|
|
61
|
+
Overview.displayName = 'Overview';
|
|
62
|
+
|
|
63
|
+
export function Definition({ children }) {
|
|
64
|
+
return <div className="semantic-definition">{children}</div>;
|
|
65
|
+
}
|
|
66
|
+
Definition.displayName = 'Definition';
|
|
67
|
+
|
|
68
|
+
export function Mechanism({ children }) {
|
|
69
|
+
return <div className="semantic-mechanism">{children}</div>;
|
|
70
|
+
}
|
|
71
|
+
Mechanism.displayName = 'Mechanism';
|
|
72
|
+
|
|
73
|
+
export function Implementation({ language = 'text', title = '实现代码', children }) {
|
|
74
|
+
return (
|
|
75
|
+
<div className="semantic-implementation" data-language={language}>
|
|
76
|
+
{title && <div className="impl-header">{title} <span className="lang-badge">{language}</span></div>}
|
|
77
|
+
<pre><code>{typeof children === 'string' ? children.trim() : children}</code></pre>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
Implementation.displayName = 'Implementation';
|
|
82
|
+
|
|
83
|
+
export function Boundary({ title = '边界与约束', children }) {
|
|
84
|
+
return (
|
|
85
|
+
<div className="semantic-boundary">
|
|
86
|
+
<div className="semantic-tag">⚠ {title}</div>
|
|
87
|
+
<div className="boundary-body">{children}</div>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
Boundary.displayName = 'Boundary';
|
|
92
|
+
|
|
93
|
+
export function Example({ title = '典型示例', children }) {
|
|
94
|
+
return (
|
|
95
|
+
<div className="semantic-example">
|
|
96
|
+
<div className="semantic-tag">✦ {title}</div>
|
|
97
|
+
<div className="example-body">{children}</div>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
Example.displayName = 'Example';
|
|
102
|
+
|
|
103
|
+
export function Counterexample({ title = '反例与误区', children }) {
|
|
104
|
+
return (
|
|
105
|
+
<div className="semantic-counterexample">
|
|
106
|
+
<div className="semantic-tag">✕ {title}</div>
|
|
107
|
+
<div className="counter-body">{children}</div>
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
Counterexample.displayName = 'Counterexample';
|
|
112
|
+
|
|
113
|
+
export function Prerequisite({ children }) {
|
|
114
|
+
return <div className="semantic-prerequisite">{children}</div>;
|
|
115
|
+
}
|
|
116
|
+
Prerequisite.displayName = 'Prerequisite';
|
|
117
|
+
|
|
118
|
+
export function Input({ children }) {
|
|
119
|
+
return <div className="semantic-input">{children}</div>;
|
|
120
|
+
}
|
|
121
|
+
Input.displayName = 'Input';
|
|
122
|
+
|
|
123
|
+
export function Output({ children }) {
|
|
124
|
+
return <div className="semantic-output">{children}</div>;
|
|
125
|
+
}
|
|
126
|
+
Output.displayName = 'Output';
|
|
127
|
+
|
|
128
|
+
export function Glossary({ term, children }) {
|
|
129
|
+
return (
|
|
130
|
+
<div className="semantic-glossary-item">
|
|
131
|
+
<span className="term">{term}</span>: <span className="def">{children}</span>
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
Glossary.displayName = 'Glossary';
|
|
136
|
+
|
|
137
|
+
// Presentation Semantic Layer Components (for structuring inside nodes without direct CSS)
|
|
138
|
+
export function Compare({ items = [], children }) {
|
|
139
|
+
if (items && items.length > 0) {
|
|
140
|
+
return (
|
|
141
|
+
<div className="semantic-compare-table">
|
|
142
|
+
<table className="compare-grid">
|
|
143
|
+
<thead>
|
|
144
|
+
<tr>
|
|
145
|
+
<th>维度 / 对象</th>
|
|
146
|
+
{items.map((it, idx) => (
|
|
147
|
+
<th key={idx}>{it.label || it.title || `方案 ${idx + 1}`}</th>
|
|
148
|
+
))}
|
|
149
|
+
</tr>
|
|
150
|
+
</thead>
|
|
151
|
+
<tbody>
|
|
152
|
+
{items[0]?.rows ? (
|
|
153
|
+
items[0].rows.map((rowKey, rIdx) => (
|
|
154
|
+
<tr key={rIdx}>
|
|
155
|
+
<td className="compare-row-label">{rowKey}</td>
|
|
156
|
+
{items.map((it, idx) => (
|
|
157
|
+
<td key={idx}>{it.values?.[rIdx] || '-'}</td>
|
|
158
|
+
))}
|
|
159
|
+
</tr>
|
|
160
|
+
))
|
|
161
|
+
) : null}
|
|
162
|
+
</tbody>
|
|
163
|
+
</table>
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
return <div className="semantic-compare-block">{children}</div>;
|
|
168
|
+
}
|
|
169
|
+
Compare.displayName = 'Compare';
|
|
170
|
+
|
|
171
|
+
export function DecisionMatrix({ title = '权衡矩阵', headers = [], rows = [], children }) {
|
|
172
|
+
if (headers.length > 0 && rows.length > 0) {
|
|
173
|
+
return (
|
|
174
|
+
<div className="semantic-decision-matrix">
|
|
175
|
+
{title && <div className="matrix-title">⚖ {title}</div>}
|
|
176
|
+
<table className="matrix-table">
|
|
177
|
+
<thead>
|
|
178
|
+
<tr>
|
|
179
|
+
{headers.map((h, i) => <th key={i}>{h}</th>)}
|
|
180
|
+
</tr>
|
|
181
|
+
</thead>
|
|
182
|
+
<tbody>
|
|
183
|
+
{rows.map((row, i) => (
|
|
184
|
+
<tr key={i}>
|
|
185
|
+
{row.map((cell, j) => <td key={j}>{cell}</td>)}
|
|
186
|
+
</tr>
|
|
187
|
+
))}
|
|
188
|
+
</tbody>
|
|
189
|
+
</table>
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
return <div className="semantic-decision-matrix">{children}</div>;
|
|
194
|
+
}
|
|
195
|
+
DecisionMatrix.displayName = 'DecisionMatrix';
|
|
196
|
+
|
|
197
|
+
export function Flow({ steps = [], children }) {
|
|
198
|
+
if (steps.length > 0) {
|
|
199
|
+
return (
|
|
200
|
+
<div className="semantic-flow-steps">
|
|
201
|
+
{steps.map((step, idx) => (
|
|
202
|
+
<div key={idx} className="flow-step">
|
|
203
|
+
<span className="step-num">{idx + 1}</span>
|
|
204
|
+
<span className="step-text">{step}</span>
|
|
205
|
+
{idx < steps.length - 1 && <span className="step-arrow">→</span>}
|
|
206
|
+
</div>
|
|
207
|
+
))}
|
|
208
|
+
</div>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
return <div className="semantic-flow-block">{children}</div>;
|
|
212
|
+
}
|
|
213
|
+
Flow.displayName = 'Flow';
|
|
214
|
+
|
|
215
|
+
export function Timeline({ events = [], children }) {
|
|
216
|
+
if (events.length > 0) {
|
|
217
|
+
return (
|
|
218
|
+
<div className="semantic-timeline">
|
|
219
|
+
{events.map((ev, i) => (
|
|
220
|
+
<div key={i} className="timeline-item">
|
|
221
|
+
<div className="timeline-point" />
|
|
222
|
+
<div className="timeline-label">{ev.label || ev.time}</div>
|
|
223
|
+
<div className="timeline-content">{ev.content || ev.desc}</div>
|
|
224
|
+
</div>
|
|
225
|
+
))}
|
|
226
|
+
</div>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
return <div className="semantic-timeline">{children}</div>;
|
|
230
|
+
}
|
|
231
|
+
Timeline.displayName = 'Timeline';
|
|
232
|
+
|
|
233
|
+
export function Callout({ type = 'info', title, children }) {
|
|
234
|
+
return (
|
|
235
|
+
<div className={`semantic-callout callout-${type}`}>
|
|
236
|
+
{title && <div className="callout-title">{title}</div>}
|
|
237
|
+
<div className="callout-content">{children}</div>
|
|
238
|
+
</div>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
Callout.displayName = 'Callout';
|
|
242
|
+
|
|
243
|
+
export function Details({ summary = '详细展开', children }) {
|
|
244
|
+
return (
|
|
245
|
+
<details className="semantic-details">
|
|
246
|
+
<summary>{summary}</summary>
|
|
247
|
+
<div className="details-body">{children}</div>
|
|
248
|
+
</details>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
Details.displayName = 'Details';
|
|
252
|
+
|
|
253
|
+
export function LearningObjectives({ items = [], children }) {
|
|
254
|
+
const goals = Array.isArray(items) ? items.filter(Boolean) : [];
|
|
255
|
+
return (
|
|
256
|
+
<section className="semantic-learning-objectives">
|
|
257
|
+
<div className="semantic-tag">◎ 学习目标</div>
|
|
258
|
+
{goals.length > 0 ? <ul>{goals.map((item, index) => <li key={index}>{item}</li>)}</ul> : children}
|
|
259
|
+
</section>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
LearningObjectives.displayName = 'LearningObjectives';
|
|
263
|
+
|
|
264
|
+
export function KeyQuestion({ children }) {
|
|
265
|
+
return (
|
|
266
|
+
<aside className="semantic-key-question">
|
|
267
|
+
<span className="semantic-tag">? 引导问题</span>
|
|
268
|
+
<strong>{children}</strong>
|
|
269
|
+
</aside>
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
KeyQuestion.displayName = 'KeyQuestion';
|
|
273
|
+
|
|
274
|
+
export function Evidence({ command, observes, children }) {
|
|
275
|
+
return (
|
|
276
|
+
<div className="semantic-evidence">
|
|
277
|
+
<div className="semantic-tag">⌕ 可验证证据</div>
|
|
278
|
+
{command && <code className="evidence-command">{command}</code>}
|
|
279
|
+
{(observes || children) && <div className="evidence-observes">{observes || children}</div>}
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
Evidence.displayName = 'Evidence';
|
|
284
|
+
|
|
285
|
+
export function Invariant({ title = '不变量', children }) {
|
|
286
|
+
return (
|
|
287
|
+
<div className="semantic-invariant">
|
|
288
|
+
<div className="semantic-tag">◆ {title}</div>
|
|
289
|
+
<div>{children}</div>
|
|
290
|
+
</div>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
Invariant.displayName = 'Invariant';
|
|
294
|
+
|
|
295
|
+
export function FailureMode({ symptom, cause, evidence, remedy, children }) {
|
|
296
|
+
const rows = [['现象', symptom], ['原因', cause], ['证据', evidence], ['建议', remedy]].filter(([, value]) => value);
|
|
297
|
+
return (
|
|
298
|
+
<div className="semantic-failure-mode">
|
|
299
|
+
<div className="semantic-tag">⚠ 故障模式</div>
|
|
300
|
+
{rows.length > 0 && <dl>{rows.map(([label, value]) => <React.Fragment key={label}><dt>{label}</dt><dd>{value}</dd></React.Fragment>)}</dl>}
|
|
301
|
+
{children && <div className="failure-details">{children}</div>}
|
|
302
|
+
</div>
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
FailureMode.displayName = 'FailureMode';
|
|
306
|
+
|
|
307
|
+
export function Tradeoff({ title = '工程权衡', options = [], children }) {
|
|
308
|
+
return (
|
|
309
|
+
<div className="semantic-tradeoff">
|
|
310
|
+
<div className="semantic-tag">⚖ {title}</div>
|
|
311
|
+
{options.length > 0 ? (
|
|
312
|
+
<div className="tradeoff-options">
|
|
313
|
+
{options.map((option, index) => (
|
|
314
|
+
<div className="tradeoff-option" key={index}>
|
|
315
|
+
<strong>{option.name || option.label || `方案 ${index + 1}`}</strong>
|
|
316
|
+
{option.benefit && <span><b>收益</b>{option.benefit}</span>}
|
|
317
|
+
{option.cost && <span><b>代价</b>{option.cost}</span>}
|
|
318
|
+
{option.when && <span><b>适用</b>{option.when}</span>}
|
|
319
|
+
</div>
|
|
320
|
+
))}
|
|
321
|
+
</div>
|
|
322
|
+
) : children}
|
|
323
|
+
</div>
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
Tradeoff.displayName = 'Tradeoff';
|
|
327
|
+
|
|
328
|
+
export function Columns({ children }) {
|
|
329
|
+
return <div className="semantic-columns">{children}</div>;
|
|
330
|
+
}
|
|
331
|
+
Columns.displayName = 'Columns';
|
|
332
|
+
|
|
333
|
+
/** Optional layout primitives. They express intent while the template owns the CSS. */
|
|
334
|
+
export function Stack({ gap = 'md', children }) {
|
|
335
|
+
return <div className={`semantic-stack gap-${gap}`}>{children}</div>;
|
|
336
|
+
}
|
|
337
|
+
Stack.displayName = 'Stack';
|
|
338
|
+
|
|
339
|
+
export function Grid({ columns = 'auto', gap = 'md', children }) {
|
|
340
|
+
return <div className={`semantic-grid grid-${columns} gap-${gap}`}>{children}</div>;
|
|
341
|
+
}
|
|
342
|
+
Grid.displayName = 'Grid';
|
|
343
|
+
|
|
344
|
+
export function Split({ ratio = '1fr 1fr', children }) {
|
|
345
|
+
return <div className="semantic-split" style={{ '--split-ratio': ratio }}>{children}</div>;
|
|
346
|
+
}
|
|
347
|
+
Split.displayName = 'Split';
|
|
348
|
+
|
|
349
|
+
export function Tabs({ items = [], children }) {
|
|
350
|
+
return <div className="semantic-tabs" data-tab-count={items.length || undefined}>{items.length ? items.map((item, i) => <details key={i} open={i === 0}><summary>{item.label || item.title}</summary><div>{item.content}</div></details>) : children}</div>;
|
|
351
|
+
}
|
|
352
|
+
Tabs.displayName = 'Tabs';
|
|
353
|
+
|
|
354
|
+
export function Mermaid({ chart = '', title = '关系草图', width = 'auto', height = 'auto', x = 0, y = 0, position = 'flow' }) {
|
|
355
|
+
const ref = React.useRef(null);
|
|
356
|
+
const id = React.useId().replace(/:/g, '');
|
|
357
|
+
const [error, setError] = React.useState('');
|
|
358
|
+
const [scale, setScale] = React.useState(1);
|
|
359
|
+
const [pan, setPan] = React.useState({ x: 0, y: 0 });
|
|
360
|
+
|
|
361
|
+
React.useEffect(() => {
|
|
362
|
+
let cancelled = false;
|
|
363
|
+
async function renderChart() {
|
|
364
|
+
if (!ref.current || !chart.trim()) return;
|
|
365
|
+
try {
|
|
366
|
+
ensureMermaid();
|
|
367
|
+
const { svg } = await mermaid.render(`mermaid-${id}`, chart.trim());
|
|
368
|
+
if (!cancelled && ref.current) {
|
|
369
|
+
ref.current.innerHTML = svg;
|
|
370
|
+
setError('');
|
|
371
|
+
}
|
|
372
|
+
} catch (err) {
|
|
373
|
+
if (!cancelled) setError(err.message || 'Mermaid 图表语法错误');
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
renderChart();
|
|
377
|
+
return () => { cancelled = true; };
|
|
378
|
+
}, [chart, id]);
|
|
379
|
+
|
|
380
|
+
const zoom = (delta) => setScale(value => Math.max(0.5, Math.min(3, +(value + delta).toFixed(2))));
|
|
381
|
+
const reset = () => { setScale(1); setPan({ x: 0, y: 0 }); };
|
|
382
|
+
return (
|
|
383
|
+
<div className={`semantic-mermaid ${widgetClass(position)}`} style={widgetStyle({ width, height, x, y, position })}>
|
|
384
|
+
<div className="semantic-widget-head">
|
|
385
|
+
<span>{title}</span>
|
|
386
|
+
<div className="mermaid-tools"><code>MERMAID</code><button type="button" onClick={() => zoom(-0.1)} aria-label="缩小图表">−</button><span>{Math.round(scale * 100)}%</span><button type="button" onClick={() => zoom(0.1)} aria-label="放大图表">+</button><button type="button" onClick={reset} aria-label="重置图表">↺</button></div>
|
|
387
|
+
</div>
|
|
388
|
+
{error ? <pre className="mermaid-error">{error}</pre> : <div className="mermaid-canvas" onWheel={(event) => { event.preventDefault(); zoom(event.deltaY < 0 ? 0.1 : -0.1); }}><div className="mermaid-canvas-inner" style={{ transform: `translate(${pan.x}px, ${pan.y}px) scale(${scale})` }} ref={ref} /></div>}
|
|
389
|
+
</div>
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
Mermaid.displayName = 'Mermaid';
|
|
393
|
+
|
|
394
|
+
export function RelationMap({ title = '关系速览', items = [], children, width = 'auto', height = 'auto', x = 0, y = 0, position = 'flow' }) {
|
|
395
|
+
return (
|
|
396
|
+
<div className={`semantic-relation-map ${widgetClass(position)}`} style={widgetStyle({ width, height, x, y, position })}>
|
|
397
|
+
<div className="semantic-widget-head"><span>{title}</span><code>RELATIONS</code></div>
|
|
398
|
+
{items.length > 0 ? (
|
|
399
|
+
<div className="relation-map-grid">
|
|
400
|
+
{items.map((item, index) => (
|
|
401
|
+
<div className="relation-map-row" key={index}>
|
|
402
|
+
<span className="relation-map-source">{item.from || item.source}</span>
|
|
403
|
+
<span className="relation-map-arrow">{item.type || '→'}</span>
|
|
404
|
+
<span className="relation-map-target">{item.to || item.target}</span>
|
|
405
|
+
{item.note && <small>{item.note}</small>}
|
|
406
|
+
</div>
|
|
407
|
+
))}
|
|
408
|
+
</div>
|
|
409
|
+
) : children}
|
|
410
|
+
</div>
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
RelationMap.displayName = 'RelationMap';
|
|
414
|
+
|
|
415
|
+
export function RelationPath({ title = '关系链', steps = [], children }) {
|
|
416
|
+
const safeSteps = Array.isArray(steps) ? steps.filter(Boolean) : [];
|
|
417
|
+
return (
|
|
418
|
+
<div className="semantic-relation-path">
|
|
419
|
+
{title && <div className="semantic-widget-head"><span>{title}</span><code>PATH</code></div>}
|
|
420
|
+
{safeSteps.length > 0 ? (
|
|
421
|
+
<div className="relation-path-steps">
|
|
422
|
+
{safeSteps.map((step, index) => (
|
|
423
|
+
<React.Fragment key={index}>
|
|
424
|
+
<div className={`relation-path-node tone-${step.tone || 'info'}`}>
|
|
425
|
+
<span className="relation-path-kicker">{step.level || `0${index + 1}`}</span>
|
|
426
|
+
<strong>{step.node || step.title}</strong>
|
|
427
|
+
{step.note && <small>{step.note}</small>}
|
|
428
|
+
</div>
|
|
429
|
+
{index < safeSteps.length - 1 && <div className="relation-path-edge"><span>{safeSteps[index].relation || '→'}</span></div>}
|
|
430
|
+
</React.Fragment>
|
|
431
|
+
))}
|
|
432
|
+
</div>
|
|
433
|
+
) : children}
|
|
434
|
+
</div>
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
RelationPath.displayName = 'RelationPath';
|
|
438
|
+
|
|
439
|
+
export function Insight({ title = '关键判断', tone = 'info', children, width = 'auto', height = 'auto', x = 0, y = 0, position = 'flow' }) {
|
|
440
|
+
return (
|
|
441
|
+
<aside className={`semantic-insight insight-${tone} ${widgetClass(position)}`} style={widgetStyle({ width, height, x, y, position })}>
|
|
442
|
+
<div className="insight-kicker">{tone === 'warn' ? '⚠' : tone === 'success' ? '✓' : '◆'} {title}</div>
|
|
443
|
+
<div className="insight-body">{children}</div>
|
|
444
|
+
</aside>
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
Insight.displayName = 'Insight';
|
|
448
|
+
|
|
449
|
+
export function NoteGrid({ notes = [], children, width = 'auto', height = 'auto', x = 0, y = 0, position = 'flow' }) {
|
|
450
|
+
return (
|
|
451
|
+
<div className={`semantic-note-grid ${widgetClass(position)}`} style={widgetStyle({ width, height, x, y, position })}>
|
|
452
|
+
{notes.length > 0 ? notes.map((note, index) => (
|
|
453
|
+
<div className="semantic-note" key={index}>
|
|
454
|
+
<strong>{note.title || note.label}</strong>
|
|
455
|
+
<span>{note.content || note.text || note.description}</span>
|
|
456
|
+
</div>
|
|
457
|
+
)) : children}
|
|
458
|
+
</div>
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
NoteGrid.displayName = 'NoteGrid';
|
|
462
|
+
|
|
463
|
+
function widgetStyle({ width, height, x, y, position }) {
|
|
464
|
+
const style = {};
|
|
465
|
+
if (width && width !== 'auto') style.width = width;
|
|
466
|
+
if (height && height !== 'auto') style.height = height;
|
|
467
|
+
if (position === 'absolute') {
|
|
468
|
+
style.position = 'absolute';
|
|
469
|
+
style.left = `${x}px`;
|
|
470
|
+
style.top = `${y}px`;
|
|
471
|
+
} else if (x || y) {
|
|
472
|
+
style.transform = `translate(${x}px, ${y}px)`;
|
|
473
|
+
}
|
|
474
|
+
return style;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function widgetClass(position) {
|
|
478
|
+
return position === 'absolute' ? 'semantic-widget-absolute' : '';
|
|
479
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './MDXComponents.jsx';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom/client';
|
|
3
|
+
import { App } from './app/App.jsx';
|
|
4
|
+
import * as Components from './components/index.js';
|
|
5
|
+
import CompileRuntimeDoc from '../content/compile-runtime.mdx';
|
|
6
|
+
|
|
7
|
+
// Mount MDX application to DOM
|
|
8
|
+
const rootElement = document.getElementById('root') || document.getElementById('app');
|
|
9
|
+
if (rootElement) {
|
|
10
|
+
const root = ReactDOM.createRoot(rootElement);
|
|
11
|
+
root.render(
|
|
12
|
+
<React.StrictMode>
|
|
13
|
+
<App mdxContent={<CompileRuntimeDoc components={Components} />} />
|
|
14
|
+
</React.StrictMode>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { RELATION_TYPES, LEVEL_DEFS } from './relation-types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates an empty Concept Knowledge Graph structure
|
|
6
|
+
*/
|
|
7
|
+
export function createConceptGraphState() {
|
|
8
|
+
return {
|
|
9
|
+
meta: {
|
|
10
|
+
id: '',
|
|
11
|
+
title: '',
|
|
12
|
+
summary: '',
|
|
13
|
+
rootId: ''
|
|
14
|
+
},
|
|
15
|
+
nodes: new Map(), // id -> NodeObject
|
|
16
|
+
relations: [], // Array<{ from, to, type, label, description }>
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Normalizes raw node data extracted from MDX AST or React component tree
|
|
22
|
+
*/
|
|
23
|
+
export function normalizeNode(raw) {
|
|
24
|
+
const id = raw.id || String(Math.random());
|
|
25
|
+
return {
|
|
26
|
+
id,
|
|
27
|
+
title: raw.title || id,
|
|
28
|
+
level: raw.level || 'L2',
|
|
29
|
+
parent: raw.parent || null,
|
|
30
|
+
children: Array.isArray(raw.children) ? raw.children : [],
|
|
31
|
+
|
|
32
|
+
// Semantic Content Sections
|
|
33
|
+
summary: raw.summary || '',
|
|
34
|
+
overview: raw.overview || null,
|
|
35
|
+
definition: raw.definition || null,
|
|
36
|
+
mechanism: raw.mechanism || null,
|
|
37
|
+
input: raw.input || null,
|
|
38
|
+
output: raw.output || null,
|
|
39
|
+
prerequisites: raw.prerequisites || [],
|
|
40
|
+
implementation: raw.implementation || null, // { language, code } or React node
|
|
41
|
+
examples: raw.examples || [],
|
|
42
|
+
counterexamples: raw.counterexamples || [],
|
|
43
|
+
boundaries: raw.boundaries || [],
|
|
44
|
+
glossary: raw.glossary || [],
|
|
45
|
+
customSections: raw.customSections || [],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Validates and indexes nodes and relations to build a graph model
|
|
51
|
+
*/
|
|
52
|
+
export function buildGraphModel(rawGraph) {
|
|
53
|
+
const nodes = new Map();
|
|
54
|
+
const diagnostics = [];
|
|
55
|
+
const rawNodes = rawGraph.nodes || [];
|
|
56
|
+
const rawRelations = rawGraph.relations || [];
|
|
57
|
+
|
|
58
|
+
// Register all nodes
|
|
59
|
+
rawNodes.forEach(n => {
|
|
60
|
+
const node = normalizeNode(n);
|
|
61
|
+
if (nodes.has(node.id)) diagnostics.push({ level: 'error', code: 'DUPLICATE_NODE_ID', nodeId: node.id });
|
|
62
|
+
nodes.set(node.id, node);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Build tree hierarchy: ensure parent <-> children consistency
|
|
66
|
+
nodes.forEach(node => {
|
|
67
|
+
if (node.parent && nodes.has(node.parent)) {
|
|
68
|
+
const parentNode = nodes.get(node.parent);
|
|
69
|
+
if (!parentNode.children.includes(node.id)) {
|
|
70
|
+
parentNode.children.push(node.id);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Explicit children references (from <ConceptRef />)
|
|
76
|
+
nodes.forEach(node => {
|
|
77
|
+
node.children.forEach(childId => {
|
|
78
|
+
if (nodes.has(childId)) {
|
|
79
|
+
const childNode = nodes.get(childId);
|
|
80
|
+
if (!childNode.parent) {
|
|
81
|
+
childNode.parent = node.id;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else diagnostics.push({ level: 'warning', code: 'MISSING_CHILD', nodeId: node.id, targetId: childId });
|
|
85
|
+
});
|
|
86
|
+
if (node.parent && !nodes.has(node.parent)) diagnostics.push({ level: 'warning', code: 'ORPHAN_NODE', nodeId: node.id, targetId: node.parent });
|
|
87
|
+
if (!LEVEL_DEFS[node.level]) diagnostics.push({ level: 'warning', code: 'UNKNOWN_LEVEL', nodeId: node.id, level: node.level });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Filter valid relations
|
|
91
|
+
const validRelations = [];
|
|
92
|
+
rawRelations.forEach(rel => {
|
|
93
|
+
if (nodes.has(rel.from) && nodes.has(rel.to)) {
|
|
94
|
+
const typeDef = RELATION_TYPES[rel.type] || {
|
|
95
|
+
label: rel.type,
|
|
96
|
+
color: '#94a3b8',
|
|
97
|
+
hasArrow: true
|
|
98
|
+
};
|
|
99
|
+
validRelations.push({
|
|
100
|
+
...rel,
|
|
101
|
+
typeLabel: rel.label || typeDef.label,
|
|
102
|
+
typeInfo: typeDef
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Determine root
|
|
108
|
+
let rootId = rawGraph.meta?.rootId;
|
|
109
|
+
if (!rootId || !nodes.has(rootId)) {
|
|
110
|
+
// Find node with no parent or level L0
|
|
111
|
+
for (const [id, node] of nodes.entries()) {
|
|
112
|
+
if (!node.parent || node.level === 'L0') {
|
|
113
|
+
rootId = id;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
meta: {
|
|
121
|
+
...rawGraph.meta,
|
|
122
|
+
rootId: rootId || (nodes.size > 0 ? Array.from(nodes.keys())[0] : null)
|
|
123
|
+
},
|
|
124
|
+
nodes,
|
|
125
|
+
relations: validRelations,
|
|
126
|
+
diagnostics
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Helper to compute ancestor path from root to current node
|
|
132
|
+
*/
|
|
133
|
+
export function getAncestorPath(nodes, nodeId) {
|
|
134
|
+
const path = [];
|
|
135
|
+
let current = nodes.get(nodeId);
|
|
136
|
+
const visited = new Set();
|
|
137
|
+
while (current && !visited.has(current.id)) {
|
|
138
|
+
visited.add(current.id);
|
|
139
|
+
path.unshift(current);
|
|
140
|
+
if (!current.parent) break;
|
|
141
|
+
current = nodes.get(current.parent);
|
|
142
|
+
}
|
|
143
|
+
return path;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Helper to get siblings of a node
|
|
148
|
+
*/
|
|
149
|
+
export function getSiblingNodes(nodes, nodeId) {
|
|
150
|
+
const current = nodes.get(nodeId);
|
|
151
|
+
if (!current || !current.parent) {
|
|
152
|
+
// If root or has no parent, find other root nodes
|
|
153
|
+
return Array.from(nodes.values()).filter(n => !n.parent && n.id !== nodeId);
|
|
154
|
+
}
|
|
155
|
+
const parent = nodes.get(current.parent);
|
|
156
|
+
if (!parent) return [];
|
|
157
|
+
return parent.children
|
|
158
|
+
.filter(id => id !== nodeId && nodes.has(id))
|
|
159
|
+
.map(id => nodes.get(id));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Helper to get all incoming and outgoing relations for a node
|
|
164
|
+
*/
|
|
165
|
+
export function getNodeRelations(relations, nodeId) {
|
|
166
|
+
const incoming = relations.filter(r => r.to === nodeId);
|
|
167
|
+
const outgoing = relations.filter(r => r.from === nodeId);
|
|
168
|
+
return { incoming, outgoing };
|
|
169
|
+
}
|