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