dsh-trivium 0.2.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/lib/client.js ADDED
@@ -0,0 +1,804 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-trivium",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+
8
+ const react = require("react");
9
+ const { jsx, jsxs } = require("react/jsx-runtime");
10
+ const { Button } = require("@deepseek-ai/dsh-client-ui-primitives");
11
+
12
+ const L = {
13
+ nav: "Trivium 记忆",
14
+ sub: "按节点和边记。默认不自动灌全文。归档后 find 不再返回;删除从 .tdb 去掉且不可恢复。",
15
+ loading: "加载中…",
16
+ empty: "还没有打开过工作区,或当前库为空。",
17
+ path: "当前 .tdb",
18
+ nodes: "节点数",
19
+ tokens: "上次短地图 token",
20
+ recall: "注入策略(三选一,默认关)",
21
+ recallHint: "与 autoRecall、实体名折中互斥。改完点右上角保存。",
22
+ recallOff: "关(默认)",
23
+ recallOffHint: "仅 session-start 短地图(≤400 token)。模型自行 ctx_find / ctx_read。",
24
+ recallAuto: "autoRecall",
25
+ recallAutoHint: "本步含直接用户文本时注入最多 3 条 L0(≤300 token)。不调用 ctx_find 工具。",
26
+ recallAnchor: "实体名折中",
27
+ recallAnchorHint: "用户文本命中已有 entity 的 name/aliases 时,注入该实体 1 跳 about/decided/broke/fixed 邻居(≤300 token)。",
28
+ extract: "compaction/end 抽取",
29
+ extractHint: "规则 + 一次小 prompt;失败只记日志。turn 结束后约 12 秒空闲会再抽一次,下次 session-start 也会重放 pending。",
30
+ embed: "远程 embedding(OpenAI 兼容,默认关)",
31
+ embedHint: "失败退回关键词 + 图检索。需单独的 embeddings endpoint;官方 DeepSeek chat API 不含 embedding。",
32
+ embedReady: "已就绪",
33
+ embedWait: "未就绪:打开开关并填写 http(s) URL 后保存。",
34
+ embedUrl: "Embedding URL",
35
+ embedModel: "模型",
36
+ embedKey: "API Key",
37
+ embedKeyHint: "已保存则留空不改。也可设环境变量 DSH_TRIVIUM_EMBED_KEY。",
38
+ embedBackfill: "回填已有节点向量",
39
+ list: "记忆条目",
40
+ filter: "类型",
41
+ search: "搜索",
42
+ all: "全部",
43
+ archive: "归档",
44
+ remove: "删除",
45
+ saveNode: "保存条目",
46
+ merge: "合并进来",
47
+ mergePick: "并入本条…",
48
+ exportJson: "导出 JSON",
49
+ importJson: "导入 JSON",
50
+ archiveHint: "归档:软删除,find / 短地图不再出现。删除:从库里去掉,不可恢复。合并:同类型节点并入本条,对方 same_as 后归档。",
51
+ save: "保存设置",
52
+ saved: "已保存",
53
+ refresh: "刷新",
54
+ noMatch: "没有匹配的节点。换个关键词,或清空搜索。",
55
+ showStale: "显示过期决策",
56
+ neighbors: "邻居(业务边)",
57
+ aboutThis: "只看挂在这上面的",
58
+ aboutOn: "正在看挂在",
59
+ clearAbout: "返回全部",
60
+ stale: "过期",
61
+ until: "until",
62
+ name: "名称",
63
+ text: "正文",
64
+ aliases: "别名",
65
+ };
66
+
67
+ const TYPES = ["", "entity", "preference", "decision", "experience", "workspace"];
68
+
69
+ const ui = {
70
+ page: { display: "flex", flexDirection: "column", gap: 14, padding: "4px 0 16px", maxWidth: 760 },
71
+ header: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 },
72
+ title: { margin: 0, fontSize: 20, lineHeight: "32px", fontWeight: 650 },
73
+ actions: { display: "flex", alignItems: "center", gap: 8, flexShrink: 0 },
74
+ sub: { margin: 0, fontSize: 13, opacity: 0.72, lineHeight: 1.45 },
75
+ meta: { display: "flex", flexWrap: "wrap", gap: "4px 16px", fontSize: 12, opacity: 0.7 },
76
+ card: {
77
+ border: "1px solid rgba(127,127,127,0.28)",
78
+ borderRadius: 10,
79
+ padding: "12px 14px",
80
+ display: "flex",
81
+ flexDirection: "column",
82
+ gap: 10,
83
+ },
84
+ cardTitle: { fontSize: 13, fontWeight: 650 },
85
+ hint: { fontSize: 12, opacity: 0.62, lineHeight: 1.4 },
86
+ switch: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontWeight: 650, fontSize: 13 },
87
+ radio: { display: "flex", alignItems: "flex-start", gap: 8, cursor: "pointer", margin: 0 },
88
+ field: { display: "flex", flexDirection: "column", gap: 4 },
89
+ fieldLabel: { fontSize: 12, opacity: 0.75 },
90
+ input: {
91
+ width: "100%",
92
+ boxSizing: "border-box",
93
+ minHeight: 32,
94
+ padding: "6px 8px",
95
+ border: "1px solid rgba(127,127,127,0.35)",
96
+ borderRadius: 6,
97
+ background: "transparent",
98
+ },
99
+ toolbar: { display: "flex", flexWrap: "wrap", alignItems: "center", gap: 10 },
100
+ error: { color: "#c44", fontSize: 13 },
101
+ saved: { fontSize: 12, opacity: 0.75 },
102
+ };
103
+
104
+ async function api(path, opts) {
105
+ const resp = await fetch(path, opts);
106
+ let data = null;
107
+ try {
108
+ data = await resp.json();
109
+ } catch {
110
+ data = null;
111
+ }
112
+ if (!resp.ok || (data && data.ok === false)) {
113
+ throw new Error((data && data.message) || "HTTP " + resp.status);
114
+ }
115
+ return data;
116
+ }
117
+
118
+ function labeledInput(label, input) {
119
+ return jsxs("label", {
120
+ style: ui.field,
121
+ children: [jsx("span", { style: ui.fieldLabel, children: label }), input],
122
+ });
123
+ }
124
+
125
+ function TriviumCard() {
126
+ const [status, setStatus] = react.useState(null);
127
+ const [nodes, setNodes] = react.useState([]);
128
+ const [type, setType] = react.useState("");
129
+ const [q, setQ] = react.useState("");
130
+ const [showStale, setShowStale] = react.useState(false);
131
+ const [aboutId, setAboutId] = react.useState("");
132
+ const [expanded, setExpanded] = react.useState(null);
133
+ const [error, setError] = react.useState("");
134
+ const [busy, setBusy] = react.useState(false);
135
+ const [saved, setSaved] = react.useState(false);
136
+ const [recallMode, setRecallMode] = react.useState("off");
137
+ const [extractEnabled, setExtractEnabled] = react.useState(true);
138
+ const [embeddingEnabled, setEmbeddingEnabled] = react.useState(false);
139
+ const [embeddingUrl, setEmbeddingUrl] = react.useState("");
140
+ const [embeddingModel, setEmbeddingModel] = react.useState("text-embedding-3-small");
141
+ const [embeddingApiKey, setEmbeddingApiKey] = react.useState("");
142
+ const [embeddingApiKeySet, setEmbeddingApiKeySet] = react.useState(false);
143
+ const [edits, setEdits] = react.useState({});
144
+ const [mergeFrom, setMergeFrom] = react.useState({});
145
+ const fileRef = react.useRef(null);
146
+
147
+ const load = react.useCallback(async () => {
148
+ setError("");
149
+ try {
150
+ const st = await api("/api/dsh-trivium/status");
151
+ setStatus(st);
152
+ setRecallMode(st.recallMode || (st.autoRecall ? "auto" : st.anchorRecall ? "anchor" : "off"));
153
+ setExtractEnabled(st.extractEnabled !== false);
154
+ setEmbeddingEnabled(!!st.embeddingEnabled);
155
+ setEmbeddingUrl(st.embeddingUrl || "");
156
+ setEmbeddingModel(st.embeddingModel || "text-embedding-3-small");
157
+ setEmbeddingApiKeySet(!!st.embeddingApiKeySet);
158
+ const params = new URLSearchParams();
159
+ if (type) params.set("type", type);
160
+ if (q.trim()) params.set("q", q.trim());
161
+ if (showStale) params.set("stale", "1");
162
+ if (aboutId) params.set("about", String(aboutId));
163
+ const qs = params.toString();
164
+ const list = await api("/api/dsh-trivium/nodes" + (qs ? "?" + qs : ""));
165
+ setNodes(list.nodes || []);
166
+ } catch (err) {
167
+ setError(String(err.message || err));
168
+ }
169
+ }, [type, q, showStale, aboutId]);
170
+
171
+ react.useEffect(() => {
172
+ load();
173
+ }, [load]);
174
+
175
+ const editOf = (n) =>
176
+ edits[n.id] || {
177
+ name: n.name || "",
178
+ text: n.text || "",
179
+ aliases: (n.aliases || []).join(", "),
180
+ until: n.until || "",
181
+ };
182
+
183
+ const saveFlags = async () => {
184
+ setBusy(true);
185
+ setSaved(false);
186
+ try {
187
+ const body = {
188
+ recallMode,
189
+ extractEnabled,
190
+ embeddingEnabled,
191
+ embeddingUrl,
192
+ embeddingModel,
193
+ };
194
+ if (embeddingApiKey.trim()) body.embeddingApiKey = embeddingApiKey.trim();
195
+ await api("/api/dsh-trivium/settings", {
196
+ method: "POST",
197
+ headers: { "content-type": "application/json" },
198
+ body: JSON.stringify(body),
199
+ });
200
+ setEmbeddingApiKey("");
201
+ setSaved(true);
202
+ await load();
203
+ } catch (err) {
204
+ setError(String(err.message || err));
205
+ } finally {
206
+ setBusy(false);
207
+ }
208
+ };
209
+
210
+ const act = async (id, op) => {
211
+ try {
212
+ await api("/api/dsh-trivium/nodes/" + id + "/" + op, { method: "POST" });
213
+ await load();
214
+ } catch (err) {
215
+ setError(String(err.message || err));
216
+ }
217
+ };
218
+
219
+ const saveNode = async (n) => {
220
+ const e = editOf(n);
221
+ try {
222
+ const patch = { name: e.name, text: e.text };
223
+ if (n.type === "entity") {
224
+ patch.aliases = String(e.aliases || "")
225
+ .split(/[,,]/)
226
+ .map((s) => s.trim())
227
+ .filter(Boolean);
228
+ }
229
+ if (n.type === "decision") patch.until = e.until;
230
+ await api("/api/dsh-trivium/nodes/" + n.id, {
231
+ method: "POST",
232
+ headers: { "content-type": "application/json" },
233
+ body: JSON.stringify(patch),
234
+ });
235
+ setEdits((prev) => {
236
+ const next = { ...prev };
237
+ delete next[n.id];
238
+ return next;
239
+ });
240
+ await load();
241
+ } catch (err) {
242
+ setError(String(err.message || err));
243
+ }
244
+ };
245
+
246
+ const mergeIntoKeep = async (keepId) => {
247
+ const dropId = mergeFrom[keepId];
248
+ if (!dropId) return;
249
+ try {
250
+ await api("/api/dsh-trivium/nodes/" + keepId + "/merge", {
251
+ method: "POST",
252
+ headers: { "content-type": "application/json" },
253
+ body: JSON.stringify({ dropId: Number(dropId) }),
254
+ });
255
+ setMergeFrom((prev) => {
256
+ const next = { ...prev };
257
+ delete next[keepId];
258
+ return next;
259
+ });
260
+ await load();
261
+ } catch (err) {
262
+ setError(String(err.message || err));
263
+ }
264
+ };
265
+
266
+ const exportJson = async () => {
267
+ try {
268
+ const data = await api("/api/dsh-trivium/export");
269
+ const blob = new Blob([JSON.stringify(data.graph, null, 2)], { type: "application/json" });
270
+ const a = document.createElement("a");
271
+ a.href = URL.createObjectURL(blob);
272
+ a.download = "trivium-memory.json";
273
+ a.click();
274
+ URL.revokeObjectURL(a.href);
275
+ } catch (err) {
276
+ setError(String(err.message || err));
277
+ }
278
+ };
279
+
280
+ const importJson = async (file) => {
281
+ if (!file) return;
282
+ setBusy(true);
283
+ try {
284
+ const text = await file.text();
285
+ const graph = JSON.parse(text);
286
+ const r = await api("/api/dsh-trivium/import", {
287
+ method: "POST",
288
+ headers: { "content-type": "application/json" },
289
+ body: JSON.stringify({ graph }),
290
+ });
291
+ setSaved(true);
292
+ setError(
293
+ r.ok
294
+ ? ""
295
+ : String(r.message || "导入失败"),
296
+ );
297
+ await load();
298
+ } catch (err) {
299
+ setError("导入失败:" + String(err.message || err));
300
+ } finally {
301
+ setBusy(false);
302
+ if (fileRef.current) fileRef.current.value = "";
303
+ }
304
+ };
305
+
306
+ const backfill = async () => {
307
+ setBusy(true);
308
+ try {
309
+ const r = await api("/api/dsh-trivium/embed-backfill", { method: "POST" });
310
+ setSaved(true);
311
+ setError(r.ok ? "" : String(r.message || "backfill failed"));
312
+ } catch (err) {
313
+ setError(String(err.message || err));
314
+ } finally {
315
+ setBusy(false);
316
+ }
317
+ };
318
+
319
+ if (!status && !error) return jsx("div", { children: L.loading });
320
+
321
+ const radio = (value, label, hint) =>
322
+ jsxs("label", {
323
+ style: ui.radio,
324
+ children: [
325
+ jsx("input", {
326
+ type: "radio",
327
+ name: "dsh-trivium-recall",
328
+ checked: recallMode === value,
329
+ onChange: () => setRecallMode(value),
330
+ style: { marginTop: 3 },
331
+ }),
332
+ jsxs("span", {
333
+ children: [
334
+ jsx("span", { style: { fontWeight: 600 }, children: label }),
335
+ jsx("span", { style: { display: "block", ...ui.hint }, children: hint }),
336
+ ],
337
+ }),
338
+ ],
339
+ });
340
+
341
+ return jsxs("div", {
342
+ style: ui.page,
343
+ children: [
344
+ jsxs("div", {
345
+ style: ui.header,
346
+ children: [
347
+ jsx("h2", { style: ui.title, children: L.nav }),
348
+ jsxs("div", {
349
+ style: ui.actions,
350
+ children: [
351
+ saved ? jsx("span", { style: ui.saved, children: L.saved }) : null,
352
+ jsx(Button, {
353
+ variant: "primary",
354
+ size: "sm",
355
+ disabled: busy,
356
+ onClick: saveFlags,
357
+ children: busy ? "…" : L.save,
358
+ }),
359
+ jsx(Button, { variant: "outline", size: "sm", onClick: load, children: L.refresh }),
360
+ jsx(Button, { variant: "outline", size: "sm", onClick: exportJson, children: L.exportJson }),
361
+ jsx(Button, {
362
+ variant: "outline",
363
+ size: "sm",
364
+ disabled: busy,
365
+ onClick: () => fileRef.current && fileRef.current.click(),
366
+ children: L.importJson,
367
+ }),
368
+ jsx("input", {
369
+ type: "file",
370
+ accept: "application/json,.json",
371
+ ref: fileRef,
372
+ style: { display: "none" },
373
+ onChange: (e) => importJson(e.target.files && e.target.files[0]),
374
+ }),
375
+ ],
376
+ }),
377
+ ],
378
+ }),
379
+ jsx("p", { style: ui.sub, children: L.sub }),
380
+ error ? jsx("div", { style: ui.error, children: error }) : null,
381
+ status
382
+ ? jsxs("div", {
383
+ style: ui.meta,
384
+ children: [
385
+ jsx("span", {
386
+ style: { wordBreak: "break-all" },
387
+ children: L.path + " " + (status.dbPath || "—"),
388
+ }),
389
+ jsx("span", { children: L.nodes + " " + (status.nodeCount ?? 0) }),
390
+ jsx("span", {
391
+ children:
392
+ L.tokens +
393
+ " " +
394
+ (status.lastInjectTokens ?? 0) +
395
+ " / " +
396
+ (status.mapTokenBudget || 400),
397
+ }),
398
+ ],
399
+ })
400
+ : null,
401
+ jsxs("div", {
402
+ style: ui.card,
403
+ children: [
404
+ jsx("div", { style: ui.cardTitle, children: L.recall }),
405
+ jsx("div", { style: ui.hint, children: L.recallHint }),
406
+ radio("off", L.recallOff, L.recallOffHint),
407
+ radio("auto", L.recallAuto, L.recallAutoHint),
408
+ radio("anchor", L.recallAnchor, L.recallAnchorHint),
409
+ ],
410
+ }),
411
+ jsxs("div", {
412
+ style: ui.card,
413
+ children: [
414
+ jsxs("label", {
415
+ style: ui.switch,
416
+ children: [
417
+ jsx("input", {
418
+ type: "checkbox",
419
+ checked: extractEnabled,
420
+ onChange: (e) => setExtractEnabled(e.target.checked),
421
+ }),
422
+ jsx("span", { children: L.extract }),
423
+ ],
424
+ }),
425
+ jsx("div", { style: ui.hint, children: L.extractHint }),
426
+ ],
427
+ }),
428
+ jsxs("div", {
429
+ style: ui.card,
430
+ children: [
431
+ jsxs("div", {
432
+ style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8, flexWrap: "wrap" },
433
+ children: [
434
+ jsxs("label", {
435
+ style: ui.switch,
436
+ children: [
437
+ jsx("input", {
438
+ type: "checkbox",
439
+ checked: embeddingEnabled,
440
+ onChange: (e) => setEmbeddingEnabled(e.target.checked),
441
+ }),
442
+ jsx("span", { children: L.embed }),
443
+ ],
444
+ }),
445
+ embeddingEnabled
446
+ ? jsx(Button, {
447
+ variant: "outline",
448
+ size: "sm",
449
+ disabled: busy,
450
+ onClick: backfill,
451
+ children: L.embedBackfill,
452
+ })
453
+ : null,
454
+ ],
455
+ }),
456
+ jsx("div", {
457
+ style: ui.hint,
458
+ children:
459
+ L.embedHint +
460
+ " " +
461
+ (status && status.embeddingReady ? L.embedReady : L.embedWait),
462
+ }),
463
+ labeledInput(
464
+ L.embedUrl,
465
+ jsx("input", {
466
+ value: embeddingUrl,
467
+ onChange: (e) => setEmbeddingUrl(e.target.value),
468
+ placeholder: "https://api.openai.com/v1/embeddings",
469
+ style: ui.input,
470
+ }),
471
+ ),
472
+ jsxs("div", {
473
+ style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 },
474
+ children: [
475
+ labeledInput(
476
+ L.embedModel,
477
+ jsx("input", {
478
+ value: embeddingModel,
479
+ onChange: (e) => setEmbeddingModel(e.target.value),
480
+ placeholder: "text-embedding-3-small",
481
+ style: ui.input,
482
+ }),
483
+ ),
484
+ labeledInput(
485
+ L.embedKey,
486
+ jsx("input", {
487
+ type: "password",
488
+ value: embeddingApiKey,
489
+ onChange: (e) => setEmbeddingApiKey(e.target.value),
490
+ placeholder: embeddingApiKeySet ? "已保存" : "",
491
+ style: ui.input,
492
+ }),
493
+ ),
494
+ ],
495
+ }),
496
+ jsx("div", { style: ui.hint, children: L.embedKeyHint }),
497
+ ],
498
+ }),
499
+ jsxs("div", {
500
+ style: ui.card,
501
+ children: [
502
+ jsx("div", { style: ui.cardTitle, children: L.list }),
503
+ jsxs("div", {
504
+ style: ui.toolbar,
505
+ children: [
506
+ jsxs("label", {
507
+ style: { display: "flex", alignItems: "center", gap: 6, fontSize: 13 },
508
+ children: [
509
+ jsx("span", { style: ui.fieldLabel, children: L.filter }),
510
+ jsx("select", {
511
+ value: type,
512
+ onChange: (e) => setType(e.target.value),
513
+ style: { ...ui.input, width: "auto", minWidth: 120 },
514
+ children: TYPES.map((t) =>
515
+ jsx("option", { value: t, children: t || L.all }, t || "all"),
516
+ ),
517
+ }),
518
+ ],
519
+ }),
520
+ jsx("input", {
521
+ value: q,
522
+ onChange: (e) => setQ(e.target.value),
523
+ placeholder: L.search + " 鉴权 / until / about",
524
+ style: { ...ui.input, flex: 1, minWidth: 160 },
525
+ }),
526
+ jsxs("label", {
527
+ style: { display: "flex", alignItems: "center", gap: 6, fontSize: 13, whiteSpace: "nowrap" },
528
+ children: [
529
+ jsx("input", {
530
+ type: "checkbox",
531
+ checked: showStale,
532
+ onChange: (e) => setShowStale(e.target.checked),
533
+ }),
534
+ jsx("span", { children: L.showStale }),
535
+ ],
536
+ }),
537
+ ],
538
+ }),
539
+ aboutId
540
+ ? jsxs("div", {
541
+ style: { display: "flex", alignItems: "center", gap: 8, fontSize: 13 },
542
+ children: [
543
+ jsx("span", { children: L.aboutOn + " #" + aboutId }),
544
+ jsx(Button, {
545
+ variant: "outline",
546
+ size: "sm",
547
+ onClick: () => setAboutId(""),
548
+ children: L.clearAbout,
549
+ }),
550
+ ],
551
+ })
552
+ : null,
553
+ jsx("div", { style: ui.hint, children: L.archiveHint }),
554
+ !nodes.length
555
+ ? jsx("div", {
556
+ style: { opacity: 0.7, fontSize: 13 },
557
+ children:
558
+ status && (status.nodeCount || 0) > 0 && (q.trim() || type || aboutId || showStale)
559
+ ? L.noMatch
560
+ : L.empty,
561
+ })
562
+ : jsx("div", {
563
+ style: { display: "flex", flexDirection: "column", gap: 8 },
564
+ children: nodes.map((n) => {
565
+ const open = expanded === n.id;
566
+ const incoming = n.incoming || [];
567
+ const outgoing = n.outgoing || [];
568
+ const hasNeighbors = incoming.length + outgoing.length > 0;
569
+ const e = editOf(n);
570
+ const peers = nodes.filter((x) => x.type === n.type && x.id !== n.id && x.type !== "workspace");
571
+ return jsxs(
572
+ "div",
573
+ {
574
+ style: {
575
+ border: "1px solid rgba(127,127,127,0.28)",
576
+ borderRadius: 8,
577
+ padding: 10,
578
+ display: "flex",
579
+ flexDirection: "column",
580
+ gap: 8,
581
+ },
582
+ children: [
583
+ jsxs("div", {
584
+ style: { fontWeight: 600, cursor: "pointer", fontSize: 13 },
585
+ onClick: () => setExpanded(open ? null : n.id),
586
+ children: [
587
+ "#" + n.id + " " + n.type,
588
+ n.name ? " · " + n.name : "",
589
+ n.until ? " · " + L.until + " " + n.until : "",
590
+ n.stale ? " · " + L.stale : "",
591
+ ],
592
+ }),
593
+ jsx("div", { style: { fontSize: 13, opacity: 0.9, lineHeight: 1.4 }, children: n.text }),
594
+ n.path && n.path.length
595
+ ? jsx("div", {
596
+ style: { fontSize: 12, opacity: 0.65, wordBreak: "break-all" },
597
+ children: n.path.join(" | "),
598
+ })
599
+ : null,
600
+ open && n.type !== "workspace"
601
+ ? jsxs("div", {
602
+ style: { display: "flex", flexDirection: "column", gap: 8 },
603
+ children: [
604
+ labeledInput(
605
+ L.name,
606
+ jsx("input", {
607
+ value: e.name,
608
+ onChange: (ev) =>
609
+ setEdits((prev) => ({
610
+ ...prev,
611
+ [n.id]: { ...editOf(n), name: ev.target.value },
612
+ })),
613
+ style: ui.input,
614
+ }),
615
+ ),
616
+ labeledInput(
617
+ L.text,
618
+ jsx("textarea", {
619
+ value: e.text,
620
+ onChange: (ev) =>
621
+ setEdits((prev) => ({
622
+ ...prev,
623
+ [n.id]: { ...editOf(n), text: ev.target.value },
624
+ })),
625
+ rows: 3,
626
+ style: { ...ui.input, minHeight: 72, resize: "vertical" },
627
+ }),
628
+ ),
629
+ n.type === "entity"
630
+ ? labeledInput(
631
+ L.aliases,
632
+ jsx("input", {
633
+ value: e.aliases,
634
+ onChange: (ev) =>
635
+ setEdits((prev) => ({
636
+ ...prev,
637
+ [n.id]: { ...editOf(n), aliases: ev.target.value },
638
+ })),
639
+ placeholder: "逗号分隔",
640
+ style: ui.input,
641
+ }),
642
+ )
643
+ : null,
644
+ n.type === "decision"
645
+ ? labeledInput(
646
+ L.until,
647
+ jsx("input", {
648
+ value: e.until,
649
+ onChange: (ev) =>
650
+ setEdits((prev) => ({
651
+ ...prev,
652
+ [n.id]: { ...editOf(n), until: ev.target.value },
653
+ })),
654
+ placeholder: "周五 / 下周 / ISO",
655
+ style: ui.input,
656
+ }),
657
+ )
658
+ : null,
659
+ jsxs("div", {
660
+ style: { display: "flex", flexWrap: "wrap", gap: 8, alignItems: "center" },
661
+ children: [
662
+ jsx(Button, {
663
+ variant: "outline",
664
+ size: "sm",
665
+ onClick: () => saveNode(n),
666
+ children: L.saveNode,
667
+ }),
668
+ peers.length
669
+ ? jsxs(react.Fragment, {
670
+ children: [
671
+ jsx("select", {
672
+ value: mergeFrom[n.id] || "",
673
+ onChange: (ev) =>
674
+ setMergeFrom((prev) => ({ ...prev, [n.id]: ev.target.value })),
675
+ style: { ...ui.input, width: "auto", minWidth: 160, flex: 1 },
676
+ children: [
677
+ jsx("option", { value: "", children: L.mergePick }, "none"),
678
+ ...peers.map((p) =>
679
+ jsx(
680
+ "option",
681
+ {
682
+ value: String(p.id),
683
+ children:
684
+ "#" + p.id + " " + (p.name || p.text || "").slice(0, 40),
685
+ },
686
+ p.id,
687
+ ),
688
+ ),
689
+ ],
690
+ }),
691
+ jsx(Button, {
692
+ variant: "outline",
693
+ size: "sm",
694
+ disabled: !mergeFrom[n.id],
695
+ onClick: () => mergeIntoKeep(n.id),
696
+ children: L.merge,
697
+ }),
698
+ ],
699
+ })
700
+ : null,
701
+ ],
702
+ }),
703
+ ],
704
+ })
705
+ : null,
706
+ open && hasNeighbors
707
+ ? jsxs("div", {
708
+ style: { fontSize: 12, opacity: 0.85, display: "flex", flexDirection: "column", gap: 2 },
709
+ children: [
710
+ jsx("div", { style: { fontWeight: 600 }, children: L.neighbors }),
711
+ ...incoming.map((edge) =>
712
+ jsx(
713
+ "div",
714
+ {
715
+ children:
716
+ "<-" +
717
+ edge.label +
718
+ "- #" +
719
+ edge.from +
720
+ (edge.type ? " " + edge.type : "") +
721
+ (edge.l0 ? " " + edge.l0 : ""),
722
+ },
723
+ "in-" + edge.from + "-" + edge.label,
724
+ ),
725
+ ),
726
+ ...outgoing.map((edge) =>
727
+ jsx(
728
+ "div",
729
+ {
730
+ children:
731
+ edge.label +
732
+ "-> #" +
733
+ edge.to +
734
+ (edge.type ? " " + edge.type : "") +
735
+ (edge.l0 ? " " + edge.l0 : ""),
736
+ },
737
+ "out-" + edge.to + "-" + edge.label,
738
+ ),
739
+ ),
740
+ ],
741
+ })
742
+ : null,
743
+ n.type === "workspace"
744
+ ? null
745
+ : jsxs("div", {
746
+ style: { display: "flex", gap: 8, flexWrap: "wrap" },
747
+ children: [
748
+ n.type === "entity"
749
+ ? jsx(Button, {
750
+ variant: "outline",
751
+ size: "sm",
752
+ onClick: () => setAboutId(String(n.id)),
753
+ children: L.aboutThis,
754
+ })
755
+ : null,
756
+ jsx(Button, {
757
+ variant: "outline",
758
+ size: "sm",
759
+ onClick: () => act(n.id, "archive"),
760
+ children: L.archive,
761
+ }),
762
+ jsx(Button, {
763
+ variant: "outline",
764
+ size: "sm",
765
+ onClick: () => act(n.id, "delete"),
766
+ children: L.remove,
767
+ }),
768
+ ],
769
+ }),
770
+ ],
771
+ },
772
+ String(n.id),
773
+ );
774
+ }),
775
+ }),
776
+ ],
777
+ }),
778
+ ],
779
+ });
780
+ }
781
+
782
+ function apply(ctx) {
783
+ ctx.slots.inject(
784
+ "settings.section",
785
+ () =>
786
+ ctx.slots.register(
787
+ {
788
+ name: "settings.section",
789
+ id: "dsh-trivium",
790
+ order: 62,
791
+ label: () => L.nav,
792
+ inject: () => ({}),
793
+ },
794
+ TriviumCard,
795
+ ),
796
+ "dsh-trivium: settings section",
797
+ );
798
+ }
799
+
800
+ exports.apply = apply;
801
+ exports.inject = ["slots"];
802
+ return module.exports;
803
+ },
804
+ });