dsh-perf-boost 0.2.1 → 0.2.2
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 +5 -0
- package/README.md +6 -1
- package/assets/icon-180.png +0 -0
- package/lib/client.js +55 -100
- package/lib/index.js +60 -26
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -9,6 +9,11 @@ Client-side rendering optimizations for DSH Desktop.
|
|
|
9
9
|
|
|
10
10
|
## 更新详情
|
|
11
11
|
|
|
12
|
+
### 0.2.2
|
|
13
|
+
|
|
14
|
+
- 优化了长对话省渲染的实现,改成一处分样式接管子项,新消息进来自动生效的效果
|
|
15
|
+
- 修复了流式输出时每来一条消息就扫一遍页面的问题,改成一分钟几十次的空闲巡检的问题
|
|
16
|
+
|
|
12
17
|
### 0.2.1
|
|
13
18
|
|
|
14
19
|
- 新增了「大会话预热」的开关,设置页里点一下就能关的功能
|
|
@@ -35,7 +40,7 @@ Client-side rendering optimizations for DSH Desktop.
|
|
|
35
40
|
|
|
36
41
|
| 开关 | 默认 | 影响视觉 | 作用 |
|
|
37
42
|
| --- | --- | --- | --- |
|
|
38
|
-
| 长对话省渲染 | 开 | 无 |
|
|
43
|
+
| 长对话省渲染 | 开 | 无 | 给滚动列表容器挂一个 class,子项的 `content-visibility: auto` 由一条 CSS 规则接管(新消息自动生效,不用逐个打 inline style),占位高度取该列表实测平均子项高度;末尾 4 项与标了 `aria-busy` 的消息保持正常渲染,代码块单独按 300px 跳过 |
|
|
39
44
|
| 窗口隐藏不排帧 | 开 | 无 | 页面不可见时 `schedule()` 直接返回;切回时有 `visibilitychange` 钩子恢复,每秒看门狗兜底 |
|
|
40
45
|
| 空闲降帧 | 开 | 无 | 20 秒无输入将 rAF 驱动的动画降到 30fps,有输入立即恢复 |
|
|
41
46
|
| 媒体懒加载 | 开 | 无 | 图片加 `loading=lazy`、`decoding=async`,视频加 `preload=metadata` |
|
package/assets/icon-180.png
CHANGED
|
Binary file
|
package/lib/client.js
CHANGED
|
@@ -26,16 +26,25 @@ window.__ModuleLoader__.load({
|
|
|
26
26
|
var NAME = 'dsh-perf-boost';
|
|
27
27
|
var STORE = 'dsh-perf-boost:cfg';
|
|
28
28
|
var STYLE_ID = 'dpb-style';
|
|
29
|
-
var
|
|
29
|
+
var CV_CLASS = 'dpb-cv'; // 挂在滚动列表容器上,子项的 content-visibility 交给 CSS
|
|
30
|
+
var CV_VAR = '--dpb-cis';
|
|
31
|
+
var VERSION = '0.2.2';
|
|
30
32
|
// 更新公告:只用「新增了…的功能 / 修复了…的问题 / 优化了…的效果」三种句式。
|
|
31
33
|
var RELEASE_NOTES = [
|
|
34
|
+
{
|
|
35
|
+
version: '0.2.2',
|
|
36
|
+
items: [
|
|
37
|
+
'优化了长对话省渲染的实现,改成一条 CSS 规则接管,新消息进来自动生效的效果',
|
|
38
|
+
'修复了流式输出时每来一条消息就扫一遍页面的问题,改成一分钟几十次的空闲巡检',
|
|
39
|
+
],
|
|
40
|
+
},
|
|
32
41
|
{
|
|
33
42
|
version: '0.2.1',
|
|
34
43
|
items: [
|
|
35
44
|
'新增了大会话预热的开关,可以直接在设置里关掉的功能',
|
|
36
45
|
'新增了浅色模式的配色,开关和边框在浅色主题下也看得清的效果',
|
|
37
|
-
'优化了开关说明的文字,统一说成「可能会导致…」的效果',
|
|
38
|
-
'新增了百分比改成最近 1 分钟窗口,长时间使用不会越用越低的功能',
|
|
46
|
+
'优化了开关说明的文字,统一说成「可能会导致…」的效果',
|
|
47
|
+
'新增了百分比改成最近 1 分钟窗口,长时间使用不会越用越低的功能',
|
|
39
48
|
'优化了开关列表的顺序,默认开启的排在前面、默认关闭的放在后面的效果',
|
|
40
49
|
],
|
|
41
50
|
},
|
|
@@ -95,7 +104,7 @@ window.__ModuleLoader__.load({
|
|
|
95
104
|
.catch(function () { return null; });
|
|
96
105
|
}
|
|
97
106
|
|
|
98
|
-
function checkUpdate() {
|
|
107
|
+
function checkUpdate(force) {
|
|
99
108
|
if (window.__dpbUpdating) return;
|
|
100
109
|
window.__dpbUpdating = true;
|
|
101
110
|
update.state = 'checking';
|
|
@@ -107,7 +116,7 @@ window.__ModuleLoader__.load({
|
|
|
107
116
|
method: 'POST',
|
|
108
117
|
credentials: 'same-origin',
|
|
109
118
|
headers: { 'content-type': 'application/json' },
|
|
110
|
-
body: '{}',
|
|
119
|
+
body: force === true ? '{"force":true}' : '{}',
|
|
111
120
|
signal: ctl ? ctl.signal : undefined,
|
|
112
121
|
})
|
|
113
122
|
.then(function (r) { return r.ok ? r.json() : Promise.reject(new Error('HTTP ' + r.status)); })
|
|
@@ -544,82 +553,46 @@ window.__ModuleLoader__.load({
|
|
|
544
553
|
return Math.max(48, Math.round(sum / cnt));
|
|
545
554
|
}
|
|
546
555
|
|
|
547
|
-
|
|
556
|
+
// 容器上挂 class,子项交给 CSS。
|
|
557
|
+
// 逐项打 inline style 等于「每来一条消息就把整个列表摸一遍」,长会话里这笔开销
|
|
558
|
+
// 比省下来的渲染还贵;改成一处分样式后,新消息天然被覆盖,JS 只负责找容器。
|
|
559
|
+
function markContainer(el) {
|
|
560
|
+
if (el.children.length < 24) return;
|
|
548
561
|
try {
|
|
549
|
-
if (el.
|
|
550
|
-
|
|
551
|
-
|
|
562
|
+
if (!el.classList.contains(CV_CLASS)) el.classList.add(CV_CLASS);
|
|
563
|
+
var h = avgChildHeight(el);
|
|
564
|
+
if (el.style.getPropertyValue(CV_VAR) !== h + 'px') el.style.setProperty(CV_VAR, h + 'px');
|
|
565
|
+
} catch (e) { void e; }
|
|
552
566
|
}
|
|
553
567
|
|
|
554
|
-
function
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
// 末尾 4 项是正在生成/刚贴上的内容,免掉跳过渲染,避免「正在动的画面被冻住」。
|
|
560
|
-
var lastSafe = Math.max(0, el.children.length - 4);
|
|
561
|
-
for (var i = 0; i < lastSafe; i++) {
|
|
562
|
-
var it = el.children[i];
|
|
563
|
-
if (it.style.contentVisibility === 'auto') continue;
|
|
564
|
-
if (isLive(it)) continue;
|
|
565
|
-
record.items.push({ el: it, cv: it.style.contentVisibility, cis: it.style.containIntrinsicSize });
|
|
566
|
-
it.style.contentVisibility = 'auto';
|
|
567
|
-
it.style.containIntrinsicSize = size;
|
|
568
|
-
}
|
|
569
|
-
var pres = el.querySelectorAll('pre');
|
|
570
|
-
for (var k = 0; k < pres.length; k++) {
|
|
571
|
-
var pre = pres[k];
|
|
572
|
-
if (pre.scrollHeight <= 300 || pre.style.contentVisibility === 'auto') continue;
|
|
573
|
-
record.items.push({ el: pre, cv: pre.style.contentVisibility, cis: pre.style.containIntrinsicSize });
|
|
574
|
-
pre.style.contentVisibility = 'auto';
|
|
575
|
-
pre.style.containIntrinsicSize = 'auto 300px';
|
|
576
|
-
}
|
|
568
|
+
function unmark(el) {
|
|
569
|
+
try {
|
|
570
|
+
el.classList.remove(CV_CLASS);
|
|
571
|
+
el.style.removeProperty(CV_VAR);
|
|
572
|
+
} catch (e) { void e; }
|
|
577
573
|
}
|
|
578
574
|
|
|
579
575
|
function clearAll() {
|
|
580
|
-
for (var i = 0; i < tagged.length; i++)
|
|
581
|
-
var rec = tagged[i];
|
|
582
|
-
for (var j = 0; j < rec.items.length; j++) {
|
|
583
|
-
var it = rec.items[j];
|
|
584
|
-
try {
|
|
585
|
-
it.el.style.contentVisibility = it.cv || '';
|
|
586
|
-
it.el.style.containIntrinsicSize = it.cis || '';
|
|
587
|
-
} catch (e) { void e; }
|
|
588
|
-
}
|
|
589
|
-
}
|
|
576
|
+
for (var i = 0; i < tagged.length; i++) unmark(tagged[i]);
|
|
590
577
|
tagged = [];
|
|
591
578
|
}
|
|
592
579
|
|
|
593
|
-
function recordFor(el) {
|
|
594
|
-
for (var i = 0; i < tagged.length; i++) if (tagged[i].el === el) return tagged[i];
|
|
595
|
-
return null;
|
|
596
|
-
}
|
|
597
|
-
|
|
598
580
|
function applyLazyList() {
|
|
599
581
|
if (!cfg.lazyList) { clearAll(); return; }
|
|
600
582
|
var targets = collectTargets();
|
|
601
|
-
if (!targets.length) return;
|
|
602
583
|
for (var i = tagged.length - 1; i >= 0; i--) {
|
|
603
|
-
if (targets.indexOf(tagged[i]
|
|
584
|
+
if (targets.indexOf(tagged[i]) < 0 || !document.contains(tagged[i])) {
|
|
585
|
+
unmark(tagged[i]);
|
|
586
|
+
tagged.splice(i, 1);
|
|
587
|
+
}
|
|
604
588
|
}
|
|
605
589
|
for (var t = 0; t < targets.length; t++) {
|
|
606
590
|
var el = targets[t];
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
rec = { el: el, items: [] };
|
|
610
|
-
tagged.push(rec);
|
|
611
|
-
}
|
|
612
|
-
try { tagItems(rec); } catch (e) { void e; }
|
|
591
|
+
if (tagged.indexOf(el) < 0) tagged.push(el);
|
|
592
|
+
try { markContainer(el); } catch (e) { void e; }
|
|
613
593
|
}
|
|
614
594
|
var n = 0;
|
|
615
|
-
for (var m = 0; m < tagged.length; m++)
|
|
616
|
-
var items = tagged[m].items;
|
|
617
|
-
// 被替换掉、被压缩掉的子项还留着引用的话,已脱离文档的 DOM 永远回收不了。
|
|
618
|
-
for (var q = items.length - 1; q >= 0; q--) {
|
|
619
|
-
if (!document.contains(items[q].el)) items.splice(q, 1);
|
|
620
|
-
}
|
|
621
|
-
n += items.length;
|
|
622
|
-
}
|
|
595
|
+
for (var m = 0; m < tagged.length; m++) n += Math.max(0, tagged[m].children.length - 4);
|
|
623
596
|
MET.lazyItems = n;
|
|
624
597
|
}
|
|
625
598
|
|
|
@@ -628,10 +601,10 @@ window.__ModuleLoader__.load({
|
|
|
628
601
|
if (!cfg.lazyList) return '已关闭。';
|
|
629
602
|
var total = 0, lists = 0, kids = 0;
|
|
630
603
|
for (var i = 0; i < tagged.length; i++) {
|
|
631
|
-
if (!document.contains(tagged[i]
|
|
604
|
+
if (!document.contains(tagged[i])) continue;
|
|
632
605
|
lists++;
|
|
633
|
-
total += tagged[i].
|
|
634
|
-
kids += tagged[i].
|
|
606
|
+
total += Math.max(0, tagged[i].children.length - 4);
|
|
607
|
+
kids += tagged[i].children.length;
|
|
635
608
|
}
|
|
636
609
|
if (!lists) {
|
|
637
610
|
var targets = [];
|
|
@@ -674,44 +647,20 @@ window.__ModuleLoader__.load({
|
|
|
674
647
|
}
|
|
675
648
|
}
|
|
676
649
|
|
|
650
|
+
// 只做「容器换没换」的巡检。子项由 CSS 自己跟上,所以不必盯整棵 DOM——
|
|
651
|
+
// 盯子树会在流式输出时被每一条新消息唤醒,那笔开销反而比省下来的大。
|
|
677
652
|
function watchLazyList() {
|
|
678
|
-
if (window.
|
|
679
|
-
window.
|
|
680
|
-
|
|
681
|
-
|
|
653
|
+
if (window.__dpbWatcher) return;
|
|
654
|
+
window.__dpbWatcher = true;
|
|
655
|
+
window.setInterval(function () {
|
|
656
|
+
if (document.hidden) return;
|
|
657
|
+
if (!cfg.lazyList && !cfg.mediaLazy && !cfg.mediaPause) return;
|
|
682
658
|
idleRun(function () {
|
|
683
659
|
try { applyLazyList(); } catch (e) { void e; }
|
|
684
660
|
try { applyMediaLazy(); } catch (e) { void e; }
|
|
685
661
|
try { applyViewportMedia(); } catch (e) { void e; }
|
|
686
662
|
}, 400);
|
|
687
|
-
};
|
|
688
|
-
var obs = new MutationObserver(function () {
|
|
689
|
-
if (timer) return;
|
|
690
|
-
// 滚动中、或者刚跑完长任务:这会儿别排队,等安静下来后的下一次变动再说。
|
|
691
|
-
if (Date.now() - lastBusyAt < 200) return;
|
|
692
|
-
timer = window.setTimeout(function () {
|
|
693
|
-
timer = null;
|
|
694
|
-
if (document.hidden) return;
|
|
695
|
-
rescan();
|
|
696
|
-
}, 1500);
|
|
697
|
-
});
|
|
698
|
-
obs.observe(document.body, { childList: true, subtree: true });
|
|
699
|
-
// 盯整页太吵:列表出现后把观察点挪到列表本身;列表被整体替换时再挪回来。
|
|
700
|
-
var watchTarget = null;
|
|
701
|
-
var follow = function () {
|
|
702
|
-
if (document.hidden) return;
|
|
703
|
-
var list = null;
|
|
704
|
-
try { list = collectTargets()[0] || null; } catch (e) { list = null; }
|
|
705
|
-
var next = list && list.isConnected ? list : document.body;
|
|
706
|
-
if (next === watchTarget) return;
|
|
707
|
-
try { obs.observe(next, { childList: true, subtree: true }); } catch (e) { void e; }
|
|
708
|
-
watchTarget = next;
|
|
709
|
-
};
|
|
710
|
-
follow();
|
|
711
|
-
window.setInterval(follow, 10000);
|
|
712
|
-
document.addEventListener('visibilitychange', function () {
|
|
713
|
-
if (document.hidden && timer) { window.clearTimeout(timer); timer = null; }
|
|
714
|
-
});
|
|
663
|
+
}, 2500);
|
|
715
664
|
}
|
|
716
665
|
|
|
717
666
|
// ————————————————— 媒体懒加载 —————————————————
|
|
@@ -967,6 +916,12 @@ window.__ModuleLoader__.load({
|
|
|
967
916
|
'.dpb-knob{position:absolute;top:2px;left:2px;width:15px;height:15px;border-radius:50%;background:color-mix(in oklab,currentColor 92%,transparent);transition:transform .15s;box-shadow:0 0 0 1px color-mix(in oklab,currentColor 10%,transparent)}',
|
|
968
917
|
'.dpb-sw[data-on="1"] .dpb-knob{transform:translateX(17px);background:#fff}',
|
|
969
918
|
'.dpb-quiet *, .dpb-quiet *::before, .dpb-quiet *::after{animation-play-state:paused!important}',
|
|
919
|
+
// 子项:滚出视口就不渲染;末尾 4 项和正在生成的那条保持正常渲染。
|
|
920
|
+
'.' + CV_CLASS + ' > *{content-visibility:auto;contain-intrinsic-size:auto var(' + CV_VAR + ',220px)}',
|
|
921
|
+
'.' + CV_CLASS + ' > :nth-last-child(-n+4){content-visibility:visible}',
|
|
922
|
+
'.' + CV_CLASS + ' > [aria-busy="true"]{content-visibility:visible}',
|
|
923
|
+
// 大代码块单独跳过:一条消息里有几千行 pre 时,救的是这一条。
|
|
924
|
+
'.' + CV_CLASS + ' pre{content-visibility:auto;contain-intrinsic-size:auto 300px;overflow-anchor:none}',
|
|
970
925
|
'.dpb-notes{margin:12px 2px 0;font-size:12px;line-height:1.8;opacity:.78}',
|
|
971
926
|
'.dpb-notes b{font-weight:600;opacity:1}',
|
|
972
927
|
'.dpb-note{margin:10px 2px 0;font-size:12px;line-height:1.7;opacity:.72}',
|
|
@@ -1152,7 +1107,7 @@ window.__ModuleLoader__.load({
|
|
|
1152
1107
|
: '—')),
|
|
1153
1108
|
React.createElement('button', {
|
|
1154
1109
|
type: 'button', className: 'dpb-btn',
|
|
1155
|
-
onClick: function () { checkUpdate(); },
|
|
1110
|
+
onClick: function () { checkUpdate(true); },
|
|
1156
1111
|
}, update.state === 'checking' ? '检查中' : '检查更新')),
|
|
1157
1112
|
React.createElement('div', { className: 'dpb-notes' },
|
|
1158
1113
|
React.createElement('b', null, '更新详情'),
|
|
@@ -1173,7 +1128,7 @@ window.__ModuleLoader__.load({
|
|
|
1173
1128
|
try { applyMediaLazy(); } catch (e) { void e; }
|
|
1174
1129
|
try { applyViewportMedia(); } catch (e) { void e; }
|
|
1175
1130
|
}, 600);
|
|
1176
|
-
|
|
1131
|
+
watchLazyList();
|
|
1177
1132
|
if (cfg.showFps) startBadge();
|
|
1178
1133
|
else {
|
|
1179
1134
|
var el = document.getElementById(BADGE_ID);
|
package/lib/index.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// 路由只读、无副作用:只取 `latest` 标签的版本号,不下载、不安装、不落盘。
|
|
12
12
|
// 网络不可用时返回 `latest: null`,页面显示「未查到」,其余功能不受影响。
|
|
13
13
|
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
14
|
+
import https from 'node:https';
|
|
14
15
|
import { homedir } from 'node:os';
|
|
15
16
|
import { join } from 'node:path';
|
|
16
17
|
|
|
@@ -32,7 +33,10 @@ const PACKAGE_NAME = 'dsh-perf-boost';
|
|
|
32
33
|
// npmmirror 优先(国内快),失败再问官方源。
|
|
33
34
|
const REGISTRIES = ['https://registry.npmmirror.com', 'https://registry.npmjs.org'];
|
|
34
35
|
const CACHE_MS = 10 * 60 * 1000;
|
|
35
|
-
const TIMEOUT_MS =
|
|
36
|
+
const TIMEOUT_MS = 5000;
|
|
37
|
+
// 复用连接:第一次握手 200ms 上下,之后 40ms 上下。宿主里的全局 fetch 首次常常
|
|
38
|
+
// 要 3-4 秒(undici 独立连接池 + 冷 DNS),本机实测差 20 倍。
|
|
39
|
+
const AGENT = new https.Agent({ keepAlive: true, maxSockets: 4 });
|
|
36
40
|
|
|
37
41
|
let cache = { at: 0, latest: null, source: '', error: '' };
|
|
38
42
|
|
|
@@ -63,45 +67,57 @@ export function isNewer(candidate, current) {
|
|
|
63
67
|
return false;
|
|
64
68
|
}
|
|
65
69
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
function httpsJson(url) {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const request = https.get(url, { agent: AGENT, family: 4, headers: { accept: 'application/json' } }, (response) => {
|
|
73
|
+
const status = response.statusCode ?? 0;
|
|
74
|
+
if (status !== 200) {
|
|
75
|
+
response.resume();
|
|
76
|
+
reject(new Error(`HTTP ${status}`));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const chunks = [];
|
|
80
|
+
response.on('data', (chunk) => chunks.push(chunk));
|
|
81
|
+
response.on('end', () => {
|
|
82
|
+
try {
|
|
83
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')));
|
|
84
|
+
} catch {
|
|
85
|
+
reject(new Error('响应不是 JSON'));
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
response.on('error', reject);
|
|
89
|
+
});
|
|
90
|
+
request.setTimeout(TIMEOUT_MS, () => request.destroy(new Error('timeout')));
|
|
91
|
+
request.on('error', reject);
|
|
71
92
|
});
|
|
72
|
-
|
|
73
|
-
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function fetchLatest(registry) {
|
|
96
|
+
const body = await httpsJson(`${registry}/${PACKAGE_NAME}/latest`);
|
|
74
97
|
const version = body?.version;
|
|
75
98
|
if (typeof version !== 'string' || version === '') throw new Error('响应里没有 version');
|
|
76
99
|
return { version, source: registry };
|
|
77
100
|
}
|
|
78
101
|
|
|
79
|
-
async function resolveLatest() {
|
|
102
|
+
async function resolveLatest(force = false) {
|
|
80
103
|
const now = Date.now();
|
|
81
|
-
if (cache.at !== 0 && cache.at !== undefined && now - cache.at < CACHE_MS) return cache;
|
|
104
|
+
if (!force && cache.at !== 0 && cache.at !== undefined && now - cache.at < CACHE_MS) return cache;
|
|
82
105
|
let lastError = '未连接';
|
|
83
106
|
let notPublished = false;
|
|
84
|
-
|
|
85
|
-
|
|
107
|
+
// 先问第一个源,拿到就回;失败才轮到下一个。
|
|
108
|
+
// 之前是「两边都问、取更高的版本」,多花一次跨洋往返(本机实测 2.0-2.5 秒),
|
|
109
|
+
// 为的只是防止镜像比官方慢半拍;版本号差半分钟不影响点「检查更新」这件事。
|
|
86
110
|
for (const registry of REGISTRIES) {
|
|
87
111
|
try {
|
|
88
112
|
const { version, source } = await fetchLatest(registry);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
best = version;
|
|
92
|
-
bestSource = source;
|
|
93
|
-
}
|
|
113
|
+
cache = { at: now, latest: version, source, error: '' };
|
|
114
|
+
return cache;
|
|
94
115
|
} catch (error) {
|
|
95
116
|
lastError = error?.message ?? String(error);
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
else notPublished = false;
|
|
117
|
+
// 这个源说 404 = 它上面没有这个包,换下一个源继续试。
|
|
118
|
+
notPublished = lastError === 'HTTP 404';
|
|
99
119
|
}
|
|
100
120
|
}
|
|
101
|
-
if (best !== null) {
|
|
102
|
-
cache = { at: now, latest: best, source: bestSource, error: '' };
|
|
103
|
-
return cache;
|
|
104
|
-
}
|
|
105
121
|
cache = { at: now, latest: null, source: '', error: notPublished ? 'not-published' : lastError };
|
|
106
122
|
return cache;
|
|
107
123
|
}
|
|
@@ -162,6 +178,14 @@ export function apply(ctx, config) {
|
|
|
162
178
|
.catch(() => { void 0; });
|
|
163
179
|
}
|
|
164
180
|
|
|
181
|
+
// 启动后先在后台问一次,设置页打开时直接命中缓存(失败无所谓,路由里还会再试)。
|
|
182
|
+
try {
|
|
183
|
+
const warmTimer = setTimeout(() => { resolveLatest().catch(() => { void 0; }); }, 3000);
|
|
184
|
+
if (typeof warmTimer?.unref === 'function') warmTimer.unref();
|
|
185
|
+
} catch {
|
|
186
|
+
// 拿不到定时器就算了,只是第一次打开设置页慢一点。
|
|
187
|
+
}
|
|
188
|
+
|
|
165
189
|
ctx.inject(['connection'], (c) => {
|
|
166
190
|
const connection = c.get('connection');
|
|
167
191
|
const register = typeof connection?.fetch?.register === 'function'
|
|
@@ -245,8 +269,17 @@ export function apply(ctx, config) {
|
|
|
245
269
|
// 注册失败只是设置页那个开关不出现,预热仍按配置文件走。
|
|
246
270
|
}
|
|
247
271
|
|
|
248
|
-
const handler = async () => {
|
|
249
|
-
|
|
272
|
+
const handler = async (request) => {
|
|
273
|
+
let force = false;
|
|
274
|
+
try {
|
|
275
|
+
const text = typeof request?.text === 'function' ? await request.text() : '';
|
|
276
|
+
// 手动点「检查更新」要拿最新答案,不能被 10 分钟缓存挡住;自动检查仍走缓存。
|
|
277
|
+
if (text) force = JSON.parse(text)?.force === true;
|
|
278
|
+
} catch {
|
|
279
|
+
force = false;
|
|
280
|
+
}
|
|
281
|
+
const startedAt = Date.now();
|
|
282
|
+
const [installed, resolved] = await Promise.all([readInstalledVersion(), resolveLatest(force)]);
|
|
250
283
|
const latest = resolved.latest;
|
|
251
284
|
return Response.json(
|
|
252
285
|
{
|
|
@@ -258,6 +291,7 @@ export function apply(ctx, config) {
|
|
|
258
291
|
source: resolved.source,
|
|
259
292
|
checkedAt: new Date(resolved.at === 0 ? Date.now() : resolved.at).toISOString(),
|
|
260
293
|
error: latest === null ? resolved.error : '',
|
|
294
|
+
elapsedMs: Date.now() - startedAt,
|
|
261
295
|
},
|
|
262
296
|
},
|
|
263
297
|
{ headers: { 'cache-control': 'no-store' } },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-perf-boost",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "DSH 桌面端性能优化(纯客户端):长对话省渲染、窗口隐藏不排帧默认开,关背景模糊/减少动画/空闲降帧按需打开,设置页显示实测省下的主线程时间。Client-side rendering optimizations for DeepSeek Harness Desktop.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|