@stardustlc/dsh-dream 0.7.0 → 0.7.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.
package/lib/client.js CHANGED
@@ -2,15 +2,9 @@
2
2
  // 仓库里没有 src/client.ts,pnpm run build 也不会重写它。改面板请直接改这里,
3
3
  // 改完用 node --check lib/client.js 自检(CI 里也会跑这一步)。
4
4
  //
5
- // 面板做四件事:把梦境日记(dreams.jsonl)画成人能看懂的样子 —— 概览、心境方块、
6
- // 教训榜、按时间倒序的梦卡片;展示与审阅技术经验(<journalDir>/knowledge/);
7
- // 展示「待核实」(candidate/disputed/stale + 被扣下的原因)与「写入记录」
8
- //(<journalDir>/bridge/ 的桥接应用记录)。
9
- // 数据来自插件自己的同源只读路由 GET /_dsh/dsh-dream/journal、/_dsh/dsh-dream/knowledge
10
- // 与 /_dsh/dsh-dream/bridge(由 src/index.ts 挂载);前端不缓存业务数据,刷新即见。
11
- // 经验区块只展示状态 / 条件 / 范围 / 适用与例外 / 证据摘要 / 最近核验;日志截断或坏行会明确提示,
12
- // 不展示权重、向量或原始 JSON。经验行只提供「复制审阅 / 预览命令」,写操作仍在对话工具里执行。
13
- // 隐私纪律:面板只读,不写文件、不上传;「隐私模式」只做显示层模糊,DOM 仍是明文。
5
+ // 日记、来源、审阅、反馈与规则变更共用同一份数据,界面语言跟随 DSH locale。
6
+ // 读取走同源只读路由,写操作走认证 Connection 通道;规则必须预览后应用。
7
+ // 日记、来源原文和人工输入保留原文;「隐私模式」仅模糊显示,DOM 仍是明文。
14
8
  window.__ModuleLoader__.load({ id: "@stardustlc/dsh-dream", factory: (require) => {
15
9
  var module = { exports: {} }; var exports = module.exports;
16
10
  "use strict";
@@ -19,6 +13,313 @@ const React = require("react");
19
13
  const { useState, useEffect } = React;
20
14
  const h = React.createElement;
21
15
 
16
+ // Interface language follows DSH; user content and stored records keep their original text.
17
+ let localeService;
18
+ const EN = Object.freeze({
19
+ "请求失败(HTTP ": "Request failed (HTTP ",
20
+ "服务器返回的不是 JSON(HTTP ": "The server did not return JSON (HTTP ",
21
+ "登录状态已失效,请重新打开 DSH 页面后重试。": "Your session has expired. Reopen DSH and try again.",
22
+ "当前宿主尚未提供此操作,请升级 DSH,或使用下方对话命令。": "This host does not support this action. Update DSH or use the chat command below.",
23
+ "候选(未采纳)": "Candidate (not accepted)",
24
+ "可用": "Usable",
25
+ "有冲突": "Disputed",
26
+ "待复核": "Needs review",
27
+ "已驳回": "Rejected",
28
+ "未知状态": "Unknown state",
29
+ "平台 ": "Platform ",
30
+ ";等 ": "; and ",
31
+ " 条适用范围": " more conditions",
32
+ "范围未标注": "Scope not specified",
33
+ "暂无可展示的证据摘要": "No evidence summary available",
34
+ "尚未核验": "Not verified yet",
35
+ "没有 lastValidatedAt(从未核验)": "No lastValidatedAt (never verified)",
36
+ "未加载的经验(": "Lesson not loaded (",
37
+ "共 ": "Total: ",
38
+ " 条": " records",
39
+ "还没有经验": "No lessons yet",
40
+ "日志过大,仅回放最近一段事件": "The log is large; only recent events were replayed",
41
+ "有 ": "",
42
+ " 行损坏已跳过": " damaged lines were skipped",
43
+ ";events.jsonl 未改动。": "; events.jsonl was preserved.",
44
+ "还没有经验记录": "No lesson records yet",
45
+ "经验是带证据、有适用条件的技术结论:收尾时用 dream_learn 提交候选,证据足够、范围明确的会出现在这里。": "Lessons are technical conclusions with evidence and conditions. Submit a candidate with dream_learn; records with sufficient evidence and a clear scope appear here.",
46
+ "旧日记里的教训不会自动升级为经验;采纳、驳回或标记冲突请用 dream_review。": "Journal observations do not become lessons automatically. Use dream_review to accept, reject or flag conflicts.",
47
+ "已驳回:不参与默认检索与注入": "Rejected: excluded from default retrieval and injection",
48
+ "待复核:条件或依赖可能已过期,默认不注入": "Needs review: conditions or dependencies may be outdated; not injected by default",
49
+ "冲突未解决:默认不注入,先 resolve-conflict": "Unresolved conflict: not injected by default; use resolve-conflict first",
50
+ "候选:可被检索到,但默认不作为任务建议注入": "Candidate: retrievable, but not injected as task advice by default",
51
+ "证据不足:没有独立证据,默认不注入": "Insufficient independent evidence: not injected by default",
52
+ "版本不匹配:当前依赖版本不满足适用范围": "Version mismatch: the dependency version is outside the applicable range",
53
+ "超出条数预算,本次未取回": "Not retrieved: record limit reached",
54
+ "超出字符预算:按排名整条装入,遇到第一条放不下即停": "Character limit reached: complete records are added by rank until the next one cannot fit",
55
+ "超出字符预算,本次未取回": "Not retrieved: character limit reached",
56
+ "新建": "Created",
57
+ "替换管理块": "Replaced managed block",
58
+ "追加管理块": "Appended managed block",
59
+ "未改动": "Unchanged",
60
+ "回滚": "Rolled back",
61
+ "未知动作": "Unknown action",
62
+ "时间未知": "Unknown time",
63
+ "没有记录时间": "No timestamp recorded",
64
+ "目标未记录": "Target not recorded",
65
+ "没有经验引用": "No lesson references",
66
+ "可回滚": "Rollback available",
67
+ "不可回滚": "Rollback unavailable",
68
+ "还没有写入记录": "No rule changes yet",
69
+ "写入记录来自 dream_bridge 的 preview → apply;面板只读展示目标、时间、经验 revision 与前后哈希,不会写文件。": "Changes are recorded by dream_bridge preview → apply. This log shows the target, time, lesson revisions and before/after hashes.",
70
+ "在可桥接经验行点「复制预览命令」,回到对话里执行;写操作只在工具中完成。": "Preview an eligible project lesson, review the changes and apply them. Chat commands are also available.",
71
+ "没有待核实的经验 —— 候选、有冲突、待复核的经验会出现在这里,并写明被扣下的原因。": "Candidates, disputed lessons and lessons needing review appear here, with an explanation of why they are held.",
72
+ "刚刚": "Just now",
73
+ " 分钟前": " min ago",
74
+ " 小时前": " h ago",
75
+ " 天前": " days ago",
76
+ "今天": "Today",
77
+ "昨天": "Yesterday",
78
+ "筛选后 ": "Filtered: ",
79
+ " 场": " dreams",
80
+ "」命中 ": "” matches ",
81
+ " 场梦": " dreams",
82
+ "筛选当前已加载的 ": "Filtering the ",
83
+ " 场 · 命中 ": " loaded dreams · Matches: ",
84
+ "新梦在前": "Newest first",
85
+ "(已达路由上限)": " (route limit reached)",
86
+ "最近 ": "Recent: ",
87
+ " 场 / 共 ": " dreams / Total: ",
88
+ "新梦在前 · 最多显示 ": "Newest first · Up to ",
89
+ "搜索结果 · 最多显示 ": "Search results · Up to ",
90
+ "本页内最近 ": "Most recent on this page: ",
91
+ "(统计未给 lastAt,按本页已加载的 ": "(lastAt is missing; calculated from the ",
92
+ " 场现算)": " dreams loaded on this page)",
93
+ "同一天": "Same day",
94
+ "约 ": "About ",
95
+ " 天": " days",
96
+ "第 1 场梦 · ": "First dream · ",
97
+ " · 心境「": " · Mood: “",
98
+ " 场梦 · 本页最近 ": " dreams · Loaded: ",
99
+ "最近:": "Latest: ",
100
+ "心境": "Mood",
101
+ "1 场梦,先不谈分布 —— 再做梦会累积。": "One dream so far. More dreams will build a mood history.",
102
+ "左 → 右:从早到晚": "Left → right: oldest to newest",
103
+ "教训榜": "Recurring observations",
104
+ "还没有教训 —— 记梦时带上 lessons,出现次数会在这里累积。": "No observations yet. Include lessons when recording dreams to track recurring observations.",
105
+ "常见教训": "Recurring observation",
106
+ "次数仅表示日记里的出现频率;出现 ≥3 次标为「常见教训」。写入项目规则前仍需核验来源并审阅经验。最近时间取自 stats.lastAt;统计缺失时按本页已加载的梦现算。": "Counts show journal frequency only; ≥3 occurrences marks a recurring observation. Verify sources and review lessons before writing project rules. Recency uses stats.lastAt, or the dreams loaded on this page when missing.",
107
+ "复制命令": "Copy command",
108
+ "已复制": "Copied",
109
+ "剪贴板不可用:请手动选中上面的命令复制。": "Clipboard unavailable. Select and copy the command above manually.",
110
+ "重新送审": "Reopen for review",
111
+ "处理冲突": "Resolve conflict",
112
+ "驳回这条": "Reject this lesson",
113
+ "标记待复核": "Mark for review",
114
+ "驳回": "Reject",
115
+ "采纳": "Accept",
116
+ "管理块内容已一致,无需写入。": "The managed block already matches; no write was needed.",
117
+ "请求超时,请先刷新确认结果后再重试。": "Request timed out. Refresh to check the result before retrying.",
118
+ "已更新「": "Updated the review state of “",
119
+ "」的审阅状态。": "”.",
120
+ "已记录使用反馈;「": "Feedback recorded; the review state of “",
121
+ "」的审阅状态保持原样。": "” is unchanged.",
122
+ "的操作": " — actions",
123
+ "收起审阅": "Hide review",
124
+ "审阅详情": "Review details",
125
+ "预览规则变更": "Preview rule changes",
126
+ "复制项目预览命令": "Copy project preview command",
127
+ "正在处理…": "Working…",
128
+ "已刷新经验列表。": "Lesson list refreshed.",
129
+ "刷新列表": "Refresh list",
130
+ "审阅动作": "Review action",
131
+ "冲突经验": "Conflicting lesson",
132
+ "处理方式": "Resolution",
133
+ "采用这条,驳回对方": "Accept this lesson and reject the other",
134
+ "放弃这条,保留对方": "Reject this lesson and keep the other",
135
+ "合并到这条,对方留作历史": "Merge into this lesson and retain the other as history",
136
+ "处理依据": "Reason for resolution",
137
+ "说明哪个来源支持这次决定…": "Explain which source supports this decision…",
138
+ "确认审阅": "Confirm review",
139
+ "在对话里处理": "Handle in chat",
140
+ "选择冲突经验并填写处理依据后即可处理。": "Select the conflicting lesson and provide a reason first.",
141
+ "复制审阅命令": "Copy review command",
142
+ "规则变更预览": "Rule change preview",
143
+ "目标:": "Target: ",
144
+ "管理块内容已经一致,无需更改。": "The managed block already matches; no changes are needed.",
145
+ "将按此预览重建 Dream 管理块,请核对新增和删除内容。块外内容保留,写入前创建回滚备份。": "Check the additions and deletions before rebuilding the Dream managed block. Content outside the block is preserved; a rollback backup is created before writing.",
146
+ "规则已应用,可在「写入记录」中回滚。": "Rules applied. You can roll back from the Rule changes tab.",
147
+ "应用此变更": "Apply these changes",
148
+ "取消预览": "Cancel preview",
149
+ "使用反馈 · 有用 ": "Usage feedback · Useful: ",
150
+ " · 不适用 ": " · Not applicable: ",
151
+ "场景备注(可选)": "Context note (optional)",
152
+ "反馈场景备注": "Feedback context note",
153
+ "这次在哪个场景使用…": "Where did you use this lesson…",
154
+ "这次有用": "Useful this time",
155
+ "这次不适用": "Not applicable this time",
156
+ "记录使用情况,供之后复核;采纳与驳回由审阅决定。": "Feedback records usage for future review. Acceptance and rejection remain review decisions.",
157
+ "有用": "Useful",
158
+ "不适用": "Not applicable",
159
+ "已恢复上一次的 Dream 管理块,块外内容保留。": "The previous Dream managed block was restored; content outside the block was preserved.",
160
+ "取消回滚": "Cancel rollback",
161
+ "回滚这次变更": "Roll back this change",
162
+ "将恢复「": "Restore the previous Dream managed block in “",
163
+ "」的前一个 Dream 管理块。块外内容保留;管理块已被修改时会拒绝回滚。": "”. Content outside the block is preserved. Rollback is refused if the managed block has changed.",
164
+ "正在回滚…": "Rolling back…",
165
+ "确认回滚管理块": "Confirm block rollback",
166
+ "隐私模式:点击此条临时查看": "Privacy mode: click to reveal temporarily",
167
+ "最近核验:": "Last verified: ",
168
+ "何时用:": "When to use: ",
169
+ "未标注适用条件": "No conditions specified",
170
+ "怎么做:": "What to do: ",
171
+ "未标注具体动作": "No action specified",
172
+ "适用范围与证据": "Scope and evidence",
173
+ "范围:": "Scope: ",
174
+ "适用:": "Applies to: ",
175
+ "例外:": "Exceptions: ",
176
+ "证据:": "Evidence: ",
177
+ "没有关联来源记录,采纳前请核对依据。": "No linked sources. Check the evidence before accepting.",
178
+ "来源记录 · ": "Source record · ",
179
+ "已读到来源": "Source read",
180
+ "待核验": "Unverified",
181
+ "会话": "Session",
182
+ "用户纠正": "User correction",
183
+ "本地资料": "Local artifact",
184
+ "未提供摘要": "No summary provided",
185
+ "会话:": "Session: ",
186
+ " · 记录 ": " · Record ",
187
+ "记录时间:": "Recorded: ",
188
+ "核验说明:": "Verification: ",
189
+ "来源校验值:": "Source hash: ",
190
+ "为什么被扣下:": "Why held: ",
191
+ "已审阅经验": "Reviewed lessons",
192
+ "重试": "Retry",
193
+ "读取经验失败:": "Could not load lessons: ",
194
+ "旧日记不受影响;修好后点「重试」。": "Your journal is preserved. Retry after resolving the error.",
195
+ "经验": "Lessons",
196
+ "加载中…": "Loading…",
197
+ "查看 ": "Show ",
198
+ "经验状态": "Lesson state",
199
+ "可用经验": "Usable lessons",
200
+ "已驳回历史": "Rejected history",
201
+ "暂无可用经验;待核实的记录在下方。": "No usable lessons yet. Records needing review are below.",
202
+ "没有已驳回记录。": "No rejected records.",
203
+ "显示更多经验": "Show more lessons",
204
+ "只显示最近 ": "Showing the most recent ",
205
+ " 条经验。": " lessons.",
206
+ "采纳记录仍需满足当前任务的平台、版本与项目条件;来源已读不代表结论永远适用。": "Accepted lessons must still match the platform, version and project of the current task. A source being read does not mean its conclusions always apply.",
207
+ "待核实": "Needs verification",
208
+ " 条待核实": " need verification",
209
+ "没有待核实的经验": "No lessons awaiting verification",
210
+ "显示更多待核实经验": "Show more pending lessons",
211
+ "这些经验默认不会作为任务建议注入。先核对来源与适用范围,再决定采纳、驳回或继续复核。": "These lessons are not injected as task advice by default. Check sources and scope before accepting, rejecting or reviewing further.",
212
+ "写入记录": "Rule changes",
213
+ "读取写入记录失败:": "Could not load rule changes: ",
214
+ " 次写入 · ": " changes · ",
215
+ " 次可回滚": " can be rolled back",
216
+ "经验:": "Lessons: ",
217
+ "前哈希:": "Before hash: ",
218
+ "后哈希:": "After hash: ",
219
+ "备份标识与回滚命令": "Backup ID and rollback command",
220
+ " 条记录。": " records.",
221
+ "回滚恢复对应备份的 Dream 管理块;块外内容保留,外部修改的管理块会停止回滚。": "Rollback restores the backed-up Dream managed block and preserves content outside it. External changes to the block prevent rollback.",
222
+ "隐私模式:点击卡片临时查看": "Privacy mode: click the card to reveal temporarily",
223
+ "心境:": "Mood: ",
224
+ "收起": "Collapse",
225
+ "展开全文": "Read more",
226
+ "没有找到「": "No dreams matching “",
227
+ "」相关的梦": "”",
228
+ "换个关键词试试,或者清空搜索看全部梦境。": "Try another keyword, or clear the search to see all dreams.",
229
+ "清空搜索": "Clear search",
230
+ "还没有梦境日记": "No dreams yet",
231
+ "对 agent 说「做个梦」,它会回放最近的会话、反思之后把第一场梦写进这里。": "Ask your agent to “have a dream”. It will replay recent sessions, reflect and record its first dream here.",
232
+ "对 agent 说「做个梦」": "Ask your agent to “have a dream”",
233
+ "搜索反思与教训…": "Search reflections and observations…",
234
+ "搜索梦境": "Search dreams",
235
+ "筛选作用于已加载窗口,不影响全量统计": "Filters apply to loaded dreams; overall statistics are unchanged",
236
+ "筛选": "Filter",
237
+ "筛选梦境": "Filter dreams",
238
+ " 场,不影响全量统计": " loaded dreams; overall statistics are unchanged",
239
+ "只看有教训的梦": "Only dreams with observations",
240
+ "清空筛选": "Clear filters",
241
+ "完成": "Done",
242
+ "显示层模糊;DOM 仍为明文,不改文件、不加密": "Display blur only; the DOM remains readable. Files are unchanged and not encrypted",
243
+ "隐私模式:开": "Privacy mode: on",
244
+ "隐私模式": "Privacy mode",
245
+ "重新读取梦境日记": "Reload the dream journal",
246
+ "刷新": "Refresh",
247
+ "读取梦境日记失败:": "Could not load the dream journal: ",
248
+ "暂时读不到梦境日记": "The dream journal is unavailable",
249
+ "等宿主路由就绪后点上面的「重试」;如果一直失败,看一眼 DSH 日志里有没有 [dsh-dream] 告警。": "Retry after the host is ready. If the problem persists, check DSH logs for [dsh-dream] warnings.",
250
+ "梦境总数": "Total dreams",
251
+ "本页显示最近 ": "Most recent loaded: ",
252
+ "全部梦境日记": "All dreams",
253
+ "时间跨度": "Time span",
254
+ "最近一场": "Latest dream",
255
+ " 种": " moods",
256
+ "最常见:": "Most common: ",
257
+ "梦境时间线": "Dream timeline",
258
+ "筛选结果": "Filtered results",
259
+ "搜索结果": "Search results",
260
+ "没有符合筛选的梦(筛选作用于已加载的 ": "No matching dreams (filters apply to the ",
261
+ " 场)。": " loaded dreams).",
262
+ "梦境面板视图": "Dream views",
263
+ "日记": "Journal",
264
+ "隐私模式:显示层模糊;DOM 仍为明文,不改文件、不加密。点击卡片可临时查看。": "Privacy mode blurs the display only. The DOM remains readable; files are unchanged and not encrypted. Click a card to reveal it temporarily.",
265
+ "显示更早的梦": "Show older dreams",
266
+ "数据来自梦境日记(dreams.jsonl)": "Data from the dream journal (dreams.jsonl)",
267
+ "梦境日记": "Dream journal",
268
+ "会话回放 → 反思 → 记忆巩固。这里按时间倒序陈列 agent 做过的梦,附心境方块与教训榜。": "Session replay → reflection → memory consolidation. Browse your agent’s dreams, mood history and recurring observations, newest first."
269
+ });
270
+ function languageOf(locale = localeService) {
271
+ try { return String(locale?.getSnapshot?.().active || '').startsWith('en') ? 'en' : 'zh'; } catch { return 'zh'; }
272
+ }
273
+ function t(zh) { return languageOf() === 'en' ? (EN[zh] ?? zh) : zh; }
274
+ function displayError(error) {
275
+ const message = error?.message || String(error || '');
276
+ if (languageOf() !== 'en') return message;
277
+ if (EN[message]) return EN[message];
278
+ if (!/[\u3400-\u9fff]/.test(message)) return message;
279
+ const hints = {
280
+ revision: 'This lesson or project changed. Refresh and review again; your input is preserved.',
281
+ conflict: 'The target changed. Preview the current rules again before applying.',
282
+ 'preview-mismatch': 'The preview no longer matches. Create a new preview.',
283
+ 'preview-expired': 'The preview expired. Create a new preview.',
284
+ 'not-rollbackable': 'Rollback is unavailable because the managed block changed or its backup is missing.',
285
+ 'ineligible': 'This lesson is not eligible for rule writing. Check its scope and review state.',
286
+ 'read-only': 'The target is read-only. Check its permissions before retrying.',
287
+ 'marker': 'The managed block markers are invalid. Check the file before retrying.',
288
+ 'path-outside-project': 'The target must stay inside the project directory.',
289
+ 'unsupported-encoding': 'The target file encoding is unsupported. Use UTF-8.',
290
+ 'target-is-directory': 'The selected target is a directory; select a file.',
291
+ 'not-found': 'The lesson, target or backup was not found. Refresh and check the project.',
292
+ forbidden: 'This operation must be performed from the authenticated Dream panel.',
293
+ 'too-large': 'The request is too large. Shorten the input and try again.',
294
+ cancelled: 'The operation was cancelled. Refresh to check the result before retrying.',
295
+ io: 'The operation could not access its files. Check the project location and file permissions, then refresh before retrying.',
296
+ internal: 'Could not load the records. Check the DSH logs and retry.',
297
+ invalid: 'The input or selected record is no longer valid. Check the fields and refresh the list.',
298
+ };
299
+ return (hints[error?.code] || 'The operation failed. Check the DSH logs, then refresh before retrying.')
300
+ + (error?.status ? ' (HTTP ' + error.status + (error.code ? ', ' + error.code : '') + ')' : '');
301
+ }
302
+ function rollbackReasonText(reason) {
303
+ if (languageOf() !== 'en') return reason;
304
+ return ({ '目标文件不可读': 'Target file is unreadable', '文件不存在': 'File does not exist',
305
+ '管理块标记异常': 'Invalid managed block markers', '管理块已被移除': 'Managed block was removed',
306
+ '管理块已被外部修改': 'Managed block changed externally' })[reason]
307
+ || (/[\u3400-\u9fff]/.test(reason) ? 'Check the target file and backup' : reason);
308
+ }
309
+ function scopeText(label) {
310
+ if (languageOf() !== 'en') return label;
311
+ return label.replace(/^全局$/, 'Global').replace(/^工作区 /, 'Workspace ').replace(/^项目 /, 'Project ');
312
+ }
313
+ function evidenceText(summary) {
314
+ if (languageOf() !== 'en') return summary;
315
+ return summary.replace(/^独立证据 (\d+) 条(已读 (\d+) \/ 仅声明 (\d+)):/, 'Independent sources: $1 (read: $2 / claimed: $3): ');
316
+ }
317
+ function useLanguage(locale) {
318
+ const [, rerender] = useState(0);
319
+ useEffect(() => { const update = () => rerender(n => n + 1); update(); return locale?.subscribe?.(update); }, [locale]);
320
+ }
321
+
322
+
22
323
  // 相对路径按 document.baseURI 解析:反代挂在 /tools/dsh/ 这类前缀下也不会打偏。
23
324
  const ROUTE = "_dsh/dsh-dream/journal";
24
325
  const ACTION_ROUTE = "api/dsh-dream/actions";
@@ -74,7 +375,7 @@ function errorMessageOf(body, status) {
74
375
  if (body.error !== null && typeof body.error === "object" && typeof body.error.message === "string" && body.error.message !== "") return body.error.message;
75
376
  if (typeof body.message === "string" && body.message !== "") return body.message;
76
377
  }
77
- return "请求失败(HTTP " + String(status) + ")";
378
+ return t("请求失败(HTTP ") + String(status) + ")";
78
379
  }
79
380
 
80
381
  /** GET /_dsh/dsh-dream/journal:契约体 { dreams, stats, query, limit }。 */
@@ -87,7 +388,7 @@ async function fetchJournal(query, limit) {
87
388
  const res = await fetch(routeUrl(params.join("&")), init);
88
389
  let body = null;
89
390
  try { body = await res.json(); } catch (error) { body = null; }
90
- if (body === null || typeof body !== "object") throw new Error("服务器返回的不是 JSON(HTTP " + String(res.status) + ")");
391
+ if (body === null || typeof body !== "object") throw new Error(t("服务器返回的不是 JSON(HTTP ") + String(res.status) + ")");
91
392
  if (res.ok !== true || body.ok === false) {
92
393
  const failure = new Error(errorMessageOf(body, res.status));
93
394
  failure.status = res.status;
@@ -111,7 +412,7 @@ async function fetchKnowledge() {
111
412
  const res = await fetch(routeUrlFor(KNOWLEDGE_ROUTE, ""), init);
112
413
  let body = null;
113
414
  try { body = await res.json(); } catch (error) { body = null; }
114
- if (body === null || typeof body !== "object") throw new Error("服务器返回的不是 JSON(HTTP " + String(res.status) + ")");
415
+ if (body === null || typeof body !== "object") throw new Error(t("服务器返回的不是 JSON(HTTP ") + String(res.status) + ")");
115
416
  if (res.ok !== true || body.ok === false) {
116
417
  const failure = new Error(errorMessageOf(body, res.status));
117
418
  failure.status = res.status;
@@ -134,7 +435,7 @@ async function fetchBridgeRecords() {
134
435
  const res = await fetch(routeUrlFor(BRIDGE_ROUTE, ""), init);
135
436
  let body = null;
136
437
  try { body = await res.json(); } catch (error) { body = null; }
137
- if (body === null || typeof body !== "object") throw new Error("服务器返回的不是 JSON(HTTP " + String(res.status) + ")");
438
+ if (body === null || typeof body !== "object") throw new Error(t("服务器返回的不是 JSON(HTTP ") + String(res.status) + ")");
138
439
  if (res.ok !== true || body.ok === false) {
139
440
  const failure = new Error(errorMessageOf(body, res.status));
140
441
  failure.status = res.status;
@@ -155,8 +456,8 @@ async function postAction(body) {
155
456
  let value = null;
156
457
  try { value = await res.json(); } catch (error) { /* Preserve HTTP status for useful recovery hints. */ }
157
458
  if (res.ok !== true || value === null || value.ok === false) {
158
- const hint = res.status === 401 ? "登录状态已失效,请重新打开 DSH 页面后重试。"
159
- : res.status === 404 ? "当前宿主尚未提供此操作,请升级 DSH,或使用下方对话命令。"
459
+ const hint = res.status === 401 ? t("登录状态已失效,请重新打开 DSH 页面后重试。")
460
+ : res.status === 404 ? t("当前宿主尚未提供此操作,请升级 DSH,或使用下方对话命令。")
160
461
  : errorMessageOf(value, res.status);
161
462
  const failure = new Error(hint);
162
463
  failure.code = value && value.error ? value.error.code : undefined;
@@ -257,12 +558,12 @@ function normalizeLessonState(value) {
257
558
  /** 状态 → 中文标签(面板文案集中在这里)。 */
258
559
  function lessonStateLabel(state) {
259
560
  switch (state) {
260
- case "candidate": return "候选(未采纳)";
261
- case "usable": return "可用";
262
- case "disputed": return "有冲突";
263
- case "stale": return "待复核";
264
- case "rejected": return "已驳回";
265
- default: return "未知状态";
561
+ case "candidate": return t("候选(未采纳)");
562
+ case "usable": return t("可用");
563
+ case "disputed": return t("有冲突");
564
+ case "stale": return t("待复核");
565
+ case "rejected": return t("已驳回");
566
+ default: return t("未知状态");
266
567
  }
267
568
  }
268
569
 
@@ -295,11 +596,11 @@ function applicabilityTextOf(value) {
295
596
  const main = pkg !== "" ? (versions !== "" ? pkg + " " + versions : pkg) : versions;
296
597
  const bits = [];
297
598
  if (main !== "") bits.push(main);
298
- if (platform !== "") bits.push("平台 " + platform);
599
+ if (platform !== "") bits.push(t("平台 ") + platform);
299
600
  if (bits.length > 0) parts.push(bits.join(" · "));
300
601
  }
301
602
  if (parts.length === 0) return "";
302
- if (parts.length > 4) return parts.slice(0, 4).join(";") + ";等 " + String(parts.length) + " 条适用范围";
603
+ if (parts.length > 4) return parts.slice(0, 4).join(";") + t(";等 ") + String(parts.length) + t(" 条适用范围");
303
604
  return parts.join(";");
304
605
  }
305
606
 
@@ -343,22 +644,22 @@ function normalizeKnowledge(value, now, limit) {
343
644
  state: state,
344
645
  stateLabel: lessonStateLabel(state),
345
646
  stateClass: lessonStateClass(state),
346
- scopeLabel: trimmedText(raw.scopeLabel) || "范围未标注",
347
- evidenceSummary: trimmedText(raw.evidenceSummary) || "暂无可展示的证据摘要",
647
+ scopeLabel: scopeText(trimmedText(raw.scopeLabel)) || t("范围未标注"),
648
+ evidenceSummary: evidenceText(trimmedText(raw.evidenceSummary)) || t("暂无可展示的证据摘要"),
348
649
  evidence: Array.isArray(source.evidence) && Array.isArray(raw.evidenceIds)
349
650
  ? source.evidence.filter(function (item) { return item && raw.evidenceIds.indexOf(item.id) !== -1; }).slice(0, 20) : [],
350
651
  feedback: raw.feedback && typeof raw.feedback === "object" ? raw.feedback : { useful: 0, notApplicable: 0, recent: [] },
351
652
  lastValidatedAt: lastValidatedAt,
352
- lastText: lastValidatedAt === "" ? "尚未核验" : relativeTime(lastValidatedAt, now),
353
- lastTitle: lastValidatedAt === "" ? "没有 lastValidatedAt(从未核验)" : formatDate(lastValidatedAt) + (formatTime(lastValidatedAt) === "" ? "" : " " + formatTime(lastValidatedAt)),
653
+ lastText: lastValidatedAt === "" ? t("尚未核验") : relativeTime(lastValidatedAt, now),
654
+ lastTitle: lastValidatedAt === "" ? t("没有 lastValidatedAt(从未核验)") : formatDate(lastValidatedAt) + (formatTime(lastValidatedAt) === "" ? "" : " " + formatTime(lastValidatedAt)),
354
655
  revision: typeof raw.revision === "number" && Number.isFinite(raw.revision) && raw.revision >= 0 ? Math.floor(raw.revision) : 0,
355
656
  independentSupportCount: typeof raw.independentSupportCount === "number" && raw.independentSupportCount > 0 ? Math.floor(raw.independentSupportCount) : 0,
356
657
  projectId: typeof raw.scope === "object" && raw.scope !== null ? trimmedText(raw.scope.projectId) : "",
357
658
  workspaceRoot: typeof raw.scope === "object" && raw.scope !== null ? trimmedText(raw.scope.workspaceRoot) : "",
358
659
  conflictIds: Array.isArray(raw.conflictIds) ? raw.conflictIds.filter(function (id) { return typeof id === "string" && id.trim() !== ""; }) : [],
359
- conflicts: Array.isArray(raw.conflictIds) ? raw.conflictIds.filter(function (id) { return typeof id === "string" && id.trim() !== ""; }).map(function (id) { return { id: id, title: lessonTitles.get(id) || "未加载的经验(" + id + ")" }; }) : [],
660
+ conflicts: Array.isArray(raw.conflictIds) ? raw.conflictIds.filter(function (id) { return typeof id === "string" && id.trim() !== ""; }).map(function (id) { return { id: id, title: lessonTitles.get(id) || t("未加载的经验(") + id + ")" }; }) : [],
360
661
  holdReason: trimmedText(raw.holdReason) !== "" ? trimmedText(raw.holdReason) : holdReasonOf(raw),
361
- holdReasonLabel: trimmedText(raw.holdReasonLabel) !== "" ? trimmedText(raw.holdReasonLabel) : skippedReasonLabel(holdReasonOf(raw)),
662
+ holdReasonLabel: trimmedText(raw.holdReasonLabel) !== "" ? t(trimmedText(raw.holdReasonLabel)) : skippedReasonLabel(holdReasonOf(raw)),
362
663
  bridgeable: bridgeableOf(raw.state) && trimmedText(raw.id) !== "",
363
664
  candidate: state === "candidate",
364
665
  disputed: state === "disputed",
@@ -387,7 +688,7 @@ function knowledgeCountText(stats) {
387
688
  }
388
689
  if (parts.length === 0) {
389
690
  const total = typeof source.lessons === "number" && source.lessons > 0 ? source.lessons : 0;
390
- return total > 0 ? "共 " + String(total) + " 条" : "还没有经验";
691
+ return total > 0 ? t("共 ") + String(total) + t(" 条") : t("还没有经验");
391
692
  }
392
693
  return parts.join(" · ");
393
694
  }
@@ -399,32 +700,32 @@ function knowledgeNoticeText(stats) {
399
700
  const badLines = typeof source.badLines === "number" && Number.isFinite(source.badLines) && source.badLines > 0 ? Math.floor(source.badLines) : 0;
400
701
  if (truncated !== true && badLines === 0) return "";
401
702
  const parts = [];
402
- if (truncated === true) parts.push("日志过大,仅回放最近一段事件");
403
- if (badLines > 0) parts.push("有 " + String(badLines) + " 行损坏已跳过");
404
- return parts.join(";") + ";events.jsonl 未改动。";
703
+ if (truncated === true) parts.push(t("日志过大,仅回放最近一段事件"));
704
+ if (badLines > 0) parts.push(t("有 ") + String(badLines) + t(" 行损坏已跳过"));
705
+ return parts.join(";") + t(";events.jsonl 未改动。");
405
706
  }
406
707
 
407
708
  /** 经验区块的独立空态:与「还没有梦境日记」分开,说明经验从哪来。 */
408
709
  function knowledgeEmptyState() {
409
710
  return {
410
- title: "还没有经验记录",
411
- body: "经验是带证据、有适用条件的技术结论:收尾时用 dream_learn 提交候选,证据足够、范围明确的会出现在这里。",
412
- hint: "旧日记里的教训不会自动升级为经验;采纳、驳回或标记冲突请用 dream_review。",
711
+ title: t("还没有经验记录"),
712
+ body: t("经验是带证据、有适用条件的技术结论:收尾时用 dream_learn 提交候选,证据足够、范围明确的会出现在这里。"),
713
+ hint: t("旧日记里的教训不会自动升级为经验;采纳、驳回或标记冲突请用 dream_review。"),
413
714
  };
414
715
  }
415
716
 
416
717
  /** 检索层 skipped reason 的中文映射(面板扣留原因文案;与 retrieval.ts 的 reason 常量同名)。 */
417
718
  function skippedReasonLabel(reason) {
418
719
  switch (trimmedText(reason)) {
419
- case "state:rejected": return "已驳回:不参与默认检索与注入";
420
- case "state:stale": return "待复核:条件或依赖可能已过期,默认不注入";
421
- case "state:disputed": return "冲突未解决:默认不注入,先 resolve-conflict";
422
- case "candidate-hold": return "候选:可被检索到,但默认不作为任务建议注入";
423
- case "no-evidence": return "证据不足:没有独立证据,默认不注入";
424
- case "version-mismatch": return "版本不匹配:当前依赖版本不满足适用范围";
425
- case "limit": return "超出条数预算,本次未取回";
426
- case "budget-stop": return "超出字符预算:按排名整条装入,遇到第一条放不下即停";
427
- case "budget": return "超出字符预算,本次未取回";
720
+ case "state:rejected": return t("已驳回:不参与默认检索与注入");
721
+ case "state:stale": return t("待复核:条件或依赖可能已过期,默认不注入");
722
+ case "state:disputed": return t("冲突未解决:默认不注入,先 resolve-conflict");
723
+ case "candidate-hold": return t("候选:可被检索到,但默认不作为任务建议注入");
724
+ case "no-evidence": return t("证据不足:没有独立证据,默认不注入");
725
+ case "version-mismatch": return t("版本不匹配:当前依赖版本不满足适用范围");
726
+ case "limit": return t("超出条数预算,本次未取回");
727
+ case "budget-stop": return t("超出字符预算:按排名整条装入,遇到第一条放不下即停");
728
+ case "budget": return t("超出字符预算,本次未取回");
428
729
  default: return "";
429
730
  }
430
731
  }
@@ -506,12 +807,12 @@ function shortHash(value) {
506
807
 
507
808
  function bridgeActionLabel(action) {
508
809
  switch (trimmedText(action)) {
509
- case "create": return "新建";
510
- case "replace": return "替换管理块";
511
- case "append": return "追加管理块";
512
- case "unchanged": return "未改动";
513
- case "rollback": return "回滚";
514
- default: return trimmedText(action) || "未知动作";
810
+ case "create": return t("新建");
811
+ case "replace": return t("替换管理块");
812
+ case "append": return t("追加管理块");
813
+ case "unchanged": return t("未改动");
814
+ case "rollback": return t("回滚");
815
+ default: return trimmedText(action) || t("未知动作");
515
816
  }
516
817
  }
517
818
 
@@ -549,17 +850,17 @@ function normalizeBridgeRecords(value, now, limit) {
549
850
  rows.push({
550
851
  id: id,
551
852
  at: at,
552
- atText: at === "" ? "时间未知" : relativeTime(at, now),
553
- atTitle: at === "" ? "没有记录时间" : formatDate(at) + (formatTime(at) === "" ? "" : " " + formatTime(at)),
554
- target: target || "目标未记录",
853
+ atText: at === "" ? t("时间未知") : relativeTime(at, now),
854
+ atTitle: at === "" ? t("没有记录时间") : formatDate(at) + (formatTime(at) === "" ? "" : " " + formatTime(at)),
855
+ target: target || t("目标未记录"),
555
856
  action: trimmedText(raw.action),
556
857
  actionLabel: bridgeActionLabel(raw.action),
557
858
  lessons: lessonRefs,
558
- lessonsText: lessonRefs.length === 0 ? "没有经验引用" : lessonRefs.join("、"),
859
+ lessonsText: lessonRefs.length === 0 ? t("没有经验引用") : lessonRefs.join("、"),
559
860
  beforeShort: shortHash(raw.beforeSha256),
560
861
  afterShort: shortHash(raw.afterSha256),
561
862
  rollbackable: rollbackable,
562
- rollbackText: rollbackable ? "可回滚" : ("不可回滚" + (rollbackReason !== "" ? ":" + rollbackReason : "")),
863
+ rollbackText: rollbackable ? t("可回滚") : (t("不可回滚") + (rollbackReason !== "" ? (languageOf() === 'en' ? ': ' : ':') + rollbackReasonText(rollbackReason) : "")),
563
864
  rollbackReason: rollbackReason,
564
865
  });
565
866
  }
@@ -576,15 +877,15 @@ function normalizeBridgeRecords(value, now, limit) {
576
877
  /** 「写入记录」的独立空态。 */
577
878
  function bridgeEmptyState() {
578
879
  return {
579
- title: "还没有写入记录",
580
- body: "写入记录来自 dream_bridge 的 preview → apply;面板只读展示目标、时间、经验 revision 与前后哈希,不会写文件。",
581
- hint: "在可桥接经验行点「复制预览命令」,回到对话里执行;写操作只在工具中完成。",
880
+ title: t("还没有写入记录"),
881
+ body: t("写入记录来自 dream_bridge 的 preview → apply;面板只读展示目标、时间、经验 revision 与前后哈希,不会写文件。"),
882
+ hint: t("在可桥接经验行点「复制预览命令」,回到对话里执行;写操作只在工具中完成。"),
582
883
  };
583
884
  }
584
885
 
585
886
  /** 「待核实」的独立空态。 */
586
887
  function reviewQueueEmptyState() {
587
- return "没有待核实的经验 —— 候选、有冲突、待复核的经验会出现在这里,并写明被扣下的原因。";
888
+ return t("没有待核实的经验 —— 候选、有冲突、待复核的经验会出现在这里,并写明被扣下的原因。");
588
889
  }
589
890
 
590
891
  function spanOf(dreams) {
@@ -602,7 +903,7 @@ function spanOf(dreams) {
602
903
 
603
904
  function formatDate(iso) {
604
905
  const date = new Date(iso);
605
- if (!Number.isFinite(date.getTime())) return iso === "" ? "时间未知" : iso;
906
+ if (!Number.isFinite(date.getTime())) return iso === "" ? t("时间未知") : iso;
606
907
  return date.getFullYear() + "-" + pad2(date.getMonth() + 1) + "-" + pad2(date.getDate());
607
908
  }
608
909
 
@@ -633,10 +934,10 @@ function relativeTime(iso, now) {
633
934
  const minute = 60000;
634
935
  const hour = 3600000;
635
936
  const day = 86400000;
636
- if (diff < minute) return "刚刚";
637
- if (diff < hour) return Math.floor(diff / minute) + " 分钟前";
638
- if (diff < day) return Math.floor(diff / hour) + " 小时前";
639
- if (diff < day * 30) return Math.floor(diff / day) + " 天前";
937
+ if (diff < minute) return t("刚刚");
938
+ if (diff < hour) return Math.floor(diff / minute) + t(" 分钟前");
939
+ if (diff < day) return Math.floor(diff / hour) + t(" 小时前");
940
+ if (diff < day * 30) return Math.floor(diff / day) + t(" 天前");
640
941
  return formatDate(iso);
641
942
  }
642
943
 
@@ -648,9 +949,9 @@ function recentText(iso, now) {
648
949
  const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
649
950
  const that = new Date(date.getFullYear(), date.getMonth(), date.getDate());
650
951
  const days = Math.round((today.getTime() - that.getTime()) / 86400000);
651
- if (days <= 0) return "今天";
652
- if (days === 1) return "昨天";
653
- if (days < 30) return String(days) + " 天前";
952
+ if (days <= 0) return t("今天");
953
+ if (days === 1) return t("昨天");
954
+ if (days < 30) return String(days) + t(" 天前");
654
955
  return formatDate(iso);
655
956
  }
656
957
 
@@ -744,23 +1045,23 @@ function needsOlderDreams(loaded, total) {
744
1045
  }
745
1046
 
746
1047
  function countTextOf(query, total, count, filterCount, visibleCount) {
747
- if (filterCount > 0) return "筛选后 " + String(visibleCount) + " / " + String(count) + " 场";
748
- if (query !== "") return "「" + query + "」命中 " + String(count) + " 场";
749
- return "共 " + String(total) + " 场梦";
1048
+ if (filterCount > 0) return t("筛选后 ") + String(visibleCount) + " / " + String(count) + t(" 场");
1049
+ if (query !== "") return (languageOf() === "en" ? "“" : "「") + query + t("」命中 ") + String(count) + t(" 场");
1050
+ return t("共 ") + String(total) + t(" 场梦");
750
1051
  }
751
1052
 
752
1053
  function listHintOf(query, count, filterCount, visibleCount) {
753
- if (filterCount > 0) return "筛选当前已加载的 " + String(count) + " 场 · 命中 " + String(visibleCount) + " 场";
754
- if (query !== "") return "「" + query + "」命中 " + String(count) + " 场";
755
- return "新梦在前";
1054
+ if (filterCount > 0) return t("筛选当前已加载的 ") + String(count) + t(" 场 · 命中 ") + String(visibleCount) + t(" 场");
1055
+ if (query !== "") return (languageOf() === "en" ? "“" : "「") + query + t("」命中 ") + String(count) + t(" 场");
1056
+ return t("新梦在前");
756
1057
  }
757
1058
 
758
1059
  function footerTextOf(query, limit, total, count) {
759
1060
  if (limit > DEFAULT_LIMIT) {
760
- const capped = limit >= MORE_LIMIT && count < total ? "(已达路由上限)" : "";
761
- return "最近 " + String(limit) + " 场 / 共 " + String(total) + " 场" + capped;
1061
+ const capped = limit >= MORE_LIMIT && count < total ? t("(已达路由上限)") : "";
1062
+ return t("最近 ") + String(limit) + t(" 场 / 共 ") + String(total) + t(" 场") + capped;
762
1063
  }
763
- return query === "" ? "新梦在前 · 最多显示 " + String(DEFAULT_LIMIT) + " 场" : "搜索结果 · 最多显示 " + String(DEFAULT_LIMIT) + " 场";
1064
+ return query === "" ? t("新梦在前 · 最多显示 ") + String(DEFAULT_LIMIT) + t(" 场") : t("搜索结果 · 最多显示 ") + String(DEFAULT_LIMIT) + t(" 场");
764
1065
  }
765
1066
 
766
1067
  /** 面板的视图模型:所有文案/数字集中在这里算,组件只负责画。 */
@@ -784,15 +1085,15 @@ function viewModelOf(dreams, stats, now) {
784
1085
  share: Math.max(6, Math.round(row.count / Math.max(lessonTotal, 1) * 100)),
785
1086
  showBar: row.count >= 2,
786
1087
  candidate: row.count >= 3,
787
- lastText: knownAt === "" ? "" : (fromStats ? "最近 " : "本页内最近 ") + recentText(knownAt, now),
1088
+ lastText: knownAt === "" ? "" : (fromStats ? t("最近 ") : t("本页内最近 ")) + recentText(knownAt, now),
788
1089
  lastTitle: knownAt === "" ? "" : formatDate(knownAt) + (formatTime(knownAt) === "" ? "" : " " + formatTime(knownAt))
789
- + (fromStats ? "" : "(统计未给 lastAt,按本页已加载的 " + String(list.length) + " 场现算)"),
1090
+ + (fromStats ? "" : t("(统计未给 lastAt,按本页已加载的 ") + String(list.length) + t(" 场现算)")),
790
1091
  };
791
1092
  });
792
1093
  const span = spanOf(list);
793
1094
  const spanText = span === null ? "" : spanTextOf(span);
794
1095
  const spanFull = span === null ? "" : formatDate(new Date(span.from).toISOString()) + " → " + formatDate(new Date(span.to).toISOString());
795
- const spanHint = span === null ? "" : (span.days === 0 ? "同一天" : "约 " + String(span.days) + " 天");
1096
+ const spanHint = span === null ? "" : (span.days === 0 ? t("同一天") : t("约 ") + String(span.days) + t(" 天"));
796
1097
  const blocks = list.slice().reverse().map((dream, index) => ({
797
1098
  key: "block-" + (dream.id !== "" ? dream.id : "no-id") + "-" + String(index),
798
1099
  tone: toneClass(moodSlot(dream.mood)),
@@ -823,12 +1124,12 @@ function headlineOf(dreams, total, span, now) {
823
1124
  if (dreams.length === 0) return "";
824
1125
  if (dreams.length === 1 && total <= 1) {
825
1126
  const dream = dreams[0];
826
- return "第 1 场梦 · " + formatDate(dream.at) + " · 心境「" + dream.mood + "」";
1127
+ return t("第 1 场梦 · ") + formatDate(dream.at) + t(" · 心境「") + dream.mood + (languageOf() === "en" ? "”" : "」");
827
1128
  }
828
1129
  const parts = [];
829
- parts.push(total > dreams.length ? "共 " + String(total) + " 场梦 · 本页最近 " + String(dreams.length) + " 场" : String(dreams.length) + " 场梦");
1130
+ parts.push(total > dreams.length ? t("共 ") + String(total) + t(" 场梦 · 本页最近 ") + String(dreams.length) + t(" 场") : String(dreams.length) + t(" 场梦"));
830
1131
  if (span !== null) parts.push(span.from === span.to ? formatDate(new Date(span.from).toISOString()) : formatDate(new Date(span.from).toISOString()) + " → " + formatDate(new Date(span.to).toISOString()));
831
- parts.push("最近:" + relativeTime(dreams[0].at, now));
1132
+ parts.push(t("最近:") + relativeTime(dreams[0].at, now));
832
1133
  return parts.join(" · ");
833
1134
  }
834
1135
 
@@ -873,20 +1174,20 @@ function StatTile(props) {
873
1174
  function MoodCard(props) {
874
1175
  const vm = props.vm;
875
1176
  return h("section", { className: "dshd-card" }, [
876
- h("h4", { key: "t" }, "心境"),
1177
+ h("h4", { key: "t" }, t("心境")),
877
1178
  h("div", { className: "dshd-mblocks", key: "blocks" }, vm.blocks.map((block) => h("span", {
878
1179
  key: block.key,
879
1180
  className: "dshd-mblock " + block.tone,
880
1181
  title: block.title,
881
1182
  }))),
882
1183
  vm.count === 1
883
- ? h("div", { className: "dshd-hint", key: "one" }, "1 场梦,先不谈分布 —— 再做梦会累积。")
1184
+ ? h("div", { className: "dshd-hint", key: "one" }, t("1 场梦,先不谈分布 —— 再做梦会累积。"))
884
1185
  : h("div", { key: "legend" }, [
885
- h("div", { className: "dshd-note", key: "cap" }, "左 → 右:从早到晚"),
1186
+ h("div", { className: "dshd-note", key: "cap" }, t("左 → 右:从早到晚")),
886
1187
  h("div", { className: "dshd-legend", key: "rows" }, vm.moodRows.map((row, index) => h("div", { className: "dshd-legend-row", key: "mood-" + String(index) + "-" + row.mood }, [
887
1188
  h("span", { className: "dshd-mblock " + toneClass(moodSlot(row.mood)), key: "d" }),
888
1189
  h("span", { className: "dshd-legend-name", key: "n", title: row.mood }, row.mood),
889
- h("span", { className: "dshd-legend-count", key: "c" }, String(row.count) + " 场"),
1190
+ h("span", { className: "dshd-legend-count", key: "c" }, String(row.count) + t(" 场")),
890
1191
  ]))),
891
1192
  ]),
892
1193
  ]);
@@ -895,9 +1196,9 @@ function MoodCard(props) {
895
1196
  function LessonCard(props) {
896
1197
  const vm = props.vm;
897
1198
  return h("section", { className: "dshd-card" }, [
898
- h("h4", { key: "t" }, "教训榜"),
1199
+ h("h4", { key: "t" }, t("教训榜")),
899
1200
  vm.lessonRows.length === 0
900
- ? h("div", { className: "dshd-hint", key: "e" }, "还没有教训 —— 记梦时带上 lessons,出现次数会在这里累积。")
1201
+ ? h("div", { className: "dshd-hint", key: "e" }, t("还没有教训 —— 记梦时带上 lessons,出现次数会在这里累积。"))
901
1202
  : h("div", { key: "rows" }, vm.lessonRows.map((row, index) => h("div", { className: "dshd-lesson", key: "lesson-" + String(index) + "-" + row.lesson }, [
902
1203
  h("span", { className: "dshd-rank", key: "r" }, String(row.rank)),
903
1204
  h("span", { className: "dshd-lesson-body", key: "b" }, [
@@ -907,10 +1208,10 @@ function LessonCard(props) {
907
1208
  h("span", { className: "dshd-lesson-meta", key: "m" }, [
908
1209
  h("span", { className: "dshd-times", key: "c" }, "×", h("b", { key: "n" }, String(row.count))),
909
1210
  row.lastText === "" ? null : h("span", { className: "dshd-last", key: "l", title: row.lastTitle }, row.lastText),
910
- row.candidate ? h("span", { className: "dshd-badge", key: "badge" }, "常见教训") : null,
1211
+ row.candidate ? h("span", { className: "dshd-badge", key: "badge" }, t("常见教训")) : null,
911
1212
  ]),
912
1213
  ]))),
913
- h("div", { className: "dshd-note", key: "note" }, "次数仅表示日记里的出现频率;出现 ≥3 次标为「常见教训」。写入项目规则前仍需核验来源并审阅经验。最近时间取自 stats.lastAt;统计缺失时按本页已加载的梦现算。"),
1214
+ h("div", { className: "dshd-note", key: "note" }, t("次数仅表示日记里的出现频率;出现 ≥3 次标为「常见教训」。写入项目规则前仍需核验来源并审阅经验。最近时间取自 stats.lastAt;统计缺失时按本页已加载的梦现算。")),
914
1215
  ]);
915
1216
  }
916
1217
 
@@ -926,11 +1227,11 @@ function CopyCommand(props) {
926
1227
  setFailed(ok !== true);
927
1228
  });
928
1229
  };
929
- const label = props.label === undefined ? "复制命令" : props.label;
1230
+ const label = props.label === undefined ? t("复制命令") : props.label;
930
1231
  return h("span", { className: "dshd-copywrap" }, [
931
- h(Btn, { key: "b", onClick: onCopy, disabled: props.disabled === true, title: props.title === undefined ? command : props.title }, copied === true ? "已复制" : label),
1232
+ h(Btn, { key: "b", onClick: onCopy, disabled: props.disabled === true, title: props.title === undefined ? command : props.title }, copied === true ? t("已复制") : label),
932
1233
  failed === true ? h("code", { key: "cmd", className: "dshd-cmd", tabIndex: 0 }, command) : null,
933
- failed === true ? h("span", { key: "hint", className: "dshd-note" }, "剪贴板不可用:请手动选中上面的命令复制。") : null,
1234
+ failed === true ? h("span", { key: "hint", className: "dshd-note" }, t("剪贴板不可用:请手动选中上面的命令复制。")) : null,
934
1235
  ]);
935
1236
  }
936
1237
 
@@ -947,10 +1248,10 @@ function ReviewControls(props) {
947
1248
  const [pending, setPending] = useState(false);
948
1249
  const [error, setError] = useState("");
949
1250
  const [preview, setPreview] = useState(null);
950
- const commands = row.state === "rejected" ? [["reopen", "重新送审"]]
951
- : row.state === "disputed" ? [["resolve-conflict", "处理冲突"], ["reject", "驳回这条"]]
952
- : row.state === "usable" ? [["mark-stale", "标记待复核"], ["reject", "驳回"]]
953
- : [["accept", "采纳"], ["reject", "驳回"], ["mark-stale", "标记待复核"]];
1251
+ const commands = row.state === "rejected" ? [["reopen", t("重新送审")]]
1252
+ : row.state === "disputed" ? [["resolve-conflict", t("处理冲突")], ["reject", t("驳回这条")]]
1253
+ : row.state === "usable" ? [["mark-stale", t("标记待复核")], ["reject", t("驳回")]]
1254
+ : [["accept", t("采纳")], ["reject", t("驳回")], ["mark-stale", t("标记待复核")]];
954
1255
  const effectiveAction = commands.some(function (item) { return item[0] === action; }) ? action : commands[0][0];
955
1256
  const command = reviewCommandOf(row, { action: effectiveAction, resolution: resolution, note: note, affectedIds: affected === "" ? [] : [affected] });
956
1257
  const submit = async function (body, message, isPreview) {
@@ -959,65 +1260,65 @@ function ReviewControls(props) {
959
1260
  try {
960
1261
  const result = await postAction(body);
961
1262
  if (isPreview) setPreview(result);
962
- else { setPreview(null); if (props.onChanged) props.onChanged(result.unchanged === true ? "管理块内容已一致,无需写入。" : message); }
1263
+ else { setPreview(null); if (props.onChanged) props.onChanged(result.unchanged === true ? t("管理块内容已一致,无需写入。") : message); }
963
1264
  } catch (failure) {
964
- setError(failure.name === "TimeoutError" ? "请求超时,请先刷新确认结果后再重试。" : failure.message || String(failure));
1265
+ setError(failure.name === "TimeoutError" ? t("请求超时,请先刷新确认结果后再重试。") : displayError(failure));
965
1266
  if (body.operation === "apply") setPreview(null);
966
1267
  } finally { setPending(false); }
967
1268
  };
968
1269
  const review = function (chosen) {
969
- return submit({ operation: "review", action: chosen, lessonId: row.id, expectedRevision: row.revision, resolution: resolution, note: note, affectedIds: affected === "" ? [] : [affected] }, "已更新「" + row.title + "」的审阅状态。");
1270
+ return submit({ operation: "review", action: chosen, lessonId: row.id, expectedRevision: row.revision, resolution: resolution, note: note, affectedIds: affected === "" ? [] : [affected] }, t("已更新「") + row.title + t("」的审阅状态。"));
970
1271
  };
971
1272
  const feedback = function (vote) {
972
1273
  const signature = row.id + "@" + row.revision + ":" + vote + ":" + feedbackNote;
973
1274
  const requestId = feedbackRetry && feedbackRetry.signature === signature ? feedbackRetry.id
974
1275
  : typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : "feedback_" + Date.now() + "_" + Math.random().toString(36).slice(2);
975
1276
  setFeedbackRetry({ signature: signature, id: requestId });
976
- return submit({ operation: "feedback", lessonId: row.id, expectedRevision: row.revision, vote: vote, requestId: requestId, ...(feedbackNote.trim() === "" ? {} : { note: feedbackNote.trim() }) }, "已记录使用反馈;「" + row.title + "」的审阅状态保持原样。");
1277
+ return submit({ operation: "feedback", lessonId: row.id, expectedRevision: row.revision, vote: vote, requestId: requestId, ...(feedbackNote.trim() === "" ? {} : { note: feedbackNote.trim() }) }, t("已记录使用反馈;「") + row.title + t("」的审阅状态保持原样。"));
977
1278
  };
978
- return h("div", { className: "dshd-review", role: "group", "aria-label": row.title + "的操作", onClick: function (event) { event.stopPropagation(); } }, [
1279
+ return h("div", { className: "dshd-review", role: "group", "aria-label": row.title + t("的操作"), onClick: function (event) { event.stopPropagation(); } }, [
979
1280
  h("div", { className: "dshd-kcmd", key: "buttons" }, [
980
1281
  ...commands.filter(function (item) { return item[0] !== "resolve-conflict"; }).map(function (item) {
981
1282
  return h(Btn, { key: item[0], disabled: pending || row.revision < 1, onClick: function () { review(item[0]); } }, item[1]);
982
1283
  }),
983
- h(Btn, { key: "review", disabled: pending, onClick: function () { setOpen(!open); }, "aria-expanded": open }, open ? "收起审阅" : row.state === "disputed" ? "处理冲突" : "审阅详情"),
984
- row.bridgeable === true && row.workspaceRoot !== "" ? h(Btn, { key: "preview", disabled: pending, onClick: function () { submit({ operation: "preview", lessonId: row.id, expectedRevision: row.revision }, "", true); } }, "预览规则变更") : null,
985
- row.bridgeable === true && row.workspaceRoot === "" ? h(CopyCommand, { key: "preview-command", command: bridgePreviewCommandOf(row.id, row.projectId, row.workspaceRoot), label: "复制项目预览命令" }) : null,
1284
+ h(Btn, { key: "review", disabled: pending, onClick: function () { setOpen(!open); }, "aria-expanded": open }, open ? t("收起审阅") : row.state === "disputed" ? t("处理冲突") : t("审阅详情")),
1285
+ row.bridgeable === true && row.workspaceRoot !== "" ? h(Btn, { key: "preview", disabled: pending, onClick: function () { submit({ operation: "preview", lessonId: row.id, expectedRevision: row.revision }, "", true); } }, t("预览规则变更")) : null,
1286
+ row.bridgeable === true && row.workspaceRoot === "" ? h(CopyCommand, { key: "preview-command", command: bridgePreviewCommandOf(row.id, row.projectId, row.workspaceRoot), label: t("复制项目预览命令") }) : null,
986
1287
  ]),
987
- pending ? h("div", { key: "pending", role: "status", className: "dshd-note" }, "正在处理…") : null,
988
- error !== "" ? h("div", { key: "error", role: "alert", className: "dshd-hint" }, [error, h(Btn, { key: "refresh", disabled: pending, onClick: function () { if (props.onChanged) props.onChanged("已刷新经验列表。"); } }, "刷新列表")]) : null,
1288
+ pending ? h("div", { key: "pending", role: "status", className: "dshd-note" }, t("正在处理…")) : null,
1289
+ error !== "" ? h("div", { key: "error", role: "alert", className: "dshd-hint" }, [error, h(Btn, { key: "refresh", disabled: pending, onClick: function () { if (props.onChanged) props.onChanged(t("已刷新经验列表。")); } }, t("刷新列表"))]) : null,
989
1290
  open ? h("div", { className: "dshd-reviewform", key: "form", onKeyDown: function (event) {
990
1291
  if (event.key === "Escape" && !pending) { event.preventDefault(); event.stopPropagation(); setOpen(false); }
991
1292
  } }, [
992
- h("label", { key: "action" }, ["审阅动作", h("select", { "aria-label": "审阅动作", disabled: pending, value: effectiveAction, onChange: function (event) { setAction(event.target.value); } }, commands.map(function (item) { return h("option", { value: item[0], key: item[0] }, item[1]); }))]),
1293
+ h("label", { key: "action" }, [t("审阅动作"), h("select", { "aria-label": t("审阅动作"), disabled: pending, value: effectiveAction, onChange: function (event) { setAction(event.target.value); } }, commands.map(function (item) { return h("option", { value: item[0], key: item[0] }, item[1]); }))]),
993
1294
  effectiveAction === "resolve-conflict" ? h("div", { key: "conflict" }, [
994
- h("label", { key: "against" }, ["冲突经验", h("select", { "aria-label": "冲突经验", disabled: pending, value: affected, onChange: function (event) { setAffected(event.target.value); } }, row.conflicts.map(function (item) { return h("option", { key: item.id, value: item.id, title: item.title }, item.title); }))]),
995
- h("label", { key: "resolution" }, ["处理方式", h("select", { "aria-label": "处理方式", disabled: pending, value: resolution, onChange: function (event) { setResolution(event.target.value); } }, [
996
- h("option", { key: "prefer", value: "prefer" }, "采用这条,驳回对方"),
997
- h("option", { key: "drop", value: "drop" }, "放弃这条,保留对方"),
998
- h("option", { key: "merge", value: "merge" }, "合并到这条,对方留作历史"),
1295
+ h("label", { key: "against" }, [t("冲突经验"), h("select", { "aria-label": t("冲突经验"), disabled: pending, value: affected, onChange: function (event) { setAffected(event.target.value); } }, row.conflicts.map(function (item) { return h("option", { key: item.id, value: item.id, title: item.title }, item.title); }))]),
1296
+ h("label", { key: "resolution" }, [t("处理方式"), h("select", { "aria-label": t("处理方式"), disabled: pending, value: resolution, onChange: function (event) { setResolution(event.target.value); } }, [
1297
+ h("option", { key: "prefer", value: "prefer" }, t("采用这条,驳回对方")),
1298
+ h("option", { key: "drop", value: "drop" }, t("放弃这条,保留对方")),
1299
+ h("option", { key: "merge", value: "merge" }, t("合并到这条,对方留作历史")),
999
1300
  ])]),
1000
- h("label", { key: "note" }, ["处理依据", h("textarea", { "aria-label": "处理依据", disabled: pending, rows: 2, maxLength: 500, value: note, placeholder: "说明哪个来源支持这次决定…", onChange: function (event) { setNote(event.target.value); } })]),
1301
+ h("label", { key: "note" }, [t("处理依据"), h("textarea", { "aria-label": t("处理依据"), disabled: pending, rows: 2, maxLength: 500, value: note, placeholder: t("说明哪个来源支持这次决定…"), onChange: function (event) { setNote(event.target.value); } })]),
1001
1302
  ]) : null,
1002
- h(Btn, { key: "confirm", disabled: pending || command === "", onClick: function () { review(effectiveAction); }, variant: "primary" }, "确认审阅"),
1003
- h("details", { key: "fallback" }, [h("summary", { key: "summary" }, "在对话里处理"), command === "" ? h("div", { key: "hint", className: "dshd-note" }, "选择冲突经验并填写处理依据后即可处理。") : h(CopyCommand, { key: "copy", command: command, label: "复制审阅命令" })]),
1303
+ h(Btn, { key: "confirm", disabled: pending || command === "", onClick: function () { review(effectiveAction); }, variant: "primary" }, t("确认审阅")),
1304
+ h("details", { key: "fallback" }, [h("summary", { key: "summary" }, t("在对话里处理")), command === "" ? h("div", { key: "hint", className: "dshd-note" }, t("选择冲突经验并填写处理依据后即可处理。")) : h(CopyCommand, { key: "copy", command: command, label: t("复制审阅命令") })]),
1004
1305
  ]) : null,
1005
- preview ? h("section", { key: "preview", className: "dshd-reviewform", "aria-label": "规则变更预览" }, [
1006
- h("strong", { key: "heading" }, "规则变更预览"),
1007
- h("div", { key: "target", className: "dshd-note" }, "目标:" + preview.resolvedPath),
1008
- h("pre", { key: "diff", className: "dshd-diff", tabIndex: 0 }, preview.diff || "管理块内容已经一致,无需更改。"),
1009
- h("div", { key: "help", className: "dshd-note" }, "将按此预览重建 Dream 管理块,请核对新增和删除内容。块外内容保留,写入前创建回滚备份。"),
1010
- h(Btn, { key: "apply", disabled: pending, variant: "primary", onClick: function () { submit({ operation: "apply", token: preview.token }, "规则已应用,可在「写入记录」中回滚。"); } }, "应用此变更"),
1011
- h(Btn, { key: "cancel", disabled: pending, onClick: function () { setPreview(null); } }, "取消预览"),
1306
+ preview ? h("section", { key: "preview", className: "dshd-reviewform", "aria-label": t("规则变更预览") }, [
1307
+ h("strong", { key: "heading" }, t("规则变更预览")),
1308
+ h("div", { key: "target", className: "dshd-note" }, t("目标:") + preview.resolvedPath),
1309
+ h("pre", { key: "diff", className: "dshd-diff", tabIndex: 0 }, preview.diff || t("管理块内容已经一致,无需更改。")),
1310
+ h("div", { key: "help", className: "dshd-note" }, t("将按此预览重建 Dream 管理块,请核对新增和删除内容。块外内容保留,写入前创建回滚备份。")),
1311
+ h(Btn, { key: "apply", disabled: pending, variant: "primary", onClick: function () { submit({ operation: "apply", token: preview.token }, t("规则已应用,可在「写入记录」中回滚。")); } }, t("应用此变更")),
1312
+ h(Btn, { key: "cancel", disabled: pending, onClick: function () { setPreview(null); } }, t("取消预览")),
1012
1313
  ]) : null,
1013
1314
  h("details", { key: "feedback", className: "dshd-feedback" }, [
1014
- h("summary", { key: "summary" }, "使用反馈 · 有用 " + (row.feedback.useful || 0) + " · 不适用 " + (row.feedback.notApplicable || 0)),
1015
- h("label", { key: "note" }, ["场景备注(可选)", h("textarea", { "aria-label": "反馈场景备注", rows: 2, disabled: pending, maxLength: 500, value: feedbackNote, placeholder: "这次在哪个场景使用…", onChange: function (event) { setFeedbackNote(event.target.value); } })]),
1016
- h(Btn, { key: "useful", disabled: pending, onClick: function () { feedback("useful"); } }, "这次有用"),
1017
- h(Btn, { key: "not", disabled: pending, onClick: function () { feedback("not-applicable"); } }, "这次不适用"),
1018
- h("div", { key: "help", className: "dshd-note" }, "记录使用情况,供之后复核;采纳与驳回由审阅决定。"),
1315
+ h("summary", { key: "summary" }, t("使用反馈 · 有用 ") + (row.feedback.useful || 0) + t(" · 不适用 ") + (row.feedback.notApplicable || 0)),
1316
+ h("label", { key: "note" }, [t("场景备注(可选)"), h("textarea", { "aria-label": t("反馈场景备注"), rows: 2, disabled: pending, maxLength: 500, value: feedbackNote, placeholder: t("这次在哪个场景使用…"), onChange: function (event) { setFeedbackNote(event.target.value); } })]),
1317
+ h(Btn, { key: "useful", disabled: pending, onClick: function () { feedback("useful"); } }, t("这次有用")),
1318
+ h(Btn, { key: "not", disabled: pending, onClick: function () { feedback("not-applicable"); } }, t("这次不适用")),
1319
+ h("div", { key: "help", className: "dshd-note" }, t("记录使用情况,供之后复核;采纳与驳回由审阅决定。")),
1019
1320
  ...(Array.isArray(row.feedback.recent) ? row.feedback.recent.slice(-3).reverse().map(function (item, index) {
1020
- return h("div", { key: "recent-" + index, className: "dshd-note" }, formatDate(item.at) + " · " + (item.vote === "useful" ? "有用" : "不适用") + (item.note ? " · " + item.note : ""));
1321
+ return h("div", { key: "recent-" + index, className: "dshd-note" }, formatDate(item.at) + " · " + (item.vote === "useful" ? t("有用") : t("不适用")) + (item.note ? " · " + item.note : ""));
1021
1322
  }) : []),
1022
1323
  ]),
1023
1324
  ]);
@@ -1033,15 +1334,15 @@ function RollbackControl(props) {
1033
1334
  try {
1034
1335
  await postAction({ operation: "rollback", backupId: props.row.id });
1035
1336
  setConfirm(false);
1036
- if (props.onChanged) props.onChanged("已恢复上一次的 Dream 管理块,块外内容保留。");
1037
- } catch (failure) { setError(failure.message || String(failure)); }
1337
+ if (props.onChanged) props.onChanged(t("已恢复上一次的 Dream 管理块,块外内容保留。"));
1338
+ } catch (failure) { setError(displayError(failure)); }
1038
1339
  finally { setPending(false); }
1039
1340
  };
1040
1341
  return h("div", { className: "dshd-reviewform" }, [
1041
- h(Btn, { key: "start", disabled: pending, onClick: function () { setConfirm(!confirm); } }, confirm ? "取消回滚" : "回滚这次变更"),
1342
+ h(Btn, { key: "start", disabled: pending, onClick: function () { setConfirm(!confirm); } }, confirm ? t("取消回滚") : t("回滚这次变更")),
1042
1343
  confirm ? h("div", { key: "confirm" }, [
1043
- h("p", { key: "target", className: "dshd-note" }, "将恢复「" + props.row.target + "」的前一个 Dream 管理块。块外内容保留;管理块已被修改时会拒绝回滚。"),
1044
- h(Btn, { key: "apply", variant: "primary", disabled: pending, onClick: rollback }, pending ? "正在回滚…" : "确认回滚管理块"),
1344
+ h("p", { key: "target", className: "dshd-note" }, t("将恢复「") + props.row.target + t("」的前一个 Dream 管理块。块外内容保留;管理块已被修改时会拒绝回滚。")),
1345
+ h(Btn, { key: "apply", variant: "primary", disabled: pending, onClick: rollback }, pending ? t("正在回滚…") : t("确认回滚管理块")),
1045
1346
  ]) : null,
1046
1347
  error ? h("div", { key: "error", role: "alert", className: "dshd-hint" }, error) : null,
1047
1348
  ]);
@@ -1055,36 +1356,36 @@ function KnowledgeRow(props) {
1055
1356
  return h("div", {
1056
1357
  className: "dshd-knowledge " + row.stateClass + (privacy === true && peek === true ? " dshd-peek" : ""),
1057
1358
  onClick: reveal,
1058
- title: privacy === true ? "隐私模式:点击此条临时查看" : undefined,
1359
+ title: privacy === true ? t("隐私模式:点击此条临时查看") : undefined,
1059
1360
  }, [
1060
1361
  h("div", { className: "dshd-khead", key: "h" }, [
1061
1362
  h("span", { className: "dshd-kbadge", key: "b" }, row.stateLabel),
1062
1363
  h("span", { className: "dshd-ktitle", key: "t", title: row.title }, row.title),
1063
1364
  ]),
1064
1365
  h("div", { className: "dshd-kmeta", key: "m" }, [
1065
- h("span", { className: "dshd-klast", key: "l", title: row.lastTitle }, "最近核验:" + row.lastText),
1366
+ h("span", { className: "dshd-klast", key: "l", title: row.lastTitle }, t("最近核验:") + row.lastText),
1066
1367
  ]),
1067
1368
  h("div", { className: "dshd-kbody", key: "body" }, [
1068
- h("div", { key: "when" }, "何时用:" + (row.when !== "" ? row.when : "未标注适用条件")),
1069
- h("div", { key: "action", className: "dshd-action" }, "怎么做:" + (row.action || "未标注具体动作")),
1369
+ h("div", { key: "when" }, t("何时用:") + (row.when !== "" ? row.when : t("未标注适用条件"))),
1370
+ h("div", { key: "action", className: "dshd-action" }, t("怎么做:") + (row.action || t("未标注具体动作"))),
1070
1371
  h("details", { key: "details", onClick: function (event) { event.stopPropagation(); } }, [
1071
- h("summary", { key: "summary" }, "适用范围与证据"),
1072
- h("div", { key: "scope" }, "范围:" + row.scopeLabel),
1073
- row.applicability !== "" ? h("div", { key: "applicability" }, "适用:" + row.applicability) : null,
1074
- row.exceptions.length > 0 ? h("div", { key: "exceptions" }, "例外:" + row.exceptions.join(";")) : null,
1075
- h("div", { key: "evidence" }, "证据:" + row.evidenceSummary),
1076
- row.evidence.length === 0 ? h("div", { key: "sources-empty", className: "dshd-note" }, "没有关联来源记录,采纳前请核对依据。") : h("div", { key: "sources", className: "dshd-sources" }, row.evidence.map(function (item) {
1372
+ h("summary", { key: "summary" }, t("适用范围与证据")),
1373
+ h("div", { key: "scope" }, t("范围:") + row.scopeLabel),
1374
+ row.applicability !== "" ? h("div", { key: "applicability" }, t("适用:") + row.applicability) : null,
1375
+ row.exceptions.length > 0 ? h("div", { key: "exceptions" }, t("例外:") + row.exceptions.join(";")) : null,
1376
+ h("div", { key: "evidence" }, t("证据:") + row.evidenceSummary),
1377
+ row.evidence.length === 0 ? h("div", { key: "sources-empty", className: "dshd-note" }, t("没有关联来源记录,采纳前请核对依据。")) : h("div", { key: "sources", className: "dshd-sources" }, row.evidence.map(function (item) {
1077
1378
  return h("details", { key: item.id }, [
1078
- h("summary", { key: "summary" }, "来源记录 · " + (item.verification === "read" ? "已读到来源" : "待核验") + " · " + (item.kind === "session" ? "会话" : item.kind === "user-correction" ? "用户纠正" : "本地资料")),
1079
- h("div", { key: "summary-text" }, item.summary || "未提供摘要"),
1080
- item.sessionId ? h("div", { key: "session", className: "dshd-note" }, "会话:" + item.sessionId + (Number.isInteger(item.recordSeq) ? " · 记录 " + item.recordSeq : "")) : null,
1081
- h("div", { key: "date", className: "dshd-note" }, "记录时间:" + formatDate(item.observedAt)),
1082
- item.verificationReason ? h("div", { key: "reason", className: "dshd-note" }, "核验说明:" + item.verificationReason) : null,
1083
- item.sourceHash ? h("code", { key: "hash", className: "dshd-cmd", title: item.sourceHash }, "来源校验值:" + item.sourceHash) : null,
1379
+ h("summary", { key: "summary" }, t("来源记录 · ") + (item.verification === "read" ? t("已读到来源") : t("待核验")) + " · " + (item.kind === "session" ? t("会话") : item.kind === "user-correction" ? t("用户纠正") : t("本地资料"))),
1380
+ h("div", { key: "summary-text" }, item.summary || t("未提供摘要")),
1381
+ item.sessionId ? h("div", { key: "session", className: "dshd-note" }, t("会话:") + item.sessionId + (Number.isInteger(item.recordSeq) ? t(" · 记录 ") + item.recordSeq : "")) : null,
1382
+ h("div", { key: "date", className: "dshd-note" }, t("记录时间:") + formatDate(item.observedAt)),
1383
+ item.verificationReason ? h("div", { key: "reason", className: "dshd-note" }, t("核验说明:") + item.verificationReason) : null,
1384
+ item.sourceHash ? h("code", { key: "hash", className: "dshd-cmd", title: item.sourceHash }, t("来源校验值:") + item.sourceHash) : null,
1084
1385
  ]);
1085
1386
  })),
1086
1387
  ]),
1087
- props.showHoldReason === true && row.holdReasonLabel !== "" ? h("div", { key: "hold", className: "dshd-holdreason" }, "为什么被扣下:" + row.holdReasonLabel) : null,
1388
+ props.showHoldReason === true && row.holdReasonLabel !== "" ? h("div", { key: "hold", className: "dshd-holdreason" }, t("为什么被扣下:") + row.holdReasonLabel) : null,
1088
1389
  ]),
1089
1390
  h(ReviewControls, { row: row, key: row.id + "@" + row.revision, onChanged: props.onChanged }),
1090
1391
  ]);
@@ -1099,22 +1400,22 @@ function KnowledgeCard(props) {
1099
1400
  const noticeNode = notice === "" ? null : h("div", { className: "dshd-knotice", key: "notice", role: "status" }, notice);
1100
1401
  const head = function (extra) {
1101
1402
  return h("div", { className: "dshd-khead", key: "head" }, [
1102
- h("h4", { key: "t" }, "已审阅经验"),
1403
+ h("h4", { key: "t" }, t("已审阅经验")),
1103
1404
  h("span", { className: "dshd-spacer", key: "s" }),
1104
1405
  extra,
1105
1406
  ]);
1106
1407
  };
1107
1408
  if (props.error !== null && props.error !== undefined) {
1108
1409
  return h("section", { className: "dshd-card dshd-kcard" }, [
1109
- head(h(Btn, { key: "retry", onClick: props.onRetry, disabled: props.loading === true }, "重试")),
1110
- h("div", { className: "dshd-hint", key: "e", role: "alert" }, "读取经验失败:" + (props.error.message === "" ? String(props.error) : props.error.message)),
1111
- h("div", { className: "dshd-note", key: "n" }, "旧日记不受影响;修好后点「重试」。"),
1410
+ head(h(Btn, { key: "retry", onClick: props.onRetry, disabled: props.loading === true }, t("重试"))),
1411
+ h("div", { className: "dshd-hint", key: "e", role: "alert" }, t("读取经验失败:") + displayError(props.error)),
1412
+ h("div", { className: "dshd-note", key: "n" }, t("旧日记不受影响;修好后点「重试」。")),
1112
1413
  ]);
1113
1414
  }
1114
1415
  if (props.empty === true) {
1115
1416
  const empty = knowledgeEmptyState();
1116
1417
  return h("section", { className: "dshd-card dshd-kcard" }, [
1117
- h("h4", { key: "t" }, "经验"),
1418
+ h("h4", { key: "t" }, t("经验")),
1118
1419
  noticeNode,
1119
1420
  h("div", { className: "dshd-kempty", key: "e" }, [
1120
1421
  h("div", { className: "dshd-kempty-title", key: "t2" }, empty.title),
@@ -1125,21 +1426,21 @@ function KnowledgeCard(props) {
1125
1426
  }
1126
1427
  return h("section", { className: "dshd-card dshd-kcard" }, [
1127
1428
  head(props.loading === true
1128
- ? h("span", { className: "dshd-hint", key: "loading" }, "加载中…")
1429
+ ? h("span", { className: "dshd-hint", key: "loading" }, t("加载中…"))
1129
1430
  : h("span", { className: "dshd-hint", key: "count" }, knowledgeCountText(vm.stats))),
1130
1431
  noticeNode,
1131
- h("label", { className: "dshd-note", key: "filter" }, ["查看 ", h("select", { "aria-label": "经验状态", value: stateFilter, onChange: function (event) { setStateFilter(event.target.value); setPageSize(5); } }, [
1132
- h("option", { key: "usable", value: "usable" }, "可用经验"), h("option", { key: "rejected", value: "rejected" }, "已驳回历史"),
1432
+ h("label", { className: "dshd-note", key: "filter" }, [t("查看 "), h("select", { "aria-label": t("经验状态"), value: stateFilter, onChange: function (event) { setStateFilter(event.target.value); setPageSize(5); } }, [
1433
+ h("option", { key: "usable", value: "usable" }, t("可用经验")), h("option", { key: "rejected", value: "rejected" }, t("已驳回历史")),
1133
1434
  ])]),
1134
- reviewed.length === 0 ? h("div", { className: "dshd-hint", key: "none" }, stateFilter === "usable" ? "暂无可用经验;待核实的记录在下方。" : "没有已驳回记录。") : null,
1435
+ reviewed.length === 0 ? h("div", { className: "dshd-hint", key: "none" }, stateFilter === "usable" ? t("暂无可用经验;待核实的记录在下方。") : t("没有已驳回记录。")) : null,
1135
1436
  h("div", { className: "dshd-krows", key: "rows" }, reviewed.slice(0, pageSize).map(function (row, index) {
1136
1437
  return h(KnowledgeRow, { key: row.id !== "" ? row.id : "k" + String(index), row: row, onChanged: props.onChanged, privacy: props.privacy === true });
1137
1438
  })),
1138
- reviewed.length > pageSize ? h(Btn, { key: "next", onClick: function () { setPageSize(pageSize + 5); } }, "显示更多经验") : null,
1439
+ reviewed.length > pageSize ? h(Btn, { key: "next", onClick: function () { setPageSize(pageSize + 5); } }, t("显示更多经验")) : null,
1139
1440
  vm.shown < vm.total
1140
- ? h("div", { className: "dshd-note", key: "more" }, "只显示最近 " + String(vm.shown) + " / " + String(vm.total) + " 条经验。")
1441
+ ? h("div", { className: "dshd-note", key: "more" }, t("只显示最近 ") + String(vm.shown) + " / " + String(vm.total) + t(" 条经验。"))
1141
1442
  : null,
1142
- h("div", { className: "dshd-note", key: "note" }, "采纳记录仍需满足当前任务的平台、版本与项目条件;来源已读不代表结论永远适用。"),
1443
+ h("div", { className: "dshd-note", key: "note" }, t("采纳记录仍需满足当前任务的平台、版本与项目条件;来源已读不代表结论永远适用。")),
1143
1444
  ]);
1144
1445
  }
1145
1446
 
@@ -1152,34 +1453,34 @@ function ReviewQueueCard(props) {
1152
1453
  : [];
1153
1454
  const head = function (extra) {
1154
1455
  return h("div", { className: "dshd-khead", key: "head" }, [
1155
- h("h4", { key: "t" }, "待核实"),
1456
+ h("h4", { key: "t" }, t("待核实")),
1156
1457
  h("span", { className: "dshd-spacer", key: "s" }),
1157
1458
  extra,
1158
1459
  ]);
1159
1460
  };
1160
1461
  if (props.error !== null && props.error !== undefined) {
1161
1462
  return h("section", { className: "dshd-card dshd-kcard" }, [
1162
- head(h(Btn, { key: "retry", onClick: props.onRetry, disabled: props.loading === true }, "重试")),
1163
- h("div", { className: "dshd-hint", key: "e", role: "alert" }, "读取经验失败:" + (props.error.message === "" ? String(props.error) : props.error.message)),
1463
+ head(h(Btn, { key: "retry", onClick: props.onRetry, disabled: props.loading === true }, t("重试"))),
1464
+ h("div", { className: "dshd-hint", key: "e", role: "alert" }, t("读取经验失败:") + displayError(props.error)),
1164
1465
  ]);
1165
1466
  }
1166
1467
  if (props.loading === true && rows.length === 0) {
1167
1468
  return h("section", { className: "dshd-card dshd-kcard" }, [
1168
- head(h("span", { className: "dshd-hint", key: "l" }, "加载中…")),
1469
+ head(h("span", { className: "dshd-hint", key: "l" }, t("加载中…"))),
1169
1470
  ]);
1170
1471
  }
1171
1472
  return h("section", { className: "dshd-card dshd-kcard" }, [
1172
- head(rows.length === 0 ? null : h("span", { className: "dshd-hint", key: "c" }, String(rows.length) + " 条待核实")),
1473
+ head(rows.length === 0 ? null : h("span", { className: "dshd-hint", key: "c" }, String(rows.length) + t(" 条待核实"))),
1173
1474
  rows.length === 0
1174
1475
  ? h("div", { className: "dshd-kempty", key: "empty" }, [
1175
- h("div", { className: "dshd-kempty-title", key: "t" }, "没有待核实的经验"),
1476
+ h("div", { className: "dshd-kempty-title", key: "t" }, t("没有待核实的经验")),
1176
1477
  h("p", { className: "dshd-kempty-body", key: "b" }, reviewQueueEmptyState()),
1177
1478
  ])
1178
1479
  : h("div", { className: "dshd-krows", key: "rows" }, rows.slice(0, pageSize).map(function (row, index) {
1179
1480
  return h(KnowledgeRow, { key: row.id !== "" ? row.id : "held-" + String(index), row: row, onChanged: props.onChanged, privacy: props.privacy === true, showHoldReason: true });
1180
1481
  })),
1181
- rows.length > pageSize ? h(Btn, { key: "more", onClick: function () { setPageSize(pageSize + 5); } }, "显示更多待核实经验") : null,
1182
- h("div", { className: "dshd-note", key: "note" }, "这些经验默认不会作为任务建议注入。先核对来源与适用范围,再决定采纳、驳回或继续复核。"),
1482
+ rows.length > pageSize ? h(Btn, { key: "more", onClick: function () { setPageSize(pageSize + 5); } }, t("显示更多待核实经验")) : null,
1483
+ h("div", { className: "dshd-note", key: "note" }, t("这些经验默认不会作为任务建议注入。先核对来源与适用范围,再决定采纳、驳回或继续复核。")),
1183
1484
  ]);
1184
1485
  }
1185
1486
 
@@ -1188,26 +1489,26 @@ function BridgeLogCard(props) {
1188
1489
  const vm = props.vm;
1189
1490
  const head = function (extra) {
1190
1491
  return h("div", { className: "dshd-khead", key: "head" }, [
1191
- h("h4", { key: "t" }, "写入记录"),
1492
+ h("h4", { key: "t" }, t("写入记录")),
1192
1493
  h("span", { className: "dshd-spacer", key: "s" }),
1193
1494
  extra,
1194
1495
  ]);
1195
1496
  };
1196
1497
  if (props.error !== null && props.error !== undefined) {
1197
1498
  return h("section", { className: "dshd-card dshd-kcard" }, [
1198
- head(h(Btn, { key: "retry", onClick: props.onRetry, disabled: props.loading === true }, "重试")),
1199
- h("div", { className: "dshd-hint", key: "e", role: "alert" }, "读取写入记录失败:" + (props.error.message === "" ? String(props.error) : props.error.message)),
1499
+ head(h(Btn, { key: "retry", onClick: props.onRetry, disabled: props.loading === true }, t("重试"))),
1500
+ h("div", { className: "dshd-hint", key: "e", role: "alert" }, t("读取写入记录失败:") + displayError(props.error)),
1200
1501
  ]);
1201
1502
  }
1202
1503
  if (props.loading === true) {
1203
1504
  return h("section", { className: "dshd-card dshd-kcard" }, [
1204
- head(h("span", { className: "dshd-hint", key: "l" }, "加载中…")),
1505
+ head(h("span", { className: "dshd-hint", key: "l" }, t("加载中…"))),
1205
1506
  ]);
1206
1507
  }
1207
1508
  if (vm.rows.length === 0) {
1208
1509
  const empty = bridgeEmptyState();
1209
1510
  return h("section", { className: "dshd-card dshd-kcard" }, [
1210
- h("h4", { key: "t" }, "写入记录"),
1511
+ h("h4", { key: "t" }, t("写入记录")),
1211
1512
  h("div", { className: "dshd-kempty", key: "empty" }, [
1212
1513
  h("div", { className: "dshd-kempty-title", key: "t2" }, empty.title),
1213
1514
  h("p", { className: "dshd-kempty-body", key: "b" }, empty.body),
@@ -1216,7 +1517,7 @@ function BridgeLogCard(props) {
1216
1517
  ]);
1217
1518
  }
1218
1519
  return h("section", { className: "dshd-card dshd-kcard" }, [
1219
- head(h("span", { className: "dshd-hint", key: "c" }, String(vm.total) + " 次写入 · " + String(vm.rollbackable) + " 次可回滚")),
1520
+ head(h("span", { className: "dshd-hint", key: "c" }, String(vm.total) + t(" 次写入 · ") + String(vm.rollbackable) + t(" 次可回滚"))),
1220
1521
  h("div", { className: "dshd-krows", key: "rows" }, vm.rows.map(function (row, index) {
1221
1522
  return h("div", { className: "dshd-bridge", key: row.id !== "" ? row.id : "bridge-" + String(index) }, [
1222
1523
  h("div", { className: "dshd-bridge-head", key: "h" }, [
@@ -1226,21 +1527,21 @@ function BridgeLogCard(props) {
1226
1527
  h("span", { className: "dshd-klast", key: "at", title: row.atTitle }, row.atText),
1227
1528
  ]),
1228
1529
  h("div", { className: "dshd-bridge-meta", key: "m" }, [
1229
- h("span", { key: "lessons", title: row.lessonsText }, "经验:" + row.lessonsText),
1230
- row.beforeShort === "" ? null : h("span", { key: "before" }, "前哈希:" + row.beforeShort),
1231
- row.afterShort === "" ? null : h("span", { key: "after" }, "后哈希:" + row.afterShort),
1530
+ h("span", { key: "lessons", title: row.lessonsText }, t("经验:") + row.lessonsText),
1531
+ row.beforeShort === "" ? null : h("span", { key: "before" }, t("前哈希:") + row.beforeShort),
1532
+ row.afterShort === "" ? null : h("span", { key: "after" }, t("后哈希:") + row.afterShort),
1232
1533
  h("span", { key: "rb", className: row.rollbackable === true ? "dshd-rollback-ok" : "dshd-rollback-no" }, row.rollbackText),
1233
1534
  ]),
1234
1535
  h("details", { key: "backup" }, [
1235
- h("summary", { key: "summary" }, "备份标识与回滚命令"),
1536
+ h("summary", { key: "summary" }, t("备份标识与回滚命令")),
1236
1537
  h("code", { key: "id", className: "dshd-cmd" }, row.id),
1237
1538
  row.rollbackable === true ? h("code", { key: "command", className: "dshd-cmd", tabIndex: 0 }, bridgeRollbackCommandOf(row)) : null,
1238
1539
  ]),
1239
1540
  row.rollbackable === true ? h(RollbackControl, { key: "rollback", row: row, onChanged: props.onChanged }) : null,
1240
1541
  ]);
1241
1542
  })),
1242
- vm.shown < vm.total ? h("div", { className: "dshd-note", key: "more" }, "只显示最近 " + String(vm.shown) + " / " + String(vm.total) + " 条记录。") : null,
1243
- h("div", { className: "dshd-note", key: "note" }, "回滚恢复对应备份的 Dream 管理块;块外内容保留,外部修改的管理块会停止回滚。"),
1543
+ vm.shown < vm.total ? h("div", { className: "dshd-note", key: "more" }, t("只显示最近 ") + String(vm.shown) + " / " + String(vm.total) + t(" 条记录。")) : null,
1544
+ h("div", { className: "dshd-note", key: "note" }, t("回滚恢复对应备份的 Dream 管理块;块外内容保留,外部修改的管理块会停止回滚。")),
1244
1545
  ]);
1245
1546
  }
1246
1547
 
@@ -1255,12 +1556,12 @@ function DreamCard(props) {
1255
1556
  return h("article", {
1256
1557
  className: "dshd-dream dshd-mood-" + String(moodSlot(dream.mood)) + (privacy && peek === true ? " dshd-peek" : ""),
1257
1558
  onClick: clickCard,
1258
- title: privacy ? "隐私模式:点击卡片临时查看" : undefined,
1559
+ title: privacy ? t("隐私模式:点击卡片临时查看") : undefined,
1259
1560
  }, [
1260
1561
  h("header", { className: "dshd-dream-head", key: "h" }, [
1261
1562
  h("time", { className: "dshd-time", key: "t", dateTime: dream.at === "" ? undefined : dream.at, title: dream.at === "" ? timeText : timeText + " · " + dream.at }, timeText),
1262
1563
  dreamAgo(dream, props.now) === "" ? null : h("span", { className: "dshd-ago", key: "a" }, dreamAgo(dream, props.now)),
1263
- h("span", { className: "dshd-mood", key: "m", title: "心境:" + dream.mood }, [
1564
+ h("span", { className: "dshd-mood", key: "m", title: t("心境:") + dream.mood }, [
1264
1565
  h("i", { className: "dshd-mooddot " + toneClass(moodSlot(dream.mood)), key: "d" }),
1265
1566
  dream.mood,
1266
1567
  ]),
@@ -1270,7 +1571,7 @@ function DreamCard(props) {
1270
1571
  className: "dshd-linkbtn",
1271
1572
  key: "btn",
1272
1573
  onClick: function (event) { if (event !== undefined && event.stopPropagation !== undefined) event.stopPropagation(); setExpanded(expanded !== true); },
1273
- }, expanded === true ? "收起" : "展开全文") : null,
1574
+ }, expanded === true ? t("收起") : t("展开全文")) : null,
1274
1575
  ]),
1275
1576
  h("div", { className: "dshd-reflection" + (collapsible && expanded !== true ? " dshd-clamp" : ""), key: "r" }, inlineNodes(dream.reflection, "reflection-" + String(props.index))),
1276
1577
  dream.lessons.length === 0 ? null : h("ol", { className: "dshd-lessons", key: "l" }, dream.lessons.map((lesson, index) => h("li", { key: "li-" + String(index) + "-" + lesson }, [
@@ -1287,17 +1588,17 @@ function EmptyState(props) {
1287
1588
  if (props.query !== undefined && props.query !== "") {
1288
1589
  return h("div", { className: "dshd-empty" }, [
1289
1590
  moon,
1290
- h("h3", { key: "t" }, "没有找到「" + props.query + "」相关的梦"),
1291
- h("p", { key: "p" }, "换个关键词试试,或者清空搜索看全部梦境。"),
1292
- props.onClear ? h("div", { key: "a" }, h(Btn, { onClick: props.onClear }, "清空搜索")) : null,
1591
+ h("h3", { key: "t" }, t("没有找到「") + props.query + t("」相关的梦")),
1592
+ h("p", { key: "p" }, t("换个关键词试试,或者清空搜索看全部梦境。")),
1593
+ props.onClear ? h("div", { key: "a" }, h(Btn, { onClick: props.onClear }, t("清空搜索"))) : null,
1293
1594
  ]);
1294
1595
  }
1295
1596
  return h("div", { className: "dshd-empty" }, [
1296
1597
  moon,
1297
- h("h3", { key: "t" }, "还没有梦境日记"),
1298
- h("p", { key: "p" }, "对 agent 说「做个梦」,它会回放最近的会话、反思之后把第一场梦写进这里。"),
1598
+ h("h3", { key: "t" }, t("还没有梦境日记")),
1599
+ h("p", { key: "p" }, t("对 agent 说「做个梦」,它会回放最近的会话、反思之后把第一场梦写进这里。")),
1299
1600
  h("div", { className: "dshd-chips", key: "c" }, [
1300
- h("span", { className: "dshd-chip", key: "s1" }, "对 agent 说「做个梦」"),
1601
+ h("span", { className: "dshd-chip", key: "s1" }, t("对 agent 说「做个梦」")),
1301
1602
  h("span", { className: "dshd-chip", key: "s2" }, "dream_digest → dream_save"),
1302
1603
  ]),
1303
1604
  ]);
@@ -1363,7 +1664,7 @@ function DreamPanel() {
1363
1664
  setKnowledgeError(null);
1364
1665
  fetchKnowledge().then(function (payload) {
1365
1666
  if (alive !== true) return;
1366
- setKnowledge(normalizeKnowledge(payload, new Date()));
1667
+ setKnowledge(payload);
1367
1668
  setKnowledgeLoading(false);
1368
1669
  }).catch(function (failure) {
1369
1670
  if (alive !== true) return;
@@ -1381,7 +1682,7 @@ function DreamPanel() {
1381
1682
  setBridgeError(null);
1382
1683
  fetchBridgeRecords().then(function (payload) {
1383
1684
  if (alive !== true) return;
1384
- setBridge(normalizeBridgeRecords(payload, new Date()));
1685
+ setBridge(payload);
1385
1686
  setBridgeLoading(false);
1386
1687
  }).catch(function (failure) {
1387
1688
  if (alive !== true) return;
@@ -1393,8 +1694,8 @@ function DreamPanel() {
1393
1694
  }, [reloadToken]);
1394
1695
 
1395
1696
  const vm = viewModelOf(dreams, stats, now);
1396
- const knowledgeVm = knowledge === null ? { rows: [], total: 0, shown: 0, stats: null } : knowledge;
1397
- const bridgeVm = bridge === null ? { rows: [], total: 0, shown: 0, rollbackable: 0 } : bridge;
1697
+ const knowledgeVm = knowledge === null ? { rows: [], total: 0, shown: 0, stats: null } : normalizeKnowledge(knowledge, now);
1698
+ const bridgeVm = bridge === null ? { rows: [], total: 0, shown: 0, rollbackable: 0 } : normalizeBridgeRecords(bridge, now);
1398
1699
  const pristine = vm.count === 0 && query === "";
1399
1700
  const filterCount = filterCountOf(filters);
1400
1701
  const visibleDreams = applyFilters(dreams, filters);
@@ -1486,8 +1787,8 @@ function DreamPanel() {
1486
1787
  className: "dshd-input",
1487
1788
  type: "text",
1488
1789
  value: queryInput,
1489
- placeholder: "搜索反思与教训…",
1490
- "aria-label": "搜索梦境",
1790
+ placeholder: t("搜索反思与教训…"),
1791
+ "aria-label": t("搜索梦境"),
1491
1792
  onChange: function (event) { setQueryInput(event.target.value); },
1492
1793
  onKeyDown: function (event) { if (event.key === "Enter") setQuery(event.target.value.trim()); },
1493
1794
  }),
@@ -1495,13 +1796,13 @@ function DreamPanel() {
1495
1796
  key: "clear-search",
1496
1797
  type: "button",
1497
1798
  className: "dshd-clear",
1498
- "aria-label": "清空搜索",
1799
+ "aria-label": t("清空搜索"),
1499
1800
  onClick: clearSearch,
1500
1801
  }, "×"),
1501
1802
  ]),
1502
1803
  h("span", { className: "dshd-spacer", key: "spacer" }),
1503
1804
  pristine || activeTab !== "journal" ? null : h("span", { className: "dshd-hint", key: "count" }, loading === true
1504
- ? "加载中…"
1805
+ ? t("加载中…")
1505
1806
  : countTextOf(query, vm.total, vm.count, filterCount, visibleDreams.length)),
1506
1807
  showFilterButton === true && activeTab === "journal" ? h("div", { className: "dshd-filter", key: "filter" }, [
1507
1808
  h(Btn, {
@@ -1509,10 +1810,10 @@ function DreamPanel() {
1509
1810
  on: filterCount > 0,
1510
1811
  pressed: filterCount > 0,
1511
1812
  onClick: function () { setFilterOpen(filterOpen !== true); },
1512
- title: "筛选作用于已加载窗口,不影响全量统计",
1513
- }, "筛选" + (filterCount > 0 ? " · " + String(filterCount) : "") + " ▾"),
1514
- filterOpen === true ? h("div", { className: "dshd-filterpop", key: "pop", role: "group", "aria-label": "筛选梦境" }, [
1515
- h("div", { className: "dshd-filtertitle", key: "title" }, "筛选当前已加载的 " + String(vm.count) + " 场,不影响全量统计"),
1813
+ title: t("筛选作用于已加载窗口,不影响全量统计"),
1814
+ }, t("筛选") + (filterCount > 0 ? " · " + String(filterCount) : "") + " ▾"),
1815
+ filterOpen === true ? h("div", { className: "dshd-filterpop", key: "pop", role: "group", "aria-label": t("筛选梦境") }, [
1816
+ h("div", { className: "dshd-filtertitle", key: "title" }, t("筛选当前已加载的 ") + String(vm.count) + t(" 场,不影响全量统计")),
1516
1817
  h("div", { key: "moods" }, vm.moodRows.map(function (row) {
1517
1818
  const on = filters.moods.indexOf(row.mood) !== -1;
1518
1819
  return h("button", {
@@ -1528,10 +1829,10 @@ function DreamPanel() {
1528
1829
  className: "dshd-chipbtn" + (filters.withLessons === true ? " on" : ""),
1529
1830
  "aria-pressed": filters.withLessons === true ? "true" : "false",
1530
1831
  onClick: toggleOnlyLessons,
1531
- }, "只看有教训的梦")),
1832
+ }, t("只看有教训的梦"))),
1532
1833
  h("div", { className: "dshd-filterfoot", key: "foot" }, [
1533
- h("button", { key: "clearbtn", type: "button", className: "dshd-linkbtn", onClick: clearFilters, disabled: filterCount === 0 }, "清空筛选"),
1534
- h(Btn, { key: "done", onClick: function () { setFilterOpen(false); } }, "完成"),
1834
+ h("button", { key: "clearbtn", type: "button", className: "dshd-linkbtn", onClick: clearFilters, disabled: filterCount === 0 }, t("清空筛选")),
1835
+ h(Btn, { key: "done", onClick: function () { setFilterOpen(false); } }, t("完成")),
1535
1836
  ]),
1536
1837
  ]) : null,
1537
1838
  ]) : null,
@@ -1540,15 +1841,15 @@ function DreamPanel() {
1540
1841
  onClick: togglePrivacy,
1541
1842
  on: privacy === true,
1542
1843
  pressed: privacy === true,
1543
- title: "显示层模糊;DOM 仍为明文,不改文件、不加密",
1544
- }, privacy === true ? "隐私模式:开" : "隐私模式"),
1545
- h(Btn, { key: "refresh", onClick: reload, disabled: loading === true, title: "重新读取梦境日记" }, "刷新"),
1844
+ title: t("显示层模糊;DOM 仍为明文,不改文件、不加密"),
1845
+ }, privacy === true ? t("隐私模式:开") : t("隐私模式")),
1846
+ h(Btn, { key: "refresh", onClick: reload, disabled: loading === true, title: t("重新读取梦境日记") }, t("刷新")),
1546
1847
  ]);
1547
1848
 
1548
1849
  const errorBox = error === null ? null : h("div", { className: "dshd-alert error", role: "alert", key: "error" }, [
1549
- h("span", { key: "m" }, "读取梦境日记失败:" + (error.message === "" ? String(error) : error.message)),
1850
+ h("span", { key: "m" }, t("读取梦境日记失败:") + displayError(error)),
1550
1851
  h("span", { className: "dshd-spacer", key: "s" }),
1551
- h(Btn, { key: "retry", onClick: reload, variant: "primary" }, "重试"),
1852
+ h(Btn, { key: "retry", onClick: reload, variant: "primary" }, t("重试")),
1552
1853
  ]);
1553
1854
 
1554
1855
  const body = function () {
@@ -1558,8 +1859,8 @@ function DreamPanel() {
1558
1859
  if (vm.count === 0) {
1559
1860
  if (error !== null) return h("div", { key: "failed-wrap" }, [
1560
1861
  h("div", { className: "dshd-empty", key: "failed" }, [
1561
- h("h3", { key: "t" }, "暂时读不到梦境日记"),
1562
- h("p", { key: "p" }, "等宿主路由就绪后点上面的「重试」;如果一直失败,看一眼 DSH 日志里有没有 [dsh-dream] 告警。"),
1862
+ h("h3", { key: "t" }, t("暂时读不到梦境日记")),
1863
+ h("p", { key: "p" }, t("等宿主路由就绪后点上面的「重试」;如果一直失败,看一眼 DSH 日志里有没有 [dsh-dream] 告警。")),
1563
1864
  ]),
1564
1865
  ]);
1565
1866
  return h(EmptyState, { key: "empty", query: query, onClear: query === "" ? null : clearSearch });
@@ -1567,10 +1868,10 @@ function DreamPanel() {
1567
1868
  return h("div", { key: "content" }, [
1568
1869
  vm.showTiles
1569
1870
  ? h("div", { className: "dshd-tiles", key: "tiles" }, [
1570
- h(StatTile, { key: "total", label: "梦境总数", value: String(vm.total) + " 场", hint: vm.total > vm.count ? "本页显示最近 " + String(vm.count) + " 场" : "全部梦境日记" }),
1571
- h(StatTile, { key: "span", label: "时间跨度", value: vm.spanText, hint: vm.spanHint, title: vm.spanFull }),
1572
- h(StatTile, { key: "latest", label: "最近一场", value: vm.latestText, hint: vm.latestDate }),
1573
- h(StatTile, { key: "mood", label: "心境", value: String(vm.moodCount) + " 种", hint: vm.moodCount === 0 ? "" : "最常见:" + vm.moodRows[0].mood }),
1871
+ h(StatTile, { key: "total", label: t("梦境总数"), value: String(vm.total) + t(" 场"), hint: vm.total > vm.count ? t("本页显示最近 ") + String(vm.count) + t(" 场") : t("全部梦境日记") }),
1872
+ h(StatTile, { key: "span", label: t("时间跨度"), value: vm.spanText, hint: vm.spanHint, title: vm.spanFull }),
1873
+ h(StatTile, { key: "latest", label: t("最近一场"), value: vm.latestText, hint: vm.latestDate }),
1874
+ h(StatTile, { key: "mood", label: t("心境"), value: String(vm.moodCount) + t(" 种"), hint: vm.moodCount === 0 ? "" : t("最常见:") + vm.moodRows[0].mood }),
1574
1875
  ])
1575
1876
  : h("div", { className: "dshd-headline", key: "headline" }, vm.headline),
1576
1877
  h("div", { className: "dshd-two", key: "two" }, [
@@ -1578,14 +1879,14 @@ function DreamPanel() {
1578
1879
  h(LessonCard, { key: "lessons", vm: vm }),
1579
1880
  ]),
1580
1881
  h("div", { className: "dshd-listhead", key: "listhead" }, [
1581
- h("h4", { key: "t" }, query === "" && filterCount === 0 ? "梦境时间线" : (query === "" ? "筛选结果" : "搜索结果")),
1882
+ h("h4", { key: "t" }, query === "" && filterCount === 0 ? t("梦境时间线") : (query === "" ? t("筛选结果") : t("搜索结果"))),
1582
1883
  h("span", { className: "dshd-hint", key: "n" }, listHintOf(query, vm.count, filterCount, visibleDreams.length)),
1583
1884
  ]),
1584
1885
  visibleDreams.length === 0 && filterCount > 0
1585
1886
  ? h("div", { className: "dshd-filterempty", key: "filtered" }, [
1586
- h("span", { key: "t" }, "没有符合筛选的梦(筛选作用于已加载的 " + String(vm.count) + " 场)。"),
1887
+ h("span", { key: "t" }, t("没有符合筛选的梦(筛选作用于已加载的 ") + String(vm.count) + t(" 场)。")),
1587
1888
  h("span", { className: "dshd-spacer", key: "s" }),
1588
- h(Btn, { key: "clearbtn", onClick: clearFilters }, "清空筛选"),
1889
+ h(Btn, { key: "clearbtn", onClick: clearFilters }, t("清空筛选")),
1589
1890
  ])
1590
1891
  : h("div", { className: "dshd-timeline", key: "timeline" }, visibleDreams.map(function (dream, index) {
1591
1892
  return h(DreamCard, { key: (dream.id !== "" ? dream.id : "dream") + "#" + String(index), dream: dream, now: now, privacy: privacy, index: index });
@@ -1596,26 +1897,27 @@ function DreamPanel() {
1596
1897
  return h("div", { className: "dshd-root" + (privacy === true ? " dshd-privacy" : ""), key: "root" }, [
1597
1898
  h("div", { className: "dshd-shell", key: "shell" }, [
1598
1899
  errorBox,
1599
- h("nav", { className: "dshd-tabs", key: "tabs", "aria-label": "梦境面板视图" }, [["journal", "日记"], ["knowledge", "经验"], ["bridge", "写入记录"]].map(function (item) {
1900
+ h("nav", { className: "dshd-tabs", key: "tabs", "aria-label": t("梦境面板视图") }, [["journal", t("日记")], ["knowledge", t("经验")], ["bridge", t("写入记录")]].map(function (item) {
1600
1901
  return h("button", { key: item[0], type: "button", className: "dshd-chipbtn" + (activeTab === item[0] ? " on" : ""), "aria-pressed": activeTab === item[0], onClick: function () { setFilterOpen(false); setActiveTab(item[0]); } }, item[1]);
1601
1902
  })),
1602
1903
  toolbar,
1603
- privacy === true ? h("div", { className: "dshd-note", key: "privacynote", role: "status" }, "隐私模式:显示层模糊;DOM 仍为明文,不改文件、不加密。点击卡片可临时查看。") : null,
1904
+ privacy === true ? h("div", { className: "dshd-note", key: "privacynote", role: "status" }, t("隐私模式:显示层模糊;DOM 仍为明文,不改文件、不加密。点击卡片可临时查看。")) : null,
1604
1905
  body(),
1605
1906
  activeTab !== "journal" ? null : h("div", { className: "dshd-footer", key: "footer" }, [
1606
1907
  h("span", { key: "l" }, footerTextOf(query, limit, vm.total, vm.count)),
1607
1908
  h("span", { className: "dshd-spacer", key: "sp" }),
1608
- canLoadMore === true ? h(Btn, { key: "more", onClick: showMore, disabled: loading === true }, loading === true ? "加载中…" : "显示更早的梦") : null,
1609
- h("span", { key: "r" }, "数据来自梦境日记(dreams.jsonl)"),
1909
+ canLoadMore === true ? h(Btn, { key: "more", onClick: showMore, disabled: loading === true }, loading === true ? t("加载中…") : t("显示更早的梦")) : null,
1910
+ h("span", { key: "r" }, t("数据来自梦境日记(dreams.jsonl)")),
1610
1911
  ]),
1611
1912
  ]),
1612
1913
  ]);
1613
1914
  }
1614
1915
 
1615
- function DreamSection() {
1916
+ function DreamSection({ locale } = {}) {
1917
+ useLanguage(locale);
1616
1918
  return h("div", null, [
1617
- h("h2", { key: "t" }, "梦境日记"),
1618
- h("p", { className: "dshd-sub", key: "p" }, "会话回放 → 反思 → 记忆巩固。这里按时间倒序陈列 agent 做过的梦,附心境方块与教训榜。"),
1919
+ h("h2", { key: "t" }, t("梦境日记")),
1920
+ h("p", { className: "dshd-sub", key: "p" }, t("会话回放 → 反思 → 记忆巩固。这里按时间倒序陈列 agent 做过的梦,附心境方块与教训榜。")),
1619
1921
  h(DreamPanel, { key: "panel" }),
1620
1922
  ]);
1621
1923
  }
@@ -1761,9 +2063,10 @@ const CSS = [
1761
2063
 
1762
2064
  // ───────────────────────── 挂载 ─────────────────────────
1763
2065
 
1764
- const inject = ["slots"];
2066
+ const inject = ["slots", "locale"];
1765
2067
 
1766
2068
  function apply(ctx) {
2069
+ try { localeService = ctx.get?.("locale") || ctx.locale; } catch { localeService = ctx.locale; }
1767
2070
  try {
1768
2071
  ctx.effect(function () {
1769
2072
  const id = "dsh-dream/client";
@@ -1783,8 +2086,8 @@ function apply(ctx) {
1783
2086
  name: "settings.section",
1784
2087
  id: "dsh-dream",
1785
2088
  order: 55,
1786
- label: function () { return "梦境日记"; },
1787
- inject: function () { return {}; },
2089
+ label: function () { return t("梦境日记"); },
2090
+ inject: function () { return { locale: localeService }; },
1788
2091
  }, DreamSection);
1789
2092
  });
1790
2093
  } catch (error) { /* 没有 slots 服务:仍是合法插件,只是没有设置页 */ }
@@ -1794,6 +2097,7 @@ exports.apply = apply;
1794
2097
  exports.inject = inject;
1795
2098
  // 纯函数内部件:验证脚本可直接取来测,不必另抄一份算法。
1796
2099
  exports.__internals = {
2100
+ displayError: displayError,
1797
2101
  routeUrl: routeUrl,
1798
2102
  routeUrlFor: routeUrlFor,
1799
2103
  KNOWLEDGE_ROUTE: KNOWLEDGE_ROUTE,