dsh-trivium 0.4.7 → 0.4.8
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 +138 -89
- package/README.zh-CN.md +226 -0
- package/lib/client.js +487 -155
- package/lib/extract.js +5 -4
- package/lib/hygiene.js +130 -0
- package/lib/index.js +47 -17
- package/lib/markdown.js +56 -13
- package/lib/pins.js +12 -2
- package/lib/server.js +39 -3
- package/lib/store.js +11 -1
- package/lib/tools.js +4 -2
- package/lib/update.js +72 -0
- package/package.json +3 -2
package/lib/client.js
CHANGED
|
@@ -9,69 +9,333 @@ window.__ModuleLoader__.load({
|
|
|
9
9
|
const { jsx, jsxs } = require("react/jsx-runtime");
|
|
10
10
|
const { Button } = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
12
|
+
const I18N = {
|
|
13
|
+
zh: {
|
|
14
|
+
nav: "Trivium 记忆",
|
|
15
|
+
sub: "按节点和边记。默认不自动灌全文。归档后 find 不再返回;删除从 .tdb 去掉且不可恢复。",
|
|
16
|
+
loading: "加载中…",
|
|
17
|
+
empty: "还没有打开过工作区,或当前库为空。",
|
|
18
|
+
path: "当前 .tdb",
|
|
19
|
+
nodes: "节点数",
|
|
20
|
+
tokens: "上次短地图 token",
|
|
21
|
+
recall: "注入策略(三选一,默认关)",
|
|
22
|
+
recallHint: "与 autoRecall、实体名折中互斥。改完点右上角保存。",
|
|
23
|
+
recallOff: "关(默认)",
|
|
24
|
+
recallOffHint: "仅 session-start 短地图(≤400 token)。模型自行 ctx_find / ctx_read。",
|
|
25
|
+
recallAuto: "autoRecall",
|
|
26
|
+
recallAutoHint: "本步含直接用户文本时注入最多 3 条 L0(≤300 token)。不调用 ctx_find 工具。",
|
|
27
|
+
recallAnchor: "实体名折中",
|
|
28
|
+
recallAnchorHint: "用户文本命中已有 entity 的 name/aliases 时,注入该实体 1 跳 about/decided/broke/fixed 邻居(≤300 token)。",
|
|
29
|
+
extract: "compaction/end 抽取",
|
|
30
|
+
extractHint: "规则 + 一次小 prompt;失败只记日志。turn 结束后约 12 秒空闲会再抽一次,下次 session-start 也会重放 pending。每轮写入正文合计 ≤3000 字、最多 24 条。",
|
|
31
|
+
markdown: "Markdown 投影",
|
|
32
|
+
markdownHint: "只读导出,带边(- 鉴权走 header X —about→ AuthGateway)。给编辑器和 git 看。改完回写用 JSON 导入或本页编辑,不解析自由散文。",
|
|
33
|
+
exportMd: "导出 Markdown",
|
|
34
|
+
external: "外部记忆一次性导入",
|
|
35
|
+
externalHint: "发现 WorkBuddy MEMORY.md、Claude Code CLAUDE.md、Codex AGENTS.md。偏严宁漏:闲聊、一次性改文件、密钥、乱码、工具 dump、无规则线索的段落跳过。不持续同步,不导入会话 jsonl。每批同样 ≤3000 字 / 24 条。",
|
|
36
|
+
externalImport: "导入找到的文件",
|
|
37
|
+
externalNone: "当前没有找到这些文件。",
|
|
38
|
+
externalFound: "找到",
|
|
39
|
+
update: "检查更新",
|
|
40
|
+
updateHint: "对比本机安装版本和 npm 上的 latest。有新版时给出安装命令,不会在后台自动升级。",
|
|
41
|
+
updateChecking: "正在检查…",
|
|
42
|
+
updateCurrent: "已是最新",
|
|
43
|
+
updateNewer: "有新版本",
|
|
44
|
+
updateInstalled: "已安装",
|
|
45
|
+
updateLatest: "npm latest",
|
|
46
|
+
updateFailed: "暂时查不到 registry",
|
|
47
|
+
embed: "远程 embedding(OpenAI 兼容,默认关)",
|
|
48
|
+
embedHint: "失败退回关键词 + 图检索。需单独的 embeddings endpoint;官方 DeepSeek chat API 不含 embedding。",
|
|
49
|
+
embedReady: "已就绪",
|
|
50
|
+
embedWait: "未就绪:打开开关并填写 http(s) URL 后保存。",
|
|
51
|
+
embedUrl: "Embedding URL",
|
|
52
|
+
embedModel: "模型",
|
|
53
|
+
embedKey: "API Key",
|
|
54
|
+
embedKeyHint: "已保存则留空不改。也可设环境变量 DSH_TRIVIUM_EMBED_KEY。",
|
|
55
|
+
embedBackfill: "回填已有节点向量",
|
|
56
|
+
list: "记忆条目",
|
|
57
|
+
filter: "类型",
|
|
58
|
+
search: "搜索",
|
|
59
|
+
all: "全部",
|
|
60
|
+
archive: "归档",
|
|
61
|
+
remove: "删除",
|
|
62
|
+
saveNode: "保存条目",
|
|
63
|
+
merge: "合并进来",
|
|
64
|
+
mergePick: "并入本条…",
|
|
65
|
+
exportJson: "导出 JSON",
|
|
66
|
+
importJson: "导入 JSON",
|
|
67
|
+
archiveHint: "归档:软删除,find / 短地图不再出现。删除:从库里去掉,不可恢复。合并:同类型节点并入本条,对方 same_as 后归档。",
|
|
68
|
+
save: "保存设置",
|
|
69
|
+
saved: "已保存",
|
|
70
|
+
savedKey: "已保存",
|
|
71
|
+
refresh: "刷新",
|
|
72
|
+
noMatch: "没有匹配的节点。换个关键词,或清空搜索。",
|
|
73
|
+
showStale: "显示过期决策",
|
|
74
|
+
neighbors: "邻居(业务边)",
|
|
75
|
+
aboutThis: "只看挂在这上面的",
|
|
76
|
+
aboutOn: "正在看挂在",
|
|
77
|
+
clearAbout: "返回全部",
|
|
78
|
+
stale: "过期",
|
|
79
|
+
dirty: "脏",
|
|
80
|
+
until: "until",
|
|
81
|
+
name: "名称",
|
|
82
|
+
text: "正文",
|
|
83
|
+
aliases: "别名",
|
|
84
|
+
aliasesPh: "逗号分隔",
|
|
85
|
+
untilPh: "周五 / 下周 / ISO",
|
|
86
|
+
searchPh: "鉴权 / until / about",
|
|
87
|
+
importOk: "导入 {created} 新建 / {merged} 合并,跳过 {skipped}",
|
|
88
|
+
importFail: "导入失败",
|
|
89
|
+
justNow: "刚刚",
|
|
90
|
+
minutesAgo: "{n} 分钟前",
|
|
91
|
+
hoursAgo: "{n} 小时前",
|
|
92
|
+
daysAgo: "{n} 天前",
|
|
93
|
+
boxNext: "后续",
|
|
94
|
+
boxFork: "分支",
|
|
95
|
+
boxCheck: "检查点",
|
|
96
|
+
forkDefault: "分叉 (1)",
|
|
97
|
+
forkName: "分叉",
|
|
98
|
+
trajectory: "轨迹",
|
|
99
|
+
fork: "分叉",
|
|
100
|
+
openChat: "打开对话",
|
|
101
|
+
chatShort: "对",
|
|
102
|
+
boxTitle: "方框标题",
|
|
103
|
+
renameHint: "双击改名",
|
|
104
|
+
rename: "改名",
|
|
105
|
+
noSummary: "(尚无摘要)",
|
|
106
|
+
backfillNone: "没有发现压缩标记或分叉。没压过的会话本来就只有「后续」。要分段请点「生成检查点」。",
|
|
107
|
+
backfillOk: "已同步 {checks} 个检查点、{forks} 条分叉。",
|
|
108
|
+
cutNeedTurn: "当前对话还没有可切的轮次。先在对话里发一条,再生成检查点。",
|
|
109
|
+
cutOk: "已把当前「后续」收成左边一格(seq {seq}),右边仍是后续。这不压缩 DSH 上下文。",
|
|
110
|
+
cutDup: "这个位置已经切过了(seq {seq})。再聊几轮后再生成。",
|
|
111
|
+
forkOpenFail: "无法打开分叉会话:会话图没有拿到 session.open",
|
|
112
|
+
chatViewFail: "无法切换到对话页:会话图没有拿到同一 chat store 的 setView",
|
|
113
|
+
trajViewFail: "无法切换到轨迹页:会话图没有拿到同一 chat store 的 setView",
|
|
114
|
+
noFork: "当前壳没有 session.fork",
|
|
115
|
+
renameFail: "会话已分叉,但侧边栏改名失败:{err}",
|
|
116
|
+
deleteConfirm: "删除会从 .tdb 里拿掉这 {n} 条,不可恢复。归档只是藏起来,find / 短地图 / 芯片都不再出现。确定删除?",
|
|
117
|
+
sessionMap: "会话图",
|
|
118
|
+
chips: "记忆芯片",
|
|
119
|
+
chipsHint: "共 {n} 条,已钉 {pins}。默认不勾选:不钉就不会按芯片白名单注入,但仍可能被开场短地图点名带上,模型也能 find 到。",
|
|
120
|
+
cutBtn: "生成检查点",
|
|
121
|
+
cutting: "生成中…",
|
|
122
|
+
cutTitle: "把当前「后续」收成左边一格,右边继续当后续。只切情节图,不触发 DSH 压缩。要压上下文请在对话里输入 /compact。",
|
|
123
|
+
updateBtn: "更新检查点",
|
|
124
|
+
updating: "更新中…",
|
|
125
|
+
updateTitle: "读取当前对话里 DSH 已有的压缩标记和侧边栏分叉,补进图。没压过就补不出历史方框。",
|
|
126
|
+
collapse: "收起",
|
|
127
|
+
expand: "展开勾选",
|
|
128
|
+
searchChips: "搜索芯片",
|
|
129
|
+
inheritPins: "分叉时继承钉选",
|
|
130
|
+
addChip: "新增",
|
|
131
|
+
cancelAdd: "取消新增",
|
|
132
|
+
select: "选择",
|
|
133
|
+
cancelSelect: "取消选择",
|
|
134
|
+
chipSelectHint: "点芯片多选。归档:软删除,find / 短地图 / 芯片不再出现,节点还在库里。删除:从 .tdb 去掉,不可恢复。",
|
|
135
|
+
chipClipped: "已截到预算(≤300 token)",
|
|
136
|
+
chipPinHint: "勾选后从当前这段的下一轮注入。空白处拖动画布,滚轮缩放。",
|
|
137
|
+
chipDraftPh: "粘贴或输入要记住的内容。点一次写入,整段就是一条芯片。",
|
|
138
|
+
writePin: "写入并钉选",
|
|
139
|
+
suggested: "建议",
|
|
140
|
+
noChipMatch: "没有匹配的芯片。",
|
|
141
|
+
noChips: "还没有可钉选的 preference / decision / entity。用「新增」写一条。",
|
|
142
|
+
forkDialog: "分叉为新会话",
|
|
143
|
+
forkDialogHint: "这个名字会出现在左侧会话列表里。",
|
|
144
|
+
newSessionName: "新会话名称",
|
|
145
|
+
cancel: "取消",
|
|
146
|
+
forking: "分叉中…",
|
|
147
|
+
noRename: "当前壳没有 session.rename",
|
|
148
|
+
},
|
|
149
|
+
en: {
|
|
150
|
+
nav: "Trivium memory",
|
|
151
|
+
sub: "Stored as nodes and edges. Full text is not auto-injected. Archive hides from find; delete removes from .tdb and cannot be undone.",
|
|
152
|
+
loading: "Loading…",
|
|
153
|
+
empty: "No workspace is open, or the store is empty.",
|
|
154
|
+
path: "Current .tdb",
|
|
155
|
+
nodes: "Nodes",
|
|
156
|
+
tokens: "Last short-map tokens",
|
|
157
|
+
recall: "Injection (pick one, default off)",
|
|
158
|
+
recallHint: "autoRecall and entity-name path are mutually exclusive. Save in the top-right when done.",
|
|
159
|
+
recallOff: "Off (default)",
|
|
160
|
+
recallOffHint: "Session-start short map only (≤400 tokens). The model calls ctx_find / ctx_read itself.",
|
|
161
|
+
recallAuto: "autoRecall",
|
|
162
|
+
recallAutoHint: "When the step has user text, inject at most 3 L0 hits (≤300 tokens). Does not call ctx_find.",
|
|
163
|
+
recallAnchor: "Entity-name path",
|
|
164
|
+
recallAnchorHint: "When user text names an existing entity, inject 1-hop about/decided/broke/fixed neighbors (≤300 tokens).",
|
|
165
|
+
extract: "Extract on compaction/end",
|
|
166
|
+
extractHint: "Rules + one small prompt; failures are logged. Idle ~12s after turn/end also extracts; pending replays on next session-start. Per batch: body ≤3000 chars, at most 24 items.",
|
|
167
|
+
markdown: "Markdown projection",
|
|
168
|
+
markdownHint: "Read-only export with edges (e.g. - auth in header X —about→ AuthGateway). For editors and git. Write back with JSON import or this page, not free prose.",
|
|
169
|
+
exportMd: "Export Markdown",
|
|
170
|
+
external: "One-shot external import",
|
|
171
|
+
externalHint: "Discovers WorkBuddy MEMORY.md, Claude Code CLAUDE.md, and Codex AGENTS.md. Strict: skip chitchat, one-off file edits, secrets, mojibake, tool dumps, and lines with no rule cue. No watch, no session jsonl. Same cap: ≤3000 chars / 24 items.",
|
|
172
|
+
externalImport: "Import found files",
|
|
173
|
+
externalNone: "None of these files were found.",
|
|
174
|
+
externalFound: "Found",
|
|
175
|
+
update: "Check for updates",
|
|
176
|
+
updateHint: "Compare the installed version with npm latest. If a newer version exists, the install command is shown — it does not auto-upgrade.",
|
|
177
|
+
updateChecking: "Checking…",
|
|
178
|
+
updateCurrent: "Up to date",
|
|
179
|
+
updateNewer: "Update available",
|
|
180
|
+
updateInstalled: "Installed",
|
|
181
|
+
updateLatest: "npm latest",
|
|
182
|
+
updateFailed: "Registry unavailable",
|
|
183
|
+
embed: "Remote embedding (OpenAI-compatible, off by default)",
|
|
184
|
+
embedHint: "Failures fall back to keyword + graph search. Needs a separate embeddings endpoint; the official DeepSeek chat API has none.",
|
|
185
|
+
embedReady: "Ready",
|
|
186
|
+
embedWait: "Not ready: turn on and fill an http(s) URL, then save.",
|
|
187
|
+
embedUrl: "Embedding URL",
|
|
188
|
+
embedModel: "Model",
|
|
189
|
+
embedKey: "API Key",
|
|
190
|
+
embedKeyHint: "Leave blank to keep the saved key. Or set DSH_TRIVIUM_EMBED_KEY.",
|
|
191
|
+
embedBackfill: "Backfill vectors",
|
|
192
|
+
list: "Memory entries",
|
|
193
|
+
filter: "Type",
|
|
194
|
+
search: "Search",
|
|
195
|
+
all: "All",
|
|
196
|
+
archive: "Archive",
|
|
197
|
+
remove: "Delete",
|
|
198
|
+
saveNode: "Save entry",
|
|
199
|
+
merge: "Merge in",
|
|
200
|
+
mergePick: "Merge into this…",
|
|
201
|
+
exportJson: "Export JSON",
|
|
202
|
+
importJson: "Import JSON",
|
|
203
|
+
archiveHint: "Archive: soft delete, gone from find / short map. Delete: removed from the store, not recoverable. Merge: same-type node into this one; the other is same_as then archived.",
|
|
204
|
+
save: "Save settings",
|
|
205
|
+
saved: "Saved",
|
|
206
|
+
savedKey: "Saved",
|
|
207
|
+
refresh: "Refresh",
|
|
208
|
+
noMatch: "No matching nodes. Try another keyword, or clear the search.",
|
|
209
|
+
showStale: "Show expired decisions",
|
|
210
|
+
neighbors: "Neighbors (business edges)",
|
|
211
|
+
aboutThis: "Only nodes hanging on this",
|
|
212
|
+
aboutOn: "Showing nodes on",
|
|
213
|
+
clearAbout: "Back to all",
|
|
214
|
+
stale: "expired",
|
|
215
|
+
dirty: "dirty",
|
|
216
|
+
until: "until",
|
|
217
|
+
name: "Name",
|
|
218
|
+
text: "Body",
|
|
219
|
+
aliases: "Aliases",
|
|
220
|
+
aliasesPh: "comma-separated",
|
|
221
|
+
untilPh: "Friday / next week / ISO",
|
|
222
|
+
searchPh: "auth / until / about",
|
|
223
|
+
importOk: "Imported {created} new / {merged} merged, skipped {skipped}",
|
|
224
|
+
importFail: "Import failed",
|
|
225
|
+
justNow: "just now",
|
|
226
|
+
minutesAgo: "{n} min ago",
|
|
227
|
+
hoursAgo: "{n} h ago",
|
|
228
|
+
daysAgo: "{n} d ago",
|
|
229
|
+
boxNext: "Next",
|
|
230
|
+
boxFork: "Branch",
|
|
231
|
+
boxCheck: "Checkpoint",
|
|
232
|
+
forkDefault: "Fork (1)",
|
|
233
|
+
forkName: "Fork",
|
|
234
|
+
trajectory: "Trajectory",
|
|
235
|
+
fork: "Fork",
|
|
236
|
+
openChat: "Open conversation",
|
|
237
|
+
chatShort: "C",
|
|
238
|
+
boxTitle: "Box title",
|
|
239
|
+
renameHint: "Double-click to rename",
|
|
240
|
+
rename: "Rename",
|
|
241
|
+
noSummary: "(no summary yet)",
|
|
242
|
+
backfillNone: "No compaction markers or forks. A never-compacted session only has Next. Split with Create checkpoint.",
|
|
243
|
+
backfillOk: "Synced {checks} checkpoints, {forks} forks.",
|
|
244
|
+
cutNeedTurn: "This conversation has no turn to cut yet. Send a message, then create a checkpoint.",
|
|
245
|
+
cutOk: "Folded current Next into a left box (seq {seq}). Right side is still Next. This does not compact DSH context.",
|
|
246
|
+
cutDup: "Already cut at seq {seq}. Chat a few more turns, then create another.",
|
|
247
|
+
forkOpenFail: "Cannot open the forked session: session map has no session.open",
|
|
248
|
+
chatViewFail: "Cannot switch to Conversation: session map has no shared chat store setView",
|
|
249
|
+
trajViewFail: "Cannot switch to Trajectory: session map has no shared chat store setView",
|
|
250
|
+
noFork: "This host has no session.fork",
|
|
251
|
+
renameFail: "Session forked, but sidebar rename failed: {err}",
|
|
252
|
+
deleteConfirm: "Delete removes these {n} nodes from .tdb and cannot be undone. Archive only hides them from find / short map / chips. Delete anyway?",
|
|
253
|
+
sessionMap: "Session graph",
|
|
254
|
+
chips: "Memory chips",
|
|
255
|
+
chipsHint: "{n} chips, {pins} pinned. Unchecked by default: pinning is a whitelist, but the short map may still name them and the model can still find them.",
|
|
256
|
+
cutBtn: "Create checkpoint",
|
|
257
|
+
cutting: "Creating…",
|
|
258
|
+
cutTitle: "Fold current Next into a left box; the right side stays Next. Plot only — does not compact DSH. Type /compact in the conversation to compact context.",
|
|
259
|
+
updateBtn: "Update checkpoints",
|
|
260
|
+
updating: "Updating…",
|
|
261
|
+
updateTitle: "Read DSH compression markers and sidebar forks in this conversation and backfill the graph. Never compacted still means only Next.",
|
|
262
|
+
collapse: "Collapse",
|
|
263
|
+
expand: "Expand to pin",
|
|
264
|
+
searchChips: "Search chips",
|
|
265
|
+
inheritPins: "Inherit pins on fork",
|
|
266
|
+
addChip: "Add",
|
|
267
|
+
cancelAdd: "Cancel add",
|
|
268
|
+
select: "Select",
|
|
269
|
+
cancelSelect: "Cancel select",
|
|
270
|
+
chipSelectHint: "Click chips to multi-select. Archive: soft delete, gone from find / short map / chips, node stays in .tdb. Delete: removed from .tdb, not recoverable.",
|
|
271
|
+
chipClipped: "Clipped to budget (≤300 tokens)",
|
|
272
|
+
chipPinHint: "Checked chips inject from the next turn of this segment. Drag empty canvas to pan, scroll to zoom.",
|
|
273
|
+
chipDraftPh: "Paste or type a fact. One click writes one chip; a whole paste is still one node.",
|
|
274
|
+
writePin: "Write and pin",
|
|
275
|
+
suggested: "suggested",
|
|
276
|
+
noChipMatch: "No matching chips.",
|
|
277
|
+
noChips: "No preference / decision / entity chips yet. Use Add to write one.",
|
|
278
|
+
forkDialog: "Fork as new session",
|
|
279
|
+
forkDialogHint: "This name appears in the left session list.",
|
|
280
|
+
newSessionName: "New session name",
|
|
281
|
+
cancel: "Cancel",
|
|
282
|
+
forking: "Forking…",
|
|
283
|
+
noRename: "This host has no session.rename",
|
|
284
|
+
},
|
|
73
285
|
};
|
|
74
286
|
|
|
287
|
+
let uiLocale = "zh";
|
|
288
|
+
const localeListeners = new Set();
|
|
289
|
+
|
|
290
|
+
function normalizeLocale(value) {
|
|
291
|
+
const s = String(value || "").toLowerCase();
|
|
292
|
+
if (s.startsWith("en")) return "en";
|
|
293
|
+
if (s.startsWith("zh")) return "zh";
|
|
294
|
+
return "";
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function setUiLocale(next) {
|
|
298
|
+
const loc = normalizeLocale(next) || "zh";
|
|
299
|
+
if (loc === uiLocale) return;
|
|
300
|
+
uiLocale = loc;
|
|
301
|
+
localeListeners.forEach((fn) => {
|
|
302
|
+
try {
|
|
303
|
+
fn();
|
|
304
|
+
} catch {
|
|
305
|
+
// ignore
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function t(key, vars) {
|
|
311
|
+
let s = (I18N[uiLocale] && I18N[uiLocale][key]) || I18N.en[key] || I18N.zh[key] || key;
|
|
312
|
+
if (vars) {
|
|
313
|
+
Object.keys(vars).forEach((k) => {
|
|
314
|
+
s = s.split("{" + k + "}").join(String(vars[k]));
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
return s;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function useT() {
|
|
321
|
+
const [, bump] = react.useState(0);
|
|
322
|
+
react.useEffect(() => {
|
|
323
|
+
const fn = () => bump((n) => n + 1);
|
|
324
|
+
localeListeners.add(fn);
|
|
325
|
+
return () => localeListeners.delete(fn);
|
|
326
|
+
}, []);
|
|
327
|
+
return t;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const L = new Proxy(
|
|
331
|
+
{},
|
|
332
|
+
{
|
|
333
|
+
get(_target, key) {
|
|
334
|
+
return t(key);
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
);
|
|
338
|
+
|
|
75
339
|
const TYPES = ["", "entity", "preference", "decision", "experience", "workspace"];
|
|
76
340
|
|
|
77
341
|
const ui = {
|
|
@@ -131,6 +395,7 @@ window.__ModuleLoader__.load({
|
|
|
131
395
|
}
|
|
132
396
|
|
|
133
397
|
function TriviumCard() {
|
|
398
|
+
useT();
|
|
134
399
|
const [status, setStatus] = react.useState(null);
|
|
135
400
|
const [nodes, setNodes] = react.useState([]);
|
|
136
401
|
const [type, setType] = react.useState("");
|
|
@@ -153,6 +418,8 @@ window.__ModuleLoader__.load({
|
|
|
153
418
|
const fileRef = react.useRef(null);
|
|
154
419
|
const [buddyFiles, setBuddyFiles] = react.useState([]);
|
|
155
420
|
const [buddyNote, setBuddyNote] = react.useState("");
|
|
421
|
+
const [updateInfo, setUpdateInfo] = react.useState(null);
|
|
422
|
+
const [updateBusy, setUpdateBusy] = react.useState(false);
|
|
156
423
|
|
|
157
424
|
const load = react.useCallback(async () => {
|
|
158
425
|
setError("");
|
|
@@ -173,7 +440,7 @@ window.__ModuleLoader__.load({
|
|
|
173
440
|
const qs = params.toString();
|
|
174
441
|
const list = await api("/api/dsh-trivium/nodes" + (qs ? "?" + qs : ""));
|
|
175
442
|
setNodes(list.nodes || []);
|
|
176
|
-
const wb = await api("/api/dsh-trivium/
|
|
443
|
+
const wb = await api("/api/dsh-trivium/external");
|
|
177
444
|
setBuddyFiles(wb.files || []);
|
|
178
445
|
} catch (err) {
|
|
179
446
|
setError(String(err.message || err));
|
|
@@ -317,19 +584,18 @@ window.__ModuleLoader__.load({
|
|
|
317
584
|
setBusy(true);
|
|
318
585
|
setBuddyNote("");
|
|
319
586
|
try {
|
|
320
|
-
const r = await api("/api/dsh-trivium/import-
|
|
587
|
+
const r = await api("/api/dsh-trivium/import-external", {
|
|
321
588
|
method: "POST",
|
|
322
589
|
headers: { "content-type": "application/json" },
|
|
323
590
|
body: "{}",
|
|
324
591
|
});
|
|
325
592
|
setSaved(true);
|
|
326
593
|
setBuddyNote(
|
|
327
|
-
"
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
(r.skipped || 0),
|
|
594
|
+
t("importOk", {
|
|
595
|
+
created: r.created || 0,
|
|
596
|
+
merged: r.merged || 0,
|
|
597
|
+
skipped: r.skipped || 0,
|
|
598
|
+
}),
|
|
333
599
|
);
|
|
334
600
|
await load();
|
|
335
601
|
} catch (err) {
|
|
@@ -339,6 +605,17 @@ window.__ModuleLoader__.load({
|
|
|
339
605
|
}
|
|
340
606
|
};
|
|
341
607
|
|
|
608
|
+
const checkUpdate = async () => {
|
|
609
|
+
setUpdateBusy(true);
|
|
610
|
+
try {
|
|
611
|
+
setUpdateInfo(await api("/api/dsh-trivium/update?force=1"));
|
|
612
|
+
} catch (err) {
|
|
613
|
+
setUpdateInfo({ ok: false, error: String(err.message || err) });
|
|
614
|
+
} finally {
|
|
615
|
+
setUpdateBusy(false);
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
|
|
342
619
|
const importJson = async (file) => {
|
|
343
620
|
if (!file) return;
|
|
344
621
|
setBusy(true);
|
|
@@ -354,11 +631,11 @@ window.__ModuleLoader__.load({
|
|
|
354
631
|
setError(
|
|
355
632
|
r.ok
|
|
356
633
|
? ""
|
|
357
|
-
: String(r.message || "
|
|
634
|
+
: String(r.message || t("importFail")),
|
|
358
635
|
);
|
|
359
636
|
await load();
|
|
360
637
|
} catch (err) {
|
|
361
|
-
setError("
|
|
638
|
+
setError(t("importFail") + ":" + String(err.message || err));
|
|
362
639
|
} finally {
|
|
363
640
|
setBusy(false);
|
|
364
641
|
if (fileRef.current) fileRef.current.value = "";
|
|
@@ -461,6 +738,42 @@ window.__ModuleLoader__.load({
|
|
|
461
738
|
],
|
|
462
739
|
})
|
|
463
740
|
: null,
|
|
741
|
+
jsxs("div", {
|
|
742
|
+
style: ui.card,
|
|
743
|
+
children: [
|
|
744
|
+
jsx("div", { style: ui.cardTitle, children: L.update }),
|
|
745
|
+
jsx("div", { style: ui.hint, children: L.updateHint }),
|
|
746
|
+
jsx("div", {
|
|
747
|
+
style: ui.hint,
|
|
748
|
+
children: updateBusy
|
|
749
|
+
? L.updateChecking
|
|
750
|
+
: updateInfo
|
|
751
|
+
? [
|
|
752
|
+
L.updateInstalled + " " + (updateInfo.installed || status?.version || "—"),
|
|
753
|
+
L.updateLatest + " " + (updateInfo.latest || "—"),
|
|
754
|
+
updateInfo.error
|
|
755
|
+
? L.updateFailed
|
|
756
|
+
: updateInfo.newer
|
|
757
|
+
? L.updateNewer
|
|
758
|
+
: L.updateCurrent,
|
|
759
|
+
].join(" · ")
|
|
760
|
+
: L.updateInstalled + " " + (status?.version || "—"),
|
|
761
|
+
}),
|
|
762
|
+
updateInfo && updateInfo.newer && updateInfo.command
|
|
763
|
+
? jsx("code", { style: { fontSize: 12 }, children: updateInfo.command })
|
|
764
|
+
: null,
|
|
765
|
+
jsx("div", {
|
|
766
|
+
style: { display: "flex", flexWrap: "wrap", gap: 8 },
|
|
767
|
+
children: jsx(Button, {
|
|
768
|
+
variant: "outline",
|
|
769
|
+
size: "sm",
|
|
770
|
+
disabled: updateBusy,
|
|
771
|
+
onClick: checkUpdate,
|
|
772
|
+
children: updateBusy ? L.updateChecking : L.update,
|
|
773
|
+
}),
|
|
774
|
+
}),
|
|
775
|
+
],
|
|
776
|
+
}),
|
|
464
777
|
jsxs("div", {
|
|
465
778
|
style: ui.card,
|
|
466
779
|
children: [
|
|
@@ -493,18 +806,18 @@ window.__ModuleLoader__.load({
|
|
|
493
806
|
children: [
|
|
494
807
|
jsx("div", { style: ui.cardTitle, children: L.markdown }),
|
|
495
808
|
jsx("div", { style: ui.hint, children: L.markdownHint }),
|
|
496
|
-
jsx("div", { style: ui.cardTitle, children: L.
|
|
497
|
-
jsx("div", { style: ui.hint, children: L.
|
|
809
|
+
jsx("div", { style: ui.cardTitle, children: L.external }),
|
|
810
|
+
jsx("div", { style: ui.hint, children: L.externalHint }),
|
|
498
811
|
jsx("div", {
|
|
499
812
|
style: { ...ui.hint, wordBreak: "break-all" },
|
|
500
813
|
children: (buddyFiles || []).some((f) => f.exists)
|
|
501
|
-
? L.
|
|
814
|
+
? L.externalFound +
|
|
502
815
|
" " +
|
|
503
816
|
buddyFiles
|
|
504
817
|
.filter((f) => f.exists)
|
|
505
|
-
.map((f) => f.path)
|
|
818
|
+
.map((f) => (f.family ? f.family + " · " : "") + f.path)
|
|
506
819
|
.join(" · ")
|
|
507
|
-
: L.
|
|
820
|
+
: L.externalNone,
|
|
508
821
|
}),
|
|
509
822
|
buddyNote ? jsx("div", { style: ui.saved, children: buddyNote }) : null,
|
|
510
823
|
jsx("div", {
|
|
@@ -514,7 +827,7 @@ window.__ModuleLoader__.load({
|
|
|
514
827
|
size: "sm",
|
|
515
828
|
disabled: busy || !(buddyFiles || []).some((f) => f.exists),
|
|
516
829
|
onClick: importBuddy,
|
|
517
|
-
children: L.
|
|
830
|
+
children: L.externalImport,
|
|
518
831
|
}),
|
|
519
832
|
}),
|
|
520
833
|
],
|
|
@@ -581,7 +894,7 @@ window.__ModuleLoader__.load({
|
|
|
581
894
|
type: "password",
|
|
582
895
|
value: embeddingApiKey,
|
|
583
896
|
onChange: (e) => setEmbeddingApiKey(e.target.value),
|
|
584
|
-
placeholder: embeddingApiKeySet ?
|
|
897
|
+
placeholder: embeddingApiKeySet ? L.savedKey : "",
|
|
585
898
|
style: ui.input,
|
|
586
899
|
}),
|
|
587
900
|
),
|
|
@@ -614,7 +927,7 @@ window.__ModuleLoader__.load({
|
|
|
614
927
|
jsx("input", {
|
|
615
928
|
value: q,
|
|
616
929
|
onChange: (e) => setQ(e.target.value),
|
|
617
|
-
placeholder: L.search + "
|
|
930
|
+
placeholder: L.search + " " + L.searchPh,
|
|
618
931
|
style: { ...ui.input, flex: 1, minWidth: 160 },
|
|
619
932
|
}),
|
|
620
933
|
jsxs("label", {
|
|
@@ -682,6 +995,7 @@ window.__ModuleLoader__.load({
|
|
|
682
995
|
n.name ? " · " + n.name : "",
|
|
683
996
|
n.until ? " · " + L.until + " " + n.until : "",
|
|
684
997
|
n.stale ? " · " + L.stale : "",
|
|
998
|
+
n.dirty ? " · " + L.dirty : "",
|
|
685
999
|
],
|
|
686
1000
|
}),
|
|
687
1001
|
jsx("div", { style: { fontSize: 13, opacity: 0.9, lineHeight: 1.4 }, children: n.text }),
|
|
@@ -730,7 +1044,7 @@ window.__ModuleLoader__.load({
|
|
|
730
1044
|
...prev,
|
|
731
1045
|
[n.id]: { ...editOf(n), aliases: ev.target.value },
|
|
732
1046
|
})),
|
|
733
|
-
placeholder:
|
|
1047
|
+
placeholder: L.aliasesPh,
|
|
734
1048
|
style: ui.input,
|
|
735
1049
|
}),
|
|
736
1050
|
)
|
|
@@ -745,7 +1059,7 @@ window.__ModuleLoader__.load({
|
|
|
745
1059
|
...prev,
|
|
746
1060
|
[n.id]: { ...editOf(n), until: ev.target.value },
|
|
747
1061
|
})),
|
|
748
|
-
placeholder:
|
|
1062
|
+
placeholder: L.untilPh,
|
|
749
1063
|
style: ui.input,
|
|
750
1064
|
}),
|
|
751
1065
|
)
|
|
@@ -874,22 +1188,35 @@ window.__ModuleLoader__.load({
|
|
|
874
1188
|
}
|
|
875
1189
|
|
|
876
1190
|
function relTime(iso) {
|
|
877
|
-
const
|
|
878
|
-
if (!Number.isFinite(
|
|
879
|
-
const sec = Math.max(0, Math.round((Date.now() -
|
|
880
|
-
if (sec < 60) return "
|
|
881
|
-
if (sec < 3600) return Math.floor(sec / 60)
|
|
882
|
-
if (sec < 86400) return Math.floor(sec / 3600)
|
|
883
|
-
return Math.floor(sec / 86400)
|
|
1191
|
+
const at = Date.parse(iso);
|
|
1192
|
+
if (!Number.isFinite(at)) return "";
|
|
1193
|
+
const sec = Math.max(0, Math.round((Date.now() - at) / 1000));
|
|
1194
|
+
if (sec < 60) return t("justNow");
|
|
1195
|
+
if (sec < 3600) return t("minutesAgo", { n: Math.floor(sec / 60) });
|
|
1196
|
+
if (sec < 86400) return t("hoursAgo", { n: Math.floor(sec / 3600) });
|
|
1197
|
+
return t("daysAgo", { n: Math.floor(sec / 86400) });
|
|
884
1198
|
}
|
|
885
1199
|
|
|
886
1200
|
function kindLabel(kind, current) {
|
|
887
|
-
if (current || kind === "tail") return "
|
|
888
|
-
if (kind === "fork") return "
|
|
889
|
-
return "
|
|
1201
|
+
if (current || kind === "tail") return t("boxNext");
|
|
1202
|
+
if (kind === "fork") return t("boxFork");
|
|
1203
|
+
return t("boxCheck");
|
|
890
1204
|
}
|
|
891
1205
|
|
|
892
|
-
const GENERIC_BOX_NAMES = {
|
|
1206
|
+
const GENERIC_BOX_NAMES = {
|
|
1207
|
+
当前: 1,
|
|
1208
|
+
已压缩: 1,
|
|
1209
|
+
分叉: 1,
|
|
1210
|
+
分叉点: 1,
|
|
1211
|
+
后续: 1,
|
|
1212
|
+
分支: 1,
|
|
1213
|
+
Current: 1,
|
|
1214
|
+
Compacted: 1,
|
|
1215
|
+
Fork: 1,
|
|
1216
|
+
Checkpoint: 1,
|
|
1217
|
+
Next: 1,
|
|
1218
|
+
Branch: 1,
|
|
1219
|
+
};
|
|
893
1220
|
const MAP_BOX_W = 220;
|
|
894
1221
|
const MAP_ARROW_W = 96;
|
|
895
1222
|
const MAP_PAD = 40;
|
|
@@ -1115,7 +1442,7 @@ window.__ModuleLoader__.load({
|
|
|
1115
1442
|
|
|
1116
1443
|
function increasedForkTitle(title) {
|
|
1117
1444
|
const text = String(title || "").trim();
|
|
1118
|
-
if (!text) return "
|
|
1445
|
+
if (!text) return t("forkDefault");
|
|
1119
1446
|
const ascii = /^(.*?)\((\d+)\)$/u.exec(text);
|
|
1120
1447
|
if (ascii?.[1] != null && ascii[2] != null) return ascii[1] + "(" + (Number(ascii[2]) + 1) + ")";
|
|
1121
1448
|
const fullWidth = /^(.*?)((\d+))$/u.exec(text);
|
|
@@ -1129,8 +1456,8 @@ window.__ModuleLoader__.load({
|
|
|
1129
1456
|
}
|
|
1130
1457
|
|
|
1131
1458
|
function boxKicker(node, { current, child, sessionTitle }) {
|
|
1132
|
-
if (child) return sessionTitle || "
|
|
1133
|
-
if (current || node?.kind === "tail") return "
|
|
1459
|
+
if (child) return sessionTitle || t("forkName");
|
|
1460
|
+
if (current || node?.kind === "tail") return t("boxNext");
|
|
1134
1461
|
if (node?.atSeq != null && Number(node.atSeq) > 0) return "seq " + node.atSeq;
|
|
1135
1462
|
return kindLabel(node?.kind, current);
|
|
1136
1463
|
}
|
|
@@ -1604,8 +1931,8 @@ window.__ModuleLoader__.load({
|
|
|
1604
1931
|
};
|
|
1605
1932
|
const inspectBtn = jsx("button", {
|
|
1606
1933
|
type: "button",
|
|
1607
|
-
title: "
|
|
1608
|
-
"aria-label": "
|
|
1934
|
+
title: t("trajectory"),
|
|
1935
|
+
"aria-label": t("trajectory"),
|
|
1609
1936
|
"data-map-inspect": "1",
|
|
1610
1937
|
style: mapUi.iconBtn,
|
|
1611
1938
|
onPointerDown: stopIcon,
|
|
@@ -1618,8 +1945,8 @@ window.__ModuleLoader__.load({
|
|
|
1618
1945
|
});
|
|
1619
1946
|
const forkBtn = jsx("button", {
|
|
1620
1947
|
type: "button",
|
|
1621
|
-
title: "
|
|
1622
|
-
"aria-label": "
|
|
1948
|
+
title: t("fork"),
|
|
1949
|
+
"aria-label": t("fork"),
|
|
1623
1950
|
"data-map-fork": "1",
|
|
1624
1951
|
disabled: !!busy,
|
|
1625
1952
|
style: { ...mapUi.iconBtn, ...(busy ? { opacity: 0.4 } : {}) },
|
|
@@ -1635,8 +1962,8 @@ window.__ModuleLoader__.load({
|
|
|
1635
1962
|
!canSetView
|
|
1636
1963
|
? jsx("button", {
|
|
1637
1964
|
type: "button",
|
|
1638
|
-
title: "
|
|
1639
|
-
"aria-label": "
|
|
1965
|
+
title: t("openChat"),
|
|
1966
|
+
"aria-label": t("openChat"),
|
|
1640
1967
|
"data-map-open": "1",
|
|
1641
1968
|
style: mapUi.iconBtn,
|
|
1642
1969
|
onPointerDown: stopIcon,
|
|
@@ -1645,14 +1972,14 @@ window.__ModuleLoader__.load({
|
|
|
1645
1972
|
stopIcon(e);
|
|
1646
1973
|
onJump(node);
|
|
1647
1974
|
},
|
|
1648
|
-
children: "
|
|
1975
|
+
children: t("chatShort"),
|
|
1649
1976
|
})
|
|
1650
1977
|
: null;
|
|
1651
1978
|
const titleNode = editing
|
|
1652
1979
|
? jsx("input", {
|
|
1653
1980
|
value: draft,
|
|
1654
1981
|
autoFocus: true,
|
|
1655
|
-
"aria-label": "
|
|
1982
|
+
"aria-label": t("boxTitle"),
|
|
1656
1983
|
style: mapUi.titleInput,
|
|
1657
1984
|
onPointerDown: (e) => e.stopPropagation(),
|
|
1658
1985
|
onClick: (e) => e.stopPropagation(),
|
|
@@ -1672,7 +1999,7 @@ window.__ModuleLoader__.load({
|
|
|
1672
1999
|
})
|
|
1673
2000
|
: jsx("div", {
|
|
1674
2001
|
style: mapUi.title,
|
|
1675
|
-
title: "
|
|
2002
|
+
title: t("renameHint"),
|
|
1676
2003
|
onClick: (e) => e.stopPropagation(),
|
|
1677
2004
|
onDoubleClick: startEdit,
|
|
1678
2005
|
children: customTitle,
|
|
@@ -1714,7 +2041,7 @@ window.__ModuleLoader__.load({
|
|
|
1714
2041
|
children: [
|
|
1715
2042
|
jsxs("div", {
|
|
1716
2043
|
style: { ...mapUi.kicker, flex: 1, minWidth: 0 },
|
|
1717
|
-
title: canRename ? "
|
|
2044
|
+
title: canRename ? t("renameHint") : undefined,
|
|
1718
2045
|
onDoubleClick: startEdit,
|
|
1719
2046
|
children: [
|
|
1720
2047
|
boxKicker(node, { current, child, sessionTitle }),
|
|
@@ -1726,13 +2053,13 @@ window.__ModuleLoader__.load({
|
|
|
1726
2053
|
style: mapUi.ghostBtn,
|
|
1727
2054
|
onPointerDown: stopIcon,
|
|
1728
2055
|
onClick: startEdit,
|
|
1729
|
-
children: "
|
|
2056
|
+
children: t("rename"),
|
|
1730
2057
|
})
|
|
1731
2058
|
: null,
|
|
1732
2059
|
],
|
|
1733
2060
|
}),
|
|
1734
2061
|
customTitle || editing ? titleNode : null,
|
|
1735
|
-
jsx("div", { style: mapUi.summary, children: body || "
|
|
2062
|
+
jsx("div", { style: mapUi.summary, children: body || t("noSummary") }),
|
|
1736
2063
|
jsx("div", { style: mapUi.hint, children: relTime(node.createdAt) }),
|
|
1737
2064
|
],
|
|
1738
2065
|
}),
|
|
@@ -1749,6 +2076,7 @@ window.__ModuleLoader__.load({
|
|
|
1749
2076
|
let pendingSessionNav = null;
|
|
1750
2077
|
|
|
1751
2078
|
function SessionMapView({ sessionId, useSessions, useSession, fork, open, renameSession, actions, seedLengthOf }) {
|
|
2079
|
+
useT();
|
|
1752
2080
|
const canSetView = typeof actions?.setView === "function";
|
|
1753
2081
|
const chat = useSession((s) => s.chat);
|
|
1754
2082
|
const cwd = useSessions((s) => (sessionId == null ? "" : s.byId[sessionId]?.cwd || ""));
|
|
@@ -1860,9 +2188,9 @@ window.__ModuleLoader__.load({
|
|
|
1860
2188
|
const checks = Number(resp.checkpoints) || 0;
|
|
1861
2189
|
const forkN = Number(resp.forks) || 0;
|
|
1862
2190
|
if (!checks && !forkN) {
|
|
1863
|
-
setBackfillNote("
|
|
2191
|
+
setBackfillNote(t("backfillNone"));
|
|
1864
2192
|
} else {
|
|
1865
|
-
setBackfillNote("
|
|
2193
|
+
setBackfillNote(t("backfillOk", { checks, forks: forkN }));
|
|
1866
2194
|
}
|
|
1867
2195
|
} catch (err) {
|
|
1868
2196
|
setError(String(err.message || err));
|
|
@@ -1875,7 +2203,7 @@ window.__ModuleLoader__.load({
|
|
|
1875
2203
|
if (!sessionId || busy) return;
|
|
1876
2204
|
const atSeq = lastChatSeq(chat);
|
|
1877
2205
|
if (atSeq == null) {
|
|
1878
|
-
setError("
|
|
2206
|
+
setError(t("cutNeedTurn"));
|
|
1879
2207
|
return;
|
|
1880
2208
|
}
|
|
1881
2209
|
setBusy(true);
|
|
@@ -1892,9 +2220,9 @@ window.__ModuleLoader__.load({
|
|
|
1892
2220
|
});
|
|
1893
2221
|
applyMap(resp);
|
|
1894
2222
|
if (resp.created) {
|
|
1895
|
-
setBackfillNote("
|
|
2223
|
+
setBackfillNote(t("cutOk", { seq: atSeq }));
|
|
1896
2224
|
} else {
|
|
1897
|
-
setBackfillNote("
|
|
2225
|
+
setBackfillNote(t("cutDup", { seq: atSeq }));
|
|
1898
2226
|
}
|
|
1899
2227
|
} catch (err) {
|
|
1900
2228
|
setError(String(err.message || err));
|
|
@@ -1974,7 +2302,7 @@ window.__ModuleLoader__.load({
|
|
|
1974
2302
|
|
|
1975
2303
|
const openSession = (id, nav) => {
|
|
1976
2304
|
if (typeof open !== "function") {
|
|
1977
|
-
setError("
|
|
2305
|
+
setError(t("forkOpenFail"));
|
|
1978
2306
|
return false;
|
|
1979
2307
|
}
|
|
1980
2308
|
try {
|
|
@@ -1994,7 +2322,7 @@ window.__ModuleLoader__.load({
|
|
|
1994
2322
|
return;
|
|
1995
2323
|
}
|
|
1996
2324
|
if (typeof actions?.setView !== "function") {
|
|
1997
|
-
setError("
|
|
2325
|
+
setError(t("chatViewFail"));
|
|
1998
2326
|
return;
|
|
1999
2327
|
}
|
|
2000
2328
|
const toEnd = node.kind === "tail" || !!node.current || !Number(node.atSeq);
|
|
@@ -2017,7 +2345,7 @@ window.__ModuleLoader__.load({
|
|
|
2017
2345
|
if (typeof actions?.setView === "function") {
|
|
2018
2346
|
actions.setView("trajectory");
|
|
2019
2347
|
} else {
|
|
2020
|
-
setError("
|
|
2348
|
+
setError(t("trajViewFail"));
|
|
2021
2349
|
}
|
|
2022
2350
|
};
|
|
2023
2351
|
|
|
@@ -2040,7 +2368,7 @@ window.__ModuleLoader__.load({
|
|
|
2040
2368
|
setBusy(true);
|
|
2041
2369
|
setError("");
|
|
2042
2370
|
try {
|
|
2043
|
-
if (typeof fork !== "function") throw new Error("
|
|
2371
|
+
if (typeof fork !== "function") throw new Error(t("noFork"));
|
|
2044
2372
|
const sourceId = String(node.sessionId || sessionId);
|
|
2045
2373
|
const opts = { sessionId: sourceId, increaseTitle: true };
|
|
2046
2374
|
if (!node.current && node.kind !== "tail" && Number.isFinite(Number(node.atSeq))) {
|
|
@@ -2052,7 +2380,7 @@ window.__ModuleLoader__.load({
|
|
|
2052
2380
|
try {
|
|
2053
2381
|
await renameSession(childId, wanted);
|
|
2054
2382
|
} catch (err) {
|
|
2055
|
-
setError("
|
|
2383
|
+
setError(t("renameFail", { err: String(err.message || err) }));
|
|
2056
2384
|
}
|
|
2057
2385
|
}
|
|
2058
2386
|
if (inheritPins && childId) {
|
|
@@ -2123,12 +2451,7 @@ window.__ModuleLoader__.load({
|
|
|
2123
2451
|
const runChipBatch = async (action) => {
|
|
2124
2452
|
if (!pickedIds.length || busy) return;
|
|
2125
2453
|
if (action === "delete") {
|
|
2126
|
-
|
|
2127
|
-
"删除会从 .tdb 里拿掉这 " +
|
|
2128
|
-
pickedIds.length +
|
|
2129
|
-
" 条,不可恢复。归档只是藏起来,find / 短地图 / 芯片都不再出现。确定删除?",
|
|
2130
|
-
);
|
|
2131
|
-
if (!ok) return;
|
|
2454
|
+
if (!window.confirm(t("deleteConfirm", { n: pickedIds.length }))) return;
|
|
2132
2455
|
}
|
|
2133
2456
|
setBusy(true);
|
|
2134
2457
|
setError("");
|
|
@@ -2195,16 +2518,12 @@ window.__ModuleLoader__.load({
|
|
|
2195
2518
|
jsxs("div", {
|
|
2196
2519
|
style: mapUi.chipsHead,
|
|
2197
2520
|
children: [
|
|
2198
|
-
jsx("div", { style: mapUi.chipsTitle, children: "
|
|
2521
|
+
jsx("div", { style: mapUi.chipsTitle, children: t("chips") }),
|
|
2199
2522
|
jsx("div", {
|
|
2200
2523
|
style: mapUi.hint,
|
|
2201
2524
|
children:
|
|
2202
2525
|
backfillNote ||
|
|
2203
|
-
"
|
|
2204
|
-
chips.length +
|
|
2205
|
-
" 条,已钉 " +
|
|
2206
|
-
pinIds.length +
|
|
2207
|
-
"。默认不勾选:不钉就不会按芯片白名单注入,但仍可能被开场短地图点名带上,模型也能 find 到。",
|
|
2526
|
+
t("chipsHint", { n: chips.length, pins: pinIds.length }),
|
|
2208
2527
|
}),
|
|
2209
2528
|
jsxs("div", {
|
|
2210
2529
|
style: mapUi.chipsActions,
|
|
@@ -2213,23 +2532,23 @@ window.__ModuleLoader__.load({
|
|
|
2213
2532
|
type: "button",
|
|
2214
2533
|
style: mapUi.actionBtn,
|
|
2215
2534
|
disabled: busy,
|
|
2216
|
-
title: "
|
|
2535
|
+
title: t("cutTitle"),
|
|
2217
2536
|
onClick: cutCheckpoint,
|
|
2218
|
-
children: busy ? "
|
|
2537
|
+
children: busy ? t("cutting") : t("cutBtn"),
|
|
2219
2538
|
}),
|
|
2220
2539
|
jsx("button", {
|
|
2221
2540
|
type: "button",
|
|
2222
2541
|
style: mapUi.actionBtn,
|
|
2223
2542
|
disabled: busy,
|
|
2224
|
-
title: "
|
|
2543
|
+
title: t("updateTitle"),
|
|
2225
2544
|
onClick: syncCheckpoints,
|
|
2226
|
-
children: busy ? "
|
|
2545
|
+
children: busy ? t("updating") : t("updateBtn"),
|
|
2227
2546
|
}),
|
|
2228
2547
|
jsx("button", {
|
|
2229
2548
|
type: "button",
|
|
2230
2549
|
style: mapUi.actionBtn,
|
|
2231
2550
|
onClick: () => setChipsOpen((v) => !v),
|
|
2232
|
-
children: chipsOpen ? "
|
|
2551
|
+
children: chipsOpen ? t("collapse") : t("expand"),
|
|
2233
2552
|
}),
|
|
2234
2553
|
],
|
|
2235
2554
|
}),
|
|
@@ -2244,7 +2563,7 @@ window.__ModuleLoader__.load({
|
|
|
2244
2563
|
children: [
|
|
2245
2564
|
jsx("input", {
|
|
2246
2565
|
type: "search",
|
|
2247
|
-
placeholder: "
|
|
2566
|
+
placeholder: t("searchChips"),
|
|
2248
2567
|
value: chipQuery,
|
|
2249
2568
|
onChange: (e) => setChipQuery(e.target.value),
|
|
2250
2569
|
style: mapUi.search,
|
|
@@ -2257,7 +2576,7 @@ window.__ModuleLoader__.load({
|
|
|
2257
2576
|
checked: inheritPins,
|
|
2258
2577
|
onChange: (e) => setInheritPins(e.target.checked),
|
|
2259
2578
|
}),
|
|
2260
|
-
"
|
|
2579
|
+
t("inheritPins"),
|
|
2261
2580
|
],
|
|
2262
2581
|
}),
|
|
2263
2582
|
jsxs("div", {
|
|
@@ -2271,7 +2590,7 @@ window.__ModuleLoader__.load({
|
|
|
2271
2590
|
setChipSelect(false);
|
|
2272
2591
|
setPickedIds([]);
|
|
2273
2592
|
},
|
|
2274
|
-
children: draftOpen ? "
|
|
2593
|
+
children: draftOpen ? t("cancelAdd") : t("addChip"),
|
|
2275
2594
|
}),
|
|
2276
2595
|
jsx("button", {
|
|
2277
2596
|
type: "button",
|
|
@@ -2281,7 +2600,7 @@ window.__ModuleLoader__.load({
|
|
|
2281
2600
|
setPickedIds([]);
|
|
2282
2601
|
setDraftOpen(false);
|
|
2283
2602
|
},
|
|
2284
|
-
children: chipSelect ? "
|
|
2603
|
+
children: chipSelect ? t("cancelSelect") : t("select"),
|
|
2285
2604
|
}),
|
|
2286
2605
|
chipSelect
|
|
2287
2606
|
? jsx("button", {
|
|
@@ -2289,7 +2608,7 @@ window.__ModuleLoader__.load({
|
|
|
2289
2608
|
style: mapUi.actionBtn,
|
|
2290
2609
|
disabled: !pickedIds.length || busy,
|
|
2291
2610
|
onClick: () => runChipBatch("archive"),
|
|
2292
|
-
children: "
|
|
2611
|
+
children: t("archive") + (pickedIds.length ? " " + pickedIds.length : ""),
|
|
2293
2612
|
})
|
|
2294
2613
|
: null,
|
|
2295
2614
|
chipSelect
|
|
@@ -2298,7 +2617,7 @@ window.__ModuleLoader__.load({
|
|
|
2298
2617
|
style: mapUi.dangerBtn,
|
|
2299
2618
|
disabled: !pickedIds.length || busy,
|
|
2300
2619
|
onClick: () => runChipBatch("delete"),
|
|
2301
|
-
children: "
|
|
2620
|
+
children: t("remove") + (pickedIds.length ? " " + pickedIds.length : ""),
|
|
2302
2621
|
})
|
|
2303
2622
|
: null,
|
|
2304
2623
|
],
|
|
@@ -2308,10 +2627,10 @@ window.__ModuleLoader__.load({
|
|
|
2308
2627
|
jsx("div", {
|
|
2309
2628
|
style: mapUi.hint,
|
|
2310
2629
|
children: chipSelect
|
|
2311
|
-
? "
|
|
2630
|
+
? t("chipSelectHint")
|
|
2312
2631
|
: clipped
|
|
2313
|
-
? "
|
|
2314
|
-
: "
|
|
2632
|
+
? t("chipClipped")
|
|
2633
|
+
: t("chipPinHint"),
|
|
2315
2634
|
}),
|
|
2316
2635
|
draftOpen
|
|
2317
2636
|
? jsxs("div", {
|
|
@@ -2320,7 +2639,7 @@ window.__ModuleLoader__.load({
|
|
|
2320
2639
|
jsx("textarea", {
|
|
2321
2640
|
value: draftText,
|
|
2322
2641
|
onChange: (e) => setDraftText(e.target.value),
|
|
2323
|
-
placeholder: "
|
|
2642
|
+
placeholder: t("chipDraftPh"),
|
|
2324
2643
|
style: mapUi.draftBox,
|
|
2325
2644
|
}),
|
|
2326
2645
|
jsx("button", {
|
|
@@ -2328,7 +2647,7 @@ window.__ModuleLoader__.load({
|
|
|
2328
2647
|
style: { ...mapUi.actionBtn, alignSelf: "flex-start" },
|
|
2329
2648
|
disabled: busy || !draftText.trim(),
|
|
2330
2649
|
onClick: submitChipDraft,
|
|
2331
|
-
children: "
|
|
2650
|
+
children: t("writePin"),
|
|
2332
2651
|
}),
|
|
2333
2652
|
],
|
|
2334
2653
|
})
|
|
@@ -2367,7 +2686,7 @@ window.__ModuleLoader__.load({
|
|
|
2367
2686
|
checked: pinSet.has(c.id),
|
|
2368
2687
|
onChange: (e) => togglePin(c.id, e.target.checked),
|
|
2369
2688
|
}),
|
|
2370
|
-
c.suggested ? jsx("span", { style: mapUi.suggest, children: "
|
|
2689
|
+
c.suggested ? jsx("span", { style: mapUi.suggest, children: t("suggested") }) : null,
|
|
2371
2690
|
jsx("span", {
|
|
2372
2691
|
style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
2373
2692
|
children: (c.type || "") + " · " + (c.l0 || c.name || "#" + c.id),
|
|
@@ -2381,8 +2700,8 @@ window.__ModuleLoader__.load({
|
|
|
2381
2700
|
: jsx("div", {
|
|
2382
2701
|
style: mapUi.hint,
|
|
2383
2702
|
children: chips.length
|
|
2384
|
-
? "
|
|
2385
|
-
: "
|
|
2703
|
+
? t("noChipMatch")
|
|
2704
|
+
: t("noChips"),
|
|
2386
2705
|
}),
|
|
2387
2706
|
],
|
|
2388
2707
|
})
|
|
@@ -2503,15 +2822,15 @@ window.__ModuleLoader__.load({
|
|
|
2503
2822
|
if (forkDraft?.node) forkBox(forkDraft.node, forkDraft.title);
|
|
2504
2823
|
},
|
|
2505
2824
|
children: [
|
|
2506
|
-
jsx("div", { style: mapUi.dialogTitle, children: "
|
|
2825
|
+
jsx("div", { style: mapUi.dialogTitle, children: t("forkDialog") }),
|
|
2507
2826
|
jsx("div", {
|
|
2508
2827
|
style: mapUi.hint,
|
|
2509
|
-
children: "
|
|
2828
|
+
children: t("forkDialogHint"),
|
|
2510
2829
|
}),
|
|
2511
2830
|
jsx("input", {
|
|
2512
2831
|
autoFocus: true,
|
|
2513
2832
|
value: forkDraft.title,
|
|
2514
|
-
"aria-label": "
|
|
2833
|
+
"aria-label": t("newSessionName"),
|
|
2515
2834
|
onChange: (e) => setForkDraft({ ...forkDraft, title: e.target.value }),
|
|
2516
2835
|
style: { ...mapUi.titleInput, flex: "none", width: "100%" },
|
|
2517
2836
|
}),
|
|
@@ -2522,13 +2841,13 @@ window.__ModuleLoader__.load({
|
|
|
2522
2841
|
type: "button",
|
|
2523
2842
|
style: mapUi.foldBtn,
|
|
2524
2843
|
onClick: () => setForkDraft(null),
|
|
2525
|
-
children: "
|
|
2844
|
+
children: t("cancel"),
|
|
2526
2845
|
}),
|
|
2527
2846
|
jsx("button", {
|
|
2528
2847
|
type: "submit",
|
|
2529
2848
|
style: mapUi.foldBtn,
|
|
2530
2849
|
disabled: busy,
|
|
2531
|
-
children: busy ? "
|
|
2850
|
+
children: busy ? t("forking") : t("fork"),
|
|
2532
2851
|
}),
|
|
2533
2852
|
],
|
|
2534
2853
|
}),
|
|
@@ -2543,6 +2862,19 @@ window.__ModuleLoader__.load({
|
|
|
2543
2862
|
function apply(ctx) {
|
|
2544
2863
|
workspacesList = ctx.workspaces?.list || null;
|
|
2545
2864
|
const sessions = ctx.sessions;
|
|
2865
|
+
try {
|
|
2866
|
+
const snap = ctx.locale && typeof ctx.locale.getLocale === "function" ? ctx.locale.getLocale() : null;
|
|
2867
|
+
if (snap && snap.active) setUiLocale(snap.active);
|
|
2868
|
+
} catch {
|
|
2869
|
+
// host may not expose locale
|
|
2870
|
+
}
|
|
2871
|
+
try {
|
|
2872
|
+
ctx.on("locale/change", (snap) => {
|
|
2873
|
+
if (snap && snap.active) setUiLocale(snap.active);
|
|
2874
|
+
});
|
|
2875
|
+
} catch {
|
|
2876
|
+
// ignore
|
|
2877
|
+
}
|
|
2546
2878
|
ctx.slots.inject(
|
|
2547
2879
|
"settings.section",
|
|
2548
2880
|
() =>
|
|
@@ -2567,7 +2899,7 @@ window.__ModuleLoader__.load({
|
|
|
2567
2899
|
name: "conversation.view",
|
|
2568
2900
|
id: "session-map",
|
|
2569
2901
|
order: 20,
|
|
2570
|
-
label: () => "
|
|
2902
|
+
label: () => t("sessionMap"),
|
|
2571
2903
|
...(chatStore ? { store: chatStore } : {}),
|
|
2572
2904
|
inject: () => ({
|
|
2573
2905
|
fork: (opts) => sessions.fork(opts),
|
|
@@ -2584,7 +2916,7 @@ window.__ModuleLoader__.load({
|
|
|
2584
2916
|
renameSession: async (id, title) => {
|
|
2585
2917
|
const face = sessions.binding?.(id)?.session;
|
|
2586
2918
|
if (!face || typeof face.rename !== "function") {
|
|
2587
|
-
throw new Error("
|
|
2919
|
+
throw new Error(t("noRename"));
|
|
2588
2920
|
}
|
|
2589
2921
|
const result = await face.rename(title);
|
|
2590
2922
|
if (result && result.ok === false) {
|