dsh-recall-plugin 2.3.16 → 2.3.20
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 +36 -0
- package/lib/client.js +1694 -1619
- package/lib/routes-core.js +2 -1
- package/lib/snapshots.js +53 -2
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -1,1767 +1,1842 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
(() => {
|
|
3
|
+
// src/client/css.ts
|
|
4
|
+
var CSS = [
|
|
5
|
+
// 语义变量集中声明(V1-5):btn-danger 的 hover 亮度是令牌体系外补丁,
|
|
6
|
+
// 集中成单一来源避免散落硬编码;前景色用官方实证配对 bg-layer-3(error
|
|
7
|
+
// primary 底色上文本随主题翻转,见 plan-settings-ui V1 核验记录)。
|
|
8
|
+
":root{--dsh-recall-btn-danger-hover:brightness(1.08);--dsh-recall-tree-indent:24px;--dsh-recall-space-1:4px;--dsh-recall-space-2:8px;--dsh-recall-space-3:12px}",
|
|
9
|
+
".dsh-recall-row{flex-direction:column;align-items:flex-end;gap:6px;display:flex}",
|
|
10
|
+
".dsh-recall-stack{flex-direction:column;align-items:flex-end;gap:8px;min-width:0;max-width:min(525px,82%);display:flex}",
|
|
11
|
+
".dsh-recall-bubble{background:var(--dsw-specific-bubble);max-width:100%;color:var(--dsw-alias-label-primary);border-radius:22px;padding:10px 16px;font-size:16px;line-height:1.5;white-space:pre-wrap;word-break:break-word}",
|
|
12
|
+
".dsh-recall-json{margin:0;max-width:100%;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;word-break:break-word;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:8px 10px;background:var(--dsw-alias-markdown-code-block)}",
|
|
13
|
+
// 文件卡片(消息里的 file 块,非图片附件):复刻官方 UserStyleBubble 的附件卡
|
|
14
|
+
// ——品牌色扩展名徽标 + 文件名(单行省略)+ 「MD 6.7KB」元信息行;令牌沿用
|
|
15
|
+
// 卡片体系(layer-3 底 + 发丝描边),与图片附件的「附件在上」布局并列。
|
|
16
|
+
".dsh-recall-filecard{display:flex;align-items:center;gap:10px;max-width:100%;box-sizing:border-box;background:var(--dsw-alias-bg-layer-3);border:.5px solid var(--dsw-alias-border-l4);border-radius:12px;padding:8px 12px}",
|
|
17
|
+
".dsh-recall-filecard-icon{flex:none;width:34px;height:34px;border-radius:8px;background:var(--dsw-alias-brand-primary);color:var(--dsw-alias-label-primary-foreground);font-size:11px;font-weight:600;line-height:1;letter-spacing:.02em;display:flex;align-items:center;justify-content:center}",
|
|
18
|
+
".dsh-recall-filecard-body{display:flex;flex-direction:column;gap:2px;min-width:0}",
|
|
19
|
+
".dsh-recall-filecard-name{color:var(--dsw-alias-label-primary);font-size:14px;line-height:1.4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
|
|
20
|
+
".dsh-recall-filecard-meta{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.4}",
|
|
21
|
+
".dsh-recall-actions{align-items:center;gap:10px;height:28px;display:flex}",
|
|
22
|
+
".dsh-recall-time{color:var(--dsw-alias-label-tertiary);white-space:nowrap;padding-right:12px;font-size:14px;line-height:1.5}",
|
|
23
|
+
".dsh-recall-action{width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:6px;display:inline-flex}",
|
|
24
|
+
".dsh-recall-action:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}",
|
|
25
|
+
"@media (hover:hover){[data-time-hover-root] .dsh-recall-time{opacity:0;transition:opacity 80ms}[data-time-hover-root]:hover .dsh-recall-time,[data-time-hover-root]:focus-within .dsh-recall-time{opacity:1}}",
|
|
26
|
+
".dsh-recall-panel{width:min(480px,100%);box-sizing:border-box;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:8px;text-align:left;box-shadow:0 8px 28px rgba(0,0,0,.22)}",
|
|
27
|
+
".dsh-recall-panel-title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600;line-height:1.5}",
|
|
28
|
+
".dsh-recall-panel-note{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1.5;word-break:break-word}",
|
|
29
|
+
".dsh-recall-list{max-height:220px;overflow:auto;display:flex;flex-direction:column;gap:2px;padding:4px 0}",
|
|
30
|
+
".dsh-recall-file{display:flex;gap:8px;align-items:baseline;font-size:12px;line-height:1.5}",
|
|
31
|
+
".dsh-recall-badge{flex:none;font-size:12px;line-height:1.5;padding:0 6px;border-radius:6px}",
|
|
32
|
+
".dsh-recall-badge-modified{color:var(--dsw-alias-state-warn-label);background:var(--dsw-alias-state-warn-tertiary)}",
|
|
33
|
+
".dsh-recall-badge-restored{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
34
|
+
".dsh-recall-badge-added{color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
35
|
+
".dsh-recall-rel{min-width:0;color:var(--dsw-alias-label-primary);word-break:break-all;font-family:var(--dsw-font-code, ui-monospace, SFMono-Regular, Consolas, monospace)}",
|
|
36
|
+
// grid-column 对非 grid 祖先(exclude/快照卡片的 flex 布局)自动无效,无害;
|
|
37
|
+
// 在 cfg-grid 内则保证操作区/占满行不被 auto-placement 塞进第一列撑爆列宽。
|
|
38
|
+
".dsh-recall-panel-actions{display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end;margin-top:2px;grid-column:1/-1}",
|
|
39
|
+
// 按钮体系对齐官方设置卡(dsh-client-ui-settings-plugins PluginCard 底部按钮
|
|
40
|
+
// 组实测产物):次级 = 描边幽灵(discard 逐字配方——l2 描边 + 透明底,hover
|
|
41
|
+
// 升 label-dimmed 描边 + primary 字色);官方 discard/save 同高靠 save 也带
|
|
42
|
+
// 1px 描边,故变体统一 border-color:transparent 保同盒模型。hover 用
|
|
43
|
+
// :not(:disabled) 收口后,disabled 的 hover 抵消规则不再需要;disabled 透明度
|
|
44
|
+
// 随官方 .4。
|
|
45
|
+
".dsh-recall-btn{border:1px solid var(--dsw-alias-border-l2);background:0 0;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5;cursor:pointer;color:var(--dsw-alias-label-secondary)}",
|
|
46
|
+
".dsh-recall-btn:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}",
|
|
47
|
+
".dsh-recall-btn:disabled,.dsh-recall-ex-chip:disabled{opacity:.4;cursor:default}",
|
|
48
|
+
// 危险/主按钮的 hover 须显式重申文字与描边——基础 ghost hover 带伪类、优先级
|
|
49
|
+
// 更高,会把它们的字色刷回 label-primary 并描出亮边
|
|
50
|
+
".dsh-recall-btn-danger{background:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-bg-layer-3);border-color:transparent}",
|
|
51
|
+
".dsh-recall-btn-danger:hover:not(:disabled){color:var(--dsw-alias-bg-layer-3);border-color:transparent;filter:var(--dsh-recall-btn-danger-hover)}",
|
|
52
|
+
// 主按钮(保存)改用官方插件设置卡 save 的逐字配方:label-primary 反色实心 +
|
|
53
|
+
// bg-layer-3 前景(同列官方插件卡的保存即此形态,比 button-primary-fill 更贴
|
|
54
|
+
// 设置页语境);官方 save 无 hover 态,这里的 hover 规则只做「不被基础 ghost
|
|
55
|
+
// hover 污染」的抵消,视觉变化为零即是官方行为。只挂「保存」,主次分明。
|
|
56
|
+
".dsh-recall-btn-primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:transparent}",
|
|
57
|
+
".dsh-recall-btn-primary:hover:not(:disabled){color:var(--dsw-alias-bg-layer-3);border-color:transparent}",
|
|
58
|
+
// 焦点可见性对齐官方设置页实测写法(同上产物):按钮类 = brand-primary 2px
|
|
59
|
+
// outline(卡片头 offset -2px 内收,其余 +1px);输入类 = brand-primary 描边
|
|
60
|
+
// 变色、不套 ring。替换 V2-5 的 border-l3 环方案(当时核验的是旧版写法)。
|
|
61
|
+
".dsh-recall-btn:focus-visible,.dsh-recall-ex-chip:focus-visible,.dsh-recall-tree-toggle:focus-visible,.dsh-recall-cfg-switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}",
|
|
62
|
+
".dsh-recall-cardbtn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}",
|
|
63
|
+
".dsh-recall-ex-input:focus-visible,.dsh-recall-cfg-input:focus-visible,.dsh-recall-ex-area:focus-visible,.dsh-recall-cfg-area:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}",
|
|
64
|
+
".dsh-recall-toast{position:fixed;top:18px;left:50%;transform:translateX(-50%);z-index:10000;max-width:min(560px,86vw);box-sizing:border-box;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:10px 16px;font-size:13px;line-height:1.5;box-shadow:0 8px 28px rgba(0,0,0,.22);display:flex;align-items:baseline;gap:8px;opacity:0;transition:opacity .25s ease;pointer-events:auto}",
|
|
65
|
+
".dsh-recall-toast.dsh-recall-toast-in{opacity:1}",
|
|
66
|
+
".dsh-recall-toast-tag{flex:none;font-weight:600;color:var(--dsw-alias-state-error-primary)}",
|
|
67
|
+
".dsh-recall-ex-card{display:flex;flex-direction:column;gap:8px}",
|
|
68
|
+
".dsh-recall-ex-title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600;line-height:1.5}",
|
|
69
|
+
".dsh-recall-ex-note{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1.5;word-break:break-word}",
|
|
70
|
+
// 存储路径独立行:等宽字体 + break-all,Windows 长路径整齐折行;12px tertiary
|
|
71
|
+
// 降为辅助信息层级(与说明正文同色系但更轻),margin-top 收紧与上一行说明的
|
|
72
|
+
// 归属关系(ex-card 统一 gap 8px 对「说明→其路径」偏松)。
|
|
73
|
+
".dsh-recall-ex-path{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5;font-family:var(--dsw-font-code, ui-monospace, SFMono-Regular, Consolas, monospace);word-break:break-all;margin-top:-4px}",
|
|
74
|
+
// 输入类控件统一官方 field input 配方(At1oFq_input 逐字):.5px l4 发丝描边 +
|
|
75
|
+
// layer-3 底 + 8px 圆角 + 0/12px 内边距(单行框 34px 高);卡片打开态底色是
|
|
76
|
+
// layer-2,layer-3 输入框在其上恰好与官方插件配置卡同层叠关系
|
|
77
|
+
".dsh-recall-ex-area{width:100%;box-sizing:border-box;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;padding:8px 10px;font-size:12px;line-height:1.5;font-family:var(--dsw-font-code, ui-monospace, SFMono-Regular, Consolas, monospace);resize:vertical;min-height:120px}",
|
|
78
|
+
".dsh-recall-ex-quick{display:flex;flex-wrap:wrap;gap:8px;align-items:center}",
|
|
79
|
+
".dsh-recall-ex-input{flex:1;min-width:180px;box-sizing:border-box;height:34px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}",
|
|
80
|
+
// chip 改官方 badge 丸形配方(bg-module-platform + 999px)——树内小操作与
|
|
81
|
+
// 排除建议属「标签级动作」,与状态徽章同形态更一致;hover 用 :not(:disabled)
|
|
82
|
+
// 收口(与按钮同法)
|
|
83
|
+
".dsh-recall-ex-chip{border:none;border-radius:999px;padding:1px 10px;font-size:12px;line-height:1.5;cursor:pointer;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-module-platform)}",
|
|
84
|
+
".dsh-recall-ex-chip:hover:not(:disabled){color:var(--dsw-alias-label-primary)}",
|
|
85
|
+
// V9 确认按钮 danger chip:红色文字 + 官方失败状态行底,与 .btn-danger 同义
|
|
86
|
+
// 但保持 chip 尺寸层级(确认条内部不出现大按钮)
|
|
87
|
+
".dsh-recall-ex-chip-danger{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover-danger)}",
|
|
88
|
+
// 基础 chip hover 带伪类、优先级更高,会把危险 chip 的红字刷回普通色——
|
|
89
|
+
// 显式重申保持 hover 下仍是红字(危险语义在悬停确认时最不能丢)
|
|
90
|
+
".dsh-recall-ex-chip-danger:hover:not(:disabled){color:var(--dsw-alias-state-error-primary)}",
|
|
91
|
+
".dsh-recall-ex-status{margin-right:auto;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}",
|
|
92
|
+
".dsh-recall-ex-status-error{color:var(--dsw-alias-state-error-primary)}",
|
|
93
|
+
".dsh-recall-ex-status-success{color:var(--dsw-alias-state-success-primary)}",
|
|
94
|
+
// V6 健康徽章(git 可用性):pill 配色直接复用官方状态行配对——成功用
|
|
95
|
+
// success-tertiary 底、失败用 interactive-bg-hover-danger 底(error 无
|
|
96
|
+
// tertiary 令牌,官方失败状态行即用此搭配,主题感知)。
|
|
97
|
+
".dsh-recall-health-pill{display:inline-flex;align-items:center;padding:1px 10px;border-radius:999px;font-size:12px;line-height:1.5}",
|
|
98
|
+
".dsh-recall-health-pill-ok{background:var(--dsw-alias-state-success-tertiary);color:var(--dsw-alias-state-success-primary)}",
|
|
99
|
+
".dsh-recall-health-pill-bad{background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary)}",
|
|
100
|
+
// V6 错误区标题:error 色 + 条数,错误不再是灰色小字(fail-loud 可见性)
|
|
101
|
+
".dsh-recall-errors-title{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:1.5;font-weight:600}",
|
|
102
|
+
".dsh-recall-tree{display:flex;flex-direction:column;gap:2px;padding:4px 0}",
|
|
103
|
+
".dsh-recall-tree-node{display:flex;flex-direction:column;gap:1px}",
|
|
104
|
+
".dsh-recall-tree-row{display:flex;gap:6px;align-items:center;min-width:0;padding:2px 4px;border-radius:6px;cursor:default}",
|
|
105
|
+
".dsh-recall-tree-row:hover{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
106
|
+
// V2 树折叠钮 span→button 的 UA 默认样式重置:button 自带 appearance/背景/边框/
|
|
107
|
+
// 内边距,与 span 形态差异在此抹平,保证纯键盘可达不引入视觉回归。
|
|
108
|
+
".dsh-recall-tree-toggle{appearance:none;background:0 0;border:0;padding:0;font:inherit;flex:none;width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;color:var(--dsw-alias-label-tertiary);cursor:pointer;border-radius:4px;font-size:12px;line-height:1.5;user-select:none}",
|
|
109
|
+
".dsh-recall-tree-toggle:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
110
|
+
".dsh-recall-tree-toggle-placeholder{flex:none;width:18px;height:18px}",
|
|
111
|
+
".dsh-recall-tree-label{flex:1;min-width:0;display:flex;gap:8px;align-items:baseline;font-size:12px;line-height:1.5;overflow:hidden}",
|
|
112
|
+
".dsh-recall-tree-name{flex:none;font-weight:600;color:var(--dsw-alias-label-secondary)}",
|
|
113
|
+
".dsh-recall-tree-title{min-width:0;color:var(--dsw-alias-label-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
|
|
114
|
+
".dsh-recall-tree-meta{flex:none;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary);white-space:nowrap}",
|
|
115
|
+
// V4 树缩进契约化:总缩进 = --dsh-recall-tree-indent(折叠钮 18px + gap 6px),
|
|
116
|
+
// children 按 2/3(16px margin)与 1/3(8px padding)拆分,恰好让子行文字
|
|
117
|
+
// 与父行折叠钮右缘对齐——把「16+8 恰等于 18+6」的巧合变成单一事实源。
|
|
118
|
+
".dsh-recall-tree-children{display:flex;flex-direction:column;gap:1px;margin-left:calc(var(--dsh-recall-tree-indent)*2/3);border-left:1px solid var(--dsw-alias-border-l1);padding-left:calc(var(--dsh-recall-tree-indent)/3);animation:dsh-recall-unfold .16s ease-out}",
|
|
119
|
+
".dsh-recall-tree-confirm{display:flex;gap:8px;align-items:center;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-secondary);animation:dsh-recall-unfold .16s ease-out}",
|
|
120
|
+
// V9 展开动效:opacity + 2px 上移入场(确认条/树展开共用一个 keyframes)。
|
|
121
|
+
// 弃用 max-height 过渡:树展开高度可变(长列表数百 px),固定上限会裁切;
|
|
122
|
+
// opacity+位移给出同等显隐反馈且零裁切风险。
|
|
123
|
+
"@keyframes dsh-recall-unfold{from{opacity:0;transform:translateY(-2px)}to{opacity:1;transform:none}}",
|
|
124
|
+
"@media (prefers-reduced-motion:reduce){.dsh-recall-tree-children,.dsh-recall-tree-confirm{animation:none}}",
|
|
125
|
+
// 卡片对齐同列官方插件设置卡(YyYd_a_card 逐字配方):.5px l4 发丝描边 +
|
|
126
|
+
// 16px 圆角 + hover 升 label-dimmed 描边;打开态(layer-2 底 + dimmed 描边)
|
|
127
|
+
// 本就与官方一致,保持不变
|
|
128
|
+
".dsh-recall-card{border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-3);border-radius:16px;list-style:none;transition:border-color .16s,background .16s;display:flex;flex-direction:column;text-align:left}",
|
|
129
|
+
".dsh-recall-card:hover{border-color:var(--dsw-alias-label-dimmed)}",
|
|
130
|
+
".dsh-recall-card.dsh-recall-card-open{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}",
|
|
131
|
+
".dsh-recall-card-head{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}",
|
|
132
|
+
".dsh-recall-card-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}",
|
|
133
|
+
".dsh-recall-card-desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}",
|
|
134
|
+
".dsh-recall-card-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:12px 0 8px;display:flex;flex-direction:column;gap:12px}",
|
|
135
|
+
".dsh-recall-cardbtn{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}",
|
|
136
|
+
// 折叠头(SectionToggle)复用卡片头 cardbtn:14/16px 内边距在卡片 body 内表现为
|
|
137
|
+
// 16px 左缩进,与分组标题/表单内容错位(用户实测反馈)——body 内覆写为贴左,
|
|
138
|
+
// 卡片头(.dsh-recall-card 直接子级)不受影响。
|
|
139
|
+
".dsh-recall-card-body .dsh-recall-cardbtn{padding:6px 0}",
|
|
140
|
+
// 折叠头箭头贴左:tree-toggle 是 18px 定宽居中盒,▸/▾ 字形居中使其墨迹比
|
|
141
|
+
// 上方表单标签右偏 ~5px(用户实测折叠头与内容左缘不齐);盒内改左对齐后,
|
|
142
|
+
// 三个折叠头(高级/排除配置/快照管理)箭头与标签共享同一左缘。
|
|
143
|
+
".dsh-recall-card-body .dsh-recall-cardbtn .dsh-recall-tree-toggle{justify-content:flex-start}",
|
|
144
|
+
// 分区折叠头箭头旋转化:与官方卡片 chevron 同一动效语言(transition .16s、
|
|
145
|
+
// 开合=旋转),替代 ▸/▾ 字符瞬切;只挂 SectionToggle,树内折叠钮维持字符
|
|
146
|
+
// 切换不受影响
|
|
147
|
+
".dsh-recall-section-chevron{transition:transform .16s}",
|
|
148
|
+
".dsh-recall-section-chevron-open{transform:rotate(90deg)}",
|
|
149
|
+
// cfg-grid:ConfigForm 全部行共享的单一 grid 容器。此前每行 cfg-row 是独立
|
|
150
|
+
// grid,「第一列 max-content」各行各算,跨行对齐从未成立(checkbox/输入框
|
|
151
|
+
// 列参差);cfg-row 改 display:contents 透明化后,label/控件行/hint 直接成为
|
|
152
|
+
// 本容器的 item,第一列列宽由全表单最长 label 决定——跨行对齐自此成立。
|
|
153
|
+
".dsh-recall-cfg-grid{display:grid;grid-template-columns:max-content minmax(0,1fr);column-gap:8px;row-gap:4px;align-items:start}",
|
|
154
|
+
".dsh-recall-cfg-row{display:contents}",
|
|
155
|
+
".dsh-recall-cfg-grid > .dsh-recall-cardbtn{grid-column:1/-1}",
|
|
156
|
+
// V5 表单分组小标题:语义分组分隔符。组间用整行分隔线 + 加倍留白划界
|
|
157
|
+
// (「快照行为」「自动治理」是两个配置维度,仅小标题用户实测分不清组界);
|
|
158
|
+
// 首组(紧跟卡片头)不需要分隔线,三件套清零。
|
|
159
|
+
// 组间分隔线随官方发丝线规格(.5px l2,与官方卡体/字段分隔同粗细)
|
|
160
|
+
".dsh-recall-cfg-group{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1.5;font-weight:600;grid-column:1/-1;margin-top:10px;padding-top:10px;border-top:.5px solid var(--dsw-alias-border-l2)}",
|
|
161
|
+
".dsh-recall-cfg-group:first-child{margin-top:0;padding-top:0;border-top:none}",
|
|
162
|
+
// V4 后 cfg-line 只装控件(label 已是 cfg-row 直接子元素,占 grid 第一列 max-content)
|
|
163
|
+
".dsh-recall-cfg-line{display:flex;align-items:center;gap:8px;min-width:0;grid-column:2}",
|
|
164
|
+
// 主标签用 label-primary + 官方字段标签字重 500(At1oFq_label),与 12px
|
|
165
|
+
// tertiary 的说明文字拉开层级;说明固定在控件下方第二行,不混排进主标签行。
|
|
166
|
+
".dsh-recall-cfg-label{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5;font-weight:500}",
|
|
167
|
+
// 数字输入框 = 官方 field input 配方(.5px l4 发丝描边 + layer-3 底 + 34px
|
|
168
|
+
// 高 + 0/12px 内边距)叠加本插件既有约定:定宽 120px + 右对齐(不定宽时框宽
|
|
169
|
+
// 随行内 tag 有无伸缩,实测参差);tabular-nums 让同列数字等宽、纵向扫描
|
|
170
|
+
// 小数位整齐。120px 足够容纳「0.01」~「1000000」区间。
|
|
171
|
+
".dsh-recall-cfg-input{flex:none;width:120px;box-sizing:border-box;height:34px;font:inherit;font-size:13px;text-align:right;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;padding:0 12px}",
|
|
172
|
+
// disabled 随官方 input:文字降 tertiary,不动透明度(整框变淡会让「框还在
|
|
173
|
+
// 只是不可写」的语义变含糊)
|
|
174
|
+
".dsh-recall-cfg-input:disabled,.dsh-recall-cfg-area:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}",
|
|
175
|
+
// 数字输入框隐藏原生加减微调按钮(spinner):34px 高的定宽框里 spinner 挤占
|
|
176
|
+
// 右侧数字区、跨引擎渲染不一(Chromium 上下箭头 / Firefox 无),与「右对齐
|
|
177
|
+
// 数字」的纵向扫描相冲;隐藏后键盘 ↑↓ 与直接输入仍可用,步进语义不丢。
|
|
178
|
+
// scope 到 cfg-input,不外溢影响宿主或其他插件的 number 输入。
|
|
179
|
+
".dsh-recall-cfg-input::-webkit-inner-spin-button,.dsh-recall-cfg-input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}",
|
|
180
|
+
".dsh-recall-cfg-input{-moz-appearance:textfield;appearance:textfield}",
|
|
181
|
+
".dsh-recall-cfg-area{font-family:inherit;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;padding:6px 8px;min-height:64px;box-sizing:border-box;width:100%;grid-column:2}",
|
|
182
|
+
// 布尔开关:官方设置表单的布尔字段用 role=switch 滑钮而非原生 checkbox
|
|
183
|
+
//(vCGm7G_switch 逐字配方:36×20 轨道 border-l3 底、开=brand-primary、
|
|
184
|
+
// 16px 拇指 label-primary-foreground 滑动 16px、transition .12s)
|
|
185
|
+
".dsh-recall-cfg-switch{box-sizing:border-box;flex:none;width:36px;height:20px;padding:2px;border:0;border-radius:10px;background:var(--dsw-alias-border-l3);cursor:pointer;position:relative}",
|
|
186
|
+
'.dsh-recall-cfg-switch[aria-checked="true"]{background:var(--dsw-alias-brand-primary)}',
|
|
187
|
+
".dsh-recall-cfg-switch:disabled{opacity:.4;cursor:default}",
|
|
188
|
+
".dsh-recall-cfg-switch-thumb{display:block;width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-primary-foreground);transition:transform .12s}",
|
|
189
|
+
'.dsh-recall-cfg-switch[aria-checked="true"] .dsh-recall-cfg-switch-thumb{transform:translateX(16px)}',
|
|
190
|
+
// hint 的 padding-bottom 即组内行距的单一事实源(row-gap 只管网格行缝)——
|
|
191
|
+
// 行间节奏统一为 8+4=12px,避免各行松紧不一。
|
|
192
|
+
".dsh-recall-cfg-hint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5;grid-column:2;padding-bottom:8px}",
|
|
193
|
+
// 基础排除表通栏(baseExcludes textarea/hint):折叠展开后内容顶满卡片宽度,
|
|
194
|
+
// 消除左侧 label 列竖直死区。必须定义在 480px media 之前——media 内的
|
|
195
|
+
// grid-column:auto 同特异性、后出现,窄屏下仍能正确回落单列堆叠。
|
|
196
|
+
".dsh-recall-cfg-span{grid-column:1/-1}",
|
|
197
|
+
// V4/V8 共用 480px 断点:cfg 表单单列堆叠(V4);exclude quick 行输入框独占
|
|
198
|
+
// 一行(flex:1 1 100%,basis 100% 强制换行,添加按钮与芯片建议另起一行)、
|
|
199
|
+
// panel-actions 长状态文案不再挤压按钮(wrap 已在基础规则,此处无需重复)。
|
|
200
|
+
"@media (max-width:480px){.dsh-recall-cfg-grid{grid-template-columns:minmax(0,1fr);row-gap:2px}.dsh-recall-cfg-line,.dsh-recall-cfg-hint,.dsh-recall-cfg-area{grid-column:auto}.dsh-recall-ex-quick .dsh-recall-ex-input{flex:1 1 100%}}",
|
|
201
|
+
".dsh-recall-cfg-tag{flex:none;font-size:12px;line-height:1.4;padding:1px 6px;border-radius:6px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-tertiary)}",
|
|
202
|
+
// 已修改(用户改了配置)用 warn 底色提示「有未保存变更」——修改不是错误,
|
|
203
|
+
// warn 家族来自 V1 核验的官方配对(tertiary 底 + label 文)。
|
|
204
|
+
".dsh-recall-cfg-tag-modified{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label)}",
|
|
205
|
+
// 环境变量锁定:系统锁住不可写,区别于「已覆盖」(值被更高优先源覆盖)——
|
|
206
|
+
// 两者同属中性,用左边框做结构级区分,不引入第三色。
|
|
207
|
+
".dsh-recall-cfg-tag-locked{border-left:2px solid var(--dsw-alias-border-l2)}",
|
|
208
|
+
// V3 快照树加载骨架:items===null 时的 5 条占位行,pulse 明暗呼吸模拟加载。
|
|
209
|
+
".dsh-recall-tree-skeleton{display:flex;flex-direction:column;gap:2px;padding:4px 0}",
|
|
210
|
+
".dsh-recall-tree-skeleton-row{height:20px;border-radius:6px;background:var(--dsw-alias-interactive-bg-hover);animation:dsh-recall-pulse 1.2s ease-in-out infinite}",
|
|
211
|
+
"@keyframes dsh-recall-pulse{0%,100%{opacity:1}50%{opacity:.45}}"
|
|
212
|
+
].join("");
|
|
2
213
|
|
|
3
|
-
// src/client/
|
|
4
|
-
|
|
5
|
-
// 语义变量集中声明(V1-5):btn-danger 的 hover 亮度是令牌体系外补丁,
|
|
6
|
-
// 集中成单一来源避免散落硬编码;前景色用官方实证配对 bg-layer-3(error
|
|
7
|
-
// primary 底色上文本随主题翻转,见 plan-settings-ui V1 核验记录)。
|
|
8
|
-
":root{--dsh-recall-btn-danger-hover:brightness(1.08);--dsh-recall-tree-indent:24px;--dsh-recall-space-1:4px;--dsh-recall-space-2:8px;--dsh-recall-space-3:12px}",
|
|
9
|
-
".dsh-recall-row{flex-direction:column;align-items:flex-end;gap:6px;display:flex}",
|
|
10
|
-
".dsh-recall-stack{flex-direction:column;align-items:flex-end;gap:8px;min-width:0;max-width:min(525px,82%);display:flex}",
|
|
11
|
-
".dsh-recall-bubble{background:var(--dsw-specific-bubble);max-width:100%;color:var(--dsw-alias-label-primary);border-radius:22px;padding:10px 16px;font-size:16px;line-height:1.5;white-space:pre-wrap;word-break:break-word}",
|
|
12
|
-
".dsh-recall-json{margin:0;max-width:100%;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;word-break:break-word;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:8px 10px;background:var(--dsw-alias-markdown-code-block)}",
|
|
13
|
-
".dsh-recall-actions{align-items:center;gap:10px;height:28px;display:flex}",
|
|
14
|
-
".dsh-recall-time{color:var(--dsw-alias-label-tertiary);white-space:nowrap;padding-right:12px;font-size:14px;line-height:1.5}",
|
|
15
|
-
".dsh-recall-action{width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:6px;display:inline-flex}",
|
|
16
|
-
".dsh-recall-action:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}",
|
|
17
|
-
"@media (hover:hover){[data-time-hover-root] .dsh-recall-time{opacity:0;transition:opacity 80ms}[data-time-hover-root]:hover .dsh-recall-time,[data-time-hover-root]:focus-within .dsh-recall-time{opacity:1}}",
|
|
18
|
-
".dsh-recall-panel{width:min(480px,100%);box-sizing:border-box;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:8px;text-align:left;box-shadow:0 8px 28px rgba(0,0,0,.22)}",
|
|
19
|
-
".dsh-recall-panel-title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600;line-height:1.5}",
|
|
20
|
-
".dsh-recall-panel-note{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1.5;word-break:break-word}",
|
|
21
|
-
".dsh-recall-list{max-height:220px;overflow:auto;display:flex;flex-direction:column;gap:2px;padding:4px 0}",
|
|
22
|
-
".dsh-recall-file{display:flex;gap:8px;align-items:baseline;font-size:12px;line-height:1.5}",
|
|
23
|
-
".dsh-recall-badge{flex:none;font-size:12px;line-height:1.5;padding:0 6px;border-radius:6px}",
|
|
24
|
-
".dsh-recall-badge-modified{color:var(--dsw-alias-state-warn-label);background:var(--dsw-alias-state-warn-tertiary)}",
|
|
25
|
-
".dsh-recall-badge-restored{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
26
|
-
".dsh-recall-badge-added{color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
27
|
-
".dsh-recall-rel{min-width:0;color:var(--dsw-alias-label-primary);word-break:break-all;font-family:var(--dsw-font-code, ui-monospace, SFMono-Regular, Consolas, monospace)}",
|
|
28
|
-
// grid-column 对非 grid 祖先(exclude/快照卡片的 flex 布局)自动无效,无害;
|
|
29
|
-
// 在 cfg-grid 内则保证操作区/占满行不被 auto-placement 塞进第一列撑爆列宽。
|
|
30
|
-
".dsh-recall-panel-actions{display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end;margin-top:2px;grid-column:1/-1}",
|
|
31
|
-
// 按钮体系对齐官方设置卡(dsh-client-ui-settings-plugins PluginCard 底部按钮
|
|
32
|
-
// 组实测产物):次级 = 描边幽灵(discard 逐字配方——l2 描边 + 透明底,hover
|
|
33
|
-
// 升 label-dimmed 描边 + primary 字色);官方 discard/save 同高靠 save 也带
|
|
34
|
-
// 1px 描边,故变体统一 border-color:transparent 保同盒模型。hover 用
|
|
35
|
-
// :not(:disabled) 收口后,disabled 的 hover 抵消规则不再需要;disabled 透明度
|
|
36
|
-
// 随官方 .4。
|
|
37
|
-
".dsh-recall-btn{border:1px solid var(--dsw-alias-border-l2);background:0 0;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5;cursor:pointer;color:var(--dsw-alias-label-secondary)}",
|
|
38
|
-
".dsh-recall-btn:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}",
|
|
39
|
-
".dsh-recall-btn:disabled,.dsh-recall-ex-chip:disabled{opacity:.4;cursor:default}",
|
|
40
|
-
// 危险/主按钮的 hover 须显式重申文字与描边——基础 ghost hover 带伪类、优先级
|
|
41
|
-
// 更高,会把它们的字色刷回 label-primary 并描出亮边
|
|
42
|
-
".dsh-recall-btn-danger{background:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-bg-layer-3);border-color:transparent}",
|
|
43
|
-
".dsh-recall-btn-danger:hover:not(:disabled){color:var(--dsw-alias-bg-layer-3);border-color:transparent;filter:var(--dsh-recall-btn-danger-hover)}",
|
|
44
|
-
// 主按钮(保存)改用官方插件设置卡 save 的逐字配方:label-primary 反色实心 +
|
|
45
|
-
// bg-layer-3 前景(同列官方插件卡的保存即此形态,比 button-primary-fill 更贴
|
|
46
|
-
// 设置页语境);官方 save 无 hover 态,这里的 hover 规则只做「不被基础 ghost
|
|
47
|
-
// hover 污染」的抵消,视觉变化为零即是官方行为。只挂「保存」,主次分明。
|
|
48
|
-
".dsh-recall-btn-primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:transparent}",
|
|
49
|
-
".dsh-recall-btn-primary:hover:not(:disabled){color:var(--dsw-alias-bg-layer-3);border-color:transparent}",
|
|
50
|
-
// 焦点可见性对齐官方设置页实测写法(同上产物):按钮类 = brand-primary 2px
|
|
51
|
-
// outline(卡片头 offset -2px 内收,其余 +1px);输入类 = brand-primary 描边
|
|
52
|
-
// 变色、不套 ring。替换 V2-5 的 border-l3 环方案(当时核验的是旧版写法)。
|
|
53
|
-
".dsh-recall-btn:focus-visible,.dsh-recall-ex-chip:focus-visible,.dsh-recall-tree-toggle:focus-visible,.dsh-recall-cfg-switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}",
|
|
54
|
-
".dsh-recall-cardbtn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}",
|
|
55
|
-
".dsh-recall-ex-input:focus-visible,.dsh-recall-cfg-input:focus-visible,.dsh-recall-ex-area:focus-visible,.dsh-recall-cfg-area:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}",
|
|
56
|
-
".dsh-recall-toast{position:fixed;top:18px;left:50%;transform:translateX(-50%);z-index:10000;max-width:min(560px,86vw);box-sizing:border-box;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:10px 16px;font-size:13px;line-height:1.5;box-shadow:0 8px 28px rgba(0,0,0,.22);display:flex;align-items:baseline;gap:8px;opacity:0;transition:opacity .25s ease;pointer-events:auto}",
|
|
57
|
-
".dsh-recall-toast.dsh-recall-toast-in{opacity:1}",
|
|
58
|
-
".dsh-recall-toast-tag{flex:none;font-weight:600;color:var(--dsw-alias-state-error-primary)}",
|
|
59
|
-
".dsh-recall-ex-card{display:flex;flex-direction:column;gap:8px}",
|
|
60
|
-
".dsh-recall-ex-title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600;line-height:1.5}",
|
|
61
|
-
".dsh-recall-ex-note{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1.5;word-break:break-word}",
|
|
62
|
-
// 存储路径独立行:等宽字体 + break-all,Windows 长路径整齐折行;12px tertiary
|
|
63
|
-
// 降为辅助信息层级(与说明正文同色系但更轻),margin-top 收紧与上一行说明的
|
|
64
|
-
// 归属关系(ex-card 统一 gap 8px 对「说明→其路径」偏松)。
|
|
65
|
-
".dsh-recall-ex-path{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5;font-family:var(--dsw-font-code, ui-monospace, SFMono-Regular, Consolas, monospace);word-break:break-all;margin-top:-4px}",
|
|
66
|
-
// 输入类控件统一官方 field input 配方(At1oFq_input 逐字):.5px l4 发丝描边 +
|
|
67
|
-
// layer-3 底 + 8px 圆角 + 0/12px 内边距(单行框 34px 高);卡片打开态底色是
|
|
68
|
-
// layer-2,layer-3 输入框在其上恰好与官方插件配置卡同层叠关系
|
|
69
|
-
".dsh-recall-ex-area{width:100%;box-sizing:border-box;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;padding:8px 10px;font-size:12px;line-height:1.5;font-family:var(--dsw-font-code, ui-monospace, SFMono-Regular, Consolas, monospace);resize:vertical;min-height:120px}",
|
|
70
|
-
".dsh-recall-ex-quick{display:flex;flex-wrap:wrap;gap:8px;align-items:center}",
|
|
71
|
-
".dsh-recall-ex-input{flex:1;min-width:180px;box-sizing:border-box;height:34px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}",
|
|
72
|
-
// chip 改官方 badge 丸形配方(bg-module-platform + 999px)——树内小操作与
|
|
73
|
-
// 排除建议属「标签级动作」,与状态徽章同形态更一致;hover 用 :not(:disabled)
|
|
74
|
-
// 收口(与按钮同法)
|
|
75
|
-
".dsh-recall-ex-chip{border:none;border-radius:999px;padding:1px 10px;font-size:12px;line-height:1.5;cursor:pointer;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-module-platform)}",
|
|
76
|
-
".dsh-recall-ex-chip:hover:not(:disabled){color:var(--dsw-alias-label-primary)}",
|
|
77
|
-
// V9 确认按钮 danger chip:红色文字 + 官方失败状态行底,与 .btn-danger 同义
|
|
78
|
-
// 但保持 chip 尺寸层级(确认条内部不出现大按钮)
|
|
79
|
-
".dsh-recall-ex-chip-danger{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover-danger)}",
|
|
80
|
-
// 基础 chip hover 带伪类、优先级更高,会把危险 chip 的红字刷回普通色——
|
|
81
|
-
// 显式重申保持 hover 下仍是红字(危险语义在悬停确认时最不能丢)
|
|
82
|
-
".dsh-recall-ex-chip-danger:hover:not(:disabled){color:var(--dsw-alias-state-error-primary)}",
|
|
83
|
-
".dsh-recall-ex-status{margin-right:auto;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}",
|
|
84
|
-
".dsh-recall-ex-status-error{color:var(--dsw-alias-state-error-primary)}",
|
|
85
|
-
".dsh-recall-ex-status-success{color:var(--dsw-alias-state-success-primary)}",
|
|
86
|
-
// V6 健康徽章(git 可用性):pill 配色直接复用官方状态行配对——成功用
|
|
87
|
-
// success-tertiary 底、失败用 interactive-bg-hover-danger 底(error 无
|
|
88
|
-
// tertiary 令牌,官方失败状态行即用此搭配,主题感知)。
|
|
89
|
-
".dsh-recall-health-pill{display:inline-flex;align-items:center;padding:1px 10px;border-radius:999px;font-size:12px;line-height:1.5}",
|
|
90
|
-
".dsh-recall-health-pill-ok{background:var(--dsw-alias-state-success-tertiary);color:var(--dsw-alias-state-success-primary)}",
|
|
91
|
-
".dsh-recall-health-pill-bad{background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary)}",
|
|
92
|
-
// V6 错误区标题:error 色 + 条数,错误不再是灰色小字(fail-loud 可见性)
|
|
93
|
-
".dsh-recall-errors-title{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:1.5;font-weight:600}",
|
|
94
|
-
".dsh-recall-tree{display:flex;flex-direction:column;gap:2px;padding:4px 0}",
|
|
95
|
-
".dsh-recall-tree-node{display:flex;flex-direction:column;gap:1px}",
|
|
96
|
-
".dsh-recall-tree-row{display:flex;gap:6px;align-items:center;min-width:0;padding:2px 4px;border-radius:6px;cursor:default}",
|
|
97
|
-
".dsh-recall-tree-row:hover{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
98
|
-
// V2 树折叠钮 span→button 的 UA 默认样式重置:button 自带 appearance/背景/边框/
|
|
99
|
-
// 内边距,与 span 形态差异在此抹平,保证纯键盘可达不引入视觉回归。
|
|
100
|
-
".dsh-recall-tree-toggle{appearance:none;background:0 0;border:0;padding:0;font:inherit;flex:none;width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;color:var(--dsw-alias-label-tertiary);cursor:pointer;border-radius:4px;font-size:12px;line-height:1.5;user-select:none}",
|
|
101
|
-
".dsh-recall-tree-toggle:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
102
|
-
".dsh-recall-tree-toggle-placeholder{flex:none;width:18px;height:18px}",
|
|
103
|
-
".dsh-recall-tree-label{flex:1;min-width:0;display:flex;gap:8px;align-items:baseline;font-size:12px;line-height:1.5;overflow:hidden}",
|
|
104
|
-
".dsh-recall-tree-name{flex:none;font-weight:600;color:var(--dsw-alias-label-secondary)}",
|
|
105
|
-
".dsh-recall-tree-title{min-width:0;color:var(--dsw-alias-label-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
|
|
106
|
-
".dsh-recall-tree-meta{flex:none;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary);white-space:nowrap}",
|
|
107
|
-
// V4 树缩进契约化:总缩进 = --dsh-recall-tree-indent(折叠钮 18px + gap 6px),
|
|
108
|
-
// children 按 2/3(16px margin)与 1/3(8px padding)拆分,恰好让子行文字
|
|
109
|
-
// 与父行折叠钮右缘对齐——把「16+8 恰等于 18+6」的巧合变成单一事实源。
|
|
110
|
-
".dsh-recall-tree-children{display:flex;flex-direction:column;gap:1px;margin-left:calc(var(--dsh-recall-tree-indent)*2/3);border-left:1px solid var(--dsw-alias-border-l1);padding-left:calc(var(--dsh-recall-tree-indent)/3);animation:dsh-recall-unfold .16s ease-out}",
|
|
111
|
-
".dsh-recall-tree-confirm{display:flex;gap:8px;align-items:center;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-secondary);animation:dsh-recall-unfold .16s ease-out}",
|
|
112
|
-
// V9 展开动效:opacity + 2px 上移入场(确认条/树展开共用一个 keyframes)。
|
|
113
|
-
// 弃用 max-height 过渡:树展开高度可变(长列表数百 px),固定上限会裁切;
|
|
114
|
-
// opacity+位移给出同等显隐反馈且零裁切风险。
|
|
115
|
-
"@keyframes dsh-recall-unfold{from{opacity:0;transform:translateY(-2px)}to{opacity:1;transform:none}}",
|
|
116
|
-
"@media (prefers-reduced-motion:reduce){.dsh-recall-tree-children,.dsh-recall-tree-confirm{animation:none}}",
|
|
117
|
-
// 卡片对齐同列官方插件设置卡(YyYd_a_card 逐字配方):.5px l4 发丝描边 +
|
|
118
|
-
// 16px 圆角 + hover 升 label-dimmed 描边;打开态(layer-2 底 + dimmed 描边)
|
|
119
|
-
// 本就与官方一致,保持不变
|
|
120
|
-
".dsh-recall-card{border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-3);border-radius:16px;list-style:none;transition:border-color .16s,background .16s;display:flex;flex-direction:column;text-align:left}",
|
|
121
|
-
".dsh-recall-card:hover{border-color:var(--dsw-alias-label-dimmed)}",
|
|
122
|
-
".dsh-recall-card.dsh-recall-card-open{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}",
|
|
123
|
-
".dsh-recall-card-head{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}",
|
|
124
|
-
".dsh-recall-card-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}",
|
|
125
|
-
".dsh-recall-card-desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}",
|
|
126
|
-
".dsh-recall-card-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:12px 0 8px;display:flex;flex-direction:column;gap:12px}",
|
|
127
|
-
".dsh-recall-cardbtn{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}",
|
|
128
|
-
// 折叠头(SectionToggle)复用卡片头 cardbtn:14/16px 内边距在卡片 body 内表现为
|
|
129
|
-
// 16px 左缩进,与分组标题/表单内容错位(用户实测反馈)——body 内覆写为贴左,
|
|
130
|
-
// 卡片头(.dsh-recall-card 直接子级)不受影响。
|
|
131
|
-
".dsh-recall-card-body .dsh-recall-cardbtn{padding:6px 0}",
|
|
132
|
-
// 折叠头箭头贴左:tree-toggle 是 18px 定宽居中盒,▸/▾ 字形居中使其墨迹比
|
|
133
|
-
// 上方表单标签右偏 ~5px(用户实测折叠头与内容左缘不齐);盒内改左对齐后,
|
|
134
|
-
// 三个折叠头(高级/排除配置/快照管理)箭头与标签共享同一左缘。
|
|
135
|
-
".dsh-recall-card-body .dsh-recall-cardbtn .dsh-recall-tree-toggle{justify-content:flex-start}",
|
|
136
|
-
// 分区折叠头箭头旋转化:与官方卡片 chevron 同一动效语言(transition .16s、
|
|
137
|
-
// 开合=旋转),替代 ▸/▾ 字符瞬切;只挂 SectionToggle,树内折叠钮维持字符
|
|
138
|
-
// 切换不受影响
|
|
139
|
-
".dsh-recall-section-chevron{transition:transform .16s}",
|
|
140
|
-
".dsh-recall-section-chevron-open{transform:rotate(90deg)}",
|
|
141
|
-
// cfg-grid:ConfigForm 全部行共享的单一 grid 容器。此前每行 cfg-row 是独立
|
|
142
|
-
// grid,「第一列 max-content」各行各算,跨行对齐从未成立(checkbox/输入框
|
|
143
|
-
// 列参差);cfg-row 改 display:contents 透明化后,label/控件行/hint 直接成为
|
|
144
|
-
// 本容器的 item,第一列列宽由全表单最长 label 决定——跨行对齐自此成立。
|
|
145
|
-
".dsh-recall-cfg-grid{display:grid;grid-template-columns:max-content minmax(0,1fr);column-gap:8px;row-gap:4px;align-items:start}",
|
|
146
|
-
".dsh-recall-cfg-row{display:contents}",
|
|
147
|
-
".dsh-recall-cfg-grid > .dsh-recall-cardbtn{grid-column:1/-1}",
|
|
148
|
-
// V5 表单分组小标题:语义分组分隔符。组间用整行分隔线 + 加倍留白划界
|
|
149
|
-
// (「快照行为」「自动治理」是两个配置维度,仅小标题用户实测分不清组界);
|
|
150
|
-
// 首组(紧跟卡片头)不需要分隔线,三件套清零。
|
|
151
|
-
// 组间分隔线随官方发丝线规格(.5px l2,与官方卡体/字段分隔同粗细)
|
|
152
|
-
".dsh-recall-cfg-group{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1.5;font-weight:600;grid-column:1/-1;margin-top:10px;padding-top:10px;border-top:.5px solid var(--dsw-alias-border-l2)}",
|
|
153
|
-
".dsh-recall-cfg-group:first-child{margin-top:0;padding-top:0;border-top:none}",
|
|
154
|
-
// V4 后 cfg-line 只装控件(label 已是 cfg-row 直接子元素,占 grid 第一列 max-content)
|
|
155
|
-
".dsh-recall-cfg-line{display:flex;align-items:center;gap:8px;min-width:0;grid-column:2}",
|
|
156
|
-
// 主标签用 label-primary + 官方字段标签字重 500(At1oFq_label),与 12px
|
|
157
|
-
// tertiary 的说明文字拉开层级;说明固定在控件下方第二行,不混排进主标签行。
|
|
158
|
-
".dsh-recall-cfg-label{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5;font-weight:500}",
|
|
159
|
-
// 数字输入框 = 官方 field input 配方(.5px l4 发丝描边 + layer-3 底 + 34px
|
|
160
|
-
// 高 + 0/12px 内边距)叠加本插件既有约定:定宽 120px + 右对齐(不定宽时框宽
|
|
161
|
-
// 随行内 tag 有无伸缩,实测参差);tabular-nums 让同列数字等宽、纵向扫描
|
|
162
|
-
// 小数位整齐。120px 足够容纳「0.01」~「1000000」区间。
|
|
163
|
-
".dsh-recall-cfg-input{flex:none;width:120px;box-sizing:border-box;height:34px;font:inherit;font-size:13px;text-align:right;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;padding:0 12px}",
|
|
164
|
-
// disabled 随官方 input:文字降 tertiary,不动透明度(整框变淡会让「框还在
|
|
165
|
-
// 只是不可写」的语义变含糊)
|
|
166
|
-
".dsh-recall-cfg-input:disabled,.dsh-recall-cfg-area:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}",
|
|
167
|
-
// 数字输入框隐藏原生加减微调按钮(spinner):34px 高的定宽框里 spinner 挤占
|
|
168
|
-
// 右侧数字区、跨引擎渲染不一(Chromium 上下箭头 / Firefox 无),与「右对齐
|
|
169
|
-
// 数字」的纵向扫描相冲;隐藏后键盘 ↑↓ 与直接输入仍可用,步进语义不丢。
|
|
170
|
-
// scope 到 cfg-input,不外溢影响宿主或其他插件的 number 输入。
|
|
171
|
-
".dsh-recall-cfg-input::-webkit-inner-spin-button,.dsh-recall-cfg-input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}",
|
|
172
|
-
".dsh-recall-cfg-input{-moz-appearance:textfield;appearance:textfield}",
|
|
173
|
-
".dsh-recall-cfg-area{font-family:inherit;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;padding:6px 8px;min-height:64px;box-sizing:border-box;width:100%;grid-column:2}",
|
|
174
|
-
// 布尔开关:官方设置表单的布尔字段用 role=switch 滑钮而非原生 checkbox
|
|
175
|
-
//(vCGm7G_switch 逐字配方:36×20 轨道 border-l3 底、开=brand-primary、
|
|
176
|
-
// 16px 拇指 label-primary-foreground 滑动 16px、transition .12s)
|
|
177
|
-
".dsh-recall-cfg-switch{box-sizing:border-box;flex:none;width:36px;height:20px;padding:2px;border:0;border-radius:10px;background:var(--dsw-alias-border-l3);cursor:pointer;position:relative}",
|
|
178
|
-
'.dsh-recall-cfg-switch[aria-checked="true"]{background:var(--dsw-alias-brand-primary)}',
|
|
179
|
-
".dsh-recall-cfg-switch:disabled{opacity:.4;cursor:default}",
|
|
180
|
-
".dsh-recall-cfg-switch-thumb{display:block;width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-primary-foreground);transition:transform .12s}",
|
|
181
|
-
'.dsh-recall-cfg-switch[aria-checked="true"] .dsh-recall-cfg-switch-thumb{transform:translateX(16px)}',
|
|
182
|
-
// hint 的 padding-bottom 即组内行距的单一事实源(row-gap 只管网格行缝)——
|
|
183
|
-
// 行间节奏统一为 8+4=12px,避免各行松紧不一。
|
|
184
|
-
".dsh-recall-cfg-hint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5;grid-column:2;padding-bottom:8px}",
|
|
185
|
-
// 基础排除表通栏(baseExcludes textarea/hint):折叠展开后内容顶满卡片宽度,
|
|
186
|
-
// 消除左侧 label 列竖直死区。必须定义在 480px media 之前——media 内的
|
|
187
|
-
// grid-column:auto 同特异性、后出现,窄屏下仍能正确回落单列堆叠。
|
|
188
|
-
".dsh-recall-cfg-span{grid-column:1/-1}",
|
|
189
|
-
// V4/V8 共用 480px 断点:cfg 表单单列堆叠(V4);exclude quick 行输入框独占
|
|
190
|
-
// 一行(flex:1 1 100%,basis 100% 强制换行,添加按钮与芯片建议另起一行)、
|
|
191
|
-
// panel-actions 长状态文案不再挤压按钮(wrap 已在基础规则,此处无需重复)。
|
|
192
|
-
"@media (max-width:480px){.dsh-recall-cfg-grid{grid-template-columns:minmax(0,1fr);row-gap:2px}.dsh-recall-cfg-line,.dsh-recall-cfg-hint,.dsh-recall-cfg-area{grid-column:auto}.dsh-recall-ex-quick .dsh-recall-ex-input{flex:1 1 100%}}",
|
|
193
|
-
".dsh-recall-cfg-tag{flex:none;font-size:12px;line-height:1.4;padding:1px 6px;border-radius:6px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-tertiary)}",
|
|
194
|
-
// 已修改(用户改了配置)用 warn 底色提示「有未保存变更」——修改不是错误,
|
|
195
|
-
// warn 家族来自 V1 核验的官方配对(tertiary 底 + label 文)。
|
|
196
|
-
".dsh-recall-cfg-tag-modified{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label)}",
|
|
197
|
-
// 环境变量锁定:系统锁住不可写,区别于「已覆盖」(值被更高优先源覆盖)——
|
|
198
|
-
// 两者同属中性,用左边框做结构级区分,不引入第三色。
|
|
199
|
-
".dsh-recall-cfg-tag-locked{border-left:2px solid var(--dsw-alias-border-l2)}",
|
|
200
|
-
// V3 快照树加载骨架:items===null 时的 5 条占位行,pulse 明暗呼吸模拟加载。
|
|
201
|
-
".dsh-recall-tree-skeleton{display:flex;flex-direction:column;gap:2px;padding:4px 0}",
|
|
202
|
-
".dsh-recall-tree-skeleton-row{height:20px;border-radius:6px;background:var(--dsw-alias-interactive-bg-hover);animation:dsh-recall-pulse 1.2s ease-in-out infinite}",
|
|
203
|
-
"@keyframes dsh-recall-pulse{0%,100%{opacity:1}50%{opacity:.45}}"
|
|
204
|
-
].join("");
|
|
205
|
-
|
|
206
|
-
// src/client/util.ts
|
|
207
|
-
function clockText(ms) {
|
|
208
|
-
try {
|
|
209
|
-
if (!ms || isNaN(new Date(ms).getTime())) return "";
|
|
210
|
-
const d = new Date(ms);
|
|
211
|
-
const now = /* @__PURE__ */ new Date();
|
|
212
|
-
const sameDay = d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate();
|
|
213
|
-
const hh = String(d.getHours()).padStart(2, "0");
|
|
214
|
-
const mm = String(d.getMinutes()).padStart(2, "0");
|
|
215
|
-
return sameDay ? hh + ":" + mm : d.getMonth() + 1 + "/" + d.getDate() + " " + hh + ":" + mm;
|
|
216
|
-
} catch (e) {
|
|
217
|
-
return "";
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
function sizeText(bytes) {
|
|
221
|
-
const n = Number(bytes);
|
|
222
|
-
if (!bytes || n <= 0) return "0 MB";
|
|
223
|
-
if (n < 1048576) return (n / 1024).toFixed(0) + " KB";
|
|
224
|
-
if (n < 1073741824) return (n / 1048576).toFixed(1) + " MB";
|
|
225
|
-
return (n / 1073741824).toFixed(2) + " GB";
|
|
226
|
-
}
|
|
227
|
-
function bytesToMb(bytes) {
|
|
228
|
-
const n = Number(bytes);
|
|
229
|
-
if (!Number.isFinite(n) || n <= 0) return "";
|
|
230
|
-
return String(Math.round(n / 1048576 * 100) / 100);
|
|
231
|
-
}
|
|
232
|
-
function buildTree(list) {
|
|
233
|
-
const workspaces = /* @__PURE__ */ new Map();
|
|
234
|
-
for (const it of list || []) {
|
|
235
|
-
const rootKey = it.root || "unknown-root";
|
|
236
|
-
if (!workspaces.has(rootKey)) workspaces.set(rootKey, { root: it.root || null, name: it.workspace || "\u672A\u77E5\u5DE5\u4F5C\u533A", sessions: /* @__PURE__ */ new Map() });
|
|
237
|
-
const ws = workspaces.get(rootKey);
|
|
238
|
-
const sidKey = it.sessionId || "unknown-session";
|
|
239
|
-
if (!ws.sessions.has(sidKey)) ws.sessions.set(sidKey, { root: ws.root, sessionId: it.sessionId || null, title: it.sessionTitle || null, items: [] });
|
|
240
|
-
ws.sessions.get(sidKey).items.push(it);
|
|
241
|
-
}
|
|
242
|
-
const wsList = Array.from(workspaces.values()).map((ws) => ({ root: ws.root, name: ws.name, sessions: Array.from(ws.sessions.values()) }));
|
|
243
|
-
wsList.sort((a, b) => (a.name || "").localeCompare(b.name || ""));
|
|
244
|
-
for (const ws of wsList) {
|
|
245
|
-
ws.sessions.sort((a, b) => (a.title || "").localeCompare(b.title || ""));
|
|
246
|
-
for (const s of ws.sessions) s.items.sort((a, b) => (b.time || 0) - (a.time || 0));
|
|
247
|
-
}
|
|
248
|
-
return wsList;
|
|
249
|
-
}
|
|
250
|
-
function useAutoDismissMessage(React, state, setState) {
|
|
251
|
-
React.useEffect(() => {
|
|
252
|
-
if (!state.message || state.error || state.busy) return;
|
|
253
|
-
const timer = setTimeout(() => {
|
|
254
|
-
setState((prev) => prev.message === state.message && !prev.error && !prev.busy ? Object.assign({}, prev, { message: "" }) : prev);
|
|
255
|
-
}, 4e3);
|
|
256
|
-
return () => clearTimeout(timer);
|
|
257
|
-
}, [state.message, state.error, state.busy]);
|
|
258
|
-
}
|
|
259
|
-
function buildUtil() {
|
|
260
|
-
function api(name, args) {
|
|
261
|
-
return fetch("/api/recall/" + name, {
|
|
262
|
-
method: "POST",
|
|
263
|
-
headers: { "content-type": "application/json" },
|
|
264
|
-
body: JSON.stringify(args || {})
|
|
265
|
-
}).then((r) => r.json());
|
|
266
|
-
}
|
|
267
|
-
const CODE_TEXT = {
|
|
268
|
-
STALE: "\u9884\u89C8\u540E\u9879\u76EE\u6587\u4EF6\u53D1\u751F\u4E86\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u9884\u89C8\u786E\u8BA4",
|
|
269
|
-
AGENT_BUSY: "Agent \u6B63\u5728\u8FD0\u884C\u4E2D\uFF0C\u8BF7\u5148\u505C\u6B62\u540E\u518D\u64A4\u56DE",
|
|
270
|
-
NO_SNAPSHOT: "\u8BE5\u6D88\u606F\u6CA1\u6709\u53EF\u7528\u7684\u9879\u76EE\u5FEB\u7167",
|
|
271
|
-
NO_STORE: "\u5FEB\u7167\u5B58\u50A8\u4E0D\u53EF\u7528",
|
|
272
|
-
ROLLBACK_FAILED: "\u56DE\u9000\u5931\u8D25"
|
|
273
|
-
};
|
|
274
|
-
function messageFor(res, fallback) {
|
|
275
|
-
if (!res) return fallback;
|
|
276
|
-
const code = res.code;
|
|
277
|
-
if (code && CODE_TEXT[String(code)]) return CODE_TEXT[String(code)];
|
|
278
|
-
const m = res.message;
|
|
279
|
-
const e = res.error;
|
|
280
|
-
return String(m || e || fallback);
|
|
281
|
-
}
|
|
282
|
-
const noticeShown = /* @__PURE__ */ new Set();
|
|
283
|
-
function mountToast(text) {
|
|
284
|
-
if (typeof document === "undefined") return;
|
|
214
|
+
// src/client/util.ts
|
|
215
|
+
function clockText(ms) {
|
|
285
216
|
try {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
var dismiss = dismiss2;
|
|
294
|
-
const el = document.createElement("div");
|
|
295
|
-
el.className = "dsh-recall-toast";
|
|
296
|
-
const tag = document.createElement("span");
|
|
297
|
-
tag.className = "dsh-recall-toast-tag";
|
|
298
|
-
tag.textContent = "\u64A4\u56DE\u63D2\u4EF6";
|
|
299
|
-
const body = document.createElement("span");
|
|
300
|
-
body.textContent = text;
|
|
301
|
-
el.appendChild(tag);
|
|
302
|
-
el.appendChild(body);
|
|
303
|
-
el.addEventListener("click", () => dismiss2(), { once: true });
|
|
304
|
-
document.body.appendChild(el);
|
|
305
|
-
requestAnimationFrame(() => el.classList.add("dsh-recall-toast-in"));
|
|
306
|
-
const timer = setTimeout(dismiss2, 7e3);
|
|
307
|
-
let dismissed = false;
|
|
217
|
+
if (!ms || isNaN(new Date(ms).getTime())) return "";
|
|
218
|
+
const d = new Date(ms);
|
|
219
|
+
const now = /* @__PURE__ */ new Date();
|
|
220
|
+
const sameDay = d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate();
|
|
221
|
+
const hh = String(d.getHours()).padStart(2, "0");
|
|
222
|
+
const mm = String(d.getMinutes()).padStart(2, "0");
|
|
223
|
+
return sameDay ? hh + ":" + mm : d.getMonth() + 1 + "/" + d.getDate() + " " + hh + ":" + mm;
|
|
308
224
|
} catch (e) {
|
|
225
|
+
return "";
|
|
309
226
|
}
|
|
310
227
|
}
|
|
311
|
-
function
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
228
|
+
function sizeText(bytes) {
|
|
229
|
+
const n = Number(bytes);
|
|
230
|
+
if (!bytes || n <= 0) return "0 MB";
|
|
231
|
+
if (n < 1048576) return (n / 1024).toFixed(0) + " KB";
|
|
232
|
+
if (n < 1073741824) return (n / 1048576).toFixed(1) + " MB";
|
|
233
|
+
return (n / 1073741824).toFixed(2) + " GB";
|
|
315
234
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
if (now - (toastLastShown.get(key) || 0) < 10 * 60 * 1e3) return;
|
|
321
|
-
if (toastLastShown.size > 50) toastLastShown.clear();
|
|
322
|
-
toastLastShown.set(key, now);
|
|
323
|
-
mountToast(text);
|
|
235
|
+
function bytesToMb(bytes) {
|
|
236
|
+
const n = Number(bytes);
|
|
237
|
+
if (!Number.isFinite(n) || n <= 0) return "";
|
|
238
|
+
return String(Math.round(n / 1048576 * 100) / 100);
|
|
324
239
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
showNotice("git", "\u672A\u68C0\u6D4B\u5230 git CLI\uFF0C\u64A4\u56DE\u529F\u80FD\u4E0D\u53EF\u7528\uFF08\u5FEB\u7167\u5F15\u64CE\u4F9D\u8D56 git\uFF09\u3002\u5B89\u88C5 git \u5E76\u91CD\u542F DSH \u540E\u5373\u53EF\u4F7F\u7528\u3002");
|
|
343
|
-
}
|
|
344
|
-
if (notice && notice.homeFallback) {
|
|
345
|
-
showNotice("home", "home \u76EE\u5F55\u4E0D\u53EF\u5199\uFF0C\u5FEB\u7167\u5DF2\u964D\u7EA7\u5B58\u50A8\u5230\u9879\u76EE\u5185 .dsh-recall-snapshots \u76EE\u5F55\u3002");
|
|
346
|
-
}
|
|
347
|
-
}).catch(() => {
|
|
348
|
-
initMap.delete(sessionId);
|
|
349
|
-
});
|
|
350
|
-
initMap.set(sessionId, done);
|
|
351
|
-
return done;
|
|
240
|
+
function buildTree(list) {
|
|
241
|
+
const workspaces = /* @__PURE__ */ new Map();
|
|
242
|
+
for (const it of list || []) {
|
|
243
|
+
const rootKey = it.root || "unknown-root";
|
|
244
|
+
if (!workspaces.has(rootKey)) workspaces.set(rootKey, { root: it.root || null, name: it.workspace || "\u672A\u77E5\u5DE5\u4F5C\u533A", sessions: /* @__PURE__ */ new Map() });
|
|
245
|
+
const ws = workspaces.get(rootKey);
|
|
246
|
+
const sidKey = it.sessionId || "unknown-session";
|
|
247
|
+
if (!ws.sessions.has(sidKey)) ws.sessions.set(sidKey, { root: ws.root, sessionId: it.sessionId || null, title: it.sessionTitle || null, items: [] });
|
|
248
|
+
ws.sessions.get(sidKey).items.push(it);
|
|
249
|
+
}
|
|
250
|
+
const wsList = Array.from(workspaces.values()).map((ws) => ({ root: ws.root, name: ws.name, sessions: Array.from(ws.sessions.values()) }));
|
|
251
|
+
wsList.sort((a, b) => (a.name || "").localeCompare(b.name || ""));
|
|
252
|
+
for (const ws of wsList) {
|
|
253
|
+
ws.sessions.sort((a, b) => (a.title || "").localeCompare(b.title || ""));
|
|
254
|
+
for (const s of ws.sessions) s.items.sort((a, b) => (b.time || 0) - (a.time || 0));
|
|
255
|
+
}
|
|
256
|
+
return wsList;
|
|
352
257
|
}
|
|
353
|
-
function
|
|
354
|
-
|
|
355
|
-
if (
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
258
|
+
function useAutoDismissMessage(React, state, setState) {
|
|
259
|
+
React.useEffect(() => {
|
|
260
|
+
if (!state.message || state.error || state.busy) return;
|
|
261
|
+
const timer = setTimeout(() => {
|
|
262
|
+
setState((prev) => prev.message === state.message && !prev.error && !prev.busy ? Object.assign({}, prev, { message: "" }) : prev);
|
|
263
|
+
}, 4e3);
|
|
264
|
+
return () => clearTimeout(timer);
|
|
265
|
+
}, [state.message, state.error, state.busy]);
|
|
266
|
+
}
|
|
267
|
+
function buildUtil() {
|
|
268
|
+
function api(name, args) {
|
|
269
|
+
return fetch("/api/recall/" + name, {
|
|
270
|
+
method: "POST",
|
|
271
|
+
headers: { "content-type": "application/json" },
|
|
272
|
+
body: JSON.stringify(args || {})
|
|
273
|
+
}).then((r) => r.json());
|
|
359
274
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
275
|
+
const CODE_TEXT = {
|
|
276
|
+
STALE: "\u9884\u89C8\u540E\u9879\u76EE\u6587\u4EF6\u53D1\u751F\u4E86\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u9884\u89C8\u786E\u8BA4",
|
|
277
|
+
AGENT_BUSY: "Agent \u6B63\u5728\u8FD0\u884C\u4E2D\uFF0C\u8BF7\u5148\u505C\u6B62\u540E\u518D\u64A4\u56DE",
|
|
278
|
+
NO_SNAPSHOT: "\u8BE5\u6D88\u606F\u6CA1\u6709\u53EF\u7528\u7684\u9879\u76EE\u5FEB\u7167",
|
|
279
|
+
NO_STORE: "\u5FEB\u7167\u5B58\u50A8\u4E0D\u53EF\u7528",
|
|
280
|
+
ROLLBACK_FAILED: "\u56DE\u9000\u5931\u8D25"
|
|
281
|
+
};
|
|
282
|
+
function messageFor(res, fallback) {
|
|
283
|
+
if (!res) return fallback;
|
|
284
|
+
const code = res.code;
|
|
285
|
+
if (code && CODE_TEXT[String(code)]) return CODE_TEXT[String(code)];
|
|
286
|
+
const m = res.message;
|
|
287
|
+
const e = res.error;
|
|
288
|
+
return String(m || e || fallback);
|
|
289
|
+
}
|
|
290
|
+
const noticeShown = /* @__PURE__ */ new Set();
|
|
291
|
+
function mountToast(text) {
|
|
292
|
+
if (typeof document === "undefined") return;
|
|
293
|
+
try {
|
|
294
|
+
let dismiss2 = function() {
|
|
295
|
+
if (dismissed) return;
|
|
296
|
+
dismissed = true;
|
|
297
|
+
clearTimeout(timer);
|
|
298
|
+
el.classList.remove("dsh-recall-toast-in");
|
|
299
|
+
setTimeout(() => el.remove(), 300);
|
|
300
|
+
};
|
|
301
|
+
var dismiss = dismiss2;
|
|
302
|
+
const el = document.createElement("div");
|
|
303
|
+
el.className = "dsh-recall-toast";
|
|
304
|
+
const tag = document.createElement("span");
|
|
305
|
+
tag.className = "dsh-recall-toast-tag";
|
|
306
|
+
tag.textContent = "\u64A4\u56DE\u63D2\u4EF6";
|
|
307
|
+
const body = document.createElement("span");
|
|
308
|
+
body.textContent = text;
|
|
309
|
+
el.appendChild(tag);
|
|
310
|
+
el.appendChild(body);
|
|
311
|
+
el.addEventListener("click", () => dismiss2(), { once: true });
|
|
367
312
|
document.body.appendChild(el);
|
|
368
|
-
el.
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
313
|
+
requestAnimationFrame(() => el.classList.add("dsh-recall-toast-in"));
|
|
314
|
+
const timer = setTimeout(dismiss2, 7e3);
|
|
315
|
+
let dismissed = false;
|
|
316
|
+
} catch (e) {
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function showNotice(kind, text) {
|
|
320
|
+
if (noticeShown.has(kind)) return;
|
|
321
|
+
noticeShown.add(kind);
|
|
322
|
+
mountToast(text);
|
|
323
|
+
}
|
|
324
|
+
const toastLastShown = /* @__PURE__ */ new Map();
|
|
325
|
+
function showThrottledToast(text) {
|
|
326
|
+
const key = String(text).slice(0, 80);
|
|
327
|
+
const now = Date.now();
|
|
328
|
+
if (now - (toastLastShown.get(key) || 0) < 10 * 60 * 1e3) return;
|
|
329
|
+
if (toastLastShown.size > 50) toastLastShown.clear();
|
|
330
|
+
toastLastShown.set(key, now);
|
|
331
|
+
mountToast(text);
|
|
332
|
+
}
|
|
333
|
+
const pluginConfig = { refillDraft: true, archiveOriginal: true };
|
|
334
|
+
const initMap = /* @__PURE__ */ new Map();
|
|
335
|
+
function ensureInit(sessionId) {
|
|
336
|
+
if (!sessionId) return Promise.resolve();
|
|
337
|
+
const cached = initMap.get(sessionId);
|
|
338
|
+
if (cached) return cached;
|
|
339
|
+
const done = api("init", { sessionId }).then((res) => {
|
|
340
|
+
if (res && res.config && typeof res.config === "object") {
|
|
341
|
+
const cfg = res.config;
|
|
342
|
+
if (typeof cfg.refillDraft === "boolean") pluginConfig.refillDraft = cfg.refillDraft;
|
|
343
|
+
if (typeof cfg.archiveOriginal === "boolean") pluginConfig.archiveOriginal = cfg.archiveOriginal;
|
|
344
|
+
}
|
|
345
|
+
const notice = res && res.notice;
|
|
346
|
+
if (notice && notice.unsupported) {
|
|
347
|
+
showNotice("unsupported", "\u64A4\u56DE\u63D2\u4EF6\u4EC5\u652F\u6301 Windows / Linux / macOS\uFF0C\u5F53\u524D\u5E73\u53F0\u7684\u5FEB\u7167\u4E0D\u53EF\u7528\u3002");
|
|
348
|
+
}
|
|
349
|
+
if (notice && notice.gitMissing) {
|
|
350
|
+
showNotice("git", "\u672A\u68C0\u6D4B\u5230 git CLI\uFF0C\u64A4\u56DE\u529F\u80FD\u4E0D\u53EF\u7528\uFF08\u5FEB\u7167\u5F15\u64CE\u4F9D\u8D56 git\uFF09\u3002\u5B89\u88C5 git \u5E76\u91CD\u542F DSH \u540E\u5373\u53EF\u4F7F\u7528\u3002");
|
|
351
|
+
}
|
|
352
|
+
if (notice && notice.homeFallback) {
|
|
353
|
+
showNotice("home", "home \u76EE\u5F55\u4E0D\u53EF\u5199\uFF0C\u5FEB\u7167\u5DF2\u964D\u7EA7\u5B58\u50A8\u5230\u9879\u76EE\u5185 .dsh-recall-snapshots \u76EE\u5F55\u3002");
|
|
373
354
|
}
|
|
355
|
+
}).catch(() => {
|
|
356
|
+
initMap.delete(sessionId);
|
|
357
|
+
});
|
|
358
|
+
initMap.set(sessionId, done);
|
|
359
|
+
return done;
|
|
360
|
+
}
|
|
361
|
+
function writeClipboard(text) {
|
|
362
|
+
try {
|
|
363
|
+
if (navigator && navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
|
|
364
|
+
return navigator.clipboard.writeText(text).then(() => true, () => false);
|
|
365
|
+
}
|
|
366
|
+
} catch (e) {
|
|
374
367
|
}
|
|
375
|
-
|
|
368
|
+
try {
|
|
369
|
+
if (typeof document !== "undefined" && typeof document.execCommand === "function") {
|
|
370
|
+
const el = document.createElement("textarea");
|
|
371
|
+
el.value = text;
|
|
372
|
+
el.setAttribute("readonly", "");
|
|
373
|
+
el.style.position = "fixed";
|
|
374
|
+
el.style.left = "-9999px";
|
|
375
|
+
document.body.appendChild(el);
|
|
376
|
+
el.select();
|
|
377
|
+
try {
|
|
378
|
+
return Promise.resolve(document.execCommand("copy"));
|
|
379
|
+
} finally {
|
|
380
|
+
el.remove();
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
} catch (e) {
|
|
384
|
+
}
|
|
385
|
+
return Promise.resolve(false);
|
|
376
386
|
}
|
|
377
|
-
return
|
|
387
|
+
return { api, messageFor, showNotice, showThrottledToast, ensureInit, clockText, writeClipboard, sizeText, bytesToMb, buildTree, pluginConfig };
|
|
378
388
|
}
|
|
379
|
-
return { api, messageFor, showNotice, showThrottledToast, ensureInit, clockText, writeClipboard, sizeText, bytesToMb, buildTree, pluginConfig };
|
|
380
|
-
}
|
|
381
389
|
|
|
382
|
-
// src/client/recall-node.ts
|
|
383
|
-
function attachmentRefsFromBlocks(blocks) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
390
|
+
// src/client/recall-node.ts
|
|
391
|
+
function attachmentRefsFromBlocks(blocks) {
|
|
392
|
+
const refs = [];
|
|
393
|
+
for (const block of blocks) {
|
|
394
|
+
if (!block || block.type !== "image" && block.type !== "file") continue;
|
|
395
|
+
const ref = block.attachment;
|
|
396
|
+
if (!ref || typeof ref.attachmentId !== "string" || ref.attachmentId === "") continue;
|
|
397
|
+
const entry = { attachmentId: ref.attachmentId };
|
|
398
|
+
if (typeof ref.name === "string" && ref.name !== "") entry.name = ref.name;
|
|
399
|
+
refs.push(entry);
|
|
400
|
+
}
|
|
401
|
+
return refs;
|
|
402
|
+
}
|
|
403
|
+
function defaultAttachmentName(mediaType, index) {
|
|
404
|
+
const text = String(mediaType || "");
|
|
405
|
+
const slash = text.indexOf("/");
|
|
406
|
+
const sub = (slash >= 0 ? text.slice(slash + 1) : text).replace(/[^a-z0-9.+-]/gi, "");
|
|
407
|
+
return "attachment-" + String(index + 1) + "." + (sub || "bin");
|
|
392
408
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
const sub = (slash >= 0 ? text.slice(slash + 1) : text).replace(/[^a-z0-9.+-]/gi, "");
|
|
399
|
-
return "attachment-" + String(index + 1) + "." + (sub || "bin");
|
|
400
|
-
}
|
|
401
|
-
var KIND_INFO = {
|
|
402
|
-
modified: { label: "\u4FEE\u6539", cls: "modified" },
|
|
403
|
-
restored: { label: "\u6062\u590D", cls: "restored" },
|
|
404
|
-
added: { label: "\u5220\u9664", cls: "added" }
|
|
405
|
-
};
|
|
406
|
-
function summaryText(counts) {
|
|
407
|
-
const parts = [];
|
|
408
|
-
for (const kind of Object.keys(KIND_INFO)) {
|
|
409
|
-
if (counts[kind] > 0) parts.push(KIND_INFO[kind].label + " " + counts[kind]);
|
|
409
|
+
function fileExtOf(name) {
|
|
410
|
+
const dot = name.lastIndexOf(".");
|
|
411
|
+
const raw = dot > 0 && dot < name.length - 1 ? name.slice(dot + 1) : "";
|
|
412
|
+
const ext = raw.replace(/[^a-z0-9]/gi, "").toUpperCase();
|
|
413
|
+
return ext ? ext.slice(0, 4) : "FILE";
|
|
410
414
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
return
|
|
417
|
-
|
|
418
|
-
{ width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.4, "aria-hidden": true },
|
|
419
|
-
React.createElement("rect", { x: 5.5, y: 5.5, width: 8, height: 8, rx: 1.5 }),
|
|
420
|
-
React.createElement("path", { d: "M10.5 5.5V4a1.5 1.5 0 0 0-1.5-1.5H4A1.5 1.5 0 0 0 2.5 4v5A1.5 1.5 0 0 0 4 10.5h1.5" })
|
|
421
|
-
);
|
|
415
|
+
function fileCardSizeText(bytes) {
|
|
416
|
+
const n = typeof bytes === "number" && Number.isFinite(bytes) && bytes >= 0 ? bytes : NaN;
|
|
417
|
+
if (Number.isNaN(n)) return "";
|
|
418
|
+
if (n < 1024) return String(Math.round(n)) + "B";
|
|
419
|
+
if (n < 1048576) return (n / 1024).toFixed(1) + "KB";
|
|
420
|
+
if (n < 1073741824) return (n / 1048576).toFixed(1) + "MB";
|
|
421
|
+
return (n / 1073741824).toFixed(1) + "GB";
|
|
422
422
|
}
|
|
423
|
-
function
|
|
424
|
-
return
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
);
|
|
423
|
+
function fileCardInfo(block) {
|
|
424
|
+
if (!block || block.type !== "file") return null;
|
|
425
|
+
const ref = block.attachment;
|
|
426
|
+
const name = ref && typeof ref === "object" && typeof ref.name === "string" && ref.name !== "" ? ref.name : "\u672A\u547D\u540D\u6587\u4EF6";
|
|
427
|
+
return { name, ext: fileExtOf(name), size: fileCardSizeText(ref && typeof ref === "object" ? ref.bytes : void 0) };
|
|
429
428
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
429
|
+
var KIND_INFO = {
|
|
430
|
+
modified: { label: "\u4FEE\u6539", cls: "modified" },
|
|
431
|
+
restored: { label: "\u6062\u590D", cls: "restored" },
|
|
432
|
+
added: { label: "\u5220\u9664", cls: "added" }
|
|
433
|
+
};
|
|
434
|
+
function summaryText(counts) {
|
|
435
|
+
const parts = [];
|
|
436
|
+
for (const kind of Object.keys(KIND_INFO)) {
|
|
437
|
+
if (counts[kind] > 0) parts.push(KIND_INFO[kind].label + " " + counts[kind]);
|
|
438
|
+
}
|
|
439
|
+
return parts.join(" \xB7 ");
|
|
437
440
|
}
|
|
438
|
-
function
|
|
439
|
-
|
|
441
|
+
function buildRecallNode(React, util, ctx, sessionsSvc, workspacesSvc) {
|
|
442
|
+
const { api, ensureInit, showThrottledToast, writeClipboard, clockText: clockText2, pluginConfig, messageFor } = util;
|
|
443
|
+
function CopyIcon() {
|
|
440
444
|
return React.createElement(
|
|
441
|
-
"
|
|
442
|
-
{
|
|
443
|
-
React.createElement("
|
|
445
|
+
"svg",
|
|
446
|
+
{ width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.4, "aria-hidden": true },
|
|
447
|
+
React.createElement("rect", { x: 5.5, y: 5.5, width: 8, height: 8, rx: 1.5 }),
|
|
448
|
+
React.createElement("path", { d: "M10.5 5.5V4a1.5 1.5 0 0 0-1.5-1.5H4A1.5 1.5 0 0 0 2.5 4v5A1.5 1.5 0 0 0 4 10.5h1.5" })
|
|
444
449
|
);
|
|
445
450
|
}
|
|
446
|
-
|
|
451
|
+
function CheckIcon() {
|
|
447
452
|
return React.createElement(
|
|
448
|
-
"
|
|
449
|
-
{
|
|
450
|
-
React.createElement("
|
|
451
|
-
React.createElement("div", { className: "dsh-recall-panel-note" }, recall.message || ""),
|
|
452
|
-
React.createElement(
|
|
453
|
-
"div",
|
|
454
|
-
{ className: "dsh-recall-panel-actions" },
|
|
455
|
-
React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: closePanel }, "\u5173\u95ED")
|
|
456
|
-
)
|
|
453
|
+
"svg",
|
|
454
|
+
{ width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true },
|
|
455
|
+
React.createElement("path", { d: "m3 8.5 3.2 3.2L13 5" })
|
|
457
456
|
);
|
|
458
457
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
458
|
+
function UndoIcon() {
|
|
459
|
+
return React.createElement(
|
|
460
|
+
"svg",
|
|
461
|
+
{ width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.4, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true },
|
|
462
|
+
React.createElement("path", { d: "M7.5 3.5 3.5 7.5l4 4" }),
|
|
463
|
+
React.createElement("path", { d: "M4.5 7.5h5a3 3 0 0 1 0 6H8" })
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
function recallPanel(recall, closePanel, executeRecall) {
|
|
467
|
+
if (recall.stage === "loading") {
|
|
468
468
|
return React.createElement(
|
|
469
469
|
"div",
|
|
470
|
-
{ className: "dsh-recall-
|
|
471
|
-
React.createElement("
|
|
472
|
-
React.createElement("span", { className: "dsh-recall-rel" }, c.rel || "")
|
|
470
|
+
{ className: "dsh-recall-panel" },
|
|
471
|
+
React.createElement("div", { className: "dsh-recall-panel-title" }, "\u6B63\u5728\u8BA1\u7B97\u53D8\u66F4\u2026")
|
|
473
472
|
);
|
|
474
|
-
});
|
|
475
|
-
if (recall.truncated) {
|
|
476
|
-
rows.push(React.createElement("div", { className: "dsh-recall-panel-note", key: "truncated" }, "\u2026\u4EC5\u663E\u793A\u524D " + changes.length + " \u6761\uFF0C\u5171 " + total + " \u4E2A\u6587\u4EF6\u5C06\u53D8\u66F4"));
|
|
477
473
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
"div",
|
|
481
|
-
{ className: "dsh-recall-panel" },
|
|
482
|
-
React.createElement("div", { className: "dsh-recall-panel-title" }, "\u6574\u6BB5\u56DE\u9000"),
|
|
483
|
-
React.createElement(
|
|
484
|
-
"div",
|
|
485
|
-
{ className: "dsh-recall-panel-note" },
|
|
486
|
-
"\u5C06\u9879\u76EE\u6062\u590D\u5230" + (recall.time ? " " + clockText2(recall.time) + " " : " ") + "\u53D1\u9001\u8BE5\u6D88\u606F\u65F6\u7684\u72B6\u6001\u3002\u5171 " + total + " \u4E2A\u6587\u4EF6\u5C06\u53D8\u66F4" + (summaryText(counts) ? "\uFF08" + summaryText(counts) + "\uFF09" : "") + "\u3002\u6B64\u64CD\u4F5C\u4F1A\u8986\u76D6\u5F53\u524D\u6587\u4EF6\u5185\u5BB9\uFF1B\u56DE\u9000\u524D\u4F1A\u81EA\u52A8\u4FDD\u5B58\u4E00\u4EFD\u5F53\u524D\u72B6\u6001\u7684\u5B89\u5168\u5FEB\u7167\uFF08\u4E0D\u542B\u5728\u4E0B\u65B9\u6E05\u5355\u5185\uFF09\u3002"
|
|
487
|
-
),
|
|
488
|
-
React.createElement(
|
|
474
|
+
if (recall.stage === "error") {
|
|
475
|
+
return React.createElement(
|
|
489
476
|
"div",
|
|
490
|
-
{ className: "dsh-recall-panel
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
477
|
+
{ className: "dsh-recall-panel" },
|
|
478
|
+
React.createElement("div", { className: "dsh-recall-panel-title" }, "\u65E0\u6CD5\u56DE\u9000"),
|
|
479
|
+
React.createElement("div", { className: "dsh-recall-panel-note" }, recall.message || ""),
|
|
480
|
+
React.createElement(
|
|
481
|
+
"div",
|
|
482
|
+
{ className: "dsh-recall-panel-actions" },
|
|
483
|
+
React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: closePanel }, "\u5173\u95ED")
|
|
484
|
+
)
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
if (recall.stage === "confirm") {
|
|
488
|
+
const changes = recall.changes || [];
|
|
489
|
+
const total = typeof recall.total === "number" ? recall.total : changes.length;
|
|
490
|
+
const counts = { modified: 0, restored: 0, added: 0 };
|
|
491
|
+
for (const c of changes) {
|
|
492
|
+
if (c && counts[c.kind] !== void 0) counts[c.kind]++;
|
|
493
|
+
}
|
|
494
|
+
const rows = changes.map((c, i) => {
|
|
495
|
+
const info = KIND_INFO[c.kind];
|
|
496
|
+
return React.createElement(
|
|
497
|
+
"div",
|
|
498
|
+
{ className: "dsh-recall-file", key: i },
|
|
499
|
+
React.createElement("span", { className: "dsh-recall-badge dsh-recall-badge-" + (info ? info.cls : "") }, info ? info.label : c.kind || ""),
|
|
500
|
+
React.createElement("span", { className: "dsh-recall-rel" }, c.rel || "")
|
|
501
|
+
);
|
|
502
|
+
});
|
|
503
|
+
if (recall.truncated) {
|
|
504
|
+
rows.push(React.createElement("div", { className: "dsh-recall-panel-note", key: "truncated" }, "\u2026\u4EC5\u663E\u793A\u524D " + changes.length + " \u6761\uFF0C\u5171 " + total + " \u4E2A\u6587\u4EF6\u5C06\u53D8\u66F4"));
|
|
505
|
+
}
|
|
506
|
+
const canRevertChat = typeof recall.cutSeq === "number";
|
|
507
|
+
return React.createElement(
|
|
495
508
|
"div",
|
|
496
|
-
{ className: "dsh-recall-panel
|
|
497
|
-
React.createElement("
|
|
498
|
-
React.createElement(
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
509
|
+
{ className: "dsh-recall-panel" },
|
|
510
|
+
React.createElement("div", { className: "dsh-recall-panel-title" }, "\u6574\u6BB5\u56DE\u9000"),
|
|
511
|
+
React.createElement(
|
|
512
|
+
"div",
|
|
513
|
+
{ className: "dsh-recall-panel-note" },
|
|
514
|
+
"\u5C06\u9879\u76EE\u6062\u590D\u5230" + (recall.time ? " " + clockText2(recall.time) + " " : " ") + "\u53D1\u9001\u8BE5\u6D88\u606F\u65F6\u7684\u72B6\u6001\u3002\u5171 " + total + " \u4E2A\u6587\u4EF6\u5C06\u53D8\u66F4" + (summaryText(counts) ? "\uFF08" + summaryText(counts) + "\uFF09" : "") + "\u3002\u6B64\u64CD\u4F5C\u4F1A\u8986\u76D6\u5F53\u524D\u6587\u4EF6\u5185\u5BB9\uFF1B\u56DE\u9000\u524D\u4F1A\u81EA\u52A8\u4FDD\u5B58\u4E00\u4EFD\u5F53\u524D\u72B6\u6001\u7684\u5B89\u5168\u5FEB\u7167\uFF08\u4E0D\u542B\u5728\u4E0B\u65B9\u6E05\u5355\u5185\uFF09\u3002"
|
|
515
|
+
),
|
|
516
|
+
React.createElement(
|
|
517
|
+
"div",
|
|
518
|
+
{ className: "dsh-recall-panel-note" },
|
|
519
|
+
canRevertChat ? "\u5BF9\u8BDD\u5C06\u4E00\u5E76\u56DE\u9000\u5230\u8BE5\u6D88\u606F\u4E4B\u524D\uFF1A\u8BE5\u6D88\u606F\u53CA\u4E4B\u540E\u7684\u5168\u90E8\u5BF9\u8BDD\u4F1A\u4ECE\u5F53\u524D\u89C6\u56FE\u79FB\u9664\uFF0C\u539F\u4F1A\u8BDD\u5F52\u6863\u4FDD\u5B58\uFF08\u53EF\u4ECE\u5F52\u6863\u627E\u56DE\uFF09\u3002" : "\u8BE5\u6D88\u606F\u662F\u672C\u4F1A\u8BDD\u4E2D\u7B2C\u4E00\u6761\u7528\u6237\u6D88\u606F\uFF0C\u65E0\u6CD5\u56DE\u9000\u5BF9\u8BDD\uFF1B\u786E\u8BA4\u540E\u4EC5\u56DE\u9000\u9879\u76EE\u6587\u4EF6\u3002"
|
|
520
|
+
),
|
|
521
|
+
changes.length > 0 ? React.createElement("div", { className: "dsh-recall-list" }, ...rows) : null,
|
|
522
|
+
React.createElement(
|
|
523
|
+
"div",
|
|
524
|
+
{ className: "dsh-recall-panel-actions" },
|
|
525
|
+
React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: closePanel }, "\u53D6\u6D88"),
|
|
526
|
+
React.createElement("button", { type: "button", className: "dsh-recall-btn dsh-recall-btn-danger", onClick: executeRecall }, "\u786E\u8BA4\u56DE\u9000")
|
|
527
|
+
)
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
if (recall.stage === "executing") {
|
|
531
|
+
return React.createElement(
|
|
515
532
|
"div",
|
|
516
|
-
{ className: "dsh-recall-panel
|
|
517
|
-
|
|
518
|
-
)
|
|
519
|
-
|
|
533
|
+
{ className: "dsh-recall-panel" },
|
|
534
|
+
React.createElement("div", { className: "dsh-recall-panel-title" }, "\u6B63\u5728\u56DE\u9000\u2026")
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
if (recall.stage === "done") {
|
|
538
|
+
return React.createElement(
|
|
520
539
|
"div",
|
|
521
|
-
{ className: "dsh-recall-panel
|
|
522
|
-
React.createElement("
|
|
523
|
-
|
|
524
|
-
|
|
540
|
+
{ className: "dsh-recall-panel" },
|
|
541
|
+
React.createElement("div", { className: "dsh-recall-panel-title" }, "\u56DE\u9000\u5B8C\u6210"),
|
|
542
|
+
React.createElement(
|
|
543
|
+
"div",
|
|
544
|
+
{ className: "dsh-recall-panel-note" },
|
|
545
|
+
recall.chatReverted ? "\u9879\u76EE\u6587\u4EF6\u4E0E\u5BF9\u8BDD\u5DF2\u56DE\u9000\u5230\u8BE5\u6D88\u606F\u4E4B\u524D\u3002\u65B0\u4F1A\u8BDD\u5DF2\u6253\u5F00\uFF0C\u539F\u4F1A\u8BDD\u5DF2\u5F52\u6863\uFF08\u53EF\u4ECE\u5F52\u6863\u627E\u56DE\uFF09\u3002" : "\u9879\u76EE\u5DF2\u6062\u590D\u5230\u53D1\u9001\u8BE5\u6D88\u606F\u65F6\u7684\u72B6\u6001\u3002" + (recall.chatError ? " \u5BF9\u8BDD\u56DE\u9000\u5931\u8D25\uFF1A" + recall.chatError : "")
|
|
546
|
+
),
|
|
547
|
+
React.createElement(
|
|
548
|
+
"div",
|
|
549
|
+
{ className: "dsh-recall-panel-actions" },
|
|
550
|
+
React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: closePanel }, "\u5173\u95ED")
|
|
551
|
+
)
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
return null;
|
|
525
555
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
shell.
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
556
|
+
function fillDraft(targetSessionId, draftText, attachmentFiles) {
|
|
557
|
+
if (!targetSessionId || !draftText && attachmentFiles === null) return;
|
|
558
|
+
let attempts = 0;
|
|
559
|
+
let attachStarted = false;
|
|
560
|
+
const attempt = () => {
|
|
561
|
+
let textDone = draftText === "";
|
|
562
|
+
try {
|
|
563
|
+
const conversation = ctx.get("conversation");
|
|
564
|
+
if (conversation && conversation.input && typeof conversation.input.shell === "function") {
|
|
565
|
+
const shell = conversation.input.shell(targetSessionId);
|
|
566
|
+
if (shell) {
|
|
567
|
+
if (!textDone) {
|
|
568
|
+
if (shell.actions && typeof shell.actions.setDraft === "function") {
|
|
569
|
+
shell.actions.setDraft(draftText);
|
|
570
|
+
textDone = true;
|
|
571
|
+
} else if (typeof shell.setDraft === "function") {
|
|
572
|
+
shell.setDraft(draftText);
|
|
573
|
+
textDone = true;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (!attachStarted && attachmentFiles !== null) {
|
|
577
|
+
attachStarted = startAttachDrafts(conversation, shell, targetSessionId, attachmentFiles);
|
|
546
578
|
}
|
|
547
|
-
}
|
|
548
|
-
if (!attachStarted && attachmentFiles !== null) {
|
|
549
|
-
attachStarted = startAttachDrafts(conversation, shell, targetSessionId, attachmentFiles);
|
|
550
579
|
}
|
|
551
580
|
}
|
|
581
|
+
} catch (e) {
|
|
552
582
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
(async () => {
|
|
568
|
-
let list = [];
|
|
569
|
-
try {
|
|
570
|
-
list = await files;
|
|
571
|
-
} catch (e) {
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
if (!Array.isArray(list) || list.length === 0) return;
|
|
575
|
-
try {
|
|
576
|
-
const drafts = conversation.createDrafts ? conversation.createDrafts(sessionId, list) : [];
|
|
577
|
-
const descriptors = Array.isArray(drafts) ? drafts : [];
|
|
578
|
-
const ids = descriptors.map((draft) => draft && draft.id).filter((id) => typeof id === "string" && id !== "");
|
|
579
|
-
if (ids.length === 0) return;
|
|
580
|
-
const accepted = add(ids);
|
|
581
|
-
if (accepted === false && typeof conversation.releaseDraftAttachments === "function") conversation.releaseDraftAttachments(descriptors);
|
|
582
|
-
} catch (e) {
|
|
583
|
-
}
|
|
584
|
-
})();
|
|
585
|
-
return true;
|
|
586
|
-
}
|
|
587
|
-
function preloadAttachmentFiles(sourceSessionId, refs) {
|
|
588
|
-
return (async () => {
|
|
589
|
-
const sessions = ctx.sessions;
|
|
590
|
-
const binding = sessions && typeof sessions.binding === "function" ? sessions.binding(sourceSessionId) : void 0;
|
|
591
|
-
const session = binding && binding.session;
|
|
592
|
-
if (!session || typeof session.readAttachment !== "function") return [];
|
|
593
|
-
const out = [];
|
|
594
|
-
for (const ref of refs) {
|
|
583
|
+
const attachDone = attachmentFiles === null || attachStarted;
|
|
584
|
+
if ((!textDone || !attachDone) && attempts++ < 8) setTimeout(attempt, 150);
|
|
585
|
+
};
|
|
586
|
+
attempt();
|
|
587
|
+
}
|
|
588
|
+
function startAttachDrafts(conversation, shell, sessionId, files) {
|
|
589
|
+
if (typeof conversation.createDrafts !== "function") return false;
|
|
590
|
+
const actions = shell.actions;
|
|
591
|
+
let add = null;
|
|
592
|
+
if (actions && typeof actions.addAttachments === "function") add = (ids) => actions.addAttachments ? actions.addAttachments(ids) : false;
|
|
593
|
+
else if (typeof shell.addAttachments === "function") add = (ids) => shell.addAttachments ? shell.addAttachments(ids) : false;
|
|
594
|
+
if (add === null) return false;
|
|
595
|
+
(async () => {
|
|
596
|
+
let list = [];
|
|
595
597
|
try {
|
|
596
|
-
|
|
597
|
-
if (!result || result.ok !== true || !result.value || result.value.data === void 0 || result.value.data === null) continue;
|
|
598
|
-
const raw = result.value.data;
|
|
599
|
-
const bytes = raw instanceof Uint8Array ? raw : Uint8Array.from(raw);
|
|
600
|
-
const meta = result.value.attachment;
|
|
601
|
-
const mediaType = meta && typeof meta.mediaType === "string" && meta.mediaType !== "" ? meta.mediaType : "application/octet-stream";
|
|
602
|
-
const name = ref.name || (meta && typeof meta.name === "string" && meta.name !== "" ? meta.name : defaultAttachmentName(mediaType, out.length));
|
|
603
|
-
const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
604
|
-
out.push(new File([buffer], name, { type: mediaType }));
|
|
598
|
+
list = await files;
|
|
605
599
|
} catch (e) {
|
|
600
|
+
return;
|
|
606
601
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
if (res && res.has) {
|
|
640
|
-
if (recent && Array.isArray(res.skipped) && res.skipped.length) {
|
|
641
|
-
const names = res.skipped.slice(0, 5).join("\u3001") + (res.skipped.length > 5 ? " \u7B49 " + res.skipped.length + " \u9879" : "");
|
|
642
|
-
showThrottledToast("\u5FEB\u7167\u5DF2\u8DF3\u8FC7\u672A\u7EB3\u5165\u7684\u8DEF\u5F84\uFF1A" + names + "\uFF08\u64A4\u56DE\u4E0D\u4F1A\u6062\u590D\u6216\u5220\u9664\u8FD9\u4E9B\u8DEF\u5F84\uFF09");
|
|
643
|
-
}
|
|
644
|
-
setHasSnapshot(true);
|
|
645
|
-
return;
|
|
602
|
+
if (!Array.isArray(list) || list.length === 0) return;
|
|
603
|
+
try {
|
|
604
|
+
const drafts = conversation.createDrafts ? conversation.createDrafts(sessionId, list) : [];
|
|
605
|
+
const descriptors = Array.isArray(drafts) ? drafts : [];
|
|
606
|
+
const ids = descriptors.map((draft) => draft && draft.id).filter((id) => typeof id === "string" && id !== "");
|
|
607
|
+
if (ids.length === 0) return;
|
|
608
|
+
const accepted = add(ids);
|
|
609
|
+
if (accepted === false && typeof conversation.releaseDraftAttachments === "function") conversation.releaseDraftAttachments(descriptors);
|
|
610
|
+
} catch (e) {
|
|
611
|
+
}
|
|
612
|
+
})();
|
|
613
|
+
return true;
|
|
614
|
+
}
|
|
615
|
+
function preloadAttachmentFiles(sourceSessionId, refs) {
|
|
616
|
+
return (async () => {
|
|
617
|
+
const sessions = ctx.sessions;
|
|
618
|
+
const binding = sessions && typeof sessions.binding === "function" ? sessions.binding(sourceSessionId) : void 0;
|
|
619
|
+
const session = binding && binding.session;
|
|
620
|
+
if (!session || typeof session.readAttachment !== "function") return [];
|
|
621
|
+
const out = [];
|
|
622
|
+
for (const ref of refs) {
|
|
623
|
+
try {
|
|
624
|
+
const result = await session.readAttachment(ref.attachmentId);
|
|
625
|
+
if (!result || result.ok !== true || !result.value || result.value.data === void 0 || result.value.data === null) continue;
|
|
626
|
+
const raw = result.value.data;
|
|
627
|
+
const bytes = raw instanceof Uint8Array ? raw : Uint8Array.from(raw);
|
|
628
|
+
const meta = result.value.attachment;
|
|
629
|
+
const mediaType = meta && typeof meta.mediaType === "string" && meta.mediaType !== "" ? meta.mediaType : "application/octet-stream";
|
|
630
|
+
const name = ref.name || (meta && typeof meta.name === "string" && meta.name !== "" ? meta.name : defaultAttachmentName(mediaType, out.length));
|
|
631
|
+
const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
632
|
+
out.push(new File([buffer], name, { type: mediaType }));
|
|
633
|
+
} catch (e) {
|
|
646
634
|
}
|
|
647
|
-
|
|
648
|
-
|
|
635
|
+
}
|
|
636
|
+
return out;
|
|
637
|
+
})();
|
|
638
|
+
}
|
|
639
|
+
function removeStaleQueueItems(targetSessionId, itemIds) {
|
|
640
|
+
if (!Array.isArray(itemIds) || itemIds.length === 0) return;
|
|
641
|
+
const deadline = Date.now() + 5e3;
|
|
642
|
+
const attempt = () => {
|
|
643
|
+
try {
|
|
644
|
+
const binding = sessionsSvc && typeof sessionsSvc.binding === "function" ? sessionsSvc.binding(targetSessionId) : null;
|
|
645
|
+
const face = binding && binding.session;
|
|
646
|
+
if (face && typeof face.updateQueue === "function") {
|
|
647
|
+
for (const itemId of itemIds) {
|
|
648
|
+
if (typeof itemId !== "string" || itemId === "") continue;
|
|
649
|
+
face.updateQueue(itemId, { kind: "remove" }).catch(() => {
|
|
650
|
+
});
|
|
651
|
+
}
|
|
649
652
|
return;
|
|
650
653
|
}
|
|
651
|
-
|
|
652
|
-
}).catch(() => {
|
|
653
|
-
if (alive && recent && attempts < MAX_ATTEMPTS) timer = setTimeout(schedule, RETRY_MS);
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
ensureInit(sessionId).then(() => {
|
|
657
|
-
if (!messageId || !alive) return;
|
|
658
|
-
schedule();
|
|
659
|
-
}).catch(() => {
|
|
660
|
-
if (alive && messageId) timer = setTimeout(schedule, RETRY_MS);
|
|
661
|
-
});
|
|
662
|
-
return () => {
|
|
663
|
-
alive = false;
|
|
664
|
-
if (timer !== null) clearTimeout(timer);
|
|
665
|
-
};
|
|
666
|
-
}, [messageId, sessionId]);
|
|
667
|
-
const onCopy = () => {
|
|
668
|
-
if (copied) return;
|
|
669
|
-
writeClipboard(text).then(() => {
|
|
670
|
-
setCopied(true);
|
|
671
|
-
const timer = ctx.timer;
|
|
672
|
-
if (timer && typeof timer.timeout === "function") {
|
|
673
|
-
timer.timeout(() => setCopied(false), 1200);
|
|
674
|
-
} else {
|
|
675
|
-
setTimeout(() => setCopied(false), 1200);
|
|
654
|
+
} catch (error) {
|
|
676
655
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
if (recall.stage === "loading" || recall.stage === "executing") return;
|
|
681
|
-
setRecall({ stage: "loading" });
|
|
682
|
-
api("preview", { messageId, sessionId }).then((res) => {
|
|
683
|
-
if (!res || !res.ok) {
|
|
684
|
-
setRecall({ stage: "error", message: messageFor(res, "\u65E0\u6CD5\u83B7\u53D6\u5FEB\u7167") });
|
|
656
|
+
if (Date.now() >= deadline) {
|
|
657
|
+
console.warn("[dsh-recall-plugin] \u6B8B\u7559\u6392\u961F\u6D88\u606F\u672A\u80FD\u81EA\u52A8\u6E05\u7406\uFF08\u4F1A\u8BDD\u9762\u672A\u5C31\u7EEA\uFF09\uFF1A", itemIds);
|
|
658
|
+
showThrottledToast("\u64A4\u56DE\u524D\u7684\u4E00\u6761\u6392\u961F\u6D88\u606F\u672A\u88AB\u81EA\u52A8\u6E05\u7406\uFF0C\u53EF\u70B9\u51FB\u8BE5\u5361\u7247\u53F3\u4E0A\u89D2\u7684\u5220\u9664\u6309\u94AE\u624B\u52A8\u79FB\u9664");
|
|
685
659
|
return;
|
|
686
660
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
const
|
|
703
|
-
const
|
|
704
|
-
const
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
661
|
+
setTimeout(attempt, 250);
|
|
662
|
+
};
|
|
663
|
+
attempt();
|
|
664
|
+
}
|
|
665
|
+
function UserRecallNode(props) {
|
|
666
|
+
const node = props && props.node;
|
|
667
|
+
const renderMessageImages = props && props.renderMessageImages;
|
|
668
|
+
const sessionId = props && props.sessionId;
|
|
669
|
+
const data = node && node.data ? node.data : {};
|
|
670
|
+
const messageId = node ? String(node.id || node.key || "") : "";
|
|
671
|
+
const blocks = Array.isArray(data.content) ? data.content : [];
|
|
672
|
+
const text = blocks.filter((b) => b && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("");
|
|
673
|
+
const imageBlocks = blocks.filter((b) => b && b.type === "image" && b.attachment).map((b) => ({ attachment: b.attachment }));
|
|
674
|
+
const fileBlocks = blocks.filter((b) => b && b.type === "file");
|
|
675
|
+
const rest = blocks.filter((b) => !b || !(b.type === "text" && typeof b.text === "string") && !(b.type === "image" && b.attachment) && b.type !== "file");
|
|
676
|
+
const attachmentRefs = attachmentRefsFromBlocks(blocks);
|
|
677
|
+
const [copied, setCopied] = React.useState(false);
|
|
678
|
+
const [hasSnapshot, setHasSnapshot] = React.useState(false);
|
|
679
|
+
const [recall, setRecall] = React.useState({ stage: "idle" });
|
|
680
|
+
React.useEffect(() => {
|
|
681
|
+
let alive = true;
|
|
682
|
+
let timer = null;
|
|
683
|
+
let attempts = 0;
|
|
684
|
+
const RETRY_WINDOW_MS = 5 * 60 * 1e3;
|
|
685
|
+
const MAX_ATTEMPTS = 20;
|
|
686
|
+
const RETRY_MS = 1e3;
|
|
687
|
+
const msgTime = data && typeof data.time === "number" ? data.time : NaN;
|
|
688
|
+
const recent = !isNaN(msgTime) && Date.now() - msgTime <= RETRY_WINDOW_MS;
|
|
689
|
+
function schedule() {
|
|
690
|
+
if (!alive || !messageId) return;
|
|
691
|
+
attempts++;
|
|
692
|
+
api("snapshot-info", { messageId, sessionId }).then((res) => {
|
|
693
|
+
if (!alive) return;
|
|
694
|
+
if (res && res.has) {
|
|
695
|
+
if (recent && Array.isArray(res.skipped) && res.skipped.length) {
|
|
696
|
+
const names = res.skipped.slice(0, 5).join("\u3001") + (res.skipped.length > 5 ? " \u7B49 " + res.skipped.length + " \u9879" : "");
|
|
697
|
+
showThrottledToast("\u5FEB\u7167\u5DF2\u8DF3\u8FC7\u672A\u7EB3\u5165\u7684\u8DEF\u5F84\uFF1A" + names + "\uFF08\u64A4\u56DE\u4E0D\u4F1A\u6062\u590D\u6216\u5220\u9664\u8FD9\u4E9B\u8DEF\u5F84\uFF09");
|
|
715
698
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
699
|
+
setHasSnapshot(true);
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
if (res && res.failed) {
|
|
703
|
+
if (recent) showThrottledToast("\u5FEB\u7167\u5931\u8D25\uFF1A" + String(res.error || "\u672A\u77E5\u539F\u56E0").slice(0, 140));
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if (recent && attempts < MAX_ATTEMPTS) timer = setTimeout(schedule, RETRY_MS);
|
|
707
|
+
}).catch(() => {
|
|
708
|
+
if (alive && recent && attempts < MAX_ATTEMPTS) timer = setTimeout(schedule, RETRY_MS);
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
ensureInit(sessionId).then(() => {
|
|
712
|
+
if (!messageId || !alive) return;
|
|
713
|
+
schedule();
|
|
714
|
+
}).catch(() => {
|
|
715
|
+
if (alive && messageId) timer = setTimeout(schedule, RETRY_MS);
|
|
716
|
+
});
|
|
717
|
+
return () => {
|
|
718
|
+
alive = false;
|
|
719
|
+
if (timer !== null) clearTimeout(timer);
|
|
720
|
+
};
|
|
721
|
+
}, [messageId, sessionId]);
|
|
722
|
+
const onCopy = () => {
|
|
723
|
+
if (copied) return;
|
|
724
|
+
writeClipboard(text).then(() => {
|
|
725
|
+
setCopied(true);
|
|
726
|
+
const timer = ctx.timer;
|
|
727
|
+
if (timer && typeof timer.timeout === "function") {
|
|
728
|
+
timer.timeout(() => setCopied(false), 1200);
|
|
729
|
+
} else {
|
|
730
|
+
setTimeout(() => setCopied(false), 1200);
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
};
|
|
734
|
+
const openPreview = () => {
|
|
735
|
+
if (recall.stage === "loading" || recall.stage === "executing") return;
|
|
736
|
+
setRecall({ stage: "loading" });
|
|
737
|
+
api("preview", { messageId, sessionId }).then((res) => {
|
|
738
|
+
if (!res || !res.ok) {
|
|
739
|
+
setRecall({ stage: "error", message: messageFor(res, "\u65E0\u6CD5\u83B7\u53D6\u5FEB\u7167") });
|
|
728
740
|
return;
|
|
729
741
|
}
|
|
730
|
-
setRecall({
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
742
|
+
setRecall({
|
|
743
|
+
stage: "confirm",
|
|
744
|
+
changes: res.changes || [],
|
|
745
|
+
total: typeof res.total === "number" ? res.total : (res.changes || []).length,
|
|
746
|
+
truncated: Boolean(res.truncated),
|
|
747
|
+
treeId: res.treeId || null,
|
|
748
|
+
time: res.time || null,
|
|
749
|
+
cutSeq: typeof res.cutSeq === "number" ? res.cutSeq : null
|
|
750
|
+
});
|
|
751
|
+
}).catch((error) => {
|
|
752
|
+
setRecall({ stage: "error", message: String(error) });
|
|
753
|
+
});
|
|
754
|
+
};
|
|
755
|
+
const executeRecall = () => {
|
|
756
|
+
if (recall.stage !== "confirm") return;
|
|
757
|
+
const changes = recall.changes || [];
|
|
758
|
+
const previewCut = typeof recall.cutSeq === "number" ? recall.cutSeq : null;
|
|
759
|
+
const previewTotal = typeof recall.total === "number" ? recall.total : changes.length;
|
|
760
|
+
const attachmentFiles = pluginConfig.refillDraft && attachmentRefs.length > 0 && sessionId ? preloadAttachmentFiles(sessionId, attachmentRefs) : null;
|
|
761
|
+
setRecall({ stage: "executing", changes });
|
|
762
|
+
api("execute", { messageId, sessionId, previewTotal, previewTreeId: recall.treeId || void 0, previewAt: Date.now() }).then(async (res) => {
|
|
763
|
+
if (!res || !res.ok) {
|
|
764
|
+
if (res && res.code === "STALE") {
|
|
765
|
+
setRecall({ stage: "loading" });
|
|
766
|
+
api("preview", { messageId, sessionId }).then((res2) => {
|
|
767
|
+
if (!res2 || !res2.ok) {
|
|
768
|
+
setRecall({ stage: "error", message: messageFor(res2, "\u65E0\u6CD5\u83B7\u53D6\u5FEB\u7167") });
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
setRecall({
|
|
772
|
+
stage: "confirm",
|
|
773
|
+
changes: res2.changes || [],
|
|
774
|
+
total: typeof res2.total === "number" ? res2.total : (res2.changes || []).length,
|
|
775
|
+
truncated: Boolean(res2.truncated),
|
|
776
|
+
treeId: res2.treeId || null,
|
|
777
|
+
time: res2.time || null,
|
|
778
|
+
cutSeq: typeof res2.cutSeq === "number" ? res2.cutSeq : null
|
|
779
|
+
});
|
|
780
|
+
}).catch((error) => {
|
|
781
|
+
setRecall({ stage: "error", message: String(error) });
|
|
745
782
|
});
|
|
746
|
-
|
|
747
|
-
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
setRecall({ stage: "error", message: messageFor(res, "\u56DE\u9000\u5931\u8D25") });
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
const cutSeq = typeof res.cutSeq === "number" ? res.cutSeq : previewCut;
|
|
789
|
+
let chatReverted = false;
|
|
790
|
+
let chatError = "";
|
|
791
|
+
let fillTarget = sessionId;
|
|
792
|
+
if (cutSeq !== null && sessionsSvc && typeof sessionsSvc.fork === "function") {
|
|
793
|
+
try {
|
|
794
|
+
const childId = await sessionsSvc.fork({ sessionId, atSeq: cutSeq });
|
|
795
|
+
if (childId) {
|
|
796
|
+
if (typeof sessionsSvc.open === "function") sessionsSvc.open(childId);
|
|
797
|
+
chatReverted = true;
|
|
798
|
+
fillTarget = childId;
|
|
799
|
+
removeStaleQueueItems(childId, res.staleQueueItemIds);
|
|
800
|
+
api("lineage-record", { childId, parentId: sessionId }).catch(() => {
|
|
748
801
|
});
|
|
802
|
+
if (pluginConfig.archiveOriginal && workspacesSvc && typeof workspacesSvc.archiveSession === "function") {
|
|
803
|
+
workspacesSvc.archiveSession(sessionId).catch(() => {
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
} else {
|
|
807
|
+
chatError = "\u672A\u8FD4\u56DE\u65B0\u4F1A\u8BDD";
|
|
749
808
|
}
|
|
750
|
-
}
|
|
751
|
-
chatError =
|
|
809
|
+
} catch (error) {
|
|
810
|
+
chatError = String(error);
|
|
752
811
|
}
|
|
753
|
-
} catch (error) {
|
|
754
|
-
chatError = String(error);
|
|
755
812
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
813
|
+
if (pluginConfig.refillDraft) {
|
|
814
|
+
fillDraft(fillTarget, text, attachmentFiles);
|
|
815
|
+
if (fileBlocks.length > 0) showThrottledToast("\u88AB\u64A4\u56DE\u6D88\u606F\u91CC\u7684\u6587\u4EF6\u9644\u4EF6\u65E0\u6CD5\u81EA\u52A8\u56DE\u586B\uFF08\u5B98\u65B9\u63A5\u53E3\u53EA\u652F\u6301\u56FE\u7247\u56DE\u8BFB\uFF09\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9\u6587\u4EF6");
|
|
816
|
+
}
|
|
817
|
+
setHasSnapshot(false);
|
|
818
|
+
setRecall({ stage: "done", count: typeof res.count === "number" ? res.count : changes.length, chatReverted, chatError });
|
|
819
|
+
}).catch((error) => {
|
|
820
|
+
setRecall({ stage: "error", message: String(error) });
|
|
821
|
+
});
|
|
822
|
+
};
|
|
823
|
+
const closePanel = () => setRecall({ stage: "idle" });
|
|
824
|
+
const bubbleChildren = [];
|
|
825
|
+
if (imageBlocks.length && typeof renderMessageImages === "function") {
|
|
826
|
+
const render = renderMessageImages;
|
|
827
|
+
bubbleChildren.push(React.createElement(
|
|
828
|
+
React.Fragment,
|
|
829
|
+
{ key: "images" },
|
|
830
|
+
render({ images: imageBlocks, align: "end" })
|
|
831
|
+
));
|
|
832
|
+
}
|
|
833
|
+
for (let i = 0; i < fileBlocks.length; i++) {
|
|
834
|
+
const info = fileCardInfo(fileBlocks[i]);
|
|
835
|
+
if (!info) continue;
|
|
836
|
+
bubbleChildren.push(React.createElement(
|
|
837
|
+
"div",
|
|
838
|
+
{ className: "dsh-recall-filecard", key: "file-" + i },
|
|
839
|
+
React.createElement("span", { className: "dsh-recall-filecard-icon" }, info.ext),
|
|
840
|
+
React.createElement(
|
|
841
|
+
"span",
|
|
842
|
+
{ className: "dsh-recall-filecard-body" },
|
|
843
|
+
React.createElement("span", { className: "dsh-recall-filecard-name", title: info.name }, info.name),
|
|
844
|
+
React.createElement("span", { className: "dsh-recall-filecard-meta" }, info.size ? info.ext + " " + info.size : info.ext)
|
|
845
|
+
)
|
|
846
|
+
));
|
|
847
|
+
}
|
|
848
|
+
if (text !== "") bubbleChildren.push(React.createElement("div", { className: "dsh-recall-bubble", key: "text" }, text));
|
|
849
|
+
for (let i = 0; i < rest.length; i++) {
|
|
850
|
+
bubbleChildren.push(React.createElement("pre", { className: "dsh-recall-json", key: "rest-" + i }, JSON.stringify(rest[i], null, 2)));
|
|
851
|
+
}
|
|
852
|
+
const actions = [];
|
|
853
|
+
actions.push(React.createElement("span", { className: "dsh-recall-time", key: "time" }, clockText2(data.time)));
|
|
789
854
|
actions.push(React.createElement("button", {
|
|
790
|
-
key: "
|
|
855
|
+
key: "copy",
|
|
791
856
|
type: "button",
|
|
792
857
|
className: "dsh-recall-action",
|
|
793
|
-
"aria-label": "\
|
|
794
|
-
title: "\
|
|
795
|
-
onClick:
|
|
796
|
-
}, React.createElement(
|
|
858
|
+
"aria-label": copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236",
|
|
859
|
+
title: copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236",
|
|
860
|
+
onClick: onCopy
|
|
861
|
+
}, copied ? React.createElement(CheckIcon, {}) : React.createElement(CopyIcon, {})));
|
|
862
|
+
if (hasSnapshot) {
|
|
863
|
+
actions.push(React.createElement("button", {
|
|
864
|
+
key: "recall",
|
|
865
|
+
type: "button",
|
|
866
|
+
className: "dsh-recall-action",
|
|
867
|
+
"aria-label": "\u64A4\u56DE",
|
|
868
|
+
title: "\u6574\u6BB5\u56DE\u9000\uFF1A\u6587\u4EF6\u4E0E\u5BF9\u8BDD\u4E00\u5E76\u56DE\u5230\u8BE5\u6D88\u606F\u4E4B\u524D",
|
|
869
|
+
onClick: openPreview
|
|
870
|
+
}, React.createElement(UndoIcon, {})));
|
|
871
|
+
}
|
|
872
|
+
return React.createElement(
|
|
873
|
+
"div",
|
|
874
|
+
{ className: "dsh-recall-row", "data-time-hover-root": true },
|
|
875
|
+
bubbleChildren.length > 0 ? React.createElement("div", { className: "dsh-recall-stack", key: "stack" }, ...bubbleChildren) : null,
|
|
876
|
+
React.createElement("div", { className: "dsh-recall-actions", key: "actions" }, ...actions),
|
|
877
|
+
recallPanel(recall, closePanel, executeRecall)
|
|
878
|
+
);
|
|
797
879
|
}
|
|
798
|
-
return
|
|
799
|
-
"div",
|
|
800
|
-
{ className: "dsh-recall-row", "data-time-hover-root": true },
|
|
801
|
-
bubbleChildren.length > 0 ? React.createElement("div", { className: "dsh-recall-stack", key: "stack" }, ...bubbleChildren) : null,
|
|
802
|
-
React.createElement("div", { className: "dsh-recall-actions", key: "actions" }, ...actions),
|
|
803
|
-
recallPanel(recall, closePanel, executeRecall)
|
|
804
|
-
);
|
|
880
|
+
return { UserRecallNode };
|
|
805
881
|
}
|
|
806
|
-
return { UserRecallNode };
|
|
807
|
-
}
|
|
808
882
|
|
|
809
|
-
// src/client/config-card.ts
|
|
810
|
-
function buildConfigForm(React, util, SectionToggle) {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
}
|
|
846
|
-
React.useEffect(() => {
|
|
847
|
-
load();
|
|
848
|
-
}, []);
|
|
849
|
-
function edit(key, value) {
|
|
850
|
-
setDraft((d) => Object.assign({}, d, { [key]: value }));
|
|
851
|
-
}
|
|
852
|
-
function save() {
|
|
853
|
-
if (state.busy || !draft || !baseline) return;
|
|
854
|
-
const patch = {};
|
|
855
|
-
for (const key of ["gcSnaps", "gcHours", "maxFileBytes", "maxSnapshotsPerWorkspace", "baseExcludes", "refillDraft", "snapshotEnabled", "archiveOriginal", "retentionDays"]) {
|
|
856
|
-
if (draft[key] !== baseline[key]) patch[key] = draft[key];
|
|
883
|
+
// src/client/config-card.ts
|
|
884
|
+
function buildConfigForm(React, util, SectionToggle) {
|
|
885
|
+
const { api, bytesToMb: bytesToMb2 } = util;
|
|
886
|
+
function ConfigForm() {
|
|
887
|
+
const [baseline, setBaseline] = React.useState(null);
|
|
888
|
+
const [draft, setDraft] = React.useState(null);
|
|
889
|
+
const [envLocks, setEnvLocks] = React.useState({});
|
|
890
|
+
const [overridden, setOverridden] = React.useState({});
|
|
891
|
+
const [writable, setWritable] = React.useState(true);
|
|
892
|
+
const [state, setState] = React.useState({ busy: false, message: "", error: false });
|
|
893
|
+
useAutoDismissMessage(React, state, setState);
|
|
894
|
+
const [showAdvanced, setShowAdvanced] = React.useState(false);
|
|
895
|
+
function load() {
|
|
896
|
+
api("config-get", {}).then((res) => {
|
|
897
|
+
if (res && res.ok) {
|
|
898
|
+
const v = res.values;
|
|
899
|
+
const next = {
|
|
900
|
+
gcSnaps: String(v.gcSnaps == null ? "" : v.gcSnaps),
|
|
901
|
+
gcHours: String(v.gcHours == null ? "" : v.gcHours),
|
|
902
|
+
maxFileBytes: bytesToMb2(v.maxFileBytes),
|
|
903
|
+
maxSnapshotsPerWorkspace: String(v.maxSnapshotsPerWorkspace == null ? "" : v.maxSnapshotsPerWorkspace),
|
|
904
|
+
baseExcludes: Array.isArray(v.baseExcludes) ? v.baseExcludes.join("\n") : "",
|
|
905
|
+
refillDraft: v.refillDraft !== false,
|
|
906
|
+
snapshotEnabled: v.snapshotEnabled !== false,
|
|
907
|
+
archiveOriginal: v.archiveOriginal !== false,
|
|
908
|
+
retentionDays: String(v.retentionDays == null ? "" : v.retentionDays)
|
|
909
|
+
};
|
|
910
|
+
setDraft(next);
|
|
911
|
+
setBaseline(next);
|
|
912
|
+
setEnvLocks(res.envLocks || {});
|
|
913
|
+
setOverridden(res.overridden || {});
|
|
914
|
+
setWritable(res.writable !== false);
|
|
915
|
+
} else {
|
|
916
|
+
setState({ busy: false, message: res && (res.message || res.error) || "\u65E0\u6CD5\u8BFB\u53D6\u914D\u7F6E", error: true });
|
|
917
|
+
}
|
|
918
|
+
}).catch((e) => setState({ busy: false, message: String(e), error: true }));
|
|
857
919
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
920
|
+
React.useEffect(() => {
|
|
921
|
+
load();
|
|
922
|
+
}, []);
|
|
923
|
+
function edit(key, value) {
|
|
924
|
+
setDraft((d) => Object.assign({}, d, { [key]: value }));
|
|
861
925
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
const
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
return;
|
|
926
|
+
function save() {
|
|
927
|
+
if (state.busy || !draft || !baseline) return;
|
|
928
|
+
const patch = {};
|
|
929
|
+
for (const key of ["gcSnaps", "gcHours", "maxFileBytes", "maxSnapshotsPerWorkspace", "baseExcludes", "refillDraft", "snapshotEnabled", "archiveOriginal", "retentionDays"]) {
|
|
930
|
+
if (draft[key] !== baseline[key]) patch[key] = draft[key];
|
|
868
931
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
if (patch.gcHours !== void 0) {
|
|
872
|
-
const n = parseInt(String(patch.gcHours), 10);
|
|
873
|
-
if (!Number.isFinite(n) || n < 1) {
|
|
874
|
-
setState({ busy: false, message: "gc \u5C0F\u65F6\u9608\u503C\u5FC5\u987B\u662F >= 1 \u7684\u6574\u6570", error: true });
|
|
932
|
+
if (!Object.keys(patch).length) {
|
|
933
|
+
setState({ busy: false, message: "\u6CA1\u6709\u4FEE\u6539", error: false });
|
|
875
934
|
return;
|
|
876
935
|
}
|
|
877
|
-
clean
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
936
|
+
const clean = {};
|
|
937
|
+
if (patch.gcSnaps !== void 0) {
|
|
938
|
+
const n = parseInt(String(patch.gcSnaps), 10);
|
|
939
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
940
|
+
setState({ busy: false, message: "\u5FEB\u7167\u6761\u6570\u9608\u503C\u5FC5\u987B\u662F >= 1 \u7684\u6574\u6570", error: true });
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
clean.gcSnaps = n;
|
|
884
944
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
945
|
+
if (patch.gcHours !== void 0) {
|
|
946
|
+
const n = parseInt(String(patch.gcHours), 10);
|
|
947
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
948
|
+
setState({ busy: false, message: "gc \u5C0F\u65F6\u9608\u503C\u5FC5\u987B\u662F >= 1 \u7684\u6574\u6570", error: true });
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
clean.gcHours = n;
|
|
892
952
|
}
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
953
|
+
if (patch.maxFileBytes !== void 0) {
|
|
954
|
+
const mb = Number(patch.maxFileBytes);
|
|
955
|
+
if (!Number.isFinite(mb) || mb < 0.01) {
|
|
956
|
+
setState({ busy: false, message: "\u6587\u4EF6\u5927\u5C0F\u4E0A\u9650\u81F3\u5C11 0.01 MB", error: true });
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
clean.maxFileBytes = Math.round(mb * 1048576);
|
|
960
|
+
}
|
|
961
|
+
if (patch.maxSnapshotsPerWorkspace !== void 0) {
|
|
962
|
+
const n = parseInt(String(patch.maxSnapshotsPerWorkspace), 10);
|
|
963
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
964
|
+
setState({ busy: false, message: "\u5FEB\u7167\u603B\u91CF\u4E0A\u9650\u5FC5\u987B\u662F >= 0 \u7684\u6574\u6570\uFF080 \u8868\u793A\u4E0D\u9650\u5236\uFF09", error: true });
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
clean.maxSnapshotsPerWorkspace = n;
|
|
968
|
+
}
|
|
969
|
+
if (patch.refillDraft !== void 0) clean.refillDraft = Boolean(patch.refillDraft);
|
|
970
|
+
if (patch.snapshotEnabled !== void 0) clean.snapshotEnabled = Boolean(patch.snapshotEnabled);
|
|
971
|
+
if (patch.archiveOriginal !== void 0) clean.archiveOriginal = Boolean(patch.archiveOriginal);
|
|
972
|
+
if (patch.retentionDays !== void 0) {
|
|
973
|
+
const n = parseInt(String(patch.retentionDays), 10);
|
|
974
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
975
|
+
setState({ busy: false, message: "\u4FDD\u7559\u5929\u6570\u5FC5\u987B\u662F >= 0 \u7684\u6574\u6570\uFF080 \u8868\u793A\u4E0D\u542F\u7528\uFF09", error: true });
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
clean.retentionDays = n;
|
|
979
|
+
}
|
|
980
|
+
if (patch.baseExcludes !== void 0) {
|
|
981
|
+
clean.baseExcludes = String(patch.baseExcludes).split("\n").map((l) => l.trim()).filter(Boolean);
|
|
903
982
|
}
|
|
904
|
-
|
|
983
|
+
setState({ busy: true, message: "\u4FDD\u5B58\u4E2D\u2026", error: false });
|
|
984
|
+
api("config-set", { patch: clean }).then((res) => {
|
|
985
|
+
if (res && res.ok) {
|
|
986
|
+
setState({ busy: false, message: "\u5DF2\u4FDD\u5B58\u5E76\u5373\u65F6\u751F\u6548", error: false });
|
|
987
|
+
load();
|
|
988
|
+
} else {
|
|
989
|
+
setState({ busy: false, message: res && (res.message || res.error) || "\u4FDD\u5B58\u5931\u8D25", error: true });
|
|
990
|
+
}
|
|
991
|
+
}).catch((e) => setState({ busy: false, message: String(e), error: true }));
|
|
992
|
+
}
|
|
993
|
+
function numRow(key, label, hint, opts) {
|
|
994
|
+
const locked = Boolean(envLocks && envLocks[key]);
|
|
995
|
+
const changed = Boolean(draft && baseline && draft[key] !== baseline[key]);
|
|
996
|
+
return React.createElement(
|
|
997
|
+
"div",
|
|
998
|
+
{ className: "dsh-recall-cfg-row", key },
|
|
999
|
+
// V4:label 上提为 cfg-row 直接子元素——grid 第一列(max-content)跨行
|
|
1000
|
+
// 对齐最长标签,消灭 130px 定宽魔法数与 hint 138px 缩进耦合;
|
|
1001
|
+
// htmlFor-id 关联不变(V2)
|
|
1002
|
+
React.createElement("label", { className: "dsh-recall-cfg-label", htmlFor: "dsh-recall-cfg-" + key }, label),
|
|
1003
|
+
React.createElement(
|
|
1004
|
+
"div",
|
|
1005
|
+
{ className: "dsh-recall-cfg-line" },
|
|
1006
|
+
React.createElement("input", {
|
|
1007
|
+
id: "dsh-recall-cfg-" + key,
|
|
1008
|
+
className: "dsh-recall-cfg-input",
|
|
1009
|
+
type: "number",
|
|
1010
|
+
value: draft ? draft[key] : "",
|
|
1011
|
+
disabled: locked || !writable,
|
|
1012
|
+
min: opts && opts.min,
|
|
1013
|
+
step: opts && opts.step,
|
|
1014
|
+
onChange: (e) => edit(key, e.target.value)
|
|
1015
|
+
}),
|
|
1016
|
+
opts && opts.suffix ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, opts.suffix) : null,
|
|
1017
|
+
changed && !locked ? React.createElement("span", { className: "dsh-recall-cfg-tag dsh-recall-cfg-tag-modified" }, "\u5DF2\u4FEE\u6539") : null,
|
|
1018
|
+
overridden && overridden[key] !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u8986\u76D6") : null,
|
|
1019
|
+
locked ? React.createElement("span", { className: "dsh-recall-cfg-tag dsh-recall-cfg-tag-locked" }, "\u73AF\u5883\u53D8\u91CF\u9501\u5B9A") : null
|
|
1020
|
+
),
|
|
1021
|
+
React.createElement("div", { className: "dsh-recall-cfg-hint" }, hint)
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
function boolRow(key, label, hint) {
|
|
1025
|
+
const changed = Boolean(draft && baseline && draft[key] !== baseline[key]);
|
|
1026
|
+
const on = Boolean(draft && draft[key]);
|
|
1027
|
+
return React.createElement(
|
|
1028
|
+
"div",
|
|
1029
|
+
{ className: "dsh-recall-cfg-row", key },
|
|
1030
|
+
// label 上提为 cfg-row 直接子元素(与 numRow 同法,V4 跨行对齐契约)
|
|
1031
|
+
React.createElement("label", { className: "dsh-recall-cfg-label", htmlFor: "dsh-recall-cfg-" + key }, label),
|
|
1032
|
+
React.createElement(
|
|
1033
|
+
"div",
|
|
1034
|
+
{ className: "dsh-recall-cfg-line" },
|
|
1035
|
+
React.createElement("button", {
|
|
1036
|
+
id: "dsh-recall-cfg-" + key,
|
|
1037
|
+
type: "button",
|
|
1038
|
+
role: "switch",
|
|
1039
|
+
"aria-checked": on,
|
|
1040
|
+
className: "dsh-recall-cfg-switch",
|
|
1041
|
+
disabled: !writable,
|
|
1042
|
+
onClick: () => edit(key, !on)
|
|
1043
|
+
}, React.createElement("span", { className: "dsh-recall-cfg-switch-thumb" })),
|
|
1044
|
+
changed ? React.createElement("span", { className: "dsh-recall-cfg-tag dsh-recall-cfg-tag-modified" }, "\u5DF2\u4FEE\u6539") : null,
|
|
1045
|
+
overridden && overridden[key] !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u8986\u76D6") : null
|
|
1046
|
+
),
|
|
1047
|
+
React.createElement("div", { className: "dsh-recall-cfg-hint" }, hint)
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
function resetDefaults() {
|
|
1051
|
+
if (state.busy || !writable) return;
|
|
1052
|
+
setState({ busy: true, message: "\u6062\u590D\u9ED8\u8BA4\u4E2D\u2026", error: false });
|
|
1053
|
+
api("config-reset", {}).then((res) => {
|
|
1054
|
+
if (res && res.ok) {
|
|
1055
|
+
load();
|
|
1056
|
+
setState({ busy: false, message: "\u5DF2\u6062\u590D\u9ED8\u8BA4\u503C", error: false });
|
|
1057
|
+
} else {
|
|
1058
|
+
setState({ busy: false, message: res && (res.message || res.error) || "\u6062\u590D\u9ED8\u8BA4\u5931\u8D25", error: true });
|
|
1059
|
+
}
|
|
1060
|
+
}).catch((e) => setState({ busy: false, message: String(e), error: true }));
|
|
905
1061
|
}
|
|
906
|
-
if (
|
|
907
|
-
|
|
1062
|
+
if (!draft || !baseline) {
|
|
1063
|
+
return React.createElement("div", { className: "dsh-recall-ex-note" }, state.message || "\u6B63\u5728\u8BFB\u53D6\u914D\u7F6E\u2026");
|
|
908
1064
|
}
|
|
909
|
-
setState({ busy: true, message: "\u4FDD\u5B58\u4E2D\u2026", error: false });
|
|
910
|
-
api("config-set", { patch: clean }).then((res) => {
|
|
911
|
-
if (res && res.ok) {
|
|
912
|
-
setState({ busy: false, message: "\u5DF2\u4FDD\u5B58\u5E76\u5373\u65F6\u751F\u6548", error: false });
|
|
913
|
-
load();
|
|
914
|
-
} else {
|
|
915
|
-
setState({ busy: false, message: res && (res.message || res.error) || "\u4FDD\u5B58\u5931\u8D25", error: true });
|
|
916
|
-
}
|
|
917
|
-
}).catch((e) => setState({ busy: false, message: String(e), error: true }));
|
|
918
|
-
}
|
|
919
|
-
function numRow(key, label, hint, opts) {
|
|
920
|
-
const locked = Boolean(envLocks && envLocks[key]);
|
|
921
|
-
const changed = Boolean(draft && baseline && draft[key] !== baseline[key]);
|
|
922
1065
|
return React.createElement(
|
|
923
1066
|
"div",
|
|
924
|
-
{ className: "dsh-recall-
|
|
925
|
-
//
|
|
926
|
-
//
|
|
927
|
-
//
|
|
928
|
-
|
|
1067
|
+
{ className: "dsh-recall-ex-card" },
|
|
1068
|
+
// 全部行包进单一 cfg-grid:cfg-row 是 display:contents 透明层(css.ts),
|
|
1069
|
+
// label/控件行/hint 直接成为同一 grid 的 item,第一列 max-content 由全表单
|
|
1070
|
+
// 最长 label 决定——checkbox 与数字输入框的控件列自此跨行对齐(此前每行
|
|
1071
|
+
// 是独立 grid 容器,max-content 各算各的,跨行对齐从未成立,实测参差)。
|
|
929
1072
|
React.createElement(
|
|
930
1073
|
"div",
|
|
931
|
-
{ className: "dsh-recall-cfg-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
}),
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1074
|
+
{ className: "dsh-recall-cfg-grid" },
|
|
1075
|
+
// V5 表单分组:9 字段平铺 → 「快照行为 / 自动治理」两组语义分组小标题,
|
|
1076
|
+
// 降低认知负担;「高级:基础排除表」沿用 SectionToggle 折叠,不重复加标题。
|
|
1077
|
+
React.createElement("div", { className: "dsh-recall-cfg-group" }, "\u5FEB\u7167\u884C\u4E3A"),
|
|
1078
|
+
boolRow("snapshotEnabled", "\u542F\u7528\u5FEB\u7167", "\u5173\u95ED\u540E\u4E0D\u518D\u65B0\u5EFA\u5FEB\u7167\uFF08\u5DF2\u6709\u5FEB\u7167\u4ECD\u53EF\u64A4\u56DE\uFF09\uFF0C\u9002\u5408\u4E34\u65F6\u7981\u7528\u5FEB\u7167\u7684\u573A\u5408"),
|
|
1079
|
+
boolRow("refillDraft", "\u64A4\u56DE\u540E\u56DE\u586B\u8F93\u5165\u6846", "\u64A4\u56DE\u6210\u529F\u540E\u628A\u88AB\u64A4\u56DE\u7684\u6D88\u606F\u6587\u672C\u56DE\u586B\u5230\u8F93\u5165\u6846\uFF0C\u65B9\u4FBF\u4FEE\u6539\u540E\u91CD\u65B0\u53D1\u9001"),
|
|
1080
|
+
boolRow("archiveOriginal", "\u64A4\u56DE\u540E\u5F52\u6863\u539F\u4F1A\u8BDD", "\u64A4\u56DE\u540E\u539F\u4F1A\u8BDD\u4ECE\u5217\u8868\u5F52\u6863\u9690\u85CF\uFF08\u53EF\u4ECE\u5F52\u6863\u627E\u56DE\uFF09\uFF1B\u5173\u95ED\u5219\u4FDD\u7559\u5728\u5217\u8868\u4E2D\uFF0C\u65B9\u4FBF\u5BF9\u7167\u56DE\u9000\u524D\u540E\u7684\u4E0A\u4E0B\u6587"),
|
|
1081
|
+
React.createElement("div", { className: "dsh-recall-cfg-group" }, "\u81EA\u52A8\u6CBB\u7406"),
|
|
1082
|
+
// 单位后缀统一挂输入框右侧(与状态标签同基线),不再只藏在说明文字里
|
|
1083
|
+
numRow("gcSnaps", "gc \u89E6\u53D1\u6761\u6570", "\u6BCF\u79EF\u7D2F\u591A\u5C11\u6761\u5FEB\u7167\u89E6\u53D1\u4E00\u6B21 git gc", { suffix: "\u6761", min: 1, step: 1 }),
|
|
1084
|
+
numRow("gcHours", "gc \u89E6\u53D1\u5C0F\u65F6", "\u8DDD\u4E0A\u6B21 gc \u8D85\u8FC7\u591A\u5C11\u5C0F\u65F6\u89E6\u53D1\uFF08\u4E0E\u6761\u6570\u5148\u5230\u5148\u89E6\u53D1\uFF09", { suffix: "\u5C0F\u65F6", min: 1, step: 1 }),
|
|
1085
|
+
numRow("maxFileBytes", "\u6587\u4EF6\u5927\u5C0F\u4E0A\u9650", "\u8D85\u8FC7\u8BE5\u5927\u5C0F\u7684\u6587\u4EF6\u4E0D\u8FDB\u5FEB\u7167\u3001\u4E0D\u88AB\u56DE\u9000\u89E6\u78B0\uFF08\u5355\u4F4D MB\uFF0C\u652F\u6301\u5C0F\u6570\uFF09", { suffix: "MB", min: 0.01, step: 0.5 }),
|
|
1086
|
+
numRow("maxSnapshotsPerWorkspace", "\u5FEB\u7167\u603B\u91CF\u4E0A\u9650", "\u6BCF\u4E2A\u5DE5\u4F5C\u533A\u4FDD\u7559\u7684\u6700\u5927\u5FEB\u7167\u6570\uFF0C\u8D85\u9650\u81EA\u52A8\u5220\u9664\u6700\u65E7\u7684\uFF1B\u586B 0 \u8868\u793A\u4E0D\u9650\u5236", { suffix: "\u6761", min: 0, step: 1 }),
|
|
1087
|
+
numRow("retentionDays", "\u5FEB\u7167\u4FDD\u7559\u5929\u6570", "\u6309\u5929\u6570\u4FDD\u7559\u5FEB\u7167\uFF0C\u8D85\u671F\u81EA\u52A8\u5220\u9664\u6700\u65E7\u7684\uFF1B\u586B 0 \u8868\u793A\u4E0D\u542F\u7528\uFF08\u4E0E\u5FEB\u7167\u603B\u6570\u4E0A\u9650\u5404\u81EA\u751F\u6548\uFF09", { suffix: "\u5929", min: 0, step: 1 }),
|
|
1088
|
+
// 操作区在「基础排除表」折叠头之前:按钮服务整个表单(含折叠区之外的字段),
|
|
1089
|
+
// 排在折叠头之后会被误读为折叠区内容、折叠时像漏收起(用户实测反馈);
|
|
1090
|
+
// 因此也不把按钮藏进折叠分支——否则折叠基础排除表后将无法保存。
|
|
1091
|
+
React.createElement(
|
|
1092
|
+
"div",
|
|
1093
|
+
{ className: "dsh-recall-panel-actions" },
|
|
1094
|
+
state.message ? React.createElement("span", { role: "status", "aria-live": "polite", className: "dsh-recall-ex-status" + (state.error ? " dsh-recall-ex-status-error" : " dsh-recall-ex-status-success") }, (state.error ? "\u9519\u8BEF\uFF1A" : "") + state.message) : null,
|
|
1095
|
+
React.createElement("button", { type: "button", className: "dsh-recall-btn", disabled: state.busy || !writable, onClick: () => setDraft(baseline ? Object.assign({}, baseline) : null) }, "\u653E\u5F03\u4FEE\u6539"),
|
|
1096
|
+
React.createElement("button", {
|
|
1097
|
+
type: "button",
|
|
1098
|
+
className: "dsh-recall-btn",
|
|
1099
|
+
disabled: state.busy || !writable,
|
|
1100
|
+
title: "\u628A\u6240\u6709\u5B57\u6BB5\u6062\u590D\u5230\u63D2\u4EF6\u51FA\u5382\u9ED8\u8BA4\u503C",
|
|
1101
|
+
onClick: resetDefaults
|
|
1102
|
+
}, "\u6062\u590D\u9ED8\u8BA4"),
|
|
1103
|
+
// 「保存」是表单唯一主动作,升主色实心按钮;放弃修改/恢复默认维持次级
|
|
1104
|
+
// 灰底(同排按钮只有一个视觉焦点,配色令牌经官方主题产物核验,见 css.ts)
|
|
1105
|
+
React.createElement("button", { type: "button", className: "dsh-recall-btn dsh-recall-btn-primary", disabled: state.busy || !writable, onClick: save }, "\u4FDD\u5B58"),
|
|
1106
|
+
!writable ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u53EA\u8BFB\u8BBE\u7F6E\u6E90") : null
|
|
1107
|
+
),
|
|
1108
|
+
React.createElement(SectionToggle, { title: "\u9AD8\u7EA7\uFF1A\u57FA\u7840\u6392\u9664\u8868", open: showAdvanced, onToggle: () => setShowAdvanced((v) => !v) }),
|
|
1109
|
+
showAdvanced ? React.createElement(
|
|
1110
|
+
"div",
|
|
1111
|
+
{ className: "dsh-recall-cfg-row", key: "baseExcludes" },
|
|
1112
|
+
// V4:label 上提(与 numRow 同法);cfg-line 只剩 tags,textarea/hint 各自归第二列
|
|
1113
|
+
React.createElement("label", { className: "dsh-recall-cfg-label", htmlFor: "dsh-recall-cfg-baseExcludes" }, "\u57FA\u7840\u6392\u9664\u8868"),
|
|
1114
|
+
React.createElement(
|
|
1115
|
+
"div",
|
|
1116
|
+
{ className: "dsh-recall-cfg-line" },
|
|
1117
|
+
draft.baseExcludes !== baseline.baseExcludes ? React.createElement("span", { className: "dsh-recall-cfg-tag dsh-recall-cfg-tag-modified" }, "\u5DF2\u4FEE\u6539") : null,
|
|
1118
|
+
overridden && overridden.baseExcludes !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u8986\u76D6") : null
|
|
1119
|
+
),
|
|
1120
|
+
// textarea/hint 加 cfg-span 通栏:折叠区在共享 grid 内展开时,内容若只占
|
|
1121
|
+
// 第二列,左侧长 label 列会成为竖直死区(实测 textarea 被挤窄);通栏后
|
|
1122
|
+
// 与 label/tags 行左缘对齐。label 保持原位不动——htmlFor 关联是 a11y 契约。
|
|
1123
|
+
React.createElement("textarea", {
|
|
1124
|
+
id: "dsh-recall-cfg-baseExcludes",
|
|
1125
|
+
className: "dsh-recall-cfg-area dsh-recall-cfg-span",
|
|
1126
|
+
rows: 4,
|
|
1127
|
+
value: draft.baseExcludes,
|
|
1128
|
+
disabled: !writable,
|
|
1129
|
+
onChange: (e) => edit("baseExcludes", e.target.value)
|
|
1130
|
+
}),
|
|
1131
|
+
React.createElement("div", { className: "dsh-recall-cfg-hint dsh-recall-cfg-span" }, "\u5185\u7F6E\u89C4\u5219\uFF0C\u6BCF\u4E2A\u5DE5\u4F5C\u533A\u5171\u4EAB\uFF0C\u5EFA\u8BAE\u4FDD\u6301\u9ED8\u8BA4\uFF1Bgitignore \u8BED\u6CD5\u6BCF\u884C\u4E00\u6761\uFF0C\u4F18\u5148\u7EA7\u4F4E\u4E8E\u300C\u6392\u9664\u914D\u7F6E\u300D\u91CC\u7684 exclude.txt\uFF08S3-2 \u6298\u53E0\uFF09")
|
|
1132
|
+
) : null
|
|
1133
|
+
)
|
|
948
1134
|
);
|
|
949
1135
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1136
|
+
return { ConfigForm };
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// src/client/exclude-card.ts
|
|
1140
|
+
var EXCLUDE_SUGGESTIONS = ["dist/", "build/", "out/", "coverage/", "*.log", ".env"];
|
|
1141
|
+
function buildExcludeCards(React, util) {
|
|
1142
|
+
const { api } = util;
|
|
1143
|
+
function ExcludeCard(props) {
|
|
1144
|
+
const file = props.file;
|
|
1145
|
+
const [draft, setDraft] = React.useState(file.content || "");
|
|
1146
|
+
const [baseline, setBaseline] = React.useState(file.content || "");
|
|
1147
|
+
const [quick, setQuick] = React.useState("");
|
|
1148
|
+
const [state, setState] = React.useState({ busy: false, message: "", error: false });
|
|
1149
|
+
useAutoDismissMessage(React, state, setState);
|
|
1150
|
+
const dirty = draft !== baseline;
|
|
1151
|
+
function appendPattern(pattern) {
|
|
1152
|
+
setDraft((d) => (d && !d.endsWith("\n") ? d + "\n" : d) + pattern + "\n");
|
|
1153
|
+
}
|
|
1154
|
+
function addQuick() {
|
|
1155
|
+
const t = quick.trim();
|
|
1156
|
+
if (!t) return;
|
|
1157
|
+
appendPattern(t);
|
|
1158
|
+
setQuick("");
|
|
1159
|
+
}
|
|
1160
|
+
function save() {
|
|
1161
|
+
if (state.busy || !dirty) return;
|
|
1162
|
+
setState({ busy: true, message: "\u4FDD\u5B58\u4E2D\u2026", error: false });
|
|
1163
|
+
api("exclude-set", { path: file.path, content: draft }).then((res) => {
|
|
1164
|
+
if (res && res.ok) {
|
|
1165
|
+
setBaseline(draft);
|
|
1166
|
+
setState({ busy: false, message: "\u5DF2\u4FDD\u5B58\uFF0C\u4E0B\u4E00\u6B21\u5FEB\u7167 / \u9884\u89C8 / \u56DE\u9000\u65F6\u751F\u6548", error: false });
|
|
1167
|
+
} else {
|
|
1168
|
+
setState({ busy: false, message: res && (res.message || res.error) || "\u4FDD\u5B58\u5931\u8D25", error: true });
|
|
1169
|
+
}
|
|
1170
|
+
}).catch((error) => {
|
|
1171
|
+
setState({ busy: false, message: String(error), error: true });
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
function discard() {
|
|
1175
|
+
if (state.busy) return;
|
|
1176
|
+
setDraft(baseline);
|
|
1177
|
+
setState({ busy: false, message: "", error: false });
|
|
1178
|
+
}
|
|
1179
|
+
const draftLines = draft.split("\n").map((l) => l.trim());
|
|
1180
|
+
const suggestions = EXCLUDE_SUGGESTIONS.filter((s) => draftLines.indexOf(s) < 0);
|
|
953
1181
|
return React.createElement(
|
|
954
1182
|
"div",
|
|
955
|
-
{ className: "dsh-recall-
|
|
956
|
-
//
|
|
957
|
-
|
|
1183
|
+
{ className: "dsh-recall-ex-card" },
|
|
1184
|
+
// 不渲染「快照排除项」内标题(与折叠头「排除配置(exclude.txt)」语义重复);
|
|
1185
|
+
// 多文件场景(降级工作区)靠紧随说明的存储路径行区分,标题文本本就相同、
|
|
1186
|
+
// 不承担区分职责。路径从长句中抽出独立等宽行——Windows 长路径内联在句号
|
|
1187
|
+
// 前会撑出断裂换行(实测丑),独立行 break-all 整齐折行。
|
|
958
1188
|
React.createElement(
|
|
959
1189
|
"div",
|
|
960
|
-
{ className: "dsh-recall-
|
|
961
|
-
|
|
962
|
-
|
|
1190
|
+
{ className: "dsh-recall-ex-note" },
|
|
1191
|
+
file.home ? "\u6B64\u914D\u7F6E\u5168\u5C40\u5171\u4EAB\uFF0C\u5BF9\u6240\u6709\u5DE5\u4F5C\u533A\u7684\u5FEB\u7167\u751F\u6548\u3002" : "home \u76EE\u5F55\u4E0D\u53EF\u5199\u65F6\u6B64\u5DE5\u4F5C\u533A\u964D\u7EA7\u5B58\u50A8\uFF0C\u6392\u9664\u914D\u7F6E\u72EC\u7ACB\u751F\u6548\u3002"
|
|
1192
|
+
),
|
|
1193
|
+
React.createElement("div", { className: "dsh-recall-ex-path" }, "\u5B58\u50A8\u4F4D\u7F6E\uFF1A" + file.path),
|
|
1194
|
+
React.createElement("div", { className: "dsh-recall-ex-note" }, "gitignore \u8BED\u6CD5\uFF0C\u4E00\u884C\u4E00\u6761\uFF0C\u652F\u6301 # \u6CE8\u91CA\uFF1B\u547D\u4E2D\u6392\u9664\u7684\u6587\u4EF6\u4E0E\u76EE\u5F55\u4E0D\u8FDB\u5165\u5FEB\u7167\uFF0C\u4E5F\u4E0D\u4F1A\u88AB\u56DE\u9000\u89E6\u78B0\u3002"),
|
|
1195
|
+
React.createElement("textarea", {
|
|
1196
|
+
className: "dsh-recall-ex-area",
|
|
1197
|
+
"aria-label": "\u5FEB\u7167\u6392\u9664\u6A21\u5F0F\u5217\u8868\uFF08gitignore \u8BED\u6CD5\uFF0C\u4E00\u884C\u4E00\u6761\uFF09",
|
|
1198
|
+
value: draft,
|
|
1199
|
+
spellCheck: false,
|
|
1200
|
+
onChange: (e) => setDraft(e.target.value)
|
|
1201
|
+
}),
|
|
1202
|
+
React.createElement(
|
|
1203
|
+
"div",
|
|
1204
|
+
{ className: "dsh-recall-ex-quick" },
|
|
1205
|
+
React.createElement("input", {
|
|
1206
|
+
className: "dsh-recall-ex-input",
|
|
1207
|
+
value: quick,
|
|
1208
|
+
placeholder: "\u8F93\u5165\u8DEF\u5F84\u6216\u6A21\u5F0F\uFF0C\u56DE\u8F66\u5FEB\u901F\u6DFB\u52A0",
|
|
1209
|
+
"aria-label": "\u5FEB\u901F\u6DFB\u52A0\u6392\u9664\u6A21\u5F0F",
|
|
1210
|
+
onChange: (e) => setQuick(e.target.value),
|
|
1211
|
+
onKeyDown: (e) => {
|
|
1212
|
+
if (e.key === "Enter") {
|
|
1213
|
+
e.preventDefault();
|
|
1214
|
+
addQuick();
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}),
|
|
1218
|
+
React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: addQuick }, "\u6DFB\u52A0"),
|
|
1219
|
+
...suggestions.map((s) => React.createElement("button", {
|
|
1220
|
+
key: "chip-" + s,
|
|
963
1221
|
type: "button",
|
|
964
|
-
|
|
965
|
-
"
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
onClick: () => edit(key, !on)
|
|
969
|
-
}, React.createElement("span", { className: "dsh-recall-cfg-switch-thumb" })),
|
|
970
|
-
changed ? React.createElement("span", { className: "dsh-recall-cfg-tag dsh-recall-cfg-tag-modified" }, "\u5DF2\u4FEE\u6539") : null,
|
|
971
|
-
overridden && overridden[key] !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u8986\u76D6") : null
|
|
1222
|
+
className: "dsh-recall-ex-chip",
|
|
1223
|
+
title: "\u70B9\u51FB\u8FFD\u52A0 " + s,
|
|
1224
|
+
onClick: () => appendPattern(s)
|
|
1225
|
+
}, s))
|
|
972
1226
|
),
|
|
973
|
-
React.createElement("div", { className: "dsh-recall-cfg-hint" }, hint)
|
|
974
|
-
);
|
|
975
|
-
}
|
|
976
|
-
function resetDefaults() {
|
|
977
|
-
if (state.busy || !writable) return;
|
|
978
|
-
setState({ busy: true, message: "\u6062\u590D\u9ED8\u8BA4\u4E2D\u2026", error: false });
|
|
979
|
-
api("config-reset", {}).then((res) => {
|
|
980
|
-
if (res && res.ok) {
|
|
981
|
-
load();
|
|
982
|
-
setState({ busy: false, message: "\u5DF2\u6062\u590D\u9ED8\u8BA4\u503C", error: false });
|
|
983
|
-
} else {
|
|
984
|
-
setState({ busy: false, message: res && (res.message || res.error) || "\u6062\u590D\u9ED8\u8BA4\u5931\u8D25", error: true });
|
|
985
|
-
}
|
|
986
|
-
}).catch((e) => setState({ busy: false, message: String(e), error: true }));
|
|
987
|
-
}
|
|
988
|
-
if (!draft || !baseline) {
|
|
989
|
-
return React.createElement("div", { className: "dsh-recall-ex-note" }, state.message || "\u6B63\u5728\u8BFB\u53D6\u914D\u7F6E\u2026");
|
|
990
|
-
}
|
|
991
|
-
return React.createElement(
|
|
992
|
-
"div",
|
|
993
|
-
{ className: "dsh-recall-ex-card" },
|
|
994
|
-
// 全部行包进单一 cfg-grid:cfg-row 是 display:contents 透明层(css.ts),
|
|
995
|
-
// label/控件行/hint 直接成为同一 grid 的 item,第一列 max-content 由全表单
|
|
996
|
-
// 最长 label 决定——checkbox 与数字输入框的控件列自此跨行对齐(此前每行
|
|
997
|
-
// 是独立 grid 容器,max-content 各算各的,跨行对齐从未成立,实测参差)。
|
|
998
|
-
React.createElement(
|
|
999
|
-
"div",
|
|
1000
|
-
{ className: "dsh-recall-cfg-grid" },
|
|
1001
|
-
// V5 表单分组:9 字段平铺 → 「快照行为 / 自动治理」两组语义分组小标题,
|
|
1002
|
-
// 降低认知负担;「高级:基础排除表」沿用 SectionToggle 折叠,不重复加标题。
|
|
1003
|
-
React.createElement("div", { className: "dsh-recall-cfg-group" }, "\u5FEB\u7167\u884C\u4E3A"),
|
|
1004
|
-
boolRow("snapshotEnabled", "\u542F\u7528\u5FEB\u7167", "\u5173\u95ED\u540E\u4E0D\u518D\u65B0\u5EFA\u5FEB\u7167\uFF08\u5DF2\u6709\u5FEB\u7167\u4ECD\u53EF\u64A4\u56DE\uFF09\uFF0C\u9002\u5408\u4E34\u65F6\u7981\u7528\u5FEB\u7167\u7684\u573A\u5408"),
|
|
1005
|
-
boolRow("refillDraft", "\u64A4\u56DE\u540E\u56DE\u586B\u8F93\u5165\u6846", "\u64A4\u56DE\u6210\u529F\u540E\u628A\u88AB\u64A4\u56DE\u7684\u6D88\u606F\u6587\u672C\u56DE\u586B\u5230\u8F93\u5165\u6846\uFF0C\u65B9\u4FBF\u4FEE\u6539\u540E\u91CD\u65B0\u53D1\u9001"),
|
|
1006
|
-
boolRow("archiveOriginal", "\u64A4\u56DE\u540E\u5F52\u6863\u539F\u4F1A\u8BDD", "\u64A4\u56DE\u540E\u539F\u4F1A\u8BDD\u4ECE\u5217\u8868\u5F52\u6863\u9690\u85CF\uFF08\u53EF\u4ECE\u5F52\u6863\u627E\u56DE\uFF09\uFF1B\u5173\u95ED\u5219\u4FDD\u7559\u5728\u5217\u8868\u4E2D\uFF0C\u65B9\u4FBF\u5BF9\u7167\u56DE\u9000\u524D\u540E\u7684\u4E0A\u4E0B\u6587"),
|
|
1007
|
-
React.createElement("div", { className: "dsh-recall-cfg-group" }, "\u81EA\u52A8\u6CBB\u7406"),
|
|
1008
|
-
// 单位后缀统一挂输入框右侧(与状态标签同基线),不再只藏在说明文字里
|
|
1009
|
-
numRow("gcSnaps", "gc \u89E6\u53D1\u6761\u6570", "\u6BCF\u79EF\u7D2F\u591A\u5C11\u6761\u5FEB\u7167\u89E6\u53D1\u4E00\u6B21 git gc", { suffix: "\u6761", min: 1, step: 1 }),
|
|
1010
|
-
numRow("gcHours", "gc \u89E6\u53D1\u5C0F\u65F6", "\u8DDD\u4E0A\u6B21 gc \u8D85\u8FC7\u591A\u5C11\u5C0F\u65F6\u89E6\u53D1\uFF08\u4E0E\u6761\u6570\u5148\u5230\u5148\u89E6\u53D1\uFF09", { suffix: "\u5C0F\u65F6", min: 1, step: 1 }),
|
|
1011
|
-
numRow("maxFileBytes", "\u6587\u4EF6\u5927\u5C0F\u4E0A\u9650", "\u8D85\u8FC7\u8BE5\u5927\u5C0F\u7684\u6587\u4EF6\u4E0D\u8FDB\u5FEB\u7167\u3001\u4E0D\u88AB\u56DE\u9000\u89E6\u78B0\uFF08\u5355\u4F4D MB\uFF0C\u652F\u6301\u5C0F\u6570\uFF09", { suffix: "MB", min: 0.01, step: 0.5 }),
|
|
1012
|
-
numRow("maxSnapshotsPerWorkspace", "\u5FEB\u7167\u603B\u91CF\u4E0A\u9650", "\u6BCF\u4E2A\u5DE5\u4F5C\u533A\u4FDD\u7559\u7684\u6700\u5927\u5FEB\u7167\u6570\uFF0C\u8D85\u9650\u81EA\u52A8\u5220\u9664\u6700\u65E7\u7684\uFF1B\u586B 0 \u8868\u793A\u4E0D\u9650\u5236", { suffix: "\u6761", min: 0, step: 1 }),
|
|
1013
|
-
numRow("retentionDays", "\u5FEB\u7167\u4FDD\u7559\u5929\u6570", "\u6309\u5929\u6570\u4FDD\u7559\u5FEB\u7167\uFF0C\u8D85\u671F\u81EA\u52A8\u5220\u9664\u6700\u65E7\u7684\uFF1B\u586B 0 \u8868\u793A\u4E0D\u542F\u7528\uFF08\u4E0E\u5FEB\u7167\u603B\u6570\u4E0A\u9650\u5404\u81EA\u751F\u6548\uFF09", { suffix: "\u5929", min: 0, step: 1 }),
|
|
1014
|
-
// 操作区在「基础排除表」折叠头之前:按钮服务整个表单(含折叠区之外的字段),
|
|
1015
|
-
// 排在折叠头之后会被误读为折叠区内容、折叠时像漏收起(用户实测反馈);
|
|
1016
|
-
// 因此也不把按钮藏进折叠分支——否则折叠基础排除表后将无法保存。
|
|
1017
1227
|
React.createElement(
|
|
1018
1228
|
"div",
|
|
1019
1229
|
{ className: "dsh-recall-panel-actions" },
|
|
1020
1230
|
state.message ? React.createElement("span", { role: "status", "aria-live": "polite", className: "dsh-recall-ex-status" + (state.error ? " dsh-recall-ex-status-error" : " dsh-recall-ex-status-success") }, (state.error ? "\u9519\u8BEF\uFF1A" : "") + state.message) : null,
|
|
1021
|
-
React.createElement("button", { type: "button", className: "dsh-recall-btn", disabled: state.busy
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1231
|
+
React.createElement("button", { type: "button", className: "dsh-recall-btn", disabled: !dirty || state.busy, onClick: discard }, "\u653E\u5F03\u4FEE\u6539"),
|
|
1232
|
+
// 保存升主色实心(与配置表单同一约定:每卡唯一主动作,对齐官方插件卡
|
|
1233
|
+
// footer 的 discard 幽灵 + save 实心组合)
|
|
1234
|
+
React.createElement("button", { type: "button", className: "dsh-recall-btn dsh-recall-btn-primary", disabled: !dirty || state.busy, onClick: save }, "\u4FDD\u5B58")
|
|
1235
|
+
)
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
function ExcludeFilesSection() {
|
|
1239
|
+
const [files, setFiles] = React.useState(null);
|
|
1240
|
+
const [error, setError] = React.useState("");
|
|
1241
|
+
function load() {
|
|
1242
|
+
api("exclude-get", {}).then((res) => {
|
|
1243
|
+
if (res && res.ok) {
|
|
1244
|
+
setFiles(res.files || []);
|
|
1245
|
+
setError("");
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
if (res && res.unsupported) {
|
|
1249
|
+
setError("\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u5FEB\u7167\u529F\u80FD\uFF0C\u6392\u9664\u914D\u7F6E\u4E0D\u53EF\u7528\u3002");
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
setError(res && (res.message || res.error) || "\u65E0\u6CD5\u8BFB\u53D6\u6392\u9664\u914D\u7F6E");
|
|
1253
|
+
}).catch((e) => setError(String(e)));
|
|
1254
|
+
}
|
|
1255
|
+
React.useEffect(() => {
|
|
1256
|
+
load();
|
|
1257
|
+
}, []);
|
|
1258
|
+
if (error) {
|
|
1259
|
+
return React.createElement(
|
|
1036
1260
|
"div",
|
|
1037
|
-
{ className: "dsh-recall-
|
|
1038
|
-
|
|
1039
|
-
React.createElement("label", { className: "dsh-recall-cfg-label", htmlFor: "dsh-recall-cfg-baseExcludes" }, "\u57FA\u7840\u6392\u9664\u8868"),
|
|
1261
|
+
{ className: "dsh-recall-ex-card" },
|
|
1262
|
+
React.createElement("div", { className: "dsh-recall-ex-note" }, error),
|
|
1040
1263
|
React.createElement(
|
|
1041
1264
|
"div",
|
|
1042
|
-
{ className: "dsh-recall-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
value: draft.baseExcludes,
|
|
1054
|
-
disabled: !writable,
|
|
1055
|
-
onChange: (e) => edit("baseExcludes", e.target.value)
|
|
1056
|
-
}),
|
|
1057
|
-
React.createElement("div", { className: "dsh-recall-cfg-hint dsh-recall-cfg-span" }, "\u5185\u7F6E\u89C4\u5219\uFF0C\u6BCF\u4E2A\u5DE5\u4F5C\u533A\u5171\u4EAB\uFF0C\u5EFA\u8BAE\u4FDD\u6301\u9ED8\u8BA4\uFF1Bgitignore \u8BED\u6CD5\u6BCF\u884C\u4E00\u6761\uFF0C\u4F18\u5148\u7EA7\u4F4E\u4E8E\u300C\u6392\u9664\u914D\u7F6E\u300D\u91CC\u7684 exclude.txt\uFF08S3-2 \u6298\u53E0\uFF09")
|
|
1058
|
-
) : null
|
|
1059
|
-
)
|
|
1060
|
-
);
|
|
1061
|
-
}
|
|
1062
|
-
return { ConfigForm };
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
// src/client/exclude-card.ts
|
|
1066
|
-
var EXCLUDE_SUGGESTIONS = ["dist/", "build/", "out/", "coverage/", "*.log", ".env"];
|
|
1067
|
-
function buildExcludeCards(React, util) {
|
|
1068
|
-
const { api } = util;
|
|
1069
|
-
function ExcludeCard(props) {
|
|
1070
|
-
const file = props.file;
|
|
1071
|
-
const [draft, setDraft] = React.useState(file.content || "");
|
|
1072
|
-
const [baseline, setBaseline] = React.useState(file.content || "");
|
|
1073
|
-
const [quick, setQuick] = React.useState("");
|
|
1074
|
-
const [state, setState] = React.useState({ busy: false, message: "", error: false });
|
|
1075
|
-
useAutoDismissMessage(React, state, setState);
|
|
1076
|
-
const dirty = draft !== baseline;
|
|
1077
|
-
function appendPattern(pattern) {
|
|
1078
|
-
setDraft((d) => (d && !d.endsWith("\n") ? d + "\n" : d) + pattern + "\n");
|
|
1079
|
-
}
|
|
1080
|
-
function addQuick() {
|
|
1081
|
-
const t = quick.trim();
|
|
1082
|
-
if (!t) return;
|
|
1083
|
-
appendPattern(t);
|
|
1084
|
-
setQuick("");
|
|
1085
|
-
}
|
|
1086
|
-
function save() {
|
|
1087
|
-
if (state.busy || !dirty) return;
|
|
1088
|
-
setState({ busy: true, message: "\u4FDD\u5B58\u4E2D\u2026", error: false });
|
|
1089
|
-
api("exclude-set", { path: file.path, content: draft }).then((res) => {
|
|
1090
|
-
if (res && res.ok) {
|
|
1091
|
-
setBaseline(draft);
|
|
1092
|
-
setState({ busy: false, message: "\u5DF2\u4FDD\u5B58\uFF0C\u4E0B\u4E00\u6B21\u5FEB\u7167 / \u9884\u89C8 / \u56DE\u9000\u65F6\u751F\u6548", error: false });
|
|
1093
|
-
} else {
|
|
1094
|
-
setState({ busy: false, message: res && (res.message || res.error) || "\u4FDD\u5B58\u5931\u8D25", error: true });
|
|
1095
|
-
}
|
|
1096
|
-
}).catch((error) => {
|
|
1097
|
-
setState({ busy: false, message: String(error), error: true });
|
|
1098
|
-
});
|
|
1099
|
-
}
|
|
1100
|
-
function discard() {
|
|
1101
|
-
if (state.busy) return;
|
|
1102
|
-
setDraft(baseline);
|
|
1103
|
-
setState({ busy: false, message: "", error: false });
|
|
1104
|
-
}
|
|
1105
|
-
const draftLines = draft.split("\n").map((l) => l.trim());
|
|
1106
|
-
const suggestions = EXCLUDE_SUGGESTIONS.filter((s) => draftLines.indexOf(s) < 0);
|
|
1107
|
-
return React.createElement(
|
|
1108
|
-
"div",
|
|
1109
|
-
{ className: "dsh-recall-ex-card" },
|
|
1110
|
-
// 不渲染「快照排除项」内标题(与折叠头「排除配置(exclude.txt)」语义重复);
|
|
1111
|
-
// 多文件场景(降级工作区)靠紧随说明的存储路径行区分,标题文本本就相同、
|
|
1112
|
-
// 不承担区分职责。路径从长句中抽出独立等宽行——Windows 长路径内联在句号
|
|
1113
|
-
// 前会撑出断裂换行(实测丑),独立行 break-all 整齐折行。
|
|
1114
|
-
React.createElement(
|
|
1115
|
-
"div",
|
|
1116
|
-
{ className: "dsh-recall-ex-note" },
|
|
1117
|
-
file.home ? "\u6B64\u914D\u7F6E\u5168\u5C40\u5171\u4EAB\uFF0C\u5BF9\u6240\u6709\u5DE5\u4F5C\u533A\u7684\u5FEB\u7167\u751F\u6548\u3002" : "home \u76EE\u5F55\u4E0D\u53EF\u5199\u65F6\u6B64\u5DE5\u4F5C\u533A\u964D\u7EA7\u5B58\u50A8\uFF0C\u6392\u9664\u914D\u7F6E\u72EC\u7ACB\u751F\u6548\u3002"
|
|
1118
|
-
),
|
|
1119
|
-
React.createElement("div", { className: "dsh-recall-ex-path" }, "\u5B58\u50A8\u4F4D\u7F6E\uFF1A" + file.path),
|
|
1120
|
-
React.createElement("div", { className: "dsh-recall-ex-note" }, "gitignore \u8BED\u6CD5\uFF0C\u4E00\u884C\u4E00\u6761\uFF0C\u652F\u6301 # \u6CE8\u91CA\uFF1B\u547D\u4E2D\u6392\u9664\u7684\u6587\u4EF6\u4E0E\u76EE\u5F55\u4E0D\u8FDB\u5165\u5FEB\u7167\uFF0C\u4E5F\u4E0D\u4F1A\u88AB\u56DE\u9000\u89E6\u78B0\u3002"),
|
|
1121
|
-
React.createElement("textarea", {
|
|
1122
|
-
className: "dsh-recall-ex-area",
|
|
1123
|
-
"aria-label": "\u5FEB\u7167\u6392\u9664\u6A21\u5F0F\u5217\u8868\uFF08gitignore \u8BED\u6CD5\uFF0C\u4E00\u884C\u4E00\u6761\uFF09",
|
|
1124
|
-
value: draft,
|
|
1125
|
-
spellCheck: false,
|
|
1126
|
-
onChange: (e) => setDraft(e.target.value)
|
|
1127
|
-
}),
|
|
1128
|
-
React.createElement(
|
|
1129
|
-
"div",
|
|
1130
|
-
{ className: "dsh-recall-ex-quick" },
|
|
1131
|
-
React.createElement("input", {
|
|
1132
|
-
className: "dsh-recall-ex-input",
|
|
1133
|
-
value: quick,
|
|
1134
|
-
placeholder: "\u8F93\u5165\u8DEF\u5F84\u6216\u6A21\u5F0F\uFF0C\u56DE\u8F66\u5FEB\u901F\u6DFB\u52A0",
|
|
1135
|
-
"aria-label": "\u5FEB\u901F\u6DFB\u52A0\u6392\u9664\u6A21\u5F0F",
|
|
1136
|
-
onChange: (e) => setQuick(e.target.value),
|
|
1137
|
-
onKeyDown: (e) => {
|
|
1138
|
-
if (e.key === "Enter") {
|
|
1139
|
-
e.preventDefault();
|
|
1140
|
-
addQuick();
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
}),
|
|
1144
|
-
React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: addQuick }, "\u6DFB\u52A0"),
|
|
1145
|
-
...suggestions.map((s) => React.createElement("button", {
|
|
1146
|
-
key: "chip-" + s,
|
|
1147
|
-
type: "button",
|
|
1148
|
-
className: "dsh-recall-ex-chip",
|
|
1149
|
-
title: "\u70B9\u51FB\u8FFD\u52A0 " + s,
|
|
1150
|
-
onClick: () => appendPattern(s)
|
|
1151
|
-
}, s))
|
|
1152
|
-
),
|
|
1153
|
-
React.createElement(
|
|
1154
|
-
"div",
|
|
1155
|
-
{ className: "dsh-recall-panel-actions" },
|
|
1156
|
-
state.message ? React.createElement("span", { role: "status", "aria-live": "polite", className: "dsh-recall-ex-status" + (state.error ? " dsh-recall-ex-status-error" : " dsh-recall-ex-status-success") }, (state.error ? "\u9519\u8BEF\uFF1A" : "") + state.message) : null,
|
|
1157
|
-
React.createElement("button", { type: "button", className: "dsh-recall-btn", disabled: !dirty || state.busy, onClick: discard }, "\u653E\u5F03\u4FEE\u6539"),
|
|
1158
|
-
// 保存升主色实心(与配置表单同一约定:每卡唯一主动作,对齐官方插件卡
|
|
1159
|
-
// footer 的 discard 幽灵 + save 实心组合)
|
|
1160
|
-
React.createElement("button", { type: "button", className: "dsh-recall-btn dsh-recall-btn-primary", disabled: !dirty || state.busy, onClick: save }, "\u4FDD\u5B58")
|
|
1161
|
-
)
|
|
1162
|
-
);
|
|
1163
|
-
}
|
|
1164
|
-
function ExcludeFilesSection() {
|
|
1165
|
-
const [files, setFiles] = React.useState(null);
|
|
1166
|
-
const [error, setError] = React.useState("");
|
|
1167
|
-
function load() {
|
|
1168
|
-
api("exclude-get", {}).then((res) => {
|
|
1169
|
-
if (res && res.ok) {
|
|
1170
|
-
setFiles(res.files || []);
|
|
1171
|
-
setError("");
|
|
1172
|
-
return;
|
|
1173
|
-
}
|
|
1174
|
-
if (res && res.unsupported) {
|
|
1175
|
-
setError("\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u5FEB\u7167\u529F\u80FD\uFF0C\u6392\u9664\u914D\u7F6E\u4E0D\u53EF\u7528\u3002");
|
|
1176
|
-
return;
|
|
1177
|
-
}
|
|
1178
|
-
setError(res && (res.message || res.error) || "\u65E0\u6CD5\u8BFB\u53D6\u6392\u9664\u914D\u7F6E");
|
|
1179
|
-
}).catch((e) => setError(String(e)));
|
|
1180
|
-
}
|
|
1181
|
-
React.useEffect(() => {
|
|
1182
|
-
load();
|
|
1183
|
-
}, []);
|
|
1184
|
-
if (error) {
|
|
1265
|
+
{ className: "dsh-recall-panel-actions" },
|
|
1266
|
+
React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: load }, "\u91CD\u8BD5")
|
|
1267
|
+
)
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
if (files === null) {
|
|
1271
|
+
return React.createElement("div", { className: "dsh-recall-ex-note" }, "\u6B63\u5728\u52A0\u8F7D\u6392\u9664\u914D\u7F6E\u2026");
|
|
1272
|
+
}
|
|
1273
|
+
if (!files.length) {
|
|
1274
|
+
return React.createElement("div", { className: "dsh-recall-ex-note" }, "\u5C1A\u672A\u521B\u5EFA\u4EFB\u4F55\u5FEB\u7167\u5B58\u50A8\uFF1A\u5728\u4EFB\u610F\u5DE5\u4F5C\u533A\u53D1\u9001\u4E00\u6761\u6D88\u606F\u540E\uFF0C\u8FD9\u91CC\u4F1A\u51FA\u73B0\u53EF\u7F16\u8F91\u7684\u6392\u9664\u914D\u7F6E\u3002");
|
|
1275
|
+
}
|
|
1185
1276
|
return React.createElement(
|
|
1186
1277
|
"div",
|
|
1187
1278
|
{ className: "dsh-recall-ex-card" },
|
|
1188
|
-
React.createElement(
|
|
1189
|
-
React.createElement(
|
|
1190
|
-
"div",
|
|
1191
|
-
{ className: "dsh-recall-panel-actions" },
|
|
1192
|
-
React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: load }, "\u91CD\u8BD5")
|
|
1193
|
-
)
|
|
1279
|
+
...files.map((f) => React.createElement(ExcludeCard, { key: f.path, file: f }))
|
|
1194
1280
|
);
|
|
1195
1281
|
}
|
|
1196
|
-
|
|
1197
|
-
return React.createElement("div", { className: "dsh-recall-ex-note" }, "\u6B63\u5728\u52A0\u8F7D\u6392\u9664\u914D\u7F6E\u2026");
|
|
1198
|
-
}
|
|
1199
|
-
if (!files.length) {
|
|
1200
|
-
return React.createElement("div", { className: "dsh-recall-ex-note" }, "\u5C1A\u672A\u521B\u5EFA\u4EFB\u4F55\u5FEB\u7167\u5B58\u50A8\uFF1A\u5728\u4EFB\u610F\u5DE5\u4F5C\u533A\u53D1\u9001\u4E00\u6761\u6D88\u606F\u540E\uFF0C\u8FD9\u91CC\u4F1A\u51FA\u73B0\u53EF\u7F16\u8F91\u7684\u6392\u9664\u914D\u7F6E\u3002");
|
|
1201
|
-
}
|
|
1202
|
-
return React.createElement(
|
|
1203
|
-
"div",
|
|
1204
|
-
{ className: "dsh-recall-ex-card" },
|
|
1205
|
-
...files.map((f) => React.createElement(ExcludeCard, { key: f.path, file: f }))
|
|
1206
|
-
);
|
|
1282
|
+
return { ExcludeFilesSection };
|
|
1207
1283
|
}
|
|
1208
|
-
return { ExcludeFilesSection };
|
|
1209
|
-
}
|
|
1210
1284
|
|
|
1211
|
-
// src/client/snapshot-manager.ts
|
|
1212
|
-
function groupByLineage(ids, lineage) {
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
}
|
|
1225
|
-
const idSet = new Set((ids || []).map((v) => String(v)));
|
|
1226
|
-
const result = /* @__PURE__ */ new Map();
|
|
1227
|
-
const assigned = /* @__PURE__ */ new Set();
|
|
1228
|
-
for (const id of idSet) {
|
|
1229
|
-
if (assigned.has(id)) continue;
|
|
1230
|
-
let root = id;
|
|
1231
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1232
|
-
while (childOf.has(root) && idSet.has(childOf.get(root)) && !seen.has(root)) {
|
|
1233
|
-
seen.add(root);
|
|
1234
|
-
root = childOf.get(root);
|
|
1235
|
-
}
|
|
1236
|
-
const chain = [];
|
|
1237
|
-
const queue = [root];
|
|
1238
|
-
while (queue.length) {
|
|
1239
|
-
const cur = queue.shift();
|
|
1240
|
-
if (!cur || !idSet.has(cur) || assigned.has(cur)) continue;
|
|
1241
|
-
chain.push(cur);
|
|
1242
|
-
assigned.add(cur);
|
|
1243
|
-
for (const k of childrenOf.get(cur) || []) queue.push(k);
|
|
1244
|
-
}
|
|
1245
|
-
if (chain.length > 1) {
|
|
1246
|
-
chain.forEach((sid, i) => result.set(sid, { family: chain, index: i + 1 }));
|
|
1285
|
+
// src/client/snapshot-manager.ts
|
|
1286
|
+
function groupByLineage(ids, lineage) {
|
|
1287
|
+
const childOf = /* @__PURE__ */ new Map();
|
|
1288
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
1289
|
+
for (const e of lineage || []) {
|
|
1290
|
+
if (e && e.childId && e.parentId) {
|
|
1291
|
+
const child = String(e.childId);
|
|
1292
|
+
const parent = String(e.parentId);
|
|
1293
|
+
childOf.set(child, parent);
|
|
1294
|
+
const kids = childrenOf.get(parent) || [];
|
|
1295
|
+
kids.push(child);
|
|
1296
|
+
childrenOf.set(parent, kids);
|
|
1297
|
+
}
|
|
1247
1298
|
}
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
if (!missing.length) {
|
|
1271
|
-
setTitlesPending(false);
|
|
1272
|
-
return;
|
|
1299
|
+
const idSet = new Set((ids || []).map((v) => String(v)));
|
|
1300
|
+
const result = /* @__PURE__ */ new Map();
|
|
1301
|
+
const assigned = /* @__PURE__ */ new Set();
|
|
1302
|
+
for (const id of idSet) {
|
|
1303
|
+
if (assigned.has(id)) continue;
|
|
1304
|
+
let root = id;
|
|
1305
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1306
|
+
while (childOf.has(root) && idSet.has(childOf.get(root)) && !seen.has(root)) {
|
|
1307
|
+
seen.add(root);
|
|
1308
|
+
root = childOf.get(root);
|
|
1309
|
+
}
|
|
1310
|
+
const chain = [];
|
|
1311
|
+
const queue = [root];
|
|
1312
|
+
while (queue.length) {
|
|
1313
|
+
const cur = queue.shift();
|
|
1314
|
+
if (!cur || !idSet.has(cur) || assigned.has(cur)) continue;
|
|
1315
|
+
chain.push(cur);
|
|
1316
|
+
assigned.add(cur);
|
|
1317
|
+
for (const k of childrenOf.get(cur) || []) queue.push(k);
|
|
1318
|
+
}
|
|
1319
|
+
if (chain.length > 1) {
|
|
1320
|
+
chain.forEach((sid, i) => result.set(sid, { family: chain, index: i + 1 }));
|
|
1273
1321
|
}
|
|
1274
|
-
setTitlesPending(true);
|
|
1275
|
-
api("manage", { op: "titles", sessionIds: missing }).then((res) => {
|
|
1276
|
-
const map = res && res.ok ? res.titles : null;
|
|
1277
|
-
if (map) {
|
|
1278
|
-
setItems((prev) => (prev || []).map((it) => it.sessionId && map[it.sessionId] ? Object.assign({}, it, { sessionTitle: map[it.sessionId] }) : it));
|
|
1279
|
-
}
|
|
1280
|
-
setTitlesPending(false);
|
|
1281
|
-
}).catch(() => setTitlesPending(false));
|
|
1282
1322
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1323
|
+
return result;
|
|
1324
|
+
}
|
|
1325
|
+
function buildSnapshotManager(React, util, sessionsSvc) {
|
|
1326
|
+
const { api, clockText: clockText2, sizeText: sizeText2, buildTree: buildTree2 } = util;
|
|
1327
|
+
function ManageCard() {
|
|
1328
|
+
const [items, setItems] = React.useState(null);
|
|
1329
|
+
const [usage, setUsage] = React.useState(null);
|
|
1330
|
+
const [errors, setErrors] = React.useState(null);
|
|
1331
|
+
const [state, setState] = React.useState({ busy: false, message: "", error: false });
|
|
1332
|
+
useAutoDismissMessage(React, state, setState);
|
|
1333
|
+
const [limit, setLimit] = React.useState(200);
|
|
1334
|
+
const [total, setTotal] = React.useState(0);
|
|
1335
|
+
const [health, setHealth] = React.useState(null);
|
|
1336
|
+
const [query, setQuery] = React.useState("");
|
|
1337
|
+
const [showAllErrors, setShowAllErrors] = React.useState(false);
|
|
1338
|
+
const [titlesPending, setTitlesPending] = React.useState(false);
|
|
1339
|
+
const [lineage, setLineage] = React.useState([]);
|
|
1340
|
+
function fetchTitles(list) {
|
|
1341
|
+
const missing = Array.from(new Set(
|
|
1342
|
+
(list || []).filter((it) => it.sessionId && !it.sessionTitle).map((it) => it.sessionId)
|
|
1343
|
+
)).slice(0, 100);
|
|
1344
|
+
if (!missing.length) {
|
|
1345
|
+
setTitlesPending(false);
|
|
1346
|
+
return;
|
|
1290
1347
|
}
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
fetchTitles(res2.items || []);
|
|
1308
|
-
fetchMessages(res2.items || []);
|
|
1309
|
-
}
|
|
1310
|
-
}).catch(() => {
|
|
1311
|
-
});
|
|
1348
|
+
setTitlesPending(true);
|
|
1349
|
+
api("manage", { op: "titles", sessionIds: missing }).then((res) => {
|
|
1350
|
+
const map = res && res.ok ? res.titles : null;
|
|
1351
|
+
if (map) {
|
|
1352
|
+
setItems((prev) => (prev || []).map((it) => it.sessionId && map[it.sessionId] ? Object.assign({}, it, { sessionTitle: map[it.sessionId] }) : it));
|
|
1353
|
+
}
|
|
1354
|
+
setTitlesPending(false);
|
|
1355
|
+
}).catch(() => setTitlesPending(false));
|
|
1356
|
+
}
|
|
1357
|
+
function fetchMessages(list) {
|
|
1358
|
+
const requests = (list || []).filter((it) => it.sessionId && it.id && !Object.prototype.hasOwnProperty.call(it, "messageText")).map((it) => ({ sessionId: it.sessionId, messageId: it.id })).slice(0, 200);
|
|
1359
|
+
if (!requests.length) return;
|
|
1360
|
+
api("manage", { op: "messages", requests }).then((res) => {
|
|
1361
|
+
const map = res && res.ok ? res.messageTexts : null;
|
|
1362
|
+
if (map) {
|
|
1363
|
+
setItems((prev) => (prev || []).map((it) => it.id && Object.prototype.hasOwnProperty.call(map, it.id) ? Object.assign({}, it, { messageText: map[it.id] }) : it));
|
|
1312
1364
|
}
|
|
1313
|
-
}
|
|
1314
|
-
api("manage", { op: "lineage" }).then((res2) => {
|
|
1315
|
-
if (res2 && res2.ok && Array.isArray(res2.lineage)) setLineage(res2.lineage);
|
|
1316
1365
|
}).catch(() => {
|
|
1317
1366
|
});
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1367
|
+
}
|
|
1368
|
+
function refresh(overLimit) {
|
|
1369
|
+
const useLimit = overLimit || limit;
|
|
1370
|
+
api("manage", { op: "list", limit: useLimit }).then((res) => {
|
|
1371
|
+
if (res && res.ok) {
|
|
1372
|
+
setItems(res.items || []);
|
|
1373
|
+
setTotal(typeof res.total === "number" ? res.total : (res.items || []).length);
|
|
1374
|
+
fetchTitles(res.items || []);
|
|
1375
|
+
fetchMessages(res.items || []);
|
|
1376
|
+
if (res.stale) {
|
|
1377
|
+
api("manage", { op: "list", limit: useLimit }).then((res2) => {
|
|
1378
|
+
if (res2 && res2.ok && !res2.stale) {
|
|
1379
|
+
setItems(res2.items || []);
|
|
1380
|
+
setTotal(typeof res2.total === "number" ? res2.total : (res2.items || []).length);
|
|
1381
|
+
fetchTitles(res2.items || []);
|
|
1382
|
+
fetchMessages(res2.items || []);
|
|
1383
|
+
}
|
|
1384
|
+
}).catch(() => {
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1322
1387
|
}
|
|
1388
|
+
api("manage", { op: "lineage" }).then((res2) => {
|
|
1389
|
+
if (res2 && res2.ok && Array.isArray(res2.lineage)) setLineage(res2.lineage);
|
|
1390
|
+
}).catch(() => {
|
|
1391
|
+
});
|
|
1392
|
+
api("manage", { op: "usage" }).then((res2) => {
|
|
1393
|
+
if (res2 && res2.ok) {
|
|
1394
|
+
setUsage(res2.bytes || 0);
|
|
1395
|
+
setHealth({ gitAvailable: res2.gitAvailable !== false, homeStores: res2.homeStores || 0, fallbackStores: res2.fallbackStores || 0 });
|
|
1396
|
+
}
|
|
1397
|
+
}).catch(() => {
|
|
1398
|
+
});
|
|
1399
|
+
api("status", {}).then((res2) => {
|
|
1400
|
+
if (res2 && res2.ok) setErrors(res2.errors || []);
|
|
1401
|
+
}).catch(() => {
|
|
1402
|
+
});
|
|
1323
1403
|
}).catch(() => {
|
|
1404
|
+
api("manage", { op: "usage" }).then((res) => {
|
|
1405
|
+
if (res && res.ok) {
|
|
1406
|
+
setUsage(res.bytes || 0);
|
|
1407
|
+
setHealth({ gitAvailable: res.gitAvailable !== false, homeStores: res.homeStores || 0, fallbackStores: res.fallbackStores || 0 });
|
|
1408
|
+
}
|
|
1409
|
+
}).catch(() => {
|
|
1410
|
+
});
|
|
1411
|
+
api("status", {}).then((res) => {
|
|
1412
|
+
if (res && res.ok) setErrors(res.errors || []);
|
|
1413
|
+
}).catch(() => {
|
|
1414
|
+
});
|
|
1324
1415
|
});
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1416
|
+
}
|
|
1417
|
+
React.useEffect(() => {
|
|
1418
|
+
refresh();
|
|
1419
|
+
}, []);
|
|
1420
|
+
function clearErrors() {
|
|
1421
|
+
setErrors([]);
|
|
1422
|
+
api("status", { op: "clear" }).catch(() => {
|
|
1328
1423
|
});
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1424
|
+
}
|
|
1425
|
+
function run(op, extra, doneText) {
|
|
1426
|
+
if (state.busy) return;
|
|
1427
|
+
setState({ busy: true, message: "\u6267\u884C\u4E2D\u2026", error: false });
|
|
1428
|
+
api("manage", Object.assign({ op }, extra || {})).then((res) => {
|
|
1331
1429
|
if (res && res.ok) {
|
|
1332
|
-
|
|
1333
|
-
|
|
1430
|
+
const deleted = res.deleted;
|
|
1431
|
+
setState({ busy: false, message: typeof deleted === "number" ? "\u5DF2\u5220\u9664 " + deleted + " \u6761\u5FEB\u7167" : doneText, error: false });
|
|
1432
|
+
refresh();
|
|
1433
|
+
} else {
|
|
1434
|
+
setState({ busy: false, message: res && (res.message || res.error) || "\u64CD\u4F5C\u5931\u8D25", error: true });
|
|
1334
1435
|
}
|
|
1335
|
-
}).catch(() => {
|
|
1436
|
+
}).catch((e) => setState({ busy: false, message: String(e), error: true }));
|
|
1437
|
+
}
|
|
1438
|
+
const [expanded, setExpanded] = React.useState(() => /* @__PURE__ */ new Set());
|
|
1439
|
+
const [confirming, setConfirming] = React.useState(null);
|
|
1440
|
+
function ConfirmRow(props) {
|
|
1441
|
+
return React.createElement(
|
|
1442
|
+
"div",
|
|
1443
|
+
{ className: "dsh-recall-tree-confirm" },
|
|
1444
|
+
props.text,
|
|
1445
|
+
React.createElement("button", { type: "button", className: "dsh-recall-ex-chip dsh-recall-ex-chip-danger", onClick: props.onConfirm }, "\u786E\u8BA4"),
|
|
1446
|
+
React.createElement("button", { type: "button", className: "dsh-recall-ex-chip", onClick: props.onCancel }, "\u53D6\u6D88")
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
function renderDeleteAllConfirm() {
|
|
1450
|
+
if (!confirming || confirming.kind !== "all") return null;
|
|
1451
|
+
return React.createElement(ConfirmRow, {
|
|
1452
|
+
text: "\u786E\u8BA4\u5220\u9664\u6240\u6709\u5DE5\u4F5C\u533A\u7684\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002",
|
|
1453
|
+
onConfirm: () => {
|
|
1454
|
+
setConfirming(null);
|
|
1455
|
+
run("deleteAll", {}, "\u5DF2\u6E05\u7A7A\u5168\u90E8\u5FEB\u7167");
|
|
1456
|
+
},
|
|
1457
|
+
onCancel: () => setConfirming(null)
|
|
1336
1458
|
});
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1459
|
+
}
|
|
1460
|
+
function toggle(key) {
|
|
1461
|
+
setExpanded((prev) => {
|
|
1462
|
+
const next = new Set(prev);
|
|
1463
|
+
if (next.has(key)) next.delete(key);
|
|
1464
|
+
else next.add(key);
|
|
1465
|
+
return next;
|
|
1340
1466
|
});
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
api("manage", Object.assign({ op }, extra || {})).then((res) => {
|
|
1355
|
-
if (res && res.ok) {
|
|
1356
|
-
const deleted = res.deleted;
|
|
1357
|
-
setState({ busy: false, message: typeof deleted === "number" ? "\u5DF2\u5220\u9664 " + deleted + " \u6761\u5FEB\u7167" : doneText, error: false });
|
|
1358
|
-
refresh();
|
|
1359
|
-
} else {
|
|
1360
|
-
setState({ busy: false, message: res && (res.message || res.error) || "\u64CD\u4F5C\u5931\u8D25", error: true });
|
|
1467
|
+
}
|
|
1468
|
+
const q = query.trim().toLowerCase();
|
|
1469
|
+
const filteredItems = q ? (items || []).filter(
|
|
1470
|
+
(it) => (it.workspace || "").toLowerCase().indexOf(q) >= 0 || (it.sessionTitle || "").toLowerCase().indexOf(q) >= 0 || (it.messageText || "").toLowerCase().indexOf(q) >= 0 || String(it.id || "").toLowerCase().indexOf(q) >= 0
|
|
1471
|
+
) : items;
|
|
1472
|
+
const tree = buildTree2(filteredItems);
|
|
1473
|
+
const allSessionIds = Array.from(new Set((items || []).map((it) => it.sessionId).filter(Boolean)));
|
|
1474
|
+
const versionMap = groupByLineage(allSessionIds, lineage);
|
|
1475
|
+
let listById = null;
|
|
1476
|
+
try {
|
|
1477
|
+
if (sessionsSvc && sessionsSvc.list && typeof sessionsSvc.list.getSnapshot === "function") {
|
|
1478
|
+
const snapshot = sessionsSvc.list.getSnapshot();
|
|
1479
|
+
listById = snapshot && snapshot.byId || null;
|
|
1361
1480
|
}
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
const [expanded, setExpanded] = React.useState(() => /* @__PURE__ */ new Set());
|
|
1365
|
-
const [confirming, setConfirming] = React.useState(null);
|
|
1366
|
-
function ConfirmRow(props) {
|
|
1367
|
-
return React.createElement(
|
|
1368
|
-
"div",
|
|
1369
|
-
{ className: "dsh-recall-tree-confirm" },
|
|
1370
|
-
props.text,
|
|
1371
|
-
React.createElement("button", { type: "button", className: "dsh-recall-ex-chip dsh-recall-ex-chip-danger", onClick: props.onConfirm }, "\u786E\u8BA4"),
|
|
1372
|
-
React.createElement("button", { type: "button", className: "dsh-recall-ex-chip", onClick: props.onCancel }, "\u53D6\u6D88")
|
|
1373
|
-
);
|
|
1374
|
-
}
|
|
1375
|
-
function renderDeleteAllConfirm() {
|
|
1376
|
-
if (!confirming || confirming.kind !== "all") return null;
|
|
1377
|
-
return React.createElement(ConfirmRow, {
|
|
1378
|
-
text: "\u786E\u8BA4\u5220\u9664\u6240\u6709\u5DE5\u4F5C\u533A\u7684\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002",
|
|
1379
|
-
onConfirm: () => {
|
|
1380
|
-
setConfirming(null);
|
|
1381
|
-
run("deleteAll", {}, "\u5DF2\u6E05\u7A7A\u5168\u90E8\u5FEB\u7167");
|
|
1382
|
-
},
|
|
1383
|
-
onCancel: () => setConfirming(null)
|
|
1384
|
-
});
|
|
1385
|
-
}
|
|
1386
|
-
function toggle(key) {
|
|
1387
|
-
setExpanded((prev) => {
|
|
1388
|
-
const next = new Set(prev);
|
|
1389
|
-
if (next.has(key)) next.delete(key);
|
|
1390
|
-
else next.add(key);
|
|
1391
|
-
return next;
|
|
1392
|
-
});
|
|
1393
|
-
}
|
|
1394
|
-
const q = query.trim().toLowerCase();
|
|
1395
|
-
const filteredItems = q ? (items || []).filter(
|
|
1396
|
-
(it) => (it.workspace || "").toLowerCase().indexOf(q) >= 0 || (it.sessionTitle || "").toLowerCase().indexOf(q) >= 0 || (it.messageText || "").toLowerCase().indexOf(q) >= 0 || String(it.id || "").toLowerCase().indexOf(q) >= 0
|
|
1397
|
-
) : items;
|
|
1398
|
-
const tree = buildTree2(filteredItems);
|
|
1399
|
-
const allSessionIds = Array.from(new Set((items || []).map((it) => it.sessionId).filter(Boolean)));
|
|
1400
|
-
const versionMap = groupByLineage(allSessionIds, lineage);
|
|
1401
|
-
let listById = null;
|
|
1402
|
-
try {
|
|
1403
|
-
if (sessionsSvc && sessionsSvc.list && typeof sessionsSvc.list.getSnapshot === "function") {
|
|
1404
|
-
const snapshot = sessionsSvc.list.getSnapshot();
|
|
1405
|
-
listById = snapshot && snapshot.byId || null;
|
|
1481
|
+
} catch (e) {
|
|
1482
|
+
listById = null;
|
|
1406
1483
|
}
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
setConfirming(null)
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
const title = text || it.id;
|
|
1429
|
-
const label = text ? clockText2(it.time) + " " + text : clockText2(it.time) + " " + it.id.slice(0, 12) + "\u2026";
|
|
1430
|
-
return React.createElement(
|
|
1431
|
-
"div",
|
|
1432
|
-
{ className: "dsh-recall-tree-node", key },
|
|
1433
|
-
React.createElement(
|
|
1484
|
+
function confirmDelete(kind, key, extra, text) {
|
|
1485
|
+
setConfirming({ kind, key, extra, text });
|
|
1486
|
+
}
|
|
1487
|
+
function renderConfirm(kind, key, extra, text) {
|
|
1488
|
+
if (!confirming || confirming.kind !== kind || confirming.key !== key) return null;
|
|
1489
|
+
return React.createElement(ConfirmRow, {
|
|
1490
|
+
text,
|
|
1491
|
+
onConfirm: () => {
|
|
1492
|
+
const c = confirming;
|
|
1493
|
+
setConfirming(null);
|
|
1494
|
+
run("delete", c.extra, "\u5DF2\u5220\u9664");
|
|
1495
|
+
},
|
|
1496
|
+
onCancel: () => setConfirming(null)
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
function renderLeaf(it) {
|
|
1500
|
+
const key = "snap-" + it.id;
|
|
1501
|
+
const text = it.messageText;
|
|
1502
|
+
const title = text || it.id;
|
|
1503
|
+
const label = text ? clockText2(it.time) + " " + text : clockText2(it.time) + " " + it.id.slice(0, 12) + "\u2026";
|
|
1504
|
+
return React.createElement(
|
|
1434
1505
|
"div",
|
|
1435
|
-
{ className: "dsh-recall-tree-
|
|
1436
|
-
React.createElement("span", { className: "dsh-recall-tree-toggle-placeholder" }),
|
|
1506
|
+
{ className: "dsh-recall-tree-node", key },
|
|
1437
1507
|
React.createElement(
|
|
1438
|
-
"
|
|
1439
|
-
{ className: "dsh-recall-tree-
|
|
1440
|
-
React.createElement("span", { className: "dsh-recall-tree-
|
|
1508
|
+
"div",
|
|
1509
|
+
{ className: "dsh-recall-tree-row", title },
|
|
1510
|
+
React.createElement("span", { className: "dsh-recall-tree-toggle-placeholder" }),
|
|
1511
|
+
React.createElement(
|
|
1512
|
+
"span",
|
|
1513
|
+
{ className: "dsh-recall-tree-label" },
|
|
1514
|
+
React.createElement("span", { className: "dsh-recall-tree-title" }, label)
|
|
1515
|
+
),
|
|
1516
|
+
React.createElement("button", {
|
|
1517
|
+
type: "button",
|
|
1518
|
+
className: "dsh-recall-ex-chip",
|
|
1519
|
+
title: "\u5220\u9664\u8BE5\u5FEB\u7167\uFF08tag \u4E0E\u7D22\u5F15\u6761\u76EE\uFF09",
|
|
1520
|
+
onClick: () => confirmDelete("snapshot", key, { messageId: it.id, root: it.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
|
|
1521
|
+
}, "\u5220\u9664")
|
|
1441
1522
|
),
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
)
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
function renderSession(s) {
|
|
1453
|
-
const key = "session-" + (s.root || "") + "-" + s.sessionId;
|
|
1454
|
-
const open = expanded.has(key);
|
|
1455
|
-
const label = s.title || (titlesPending && s.sessionId ? "\u2026" : "\uFF08\u5DF2\u5220\u9664\u4F1A\u8BDD\uFF09");
|
|
1456
|
-
const version = s.sessionId ? versionMap.get(String(s.sessionId)) : null;
|
|
1457
|
-
const switchable = Boolean(s.sessionId && listById && listById[s.sessionId]);
|
|
1458
|
-
return React.createElement(
|
|
1459
|
-
"div",
|
|
1460
|
-
{ className: "dsh-recall-tree-node", key },
|
|
1461
|
-
React.createElement(
|
|
1523
|
+
renderConfirm("snapshot", key, { messageId: it.id, root: it.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
function renderSession(s) {
|
|
1527
|
+
const key = "session-" + (s.root || "") + "-" + s.sessionId;
|
|
1528
|
+
const open = expanded.has(key);
|
|
1529
|
+
const label = s.title || (titlesPending && s.sessionId ? "\u2026" : "\uFF08\u5DF2\u5220\u9664\u4F1A\u8BDD\uFF09");
|
|
1530
|
+
const version = s.sessionId ? versionMap.get(String(s.sessionId)) : null;
|
|
1531
|
+
const switchable = Boolean(s.sessionId && listById && listById[s.sessionId]);
|
|
1532
|
+
return React.createElement(
|
|
1462
1533
|
"div",
|
|
1463
|
-
{ className: "dsh-recall-tree-
|
|
1464
|
-
// V2:折叠钮 span→button——Tab/Enter/Space 可达,读屏经 aria-expanded
|
|
1465
|
-
// 与 aria-label 播报展开语义与节点名;CSS 已做 button 重置防视觉回归。
|
|
1466
|
-
React.createElement("button", {
|
|
1467
|
-
type: "button",
|
|
1468
|
-
className: "dsh-recall-tree-toggle",
|
|
1469
|
-
"aria-expanded": open,
|
|
1470
|
-
"aria-label": (open ? "\u6536\u8D77" : "\u5C55\u5F00") + "\uFF1A" + label,
|
|
1471
|
-
onClick: () => toggle(key)
|
|
1472
|
-
}, open ? "\u25BE" : "\u25B8"),
|
|
1534
|
+
{ className: "dsh-recall-tree-node", key },
|
|
1473
1535
|
React.createElement(
|
|
1474
|
-
"
|
|
1475
|
-
{ className: "dsh-recall-tree-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
React.createElement("
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1536
|
+
"div",
|
|
1537
|
+
{ className: "dsh-recall-tree-row" },
|
|
1538
|
+
// V2:折叠钮 span→button——Tab/Enter/Space 可达,读屏经 aria-expanded
|
|
1539
|
+
// 与 aria-label 播报展开语义与节点名;CSS 已做 button 重置防视觉回归。
|
|
1540
|
+
React.createElement("button", {
|
|
1541
|
+
type: "button",
|
|
1542
|
+
className: "dsh-recall-tree-toggle",
|
|
1543
|
+
"aria-expanded": open,
|
|
1544
|
+
"aria-label": (open ? "\u6536\u8D77" : "\u5C55\u5F00") + "\uFF1A" + label,
|
|
1545
|
+
onClick: () => toggle(key)
|
|
1546
|
+
}, open ? "\u25BE" : "\u25B8"),
|
|
1547
|
+
React.createElement(
|
|
1548
|
+
"span",
|
|
1549
|
+
{ className: "dsh-recall-tree-label", title: s.sessionId || "" },
|
|
1550
|
+
React.createElement("span", { className: "dsh-recall-tree-title" }, label),
|
|
1551
|
+
version ? React.createElement("span", { className: "dsh-recall-tree-meta", title: "\u7248\u672C\u5BB6\u65CF\uFF1A" + version.family.join(" \u2192 ") }, "v" + version.index + "/" + version.family.length) : null,
|
|
1552
|
+
React.createElement("span", { className: "dsh-recall-tree-meta" }, s.items.length + " \u6761")
|
|
1553
|
+
),
|
|
1554
|
+
switchable ? React.createElement("button", {
|
|
1555
|
+
type: "button",
|
|
1556
|
+
className: "dsh-recall-ex-chip",
|
|
1557
|
+
title: "\u5207\u6362\u5230\u8BE5\u7248\u672C\u4F1A\u8BDD",
|
|
1558
|
+
onClick: () => {
|
|
1559
|
+
try {
|
|
1560
|
+
sessionsSvc.open(s.sessionId);
|
|
1561
|
+
} catch (e) {
|
|
1562
|
+
}
|
|
1488
1563
|
}
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1564
|
+
}, "\u5207\u6362") : null,
|
|
1565
|
+
s.sessionId ? React.createElement("button", {
|
|
1566
|
+
type: "button",
|
|
1567
|
+
className: "dsh-recall-ex-chip",
|
|
1568
|
+
title: "\u5220\u9664\u8BE5\u4F1A\u8BDD\u5168\u90E8\u5FEB\u7167",
|
|
1569
|
+
onClick: () => confirmDelete("session", key, { scope: "session", sessionId: s.sessionId, root: s.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u4F1A\u8BDD\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
|
|
1570
|
+
}, "\u5220\u9664") : null
|
|
1571
|
+
),
|
|
1572
|
+
open ? React.createElement("div", { className: "dsh-recall-tree-children" }, ...s.items.map(renderLeaf)) : null,
|
|
1573
|
+
s.sessionId ? renderConfirm("session", key, { scope: "session", sessionId: s.sessionId, root: s.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u4F1A\u8BDD\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002") : null
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1576
|
+
function renderWorkspace(ws) {
|
|
1577
|
+
const key = "ws-" + ws.root;
|
|
1578
|
+
const open = expanded.has(key);
|
|
1579
|
+
const sessionCount = ws.sessions.length;
|
|
1580
|
+
const snapCount = ws.sessions.reduce((n, s) => n + s.items.length, 0);
|
|
1581
|
+
return React.createElement(
|
|
1582
|
+
"div",
|
|
1583
|
+
{ className: "dsh-recall-tree-node", key },
|
|
1584
|
+
React.createElement(
|
|
1585
|
+
"div",
|
|
1586
|
+
{ className: "dsh-recall-tree-row" },
|
|
1587
|
+
// V2:工作区折叠钮同 renderSession——span→button 键盘化,aria 语义并列播报
|
|
1588
|
+
React.createElement("button", {
|
|
1589
|
+
type: "button",
|
|
1590
|
+
className: "dsh-recall-tree-toggle",
|
|
1591
|
+
"aria-expanded": open,
|
|
1592
|
+
"aria-label": (open ? "\u6536\u8D77" : "\u5C55\u5F00") + "\uFF1A" + ws.name,
|
|
1593
|
+
onClick: () => toggle(key)
|
|
1594
|
+
}, open ? "\u25BE" : "\u25B8"),
|
|
1595
|
+
React.createElement(
|
|
1596
|
+
"span",
|
|
1597
|
+
{ className: "dsh-recall-tree-label", title: ws.root || "" },
|
|
1598
|
+
React.createElement("span", { className: "dsh-recall-tree-name" }, ws.name),
|
|
1599
|
+
React.createElement("span", { className: "dsh-recall-tree-meta" }, sessionCount + " \u4F1A\u8BDD / " + snapCount + " \u5FEB\u7167")
|
|
1600
|
+
),
|
|
1601
|
+
ws.root ? React.createElement("button", {
|
|
1602
|
+
type: "button",
|
|
1603
|
+
className: "dsh-recall-ex-chip",
|
|
1604
|
+
title: "\u5220\u9664\u8BE5\u5DE5\u4F5C\u533A\u5168\u90E8\u5FEB\u7167",
|
|
1605
|
+
onClick: () => confirmDelete("workspace", key, { scope: "workspace", root: ws.root }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5DE5\u4F5C\u533A\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
|
|
1606
|
+
}, "\u5220\u9664") : null
|
|
1607
|
+
),
|
|
1608
|
+
open ? React.createElement("div", { className: "dsh-recall-tree-children" }, ...ws.sessions.map(renderSession)) : null,
|
|
1609
|
+
ws.root ? renderConfirm("workspace", key, { scope: "workspace", root: ws.root }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5DE5\u4F5C\u533A\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002") : null
|
|
1610
|
+
);
|
|
1611
|
+
}
|
|
1612
|
+
const treeNodes = tree.map(renderWorkspace);
|
|
1613
|
+
const loaded = items ? items.length : null;
|
|
1614
|
+
const countText = loaded === null ? "\u5171 \u2026 \u6761\u5FEB\u7167" : "\u5171 " + total + " \u6761\u5FEB\u7167" + (limit < total ? "\uFF08\u5F53\u524D\u663E\u793A\u6700\u65B0 " + loaded + " \u6761\uFF09" : "");
|
|
1615
|
+
function loadMore() {
|
|
1616
|
+
const next = Math.min(Math.max(total, limit), 2e3);
|
|
1617
|
+
if (next <= limit) return;
|
|
1618
|
+
setLimit(next);
|
|
1619
|
+
refresh(next);
|
|
1620
|
+
}
|
|
1507
1621
|
return React.createElement(
|
|
1508
1622
|
"div",
|
|
1509
|
-
{ className: "dsh-recall-
|
|
1623
|
+
{ className: "dsh-recall-ex-card" },
|
|
1624
|
+
// 不再渲染「快照管理」内标题:折叠头(SectionToggle)已承担分区标题
|
|
1625
|
+
// 角色,展开后再出现同名大标题是纯重复(实测观感噪音);计数 note 紧跟
|
|
1626
|
+
// 折叠头,信息层级 = 折叠头(标题)→ 计数/健康(摘要)→ 搜索 → 树。
|
|
1510
1627
|
React.createElement(
|
|
1511
1628
|
"div",
|
|
1512
|
-
{ className: "dsh-recall-
|
|
1513
|
-
|
|
1514
|
-
React.createElement("button", {
|
|
1515
|
-
type: "button",
|
|
1516
|
-
className: "dsh-recall-tree-toggle",
|
|
1517
|
-
"aria-expanded": open,
|
|
1518
|
-
"aria-label": (open ? "\u6536\u8D77" : "\u5C55\u5F00") + "\uFF1A" + ws.name,
|
|
1519
|
-
onClick: () => toggle(key)
|
|
1520
|
-
}, open ? "\u25BE" : "\u25B8"),
|
|
1521
|
-
React.createElement(
|
|
1522
|
-
"span",
|
|
1523
|
-
{ className: "dsh-recall-tree-label", title: ws.root || "" },
|
|
1524
|
-
React.createElement("span", { className: "dsh-recall-tree-name" }, ws.name),
|
|
1525
|
-
React.createElement("span", { className: "dsh-recall-tree-meta" }, sessionCount + " \u4F1A\u8BDD / " + snapCount + " \u5FEB\u7167")
|
|
1526
|
-
),
|
|
1527
|
-
ws.root ? React.createElement("button", {
|
|
1528
|
-
type: "button",
|
|
1529
|
-
className: "dsh-recall-ex-chip",
|
|
1530
|
-
title: "\u5220\u9664\u8BE5\u5DE5\u4F5C\u533A\u5168\u90E8\u5FEB\u7167",
|
|
1531
|
-
onClick: () => confirmDelete("workspace", key, { scope: "workspace", root: ws.root }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5DE5\u4F5C\u533A\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
|
|
1532
|
-
}, "\u5220\u9664") : null
|
|
1629
|
+
{ className: "dsh-recall-ex-note" },
|
|
1630
|
+
usage === null ? countText + "\u3002" : countText + "\uFF0C\u5168\u90E8\u5DE5\u4F5C\u533A\u5FEB\u7167\u5B58\u50A8\u5360\u7528 " + sizeText2(usage) + "\u3002"
|
|
1533
1631
|
),
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
"div",
|
|
1563
|
-
{ className: "dsh-recall-ex-note", key: "
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
// 打开快照管理时的一段空白;aria-hidden 纯装饰不打扰读屏
|
|
1580
|
-
items === null ? React.createElement(
|
|
1581
|
-
"div",
|
|
1582
|
-
{ className: "dsh-recall-tree-skeleton", "aria-hidden": true },
|
|
1583
|
-
...[1, 2, 3, 4, 5].map((n) => React.createElement("div", { key: "sk-" + n, className: "dsh-recall-tree-skeleton-row" }))
|
|
1584
|
-
) : null,
|
|
1585
|
-
treeNodes.length > 0 ? React.createElement("div", { className: "dsh-recall-tree" }, ...treeNodes) : null,
|
|
1586
|
-
items && items.length === 0 && !q ? React.createElement("div", { className: "dsh-recall-ex-note", key: "empty" }, "\u5728\u4EFB\u610F\u5DE5\u4F5C\u533A\u53D1\u9001\u4E00\u6761\u6D88\u606F\u540E\uFF0C\u8FD9\u91CC\u4F1A\u51FA\u73B0\u5FEB\u7167\u3002") : null,
|
|
1587
|
-
q && filteredItems && filteredItems.length === 0 ? React.createElement("div", { className: "dsh-recall-ex-note", key: "no-match" }, "\u65E0\u5339\u914D\u5FEB\u7167") : null,
|
|
1588
|
-
renderDeleteAllConfirm(),
|
|
1589
|
-
// V6:错误区从卡片最底上移到操作区上方(fail-loud 可见性);标题
|
|
1590
|
-
// error 色带条数徽章,时间戳格式维持原样
|
|
1591
|
-
errors && errors.length > 0 ? React.createElement(
|
|
1592
|
-
"div",
|
|
1593
|
-
{ key: "errors" },
|
|
1594
|
-
React.createElement("div", { className: "dsh-recall-errors-title" }, "\u6700\u8FD1\u9519\u8BEF (" + errors.length + ")"),
|
|
1595
|
-
(showAllErrors ? errors : errors.slice(0, 5)).map((e, i) => React.createElement("div", { key: i, className: "dsh-recall-ex-note" }, clockText2(e.time) + " " + e.message)),
|
|
1632
|
+
// V6 健康行徽章化:git 状态用彩色 pill(成功/失败,官方状态行配对),
|
|
1633
|
+
// 从普通 note 提升为卡片顶部横幅(渲染在搜索框与树之前);存储计数
|
|
1634
|
+
// 维持文字,避免 pill 堆叠丢失信息。
|
|
1635
|
+
health ? React.createElement(
|
|
1636
|
+
"div",
|
|
1637
|
+
{ className: "dsh-recall-ex-note", key: "health" },
|
|
1638
|
+
React.createElement("span", {
|
|
1639
|
+
className: "dsh-recall-health-pill " + (health.gitAvailable ? "dsh-recall-health-pill-ok" : "dsh-recall-health-pill-bad"),
|
|
1640
|
+
title: "\u5FEB\u7167\u5F15\u64CE\u4F9D\u8D56 git"
|
|
1641
|
+
}, health.gitAvailable ? "git \u53EF\u7528" : "git \u4E0D\u53EF\u7528\uFF08\u5FEB\u7167\u5F15\u64CE\u4F9D\u8D56 git\uFF09"),
|
|
1642
|
+
" \xB7 \u5FEB\u7167\u5B58\u50A8\uFF1Ahome " + health.homeStores + " \u4E2A\u5DE5\u4F5C\u533A" + (health.fallbackStores ? "\uFF0C\u964D\u7EA7 " + health.fallbackStores + " \u4E2A" : "")
|
|
1643
|
+
) : null,
|
|
1644
|
+
React.createElement("input", {
|
|
1645
|
+
className: "dsh-recall-ex-input",
|
|
1646
|
+
placeholder: "\u641C\u7D22\u5DE5\u4F5C\u533A / \u4F1A\u8BDD\u6807\u9898 / \u6D88\u606F\u5185\u5BB9 / ID",
|
|
1647
|
+
"aria-label": "\u641C\u7D22\u5FEB\u7167",
|
|
1648
|
+
value: query,
|
|
1649
|
+
spellCheck: false,
|
|
1650
|
+
onChange: (e) => setQuery(e.target.value)
|
|
1651
|
+
}),
|
|
1652
|
+
// V3 加载骨架:items===null 表示首查未回——用 5 条 pulse 灰条占位替代
|
|
1653
|
+
// 打开快照管理时的一段空白;aria-hidden 纯装饰不打扰读屏
|
|
1654
|
+
items === null ? React.createElement(
|
|
1655
|
+
"div",
|
|
1656
|
+
{ className: "dsh-recall-tree-skeleton", "aria-hidden": true },
|
|
1657
|
+
...[1, 2, 3, 4, 5].map((n) => React.createElement("div", { key: "sk-" + n, className: "dsh-recall-tree-skeleton-row" }))
|
|
1658
|
+
) : null,
|
|
1659
|
+
treeNodes.length > 0 ? React.createElement("div", { className: "dsh-recall-tree" }, ...treeNodes) : null,
|
|
1660
|
+
items && items.length === 0 && !q ? React.createElement("div", { className: "dsh-recall-ex-note", key: "empty" }, "\u5728\u4EFB\u610F\u5DE5\u4F5C\u533A\u53D1\u9001\u4E00\u6761\u6D88\u606F\u540E\uFF0C\u8FD9\u91CC\u4F1A\u51FA\u73B0\u5FEB\u7167\u3002") : null,
|
|
1661
|
+
q && filteredItems && filteredItems.length === 0 ? React.createElement("div", { className: "dsh-recall-ex-note", key: "no-match" }, "\u65E0\u5339\u914D\u5FEB\u7167") : null,
|
|
1662
|
+
renderDeleteAllConfirm(),
|
|
1663
|
+
// V6:错误区从卡片最底上移到操作区上方(fail-loud 可见性);标题
|
|
1664
|
+
// error 色带条数徽章,时间戳格式维持原样
|
|
1665
|
+
errors && errors.length > 0 ? React.createElement(
|
|
1666
|
+
"div",
|
|
1667
|
+
{ key: "errors" },
|
|
1668
|
+
React.createElement("div", { className: "dsh-recall-errors-title" }, "\u6700\u8FD1\u9519\u8BEF (" + errors.length + ")"),
|
|
1669
|
+
(showAllErrors ? errors : errors.slice(0, 5)).map((e, i) => React.createElement("div", { key: i, className: "dsh-recall-ex-note" }, clockText2(e.time) + " " + e.message)),
|
|
1670
|
+
React.createElement(
|
|
1671
|
+
"div",
|
|
1672
|
+
{ className: "dsh-recall-panel-actions" },
|
|
1673
|
+
errors.length > 5 ? React.createElement("button", { type: "button", className: "dsh-recall-ex-chip", onClick: () => setShowAllErrors((v) => !v) }, showAllErrors ? "\u6536\u8D77" : "\u5C55\u5F00\u5168\u90E8 (" + errors.length + ")") : null,
|
|
1674
|
+
React.createElement("button", { type: "button", className: "dsh-recall-ex-chip", onClick: clearErrors }, "\u6E05\u7A7A")
|
|
1675
|
+
)
|
|
1676
|
+
) : null,
|
|
1596
1677
|
React.createElement(
|
|
1597
1678
|
"div",
|
|
1598
1679
|
{ className: "dsh-recall-panel-actions" },
|
|
1599
|
-
|
|
1600
|
-
React.createElement("button", {
|
|
1680
|
+
state.message ? React.createElement("span", { role: "status", "aria-live": "polite", className: "dsh-recall-ex-status" + (state.error ? " dsh-recall-ex-status-error" : " dsh-recall-ex-status-success") }, (state.error ? "\u9519\u8BEF\uFF1A" : "") + state.message) : null,
|
|
1681
|
+
limit < total ? React.createElement("button", {
|
|
1682
|
+
type: "button",
|
|
1683
|
+
className: "dsh-recall-btn",
|
|
1684
|
+
disabled: state.busy,
|
|
1685
|
+
onClick: loadMore
|
|
1686
|
+
}, "\u52A0\u8F7D\u66F4\u591A") : null,
|
|
1687
|
+
React.createElement("button", { type: "button", className: "dsh-recall-btn", disabled: state.busy, onClick: refresh }, "\u5237\u65B0"),
|
|
1688
|
+
React.createElement("button", {
|
|
1689
|
+
type: "button",
|
|
1690
|
+
className: "dsh-recall-btn",
|
|
1691
|
+
disabled: state.busy,
|
|
1692
|
+
title: "\u7ACB\u5373\u5BF9\u5168\u90E8\u5DE5\u4F5C\u533A\u6267\u884C\u4E00\u6B21 git gc\uFF08\u538B\u7F29\u5BF9\u8C61\u5E93\u91CA\u653E\u7A7A\u95F4\uFF09",
|
|
1693
|
+
onClick: () => run("gc", {}, "gc \u5B8C\u6210")
|
|
1694
|
+
}, "\u7ACB\u5373 gc"),
|
|
1695
|
+
// V5:全部删除固定为操作区最后一个按钮(排在立即 gc 之后)——即使
|
|
1696
|
+
// 「加载更多」出现/消失也不漂移;danger 与普通按钮间由 panel-actions
|
|
1697
|
+
// 统一 gap:8px 形成视觉分组
|
|
1698
|
+
React.createElement("button", {
|
|
1699
|
+
type: "button",
|
|
1700
|
+
className: "dsh-recall-btn dsh-recall-btn-danger",
|
|
1701
|
+
disabled: state.busy,
|
|
1702
|
+
title: "\u5220\u9664\u5168\u90E8\u5DE5\u4F5C\u533A\u7684\u6240\u6709\u5FEB\u7167\uFF1B\u4F1A\u76F4\u63A5\u6838\u5BF9\u5E76\u5220\u9664 git tag\uFF08\u5373\u4F7F\u5217\u8868\u4E3A\u7A7A\u4E5F\u53EF\u6E05\u7406\u6B8B\u7559\uFF09",
|
|
1703
|
+
onClick: () => setConfirming({ kind: "all" })
|
|
1704
|
+
}, "\u5168\u90E8\u5220\u9664")
|
|
1601
1705
|
)
|
|
1602
|
-
)
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
{ className: "dsh-recall-panel-actions" },
|
|
1606
|
-
state.message ? React.createElement("span", { role: "status", "aria-live": "polite", className: "dsh-recall-ex-status" + (state.error ? " dsh-recall-ex-status-error" : " dsh-recall-ex-status-success") }, (state.error ? "\u9519\u8BEF\uFF1A" : "") + state.message) : null,
|
|
1607
|
-
limit < total ? React.createElement("button", {
|
|
1608
|
-
type: "button",
|
|
1609
|
-
className: "dsh-recall-btn",
|
|
1610
|
-
disabled: state.busy,
|
|
1611
|
-
onClick: loadMore
|
|
1612
|
-
}, "\u52A0\u8F7D\u66F4\u591A") : null,
|
|
1613
|
-
React.createElement("button", { type: "button", className: "dsh-recall-btn", disabled: state.busy, onClick: refresh }, "\u5237\u65B0"),
|
|
1614
|
-
React.createElement("button", {
|
|
1615
|
-
type: "button",
|
|
1616
|
-
className: "dsh-recall-btn",
|
|
1617
|
-
disabled: state.busy,
|
|
1618
|
-
title: "\u7ACB\u5373\u5BF9\u5168\u90E8\u5DE5\u4F5C\u533A\u6267\u884C\u4E00\u6B21 git gc\uFF08\u538B\u7F29\u5BF9\u8C61\u5E93\u91CA\u653E\u7A7A\u95F4\uFF09",
|
|
1619
|
-
onClick: () => run("gc", {}, "gc \u5B8C\u6210")
|
|
1620
|
-
}, "\u7ACB\u5373 gc"),
|
|
1621
|
-
// V5:全部删除固定为操作区最后一个按钮(排在立即 gc 之后)——即使
|
|
1622
|
-
// 「加载更多」出现/消失也不漂移;danger 与普通按钮间由 panel-actions
|
|
1623
|
-
// 统一 gap:8px 形成视觉分组
|
|
1624
|
-
React.createElement("button", {
|
|
1625
|
-
type: "button",
|
|
1626
|
-
className: "dsh-recall-btn dsh-recall-btn-danger",
|
|
1627
|
-
disabled: state.busy,
|
|
1628
|
-
title: "\u5220\u9664\u5168\u90E8\u5DE5\u4F5C\u533A\u7684\u6240\u6709\u5FEB\u7167\uFF1B\u4F1A\u76F4\u63A5\u6838\u5BF9\u5E76\u5220\u9664 git tag\uFF08\u5373\u4F7F\u5217\u8868\u4E3A\u7A7A\u4E5F\u53EF\u6E05\u7406\u6B8B\u7559\uFF09",
|
|
1629
|
-
onClick: () => setConfirming({ kind: "all" })
|
|
1630
|
-
}, "\u5168\u90E8\u5220\u9664")
|
|
1631
|
-
)
|
|
1632
|
-
);
|
|
1706
|
+
);
|
|
1707
|
+
}
|
|
1708
|
+
return { ManageCard };
|
|
1633
1709
|
}
|
|
1634
|
-
return { ManageCard };
|
|
1635
|
-
}
|
|
1636
1710
|
|
|
1637
|
-
// src/client/settings-cards.ts
|
|
1638
|
-
function buildSettingsCards(React, util, sessionsSvc) {
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
"button",
|
|
1642
|
-
{
|
|
1643
|
-
type: "button",
|
|
1644
|
-
className: "dsh-recall-cardbtn",
|
|
1645
|
-
"aria-expanded": props.open,
|
|
1646
|
-
onClick: props.onToggle
|
|
1647
|
-
},
|
|
1648
|
-
// 箭头恒为 ▸、开合靠旋转(CSS transition .16s)——与官方卡片 chevron 同一
|
|
1649
|
-
// 动效语言;读屏状态仍由 aria-expanded 播报,字形不再承担状态语义
|
|
1650
|
-
React.createElement("span", { className: "dsh-recall-tree-toggle dsh-recall-section-chevron" + (props.open ? " dsh-recall-section-chevron-open" : "") }, "\u25B8"),
|
|
1651
|
-
React.createElement("span", { className: "dsh-recall-ex-title" }, props.title),
|
|
1652
|
-
props.meta ? React.createElement("span", { className: "dsh-recall-tree-meta" }, props.meta) : null
|
|
1653
|
-
);
|
|
1654
|
-
}
|
|
1655
|
-
const { ConfigForm } = buildConfigForm(React, util, SectionToggle);
|
|
1656
|
-
const { ExcludeFilesSection } = buildExcludeCards(React, util);
|
|
1657
|
-
const { ManageCard } = buildSnapshotManager(React, util, sessionsSvc);
|
|
1658
|
-
function RecallSettingsCard() {
|
|
1659
|
-
const [open, setOpen] = React.useState(false);
|
|
1660
|
-
const [sections, setSections] = React.useState({ exclude: false, manage: false });
|
|
1661
|
-
function toggle(key) {
|
|
1662
|
-
setSections((prev) => Object.assign({}, prev, { [key]: !prev[key] }));
|
|
1663
|
-
}
|
|
1664
|
-
return React.createElement(
|
|
1665
|
-
"li",
|
|
1666
|
-
{ className: "dsh-recall-card" + (open ? " dsh-recall-card-open" : "") },
|
|
1667
|
-
React.createElement(
|
|
1711
|
+
// src/client/settings-cards.ts
|
|
1712
|
+
function buildSettingsCards(React, util, sessionsSvc) {
|
|
1713
|
+
function SectionToggle(props) {
|
|
1714
|
+
return React.createElement(
|
|
1668
1715
|
"button",
|
|
1669
1716
|
{
|
|
1670
1717
|
type: "button",
|
|
1671
1718
|
className: "dsh-recall-cardbtn",
|
|
1672
|
-
"aria-expanded": open,
|
|
1673
|
-
|
|
1674
|
-
onClick: () => setOpen((v) => !v)
|
|
1719
|
+
"aria-expanded": props.open,
|
|
1720
|
+
onClick: props.onToggle
|
|
1675
1721
|
},
|
|
1722
|
+
// 箭头恒为 ▸、开合靠旋转(CSS transition .16s)——与官方卡片 chevron 同一
|
|
1723
|
+
// 动效语言;读屏状态仍由 aria-expanded 播报,字形不再承担状态语义
|
|
1724
|
+
React.createElement("span", { className: "dsh-recall-tree-toggle dsh-recall-section-chevron" + (props.open ? " dsh-recall-section-chevron-open" : "") }, "\u25B8"),
|
|
1725
|
+
React.createElement("span", { className: "dsh-recall-ex-title" }, props.title),
|
|
1726
|
+
props.meta ? React.createElement("span", { className: "dsh-recall-tree-meta" }, props.meta) : null
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
const { ConfigForm } = buildConfigForm(React, util, SectionToggle);
|
|
1730
|
+
const { ExcludeFilesSection } = buildExcludeCards(React, util);
|
|
1731
|
+
const { ManageCard } = buildSnapshotManager(React, util, sessionsSvc);
|
|
1732
|
+
function RecallSettingsCard() {
|
|
1733
|
+
const [open, setOpen] = React.useState(false);
|
|
1734
|
+
const [sections, setSections] = React.useState({ exclude: false, manage: false });
|
|
1735
|
+
function toggle(key) {
|
|
1736
|
+
setSections((prev) => Object.assign({}, prev, { [key]: !prev[key] }));
|
|
1737
|
+
}
|
|
1738
|
+
return React.createElement(
|
|
1739
|
+
"li",
|
|
1740
|
+
{ className: "dsh-recall-card" + (open ? " dsh-recall-card-open" : "") },
|
|
1676
1741
|
React.createElement(
|
|
1677
|
-
"
|
|
1678
|
-
{
|
|
1679
|
-
|
|
1680
|
-
|
|
1742
|
+
"button",
|
|
1743
|
+
{
|
|
1744
|
+
type: "button",
|
|
1745
|
+
className: "dsh-recall-cardbtn",
|
|
1746
|
+
"aria-expanded": open,
|
|
1747
|
+
"aria-label": (open ? "\u6536\u8D77" : "\u5C55\u5F00") + ": \u64A4\u56DE\u63D2\u4EF6",
|
|
1748
|
+
onClick: () => setOpen((v) => !v)
|
|
1749
|
+
},
|
|
1750
|
+
React.createElement(
|
|
1751
|
+
"span",
|
|
1752
|
+
{ className: "dsh-recall-card-head" },
|
|
1753
|
+
React.createElement("span", { className: "dsh-recall-card-name" }, "\u64A4\u56DE\u63D2\u4EF6"),
|
|
1754
|
+
React.createElement("span", { className: "dsh-recall-card-desc" }, "\u6D88\u606F\u64A4\u56DE\uFF08\u6587\u4EF6\u5FEB\u7167 + \u5BF9\u8BDD\u56DE\u9000\uFF09\u7684\u9608\u503C\u4E0E\u6CBB\u7406")
|
|
1755
|
+
),
|
|
1756
|
+
React.createElement("svg", {
|
|
1757
|
+
width: 14,
|
|
1758
|
+
height: 14,
|
|
1759
|
+
viewBox: "0 0 16 16",
|
|
1760
|
+
style: { color: "var(--dsw-alias-label-tertiary)", flex: "none", transition: "transform .16s", transform: open ? "rotate(180deg)" : "none" }
|
|
1761
|
+
}, React.createElement("path", { d: "M4 6l4 4 4-4", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" }))
|
|
1681
1762
|
),
|
|
1682
|
-
React.createElement(
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
sections.exclude ? React.createElement(ExcludeFilesSection) : null,
|
|
1695
|
-
React.createElement(SectionToggle, { title: "\u5FEB\u7167\u7BA1\u7406", open: sections.manage, onToggle: () => toggle("manage") }),
|
|
1696
|
-
sections.manage ? React.createElement(ManageCard) : null
|
|
1697
|
-
) : null
|
|
1698
|
-
);
|
|
1763
|
+
open ? React.createElement(
|
|
1764
|
+
"div",
|
|
1765
|
+
{ className: "dsh-recall-card-body" },
|
|
1766
|
+
React.createElement(ConfigForm),
|
|
1767
|
+
React.createElement(SectionToggle, { title: "\u6392\u9664\u914D\u7F6E\uFF08exclude.txt\uFF09", open: sections.exclude, onToggle: () => toggle("exclude") }),
|
|
1768
|
+
sections.exclude ? React.createElement(ExcludeFilesSection) : null,
|
|
1769
|
+
React.createElement(SectionToggle, { title: "\u5FEB\u7167\u7BA1\u7406", open: sections.manage, onToggle: () => toggle("manage") }),
|
|
1770
|
+
sections.manage ? React.createElement(ManageCard) : null
|
|
1771
|
+
) : null
|
|
1772
|
+
);
|
|
1773
|
+
}
|
|
1774
|
+
return { RecallSettingsCard };
|
|
1699
1775
|
}
|
|
1700
|
-
return { RecallSettingsCard };
|
|
1701
|
-
}
|
|
1702
1776
|
|
|
1703
|
-
// src/client/app.ts
|
|
1704
|
-
function nextShadowPriority(entries, key) {
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
}
|
|
1712
|
-
return priority;
|
|
1713
|
-
}
|
|
1714
|
-
function createApp(React) {
|
|
1715
|
-
return function apply(ctx) {
|
|
1716
|
-
const slots = ctx.slots;
|
|
1717
|
-
if (!slots) return;
|
|
1718
|
-
const sessionsSvc = ctx.sessions;
|
|
1719
|
-
const workspacesSvc = ctx.workspaces;
|
|
1720
|
-
const stylesSvc = ctx.get("styles");
|
|
1721
|
-
if (stylesSvc && typeof stylesSvc.insert === "function") {
|
|
1722
|
-
stylesSvc.insert(CSS);
|
|
1723
|
-
} else if (typeof document !== "undefined") {
|
|
1724
|
-
const tag = document.createElement("style");
|
|
1725
|
-
tag.setAttribute("data-plugin", "dsh-recall-plugin");
|
|
1726
|
-
tag.textContent = CSS;
|
|
1727
|
-
document.head.appendChild(tag);
|
|
1777
|
+
// src/client/app.ts
|
|
1778
|
+
function nextShadowPriority(entries, key) {
|
|
1779
|
+
let priority = -1;
|
|
1780
|
+
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
1781
|
+
if (!entry || !entry.options || entry.options.key !== key) continue;
|
|
1782
|
+
const p = entry.options.priority;
|
|
1783
|
+
const occupied = typeof p === "number" && Number.isFinite(p) ? p : 0;
|
|
1784
|
+
if (occupied <= priority) priority = occupied - 1;
|
|
1728
1785
|
}
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1786
|
+
return priority;
|
|
1787
|
+
}
|
|
1788
|
+
function createApp(React) {
|
|
1789
|
+
return function apply(ctx) {
|
|
1790
|
+
const slots = ctx.slots;
|
|
1791
|
+
if (!slots) return;
|
|
1792
|
+
const sessionsSvc = ctx.sessions;
|
|
1793
|
+
const workspacesSvc = ctx.workspaces;
|
|
1794
|
+
const stylesSvc = ctx.get("styles");
|
|
1795
|
+
if (stylesSvc && typeof stylesSvc.insert === "function") {
|
|
1796
|
+
stylesSvc.insert(CSS);
|
|
1797
|
+
} else if (typeof document !== "undefined") {
|
|
1798
|
+
const tag = document.createElement("style");
|
|
1799
|
+
tag.setAttribute("data-plugin", "dsh-recall-plugin");
|
|
1800
|
+
tag.textContent = CSS;
|
|
1801
|
+
document.head.appendChild(tag);
|
|
1802
|
+
}
|
|
1803
|
+
const util = buildUtil();
|
|
1804
|
+
const { UserRecallNode } = buildRecallNode(React, util, ctx, sessionsSvc, workspacesSvc);
|
|
1805
|
+
const { RecallSettingsCard } = buildSettingsCards(React, util, sessionsSvc);
|
|
1806
|
+
for (const slotKey of ["user", "steering"]) {
|
|
1807
|
+
try {
|
|
1808
|
+
slots.inject("conversation.chat.node", () => {
|
|
1809
|
+
const priority = nextShadowPriority(slots.entries("conversation.chat.node"), slotKey);
|
|
1810
|
+
return slots.register(
|
|
1811
|
+
{ name: "conversation.chat.node", key: slotKey, priority },
|
|
1812
|
+
UserRecallNode
|
|
1813
|
+
);
|
|
1814
|
+
});
|
|
1815
|
+
} catch (error) {
|
|
1816
|
+
console.error("[dsh-recall-plugin] slot register failed (" + slotKey + "):", error);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1733
1819
|
try {
|
|
1734
|
-
slots.inject("
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
UserRecallNode
|
|
1739
|
-
);
|
|
1740
|
-
});
|
|
1820
|
+
slots.inject("settings.plugin.item", () => slots.register(
|
|
1821
|
+
{ name: "settings.plugin.item", key: "dsh-recall" },
|
|
1822
|
+
RecallSettingsCard
|
|
1823
|
+
));
|
|
1741
1824
|
} catch (error) {
|
|
1742
|
-
console.error("[dsh-recall-plugin]
|
|
1825
|
+
console.error("[dsh-recall-plugin] settings card register failed:", error);
|
|
1743
1826
|
}
|
|
1744
|
-
}
|
|
1745
|
-
try {
|
|
1746
|
-
slots.inject("settings.plugin.item", () => slots.register(
|
|
1747
|
-
{ name: "settings.plugin.item", key: "dsh-recall" },
|
|
1748
|
-
RecallSettingsCard
|
|
1749
|
-
));
|
|
1750
|
-
} catch (error) {
|
|
1751
|
-
console.error("[dsh-recall-plugin] settings card register failed:", error);
|
|
1752
|
-
}
|
|
1753
|
-
};
|
|
1754
|
-
}
|
|
1755
|
-
|
|
1756
|
-
// src/client/entry.ts
|
|
1757
|
-
window.__ModuleLoader__.load({
|
|
1758
|
-
id: "dsh-recall-plugin",
|
|
1759
|
-
factory: (require2) => {
|
|
1760
|
-
const React = require2("react");
|
|
1761
|
-
return {
|
|
1762
|
-
name: "dsh-recall-plugin",
|
|
1763
|
-
inject: ["slots", "sessions", "workspaces", "timer"],
|
|
1764
|
-
apply: createApp(React)
|
|
1765
1827
|
};
|
|
1766
1828
|
}
|
|
1767
|
-
|
|
1829
|
+
|
|
1830
|
+
// src/client/entry.ts
|
|
1831
|
+
window.__ModuleLoader__.load({
|
|
1832
|
+
id: "dsh-recall-plugin",
|
|
1833
|
+
factory: (require2) => {
|
|
1834
|
+
const React = require2("react");
|
|
1835
|
+
return {
|
|
1836
|
+
name: "dsh-recall-plugin",
|
|
1837
|
+
inject: ["slots", "sessions", "workspaces", "timer"],
|
|
1838
|
+
apply: createApp(React)
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
});
|
|
1842
|
+
})();
|