@stardustlc/dsh-dream 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.en.md +35 -13
- package/README.md +39 -17
- package/lib/bridge.d.ts +205 -2
- package/lib/bridge.js +1053 -6
- package/lib/client.js +564 -27
- package/lib/evidence.d.ts +15 -0
- package/lib/evidence.js +123 -0
- package/lib/journal.d.ts +15 -0
- package/lib/journal.js +32 -11
- package/lib/knowledge-store.d.ts +111 -10
- package/lib/knowledge-store.js +1056 -198
- package/lib/knowledge.d.ts +55 -3
- package/lib/knowledge.js +65 -5
- package/lib/mask.d.ts +3 -1
- package/lib/mask.js +8 -2
- package/lib/retrieval.d.ts +47 -7
- package/lib/retrieval.js +196 -19
- package/lib/tools.js +406 -60
- package/lib/web.d.ts +52 -2
- package/lib/web.js +155 -14
- package/package.json +2 -2
- package/skills/dream-protocol/SKILL.md +15 -6
package/lib/client.js
CHANGED
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
// 仓库里没有 src/client.ts,pnpm run build 也不会重写它。改面板请直接改这里,
|
|
3
3
|
// 改完用 node --check lib/client.js 自检(CI 里也会跑这一步)。
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
//
|
|
5
|
+
// 面板做四件事:把梦境日记(dreams.jsonl)画成人能看懂的样子 —— 概览、心境方块、
|
|
6
|
+
// 教训榜、按时间倒序的梦卡片;只读展示技术经验(<journalDir>/knowledge/);
|
|
7
|
+
// 只读展示「待核实」(candidate/disputed/stale + 被扣下的原因)与「写入记录」
|
|
8
|
+
//(<journalDir>/bridge/ 的桥接应用记录)。
|
|
9
|
+
// 数据来自插件自己的同源只读路由 GET /_dsh/dsh-dream/journal、/_dsh/dsh-dream/knowledge
|
|
10
|
+
// 与 /_dsh/dsh-dream/bridge(由 src/index.ts 挂载);前端不缓存业务数据,刷新即见。
|
|
11
|
+
// 经验区块只展示状态 / 条件 / 范围 / 适用与例外 / 证据摘要 / 最近核验;日志截断或坏行会明确提示,
|
|
12
|
+
// 不展示权重、向量或原始 JSON。经验行只提供「复制审阅 / 预览命令」,写操作仍在对话工具里执行。
|
|
10
13
|
// 隐私纪律:面板只读,不写文件、不上传;「隐私模式」只做显示层模糊,DOM 仍是明文。
|
|
11
14
|
window.__ModuleLoader__.load({ id: "@stardustlc/dsh-dream", factory: (require) => {
|
|
12
15
|
var module = { exports: {} }; var exports = module.exports;
|
|
@@ -19,8 +22,11 @@ const h = React.createElement;
|
|
|
19
22
|
// 相对路径按 document.baseURI 解析:反代挂在 /tools/dsh/ 这类前缀下也不会打偏。
|
|
20
23
|
const ROUTE = "_dsh/dsh-dream/journal";
|
|
21
24
|
const KNOWLEDGE_ROUTE = "_dsh/dsh-dream/knowledge";
|
|
25
|
+
const BRIDGE_ROUTE = "_dsh/dsh-dream/bridge";
|
|
22
26
|
const KNOWLEDGE_LIMIT = 50;
|
|
27
|
+
const BRIDGE_LIMIT = 50;
|
|
23
28
|
const KNOWLEDGE_STATES = ["candidate", "usable", "disputed", "stale", "rejected"];
|
|
29
|
+
const HELD_STATES = ["candidate", "disputed", "stale"];
|
|
24
30
|
const DEFAULT_LIMIT = 50;
|
|
25
31
|
const MORE_LIMIT = 500;
|
|
26
32
|
const FETCH_TIMEOUT_MS = 15000;
|
|
@@ -120,6 +126,40 @@ async function fetchKnowledge() {
|
|
|
120
126
|
};
|
|
121
127
|
}
|
|
122
128
|
|
|
129
|
+
/** GET /_dsh/dsh-dream/bridge:只读契约体 { records, stats },无查询参数。 */
|
|
130
|
+
async function fetchBridgeRecords() {
|
|
131
|
+
const init = { credentials: "same-origin" };
|
|
132
|
+
if (typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function") init.signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
|
|
133
|
+
const res = await fetch(routeUrlFor(BRIDGE_ROUTE, ""), init);
|
|
134
|
+
let body = null;
|
|
135
|
+
try { body = await res.json(); } catch (error) { body = null; }
|
|
136
|
+
if (body === null || typeof body !== "object") throw new Error("服务器返回的不是 JSON(HTTP " + String(res.status) + ")");
|
|
137
|
+
if (res.ok !== true || body.ok === false) {
|
|
138
|
+
const failure = new Error(errorMessageOf(body, res.status));
|
|
139
|
+
failure.status = res.status;
|
|
140
|
+
failure.code = body.error !== null && typeof body.error === "object" ? body.error.code : undefined;
|
|
141
|
+
throw failure;
|
|
142
|
+
}
|
|
143
|
+
const data = body.value !== null && typeof body.value === "object" ? body.value : body;
|
|
144
|
+
return {
|
|
145
|
+
records: Array.isArray(data.records) ? data.records : [],
|
|
146
|
+
stats: data.stats !== null && typeof data.stats === "object" ? data.stats : null,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** 复制命令到剪贴板;没有 clipboard / 被拒绝时返回 false,调用方回退为可选中文本。 */
|
|
151
|
+
async function copyText(text) {
|
|
152
|
+
const value = String(text);
|
|
153
|
+
if (value === "") return false;
|
|
154
|
+
try {
|
|
155
|
+
if (typeof navigator !== "undefined" && navigator !== null && navigator.clipboard !== undefined && navigator.clipboard !== null && typeof navigator.clipboard.writeText === "function") {
|
|
156
|
+
await navigator.clipboard.writeText(value);
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
} catch (error) { /* 权限被拒 / 非安全上下文:回退展示可选中文本 */ }
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
123
163
|
function normalizeDreams(value, limit) {
|
|
124
164
|
if (!Array.isArray(value) || limit <= 0) return [];
|
|
125
165
|
const out = [];
|
|
@@ -224,15 +264,42 @@ function trimmedText(value) {
|
|
|
224
264
|
return typeof value === "string" ? value.trim() : "";
|
|
225
265
|
}
|
|
226
266
|
|
|
267
|
+
/** applicability 数组 → 紧凑适用串(package + versions / platform);坏形状跳过,空数组给 ""。 */
|
|
268
|
+
function applicabilityTextOf(value) {
|
|
269
|
+
if (!Array.isArray(value)) return "";
|
|
270
|
+
const parts = [];
|
|
271
|
+
for (const raw of value) {
|
|
272
|
+
if (raw === null || typeof raw !== "object") continue;
|
|
273
|
+
const pkg = trimmedText(raw.package);
|
|
274
|
+
const versions = trimmedText(raw.versions);
|
|
275
|
+
const platform = trimmedText(raw.platform);
|
|
276
|
+
const main = pkg !== "" ? (versions !== "" ? pkg + " " + versions : pkg) : versions;
|
|
277
|
+
const bits = [];
|
|
278
|
+
if (main !== "") bits.push(main);
|
|
279
|
+
if (platform !== "") bits.push("平台 " + platform);
|
|
280
|
+
if (bits.length > 0) parts.push(bits.join(" · "));
|
|
281
|
+
}
|
|
282
|
+
if (parts.length === 0) return "";
|
|
283
|
+
if (parts.length > 4) return parts.slice(0, 4).join(";") + ";等 " + String(parts.length) + " 条适用范围";
|
|
284
|
+
return parts.join(";");
|
|
285
|
+
}
|
|
286
|
+
|
|
227
287
|
/**
|
|
228
|
-
* 经验视图模型:只投影展示所需字段(title / when /
|
|
229
|
-
* evidenceSummary / lastValidatedAt
|
|
230
|
-
*
|
|
288
|
+
* 经验视图模型:只投影展示所需字段(title / when / applicability / exceptions /
|
|
289
|
+
* state / scopeLabel / evidenceSummary / lastValidatedAt),并派生 revision / 扣留原因 /
|
|
290
|
+
* 可桥接标记与复制命令所需的稳定字段;不碰权重、向量、原始 JSON。
|
|
291
|
+
* limit 只截断渲染行数,byState 仍按全量统计;truncated / badLines 原样带给提示行。
|
|
231
292
|
*/
|
|
232
293
|
function normalizeKnowledge(value, now, limit) {
|
|
233
294
|
const source = value !== null && typeof value === "object" ? value : {};
|
|
234
295
|
const cap = typeof limit === "number" && limit > 0 ? limit : KNOWLEDGE_LIMIT;
|
|
235
296
|
const rawLessons = Array.isArray(source.lessons) ? source.lessons : [];
|
|
297
|
+
const lessonTitles = new Map();
|
|
298
|
+
for (const raw of rawLessons) {
|
|
299
|
+
if (raw !== null && typeof raw === "object" && trimmedText(raw.id) !== "") {
|
|
300
|
+
lessonTitles.set(trimmedText(raw.id), trimmedText(raw.title) || trimmedText(raw.action));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
236
303
|
const statsSource = source.stats !== null && typeof source.stats === "object" ? source.stats : {};
|
|
237
304
|
const byState = {};
|
|
238
305
|
const rows = [];
|
|
@@ -248,7 +315,12 @@ function normalizeKnowledge(value, now, limit) {
|
|
|
248
315
|
rows.push({
|
|
249
316
|
id: trimmedText(raw.id),
|
|
250
317
|
title: title !== "" ? title : action,
|
|
318
|
+
action: action,
|
|
251
319
|
when: trimmedText(raw.when),
|
|
320
|
+
applicability: applicabilityTextOf(raw.applicability),
|
|
321
|
+
exceptions: Array.isArray(raw.exceptions)
|
|
322
|
+
? raw.exceptions.filter((item) => typeof item === "string" && item.trim() !== "").map((item) => item.trim()).slice(0, 10)
|
|
323
|
+
: [],
|
|
252
324
|
state: state,
|
|
253
325
|
stateLabel: lessonStateLabel(state),
|
|
254
326
|
stateClass: lessonStateClass(state),
|
|
@@ -257,17 +329,28 @@ function normalizeKnowledge(value, now, limit) {
|
|
|
257
329
|
lastValidatedAt: lastValidatedAt,
|
|
258
330
|
lastText: lastValidatedAt === "" ? "尚未核验" : relativeTime(lastValidatedAt, now),
|
|
259
331
|
lastTitle: lastValidatedAt === "" ? "没有 lastValidatedAt(从未核验)" : formatDate(lastValidatedAt) + (formatTime(lastValidatedAt) === "" ? "" : " " + formatTime(lastValidatedAt)),
|
|
332
|
+
revision: typeof raw.revision === "number" && Number.isFinite(raw.revision) && raw.revision >= 0 ? Math.floor(raw.revision) : 0,
|
|
333
|
+
independentSupportCount: typeof raw.independentSupportCount === "number" && raw.independentSupportCount > 0 ? Math.floor(raw.independentSupportCount) : 0,
|
|
334
|
+
projectId: typeof raw.scope === "object" && raw.scope !== null ? trimmedText(raw.scope.projectId) : "",
|
|
335
|
+
workspaceRoot: typeof raw.scope === "object" && raw.scope !== null ? trimmedText(raw.scope.workspaceRoot) : "",
|
|
336
|
+
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 + ")" }; }) : [],
|
|
338
|
+
holdReason: trimmedText(raw.holdReason) !== "" ? trimmedText(raw.holdReason) : holdReasonOf(raw),
|
|
339
|
+
holdReasonLabel: trimmedText(raw.holdReasonLabel) !== "" ? trimmedText(raw.holdReasonLabel) : skippedReasonLabel(holdReasonOf(raw)),
|
|
340
|
+
bridgeable: bridgeableOf(raw.state) && trimmedText(raw.id) !== "",
|
|
260
341
|
candidate: state === "candidate",
|
|
261
342
|
disputed: state === "disputed",
|
|
262
343
|
});
|
|
263
344
|
}
|
|
264
345
|
const total = typeof statsSource.lessons === "number" && statsSource.lessons >= 0 ? statsSource.lessons : rawLessons.length;
|
|
265
346
|
const evidenceTotal = typeof statsSource.evidence === "number" && statsSource.evidence >= 0 ? statsSource.evidence : 0;
|
|
347
|
+
const badLinesRaw = statsSource.badLines;
|
|
348
|
+
const badLines = typeof badLinesRaw === "number" && Number.isFinite(badLinesRaw) && badLinesRaw > 0 ? Math.floor(badLinesRaw) : 0;
|
|
266
349
|
return {
|
|
267
350
|
rows: rows,
|
|
268
351
|
total: total,
|
|
269
352
|
shown: rows.length,
|
|
270
|
-
stats: { lessons: total, evidence: evidenceTotal, byState: byState },
|
|
353
|
+
stats: { lessons: total, evidence: evidenceTotal, byState: byState, truncated: statsSource.truncated === true, badLines: badLines },
|
|
271
354
|
};
|
|
272
355
|
}
|
|
273
356
|
|
|
@@ -287,6 +370,18 @@ function knowledgeCountText(stats) {
|
|
|
287
370
|
return parts.join(" · ");
|
|
288
371
|
}
|
|
289
372
|
|
|
373
|
+
/** 截断 / 坏行提示:无问题时给 "";确保面板不静默展示不完整的经验集。 */
|
|
374
|
+
function knowledgeNoticeText(stats) {
|
|
375
|
+
const source = stats !== null && typeof stats === "object" ? stats : {};
|
|
376
|
+
const truncated = source.truncated === true;
|
|
377
|
+
const badLines = typeof source.badLines === "number" && Number.isFinite(source.badLines) && source.badLines > 0 ? Math.floor(source.badLines) : 0;
|
|
378
|
+
if (truncated !== true && badLines === 0) return "";
|
|
379
|
+
const parts = [];
|
|
380
|
+
if (truncated === true) parts.push("日志过大,仅回放最近一段事件");
|
|
381
|
+
if (badLines > 0) parts.push("有 " + String(badLines) + " 行损坏已跳过");
|
|
382
|
+
return parts.join(";") + ";events.jsonl 未改动。";
|
|
383
|
+
}
|
|
384
|
+
|
|
290
385
|
/** 经验区块的独立空态:与「还没有梦境日记」分开,说明经验从哪来。 */
|
|
291
386
|
function knowledgeEmptyState() {
|
|
292
387
|
return {
|
|
@@ -296,6 +391,180 @@ function knowledgeEmptyState() {
|
|
|
296
391
|
};
|
|
297
392
|
}
|
|
298
393
|
|
|
394
|
+
/** 检索层 skipped reason 的中文映射(面板扣留原因文案;与 retrieval.ts 的 reason 常量同名)。 */
|
|
395
|
+
function skippedReasonLabel(reason) {
|
|
396
|
+
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 "超出字符预算,本次未取回";
|
|
406
|
+
default: return "";
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** 浏览态的确定性扣留原因;version-mismatch 需要 query / 包名上下文,不在此列。 */
|
|
411
|
+
function holdReasonOf(lesson) {
|
|
412
|
+
const source = lesson !== null && typeof lesson === "object" ? lesson : {};
|
|
413
|
+
const state = normalizeLessonState(source.state);
|
|
414
|
+
if (state === "disputed") return "state:disputed";
|
|
415
|
+
if (state === "stale") return "state:stale";
|
|
416
|
+
if (state === "rejected") return "state:rejected";
|
|
417
|
+
if (state === "candidate") {
|
|
418
|
+
const support = typeof source.independentSupportCount === "number" && source.independentSupportCount > 0 ? source.independentSupportCount : 0;
|
|
419
|
+
return support === 0 ? "no-evidence" : "candidate-hold";
|
|
420
|
+
}
|
|
421
|
+
return "";
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** 面板上的「可桥接」只按契约状态判断:usable;scope / 用户采纳等资格由 dream_bridge 执行时校验。 */
|
|
425
|
+
function bridgeableOf(state) {
|
|
426
|
+
return normalizeLessonState(state) === "usable";
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** 复制命令里的默认 action:按状态给最常用的一步,用户可在对话里再改。 */
|
|
430
|
+
function reviewActionFor(state) {
|
|
431
|
+
switch (normalizeLessonState(state)) {
|
|
432
|
+
case "candidate": return "accept";
|
|
433
|
+
case "disputed": return "resolve-conflict";
|
|
434
|
+
case "stale": return "accept";
|
|
435
|
+
case "usable": return "mark-stale";
|
|
436
|
+
case "rejected": return "reopen";
|
|
437
|
+
default: return "";
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/** 派生 dream_review 复制命令;缺 id 时给 ""(面板不展示不可执行命令)。 */
|
|
442
|
+
function reviewCommandOf(lesson, options) {
|
|
443
|
+
const source = lesson !== null && typeof lesson === "object" ? lesson : {};
|
|
444
|
+
const id = trimmedText(source.id);
|
|
445
|
+
if (id === "" || !Number.isInteger(source.revision) || source.revision < 1) return "";
|
|
446
|
+
const opts = options || {};
|
|
447
|
+
const action = opts.action || reviewActionFor(source.state);
|
|
448
|
+
if (action === "") return "";
|
|
449
|
+
const args = { action: action, lessonId: id, expectedRevision: source.revision };
|
|
450
|
+
if (action === "resolve-conflict") {
|
|
451
|
+
if (!["prefer", "drop", "merge"].includes(opts.resolution) || trimmedText(opts.note) === "" || !Array.isArray(opts.affectedIds) || opts.affectedIds.length === 0) return "";
|
|
452
|
+
args.resolution = opts.resolution;
|
|
453
|
+
args.note = opts.note.trim();
|
|
454
|
+
args.affectedIds = opts.affectedIds;
|
|
455
|
+
}
|
|
456
|
+
return "dream_review " + JSON.stringify(args);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** 派生 dream_bridge preview 复制命令(单条经验);projectId 来自 payload,scopeLabel 只展示。 */
|
|
460
|
+
function bridgePreviewCommandOf(lessonId, projectId, workspaceRoot) {
|
|
461
|
+
const id = trimmedText(lessonId);
|
|
462
|
+
if (id === "") return "";
|
|
463
|
+
const project = trimmedText(projectId);
|
|
464
|
+
const args = { mode: "preview", lessonIds: [id] };
|
|
465
|
+
if (project !== "") args.projectId = project;
|
|
466
|
+
const root = trimmedText(workspaceRoot);
|
|
467
|
+
if (root !== "") args.path = root.replace(/[\\/]+$/, "") + "/AGENTS.md";
|
|
468
|
+
return "dream_bridge " + JSON.stringify(args);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function bridgeRollbackCommandOf(row) {
|
|
472
|
+
if (!row || row.rollbackable !== true || trimmedText(row.target) === "") return "";
|
|
473
|
+
const backupId = trimmedText(row.id) || trimmedText(row.backupId);
|
|
474
|
+
if (backupId === "") return "";
|
|
475
|
+
return "dream_bridge " + JSON.stringify({ mode: "rollback", backupId: backupId, path: row.target });
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/** 哈希只展示前 12 位;空值给 ""。 */
|
|
479
|
+
function shortHash(value) {
|
|
480
|
+
const text = trimmedText(value);
|
|
481
|
+
if (text === "") return "";
|
|
482
|
+
return text.length > 12 ? text.slice(0, 12) + "…" : text;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function bridgeActionLabel(action) {
|
|
486
|
+
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) || "未知动作";
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/** 写入记录视图模型:只投影展示字段,不展示原始 JSON / 管理块正文。 */
|
|
497
|
+
function normalizeBridgeRecords(value, now, limit) {
|
|
498
|
+
const source = value !== null && typeof value === "object" ? value : {};
|
|
499
|
+
const cap = typeof limit === "number" && limit > 0 ? limit : BRIDGE_LIMIT;
|
|
500
|
+
const rawRecords = Array.isArray(source.records) ? source.records : [];
|
|
501
|
+
const statsSource = source.stats !== null && typeof source.stats === "object" ? source.stats : {};
|
|
502
|
+
const rows = [];
|
|
503
|
+
let rollbackableCount = 0;
|
|
504
|
+
for (const raw of rawRecords) {
|
|
505
|
+
if (raw === null || typeof raw !== "object") continue;
|
|
506
|
+
const id = trimmedText(raw.backupId);
|
|
507
|
+
const target = trimmedText(raw.target);
|
|
508
|
+
if (id === "" && target === "") continue;
|
|
509
|
+
const rollbackable = raw.rollbackable === true;
|
|
510
|
+
if (rollbackable) rollbackableCount += 1;
|
|
511
|
+
if (rows.length >= cap) continue;
|
|
512
|
+
const at = trimmedText(raw.at);
|
|
513
|
+
const lessonRefs = [];
|
|
514
|
+
if (Array.isArray(raw.lessons)) {
|
|
515
|
+
for (const item of raw.lessons) {
|
|
516
|
+
if (item !== null && typeof item === "object") {
|
|
517
|
+
const lessonId = trimmedText(item.lessonId);
|
|
518
|
+
if (lessonId === "") continue;
|
|
519
|
+
const revision = typeof item.revision === "number" && Number.isFinite(item.revision) && item.revision >= 0 ? Math.floor(item.revision) : 0;
|
|
520
|
+
lessonRefs.push(lessonId + "@" + String(revision));
|
|
521
|
+
} else if (typeof item === "string" && item.trim() !== "") {
|
|
522
|
+
lessonRefs.push(item.trim());
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
const rollbackReason = trimmedText(raw.rollbackReason);
|
|
527
|
+
rows.push({
|
|
528
|
+
id: id,
|
|
529
|
+
at: at,
|
|
530
|
+
atText: at === "" ? "时间未知" : relativeTime(at, now),
|
|
531
|
+
atTitle: at === "" ? "没有记录时间" : formatDate(at) + (formatTime(at) === "" ? "" : " " + formatTime(at)),
|
|
532
|
+
target: target || "目标未记录",
|
|
533
|
+
action: trimmedText(raw.action),
|
|
534
|
+
actionLabel: bridgeActionLabel(raw.action),
|
|
535
|
+
lessons: lessonRefs,
|
|
536
|
+
lessonsText: lessonRefs.length === 0 ? "没有经验引用" : lessonRefs.join("、"),
|
|
537
|
+
beforeShort: shortHash(raw.beforeSha256),
|
|
538
|
+
afterShort: shortHash(raw.afterSha256),
|
|
539
|
+
rollbackable: rollbackable,
|
|
540
|
+
rollbackText: rollbackable ? "可回滚" : ("不可回滚" + (rollbackReason !== "" ? ":" + rollbackReason : "")),
|
|
541
|
+
rollbackReason: rollbackReason,
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
const total = typeof statsSource.total === "number" && statsSource.total >= 0 ? statsSource.total : rawRecords.length;
|
|
545
|
+
const statsRollback = typeof statsSource.rollbackable === "number" && statsSource.rollbackable >= 0 ? statsSource.rollbackable : rollbackableCount;
|
|
546
|
+
return {
|
|
547
|
+
rows: rows,
|
|
548
|
+
total: total,
|
|
549
|
+
shown: rows.length,
|
|
550
|
+
rollbackable: statsRollback,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/** 「写入记录」的独立空态。 */
|
|
555
|
+
function bridgeEmptyState() {
|
|
556
|
+
return {
|
|
557
|
+
title: "还没有写入记录",
|
|
558
|
+
body: "写入记录来自 dream_bridge 的 preview → apply;面板只读展示目标、时间、经验 revision 与前后哈希,不会写文件。",
|
|
559
|
+
hint: "在可桥接经验行点「复制预览命令」,回到对话里执行;写操作只在工具中完成。",
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/** 「待核实」的独立空态。 */
|
|
564
|
+
function reviewQueueEmptyState() {
|
|
565
|
+
return "没有待核实的经验 —— 候选、有冲突、待复核的经验会出现在这里,并写明被扣下的原因。";
|
|
566
|
+
}
|
|
567
|
+
|
|
299
568
|
function spanOf(dreams) {
|
|
300
569
|
let min = null;
|
|
301
570
|
let max = null;
|
|
@@ -566,6 +835,7 @@ function Btn(props) {
|
|
|
566
835
|
onClick: props.onClick,
|
|
567
836
|
disabled: props.disabled === true,
|
|
568
837
|
title: props.title,
|
|
838
|
+
"aria-expanded": props["aria-expanded"],
|
|
569
839
|
"aria-pressed": props.pressed === undefined ? undefined : props.pressed,
|
|
570
840
|
}, props.children);
|
|
571
841
|
}
|
|
@@ -615,10 +885,69 @@ function LessonCard(props) {
|
|
|
615
885
|
h("span", { className: "dshd-lesson-meta", key: "m" }, [
|
|
616
886
|
h("span", { className: "dshd-times", key: "c" }, "×", h("b", { key: "n" }, String(row.count))),
|
|
617
887
|
row.lastText === "" ? null : h("span", { className: "dshd-last", key: "l", title: row.lastTitle }, row.lastText),
|
|
618
|
-
row.candidate ? h("span", { className: "dshd-badge", key: "badge" }, "
|
|
888
|
+
row.candidate ? h("span", { className: "dshd-badge", key: "badge" }, "常见教训") : null,
|
|
619
889
|
]),
|
|
620
890
|
]))),
|
|
621
|
-
h("div", { className: "dshd-note", key: "note" }, "
|
|
891
|
+
h("div", { className: "dshd-note", key: "note" }, "次数仅表示日记里的出现频率;出现 ≥3 次标为「常见教训」。写入项目规则前仍需核验来源并审阅经验。最近时间取自 stats.lastAt;统计缺失时按本页已加载的梦现算。"),
|
|
892
|
+
]);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/** 复制命令按钮:成功给「已复制」,剪贴板不可用时回退为可选中文本。 */
|
|
896
|
+
function CopyCommand(props) {
|
|
897
|
+
const command = typeof props.command === "string" ? props.command : "";
|
|
898
|
+
const [copied, setCopied] = useState(false);
|
|
899
|
+
const [failed, setFailed] = useState(false);
|
|
900
|
+
if (command === "") return null;
|
|
901
|
+
const onCopy = function () {
|
|
902
|
+
copyText(command).then(function (ok) {
|
|
903
|
+
setCopied(ok === true);
|
|
904
|
+
setFailed(ok !== true);
|
|
905
|
+
});
|
|
906
|
+
};
|
|
907
|
+
const label = props.label === undefined ? "复制命令" : props.label;
|
|
908
|
+
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),
|
|
910
|
+
failed === true ? h("code", { key: "cmd", className: "dshd-cmd", tabIndex: 0 }, command) : null,
|
|
911
|
+
failed === true ? h("span", { key: "hint", className: "dshd-note" }, "剪贴板不可用:请手动选中上面的命令复制。") : null,
|
|
912
|
+
]);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
/** 面板保持只读:先让人看清决策,再生成可直接粘贴进对话的完整命令。 */
|
|
916
|
+
function ReviewControls(props) {
|
|
917
|
+
const row = props.row;
|
|
918
|
+
const [open, setOpen] = useState(false);
|
|
919
|
+
const [action, setAction] = useState(function () { return reviewActionFor(row.state); });
|
|
920
|
+
const [resolution, setResolution] = useState("prefer");
|
|
921
|
+
const [note, setNote] = useState("");
|
|
922
|
+
const [affected, setAffected] = useState(function () { return row.conflictIds[0] || ""; });
|
|
923
|
+
const commands = row.state === "rejected" ? [["reopen", "重新送审"]]
|
|
924
|
+
: row.state === "disputed" ? [["resolve-conflict", "处理冲突"], ["reject", "驳回这条"]]
|
|
925
|
+
: [["accept", "采纳"], ["reject", "驳回"], ["mark-stale", "标记待复核"]];
|
|
926
|
+
const command = reviewCommandOf(row, { action: action, resolution: resolution, note: note, affectedIds: affected === "" ? [] : [affected] });
|
|
927
|
+
return h("div", { className: "dshd-review", onClick: function (event) { event.stopPropagation(); } }, [
|
|
928
|
+
h("div", { className: "dshd-kcmd", key: "buttons" }, [
|
|
929
|
+
h(Btn, { key: "review", onClick: function () { setOpen(!open); }, "aria-expanded": open }, open ? "收起审阅" : "审阅"),
|
|
930
|
+
row.bridgeable === true ? h(CopyCommand, { key: "preview", command: bridgePreviewCommandOf(row.id, row.projectId, row.workspaceRoot), label: "复制预览命令", title: "粘贴到对应项目对话,先查看写入差异" }) : null,
|
|
931
|
+
]),
|
|
932
|
+
open ? h("div", { className: "dshd-reviewform", key: "form", onKeyDown: function (event) {
|
|
933
|
+
if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); setOpen(false); }
|
|
934
|
+
} }, [
|
|
935
|
+
h("label", { key: "action" }, ["审阅动作", h("select", { "aria-label": "审阅动作", value: action, onChange: function (event) { setAction(event.target.value); } }, commands.map(function (item) { return h("option", { value: item[0], key: item[0] }, item[1]); }))]),
|
|
936
|
+
action === "resolve-conflict" ? h("div", { key: "conflict" }, [
|
|
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" }, "合并到这条,对方留作历史"),
|
|
942
|
+
])]),
|
|
943
|
+
h("label", { key: "note" }, ["处理依据", h("textarea", { "aria-label": "处理依据", rows: 2, value: note, placeholder: "说明哪个来源支持这次决定…", onChange: function (event) { setNote(event.target.value); } })]),
|
|
944
|
+
]) : null,
|
|
945
|
+
command === "" ? h("div", { className: "dshd-note", key: "incomplete" }, row.revision < 1 ? "记录缺少版本,请刷新后重试。" : "选择冲突经验并填写处理依据后,才能生成命令。") : h("div", { key: "command" }, [
|
|
946
|
+
h("code", { className: "dshd-cmd", tabIndex: 0, key: "text" }, command),
|
|
947
|
+
h(CopyCommand, { key: "copy", command: command, label: "复制审阅命令" }),
|
|
948
|
+
]),
|
|
949
|
+
h("div", { className: "dshd-note", key: "help" }, "复制后粘贴到 DSH 对话,请助手执行;完成后刷新。命令带版本校验,记录变化时会拒绝旧命令。"),
|
|
950
|
+
]) : null,
|
|
622
951
|
]);
|
|
623
952
|
}
|
|
624
953
|
|
|
@@ -637,21 +966,34 @@ function KnowledgeRow(props) {
|
|
|
637
966
|
h("span", { className: "dshd-ktitle", key: "t", title: row.title }, row.title),
|
|
638
967
|
]),
|
|
639
968
|
h("div", { className: "dshd-kmeta", key: "m" }, [
|
|
640
|
-
h("span", { className: "dshd-kscope", key: "s", title: "适用范围" }, row.scopeLabel),
|
|
641
969
|
h("span", { className: "dshd-klast", key: "l", title: row.lastTitle }, "最近核验:" + row.lastText),
|
|
642
970
|
]),
|
|
643
971
|
h("div", { className: "dshd-kbody", key: "body" }, [
|
|
644
972
|
h("div", { key: "when" }, "何时用:" + (row.when !== "" ? row.when : "未标注适用条件")),
|
|
645
|
-
h("div", { key: "
|
|
973
|
+
h("div", { key: "action", className: "dshd-action" }, "怎么做:" + (row.action || "未标注具体动作")),
|
|
974
|
+
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),
|
|
980
|
+
]),
|
|
981
|
+
props.showHoldReason === true && row.holdReasonLabel !== "" ? h("div", { key: "hold", className: "dshd-holdreason" }, "为什么被扣下:" + row.holdReasonLabel) : null,
|
|
646
982
|
]),
|
|
983
|
+
h(ReviewControls, { row: row, key: row.id + "@" + row.revision }),
|
|
647
984
|
]);
|
|
648
985
|
}
|
|
649
986
|
|
|
650
987
|
function KnowledgeCard(props) {
|
|
651
988
|
const vm = props.vm;
|
|
989
|
+
const [stateFilter, setStateFilter] = useState("usable");
|
|
990
|
+
const [pageSize, setPageSize] = useState(5);
|
|
991
|
+
const reviewed = vm.rows.filter(function (row) { return row.state === stateFilter; });
|
|
992
|
+
const notice = knowledgeNoticeText(vm === null || vm === undefined ? null : vm.stats);
|
|
993
|
+
const noticeNode = notice === "" ? null : h("div", { className: "dshd-knotice", key: "notice", role: "status" }, notice);
|
|
652
994
|
const head = function (extra) {
|
|
653
995
|
return h("div", { className: "dshd-khead", key: "head" }, [
|
|
654
|
-
h("h4", { key: "t" }, "
|
|
996
|
+
h("h4", { key: "t" }, "已审阅经验"),
|
|
655
997
|
h("span", { className: "dshd-spacer", key: "s" }),
|
|
656
998
|
extra,
|
|
657
999
|
]);
|
|
@@ -667,6 +1009,7 @@ function KnowledgeCard(props) {
|
|
|
667
1009
|
const empty = knowledgeEmptyState();
|
|
668
1010
|
return h("section", { className: "dshd-card dshd-kcard" }, [
|
|
669
1011
|
h("h4", { key: "t" }, "经验"),
|
|
1012
|
+
noticeNode,
|
|
670
1013
|
h("div", { className: "dshd-kempty", key: "e" }, [
|
|
671
1014
|
h("div", { className: "dshd-kempty-title", key: "t2" }, empty.title),
|
|
672
1015
|
h("p", { className: "dshd-kempty-body", key: "b" }, empty.body),
|
|
@@ -678,13 +1021,120 @@ function KnowledgeCard(props) {
|
|
|
678
1021
|
head(props.loading === true
|
|
679
1022
|
? h("span", { className: "dshd-hint", key: "loading" }, "加载中…")
|
|
680
1023
|
: h("span", { className: "dshd-hint", key: "count" }, knowledgeCountText(vm.stats))),
|
|
681
|
-
|
|
1024
|
+
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" }, "已驳回历史"),
|
|
1027
|
+
])]),
|
|
1028
|
+
reviewed.length === 0 ? h("div", { className: "dshd-hint", key: "none" }, stateFilter === "usable" ? "暂无可用经验;待核实的记录在下方。" : "没有已驳回记录。") : null,
|
|
1029
|
+
h("div", { className: "dshd-krows", key: "rows" }, reviewed.slice(0, pageSize).map(function (row, index) {
|
|
682
1030
|
return h(KnowledgeRow, { key: row.id !== "" ? row.id : "k" + String(index), row: row, privacy: props.privacy === true });
|
|
683
1031
|
})),
|
|
1032
|
+
reviewed.length > pageSize ? h(Btn, { key: "next", onClick: function () { setPageSize(pageSize + 5); } }, "显示更多经验") : null,
|
|
684
1033
|
vm.shown < vm.total
|
|
685
1034
|
? h("div", { className: "dshd-note", key: "more" }, "只显示最近 " + String(vm.shown) + " / " + String(vm.total) + " 条经验。")
|
|
686
1035
|
: null,
|
|
687
|
-
h("div", { className: "dshd-note", key: "note" }, "
|
|
1036
|
+
h("div", { className: "dshd-note", key: "note" }, "采纳记录仍需满足当前任务的平台、版本与项目条件;来源已读不代表结论永远适用。"),
|
|
1037
|
+
]);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
/** 「待核实」只读区块:candidate / disputed / stale + 被扣下的原因(复用 KnowledgeRow)。 */
|
|
1041
|
+
function ReviewQueueCard(props) {
|
|
1042
|
+
const vm = props.vm;
|
|
1043
|
+
const [pageSize, setPageSize] = useState(5);
|
|
1044
|
+
const rows = vm !== null && vm !== undefined && Array.isArray(vm.rows)
|
|
1045
|
+
? vm.rows.filter(function (row) { return HELD_STATES.indexOf(row.state) !== -1; })
|
|
1046
|
+
: [];
|
|
1047
|
+
const head = function (extra) {
|
|
1048
|
+
return h("div", { className: "dshd-khead", key: "head" }, [
|
|
1049
|
+
h("h4", { key: "t" }, "待核实"),
|
|
1050
|
+
h("span", { className: "dshd-spacer", key: "s" }),
|
|
1051
|
+
extra,
|
|
1052
|
+
]);
|
|
1053
|
+
};
|
|
1054
|
+
if (props.error !== null && props.error !== undefined) {
|
|
1055
|
+
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.message === "" ? String(props.error) : props.error.message)),
|
|
1058
|
+
]);
|
|
1059
|
+
}
|
|
1060
|
+
if (props.loading === true) {
|
|
1061
|
+
return h("section", { className: "dshd-card dshd-kcard" }, [
|
|
1062
|
+
head(h("span", { className: "dshd-hint", key: "l" }, "加载中…")),
|
|
1063
|
+
]);
|
|
1064
|
+
}
|
|
1065
|
+
return h("section", { className: "dshd-card dshd-kcard" }, [
|
|
1066
|
+
head(rows.length === 0 ? null : h("span", { className: "dshd-hint", key: "c" }, String(rows.length) + " 条待核实")),
|
|
1067
|
+
rows.length === 0
|
|
1068
|
+
? h("div", { className: "dshd-kempty", key: "empty" }, [
|
|
1069
|
+
h("div", { className: "dshd-kempty-title", key: "t" }, "没有待核实的经验"),
|
|
1070
|
+
h("p", { className: "dshd-kempty-body", key: "b" }, reviewQueueEmptyState()),
|
|
1071
|
+
])
|
|
1072
|
+
: 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 });
|
|
1074
|
+
})),
|
|
1075
|
+
rows.length > pageSize ? h(Btn, { key: "more", onClick: function () { setPageSize(pageSize + 5); } }, "显示更多待核实经验") : null,
|
|
1076
|
+
h("div", { className: "dshd-note", key: "note" }, "这些经验默认不会作为任务建议注入;扣留原因来自检索层的 skipped reason。复制审阅命令后到对话里执行,面板不会写文件。"),
|
|
1077
|
+
]);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
/** 「写入记录」只读区块:<journalDir>/bridge/ 的桥接应用记录。 */
|
|
1081
|
+
function BridgeLogCard(props) {
|
|
1082
|
+
const vm = props.vm;
|
|
1083
|
+
const head = function (extra) {
|
|
1084
|
+
return h("div", { className: "dshd-khead", key: "head" }, [
|
|
1085
|
+
h("h4", { key: "t" }, "写入记录"),
|
|
1086
|
+
h("span", { className: "dshd-spacer", key: "s" }),
|
|
1087
|
+
extra,
|
|
1088
|
+
]);
|
|
1089
|
+
};
|
|
1090
|
+
if (props.error !== null && props.error !== undefined) {
|
|
1091
|
+
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.message === "" ? String(props.error) : props.error.message)),
|
|
1094
|
+
]);
|
|
1095
|
+
}
|
|
1096
|
+
if (props.loading === true) {
|
|
1097
|
+
return h("section", { className: "dshd-card dshd-kcard" }, [
|
|
1098
|
+
head(h("span", { className: "dshd-hint", key: "l" }, "加载中…")),
|
|
1099
|
+
]);
|
|
1100
|
+
}
|
|
1101
|
+
if (vm.rows.length === 0) {
|
|
1102
|
+
const empty = bridgeEmptyState();
|
|
1103
|
+
return h("section", { className: "dshd-card dshd-kcard" }, [
|
|
1104
|
+
h("h4", { key: "t" }, "写入记录"),
|
|
1105
|
+
h("div", { className: "dshd-kempty", key: "empty" }, [
|
|
1106
|
+
h("div", { className: "dshd-kempty-title", key: "t2" }, empty.title),
|
|
1107
|
+
h("p", { className: "dshd-kempty-body", key: "b" }, empty.body),
|
|
1108
|
+
h("p", { className: "dshd-note", key: "h" }, empty.hint),
|
|
1109
|
+
]),
|
|
1110
|
+
]);
|
|
1111
|
+
}
|
|
1112
|
+
return h("section", { className: "dshd-card dshd-kcard" }, [
|
|
1113
|
+
head(h("span", { className: "dshd-hint", key: "c" }, String(vm.total) + " 次写入 · " + String(vm.rollbackable) + " 次可回滚")),
|
|
1114
|
+
h("div", { className: "dshd-krows", key: "rows" }, vm.rows.map(function (row, index) {
|
|
1115
|
+
return h("div", { className: "dshd-bridge", key: row.id !== "" ? row.id : "bridge-" + String(index) }, [
|
|
1116
|
+
h("div", { className: "dshd-bridge-head", key: "h" }, [
|
|
1117
|
+
h("span", { className: "dshd-kbadge", key: "a" }, row.actionLabel),
|
|
1118
|
+
h("span", { className: "dshd-bridge-target", key: "t", title: row.target }, row.target),
|
|
1119
|
+
h("span", { className: "dshd-spacer", key: "s" }),
|
|
1120
|
+
h("span", { className: "dshd-klast", key: "at", title: row.atTitle }, row.atText),
|
|
1121
|
+
]),
|
|
1122
|
+
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),
|
|
1126
|
+
h("span", { key: "rb", className: row.rollbackable === true ? "dshd-rollback-ok" : "dshd-rollback-no" }, row.rollbackText),
|
|
1127
|
+
]),
|
|
1128
|
+
h("details", { key: "backup" }, [
|
|
1129
|
+
h("summary", { key: "summary" }, "备份标识与回滚命令"),
|
|
1130
|
+
h("code", { key: "id", className: "dshd-cmd" }, row.id),
|
|
1131
|
+
row.rollbackable === true ? h("code", { key: "command", className: "dshd-cmd", tabIndex: 0 }, bridgeRollbackCommandOf(row)) : null,
|
|
1132
|
+
]),
|
|
1133
|
+
row.rollbackable === true ? h(CopyCommand, { key: "rollback", label: "复制回滚命令", command: bridgeRollbackCommandOf(row), title: "粘贴到对应项目对话执行;只恢复管理块,保留块外人工内容" }) : null,
|
|
1134
|
+
]);
|
|
1135
|
+
})),
|
|
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" }, "只读展示:记录来自 dream_bridge 的 apply;回滚请按 dream_bridge 返回的备份标识在对话里执行,面板不会写文件。"),
|
|
688
1138
|
]);
|
|
689
1139
|
}
|
|
690
1140
|
|
|
@@ -754,6 +1204,7 @@ function Skeleton() {
|
|
|
754
1204
|
// ───────────────────────── 主面板 ─────────────────────────
|
|
755
1205
|
|
|
756
1206
|
function DreamPanel() {
|
|
1207
|
+
const [activeTab, setActiveTab] = useState("journal");
|
|
757
1208
|
const [dreams, setDreams] = useState([]);
|
|
758
1209
|
const [stats, setStats] = useState(null);
|
|
759
1210
|
const [loading, setLoading] = useState(true);
|
|
@@ -769,6 +1220,9 @@ function DreamPanel() {
|
|
|
769
1220
|
const [knowledge, setKnowledge] = useState(null);
|
|
770
1221
|
const [knowledgeLoading, setKnowledgeLoading] = useState(true);
|
|
771
1222
|
const [knowledgeError, setKnowledgeError] = useState(null);
|
|
1223
|
+
const [bridge, setBridge] = useState(null);
|
|
1224
|
+
const [bridgeLoading, setBridgeLoading] = useState(true);
|
|
1225
|
+
const [bridgeError, setBridgeError] = useState(null);
|
|
772
1226
|
|
|
773
1227
|
// 搜索防抖:连续打字只发一次请求;回车立即生效。
|
|
774
1228
|
useEffect(function () {
|
|
@@ -813,8 +1267,27 @@ function DreamPanel() {
|
|
|
813
1267
|
return function () { alive = false; };
|
|
814
1268
|
}, [reloadToken]);
|
|
815
1269
|
|
|
1270
|
+
// 写入记录独立取数:桥接目录不存在时返回空,不影响其余区块。
|
|
1271
|
+
useEffect(function () {
|
|
1272
|
+
let alive = true;
|
|
1273
|
+
setBridgeLoading(true);
|
|
1274
|
+
setBridgeError(null);
|
|
1275
|
+
fetchBridgeRecords().then(function (payload) {
|
|
1276
|
+
if (alive !== true) return;
|
|
1277
|
+
setBridge(normalizeBridgeRecords(payload, new Date()));
|
|
1278
|
+
setBridgeLoading(false);
|
|
1279
|
+
}).catch(function (failure) {
|
|
1280
|
+
if (alive !== true) return;
|
|
1281
|
+
setBridgeError(failure instanceof Error ? failure : new Error(String(failure)));
|
|
1282
|
+
setBridgeLoading(false);
|
|
1283
|
+
setBridge(null);
|
|
1284
|
+
});
|
|
1285
|
+
return function () { alive = false; };
|
|
1286
|
+
}, [reloadToken]);
|
|
1287
|
+
|
|
816
1288
|
const vm = viewModelOf(dreams, stats, now);
|
|
817
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;
|
|
818
1291
|
const pristine = vm.count === 0 && query === "";
|
|
819
1292
|
const filterCount = filterCountOf(filters);
|
|
820
1293
|
const visibleDreams = applyFilters(dreams, filters);
|
|
@@ -849,6 +1322,22 @@ function DreamPanel() {
|
|
|
849
1322
|
privacy: privacy === true,
|
|
850
1323
|
onRetry: showKnowledge,
|
|
851
1324
|
});
|
|
1325
|
+
const reviewQueueCard = h(ReviewQueueCard, {
|
|
1326
|
+
key: "review-queue",
|
|
1327
|
+
vm: knowledgeVm,
|
|
1328
|
+
loading: knowledgeLoading === true,
|
|
1329
|
+
error: knowledgeError,
|
|
1330
|
+
privacy: privacy === true,
|
|
1331
|
+
onRetry: showKnowledge,
|
|
1332
|
+
});
|
|
1333
|
+
const showBridge = function () { if (bridgeLoading !== true) setReloadToken(function (value) { return value + 1; }); };
|
|
1334
|
+
const bridgeLogCard = h(BridgeLogCard, {
|
|
1335
|
+
key: "bridge-log",
|
|
1336
|
+
vm: bridgeVm,
|
|
1337
|
+
loading: bridgeLoading === true,
|
|
1338
|
+
error: bridgeError,
|
|
1339
|
+
onRetry: showBridge,
|
|
1340
|
+
});
|
|
852
1341
|
|
|
853
1342
|
// 筛选 popover:点面板外任意处收起;纯 node 冒烟没有事件系统就跳过。
|
|
854
1343
|
useEffect(function () {
|
|
@@ -859,8 +1348,18 @@ function DreamPanel() {
|
|
|
859
1348
|
if (pop !== null && typeof pop.contains === "function" && pop.contains(event.target) === true) return;
|
|
860
1349
|
setFilterOpen(false);
|
|
861
1350
|
};
|
|
1351
|
+
const onKey = function (event) {
|
|
1352
|
+
if (event.key !== "Escape") return;
|
|
1353
|
+
event.preventDefault();
|
|
1354
|
+
event.stopPropagation();
|
|
1355
|
+
event.stopImmediatePropagation();
|
|
1356
|
+
setFilterOpen(false);
|
|
1357
|
+
const trigger = document.querySelector(".dshd-filter button");
|
|
1358
|
+
if (trigger && typeof trigger.focus === "function") trigger.focus();
|
|
1359
|
+
};
|
|
862
1360
|
document.addEventListener("mousedown", onDown);
|
|
863
|
-
|
|
1361
|
+
document.addEventListener("keydown", onKey, true);
|
|
1362
|
+
return function () { document.removeEventListener("mousedown", onDown); document.removeEventListener("keydown", onKey, true); };
|
|
864
1363
|
}, [filterOpen]);
|
|
865
1364
|
|
|
866
1365
|
const searchIcon = h("svg", { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.8, strokeLinecap: "round", key: "i" }, [
|
|
@@ -869,7 +1368,7 @@ function DreamPanel() {
|
|
|
869
1368
|
]);
|
|
870
1369
|
|
|
871
1370
|
const toolbar = h("div", { className: "dshd-toolbar", key: "toolbar" }, [
|
|
872
|
-
pristine ? null : h("div", { className: "dshd-search", key: "search" }, [
|
|
1371
|
+
pristine || activeTab !== "journal" ? null : h("div", { className: "dshd-search", key: "search" }, [
|
|
873
1372
|
searchIcon,
|
|
874
1373
|
h("input", {
|
|
875
1374
|
key: "search-input",
|
|
@@ -890,10 +1389,10 @@ function DreamPanel() {
|
|
|
890
1389
|
}, "×"),
|
|
891
1390
|
]),
|
|
892
1391
|
h("span", { className: "dshd-spacer", key: "spacer" }),
|
|
893
|
-
pristine ? null : h("span", { className: "dshd-hint", key: "count" }, loading === true
|
|
1392
|
+
pristine || activeTab !== "journal" ? null : h("span", { className: "dshd-hint", key: "count" }, loading === true
|
|
894
1393
|
? "加载中…"
|
|
895
1394
|
: countTextOf(query, vm.total, vm.count, filterCount, visibleDreams.length)),
|
|
896
|
-
showFilterButton === true ? h("div", { className: "dshd-filter", key: "filter" }, [
|
|
1395
|
+
showFilterButton === true && activeTab === "journal" ? h("div", { className: "dshd-filter", key: "filter" }, [
|
|
897
1396
|
h(Btn, {
|
|
898
1397
|
key: "btn",
|
|
899
1398
|
on: filterCount > 0,
|
|
@@ -942,16 +1441,17 @@ function DreamPanel() {
|
|
|
942
1441
|
]);
|
|
943
1442
|
|
|
944
1443
|
const body = function () {
|
|
945
|
-
if (
|
|
1444
|
+
if (activeTab === "knowledge") return h("div", { key: "knowledge-tab" }, [knowledgeCard, reviewQueueCard]);
|
|
1445
|
+
if (activeTab === "bridge") return bridgeLogCard;
|
|
1446
|
+
if (loading === true && vm.count === 0) return h(Skeleton, { key: "skeleton" });
|
|
946
1447
|
if (vm.count === 0) {
|
|
947
1448
|
if (error !== null) return h("div", { key: "failed-wrap" }, [
|
|
948
1449
|
h("div", { className: "dshd-empty", key: "failed" }, [
|
|
949
1450
|
h("h3", { key: "t" }, "暂时读不到梦境日记"),
|
|
950
1451
|
h("p", { key: "p" }, "等宿主路由就绪后点上面的「重试」;如果一直失败,看一眼 DSH 日志里有没有 [dsh-dream] 告警。"),
|
|
951
1452
|
]),
|
|
952
|
-
knowledgeCard,
|
|
953
1453
|
]);
|
|
954
|
-
return h(
|
|
1454
|
+
return h(EmptyState, { key: "empty", query: query, onClear: query === "" ? null : clearSearch });
|
|
955
1455
|
}
|
|
956
1456
|
return h("div", { key: "content" }, [
|
|
957
1457
|
vm.showTiles
|
|
@@ -966,7 +1466,6 @@ function DreamPanel() {
|
|
|
966
1466
|
h(MoodCard, { key: "mood", vm: vm }),
|
|
967
1467
|
h(LessonCard, { key: "lessons", vm: vm }),
|
|
968
1468
|
]),
|
|
969
|
-
knowledgeCard,
|
|
970
1469
|
h("div", { className: "dshd-listhead", key: "listhead" }, [
|
|
971
1470
|
h("h4", { key: "t" }, query === "" && filterCount === 0 ? "梦境时间线" : (query === "" ? "筛选结果" : "搜索结果")),
|
|
972
1471
|
h("span", { className: "dshd-hint", key: "n" }, listHintOf(query, vm.count, filterCount, visibleDreams.length)),
|
|
@@ -986,10 +1485,13 @@ function DreamPanel() {
|
|
|
986
1485
|
return h("div", { className: "dshd-root" + (privacy === true ? " dshd-privacy" : ""), key: "root" }, [
|
|
987
1486
|
h("div", { className: "dshd-shell", key: "shell" }, [
|
|
988
1487
|
errorBox,
|
|
1488
|
+
h("nav", { className: "dshd-tabs", key: "tabs", "aria-label": "梦境面板视图" }, [["journal", "日记"], ["knowledge", "经验"], ["bridge", "写入记录"]].map(function (item) {
|
|
1489
|
+
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
|
+
})),
|
|
989
1491
|
toolbar,
|
|
990
1492
|
privacy === true ? h("div", { className: "dshd-note", key: "privacynote", role: "status" }, "隐私模式:显示层模糊;DOM 仍为明文,不改文件、不加密。点击卡片可临时查看。") : null,
|
|
991
1493
|
body(),
|
|
992
|
-
h("div", { className: "dshd-footer", key: "footer" }, [
|
|
1494
|
+
activeTab !== "journal" ? null : h("div", { className: "dshd-footer", key: "footer" }, [
|
|
993
1495
|
h("span", { key: "l" }, footerTextOf(query, limit, vm.total, vm.count)),
|
|
994
1496
|
h("span", { className: "dshd-spacer", key: "sp" }),
|
|
995
1497
|
canLoadMore === true ? h(Btn, { key: "more", onClick: showMore, disabled: loading === true }, loading === true ? "加载中…" : "显示更早的梦") : null,
|
|
@@ -1012,7 +1514,6 @@ const CSS = [
|
|
|
1012
1514
|
".dshd-root{color:var(--dsw-alias-label-primary,#111);font-size:13px;line-height:1.6}",
|
|
1013
1515
|
".dshd-shell{background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.08));border-radius:14px;padding:14px 16px;container-type:inline-size}",
|
|
1014
1516
|
".dshd-sub{color:var(--dsw-alias-label-tertiary,#6b7280);font-size:12px;margin:4px 0 12px}",
|
|
1015
|
-
"@container (max-width:620px){.dshd-two{grid-template-columns:1fr}.dshd-toolbar .dshd-hint{display:none}}",
|
|
1016
1517
|
".dshd-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:12px}",
|
|
1017
1518
|
".dshd-search{position:relative;display:flex;align-items:center;flex:1;min-width:180px}",
|
|
1018
1519
|
".dshd-search svg{position:absolute;left:9px;color:var(--dsw-alias-label-tertiary,#6b7280);pointer-events:none}",
|
|
@@ -1118,8 +1619,26 @@ const CSS = [
|
|
|
1118
1619
|
".dshd-kempty{padding:14px 10px;text-align:center;color:var(--dsw-alias-label-tertiary,#6b7280)}",
|
|
1119
1620
|
".dshd-kempty-title{color:var(--dsw-alias-label-primary,#111);font-weight:600;font-size:13px;margin-bottom:4px}",
|
|
1120
1621
|
".dshd-kempty-body{margin:0 auto 5px;max-width:460px;font-size:12px}",
|
|
1622
|
+
".dshd-knotice{font-size:11px;color:var(--dsw-alias-label-primary,#111);background:color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 14%,transparent);border-radius:8px;padding:5px 8px;margin-bottom:7px}",
|
|
1121
1623
|
".dshd-privacy .dshd-kbody{filter:blur(4px)}",
|
|
1122
|
-
".dshd-privacy .dshd-knowledge.dshd-peek .dshd-kbody{filter:none}"
|
|
1624
|
+
".dshd-privacy .dshd-knowledge.dshd-peek .dshd-kbody{filter:none}",
|
|
1625
|
+
".dshd-kcmd{display:flex;gap:6px;flex-wrap:wrap;margin-top:6px}",
|
|
1626
|
+
".dshd-tabs{display:flex;gap:4px;margin-bottom:8px;flex-wrap:wrap}",
|
|
1627
|
+
".dshd-action{font-weight:500;white-space:pre-wrap}",
|
|
1628
|
+
".dshd-kbody details summary,.dshd-bridge-row details summary{cursor:pointer;color:var(--dsw-alias-label-tertiary,#6b7280);font-size:11px}",
|
|
1629
|
+
".dshd-reviewform{margin-top:8px;padding:9px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));border-radius:8px}",
|
|
1630
|
+
".dshd-reviewform label{display:flex;flex-direction:column;gap:3px;margin-bottom:6px;font-size:12px}",
|
|
1631
|
+
".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
|
+
".dshd-copywrap{display:inline-flex;flex-direction:column;gap:3px;max-width:100%;align-items:flex-start}",
|
|
1633
|
+
".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%}",
|
|
1634
|
+
".dshd-holdreason{font-size:11px;color:var(--dsw-alias-state-warn-primary,#f59e0b);margin-top:4px}",
|
|
1635
|
+
".dshd-bridge{background:var(--dsw-alias-bg-layer-1,#fff);border-radius:9px;padding:8px 10px;border-left:3px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));min-width:0}",
|
|
1636
|
+
".dshd-bridge-head{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}",
|
|
1637
|
+
".dshd-bridge-target{font-size:12px;font-weight:600;word-break:break-all;min-width:0}",
|
|
1638
|
+
".dshd-bridge-meta{display:flex;gap:10px;flex-wrap:wrap;font-size:11px;color:var(--dsw-alias-label-tertiary,#6b7280);margin-top:3px}",
|
|
1639
|
+
".dshd-rollback-ok{color:var(--dsw-alias-state-success-primary,#16a34a)}",
|
|
1640
|
+
".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}}"
|
|
1123
1642
|
].join("");
|
|
1124
1643
|
/* @ui-css-end */
|
|
1125
1644
|
|
|
@@ -1163,8 +1682,26 @@ exports.__internals = {
|
|
|
1163
1682
|
KNOWLEDGE_ROUTE: KNOWLEDGE_ROUTE,
|
|
1164
1683
|
KNOWLEDGE_LIMIT: KNOWLEDGE_LIMIT,
|
|
1165
1684
|
fetchKnowledge: fetchKnowledge,
|
|
1685
|
+
fetchBridgeRecords: fetchBridgeRecords,
|
|
1686
|
+
copyText: copyText,
|
|
1687
|
+
normalizeBridgeRecords: normalizeBridgeRecords,
|
|
1688
|
+
skippedReasonLabel: skippedReasonLabel,
|
|
1689
|
+
holdReasonOf: holdReasonOf,
|
|
1690
|
+
bridgeableOf: bridgeableOf,
|
|
1691
|
+
reviewActionFor: reviewActionFor,
|
|
1692
|
+
reviewCommandOf: reviewCommandOf,
|
|
1693
|
+
bridgePreviewCommandOf: bridgePreviewCommandOf,
|
|
1694
|
+
bridgeRollbackCommandOf: bridgeRollbackCommandOf,
|
|
1695
|
+
shortHash: shortHash,
|
|
1696
|
+
bridgeActionLabel: bridgeActionLabel,
|
|
1697
|
+
bridgeEmptyState: bridgeEmptyState,
|
|
1698
|
+
reviewQueueEmptyState: reviewQueueEmptyState,
|
|
1699
|
+
BRIDGE_ROUTE: BRIDGE_ROUTE,
|
|
1700
|
+
BRIDGE_LIMIT: BRIDGE_LIMIT,
|
|
1166
1701
|
normalizeKnowledge: normalizeKnowledge,
|
|
1167
1702
|
normalizeLessonState: normalizeLessonState,
|
|
1703
|
+
applicabilityTextOf: applicabilityTextOf,
|
|
1704
|
+
knowledgeNoticeText: knowledgeNoticeText,
|
|
1168
1705
|
lessonStateLabel: lessonStateLabel,
|
|
1169
1706
|
lessonStateClass: lessonStateClass,
|
|
1170
1707
|
knowledgeCountText: knowledgeCountText,
|