@stardustlc/dsh-dream 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -16
- package/README.en.md +24 -113
- package/README.md +23 -160
- package/cordis.patch.yml +4 -0
- package/docs/AUTOMATIC-MEMORY-PLAN.md +23 -0
- package/docs/CHANGELOG.en.md +43 -0
- package/docs/USAGE.en.md +98 -0
- package/docs/USAGE.md +157 -0
- package/docs/VALIDATION.md +29 -0
- package/docs/validation/0.5.1.md +68 -0
- package/docs/validation/0.5.2.md +39 -0
- package/docs/validation/0.6.0.md +73 -0
- package/docs/validation/0.7.0.md +11 -0
- package/docs/validation/0.8.0.md +21 -0
- package/docs/validation-language-2026-10-03.md +16 -0
- package/lib/automatic-state.d.ts +20 -0
- package/lib/automatic-state.js +76 -0
- package/lib/automatic.d.ts +36 -0
- package/lib/automatic.js +376 -0
- package/lib/client.js +610 -232
- package/lib/config.d.ts +9 -0
- package/lib/config.js +8 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +3 -0
- package/lib/web-actions.d.ts +1 -0
- package/lib/web-actions.js +15 -0
- package/package.json +4 -3
- package/skills/dream-protocol/SKILL.md +8 -4
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
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
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,316 @@ 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
|
+
"自动整理的内容在「经验」页待审阅;日记可让助手复盘时单独生成。": "Automatically collected lessons await review in Lessons. Ask the assistant to reflect when you want a separate journal entry.",
|
|
20
|
+
"查看待审阅经验": "Review candidate lessons",
|
|
21
|
+
"请求失败(HTTP ": "Request failed (HTTP ",
|
|
22
|
+
"服务器返回的不是 JSON(HTTP ": "The server did not return JSON (HTTP ",
|
|
23
|
+
"登录状态已失效,请重新打开 DSH 页面后重试。": "Your session has expired. Reopen DSH and try again.",
|
|
24
|
+
"当前宿主尚未提供此操作,请升级 DSH,或使用下方对话命令。": "This host does not support this action. Update DSH or use the chat command below.",
|
|
25
|
+
"候选(未采纳)": "Candidate (not accepted)",
|
|
26
|
+
"可用": "Usable",
|
|
27
|
+
"有冲突": "Disputed",
|
|
28
|
+
"待复核": "Needs review",
|
|
29
|
+
"已驳回": "Rejected",
|
|
30
|
+
"未知状态": "Unknown state",
|
|
31
|
+
"平台 ": "Platform ",
|
|
32
|
+
";等 ": "; and ",
|
|
33
|
+
" 条适用范围": " more conditions",
|
|
34
|
+
"范围未标注": "Scope not specified",
|
|
35
|
+
"暂无可展示的证据摘要": "No evidence summary available",
|
|
36
|
+
"尚未核验": "Not verified yet",
|
|
37
|
+
"没有 lastValidatedAt(从未核验)": "No lastValidatedAt (never verified)",
|
|
38
|
+
"未加载的经验(": "Lesson not loaded (",
|
|
39
|
+
"共 ": "Total: ",
|
|
40
|
+
" 条": " records",
|
|
41
|
+
"还没有经验": "No lessons yet",
|
|
42
|
+
"日志过大,仅回放最近一段事件": "The log is large; only recent events were replayed",
|
|
43
|
+
"有 ": "",
|
|
44
|
+
" 行损坏已跳过": " damaged lines were skipped",
|
|
45
|
+
";events.jsonl 未改动。": "; events.jsonl was preserved.",
|
|
46
|
+
"还没有经验记录": "No lesson records yet",
|
|
47
|
+
"经验是带证据、有适用条件的技术结论:收尾时用 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.",
|
|
48
|
+
"旧日记里的教训不会自动升级为经验;采纳、驳回或标记冲突请用 dream_review。": "Journal observations do not become lessons automatically. Use dream_review to accept, reject or flag conflicts.",
|
|
49
|
+
"已驳回:不参与默认检索与注入": "Rejected: excluded from default retrieval and injection",
|
|
50
|
+
"待复核:条件或依赖可能已过期,默认不注入": "Needs review: conditions or dependencies may be outdated; not injected by default",
|
|
51
|
+
"冲突未解决:默认不注入,先 resolve-conflict": "Unresolved conflict: not injected by default; use resolve-conflict first",
|
|
52
|
+
"候选:可被检索到,但默认不作为任务建议注入": "Candidate: retrievable, but not injected as task advice by default",
|
|
53
|
+
"证据不足:没有独立证据,默认不注入": "Insufficient independent evidence: not injected by default",
|
|
54
|
+
"版本不匹配:当前依赖版本不满足适用范围": "Version mismatch: the dependency version is outside the applicable range",
|
|
55
|
+
"超出条数预算,本次未取回": "Not retrieved: record limit reached",
|
|
56
|
+
"超出字符预算:按排名整条装入,遇到第一条放不下即停": "Character limit reached: complete records are added by rank until the next one cannot fit",
|
|
57
|
+
"超出字符预算,本次未取回": "Not retrieved: character limit reached",
|
|
58
|
+
"新建": "Created",
|
|
59
|
+
"替换管理块": "Replaced managed block",
|
|
60
|
+
"追加管理块": "Appended managed block",
|
|
61
|
+
"未改动": "Unchanged",
|
|
62
|
+
"回滚": "Rolled back",
|
|
63
|
+
"未知动作": "Unknown action",
|
|
64
|
+
"时间未知": "Unknown time",
|
|
65
|
+
"没有记录时间": "No timestamp recorded",
|
|
66
|
+
"目标未记录": "Target not recorded",
|
|
67
|
+
"没有经验引用": "No lesson references",
|
|
68
|
+
"可回滚": "Rollback available",
|
|
69
|
+
"不可回滚": "Rollback unavailable",
|
|
70
|
+
"还没有写入记录": "No rule changes yet",
|
|
71
|
+
"写入记录来自 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.",
|
|
72
|
+
"在可桥接经验行点「复制预览命令」,回到对话里执行;写操作只在工具中完成。": "Preview an eligible project lesson, review the changes and apply them. Chat commands are also available.",
|
|
73
|
+
"没有待核实的经验 —— 候选、有冲突、待复核的经验会出现在这里,并写明被扣下的原因。": "Candidates, disputed lessons and lessons needing review appear here, with an explanation of why they are held.",
|
|
74
|
+
"刚刚": "Just now",
|
|
75
|
+
" 分钟前": " min ago",
|
|
76
|
+
" 小时前": " h ago",
|
|
77
|
+
" 天前": " days ago",
|
|
78
|
+
"今天": "Today",
|
|
79
|
+
"昨天": "Yesterday",
|
|
80
|
+
"筛选后 ": "Filtered: ",
|
|
81
|
+
" 场": " dreams",
|
|
82
|
+
"」命中 ": "” matches ",
|
|
83
|
+
" 场梦": " dreams",
|
|
84
|
+
"筛选当前已加载的 ": "Filtering the ",
|
|
85
|
+
" 场 · 命中 ": " loaded dreams · Matches: ",
|
|
86
|
+
"新梦在前": "Newest first",
|
|
87
|
+
"(已达路由上限)": " (route limit reached)",
|
|
88
|
+
"最近 ": "Recent: ",
|
|
89
|
+
" 场 / 共 ": " dreams / Total: ",
|
|
90
|
+
"新梦在前 · 最多显示 ": "Newest first · Up to ",
|
|
91
|
+
"搜索结果 · 最多显示 ": "Search results · Up to ",
|
|
92
|
+
"本页内最近 ": "Most recent on this page: ",
|
|
93
|
+
"(统计未给 lastAt,按本页已加载的 ": "(lastAt is missing; calculated from the ",
|
|
94
|
+
" 场现算)": " dreams loaded on this page)",
|
|
95
|
+
"同一天": "Same day",
|
|
96
|
+
"约 ": "About ",
|
|
97
|
+
" 天": " days",
|
|
98
|
+
"第 1 场梦 · ": "First dream · ",
|
|
99
|
+
" · 心境「": " · Mood: “",
|
|
100
|
+
" 场梦 · 本页最近 ": " dreams · Loaded: ",
|
|
101
|
+
"最近:": "Latest: ",
|
|
102
|
+
"心境": "Mood",
|
|
103
|
+
"1 场梦,先不谈分布 —— 再做梦会累积。": "One dream so far. More dreams will build a mood history.",
|
|
104
|
+
"左 → 右:从早到晚": "Left → right: oldest to newest",
|
|
105
|
+
"教训榜": "Recurring observations",
|
|
106
|
+
"还没有教训 —— 记梦时带上 lessons,出现次数会在这里累积。": "No observations yet. Include lessons when recording dreams to track recurring observations.",
|
|
107
|
+
"常见教训": "Recurring observation",
|
|
108
|
+
"次数仅表示日记里的出现频率;出现 ≥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.",
|
|
109
|
+
"复制命令": "Copy command",
|
|
110
|
+
"已复制": "Copied",
|
|
111
|
+
"剪贴板不可用:请手动选中上面的命令复制。": "Clipboard unavailable. Select and copy the command above manually.",
|
|
112
|
+
"重新送审": "Reopen for review",
|
|
113
|
+
"处理冲突": "Resolve conflict",
|
|
114
|
+
"驳回这条": "Reject this lesson",
|
|
115
|
+
"标记待复核": "Mark for review",
|
|
116
|
+
"驳回": "Reject",
|
|
117
|
+
"采纳": "Accept",
|
|
118
|
+
"管理块内容已一致,无需写入。": "The managed block already matches; no write was needed.",
|
|
119
|
+
"请求超时,请先刷新确认结果后再重试。": "Request timed out. Refresh to check the result before retrying.",
|
|
120
|
+
"已更新「": "Updated the review state of “",
|
|
121
|
+
"」的审阅状态。": "”.",
|
|
122
|
+
"已记录使用反馈;「": "Feedback recorded; the review state of “",
|
|
123
|
+
"」的审阅状态保持原样。": "” is unchanged.",
|
|
124
|
+
"的操作": " — actions",
|
|
125
|
+
"收起审阅": "Hide review",
|
|
126
|
+
"审阅详情": "Review details",
|
|
127
|
+
"预览规则变更": "Preview rule changes",
|
|
128
|
+
"复制项目预览命令": "Copy project preview command",
|
|
129
|
+
"正在处理…": "Working…",
|
|
130
|
+
"已刷新经验列表。": "Lesson list refreshed.",
|
|
131
|
+
"刷新列表": "Refresh list",
|
|
132
|
+
"审阅动作": "Review action",
|
|
133
|
+
"冲突经验": "Conflicting lesson",
|
|
134
|
+
"处理方式": "Resolution",
|
|
135
|
+
"采用这条,驳回对方": "Accept this lesson and reject the other",
|
|
136
|
+
"放弃这条,保留对方": "Reject this lesson and keep the other",
|
|
137
|
+
"合并到这条,对方留作历史": "Merge into this lesson and retain the other as history",
|
|
138
|
+
"处理依据": "Reason for resolution",
|
|
139
|
+
"说明哪个来源支持这次决定…": "Explain which source supports this decision…",
|
|
140
|
+
"确认审阅": "Confirm review",
|
|
141
|
+
"在对话里处理": "Handle in chat",
|
|
142
|
+
"选择冲突经验并填写处理依据后即可处理。": "Select the conflicting lesson and provide a reason first.",
|
|
143
|
+
"复制审阅命令": "Copy review command",
|
|
144
|
+
"规则变更预览": "Rule change preview",
|
|
145
|
+
"目标:": "Target: ",
|
|
146
|
+
"管理块内容已经一致,无需更改。": "The managed block already matches; no changes are needed.",
|
|
147
|
+
"将按此预览重建 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.",
|
|
148
|
+
"规则已应用,可在「写入记录」中回滚。": "Rules applied. You can roll back from the Rule changes tab.",
|
|
149
|
+
"应用此变更": "Apply these changes",
|
|
150
|
+
"取消预览": "Cancel preview",
|
|
151
|
+
"使用反馈 · 有用 ": "Usage feedback · Useful: ",
|
|
152
|
+
" · 不适用 ": " · Not applicable: ",
|
|
153
|
+
"场景备注(可选)": "Context note (optional)",
|
|
154
|
+
"反馈场景备注": "Feedback context note",
|
|
155
|
+
"这次在哪个场景使用…": "Where did you use this lesson…",
|
|
156
|
+
"这次有用": "Useful this time",
|
|
157
|
+
"这次不适用": "Not applicable this time",
|
|
158
|
+
"记录使用情况,供之后复核;采纳与驳回由审阅决定。": "Feedback records usage for future review. Acceptance and rejection remain review decisions.",
|
|
159
|
+
"有用": "Useful",
|
|
160
|
+
"不适用": "Not applicable",
|
|
161
|
+
"已恢复上一次的 Dream 管理块,块外内容保留。": "The previous Dream managed block was restored; content outside the block was preserved.",
|
|
162
|
+
"取消回滚": "Cancel rollback",
|
|
163
|
+
"回滚这次变更": "Roll back this change",
|
|
164
|
+
"将恢复「": "Restore the previous Dream managed block in “",
|
|
165
|
+
"」的前一个 Dream 管理块。块外内容保留;管理块已被修改时会拒绝回滚。": "”. Content outside the block is preserved. Rollback is refused if the managed block has changed.",
|
|
166
|
+
"正在回滚…": "Rolling back…",
|
|
167
|
+
"确认回滚管理块": "Confirm block rollback",
|
|
168
|
+
"隐私模式:点击此条临时查看": "Privacy mode: click to reveal temporarily",
|
|
169
|
+
"最近核验:": "Last verified: ",
|
|
170
|
+
"何时用:": "When to use: ",
|
|
171
|
+
"未标注适用条件": "No conditions specified",
|
|
172
|
+
"怎么做:": "What to do: ",
|
|
173
|
+
"未标注具体动作": "No action specified",
|
|
174
|
+
"适用范围与证据": "Scope and evidence",
|
|
175
|
+
"范围:": "Scope: ",
|
|
176
|
+
"适用:": "Applies to: ",
|
|
177
|
+
"例外:": "Exceptions: ",
|
|
178
|
+
"证据:": "Evidence: ",
|
|
179
|
+
"没有关联来源记录,采纳前请核对依据。": "No linked sources. Check the evidence before accepting.",
|
|
180
|
+
"来源记录 · ": "Source record · ",
|
|
181
|
+
"已读到来源": "Source read",
|
|
182
|
+
"待核验": "Unverified",
|
|
183
|
+
"会话": "Session",
|
|
184
|
+
"用户纠正": "User correction",
|
|
185
|
+
"本地资料": "Local artifact",
|
|
186
|
+
"未提供摘要": "No summary provided",
|
|
187
|
+
"会话:": "Session: ",
|
|
188
|
+
" · 记录 ": " · Record ",
|
|
189
|
+
"记录时间:": "Recorded: ",
|
|
190
|
+
"核验说明:": "Verification: ",
|
|
191
|
+
"已核对当前会话原文;不代表结论正确。": "Matched the current session source; this does not prove the conclusion is correct.",
|
|
192
|
+
"来源校验值:": "Source hash: ",
|
|
193
|
+
"为什么被扣下:": "Why held: ",
|
|
194
|
+
"已审阅经验": "Reviewed lessons",
|
|
195
|
+
"重试": "Retry",
|
|
196
|
+
"读取经验失败:": "Could not load lessons: ",
|
|
197
|
+
"旧日记不受影响;修好后点「重试」。": "Your journal is preserved. Retry after resolving the error.",
|
|
198
|
+
"经验": "Lessons",
|
|
199
|
+
"加载中…": "Loading…",
|
|
200
|
+
"查看 ": "Show ",
|
|
201
|
+
"经验状态": "Lesson state",
|
|
202
|
+
"可用经验": "Usable lessons",
|
|
203
|
+
"已驳回历史": "Rejected history",
|
|
204
|
+
"暂无可用经验;待核实的记录在下方。": "No usable lessons yet. Records needing review are below.",
|
|
205
|
+
"没有已驳回记录。": "No rejected records.",
|
|
206
|
+
"显示更多经验": "Show more lessons",
|
|
207
|
+
"只显示最近 ": "Showing the most recent ",
|
|
208
|
+
" 条经验。": " lessons.",
|
|
209
|
+
"采纳记录仍需满足当前任务的平台、版本与项目条件;来源已读不代表结论永远适用。": "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.",
|
|
210
|
+
"待核实": "Needs verification",
|
|
211
|
+
" 条待核实": " need verification",
|
|
212
|
+
"没有待核实的经验": "No lessons awaiting verification",
|
|
213
|
+
"显示更多待核实经验": "Show more pending lessons",
|
|
214
|
+
"这些经验默认不会作为任务建议注入。先核对来源与适用范围,再决定采纳、驳回或继续复核。": "These lessons are not injected as task advice by default. Check sources and scope before accepting, rejecting or reviewing further.",
|
|
215
|
+
"写入记录": "Rule changes",
|
|
216
|
+
"读取写入记录失败:": "Could not load rule changes: ",
|
|
217
|
+
" 次写入 · ": " changes · ",
|
|
218
|
+
" 次可回滚": " can be rolled back",
|
|
219
|
+
"经验:": "Lessons: ",
|
|
220
|
+
"前哈希:": "Before hash: ",
|
|
221
|
+
"后哈希:": "After hash: ",
|
|
222
|
+
"备份标识与回滚命令": "Backup ID and rollback command",
|
|
223
|
+
" 条记录。": " records.",
|
|
224
|
+
"回滚恢复对应备份的 Dream 管理块;块外内容保留,外部修改的管理块会停止回滚。": "Rollback restores the backed-up Dream managed block and preserves content outside it. External changes to the block prevent rollback.",
|
|
225
|
+
"隐私模式:点击卡片临时查看": "Privacy mode: click the card to reveal temporarily",
|
|
226
|
+
"心境:": "Mood: ",
|
|
227
|
+
"收起": "Collapse",
|
|
228
|
+
"展开全文": "Read more",
|
|
229
|
+
"没有找到「": "No dreams matching “",
|
|
230
|
+
"」相关的梦": "”",
|
|
231
|
+
"换个关键词试试,或者清空搜索看全部梦境。": "Try another keyword, or clear the search to see all dreams.",
|
|
232
|
+
"清空搜索": "Clear search",
|
|
233
|
+
"还没有梦境日记": "No dreams yet",
|
|
234
|
+
"对 agent 说「做个梦」,它会回放最近的会话、反思之后把第一场梦写进这里。": "Ask your agent to “have a dream”. It will replay recent sessions, reflect and record its first dream here.",
|
|
235
|
+
"对 agent 说「做个梦」": "Ask your agent to “have a dream”",
|
|
236
|
+
"搜索反思与教训…": "Search reflections and observations…",
|
|
237
|
+
"搜索梦境": "Search dreams",
|
|
238
|
+
"筛选作用于已加载窗口,不影响全量统计": "Filters apply to loaded dreams; overall statistics are unchanged",
|
|
239
|
+
"筛选": "Filter",
|
|
240
|
+
"筛选梦境": "Filter dreams",
|
|
241
|
+
" 场,不影响全量统计": " loaded dreams; overall statistics are unchanged",
|
|
242
|
+
"只看有教训的梦": "Only dreams with observations",
|
|
243
|
+
"清空筛选": "Clear filters",
|
|
244
|
+
"完成": "Done",
|
|
245
|
+
"显示层模糊;DOM 仍为明文,不改文件、不加密": "Display blur only; the DOM remains readable. Files are unchanged and not encrypted",
|
|
246
|
+
"隐私模式:开": "Privacy mode: on",
|
|
247
|
+
"隐私模式": "Privacy mode",
|
|
248
|
+
"重新读取梦境日记": "Reload the dream journal",
|
|
249
|
+
"刷新": "Refresh",
|
|
250
|
+
"读取梦境日记失败:": "Could not load the dream journal: ",
|
|
251
|
+
"暂时读不到梦境日记": "The dream journal is unavailable",
|
|
252
|
+
"等宿主路由就绪后点上面的「重试」;如果一直失败,看一眼 DSH 日志里有没有 [dsh-dream] 告警。": "Retry after the host is ready. If the problem persists, check DSH logs for [dsh-dream] warnings.",
|
|
253
|
+
"梦境总数": "Total dreams",
|
|
254
|
+
"本页显示最近 ": "Most recent loaded: ",
|
|
255
|
+
"全部梦境日记": "All dreams",
|
|
256
|
+
"时间跨度": "Time span",
|
|
257
|
+
"最近一场": "Latest dream",
|
|
258
|
+
" 种": " moods",
|
|
259
|
+
"最常见:": "Most common: ",
|
|
260
|
+
"梦境时间线": "Dream timeline",
|
|
261
|
+
"筛选结果": "Filtered results",
|
|
262
|
+
"搜索结果": "Search results",
|
|
263
|
+
"没有符合筛选的梦(筛选作用于已加载的 ": "No matching dreams (filters apply to the ",
|
|
264
|
+
" 场)。": " loaded dreams).",
|
|
265
|
+
"梦境面板视图": "Dream views",
|
|
266
|
+
"日记": "Journal",
|
|
267
|
+
"隐私模式:显示层模糊;DOM 仍为明文,不改文件、不加密。点击卡片可临时查看。": "Privacy mode blurs the display only. The DOM remains readable; files are unchanged and not encrypted. Click a card to reveal it temporarily.",
|
|
268
|
+
"显示更早的梦": "Show older dreams",
|
|
269
|
+
"数据来自梦境日记(dreams.jsonl)": "Data from the dream journal (dreams.jsonl)",
|
|
270
|
+
"梦境日记": "Dream journal",
|
|
271
|
+
"自动整理任务经验,审阅后回用;日记保留独立的反思记录。": "Collect lessons from tasks and reuse them after review; journals keep separate reflections."
|
|
272
|
+
});
|
|
273
|
+
function languageOf(locale = localeService) {
|
|
274
|
+
try { return String(locale?.getSnapshot?.().active || '').startsWith('en') ? 'en' : 'zh'; } catch { return 'zh'; }
|
|
275
|
+
}
|
|
276
|
+
function t(zh) { return languageOf() === 'en' ? (EN[zh] ?? zh) : zh; }
|
|
277
|
+
function displayError(error) {
|
|
278
|
+
const message = error?.message || String(error || '');
|
|
279
|
+
if (languageOf() !== 'en') return message;
|
|
280
|
+
if (EN[message]) return EN[message];
|
|
281
|
+
if (!/[\u3400-\u9fff]/.test(message)) return message;
|
|
282
|
+
const hints = {
|
|
283
|
+
revision: 'This lesson or project changed. Refresh and review again; your input is preserved.',
|
|
284
|
+
conflict: 'The target changed. Preview the current rules again before applying.',
|
|
285
|
+
'preview-mismatch': 'The preview no longer matches. Create a new preview.',
|
|
286
|
+
'preview-expired': 'The preview expired. Create a new preview.',
|
|
287
|
+
'not-rollbackable': 'Rollback is unavailable because the managed block changed or its backup is missing.',
|
|
288
|
+
'ineligible': 'This lesson is not eligible for rule writing. Check its scope and review state.',
|
|
289
|
+
'read-only': 'The target is read-only. Check its permissions before retrying.',
|
|
290
|
+
'marker': 'The managed block markers are invalid. Check the file before retrying.',
|
|
291
|
+
'path-outside-project': 'The target must stay inside the project directory.',
|
|
292
|
+
'unsupported-encoding': 'The target file encoding is unsupported. Use UTF-8.',
|
|
293
|
+
'target-is-directory': 'The selected target is a directory; select a file.',
|
|
294
|
+
'not-found': 'The lesson, target or backup was not found. Refresh and check the project.',
|
|
295
|
+
forbidden: 'This operation must be performed from the authenticated Dream panel.',
|
|
296
|
+
'too-large': 'The request is too large. Shorten the input and try again.',
|
|
297
|
+
cancelled: 'The operation was cancelled. Refresh to check the result before retrying.',
|
|
298
|
+
io: 'The operation could not access its files. Check the project location and file permissions, then refresh before retrying.',
|
|
299
|
+
internal: 'Could not load the records. Check the DSH logs and retry.',
|
|
300
|
+
invalid: 'The input or selected record is no longer valid. Check the fields and refresh the list.',
|
|
301
|
+
};
|
|
302
|
+
return (hints[error?.code] || 'The operation failed. Check the DSH logs, then refresh before retrying.')
|
|
303
|
+
+ (error?.status ? ' (HTTP ' + error.status + (error.code ? ', ' + error.code : '') + ')' : '');
|
|
304
|
+
}
|
|
305
|
+
function rollbackReasonText(reason) {
|
|
306
|
+
if (languageOf() !== 'en') return reason;
|
|
307
|
+
return ({ '目标文件不可读': 'Target file is unreadable', '文件不存在': 'File does not exist',
|
|
308
|
+
'管理块标记异常': 'Invalid managed block markers', '管理块已被移除': 'Managed block was removed',
|
|
309
|
+
'管理块已被外部修改': 'Managed block changed externally' })[reason]
|
|
310
|
+
|| (/[\u3400-\u9fff]/.test(reason) ? 'Check the target file and backup' : reason);
|
|
311
|
+
}
|
|
312
|
+
function scopeText(label) {
|
|
313
|
+
if (languageOf() !== 'en') return label;
|
|
314
|
+
return label.replace(/^全局$/, 'Global').replace(/^工作区 /, 'Workspace ').replace(/^项目 /, 'Project ');
|
|
315
|
+
}
|
|
316
|
+
function evidenceText(summary) {
|
|
317
|
+
if (languageOf() !== 'en') return summary;
|
|
318
|
+
return summary.replace(/^独立证据 (\d+) 条(已读 (\d+) \/ 仅声明 (\d+)):/, 'Independent sources: $1 (read: $2 / claimed: $3): ');
|
|
319
|
+
}
|
|
320
|
+
function useLanguage(locale) {
|
|
321
|
+
const [, rerender] = useState(0);
|
|
322
|
+
useEffect(() => { const update = () => rerender(n => n + 1); update(); return locale?.subscribe?.(update); }, [locale]);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
|
|
22
326
|
// 相对路径按 document.baseURI 解析:反代挂在 /tools/dsh/ 这类前缀下也不会打偏。
|
|
23
327
|
const ROUTE = "_dsh/dsh-dream/journal";
|
|
24
328
|
const ACTION_ROUTE = "api/dsh-dream/actions";
|
|
@@ -74,7 +378,7 @@ function errorMessageOf(body, status) {
|
|
|
74
378
|
if (body.error !== null && typeof body.error === "object" && typeof body.error.message === "string" && body.error.message !== "") return body.error.message;
|
|
75
379
|
if (typeof body.message === "string" && body.message !== "") return body.message;
|
|
76
380
|
}
|
|
77
|
-
return "请求失败(HTTP " + String(status) + ")";
|
|
381
|
+
return t("请求失败(HTTP ") + String(status) + ")";
|
|
78
382
|
}
|
|
79
383
|
|
|
80
384
|
/** GET /_dsh/dsh-dream/journal:契约体 { dreams, stats, query, limit }。 */
|
|
@@ -87,7 +391,7 @@ async function fetchJournal(query, limit) {
|
|
|
87
391
|
const res = await fetch(routeUrl(params.join("&")), init);
|
|
88
392
|
let body = null;
|
|
89
393
|
try { body = await res.json(); } catch (error) { body = null; }
|
|
90
|
-
if (body === null || typeof body !== "object") throw new Error("服务器返回的不是 JSON(HTTP " + String(res.status) + ")");
|
|
394
|
+
if (body === null || typeof body !== "object") throw new Error(t("服务器返回的不是 JSON(HTTP ") + String(res.status) + ")");
|
|
91
395
|
if (res.ok !== true || body.ok === false) {
|
|
92
396
|
const failure = new Error(errorMessageOf(body, res.status));
|
|
93
397
|
failure.status = res.status;
|
|
@@ -111,7 +415,7 @@ async function fetchKnowledge() {
|
|
|
111
415
|
const res = await fetch(routeUrlFor(KNOWLEDGE_ROUTE, ""), init);
|
|
112
416
|
let body = null;
|
|
113
417
|
try { body = await res.json(); } catch (error) { body = null; }
|
|
114
|
-
if (body === null || typeof body !== "object") throw new Error("服务器返回的不是 JSON(HTTP " + String(res.status) + ")");
|
|
418
|
+
if (body === null || typeof body !== "object") throw new Error(t("服务器返回的不是 JSON(HTTP ") + String(res.status) + ")");
|
|
115
419
|
if (res.ok !== true || body.ok === false) {
|
|
116
420
|
const failure = new Error(errorMessageOf(body, res.status));
|
|
117
421
|
failure.status = res.status;
|
|
@@ -134,7 +438,7 @@ async function fetchBridgeRecords() {
|
|
|
134
438
|
const res = await fetch(routeUrlFor(BRIDGE_ROUTE, ""), init);
|
|
135
439
|
let body = null;
|
|
136
440
|
try { body = await res.json(); } catch (error) { body = null; }
|
|
137
|
-
if (body === null || typeof body !== "object") throw new Error("服务器返回的不是 JSON(HTTP " + String(res.status) + ")");
|
|
441
|
+
if (body === null || typeof body !== "object") throw new Error(t("服务器返回的不是 JSON(HTTP ") + String(res.status) + ")");
|
|
138
442
|
if (res.ok !== true || body.ok === false) {
|
|
139
443
|
const failure = new Error(errorMessageOf(body, res.status));
|
|
140
444
|
failure.status = res.status;
|
|
@@ -155,8 +459,8 @@ async function postAction(body) {
|
|
|
155
459
|
let value = null;
|
|
156
460
|
try { value = await res.json(); } catch (error) { /* Preserve HTTP status for useful recovery hints. */ }
|
|
157
461
|
if (res.ok !== true || value === null || value.ok === false) {
|
|
158
|
-
const hint = res.status === 401 ? "登录状态已失效,请重新打开 DSH 页面后重试。"
|
|
159
|
-
: res.status === 404 ? "当前宿主尚未提供此操作,请升级 DSH,或使用下方对话命令。"
|
|
462
|
+
const hint = res.status === 401 ? t("登录状态已失效,请重新打开 DSH 页面后重试。")
|
|
463
|
+
: res.status === 404 ? t("当前宿主尚未提供此操作,请升级 DSH,或使用下方对话命令。")
|
|
160
464
|
: errorMessageOf(value, res.status);
|
|
161
465
|
const failure = new Error(hint);
|
|
162
466
|
failure.code = value && value.error ? value.error.code : undefined;
|
|
@@ -257,12 +561,12 @@ function normalizeLessonState(value) {
|
|
|
257
561
|
/** 状态 → 中文标签(面板文案集中在这里)。 */
|
|
258
562
|
function lessonStateLabel(state) {
|
|
259
563
|
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 "未知状态";
|
|
564
|
+
case "candidate": return t("候选(未采纳)");
|
|
565
|
+
case "usable": return t("可用");
|
|
566
|
+
case "disputed": return t("有冲突");
|
|
567
|
+
case "stale": return t("待复核");
|
|
568
|
+
case "rejected": return t("已驳回");
|
|
569
|
+
default: return t("未知状态");
|
|
266
570
|
}
|
|
267
571
|
}
|
|
268
572
|
|
|
@@ -295,11 +599,11 @@ function applicabilityTextOf(value) {
|
|
|
295
599
|
const main = pkg !== "" ? (versions !== "" ? pkg + " " + versions : pkg) : versions;
|
|
296
600
|
const bits = [];
|
|
297
601
|
if (main !== "") bits.push(main);
|
|
298
|
-
if (platform !== "") bits.push("平台 " + platform);
|
|
602
|
+
if (platform !== "") bits.push(t("平台 ") + platform);
|
|
299
603
|
if (bits.length > 0) parts.push(bits.join(" · "));
|
|
300
604
|
}
|
|
301
605
|
if (parts.length === 0) return "";
|
|
302
|
-
if (parts.length > 4) return parts.slice(0, 4).join(";") + ";等 " + String(parts.length) + " 条适用范围";
|
|
606
|
+
if (parts.length > 4) return parts.slice(0, 4).join(";") + t(";等 ") + String(parts.length) + t(" 条适用范围");
|
|
303
607
|
return parts.join(";");
|
|
304
608
|
}
|
|
305
609
|
|
|
@@ -343,22 +647,22 @@ function normalizeKnowledge(value, now, limit) {
|
|
|
343
647
|
state: state,
|
|
344
648
|
stateLabel: lessonStateLabel(state),
|
|
345
649
|
stateClass: lessonStateClass(state),
|
|
346
|
-
scopeLabel: trimmedText(raw.scopeLabel) || "范围未标注",
|
|
347
|
-
evidenceSummary: trimmedText(raw.evidenceSummary) || "暂无可展示的证据摘要",
|
|
650
|
+
scopeLabel: scopeText(trimmedText(raw.scopeLabel)) || t("范围未标注"),
|
|
651
|
+
evidenceSummary: evidenceText(trimmedText(raw.evidenceSummary)) || t("暂无可展示的证据摘要"),
|
|
348
652
|
evidence: Array.isArray(source.evidence) && Array.isArray(raw.evidenceIds)
|
|
349
653
|
? source.evidence.filter(function (item) { return item && raw.evidenceIds.indexOf(item.id) !== -1; }).slice(0, 20) : [],
|
|
350
654
|
feedback: raw.feedback && typeof raw.feedback === "object" ? raw.feedback : { useful: 0, notApplicable: 0, recent: [] },
|
|
351
655
|
lastValidatedAt: lastValidatedAt,
|
|
352
|
-
lastText: lastValidatedAt === "" ? "尚未核验" : relativeTime(lastValidatedAt, now),
|
|
353
|
-
lastTitle: lastValidatedAt === "" ? "没有 lastValidatedAt(从未核验)" : formatDate(lastValidatedAt) + (formatTime(lastValidatedAt) === "" ? "" : " " + formatTime(lastValidatedAt)),
|
|
656
|
+
lastText: lastValidatedAt === "" ? t("尚未核验") : relativeTime(lastValidatedAt, now),
|
|
657
|
+
lastTitle: lastValidatedAt === "" ? t("没有 lastValidatedAt(从未核验)") : formatDate(lastValidatedAt) + (formatTime(lastValidatedAt) === "" ? "" : " " + formatTime(lastValidatedAt)),
|
|
354
658
|
revision: typeof raw.revision === "number" && Number.isFinite(raw.revision) && raw.revision >= 0 ? Math.floor(raw.revision) : 0,
|
|
355
659
|
independentSupportCount: typeof raw.independentSupportCount === "number" && raw.independentSupportCount > 0 ? Math.floor(raw.independentSupportCount) : 0,
|
|
356
660
|
projectId: typeof raw.scope === "object" && raw.scope !== null ? trimmedText(raw.scope.projectId) : "",
|
|
357
661
|
workspaceRoot: typeof raw.scope === "object" && raw.scope !== null ? trimmedText(raw.scope.workspaceRoot) : "",
|
|
358
662
|
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 + ")" }; }) : [],
|
|
663
|
+
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
664
|
holdReason: trimmedText(raw.holdReason) !== "" ? trimmedText(raw.holdReason) : holdReasonOf(raw),
|
|
361
|
-
holdReasonLabel: trimmedText(raw.holdReasonLabel) !== "" ? trimmedText(raw.holdReasonLabel) : skippedReasonLabel(holdReasonOf(raw)),
|
|
665
|
+
holdReasonLabel: trimmedText(raw.holdReasonLabel) !== "" ? t(trimmedText(raw.holdReasonLabel)) : skippedReasonLabel(holdReasonOf(raw)),
|
|
362
666
|
bridgeable: bridgeableOf(raw.state) && trimmedText(raw.id) !== "",
|
|
363
667
|
candidate: state === "candidate",
|
|
364
668
|
disputed: state === "disputed",
|
|
@@ -387,7 +691,7 @@ function knowledgeCountText(stats) {
|
|
|
387
691
|
}
|
|
388
692
|
if (parts.length === 0) {
|
|
389
693
|
const total = typeof source.lessons === "number" && source.lessons > 0 ? source.lessons : 0;
|
|
390
|
-
return total > 0 ? "共 " + String(total) + " 条" : "还没有经验";
|
|
694
|
+
return total > 0 ? t("共 ") + String(total) + t(" 条") : t("还没有经验");
|
|
391
695
|
}
|
|
392
696
|
return parts.join(" · ");
|
|
393
697
|
}
|
|
@@ -399,32 +703,32 @@ function knowledgeNoticeText(stats) {
|
|
|
399
703
|
const badLines = typeof source.badLines === "number" && Number.isFinite(source.badLines) && source.badLines > 0 ? Math.floor(source.badLines) : 0;
|
|
400
704
|
if (truncated !== true && badLines === 0) return "";
|
|
401
705
|
const parts = [];
|
|
402
|
-
if (truncated === true) parts.push("日志过大,仅回放最近一段事件");
|
|
403
|
-
if (badLines > 0) parts.push("有 " + String(badLines) + " 行损坏已跳过");
|
|
404
|
-
return parts.join(";") + ";events.jsonl 未改动。";
|
|
706
|
+
if (truncated === true) parts.push(t("日志过大,仅回放最近一段事件"));
|
|
707
|
+
if (badLines > 0) parts.push(t("有 ") + String(badLines) + t(" 行损坏已跳过"));
|
|
708
|
+
return parts.join(";") + t(";events.jsonl 未改动。");
|
|
405
709
|
}
|
|
406
710
|
|
|
407
711
|
/** 经验区块的独立空态:与「还没有梦境日记」分开,说明经验从哪来。 */
|
|
408
712
|
function knowledgeEmptyState() {
|
|
409
713
|
return {
|
|
410
|
-
title: "还没有经验记录",
|
|
411
|
-
body: "经验是带证据、有适用条件的技术结论:收尾时用 dream_learn 提交候选,证据足够、范围明确的会出现在这里。",
|
|
412
|
-
hint: "旧日记里的教训不会自动升级为经验;采纳、驳回或标记冲突请用 dream_review。",
|
|
714
|
+
title: t("还没有经验记录"),
|
|
715
|
+
body: t("经验是带证据、有适用条件的技术结论:收尾时用 dream_learn 提交候选,证据足够、范围明确的会出现在这里。"),
|
|
716
|
+
hint: t("旧日记里的教训不会自动升级为经验;采纳、驳回或标记冲突请用 dream_review。"),
|
|
413
717
|
};
|
|
414
718
|
}
|
|
415
719
|
|
|
416
720
|
/** 检索层 skipped reason 的中文映射(面板扣留原因文案;与 retrieval.ts 的 reason 常量同名)。 */
|
|
417
721
|
function skippedReasonLabel(reason) {
|
|
418
722
|
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 "超出字符预算,本次未取回";
|
|
723
|
+
case "state:rejected": return t("已驳回:不参与默认检索与注入");
|
|
724
|
+
case "state:stale": return t("待复核:条件或依赖可能已过期,默认不注入");
|
|
725
|
+
case "state:disputed": return t("冲突未解决:默认不注入,先 resolve-conflict");
|
|
726
|
+
case "candidate-hold": return t("候选:可被检索到,但默认不作为任务建议注入");
|
|
727
|
+
case "no-evidence": return t("证据不足:没有独立证据,默认不注入");
|
|
728
|
+
case "version-mismatch": return t("版本不匹配:当前依赖版本不满足适用范围");
|
|
729
|
+
case "limit": return t("超出条数预算,本次未取回");
|
|
730
|
+
case "budget-stop": return t("超出字符预算:按排名整条装入,遇到第一条放不下即停");
|
|
731
|
+
case "budget": return t("超出字符预算,本次未取回");
|
|
428
732
|
default: return "";
|
|
429
733
|
}
|
|
430
734
|
}
|
|
@@ -506,12 +810,12 @@ function shortHash(value) {
|
|
|
506
810
|
|
|
507
811
|
function bridgeActionLabel(action) {
|
|
508
812
|
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) || "未知动作";
|
|
813
|
+
case "create": return t("新建");
|
|
814
|
+
case "replace": return t("替换管理块");
|
|
815
|
+
case "append": return t("追加管理块");
|
|
816
|
+
case "unchanged": return t("未改动");
|
|
817
|
+
case "rollback": return t("回滚");
|
|
818
|
+
default: return trimmedText(action) || t("未知动作");
|
|
515
819
|
}
|
|
516
820
|
}
|
|
517
821
|
|
|
@@ -549,17 +853,17 @@ function normalizeBridgeRecords(value, now, limit) {
|
|
|
549
853
|
rows.push({
|
|
550
854
|
id: id,
|
|
551
855
|
at: at,
|
|
552
|
-
atText: at === "" ? "时间未知" : relativeTime(at, now),
|
|
553
|
-
atTitle: at === "" ? "没有记录时间" : formatDate(at) + (formatTime(at) === "" ? "" : " " + formatTime(at)),
|
|
554
|
-
target: target || "目标未记录",
|
|
856
|
+
atText: at === "" ? t("时间未知") : relativeTime(at, now),
|
|
857
|
+
atTitle: at === "" ? t("没有记录时间") : formatDate(at) + (formatTime(at) === "" ? "" : " " + formatTime(at)),
|
|
858
|
+
target: target || t("目标未记录"),
|
|
555
859
|
action: trimmedText(raw.action),
|
|
556
860
|
actionLabel: bridgeActionLabel(raw.action),
|
|
557
861
|
lessons: lessonRefs,
|
|
558
|
-
lessonsText: lessonRefs.length === 0 ? "没有经验引用" : lessonRefs.join("、"),
|
|
862
|
+
lessonsText: lessonRefs.length === 0 ? t("没有经验引用") : lessonRefs.join("、"),
|
|
559
863
|
beforeShort: shortHash(raw.beforeSha256),
|
|
560
864
|
afterShort: shortHash(raw.afterSha256),
|
|
561
865
|
rollbackable: rollbackable,
|
|
562
|
-
rollbackText: rollbackable ? "可回滚" : ("不可回滚" + (rollbackReason !== "" ?
|
|
866
|
+
rollbackText: rollbackable ? t("可回滚") : (t("不可回滚") + (rollbackReason !== "" ? (languageOf() === 'en' ? ': ' : ':') + rollbackReasonText(rollbackReason) : "")),
|
|
563
867
|
rollbackReason: rollbackReason,
|
|
564
868
|
});
|
|
565
869
|
}
|
|
@@ -576,15 +880,15 @@ function normalizeBridgeRecords(value, now, limit) {
|
|
|
576
880
|
/** 「写入记录」的独立空态。 */
|
|
577
881
|
function bridgeEmptyState() {
|
|
578
882
|
return {
|
|
579
|
-
title: "还没有写入记录",
|
|
580
|
-
body: "写入记录来自 dream_bridge 的 preview → apply;面板只读展示目标、时间、经验 revision 与前后哈希,不会写文件。",
|
|
581
|
-
hint: "在可桥接经验行点「复制预览命令」,回到对话里执行;写操作只在工具中完成。",
|
|
883
|
+
title: t("还没有写入记录"),
|
|
884
|
+
body: t("写入记录来自 dream_bridge 的 preview → apply;面板只读展示目标、时间、经验 revision 与前后哈希,不会写文件。"),
|
|
885
|
+
hint: t("在可桥接经验行点「复制预览命令」,回到对话里执行;写操作只在工具中完成。"),
|
|
582
886
|
};
|
|
583
887
|
}
|
|
584
888
|
|
|
585
889
|
/** 「待核实」的独立空态。 */
|
|
586
890
|
function reviewQueueEmptyState() {
|
|
587
|
-
return "没有待核实的经验 —— 候选、有冲突、待复核的经验会出现在这里,并写明被扣下的原因。";
|
|
891
|
+
return t("没有待核实的经验 —— 候选、有冲突、待复核的经验会出现在这里,并写明被扣下的原因。");
|
|
588
892
|
}
|
|
589
893
|
|
|
590
894
|
function spanOf(dreams) {
|
|
@@ -602,7 +906,7 @@ function spanOf(dreams) {
|
|
|
602
906
|
|
|
603
907
|
function formatDate(iso) {
|
|
604
908
|
const date = new Date(iso);
|
|
605
|
-
if (!Number.isFinite(date.getTime())) return iso === "" ? "时间未知" : iso;
|
|
909
|
+
if (!Number.isFinite(date.getTime())) return iso === "" ? t("时间未知") : iso;
|
|
606
910
|
return date.getFullYear() + "-" + pad2(date.getMonth() + 1) + "-" + pad2(date.getDate());
|
|
607
911
|
}
|
|
608
912
|
|
|
@@ -633,10 +937,10 @@ function relativeTime(iso, now) {
|
|
|
633
937
|
const minute = 60000;
|
|
634
938
|
const hour = 3600000;
|
|
635
939
|
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) + " 天前";
|
|
940
|
+
if (diff < minute) return t("刚刚");
|
|
941
|
+
if (diff < hour) return Math.floor(diff / minute) + t(" 分钟前");
|
|
942
|
+
if (diff < day) return Math.floor(diff / hour) + t(" 小时前");
|
|
943
|
+
if (diff < day * 30) return Math.floor(diff / day) + t(" 天前");
|
|
640
944
|
return formatDate(iso);
|
|
641
945
|
}
|
|
642
946
|
|
|
@@ -648,9 +952,9 @@ function recentText(iso, now) {
|
|
|
648
952
|
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
649
953
|
const that = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
650
954
|
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) + " 天前";
|
|
955
|
+
if (days <= 0) return t("今天");
|
|
956
|
+
if (days === 1) return t("昨天");
|
|
957
|
+
if (days < 30) return String(days) + t(" 天前");
|
|
654
958
|
return formatDate(iso);
|
|
655
959
|
}
|
|
656
960
|
|
|
@@ -744,23 +1048,23 @@ function needsOlderDreams(loaded, total) {
|
|
|
744
1048
|
}
|
|
745
1049
|
|
|
746
1050
|
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) + " 场梦";
|
|
1051
|
+
if (filterCount > 0) return t("筛选后 ") + String(visibleCount) + " / " + String(count) + t(" 场");
|
|
1052
|
+
if (query !== "") return (languageOf() === "en" ? "“" : "「") + query + t("」命中 ") + String(count) + t(" 场");
|
|
1053
|
+
return t("共 ") + String(total) + t(" 场梦");
|
|
750
1054
|
}
|
|
751
1055
|
|
|
752
1056
|
function listHintOf(query, count, filterCount, visibleCount) {
|
|
753
|
-
if (filterCount > 0) return "筛选当前已加载的 " + String(count) + " 场 · 命中 " + String(visibleCount) + " 场";
|
|
754
|
-
if (query !== "") return "「" + query + "」命中 " + String(count) + " 场";
|
|
755
|
-
return "新梦在前";
|
|
1057
|
+
if (filterCount > 0) return t("筛选当前已加载的 ") + String(count) + t(" 场 · 命中 ") + String(visibleCount) + t(" 场");
|
|
1058
|
+
if (query !== "") return (languageOf() === "en" ? "“" : "「") + query + t("」命中 ") + String(count) + t(" 场");
|
|
1059
|
+
return t("新梦在前");
|
|
756
1060
|
}
|
|
757
1061
|
|
|
758
1062
|
function footerTextOf(query, limit, total, count) {
|
|
759
1063
|
if (limit > DEFAULT_LIMIT) {
|
|
760
|
-
const capped = limit >= MORE_LIMIT && count < total ? "(已达路由上限)" : "";
|
|
761
|
-
return "最近 " + String(limit) + " 场 / 共 " + String(total) + " 场" + capped;
|
|
1064
|
+
const capped = limit >= MORE_LIMIT && count < total ? t("(已达路由上限)") : "";
|
|
1065
|
+
return t("最近 ") + String(limit) + t(" 场 / 共 ") + String(total) + t(" 场") + capped;
|
|
762
1066
|
}
|
|
763
|
-
return query === "" ? "新梦在前 · 最多显示 " + String(DEFAULT_LIMIT) + " 场" : "搜索结果 · 最多显示 " + String(DEFAULT_LIMIT) + " 场";
|
|
1067
|
+
return query === "" ? t("新梦在前 · 最多显示 ") + String(DEFAULT_LIMIT) + t(" 场") : t("搜索结果 · 最多显示 ") + String(DEFAULT_LIMIT) + t(" 场");
|
|
764
1068
|
}
|
|
765
1069
|
|
|
766
1070
|
/** 面板的视图模型:所有文案/数字集中在这里算,组件只负责画。 */
|
|
@@ -784,15 +1088,15 @@ function viewModelOf(dreams, stats, now) {
|
|
|
784
1088
|
share: Math.max(6, Math.round(row.count / Math.max(lessonTotal, 1) * 100)),
|
|
785
1089
|
showBar: row.count >= 2,
|
|
786
1090
|
candidate: row.count >= 3,
|
|
787
|
-
lastText: knownAt === "" ? "" : (fromStats ? "最近 " : "本页内最近 ") + recentText(knownAt, now),
|
|
1091
|
+
lastText: knownAt === "" ? "" : (fromStats ? t("最近 ") : t("本页内最近 ")) + recentText(knownAt, now),
|
|
788
1092
|
lastTitle: knownAt === "" ? "" : formatDate(knownAt) + (formatTime(knownAt) === "" ? "" : " " + formatTime(knownAt))
|
|
789
|
-
+ (fromStats ? "" : "(统计未给 lastAt,按本页已加载的 " + String(list.length) + " 场现算)"),
|
|
1093
|
+
+ (fromStats ? "" : t("(统计未给 lastAt,按本页已加载的 ") + String(list.length) + t(" 场现算)")),
|
|
790
1094
|
};
|
|
791
1095
|
});
|
|
792
1096
|
const span = spanOf(list);
|
|
793
1097
|
const spanText = span === null ? "" : spanTextOf(span);
|
|
794
1098
|
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) + " 天");
|
|
1099
|
+
const spanHint = span === null ? "" : (span.days === 0 ? t("同一天") : t("约 ") + String(span.days) + t(" 天"));
|
|
796
1100
|
const blocks = list.slice().reverse().map((dream, index) => ({
|
|
797
1101
|
key: "block-" + (dream.id !== "" ? dream.id : "no-id") + "-" + String(index),
|
|
798
1102
|
tone: toneClass(moodSlot(dream.mood)),
|
|
@@ -823,12 +1127,12 @@ function headlineOf(dreams, total, span, now) {
|
|
|
823
1127
|
if (dreams.length === 0) return "";
|
|
824
1128
|
if (dreams.length === 1 && total <= 1) {
|
|
825
1129
|
const dream = dreams[0];
|
|
826
|
-
return "第 1 场梦 · " + formatDate(dream.at) + " · 心境「" + dream.mood + "」";
|
|
1130
|
+
return t("第 1 场梦 · ") + formatDate(dream.at) + t(" · 心境「") + dream.mood + (languageOf() === "en" ? "”" : "」");
|
|
827
1131
|
}
|
|
828
1132
|
const parts = [];
|
|
829
|
-
parts.push(total > dreams.length ? "共 " + String(total) + " 场梦 · 本页最近 " + String(dreams.length) + " 场" : String(dreams.length) + " 场梦");
|
|
1133
|
+
parts.push(total > dreams.length ? t("共 ") + String(total) + t(" 场梦 · 本页最近 ") + String(dreams.length) + t(" 场") : String(dreams.length) + t(" 场梦"));
|
|
830
1134
|
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));
|
|
1135
|
+
parts.push(t("最近:") + relativeTime(dreams[0].at, now));
|
|
832
1136
|
return parts.join(" · ");
|
|
833
1137
|
}
|
|
834
1138
|
|
|
@@ -873,20 +1177,20 @@ function StatTile(props) {
|
|
|
873
1177
|
function MoodCard(props) {
|
|
874
1178
|
const vm = props.vm;
|
|
875
1179
|
return h("section", { className: "dshd-card" }, [
|
|
876
|
-
h("h4", { key: "t" }, "心境"),
|
|
1180
|
+
h("h4", { key: "t" }, t("心境")),
|
|
877
1181
|
h("div", { className: "dshd-mblocks", key: "blocks" }, vm.blocks.map((block) => h("span", {
|
|
878
1182
|
key: block.key,
|
|
879
1183
|
className: "dshd-mblock " + block.tone,
|
|
880
1184
|
title: block.title,
|
|
881
1185
|
}))),
|
|
882
1186
|
vm.count === 1
|
|
883
|
-
? h("div", { className: "dshd-hint", key: "one" }, "1 场梦,先不谈分布 —— 再做梦会累积。")
|
|
1187
|
+
? h("div", { className: "dshd-hint", key: "one" }, t("1 场梦,先不谈分布 —— 再做梦会累积。"))
|
|
884
1188
|
: h("div", { key: "legend" }, [
|
|
885
|
-
h("div", { className: "dshd-note", key: "cap" }, "左 → 右:从早到晚"),
|
|
1189
|
+
h("div", { className: "dshd-note", key: "cap" }, t("左 → 右:从早到晚")),
|
|
886
1190
|
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
1191
|
h("span", { className: "dshd-mblock " + toneClass(moodSlot(row.mood)), key: "d" }),
|
|
888
1192
|
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) + " 场"),
|
|
1193
|
+
h("span", { className: "dshd-legend-count", key: "c" }, String(row.count) + t(" 场")),
|
|
890
1194
|
]))),
|
|
891
1195
|
]),
|
|
892
1196
|
]);
|
|
@@ -895,9 +1199,9 @@ function MoodCard(props) {
|
|
|
895
1199
|
function LessonCard(props) {
|
|
896
1200
|
const vm = props.vm;
|
|
897
1201
|
return h("section", { className: "dshd-card" }, [
|
|
898
|
-
h("h4", { key: "t" }, "教训榜"),
|
|
1202
|
+
h("h4", { key: "t" }, t("教训榜")),
|
|
899
1203
|
vm.lessonRows.length === 0
|
|
900
|
-
? h("div", { className: "dshd-hint", key: "e" }, "还没有教训 —— 记梦时带上 lessons,出现次数会在这里累积。")
|
|
1204
|
+
? h("div", { className: "dshd-hint", key: "e" }, t("还没有教训 —— 记梦时带上 lessons,出现次数会在这里累积。"))
|
|
901
1205
|
: h("div", { key: "rows" }, vm.lessonRows.map((row, index) => h("div", { className: "dshd-lesson", key: "lesson-" + String(index) + "-" + row.lesson }, [
|
|
902
1206
|
h("span", { className: "dshd-rank", key: "r" }, String(row.rank)),
|
|
903
1207
|
h("span", { className: "dshd-lesson-body", key: "b" }, [
|
|
@@ -907,10 +1211,10 @@ function LessonCard(props) {
|
|
|
907
1211
|
h("span", { className: "dshd-lesson-meta", key: "m" }, [
|
|
908
1212
|
h("span", { className: "dshd-times", key: "c" }, "×", h("b", { key: "n" }, String(row.count))),
|
|
909
1213
|
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,
|
|
1214
|
+
row.candidate ? h("span", { className: "dshd-badge", key: "badge" }, t("常见教训")) : null,
|
|
911
1215
|
]),
|
|
912
1216
|
]))),
|
|
913
|
-
h("div", { className: "dshd-note", key: "note" }, "次数仅表示日记里的出现频率;出现 ≥3 次标为「常见教训」。写入项目规则前仍需核验来源并审阅经验。最近时间取自 stats.lastAt;统计缺失时按本页已加载的梦现算。"),
|
|
1217
|
+
h("div", { className: "dshd-note", key: "note" }, t("次数仅表示日记里的出现频率;出现 ≥3 次标为「常见教训」。写入项目规则前仍需核验来源并审阅经验。最近时间取自 stats.lastAt;统计缺失时按本页已加载的梦现算。")),
|
|
914
1218
|
]);
|
|
915
1219
|
}
|
|
916
1220
|
|
|
@@ -926,11 +1230,11 @@ function CopyCommand(props) {
|
|
|
926
1230
|
setFailed(ok !== true);
|
|
927
1231
|
});
|
|
928
1232
|
};
|
|
929
|
-
const label = props.label === undefined ? "复制命令" : props.label;
|
|
1233
|
+
const label = props.label === undefined ? t("复制命令") : props.label;
|
|
930
1234
|
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),
|
|
1235
|
+
h(Btn, { key: "b", onClick: onCopy, disabled: props.disabled === true, title: props.title === undefined ? command : props.title }, copied === true ? t("已复制") : label),
|
|
932
1236
|
failed === true ? h("code", { key: "cmd", className: "dshd-cmd", tabIndex: 0 }, command) : null,
|
|
933
|
-
failed === true ? h("span", { key: "hint", className: "dshd-note" }, "剪贴板不可用:请手动选中上面的命令复制。") : null,
|
|
1237
|
+
failed === true ? h("span", { key: "hint", className: "dshd-note" }, t("剪贴板不可用:请手动选中上面的命令复制。")) : null,
|
|
934
1238
|
]);
|
|
935
1239
|
}
|
|
936
1240
|
|
|
@@ -947,10 +1251,10 @@ function ReviewControls(props) {
|
|
|
947
1251
|
const [pending, setPending] = useState(false);
|
|
948
1252
|
const [error, setError] = useState("");
|
|
949
1253
|
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", "标记待复核"]];
|
|
1254
|
+
const commands = row.state === "rejected" ? [["reopen", t("重新送审")]]
|
|
1255
|
+
: row.state === "disputed" ? [["resolve-conflict", t("处理冲突")], ["reject", t("驳回这条")]]
|
|
1256
|
+
: row.state === "usable" ? [["mark-stale", t("标记待复核")], ["reject", t("驳回")]]
|
|
1257
|
+
: [["accept", t("采纳")], ["reject", t("驳回")], ["mark-stale", t("标记待复核")]];
|
|
954
1258
|
const effectiveAction = commands.some(function (item) { return item[0] === action; }) ? action : commands[0][0];
|
|
955
1259
|
const command = reviewCommandOf(row, { action: effectiveAction, resolution: resolution, note: note, affectedIds: affected === "" ? [] : [affected] });
|
|
956
1260
|
const submit = async function (body, message, isPreview) {
|
|
@@ -959,65 +1263,65 @@ function ReviewControls(props) {
|
|
|
959
1263
|
try {
|
|
960
1264
|
const result = await postAction(body);
|
|
961
1265
|
if (isPreview) setPreview(result);
|
|
962
|
-
else { setPreview(null); if (props.onChanged) props.onChanged(result.unchanged === true ? "管理块内容已一致,无需写入。" : message); }
|
|
1266
|
+
else { setPreview(null); if (props.onChanged) props.onChanged(result.unchanged === true ? t("管理块内容已一致,无需写入。") : message); }
|
|
963
1267
|
} catch (failure) {
|
|
964
|
-
setError(failure.name === "TimeoutError" ? "请求超时,请先刷新确认结果后再重试。" :
|
|
1268
|
+
setError(failure.name === "TimeoutError" ? t("请求超时,请先刷新确认结果后再重试。") : displayError(failure));
|
|
965
1269
|
if (body.operation === "apply") setPreview(null);
|
|
966
1270
|
} finally { setPending(false); }
|
|
967
1271
|
};
|
|
968
1272
|
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 + "」的审阅状态。");
|
|
1273
|
+
return submit({ operation: "review", action: chosen, lessonId: row.id, expectedRevision: row.revision, resolution: resolution, note: note, affectedIds: affected === "" ? [] : [affected] }, t("已更新「") + row.title + t("」的审阅状态。"));
|
|
970
1274
|
};
|
|
971
1275
|
const feedback = function (vote) {
|
|
972
1276
|
const signature = row.id + "@" + row.revision + ":" + vote + ":" + feedbackNote;
|
|
973
1277
|
const requestId = feedbackRetry && feedbackRetry.signature === signature ? feedbackRetry.id
|
|
974
1278
|
: typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : "feedback_" + Date.now() + "_" + Math.random().toString(36).slice(2);
|
|
975
1279
|
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 + "」的审阅状态保持原样。");
|
|
1280
|
+
return submit({ operation: "feedback", lessonId: row.id, expectedRevision: row.revision, vote: vote, requestId: requestId, ...(feedbackNote.trim() === "" ? {} : { note: feedbackNote.trim() }) }, t("已记录使用反馈;「") + row.title + t("」的审阅状态保持原样。"));
|
|
977
1281
|
};
|
|
978
|
-
return h("div", { className: "dshd-review", role: "group", "aria-label": row.title + "的操作", onClick: function (event) { event.stopPropagation(); } }, [
|
|
1282
|
+
return h("div", { className: "dshd-review", role: "group", "aria-label": row.title + t("的操作"), onClick: function (event) { event.stopPropagation(); } }, [
|
|
979
1283
|
h("div", { className: "dshd-kcmd", key: "buttons" }, [
|
|
980
1284
|
...commands.filter(function (item) { return item[0] !== "resolve-conflict"; }).map(function (item) {
|
|
981
1285
|
return h(Btn, { key: item[0], disabled: pending || row.revision < 1, onClick: function () { review(item[0]); } }, item[1]);
|
|
982
1286
|
}),
|
|
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,
|
|
1287
|
+
h(Btn, { key: "review", disabled: pending, onClick: function () { setOpen(!open); }, "aria-expanded": open }, open ? t("收起审阅") : row.state === "disputed" ? t("处理冲突") : t("审阅详情")),
|
|
1288
|
+
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,
|
|
1289
|
+
row.bridgeable === true && row.workspaceRoot === "" ? h(CopyCommand, { key: "preview-command", command: bridgePreviewCommandOf(row.id, row.projectId, row.workspaceRoot), label: t("复制项目预览命令") }) : null,
|
|
986
1290
|
]),
|
|
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,
|
|
1291
|
+
pending ? h("div", { key: "pending", role: "status", className: "dshd-note" }, t("正在处理…")) : null,
|
|
1292
|
+
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
1293
|
open ? h("div", { className: "dshd-reviewform", key: "form", onKeyDown: function (event) {
|
|
990
1294
|
if (event.key === "Escape" && !pending) { event.preventDefault(); event.stopPropagation(); setOpen(false); }
|
|
991
1295
|
} }, [
|
|
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]); }))]),
|
|
1296
|
+
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
1297
|
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" }, "合并到这条,对方留作历史"),
|
|
1298
|
+
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); }))]),
|
|
1299
|
+
h("label", { key: "resolution" }, [t("处理方式"), h("select", { "aria-label": t("处理方式"), disabled: pending, value: resolution, onChange: function (event) { setResolution(event.target.value); } }, [
|
|
1300
|
+
h("option", { key: "prefer", value: "prefer" }, t("采用这条,驳回对方")),
|
|
1301
|
+
h("option", { key: "drop", value: "drop" }, t("放弃这条,保留对方")),
|
|
1302
|
+
h("option", { key: "merge", value: "merge" }, t("合并到这条,对方留作历史")),
|
|
999
1303
|
])]),
|
|
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); } })]),
|
|
1304
|
+
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
1305
|
]) : 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: "复制审阅命令" })]),
|
|
1306
|
+
h(Btn, { key: "confirm", disabled: pending || command === "", onClick: function () { review(effectiveAction); }, variant: "primary" }, t("确认审阅")),
|
|
1307
|
+
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
1308
|
]) : 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); } }, "取消预览"),
|
|
1309
|
+
preview ? h("section", { key: "preview", className: "dshd-reviewform", "aria-label": t("规则变更预览") }, [
|
|
1310
|
+
h("strong", { key: "heading" }, t("规则变更预览")),
|
|
1311
|
+
h("div", { key: "target", className: "dshd-note" }, t("目标:") + preview.resolvedPath),
|
|
1312
|
+
h("pre", { key: "diff", className: "dshd-diff", tabIndex: 0 }, preview.diff || t("管理块内容已经一致,无需更改。")),
|
|
1313
|
+
h("div", { key: "help", className: "dshd-note" }, t("将按此预览重建 Dream 管理块,请核对新增和删除内容。块外内容保留,写入前创建回滚备份。")),
|
|
1314
|
+
h(Btn, { key: "apply", disabled: pending, variant: "primary", onClick: function () { submit({ operation: "apply", token: preview.token }, t("规则已应用,可在「写入记录」中回滚。")); } }, t("应用此变更")),
|
|
1315
|
+
h(Btn, { key: "cancel", disabled: pending, onClick: function () { setPreview(null); } }, t("取消预览")),
|
|
1012
1316
|
]) : null,
|
|
1013
1317
|
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" }, "记录使用情况,供之后复核;采纳与驳回由审阅决定。"),
|
|
1318
|
+
h("summary", { key: "summary" }, t("使用反馈 · 有用 ") + (row.feedback.useful || 0) + t(" · 不适用 ") + (row.feedback.notApplicable || 0)),
|
|
1319
|
+
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); } })]),
|
|
1320
|
+
h(Btn, { key: "useful", disabled: pending, onClick: function () { feedback("useful"); } }, t("这次有用")),
|
|
1321
|
+
h(Btn, { key: "not", disabled: pending, onClick: function () { feedback("not-applicable"); } }, t("这次不适用")),
|
|
1322
|
+
h("div", { key: "help", className: "dshd-note" }, t("记录使用情况,供之后复核;采纳与驳回由审阅决定。")),
|
|
1019
1323
|
...(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 : ""));
|
|
1324
|
+
return h("div", { key: "recent-" + index, className: "dshd-note" }, formatDate(item.at) + " · " + (item.vote === "useful" ? t("有用") : t("不适用")) + (item.note ? " · " + item.note : ""));
|
|
1021
1325
|
}) : []),
|
|
1022
1326
|
]),
|
|
1023
1327
|
]);
|
|
@@ -1033,15 +1337,15 @@ function RollbackControl(props) {
|
|
|
1033
1337
|
try {
|
|
1034
1338
|
await postAction({ operation: "rollback", backupId: props.row.id });
|
|
1035
1339
|
setConfirm(false);
|
|
1036
|
-
if (props.onChanged) props.onChanged("已恢复上一次的 Dream 管理块,块外内容保留。");
|
|
1037
|
-
} catch (failure) { setError(
|
|
1340
|
+
if (props.onChanged) props.onChanged(t("已恢复上一次的 Dream 管理块,块外内容保留。"));
|
|
1341
|
+
} catch (failure) { setError(displayError(failure)); }
|
|
1038
1342
|
finally { setPending(false); }
|
|
1039
1343
|
};
|
|
1040
1344
|
return h("div", { className: "dshd-reviewform" }, [
|
|
1041
|
-
h(Btn, { key: "start", disabled: pending, onClick: function () { setConfirm(!confirm); } }, confirm ? "取消回滚" : "回滚这次变更"),
|
|
1345
|
+
h(Btn, { key: "start", disabled: pending, onClick: function () { setConfirm(!confirm); } }, confirm ? t("取消回滚") : t("回滚这次变更")),
|
|
1042
1346
|
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 ? "正在回滚…" : "确认回滚管理块"),
|
|
1347
|
+
h("p", { key: "target", className: "dshd-note" }, t("将恢复「") + props.row.target + t("」的前一个 Dream 管理块。块外内容保留;管理块已被修改时会拒绝回滚。")),
|
|
1348
|
+
h(Btn, { key: "apply", variant: "primary", disabled: pending, onClick: rollback }, pending ? t("正在回滚…") : t("确认回滚管理块")),
|
|
1045
1349
|
]) : null,
|
|
1046
1350
|
error ? h("div", { key: "error", role: "alert", className: "dshd-hint" }, error) : null,
|
|
1047
1351
|
]);
|
|
@@ -1055,36 +1359,36 @@ function KnowledgeRow(props) {
|
|
|
1055
1359
|
return h("div", {
|
|
1056
1360
|
className: "dshd-knowledge " + row.stateClass + (privacy === true && peek === true ? " dshd-peek" : ""),
|
|
1057
1361
|
onClick: reveal,
|
|
1058
|
-
title: privacy === true ? "隐私模式:点击此条临时查看" : undefined,
|
|
1362
|
+
title: privacy === true ? t("隐私模式:点击此条临时查看") : undefined,
|
|
1059
1363
|
}, [
|
|
1060
1364
|
h("div", { className: "dshd-khead", key: "h" }, [
|
|
1061
1365
|
h("span", { className: "dshd-kbadge", key: "b" }, row.stateLabel),
|
|
1062
1366
|
h("span", { className: "dshd-ktitle", key: "t", title: row.title }, row.title),
|
|
1063
1367
|
]),
|
|
1064
1368
|
h("div", { className: "dshd-kmeta", key: "m" }, [
|
|
1065
|
-
h("span", { className: "dshd-klast", key: "l", title: row.lastTitle }, "最近核验:" + row.lastText),
|
|
1369
|
+
h("span", { className: "dshd-klast", key: "l", title: row.lastTitle }, t("最近核验:") + row.lastText),
|
|
1066
1370
|
]),
|
|
1067
1371
|
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 || "未标注具体动作")),
|
|
1372
|
+
h("div", { key: "when" }, t("何时用:") + (row.when !== "" ? row.when : t("未标注适用条件"))),
|
|
1373
|
+
h("div", { key: "action", className: "dshd-action" }, t("怎么做:") + (row.action || t("未标注具体动作"))),
|
|
1070
1374
|
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) {
|
|
1375
|
+
h("summary", { key: "summary" }, t("适用范围与证据")),
|
|
1376
|
+
h("div", { key: "scope" }, t("范围:") + row.scopeLabel),
|
|
1377
|
+
row.applicability !== "" ? h("div", { key: "applicability" }, t("适用:") + row.applicability) : null,
|
|
1378
|
+
row.exceptions.length > 0 ? h("div", { key: "exceptions" }, t("例外:") + row.exceptions.join(";")) : null,
|
|
1379
|
+
h("div", { key: "evidence" }, t("证据:") + row.evidenceSummary),
|
|
1380
|
+
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
1381
|
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,
|
|
1382
|
+
h("summary", { key: "summary" }, t("来源记录 · ") + (item.verification === "read" ? t("已读到来源") : t("待核验")) + " · " + (item.kind === "session" ? t("会话") : item.kind === "user-correction" ? t("用户纠正") : t("本地资料"))),
|
|
1383
|
+
h("div", { key: "summary-text" }, item.summary || t("未提供摘要")),
|
|
1384
|
+
item.sessionId ? h("div", { key: "session", className: "dshd-note" }, t("会话:") + item.sessionId + (Number.isInteger(item.recordSeq) ? t(" · 记录 ") + item.recordSeq : "")) : null,
|
|
1385
|
+
h("div", { key: "date", className: "dshd-note" }, t("记录时间:") + formatDate(item.observedAt)),
|
|
1386
|
+
item.verificationReason ? h("div", { key: "reason", className: "dshd-note" }, t("核验说明:") + (item.verificationReason === 'live-session-source-verified' ? t("已核对当前会话原文;不代表结论正确。") : item.verificationReason)) : null,
|
|
1387
|
+
item.sourceHash ? h("code", { key: "hash", className: "dshd-cmd", title: item.sourceHash }, t("来源校验值:") + item.sourceHash) : null,
|
|
1084
1388
|
]);
|
|
1085
1389
|
})),
|
|
1086
1390
|
]),
|
|
1087
|
-
props.showHoldReason === true && row.holdReasonLabel !== "" ? h("div", { key: "hold", className: "dshd-holdreason" }, "为什么被扣下:" + row.holdReasonLabel) : null,
|
|
1391
|
+
props.showHoldReason === true && row.holdReasonLabel !== "" ? h("div", { key: "hold", className: "dshd-holdreason" }, t("为什么被扣下:") + row.holdReasonLabel) : null,
|
|
1088
1392
|
]),
|
|
1089
1393
|
h(ReviewControls, { row: row, key: row.id + "@" + row.revision, onChanged: props.onChanged }),
|
|
1090
1394
|
]);
|
|
@@ -1099,22 +1403,22 @@ function KnowledgeCard(props) {
|
|
|
1099
1403
|
const noticeNode = notice === "" ? null : h("div", { className: "dshd-knotice", key: "notice", role: "status" }, notice);
|
|
1100
1404
|
const head = function (extra) {
|
|
1101
1405
|
return h("div", { className: "dshd-khead", key: "head" }, [
|
|
1102
|
-
h("h4", { key: "t" }, "已审阅经验"),
|
|
1406
|
+
h("h4", { key: "t" }, t("已审阅经验")),
|
|
1103
1407
|
h("span", { className: "dshd-spacer", key: "s" }),
|
|
1104
1408
|
extra,
|
|
1105
1409
|
]);
|
|
1106
1410
|
};
|
|
1107
1411
|
if (props.error !== null && props.error !== undefined) {
|
|
1108
1412
|
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
|
|
1111
|
-
h("div", { className: "dshd-note", key: "n" }, "旧日记不受影响;修好后点「重试」。"),
|
|
1413
|
+
head(h(Btn, { key: "retry", onClick: props.onRetry, disabled: props.loading === true }, t("重试"))),
|
|
1414
|
+
h("div", { className: "dshd-hint", key: "e", role: "alert" }, t("读取经验失败:") + displayError(props.error)),
|
|
1415
|
+
h("div", { className: "dshd-note", key: "n" }, t("旧日记不受影响;修好后点「重试」。")),
|
|
1112
1416
|
]);
|
|
1113
1417
|
}
|
|
1114
1418
|
if (props.empty === true) {
|
|
1115
1419
|
const empty = knowledgeEmptyState();
|
|
1116
1420
|
return h("section", { className: "dshd-card dshd-kcard" }, [
|
|
1117
|
-
h("h4", { key: "t" }, "经验"),
|
|
1421
|
+
h("h4", { key: "t" }, t("经验")),
|
|
1118
1422
|
noticeNode,
|
|
1119
1423
|
h("div", { className: "dshd-kempty", key: "e" }, [
|
|
1120
1424
|
h("div", { className: "dshd-kempty-title", key: "t2" }, empty.title),
|
|
@@ -1125,21 +1429,21 @@ function KnowledgeCard(props) {
|
|
|
1125
1429
|
}
|
|
1126
1430
|
return h("section", { className: "dshd-card dshd-kcard" }, [
|
|
1127
1431
|
head(props.loading === true
|
|
1128
|
-
? h("span", { className: "dshd-hint", key: "loading" }, "加载中…")
|
|
1432
|
+
? h("span", { className: "dshd-hint", key: "loading" }, t("加载中…"))
|
|
1129
1433
|
: h("span", { className: "dshd-hint", key: "count" }, knowledgeCountText(vm.stats))),
|
|
1130
1434
|
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" }, "已驳回历史"),
|
|
1435
|
+
h("label", { className: "dshd-note", key: "filter" }, [t("查看 "), h("select", { "aria-label": t("经验状态"), value: stateFilter, onChange: function (event) { setStateFilter(event.target.value); setPageSize(5); } }, [
|
|
1436
|
+
h("option", { key: "usable", value: "usable" }, t("可用经验")), h("option", { key: "rejected", value: "rejected" }, t("已驳回历史")),
|
|
1133
1437
|
])]),
|
|
1134
|
-
reviewed.length === 0 ? h("div", { className: "dshd-hint", key: "none" }, stateFilter === "usable" ? "暂无可用经验;待核实的记录在下方。" : "没有已驳回记录。") : null,
|
|
1438
|
+
reviewed.length === 0 ? h("div", { className: "dshd-hint", key: "none" }, stateFilter === "usable" ? t("暂无可用经验;待核实的记录在下方。") : t("没有已驳回记录。")) : null,
|
|
1135
1439
|
h("div", { className: "dshd-krows", key: "rows" }, reviewed.slice(0, pageSize).map(function (row, index) {
|
|
1136
1440
|
return h(KnowledgeRow, { key: row.id !== "" ? row.id : "k" + String(index), row: row, onChanged: props.onChanged, privacy: props.privacy === true });
|
|
1137
1441
|
})),
|
|
1138
|
-
reviewed.length > pageSize ? h(Btn, { key: "next", onClick: function () { setPageSize(pageSize + 5); } }, "显示更多经验") : null,
|
|
1442
|
+
reviewed.length > pageSize ? h(Btn, { key: "next", onClick: function () { setPageSize(pageSize + 5); } }, t("显示更多经验")) : null,
|
|
1139
1443
|
vm.shown < vm.total
|
|
1140
|
-
? h("div", { className: "dshd-note", key: "more" }, "只显示最近 " + String(vm.shown) + " / " + String(vm.total) + " 条经验。")
|
|
1444
|
+
? h("div", { className: "dshd-note", key: "more" }, t("只显示最近 ") + String(vm.shown) + " / " + String(vm.total) + t(" 条经验。"))
|
|
1141
1445
|
: null,
|
|
1142
|
-
h("div", { className: "dshd-note", key: "note" }, "采纳记录仍需满足当前任务的平台、版本与项目条件;来源已读不代表结论永远适用。"),
|
|
1446
|
+
h("div", { className: "dshd-note", key: "note" }, t("采纳记录仍需满足当前任务的平台、版本与项目条件;来源已读不代表结论永远适用。")),
|
|
1143
1447
|
]);
|
|
1144
1448
|
}
|
|
1145
1449
|
|
|
@@ -1152,34 +1456,34 @@ function ReviewQueueCard(props) {
|
|
|
1152
1456
|
: [];
|
|
1153
1457
|
const head = function (extra) {
|
|
1154
1458
|
return h("div", { className: "dshd-khead", key: "head" }, [
|
|
1155
|
-
h("h4", { key: "t" }, "待核实"),
|
|
1459
|
+
h("h4", { key: "t" }, t("待核实")),
|
|
1156
1460
|
h("span", { className: "dshd-spacer", key: "s" }),
|
|
1157
1461
|
extra,
|
|
1158
1462
|
]);
|
|
1159
1463
|
};
|
|
1160
1464
|
if (props.error !== null && props.error !== undefined) {
|
|
1161
1465
|
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
|
|
1466
|
+
head(h(Btn, { key: "retry", onClick: props.onRetry, disabled: props.loading === true }, t("重试"))),
|
|
1467
|
+
h("div", { className: "dshd-hint", key: "e", role: "alert" }, t("读取经验失败:") + displayError(props.error)),
|
|
1164
1468
|
]);
|
|
1165
1469
|
}
|
|
1166
1470
|
if (props.loading === true && rows.length === 0) {
|
|
1167
1471
|
return h("section", { className: "dshd-card dshd-kcard" }, [
|
|
1168
|
-
head(h("span", { className: "dshd-hint", key: "l" }, "加载中…")),
|
|
1472
|
+
head(h("span", { className: "dshd-hint", key: "l" }, t("加载中…"))),
|
|
1169
1473
|
]);
|
|
1170
1474
|
}
|
|
1171
1475
|
return h("section", { className: "dshd-card dshd-kcard" }, [
|
|
1172
|
-
head(rows.length === 0 ? null : h("span", { className: "dshd-hint", key: "c" }, String(rows.length) + " 条待核实")),
|
|
1476
|
+
head(rows.length === 0 ? null : h("span", { className: "dshd-hint", key: "c" }, String(rows.length) + t(" 条待核实"))),
|
|
1173
1477
|
rows.length === 0
|
|
1174
1478
|
? h("div", { className: "dshd-kempty", key: "empty" }, [
|
|
1175
|
-
h("div", { className: "dshd-kempty-title", key: "t" }, "没有待核实的经验"),
|
|
1479
|
+
h("div", { className: "dshd-kempty-title", key: "t" }, t("没有待核实的经验")),
|
|
1176
1480
|
h("p", { className: "dshd-kempty-body", key: "b" }, reviewQueueEmptyState()),
|
|
1177
1481
|
])
|
|
1178
1482
|
: h("div", { className: "dshd-krows", key: "rows" }, rows.slice(0, pageSize).map(function (row, index) {
|
|
1179
1483
|
return h(KnowledgeRow, { key: row.id !== "" ? row.id : "held-" + String(index), row: row, onChanged: props.onChanged, privacy: props.privacy === true, showHoldReason: true });
|
|
1180
1484
|
})),
|
|
1181
|
-
rows.length > pageSize ? h(Btn, { key: "more", onClick: function () { setPageSize(pageSize + 5); } }, "显示更多待核实经验") : null,
|
|
1182
|
-
h("div", { className: "dshd-note", key: "note" }, "这些经验默认不会作为任务建议注入。先核对来源与适用范围,再决定采纳、驳回或继续复核。"),
|
|
1485
|
+
rows.length > pageSize ? h(Btn, { key: "more", onClick: function () { setPageSize(pageSize + 5); } }, t("显示更多待核实经验")) : null,
|
|
1486
|
+
h("div", { className: "dshd-note", key: "note" }, t("这些经验默认不会作为任务建议注入。先核对来源与适用范围,再决定采纳、驳回或继续复核。")),
|
|
1183
1487
|
]);
|
|
1184
1488
|
}
|
|
1185
1489
|
|
|
@@ -1188,26 +1492,26 @@ function BridgeLogCard(props) {
|
|
|
1188
1492
|
const vm = props.vm;
|
|
1189
1493
|
const head = function (extra) {
|
|
1190
1494
|
return h("div", { className: "dshd-khead", key: "head" }, [
|
|
1191
|
-
h("h4", { key: "t" }, "写入记录"),
|
|
1495
|
+
h("h4", { key: "t" }, t("写入记录")),
|
|
1192
1496
|
h("span", { className: "dshd-spacer", key: "s" }),
|
|
1193
1497
|
extra,
|
|
1194
1498
|
]);
|
|
1195
1499
|
};
|
|
1196
1500
|
if (props.error !== null && props.error !== undefined) {
|
|
1197
1501
|
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
|
|
1502
|
+
head(h(Btn, { key: "retry", onClick: props.onRetry, disabled: props.loading === true }, t("重试"))),
|
|
1503
|
+
h("div", { className: "dshd-hint", key: "e", role: "alert" }, t("读取写入记录失败:") + displayError(props.error)),
|
|
1200
1504
|
]);
|
|
1201
1505
|
}
|
|
1202
1506
|
if (props.loading === true) {
|
|
1203
1507
|
return h("section", { className: "dshd-card dshd-kcard" }, [
|
|
1204
|
-
head(h("span", { className: "dshd-hint", key: "l" }, "加载中…")),
|
|
1508
|
+
head(h("span", { className: "dshd-hint", key: "l" }, t("加载中…"))),
|
|
1205
1509
|
]);
|
|
1206
1510
|
}
|
|
1207
1511
|
if (vm.rows.length === 0) {
|
|
1208
1512
|
const empty = bridgeEmptyState();
|
|
1209
1513
|
return h("section", { className: "dshd-card dshd-kcard" }, [
|
|
1210
|
-
h("h4", { key: "t" }, "写入记录"),
|
|
1514
|
+
h("h4", { key: "t" }, t("写入记录")),
|
|
1211
1515
|
h("div", { className: "dshd-kempty", key: "empty" }, [
|
|
1212
1516
|
h("div", { className: "dshd-kempty-title", key: "t2" }, empty.title),
|
|
1213
1517
|
h("p", { className: "dshd-kempty-body", key: "b" }, empty.body),
|
|
@@ -1216,7 +1520,7 @@ function BridgeLogCard(props) {
|
|
|
1216
1520
|
]);
|
|
1217
1521
|
}
|
|
1218
1522
|
return h("section", { className: "dshd-card dshd-kcard" }, [
|
|
1219
|
-
head(h("span", { className: "dshd-hint", key: "c" }, String(vm.total) + " 次写入 · " + String(vm.rollbackable) + " 次可回滚")),
|
|
1523
|
+
head(h("span", { className: "dshd-hint", key: "c" }, String(vm.total) + t(" 次写入 · ") + String(vm.rollbackable) + t(" 次可回滚"))),
|
|
1220
1524
|
h("div", { className: "dshd-krows", key: "rows" }, vm.rows.map(function (row, index) {
|
|
1221
1525
|
return h("div", { className: "dshd-bridge", key: row.id !== "" ? row.id : "bridge-" + String(index) }, [
|
|
1222
1526
|
h("div", { className: "dshd-bridge-head", key: "h" }, [
|
|
@@ -1226,21 +1530,21 @@ function BridgeLogCard(props) {
|
|
|
1226
1530
|
h("span", { className: "dshd-klast", key: "at", title: row.atTitle }, row.atText),
|
|
1227
1531
|
]),
|
|
1228
1532
|
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),
|
|
1533
|
+
h("span", { key: "lessons", title: row.lessonsText }, t("经验:") + row.lessonsText),
|
|
1534
|
+
row.beforeShort === "" ? null : h("span", { key: "before" }, t("前哈希:") + row.beforeShort),
|
|
1535
|
+
row.afterShort === "" ? null : h("span", { key: "after" }, t("后哈希:") + row.afterShort),
|
|
1232
1536
|
h("span", { key: "rb", className: row.rollbackable === true ? "dshd-rollback-ok" : "dshd-rollback-no" }, row.rollbackText),
|
|
1233
1537
|
]),
|
|
1234
1538
|
h("details", { key: "backup" }, [
|
|
1235
|
-
h("summary", { key: "summary" }, "备份标识与回滚命令"),
|
|
1539
|
+
h("summary", { key: "summary" }, t("备份标识与回滚命令")),
|
|
1236
1540
|
h("code", { key: "id", className: "dshd-cmd" }, row.id),
|
|
1237
1541
|
row.rollbackable === true ? h("code", { key: "command", className: "dshd-cmd", tabIndex: 0 }, bridgeRollbackCommandOf(row)) : null,
|
|
1238
1542
|
]),
|
|
1239
1543
|
row.rollbackable === true ? h(RollbackControl, { key: "rollback", row: row, onChanged: props.onChanged }) : null,
|
|
1240
1544
|
]);
|
|
1241
1545
|
})),
|
|
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 管理块;块外内容保留,外部修改的管理块会停止回滚。"),
|
|
1546
|
+
vm.shown < vm.total ? h("div", { className: "dshd-note", key: "more" }, t("只显示最近 ") + String(vm.shown) + " / " + String(vm.total) + t(" 条记录。")) : null,
|
|
1547
|
+
h("div", { className: "dshd-note", key: "note" }, t("回滚恢复对应备份的 Dream 管理块;块外内容保留,外部修改的管理块会停止回滚。")),
|
|
1244
1548
|
]);
|
|
1245
1549
|
}
|
|
1246
1550
|
|
|
@@ -1255,12 +1559,12 @@ function DreamCard(props) {
|
|
|
1255
1559
|
return h("article", {
|
|
1256
1560
|
className: "dshd-dream dshd-mood-" + String(moodSlot(dream.mood)) + (privacy && peek === true ? " dshd-peek" : ""),
|
|
1257
1561
|
onClick: clickCard,
|
|
1258
|
-
title: privacy ? "隐私模式:点击卡片临时查看" : undefined,
|
|
1562
|
+
title: privacy ? t("隐私模式:点击卡片临时查看") : undefined,
|
|
1259
1563
|
}, [
|
|
1260
1564
|
h("header", { className: "dshd-dream-head", key: "h" }, [
|
|
1261
1565
|
h("time", { className: "dshd-time", key: "t", dateTime: dream.at === "" ? undefined : dream.at, title: dream.at === "" ? timeText : timeText + " · " + dream.at }, timeText),
|
|
1262
1566
|
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 }, [
|
|
1567
|
+
h("span", { className: "dshd-mood", key: "m", title: t("心境:") + dream.mood }, [
|
|
1264
1568
|
h("i", { className: "dshd-mooddot " + toneClass(moodSlot(dream.mood)), key: "d" }),
|
|
1265
1569
|
dream.mood,
|
|
1266
1570
|
]),
|
|
@@ -1270,7 +1574,7 @@ function DreamCard(props) {
|
|
|
1270
1574
|
className: "dshd-linkbtn",
|
|
1271
1575
|
key: "btn",
|
|
1272
1576
|
onClick: function (event) { if (event !== undefined && event.stopPropagation !== undefined) event.stopPropagation(); setExpanded(expanded !== true); },
|
|
1273
|
-
}, expanded === true ? "收起" : "展开全文") : null,
|
|
1577
|
+
}, expanded === true ? t("收起") : t("展开全文")) : null,
|
|
1274
1578
|
]),
|
|
1275
1579
|
h("div", { className: "dshd-reflection" + (collapsible && expanded !== true ? " dshd-clamp" : ""), key: "r" }, inlineNodes(dream.reflection, "reflection-" + String(props.index))),
|
|
1276
1580
|
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 +1591,17 @@ function EmptyState(props) {
|
|
|
1287
1591
|
if (props.query !== undefined && props.query !== "") {
|
|
1288
1592
|
return h("div", { className: "dshd-empty" }, [
|
|
1289
1593
|
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,
|
|
1594
|
+
h("h3", { key: "t" }, t("没有找到「") + props.query + t("」相关的梦")),
|
|
1595
|
+
h("p", { key: "p" }, t("换个关键词试试,或者清空搜索看全部梦境。")),
|
|
1596
|
+
props.onClear ? h("div", { key: "a" }, h(Btn, { onClick: props.onClear }, t("清空搜索"))) : null,
|
|
1293
1597
|
]);
|
|
1294
1598
|
}
|
|
1295
1599
|
return h("div", { className: "dshd-empty" }, [
|
|
1296
1600
|
moon,
|
|
1297
|
-
h("h3", { key: "t" }, "还没有梦境日记"),
|
|
1298
|
-
h("p", { key: "p" }, "
|
|
1601
|
+
h("h3", { key: "t" }, t("还没有梦境日记")),
|
|
1602
|
+
h("p", { key: "p" }, t("自动整理的内容在「经验」页待审阅;日记可让助手复盘时单独生成。")),
|
|
1299
1603
|
h("div", { className: "dshd-chips", key: "c" }, [
|
|
1300
|
-
h("span", { className: "dshd-chip", key: "s1" }, "对 agent 说「做个梦」"),
|
|
1604
|
+
h("span", { className: "dshd-chip", key: "s1" }, t("对 agent 说「做个梦」")),
|
|
1301
1605
|
h("span", { className: "dshd-chip", key: "s2" }, "dream_digest → dream_save"),
|
|
1302
1606
|
]),
|
|
1303
1607
|
]);
|
|
@@ -1307,6 +1611,76 @@ function Skeleton() {
|
|
|
1307
1611
|
return h("div", null, [0, 1, 2].map((index) => h("div", { className: "dshd-skel", key: String(index) })));
|
|
1308
1612
|
}
|
|
1309
1613
|
|
|
1614
|
+
function AutomaticMemoryCard(props) {
|
|
1615
|
+
const [value, setValue] = useState(null);
|
|
1616
|
+
const [failure, setFailure] = useState(null);
|
|
1617
|
+
const [pending, setPending] = useState(false);
|
|
1618
|
+
const a = function (zh, en) { return languageOf() === 'en' ? en : zh; };
|
|
1619
|
+
useEffect(function () {
|
|
1620
|
+
let alive = true, timer;
|
|
1621
|
+
const controller = new AbortController();
|
|
1622
|
+
async function refresh() {
|
|
1623
|
+
try {
|
|
1624
|
+
const response = await fetch(routeUrlFor('api/dsh-dream/automatic', ''), { credentials: 'same-origin', signal: controller.signal });
|
|
1625
|
+
if (!response.ok) throw new Error(a('自动功能状态暂时无法读取,请刷新重试。', 'Automatic memory status is unavailable. Refresh to retry.'));
|
|
1626
|
+
const payload = await response.json();
|
|
1627
|
+
if (alive) { setValue(payload.automatic); setFailure(null); }
|
|
1628
|
+
} catch (error) { if (alive) setFailure(error); }
|
|
1629
|
+
if (alive) timer = setTimeout(refresh, 15000);
|
|
1630
|
+
}
|
|
1631
|
+
refresh();
|
|
1632
|
+
return function () { alive = false; clearTimeout(timer); controller.abort(); };
|
|
1633
|
+
}, [props.reloadToken]);
|
|
1634
|
+
async function toggle(key, next) {
|
|
1635
|
+
setPending(true); setFailure(null);
|
|
1636
|
+
try { const result = await postAction({ operation: 'automatic', [key]: next }); setValue(result.automatic); }
|
|
1637
|
+
catch (error) { setFailure(error); }
|
|
1638
|
+
finally { setPending(false); }
|
|
1639
|
+
}
|
|
1640
|
+
const reasons = {
|
|
1641
|
+
saved: a('已保存新候选,等待审阅', 'New candidates saved; review required'),
|
|
1642
|
+
merged: a('已合并重复经验', 'Duplicate lessons merged'),
|
|
1643
|
+
'nothing-useful': a('没有可复用的新经验', 'No reusable new lessons'),
|
|
1644
|
+
'ordinary-chat': a('普通闲聊,已跳过', 'Ordinary chat skipped'),
|
|
1645
|
+
'no-source': a('缺少可见来源,已跳过', 'No visible sources; skipped'),
|
|
1646
|
+
'daily-budget': a('今天的整理预算已用完', 'Daily consolidation budget reached'),
|
|
1647
|
+
cooldown: a('处于冷却时间,已跳过', 'Cooling down; skipped'),
|
|
1648
|
+
disabled: a('自动整理已暂停', 'Automatic collection paused'),
|
|
1649
|
+
unfinished: a('任务未正常完成,已跳过', 'Turn did not complete normally; skipped'),
|
|
1650
|
+
'no-model': a('当前会话尚无可用模型', 'No model route for this session'),
|
|
1651
|
+
recording: a('正在整理候选经验', 'Collecting candidate lessons'),
|
|
1652
|
+
timeout: a('整理超时,本次已停止', 'Consolidation timed out and stopped'),
|
|
1653
|
+
cancelled: a('新输入或关闭操作已停止整理', 'New input or a shutdown stopped consolidation'),
|
|
1654
|
+
superseded: a('已有新任务,旧轮次不再整理', 'New work superseded this turn; skipped'),
|
|
1655
|
+
'model-error': a('模型返回失败或无法核验,请检查 DSH 模型连接', 'Model output failed or could not be verified. Check the DSH model connection.'),
|
|
1656
|
+
'input-limit': a('内容超过预算,已跳过', 'Input exceeds the budget; skipped'),
|
|
1657
|
+
};
|
|
1658
|
+
const last = value && value.last;
|
|
1659
|
+
const minutes = Math.max(0, Number(value && value.cooldownMinutes) || 0);
|
|
1660
|
+
const seconds = Math.ceil(minutes * 60);
|
|
1661
|
+
const interval = minutes < 1 ? String(seconds) + a(' 秒', seconds === 1 ? ' second' : ' seconds') : new Intl.NumberFormat(languageOf() === 'en' ? 'en-US' : 'zh-CN', { maximumFractionDigits: 2 }).format(minutes) + a(' 分钟', minutes === 1 ? ' minute' : ' minutes');
|
|
1662
|
+
return h('section', { className: 'dshd-card dshd-kcard', 'aria-label': a('自动记忆', 'Automatic memory') }, [
|
|
1663
|
+
h('h3', { key: 'title' }, a('自动记忆', 'Automatic memory')),
|
|
1664
|
+
h('p', { key: 'intro' }, a('任务结束后整理可复用经验,不用记住“做梦”口令。新经验先待审阅,不自动修改项目规则。', 'Collect reusable lessons after tasks, without a trigger phrase. New lessons await review; project rules are not changed automatically.')),
|
|
1665
|
+
failure ? h('p', { key: 'error', role: 'alert' }, displayError(failure)) : null,
|
|
1666
|
+
!value ? (failure ? null : h('p', { key: 'loading' }, a('正在读取状态…', 'Loading status…'))) : h('div', { key: 'controls' }, [
|
|
1667
|
+
h('p', { key: 'status', role: 'status' }, value.problem ? a('状态异常,请检查 DSH 日志。', 'Status error. Check the DSH logs.') : value.available ? (value.enabled === false ? a('自动整理已暂停', 'Automatic collection paused') : value.running ? a('正在整理', 'Consolidating') : a('已就绪,等待符合条件的任务', 'Ready; waiting for a qualifying task')) : a('当前宿主服务尚未就绪;手动工具仍可使用。', 'Host services are not ready; manual tools remain available.')),
|
|
1668
|
+
h('label', { key: 'collect', style: { display: 'block', marginBottom: '8px' } }, [
|
|
1669
|
+
h('input', { key: 'input', type: 'checkbox', checked: value.enabled === true, disabled: pending, onChange: event => toggle('enabled', event.target.checked) }),
|
|
1670
|
+
' ' + a('自动整理候选经验', 'Collect candidate lessons automatically'),
|
|
1671
|
+
]),
|
|
1672
|
+
h('label', { key: 'retrieve', style: { display: 'block' } }, [
|
|
1673
|
+
h('input', { key: 'input', type: 'checkbox', checked: value.retrievalEnabled === true, disabled: pending, onChange: event => toggle('retrievalEnabled', event.target.checked) }),
|
|
1674
|
+
' ' + a('自动取回已采纳经验', 'Retrieve accepted lessons automatically'),
|
|
1675
|
+
]),
|
|
1676
|
+
h('p', { key: 'budget' }, a('今天已调用模型 ', 'Model calls today: ') + String(value.callsToday ?? 0) + ' / ' + String(value.maxCallsPerDay ?? 0) + a(' 次;每次间隔至少 ', '; at least ') + interval + a('。', ' between calls.')),
|
|
1677
|
+
h('p', { key: 'cost', className: 'dshd-note' }, a('沿用当前会话模型,会消耗模型额度;普通闲聊跳过。每次最多 ', 'Uses the current session model and its quota; ordinary chat is skipped. Up to ') + String(value.maxOutputTokens ?? 0) + a(' 个输出 token。', ' output tokens per call.')),
|
|
1678
|
+
last ? h('p', { key: 'last' }, a('最近检查:', 'Last check: ') + formatDate(new Date(last.at).toISOString()) + ' ' + formatTime(new Date(last.at).toISOString()) + ' · ' + (reasons[last.reason] || a('已检查', 'Checked'))) : null,
|
|
1679
|
+
h(Btn, { key: 'review', onClick: props.onReview }, a('待审阅 ', 'Awaiting review: ') + String(value.candidates ?? 0)),
|
|
1680
|
+
]),
|
|
1681
|
+
]);
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1310
1684
|
// ───────────────────────── 主面板 ─────────────────────────
|
|
1311
1685
|
|
|
1312
1686
|
function DreamPanel() {
|
|
@@ -1363,7 +1737,7 @@ function DreamPanel() {
|
|
|
1363
1737
|
setKnowledgeError(null);
|
|
1364
1738
|
fetchKnowledge().then(function (payload) {
|
|
1365
1739
|
if (alive !== true) return;
|
|
1366
|
-
setKnowledge(
|
|
1740
|
+
setKnowledge(payload);
|
|
1367
1741
|
setKnowledgeLoading(false);
|
|
1368
1742
|
}).catch(function (failure) {
|
|
1369
1743
|
if (alive !== true) return;
|
|
@@ -1381,7 +1755,7 @@ function DreamPanel() {
|
|
|
1381
1755
|
setBridgeError(null);
|
|
1382
1756
|
fetchBridgeRecords().then(function (payload) {
|
|
1383
1757
|
if (alive !== true) return;
|
|
1384
|
-
setBridge(
|
|
1758
|
+
setBridge(payload);
|
|
1385
1759
|
setBridgeLoading(false);
|
|
1386
1760
|
}).catch(function (failure) {
|
|
1387
1761
|
if (alive !== true) return;
|
|
@@ -1393,8 +1767,8 @@ function DreamPanel() {
|
|
|
1393
1767
|
}, [reloadToken]);
|
|
1394
1768
|
|
|
1395
1769
|
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;
|
|
1770
|
+
const knowledgeVm = knowledge === null ? { rows: [], total: 0, shown: 0, stats: null } : normalizeKnowledge(knowledge, now);
|
|
1771
|
+
const bridgeVm = bridge === null ? { rows: [], total: 0, shown: 0, rollbackable: 0 } : normalizeBridgeRecords(bridge, now);
|
|
1398
1772
|
const pristine = vm.count === 0 && query === "";
|
|
1399
1773
|
const filterCount = filterCountOf(filters);
|
|
1400
1774
|
const visibleDreams = applyFilters(dreams, filters);
|
|
@@ -1486,8 +1860,8 @@ function DreamPanel() {
|
|
|
1486
1860
|
className: "dshd-input",
|
|
1487
1861
|
type: "text",
|
|
1488
1862
|
value: queryInput,
|
|
1489
|
-
placeholder: "搜索反思与教训…",
|
|
1490
|
-
"aria-label": "搜索梦境",
|
|
1863
|
+
placeholder: t("搜索反思与教训…"),
|
|
1864
|
+
"aria-label": t("搜索梦境"),
|
|
1491
1865
|
onChange: function (event) { setQueryInput(event.target.value); },
|
|
1492
1866
|
onKeyDown: function (event) { if (event.key === "Enter") setQuery(event.target.value.trim()); },
|
|
1493
1867
|
}),
|
|
@@ -1495,13 +1869,13 @@ function DreamPanel() {
|
|
|
1495
1869
|
key: "clear-search",
|
|
1496
1870
|
type: "button",
|
|
1497
1871
|
className: "dshd-clear",
|
|
1498
|
-
"aria-label": "清空搜索",
|
|
1872
|
+
"aria-label": t("清空搜索"),
|
|
1499
1873
|
onClick: clearSearch,
|
|
1500
1874
|
}, "×"),
|
|
1501
1875
|
]),
|
|
1502
1876
|
h("span", { className: "dshd-spacer", key: "spacer" }),
|
|
1503
1877
|
pristine || activeTab !== "journal" ? null : h("span", { className: "dshd-hint", key: "count" }, loading === true
|
|
1504
|
-
? "加载中…"
|
|
1878
|
+
? t("加载中…")
|
|
1505
1879
|
: countTextOf(query, vm.total, vm.count, filterCount, visibleDreams.length)),
|
|
1506
1880
|
showFilterButton === true && activeTab === "journal" ? h("div", { className: "dshd-filter", key: "filter" }, [
|
|
1507
1881
|
h(Btn, {
|
|
@@ -1509,10 +1883,10 @@ function DreamPanel() {
|
|
|
1509
1883
|
on: filterCount > 0,
|
|
1510
1884
|
pressed: filterCount > 0,
|
|
1511
1885
|
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) + " 场,不影响全量统计"),
|
|
1886
|
+
title: t("筛选作用于已加载窗口,不影响全量统计"),
|
|
1887
|
+
}, t("筛选") + (filterCount > 0 ? " · " + String(filterCount) : "") + " ▾"),
|
|
1888
|
+
filterOpen === true ? h("div", { className: "dshd-filterpop", key: "pop", role: "group", "aria-label": t("筛选梦境") }, [
|
|
1889
|
+
h("div", { className: "dshd-filtertitle", key: "title" }, t("筛选当前已加载的 ") + String(vm.count) + t(" 场,不影响全量统计")),
|
|
1516
1890
|
h("div", { key: "moods" }, vm.moodRows.map(function (row) {
|
|
1517
1891
|
const on = filters.moods.indexOf(row.mood) !== -1;
|
|
1518
1892
|
return h("button", {
|
|
@@ -1528,10 +1902,10 @@ function DreamPanel() {
|
|
|
1528
1902
|
className: "dshd-chipbtn" + (filters.withLessons === true ? " on" : ""),
|
|
1529
1903
|
"aria-pressed": filters.withLessons === true ? "true" : "false",
|
|
1530
1904
|
onClick: toggleOnlyLessons,
|
|
1531
|
-
}, "只看有教训的梦")),
|
|
1905
|
+
}, t("只看有教训的梦"))),
|
|
1532
1906
|
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); } }, "完成"),
|
|
1907
|
+
h("button", { key: "clearbtn", type: "button", className: "dshd-linkbtn", onClick: clearFilters, disabled: filterCount === 0 }, t("清空筛选")),
|
|
1908
|
+
h(Btn, { key: "done", onClick: function () { setFilterOpen(false); } }, t("完成")),
|
|
1535
1909
|
]),
|
|
1536
1910
|
]) : null,
|
|
1537
1911
|
]) : null,
|
|
@@ -1540,15 +1914,15 @@ function DreamPanel() {
|
|
|
1540
1914
|
onClick: togglePrivacy,
|
|
1541
1915
|
on: privacy === true,
|
|
1542
1916
|
pressed: privacy === true,
|
|
1543
|
-
title: "显示层模糊;DOM 仍为明文,不改文件、不加密",
|
|
1544
|
-
}, privacy === true ? "隐私模式:开" : "隐私模式"),
|
|
1545
|
-
h(Btn, { key: "refresh", onClick: reload, disabled: loading === true, title: "重新读取梦境日记" }, "刷新"),
|
|
1917
|
+
title: t("显示层模糊;DOM 仍为明文,不改文件、不加密"),
|
|
1918
|
+
}, privacy === true ? t("隐私模式:开") : t("隐私模式")),
|
|
1919
|
+
h(Btn, { key: "refresh", onClick: reload, disabled: loading === true, title: t("重新读取梦境日记") }, t("刷新")),
|
|
1546
1920
|
]);
|
|
1547
1921
|
|
|
1548
1922
|
const errorBox = error === null ? null : h("div", { className: "dshd-alert error", role: "alert", key: "error" }, [
|
|
1549
|
-
h("span", { key: "m" }, "读取梦境日记失败:" + (error
|
|
1923
|
+
h("span", { key: "m" }, t("读取梦境日记失败:") + displayError(error)),
|
|
1550
1924
|
h("span", { className: "dshd-spacer", key: "s" }),
|
|
1551
|
-
h(Btn, { key: "retry", onClick: reload, variant: "primary" }, "重试"),
|
|
1925
|
+
h(Btn, { key: "retry", onClick: reload, variant: "primary" }, t("重试")),
|
|
1552
1926
|
]);
|
|
1553
1927
|
|
|
1554
1928
|
const body = function () {
|
|
@@ -1558,8 +1932,8 @@ function DreamPanel() {
|
|
|
1558
1932
|
if (vm.count === 0) {
|
|
1559
1933
|
if (error !== null) return h("div", { key: "failed-wrap" }, [
|
|
1560
1934
|
h("div", { className: "dshd-empty", key: "failed" }, [
|
|
1561
|
-
h("h3", { key: "t" }, "暂时读不到梦境日记"),
|
|
1562
|
-
h("p", { key: "p" }, "等宿主路由就绪后点上面的「重试」;如果一直失败,看一眼 DSH 日志里有没有 [dsh-dream] 告警。"),
|
|
1935
|
+
h("h3", { key: "t" }, t("暂时读不到梦境日记")),
|
|
1936
|
+
h("p", { key: "p" }, t("等宿主路由就绪后点上面的「重试」;如果一直失败,看一眼 DSH 日志里有没有 [dsh-dream] 告警。")),
|
|
1563
1937
|
]),
|
|
1564
1938
|
]);
|
|
1565
1939
|
return h(EmptyState, { key: "empty", query: query, onClear: query === "" ? null : clearSearch });
|
|
@@ -1567,10 +1941,10 @@ function DreamPanel() {
|
|
|
1567
1941
|
return h("div", { key: "content" }, [
|
|
1568
1942
|
vm.showTiles
|
|
1569
1943
|
? 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 }),
|
|
1944
|
+
h(StatTile, { key: "total", label: t("梦境总数"), value: String(vm.total) + t(" 场"), hint: vm.total > vm.count ? t("本页显示最近 ") + String(vm.count) + t(" 场") : t("全部梦境日记") }),
|
|
1945
|
+
h(StatTile, { key: "span", label: t("时间跨度"), value: vm.spanText, hint: vm.spanHint, title: vm.spanFull }),
|
|
1946
|
+
h(StatTile, { key: "latest", label: t("最近一场"), value: vm.latestText, hint: vm.latestDate }),
|
|
1947
|
+
h(StatTile, { key: "mood", label: t("心境"), value: String(vm.moodCount) + t(" 种"), hint: vm.moodCount === 0 ? "" : t("最常见:") + vm.moodRows[0].mood }),
|
|
1574
1948
|
])
|
|
1575
1949
|
: h("div", { className: "dshd-headline", key: "headline" }, vm.headline),
|
|
1576
1950
|
h("div", { className: "dshd-two", key: "two" }, [
|
|
@@ -1578,14 +1952,14 @@ function DreamPanel() {
|
|
|
1578
1952
|
h(LessonCard, { key: "lessons", vm: vm }),
|
|
1579
1953
|
]),
|
|
1580
1954
|
h("div", { className: "dshd-listhead", key: "listhead" }, [
|
|
1581
|
-
h("h4", { key: "t" }, query === "" && filterCount === 0 ? "梦境时间线" : (query === "" ? "筛选结果" : "搜索结果")),
|
|
1955
|
+
h("h4", { key: "t" }, query === "" && filterCount === 0 ? t("梦境时间线") : (query === "" ? t("筛选结果") : t("搜索结果"))),
|
|
1582
1956
|
h("span", { className: "dshd-hint", key: "n" }, listHintOf(query, vm.count, filterCount, visibleDreams.length)),
|
|
1583
1957
|
]),
|
|
1584
1958
|
visibleDreams.length === 0 && filterCount > 0
|
|
1585
1959
|
? h("div", { className: "dshd-filterempty", key: "filtered" }, [
|
|
1586
|
-
h("span", { key: "t" }, "没有符合筛选的梦(筛选作用于已加载的 " + String(vm.count) + " 场)。"),
|
|
1960
|
+
h("span", { key: "t" }, t("没有符合筛选的梦(筛选作用于已加载的 ") + String(vm.count) + t(" 场)。")),
|
|
1587
1961
|
h("span", { className: "dshd-spacer", key: "s" }),
|
|
1588
|
-
h(Btn, { key: "clearbtn", onClick: clearFilters }, "清空筛选"),
|
|
1962
|
+
h(Btn, { key: "clearbtn", onClick: clearFilters }, t("清空筛选")),
|
|
1589
1963
|
])
|
|
1590
1964
|
: h("div", { className: "dshd-timeline", key: "timeline" }, visibleDreams.map(function (dream, index) {
|
|
1591
1965
|
return h(DreamCard, { key: (dream.id !== "" ? dream.id : "dream") + "#" + String(index), dream: dream, now: now, privacy: privacy, index: index });
|
|
@@ -1596,26 +1970,28 @@ function DreamPanel() {
|
|
|
1596
1970
|
return h("div", { className: "dshd-root" + (privacy === true ? " dshd-privacy" : ""), key: "root" }, [
|
|
1597
1971
|
h("div", { className: "dshd-shell", key: "shell" }, [
|
|
1598
1972
|
errorBox,
|
|
1599
|
-
h(
|
|
1973
|
+
h(AutomaticMemoryCard, { key: 'automatic', reloadToken: reloadToken, onReview: function () { setActiveTab('knowledge'); reload(); } }),
|
|
1974
|
+
h("nav", { className: "dshd-tabs", key: "tabs", "aria-label": t("梦境面板视图") }, [["journal", t("日记")], ["knowledge", t("经验")], ["bridge", t("写入记录")]].map(function (item) {
|
|
1600
1975
|
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
1976
|
})),
|
|
1602
1977
|
toolbar,
|
|
1603
|
-
privacy === true ? h("div", { className: "dshd-note", key: "privacynote", role: "status" }, "隐私模式:显示层模糊;DOM 仍为明文,不改文件、不加密。点击卡片可临时查看。") : null,
|
|
1978
|
+
privacy === true ? h("div", { className: "dshd-note", key: "privacynote", role: "status" }, t("隐私模式:显示层模糊;DOM 仍为明文,不改文件、不加密。点击卡片可临时查看。")) : null,
|
|
1604
1979
|
body(),
|
|
1605
1980
|
activeTab !== "journal" ? null : h("div", { className: "dshd-footer", key: "footer" }, [
|
|
1606
1981
|
h("span", { key: "l" }, footerTextOf(query, limit, vm.total, vm.count)),
|
|
1607
1982
|
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)"),
|
|
1983
|
+
canLoadMore === true ? h(Btn, { key: "more", onClick: showMore, disabled: loading === true }, loading === true ? t("加载中…") : t("显示更早的梦")) : null,
|
|
1984
|
+
h("span", { key: "r" }, t("数据来自梦境日记(dreams.jsonl)")),
|
|
1610
1985
|
]),
|
|
1611
1986
|
]),
|
|
1612
1987
|
]);
|
|
1613
1988
|
}
|
|
1614
1989
|
|
|
1615
|
-
function DreamSection() {
|
|
1990
|
+
function DreamSection({ locale } = {}) {
|
|
1991
|
+
useLanguage(locale);
|
|
1616
1992
|
return h("div", null, [
|
|
1617
|
-
h("h2", { key: "t" }, "梦境日记"),
|
|
1618
|
-
h("p", { className: "dshd-sub", key: "p" }, "
|
|
1993
|
+
h("h2", { key: "t" }, t("梦境日记")),
|
|
1994
|
+
h("p", { className: "dshd-sub", key: "p" }, t("自动整理任务经验,审阅后回用;日记保留独立的反思记录。")),
|
|
1619
1995
|
h(DreamPanel, { key: "panel" }),
|
|
1620
1996
|
]);
|
|
1621
1997
|
}
|
|
@@ -1761,9 +2137,10 @@ const CSS = [
|
|
|
1761
2137
|
|
|
1762
2138
|
// ───────────────────────── 挂载 ─────────────────────────
|
|
1763
2139
|
|
|
1764
|
-
const inject = ["slots"];
|
|
2140
|
+
const inject = ["slots", "locale"];
|
|
1765
2141
|
|
|
1766
2142
|
function apply(ctx) {
|
|
2143
|
+
try { localeService = ctx.get?.("locale") || ctx.locale; } catch { localeService = ctx.locale; }
|
|
1767
2144
|
try {
|
|
1768
2145
|
ctx.effect(function () {
|
|
1769
2146
|
const id = "dsh-dream/client";
|
|
@@ -1783,8 +2160,8 @@ function apply(ctx) {
|
|
|
1783
2160
|
name: "settings.section",
|
|
1784
2161
|
id: "dsh-dream",
|
|
1785
2162
|
order: 55,
|
|
1786
|
-
label: function () { return "梦境日记"; },
|
|
1787
|
-
inject: function () { return {}; },
|
|
2163
|
+
label: function () { return t("梦境日记"); },
|
|
2164
|
+
inject: function () { return { locale: localeService }; },
|
|
1788
2165
|
}, DreamSection);
|
|
1789
2166
|
});
|
|
1790
2167
|
} catch (error) { /* 没有 slots 服务:仍是合法插件,只是没有设置页 */ }
|
|
@@ -1794,6 +2171,7 @@ exports.apply = apply;
|
|
|
1794
2171
|
exports.inject = inject;
|
|
1795
2172
|
// 纯函数内部件:验证脚本可直接取来测,不必另抄一份算法。
|
|
1796
2173
|
exports.__internals = {
|
|
2174
|
+
displayError: displayError,
|
|
1797
2175
|
routeUrl: routeUrl,
|
|
1798
2176
|
routeUrlFor: routeUrlFor,
|
|
1799
2177
|
KNOWLEDGE_ROUTE: KNOWLEDGE_ROUTE,
|