@stardustlc/dsh-dream 0.5.1 → 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 +13 -0
- package/README.en.md +29 -12
- package/README.md +33 -16
- package/lib/bridge.d.ts +205 -2
- package/lib/bridge.js +1053 -6
- package/lib/client.js +515 -26
- package/lib/evidence.d.ts +15 -0
- package/lib/evidence.js +123 -0
- package/lib/knowledge-store.d.ts +21 -5
- package/lib/knowledge-store.js +271 -41
- package/lib/knowledge.d.ts +29 -0
- package/lib/knowledge.js +29 -0
- package/lib/mask.d.ts +3 -1
- package/lib/mask.js +8 -2
- package/lib/retrieval.d.ts +12 -0
- package/lib/retrieval.js +68 -3
- package/lib/tools.js +376 -72
- package/lib/web.d.ts +48 -1
- package/lib/web.js +153 -13
- package/package.json +2 -2
- package/skills/dream-protocol/SKILL.md +9 -4
package/lib/client.js
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
// 仓库里没有 src/client.ts,pnpm run build 也不会重写它。改面板请直接改这里,
|
|
3
3
|
// 改完用 node --check lib/client.js 自检(CI 里也会跑这一步)。
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
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 挂载);前端不缓存业务数据,刷新即见。
|
|
9
11
|
// 经验区块只展示状态 / 条件 / 范围 / 适用与例外 / 证据摘要 / 最近核验;日志截断或坏行会明确提示,
|
|
10
|
-
// 不展示权重、向量或原始 JSON
|
|
12
|
+
// 不展示权重、向量或原始 JSON。经验行只提供「复制审阅 / 预览命令」,写操作仍在对话工具里执行。
|
|
11
13
|
// 隐私纪律:面板只读,不写文件、不上传;「隐私模式」只做显示层模糊,DOM 仍是明文。
|
|
12
14
|
window.__ModuleLoader__.load({ id: "@stardustlc/dsh-dream", factory: (require) => {
|
|
13
15
|
var module = { exports: {} }; var exports = module.exports;
|
|
@@ -20,8 +22,11 @@ const h = React.createElement;
|
|
|
20
22
|
// 相对路径按 document.baseURI 解析:反代挂在 /tools/dsh/ 这类前缀下也不会打偏。
|
|
21
23
|
const ROUTE = "_dsh/dsh-dream/journal";
|
|
22
24
|
const KNOWLEDGE_ROUTE = "_dsh/dsh-dream/knowledge";
|
|
25
|
+
const BRIDGE_ROUTE = "_dsh/dsh-dream/bridge";
|
|
23
26
|
const KNOWLEDGE_LIMIT = 50;
|
|
27
|
+
const BRIDGE_LIMIT = 50;
|
|
24
28
|
const KNOWLEDGE_STATES = ["candidate", "usable", "disputed", "stale", "rejected"];
|
|
29
|
+
const HELD_STATES = ["candidate", "disputed", "stale"];
|
|
25
30
|
const DEFAULT_LIMIT = 50;
|
|
26
31
|
const MORE_LIMIT = 500;
|
|
27
32
|
const FETCH_TIMEOUT_MS = 15000;
|
|
@@ -121,6 +126,40 @@ async function fetchKnowledge() {
|
|
|
121
126
|
};
|
|
122
127
|
}
|
|
123
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
|
+
|
|
124
163
|
function normalizeDreams(value, limit) {
|
|
125
164
|
if (!Array.isArray(value) || limit <= 0) return [];
|
|
126
165
|
const out = [];
|
|
@@ -247,13 +286,20 @@ function applicabilityTextOf(value) {
|
|
|
247
286
|
|
|
248
287
|
/**
|
|
249
288
|
* 经验视图模型:只投影展示所需字段(title / when / applicability / exceptions /
|
|
250
|
-
* state / scopeLabel / evidenceSummary / lastValidatedAt
|
|
289
|
+
* state / scopeLabel / evidenceSummary / lastValidatedAt),并派生 revision / 扣留原因 /
|
|
290
|
+
* 可桥接标记与复制命令所需的稳定字段;不碰权重、向量、原始 JSON。
|
|
251
291
|
* limit 只截断渲染行数,byState 仍按全量统计;truncated / badLines 原样带给提示行。
|
|
252
292
|
*/
|
|
253
293
|
function normalizeKnowledge(value, now, limit) {
|
|
254
294
|
const source = value !== null && typeof value === "object" ? value : {};
|
|
255
295
|
const cap = typeof limit === "number" && limit > 0 ? limit : KNOWLEDGE_LIMIT;
|
|
256
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
|
+
}
|
|
257
303
|
const statsSource = source.stats !== null && typeof source.stats === "object" ? source.stats : {};
|
|
258
304
|
const byState = {};
|
|
259
305
|
const rows = [];
|
|
@@ -269,6 +315,7 @@ function normalizeKnowledge(value, now, limit) {
|
|
|
269
315
|
rows.push({
|
|
270
316
|
id: trimmedText(raw.id),
|
|
271
317
|
title: title !== "" ? title : action,
|
|
318
|
+
action: action,
|
|
272
319
|
when: trimmedText(raw.when),
|
|
273
320
|
applicability: applicabilityTextOf(raw.applicability),
|
|
274
321
|
exceptions: Array.isArray(raw.exceptions)
|
|
@@ -282,6 +329,15 @@ function normalizeKnowledge(value, now, limit) {
|
|
|
282
329
|
lastValidatedAt: lastValidatedAt,
|
|
283
330
|
lastText: lastValidatedAt === "" ? "尚未核验" : relativeTime(lastValidatedAt, now),
|
|
284
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) !== "",
|
|
285
341
|
candidate: state === "candidate",
|
|
286
342
|
disputed: state === "disputed",
|
|
287
343
|
});
|
|
@@ -335,6 +391,180 @@ function knowledgeEmptyState() {
|
|
|
335
391
|
};
|
|
336
392
|
}
|
|
337
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
|
+
|
|
338
568
|
function spanOf(dreams) {
|
|
339
569
|
let min = null;
|
|
340
570
|
let max = null;
|
|
@@ -605,6 +835,7 @@ function Btn(props) {
|
|
|
605
835
|
onClick: props.onClick,
|
|
606
836
|
disabled: props.disabled === true,
|
|
607
837
|
title: props.title,
|
|
838
|
+
"aria-expanded": props["aria-expanded"],
|
|
608
839
|
"aria-pressed": props.pressed === undefined ? undefined : props.pressed,
|
|
609
840
|
}, props.children);
|
|
610
841
|
}
|
|
@@ -654,10 +885,69 @@ function LessonCard(props) {
|
|
|
654
885
|
h("span", { className: "dshd-lesson-meta", key: "m" }, [
|
|
655
886
|
h("span", { className: "dshd-times", key: "c" }, "×", h("b", { key: "n" }, String(row.count))),
|
|
656
887
|
row.lastText === "" ? null : h("span", { className: "dshd-last", key: "l", title: row.lastTitle }, row.lastText),
|
|
657
|
-
row.candidate ? h("span", { className: "dshd-badge", key: "badge" }, "
|
|
888
|
+
row.candidate ? h("span", { className: "dshd-badge", key: "badge" }, "常见教训") : null,
|
|
658
889
|
]),
|
|
659
890
|
]))),
|
|
660
|
-
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,
|
|
661
951
|
]);
|
|
662
952
|
}
|
|
663
953
|
|
|
@@ -676,25 +966,34 @@ function KnowledgeRow(props) {
|
|
|
676
966
|
h("span", { className: "dshd-ktitle", key: "t", title: row.title }, row.title),
|
|
677
967
|
]),
|
|
678
968
|
h("div", { className: "dshd-kmeta", key: "m" }, [
|
|
679
|
-
h("span", { className: "dshd-kscope", key: "s", title: "适用范围" }, row.scopeLabel),
|
|
680
969
|
h("span", { className: "dshd-klast", key: "l", title: row.lastTitle }, "最近核验:" + row.lastText),
|
|
681
970
|
]),
|
|
682
971
|
h("div", { className: "dshd-kbody", key: "body" }, [
|
|
683
972
|
h("div", { key: "when" }, "何时用:" + (row.when !== "" ? row.when : "未标注适用条件")),
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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,
|
|
687
982
|
]),
|
|
983
|
+
h(ReviewControls, { row: row, key: row.id + "@" + row.revision }),
|
|
688
984
|
]);
|
|
689
985
|
}
|
|
690
986
|
|
|
691
987
|
function KnowledgeCard(props) {
|
|
692
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; });
|
|
693
992
|
const notice = knowledgeNoticeText(vm === null || vm === undefined ? null : vm.stats);
|
|
694
993
|
const noticeNode = notice === "" ? null : h("div", { className: "dshd-knotice", key: "notice", role: "status" }, notice);
|
|
695
994
|
const head = function (extra) {
|
|
696
995
|
return h("div", { className: "dshd-khead", key: "head" }, [
|
|
697
|
-
h("h4", { key: "t" }, "
|
|
996
|
+
h("h4", { key: "t" }, "已审阅经验"),
|
|
698
997
|
h("span", { className: "dshd-spacer", key: "s" }),
|
|
699
998
|
extra,
|
|
700
999
|
]);
|
|
@@ -723,13 +1022,119 @@ function KnowledgeCard(props) {
|
|
|
723
1022
|
? h("span", { className: "dshd-hint", key: "loading" }, "加载中…")
|
|
724
1023
|
: h("span", { className: "dshd-hint", key: "count" }, knowledgeCountText(vm.stats))),
|
|
725
1024
|
noticeNode,
|
|
726
|
-
h("
|
|
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) {
|
|
727
1030
|
return h(KnowledgeRow, { key: row.id !== "" ? row.id : "k" + String(index), row: row, privacy: props.privacy === true });
|
|
728
1031
|
})),
|
|
1032
|
+
reviewed.length > pageSize ? h(Btn, { key: "next", onClick: function () { setPageSize(pageSize + 5); } }, "显示更多经验") : null,
|
|
729
1033
|
vm.shown < vm.total
|
|
730
1034
|
? h("div", { className: "dshd-note", key: "more" }, "只显示最近 " + String(vm.shown) + " / " + String(vm.total) + " 条经验。")
|
|
731
1035
|
: null,
|
|
732
|
-
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 返回的备份标识在对话里执行,面板不会写文件。"),
|
|
733
1138
|
]);
|
|
734
1139
|
}
|
|
735
1140
|
|
|
@@ -799,6 +1204,7 @@ function Skeleton() {
|
|
|
799
1204
|
// ───────────────────────── 主面板 ─────────────────────────
|
|
800
1205
|
|
|
801
1206
|
function DreamPanel() {
|
|
1207
|
+
const [activeTab, setActiveTab] = useState("journal");
|
|
802
1208
|
const [dreams, setDreams] = useState([]);
|
|
803
1209
|
const [stats, setStats] = useState(null);
|
|
804
1210
|
const [loading, setLoading] = useState(true);
|
|
@@ -814,6 +1220,9 @@ function DreamPanel() {
|
|
|
814
1220
|
const [knowledge, setKnowledge] = useState(null);
|
|
815
1221
|
const [knowledgeLoading, setKnowledgeLoading] = useState(true);
|
|
816
1222
|
const [knowledgeError, setKnowledgeError] = useState(null);
|
|
1223
|
+
const [bridge, setBridge] = useState(null);
|
|
1224
|
+
const [bridgeLoading, setBridgeLoading] = useState(true);
|
|
1225
|
+
const [bridgeError, setBridgeError] = useState(null);
|
|
817
1226
|
|
|
818
1227
|
// 搜索防抖:连续打字只发一次请求;回车立即生效。
|
|
819
1228
|
useEffect(function () {
|
|
@@ -858,8 +1267,27 @@ function DreamPanel() {
|
|
|
858
1267
|
return function () { alive = false; };
|
|
859
1268
|
}, [reloadToken]);
|
|
860
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
|
+
|
|
861
1288
|
const vm = viewModelOf(dreams, stats, now);
|
|
862
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;
|
|
863
1291
|
const pristine = vm.count === 0 && query === "";
|
|
864
1292
|
const filterCount = filterCountOf(filters);
|
|
865
1293
|
const visibleDreams = applyFilters(dreams, filters);
|
|
@@ -894,6 +1322,22 @@ function DreamPanel() {
|
|
|
894
1322
|
privacy: privacy === true,
|
|
895
1323
|
onRetry: showKnowledge,
|
|
896
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
|
+
});
|
|
897
1341
|
|
|
898
1342
|
// 筛选 popover:点面板外任意处收起;纯 node 冒烟没有事件系统就跳过。
|
|
899
1343
|
useEffect(function () {
|
|
@@ -904,8 +1348,18 @@ function DreamPanel() {
|
|
|
904
1348
|
if (pop !== null && typeof pop.contains === "function" && pop.contains(event.target) === true) return;
|
|
905
1349
|
setFilterOpen(false);
|
|
906
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
|
+
};
|
|
907
1360
|
document.addEventListener("mousedown", onDown);
|
|
908
|
-
|
|
1361
|
+
document.addEventListener("keydown", onKey, true);
|
|
1362
|
+
return function () { document.removeEventListener("mousedown", onDown); document.removeEventListener("keydown", onKey, true); };
|
|
909
1363
|
}, [filterOpen]);
|
|
910
1364
|
|
|
911
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" }, [
|
|
@@ -914,7 +1368,7 @@ function DreamPanel() {
|
|
|
914
1368
|
]);
|
|
915
1369
|
|
|
916
1370
|
const toolbar = h("div", { className: "dshd-toolbar", key: "toolbar" }, [
|
|
917
|
-
pristine ? null : h("div", { className: "dshd-search", key: "search" }, [
|
|
1371
|
+
pristine || activeTab !== "journal" ? null : h("div", { className: "dshd-search", key: "search" }, [
|
|
918
1372
|
searchIcon,
|
|
919
1373
|
h("input", {
|
|
920
1374
|
key: "search-input",
|
|
@@ -935,10 +1389,10 @@ function DreamPanel() {
|
|
|
935
1389
|
}, "×"),
|
|
936
1390
|
]),
|
|
937
1391
|
h("span", { className: "dshd-spacer", key: "spacer" }),
|
|
938
|
-
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
|
|
939
1393
|
? "加载中…"
|
|
940
1394
|
: countTextOf(query, vm.total, vm.count, filterCount, visibleDreams.length)),
|
|
941
|
-
showFilterButton === true ? h("div", { className: "dshd-filter", key: "filter" }, [
|
|
1395
|
+
showFilterButton === true && activeTab === "journal" ? h("div", { className: "dshd-filter", key: "filter" }, [
|
|
942
1396
|
h(Btn, {
|
|
943
1397
|
key: "btn",
|
|
944
1398
|
on: filterCount > 0,
|
|
@@ -987,16 +1441,17 @@ function DreamPanel() {
|
|
|
987
1441
|
]);
|
|
988
1442
|
|
|
989
1443
|
const body = function () {
|
|
990
|
-
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" });
|
|
991
1447
|
if (vm.count === 0) {
|
|
992
1448
|
if (error !== null) return h("div", { key: "failed-wrap" }, [
|
|
993
1449
|
h("div", { className: "dshd-empty", key: "failed" }, [
|
|
994
1450
|
h("h3", { key: "t" }, "暂时读不到梦境日记"),
|
|
995
1451
|
h("p", { key: "p" }, "等宿主路由就绪后点上面的「重试」;如果一直失败,看一眼 DSH 日志里有没有 [dsh-dream] 告警。"),
|
|
996
1452
|
]),
|
|
997
|
-
knowledgeCard,
|
|
998
1453
|
]);
|
|
999
|
-
return h(
|
|
1454
|
+
return h(EmptyState, { key: "empty", query: query, onClear: query === "" ? null : clearSearch });
|
|
1000
1455
|
}
|
|
1001
1456
|
return h("div", { key: "content" }, [
|
|
1002
1457
|
vm.showTiles
|
|
@@ -1011,7 +1466,6 @@ function DreamPanel() {
|
|
|
1011
1466
|
h(MoodCard, { key: "mood", vm: vm }),
|
|
1012
1467
|
h(LessonCard, { key: "lessons", vm: vm }),
|
|
1013
1468
|
]),
|
|
1014
|
-
knowledgeCard,
|
|
1015
1469
|
h("div", { className: "dshd-listhead", key: "listhead" }, [
|
|
1016
1470
|
h("h4", { key: "t" }, query === "" && filterCount === 0 ? "梦境时间线" : (query === "" ? "筛选结果" : "搜索结果")),
|
|
1017
1471
|
h("span", { className: "dshd-hint", key: "n" }, listHintOf(query, vm.count, filterCount, visibleDreams.length)),
|
|
@@ -1031,10 +1485,13 @@ function DreamPanel() {
|
|
|
1031
1485
|
return h("div", { className: "dshd-root" + (privacy === true ? " dshd-privacy" : ""), key: "root" }, [
|
|
1032
1486
|
h("div", { className: "dshd-shell", key: "shell" }, [
|
|
1033
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
|
+
})),
|
|
1034
1491
|
toolbar,
|
|
1035
1492
|
privacy === true ? h("div", { className: "dshd-note", key: "privacynote", role: "status" }, "隐私模式:显示层模糊;DOM 仍为明文,不改文件、不加密。点击卡片可临时查看。") : null,
|
|
1036
1493
|
body(),
|
|
1037
|
-
h("div", { className: "dshd-footer", key: "footer" }, [
|
|
1494
|
+
activeTab !== "journal" ? null : h("div", { className: "dshd-footer", key: "footer" }, [
|
|
1038
1495
|
h("span", { key: "l" }, footerTextOf(query, limit, vm.total, vm.count)),
|
|
1039
1496
|
h("span", { className: "dshd-spacer", key: "sp" }),
|
|
1040
1497
|
canLoadMore === true ? h(Btn, { key: "more", onClick: showMore, disabled: loading === true }, loading === true ? "加载中…" : "显示更早的梦") : null,
|
|
@@ -1057,7 +1514,6 @@ const CSS = [
|
|
|
1057
1514
|
".dshd-root{color:var(--dsw-alias-label-primary,#111);font-size:13px;line-height:1.6}",
|
|
1058
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}",
|
|
1059
1516
|
".dshd-sub{color:var(--dsw-alias-label-tertiary,#6b7280);font-size:12px;margin:4px 0 12px}",
|
|
1060
|
-
"@container (max-width:620px){.dshd-two{grid-template-columns:1fr}.dshd-toolbar .dshd-hint{display:none}}",
|
|
1061
1517
|
".dshd-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:12px}",
|
|
1062
1518
|
".dshd-search{position:relative;display:flex;align-items:center;flex:1;min-width:180px}",
|
|
1063
1519
|
".dshd-search svg{position:absolute;left:9px;color:var(--dsw-alias-label-tertiary,#6b7280);pointer-events:none}",
|
|
@@ -1165,7 +1621,24 @@ const CSS = [
|
|
|
1165
1621
|
".dshd-kempty-body{margin:0 auto 5px;max-width:460px;font-size:12px}",
|
|
1166
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}",
|
|
1167
1623
|
".dshd-privacy .dshd-kbody{filter:blur(4px)}",
|
|
1168
|
-
".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}}"
|
|
1169
1642
|
].join("");
|
|
1170
1643
|
/* @ui-css-end */
|
|
1171
1644
|
|
|
@@ -1209,6 +1682,22 @@ exports.__internals = {
|
|
|
1209
1682
|
KNOWLEDGE_ROUTE: KNOWLEDGE_ROUTE,
|
|
1210
1683
|
KNOWLEDGE_LIMIT: KNOWLEDGE_LIMIT,
|
|
1211
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,
|
|
1212
1701
|
normalizeKnowledge: normalizeKnowledge,
|
|
1213
1702
|
normalizeLessonState: normalizeLessonState,
|
|
1214
1703
|
applicabilityTextOf: applicabilityTextOf,
|