@roarpeng/graphflow 1.9.16 → 1.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/CHANGELOG.md +43 -1
  2. package/README.md +9 -8
  3. package/README.zh.md +3 -2
  4. package/dist/config/resolve.d.ts +13 -1
  5. package/dist/config/resolve.d.ts.map +1 -1
  6. package/dist/config/resolve.js +29 -3
  7. package/dist/config/resolve.js.map +1 -1
  8. package/dist/config/workspace-root.d.ts +6 -0
  9. package/dist/config/workspace-root.d.ts.map +1 -1
  10. package/dist/config/workspace-root.js +3 -2
  11. package/dist/config/workspace-root.js.map +1 -1
  12. package/dist/graph/graphify-file-client.d.ts +49 -0
  13. package/dist/graph/graphify-file-client.d.ts.map +1 -1
  14. package/dist/graph/graphify-file-client.js +113 -15
  15. package/dist/graph/graphify-file-client.js.map +1 -1
  16. package/dist/graph/knowledge-extraction.d.ts +43 -0
  17. package/dist/graph/knowledge-extraction.d.ts.map +1 -0
  18. package/dist/graph/knowledge-extraction.js +274 -0
  19. package/dist/graph/knowledge-extraction.js.map +1 -0
  20. package/dist/graph/token-savings.d.ts +39 -0
  21. package/dist/graph/token-savings.d.ts.map +1 -1
  22. package/dist/graph/token-savings.js +138 -0
  23. package/dist/graph/token-savings.js.map +1 -1
  24. package/dist/learning/canary-gate.d.ts +5 -1
  25. package/dist/learning/canary-gate.d.ts.map +1 -1
  26. package/dist/learning/canary-gate.js +5 -2
  27. package/dist/learning/canary-gate.js.map +1 -1
  28. package/dist/learning/episodic-memory.d.ts.map +1 -1
  29. package/dist/learning/episodic-memory.js +19 -3
  30. package/dist/learning/episodic-memory.js.map +1 -1
  31. package/dist/learning/skill-admission.d.ts +55 -10
  32. package/dist/learning/skill-admission.d.ts.map +1 -1
  33. package/dist/learning/skill-admission.js +177 -35
  34. package/dist/learning/skill-admission.js.map +1 -1
  35. package/dist/learning/skill-flywheel.d.ts +24 -6
  36. package/dist/learning/skill-flywheel.d.ts.map +1 -1
  37. package/dist/learning/skill-flywheel.js +99 -19
  38. package/dist/learning/skill-flywheel.js.map +1 -1
  39. package/dist/learning/skill-markdown.d.ts +12 -0
  40. package/dist/learning/skill-markdown.d.ts.map +1 -0
  41. package/dist/learning/skill-markdown.js +226 -0
  42. package/dist/learning/skill-markdown.js.map +1 -0
  43. package/dist/learning/skill-store.d.ts.map +1 -1
  44. package/dist/learning/skill-store.js +23 -4
  45. package/dist/learning/skill-store.js.map +1 -1
  46. package/dist/learning/skill-types.d.ts +10 -0
  47. package/dist/learning/skill-types.d.ts.map +1 -1
  48. package/dist/learning/skill-types.js.map +1 -1
  49. package/dist/learning/workflow-skill.d.ts +2 -0
  50. package/dist/learning/workflow-skill.d.ts.map +1 -1
  51. package/dist/learning/workflow-skill.js +15 -0
  52. package/dist/learning/workflow-skill.js.map +1 -1
  53. package/dist/surfaces/cli/index.js +69 -0
  54. package/dist/surfaces/cli/index.js.map +1 -1
  55. package/dist/surfaces/cli/output.d.ts.map +1 -1
  56. package/dist/surfaces/cli/output.js +3 -0
  57. package/dist/surfaces/cli/output.js.map +1 -1
  58. package/dist/surfaces/cli/runtime/dialogue.js +3 -3
  59. package/dist/surfaces/cli/runtime/dialogue.js.map +1 -1
  60. package/dist/surfaces/cli/runtime/graph.d.ts.map +1 -1
  61. package/dist/surfaces/cli/runtime/graph.js +14 -10
  62. package/dist/surfaces/cli/runtime/graph.js.map +1 -1
  63. package/dist/surfaces/cli/runtime/knowledge.d.ts +37 -0
  64. package/dist/surfaces/cli/runtime/knowledge.d.ts.map +1 -0
  65. package/dist/surfaces/cli/runtime/knowledge.js +162 -0
  66. package/dist/surfaces/cli/runtime/knowledge.js.map +1 -0
  67. package/dist/surfaces/cli/runtime/routing.js +3 -3
  68. package/dist/surfaces/cli/runtime/routing.js.map +1 -1
  69. package/dist/surfaces/cli/runtime/types.d.ts +6 -0
  70. package/dist/surfaces/cli/runtime/types.d.ts.map +1 -1
  71. package/dist/surfaces/cli/runtime.d.ts +1 -0
  72. package/dist/surfaces/cli/runtime.d.ts.map +1 -1
  73. package/dist/surfaces/cli/runtime.js +5 -1
  74. package/dist/surfaces/cli/runtime.js.map +1 -1
  75. package/dist/surfaces/mcp/server.d.ts +24 -0
  76. package/dist/surfaces/mcp/server.d.ts.map +1 -1
  77. package/dist/surfaces/mcp/server.js +265 -1
  78. package/dist/surfaces/mcp/server.js.map +1 -1
  79. package/dist/surfaces/mcp/tool-definitions.d.ts +1 -0
  80. package/dist/surfaces/mcp/tool-definitions.d.ts.map +1 -1
  81. package/dist/surfaces/mcp/tool-definitions.js +9 -1
  82. package/dist/surfaces/mcp/tool-definitions.js.map +1 -1
  83. package/dist/surfaces/mcp/tool-handlers.d.ts +4 -1
  84. package/dist/surfaces/mcp/tool-handlers.d.ts.map +1 -1
  85. package/dist/surfaces/mcp/tool-handlers.js +69 -19
  86. package/dist/surfaces/mcp/tool-handlers.js.map +1 -1
  87. package/dsh/client.js +431 -0
  88. package/dsh/plugin.mjs +766 -40
  89. package/package.json +12 -3
  90. package/plugin.json +1 -1
package/dsh/client.js ADDED
@@ -0,0 +1,431 @@
1
+ /**
2
+ * GraphFlow — DeepSeek Harness 知识节点面板(静态 dsh client bundle,预构建即源码)。
3
+ *
4
+ * 本文件是 dsh client-modules 的静态客户端 bundle:预构建 CJS factory 形式,
5
+ * 文件末尾以 `window.__ModuleLoader__.load({ id, factory })` 注册(id = 扫描条目名,
6
+ * 与 cordis.patch.yml 的 glue 行一致)。dsh 宿主把 `exports["./client"]` 指向的
7
+ * 本文件原样 serve(/plugins/@roarpeng/graphflow/dsh/client.js?rev=<sha1>),因此
8
+ * 本文件本身就必须是 factory 产物 —— 不需要 bundler:factory 只 require seed 模块
9
+ * `"react"`(dsh shell 提供的 7 个 seed 词之一,见 gf-static-spec §1.1)。
10
+ *
11
+ * 功能与动态版(web/plugin.mjs clientHalf)完全一致:
12
+ * - `conversation.session.header.utilities` 注册 "知识节点" ToggleButton
13
+ * (id gf-knowledge-toggle,order 90);
14
+ * - `shell.overlay` 注册右侧 KnowledgePanel(id gf-knowledge,order 10);
15
+ * - 取数走 Connection 通用 RPC 通道(规格书 §3.2 路 A):
16
+ * `ctx.connection.rpc.call("/gf", "nodes", { workspaceRoot: cwd })`,
17
+ * 宿主(dsh/plugin.mjs,I2 负责)在 `/gf` 注册 handler 并返回 RpcResult 信封
18
+ * `{ ok: true, value: { workbench, dialogues } }`;
19
+ * - 点击续聊:`sessions.binding(currentId).session.prompt([...], "queue")`。
20
+ *
21
+ * 仅纯 JavaScript:无 TS/JSX/import;React 来自 require seed。样式在 factory 闭包内
22
+ * 注入 <style>(防重守卫,照抄 dsh-client-ui-jobs/lib/client.js:10-19 先例)。
23
+ */
24
+
25
+ const factory = (require) => {
26
+ const module = { exports: {} };
27
+ const exports = module.exports;
28
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
29
+
30
+ const React = require("react");
31
+
32
+ // ── styles(闭包内注入,data-plugin-css 防重守卫) ──
33
+ const CSS = `
34
+ .gf-panel {
35
+ position: fixed;
36
+ top: 64px;
37
+ right: 16px;
38
+ bottom: 96px;
39
+ width: 340px;
40
+ max-width: calc(100vw - 32px);
41
+ display: flex;
42
+ flex-direction: column;
43
+ background: var(--dsw-alias-bg-overlay, #202020);
44
+ border: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,.4));
45
+ border-radius: 12px;
46
+ box-shadow: 0 8px 32px rgba(0,0,0,.35);
47
+ color: var(--dsw-alias-label-primary, #eee);
48
+ font-size: 13px;
49
+ line-height: 1.45;
50
+ overflow: hidden;
51
+ z-index: 1000;
52
+ pointer-events: auto;
53
+ }
54
+ .gf-panel-header {
55
+ display: flex;
56
+ align-items: center;
57
+ gap: 6px;
58
+ padding: 10px 12px;
59
+ border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,.3));
60
+ flex: none;
61
+ }
62
+ .gf-panel-title { font-weight: 600; flex: 1; }
63
+ .gf-panel-btn {
64
+ background: none;
65
+ border: none;
66
+ color: var(--dsw-alias-label-secondary, #aaa);
67
+ cursor: pointer;
68
+ font-size: 12px;
69
+ padding: 2px 8px;
70
+ border-radius: 6px;
71
+ }
72
+ .gf-panel-btn:hover { background: rgba(128,128,128,.18); }
73
+ .gf-panel-body { flex: 1; overflow: auto; padding: 4px 10px 16px; }
74
+ .gf-section-title {
75
+ font-size: 11px;
76
+ font-weight: 600;
77
+ color: var(--dsw-alias-label-secondary, #aaa);
78
+ margin: 12px 2px 6px;
79
+ letter-spacing: .04em;
80
+ }
81
+ .gf-node {
82
+ display: block;
83
+ width: 100%;
84
+ text-align: left;
85
+ background: none;
86
+ border: none;
87
+ border-radius: 8px;
88
+ padding: 6px 8px;
89
+ margin: 2px 0;
90
+ color: var(--dsw-alias-label-primary, #eee);
91
+ cursor: pointer;
92
+ font: inherit;
93
+ }
94
+ .gf-node:hover { background: rgba(128,128,128,.14); }
95
+ .gf-node-title { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
96
+ .gf-node-label { font-weight: 500; min-width: 0; }
97
+ .gf-badge {
98
+ font-size: 10px;
99
+ padding: 1px 6px;
100
+ border-radius: 8px;
101
+ background: rgba(128,128,128,.22);
102
+ color: var(--dsw-alias-label-secondary, #aaa);
103
+ flex: none;
104
+ }
105
+ .gf-badge-active { background: var(--dsw-alias-brand-primary, #4a90d9); color: #fff; }
106
+ .gf-badge-pending { background: var(--dsw-alias-state-warn-primary, #e0a13c); color: #000; }
107
+ .gf-badge-jump { background: var(--dsw-alias-state-warn-primary, #e0a13c); color: #000; }
108
+ .gf-node-preview {
109
+ color: var(--dsw-alias-label-secondary, #aaa);
110
+ font-size: 12px;
111
+ margin-top: 2px;
112
+ overflow: hidden;
113
+ text-overflow: ellipsis;
114
+ white-space: nowrap;
115
+ }
116
+ .gf-empty { color: var(--dsw-alias-label-secondary, #aaa); padding: 18px 6px; text-align: center; font-size: 12px; }
117
+ .gf-error { color: var(--dsw-alias-state-error-primary, #e06c6c); padding: 10px; font-size: 12px; }
118
+ .gf-toggle {
119
+ background: none;
120
+ border: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,.4));
121
+ color: var(--dsw-alias-label-secondary, #aaa);
122
+ cursor: pointer;
123
+ font-size: 12px;
124
+ padding: 3px 10px;
125
+ border-radius: 8px;
126
+ }
127
+ .gf-toggle:hover { background: rgba(128,128,128,.14); }
128
+ .gf-toggle-active { color: var(--dsw-alias-brand-primary, #4a90d9); border-color: var(--dsw-alias-brand-primary, #4a90d9); }
129
+ `;
130
+ const tagId = "@roarpeng/graphflow/dsh/gf-panel";
131
+ if (
132
+ typeof document !== "undefined" &&
133
+ document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null
134
+ ) {
135
+ const tag = document.createElement("style");
136
+ tag.dataset.plugin = "@roarpeng/graphflow/dsh";
137
+ tag.dataset.pluginCss = tagId;
138
+ tag.textContent = CSS;
139
+ document.head.appendChild(tag);
140
+ }
141
+
142
+ // ── package-private 面板状态(factory 只物化一次,状态随页面存活) ──
143
+ const panel = { open: false, sessionId: undefined, listeners: new Set() };
144
+ const subscribe = (fn) => {
145
+ panel.listeners.add(fn);
146
+ return () => {
147
+ panel.listeners.delete(fn);
148
+ };
149
+ };
150
+ const setOpen = (open, sessionId) => {
151
+ panel.open = open;
152
+ if (sessionId !== undefined) panel.sessionId = sessionId;
153
+ for (const fn of Array.from(panel.listeners)) {
154
+ try {
155
+ fn();
156
+ } catch {
157
+ // ignore
158
+ }
159
+ }
160
+ };
161
+
162
+ /** Cordis 客户端服务声明(模块级,fiber 等待这些服务出现后才 apply)。 */
163
+ const inject = ["sessions", "slots", "connection"];
164
+
165
+ /** apply 时的 ctx,供组件按需取服务(Connection 取数 / sessions 续聊)。 */
166
+ let pluginCtx = null;
167
+
168
+ function apply(ctx) {
169
+ pluginCtx = ctx;
170
+ const slots = ctx.get("slots");
171
+ if (slots === undefined) return;
172
+
173
+ // 会话头部开关(session 作用域 list slot)
174
+ slots.inject("conversation.session.header.utilities", () =>
175
+ slots.register(
176
+ { name: "conversation.session.header.utilities", id: "gf-knowledge-toggle", order: 90, label: () => "知识节点" },
177
+ (props) => React.createElement(ToggleButton, { ...props, panel, setOpen, subscribe })
178
+ )
179
+ );
180
+
181
+ // 右侧面板(root 作用域 list slot,additive)
182
+ slots.inject("shell.overlay", () =>
183
+ slots.register(
184
+ { name: "shell.overlay", id: "gf-knowledge", order: 10, label: () => "知识节点" },
185
+ (props) =>
186
+ React.createElement(KnowledgePanel, {
187
+ ...props,
188
+ panel,
189
+ setOpen,
190
+ subscribe,
191
+ sessionsOf: () => ctx.get("sessions"),
192
+ })
193
+ )
194
+ );
195
+ }
196
+
197
+ // ── 取数:Connection 通用 RPC 通道(规格书 §3.2 路 A) ──
198
+ // 宿主端点: ctx.connection.rpc.handle("/gf", handler, { authority: "trusted-host" })
199
+ // 返回信封: { ok: true, value: { workbench, dialogues } } | { ok: false, error: { code, message, details } }
200
+ function fetchNodes(ctx, cwd) {
201
+ const connection = ctx && typeof ctx.get === "function" ? ctx.get("connection") : undefined;
202
+ if (!connection || !connection.rpc || typeof connection.rpc.call !== "function") {
203
+ return Promise.resolve({ ok: false, error: "no-connection" });
204
+ }
205
+ return connection.rpc.call("/gf", "nodes", { workspaceRoot: cwd }).catch((err) => ({
206
+ ok: false,
207
+ error: String(err && err.message ? err.message : err),
208
+ }));
209
+ }
210
+
211
+ // ── 共享 helpers ──
212
+
213
+ function buildResumeInstruction(kind, id, cwd, title) {
214
+ const arg = kind === "topic" ? `topicId: "${id}"` : `resumeFromTurnId: "${id}"`;
215
+ return (
216
+ `继续对话:请先调用 mcp__graphflow__graphflow_context({ rootDir: "${cwd}", ${arg} })` +
217
+ `,读取该知识节点记录的历史问答与上下文,然后基于它继续当前任务。` +
218
+ `节点: ${title || id}`
219
+ );
220
+ }
221
+
222
+ function clip(text, max) {
223
+ const normalized = String(text || "").replace(/\s+/g, " ").trim();
224
+ if (normalized.length <= max) return normalized;
225
+ return `${normalized.slice(0, max - 1)}…`;
226
+ }
227
+
228
+ // ── components ──
229
+
230
+ function ToggleButton(props) {
231
+ const { panel, setOpen, subscribe, sessionId } = props;
232
+ const [open, setLocal] = React.useState(panel.open);
233
+ React.useEffect(() => subscribe(() => setLocal(panel.open)), []);
234
+ return React.createElement(
235
+ "button",
236
+ {
237
+ type: "button",
238
+ "aria-label": "知识节点",
239
+ title: "知识节点",
240
+ onClick: () => setOpen(!panel.open, sessionId),
241
+ className: "gf-toggle" + (open ? " gf-toggle-active" : ""),
242
+ },
243
+ "知识节点"
244
+ );
245
+ }
246
+
247
+ function KnowledgePanel(props) {
248
+ const { panel, setOpen, subscribe, useSessions, sessionsOf } = props;
249
+ const [open, setLocal] = React.useState(panel.open);
250
+ React.useEffect(() => subscribe(() => setLocal(panel.open)), []);
251
+ const currentId = useSessions((s) => s.current);
252
+ const cwd = useSessions((s) => (s.current ? s.byId[s.current]?.cwd : undefined));
253
+ const [data, setData] = React.useState(null);
254
+ const [loading, setLoading] = React.useState(false);
255
+ const [error, setError] = React.useState(null);
256
+ const [tick, setTick] = React.useState(0);
257
+
258
+ const refresh = React.useCallback(() => {
259
+ if (!cwd) return;
260
+ setLoading(true);
261
+ fetchNodes(pluginCtx, cwd)
262
+ .then((res) => {
263
+ setData(res);
264
+ setError(null);
265
+ })
266
+ .catch((err) => setError(String(err && err.message ? err.message : err)))
267
+ .finally(() => setLoading(false));
268
+ }, [cwd]);
269
+
270
+ React.useEffect(() => {
271
+ if (open && cwd) refresh();
272
+ }, [open, cwd, tick]);
273
+
274
+ if (!open) return null;
275
+
276
+ const resume = (kind, id, title) => {
277
+ const sessions = sessionsOf && typeof sessionsOf === "function" ? sessionsOf() : undefined;
278
+ const binding = sessions && currentId ? sessions.binding(currentId) : undefined;
279
+ if (!binding || !binding.session || !cwd) return;
280
+ const text = buildResumeInstruction(kind, id, cwd, title);
281
+ binding.session.prompt([{ type: "text", text }], "queue").catch(() => {});
282
+ };
283
+
284
+ const header = React.createElement(
285
+ "div",
286
+ { className: "gf-panel-header" },
287
+ React.createElement("div", { className: "gf-panel-title" }, "知识节点"),
288
+ React.createElement(
289
+ "button",
290
+ {
291
+ type: "button",
292
+ className: "gf-panel-btn",
293
+ "aria-label": "刷新",
294
+ title: "刷新",
295
+ onClick: () => setTick((v) => v + 1),
296
+ },
297
+ "刷新"
298
+ ),
299
+ React.createElement(
300
+ "button",
301
+ {
302
+ type: "button",
303
+ className: "gf-panel-btn",
304
+ "aria-label": "关闭",
305
+ title: "关闭",
306
+ onClick: () => setOpen(false, undefined),
307
+ },
308
+ "✕"
309
+ )
310
+ );
311
+
312
+ let body;
313
+ if (loading && !data) {
314
+ body = React.createElement("div", { className: "gf-empty" }, "加载中…");
315
+ } else if (error) {
316
+ body = React.createElement("div", { className: "gf-error" }, `加载失败: ${error}`);
317
+ } else if (!data || data.ok !== true) {
318
+ const envelopeError =
319
+ data && data.error
320
+ ? typeof data.error === "string"
321
+ ? data.error
322
+ : data.error.message || data.error.code || null
323
+ : null;
324
+ body = envelopeError
325
+ ? React.createElement("div", { className: "gf-error" }, `加载失败: ${envelopeError}`)
326
+ : React.createElement("div", { className: "gf-empty" }, "暂无数据(需要安装 GraphFlow MCP 插件并建立知识图谱)");
327
+ } else {
328
+ // data 是 RpcResult 信封:data.ok === true 时数据在 data.value
329
+ const value = data.value || {};
330
+ const wbData = value.workbench;
331
+ const outlines = Array.isArray(wbData) ? wbData : wbData && Array.isArray(wbData.outlines) ? wbData.outlines : [];
332
+ const dialogues = Array.isArray(value.dialogues) ? value.dialogues : [];
333
+ const sections = [];
334
+ if (outlines.length === 0 && dialogues.length === 0) {
335
+ sections.push(
336
+ React.createElement(
337
+ "div",
338
+ { className: "gf-empty" },
339
+ "暂无知识节点 — 使用 graphflow_plan / graphflow_context 后,这里会出现主题与对话记录"
340
+ )
341
+ );
342
+ }
343
+ for (const outline of outlines) {
344
+ const children = [
345
+ React.createElement("div", { className: "gf-section-title" }, `Workbench · ${clip(outline.task, 60)}`),
346
+ ];
347
+ for (const node of outline.nodes || []) {
348
+ children.push(renderTopicNode(node, resume));
349
+ }
350
+ sections.push(React.createElement("div", { key: outline.rootId }, children));
351
+ }
352
+ if (dialogues.length > 0) {
353
+ const children = [React.createElement("div", { className: "gf-section-title" }, "对话记录")];
354
+ for (const turn of dialogues) {
355
+ children.push(
356
+ React.createElement(
357
+ "button",
358
+ {
359
+ key: turn.id,
360
+ type: "button",
361
+ className: "gf-node",
362
+ onClick: () => resume("turn", turn.id, `#${turn.seq} ${turn.userQuery}`),
363
+ },
364
+ React.createElement(
365
+ "div",
366
+ { className: "gf-node-title" },
367
+ React.createElement("span", { className: "gf-badge" }, `#${turn.seq}`),
368
+ React.createElement(
369
+ "span",
370
+ { className: "gf-node-label" },
371
+ clip(turn.title || turn.userQuery, 40)
372
+ ),
373
+ turn.jumped ? React.createElement("span", { className: "gf-badge gf-badge-jump" }, "跳转") : null
374
+ ),
375
+ React.createElement(
376
+ "div",
377
+ { className: "gf-node-preview" },
378
+ turn.summary && turn.summary.trim()
379
+ ? `A: ${clip(turn.summary, 60)}`
380
+ : turn.assistantReply && turn.assistantReply.trim()
381
+ ? `A: ${clip(turn.assistantReply, 60)}`
382
+ : "A: (待回复)"
383
+ )
384
+ )
385
+ );
386
+ }
387
+ sections.push(React.createElement("div", { key: "dialogues" }, children));
388
+ }
389
+ body = React.createElement("div", { className: "gf-panel-body" }, sections);
390
+ }
391
+
392
+ return React.createElement("div", { className: "gf-panel" }, header, body);
393
+ }
394
+
395
+ function renderTopicNode(node, resume) {
396
+ const badges = [];
397
+ if (node.active) badges.push(React.createElement("span", { key: "a", className: "gf-badge gf-badge-active" }, "当前"));
398
+ if (node.kind === "side") badges.push(React.createElement("span", { key: "s", className: "gf-badge" }, "旁支"));
399
+ if (node.pendingReply) badges.push(React.createElement("span", { key: "p", className: "gf-badge gf-badge-pending" }, "待回复"));
400
+ if (node.messageCount > 0) {
401
+ badges.push(React.createElement("span", { key: "m", className: "gf-badge" }, `${node.messageCount} 条`));
402
+ }
403
+ const children = [];
404
+ for (const child of node.children || []) {
405
+ children.push(renderTopicNode(child, resume));
406
+ }
407
+ return React.createElement(
408
+ "div",
409
+ { key: node.id },
410
+ React.createElement(
411
+ "button",
412
+ {
413
+ type: "button",
414
+ className: "gf-node",
415
+ onClick: () => resume("topic", node.id, node.title),
416
+ },
417
+ React.createElement("div", { className: "gf-node-title" }, React.createElement("span", { className: "gf-node-label" }, node.title), badges),
418
+ node.lastUserPreview
419
+ ? React.createElement("div", { className: "gf-node-preview" }, clip(node.lastUserPreview, 70))
420
+ : null
421
+ ),
422
+ children
423
+ );
424
+ }
425
+
426
+ exports.apply = apply;
427
+ exports.inject = inject;
428
+ return module.exports;
429
+ };
430
+
431
+ window.__ModuleLoader__.load({ id: "@roarpeng/graphflow/dsh", factory });