dsh-splash-launcher 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/DSH-GUI.exe CHANGED
Binary file
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # DSH GUI
2
2
 
3
- > DeepSeek Harness(dsh)Web GUI 的 Windows 一键启动器:双击即弹出无边框启动动画,后台拉起 `dsh web`,GUI 真正加载完成后无缝淡入;关闭窗口自动停服务。
3
+ > DeepSeek Harness(dsh)Web GUI 的 Windows 一键启动器:双击即用浏览器 `--app` 窗口的 splash 动画作为唯一启动画布,后台拉起 `dsh web`,GUI 就绪后揭示;关闭窗口自动停服务。
4
4
 
5
5
  启动动画借鉴 [SPlayer-Next](https://github.com/SPlayer-Dev/SPlayer-Next) 的思路:SVG `stroke-dashoffset` “一笔一划”书写效果——鲸鱼 Logo 升起呼吸,`deepseek` 官方字标浮现,`HARNESS` 七个字母逐笔描边。
6
6
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  ## 效果截图
10
10
 
11
- **启动动画(WPF 无边框,HARNESS 逐笔描边)**
11
+ **启动动画(浏览器 splash,HARNESS 逐笔描边)**
12
12
 
13
13
  ![启动动画](docs/screenshots/startup-animation.png)
14
14
 
@@ -20,11 +20,11 @@
20
20
 
21
21
  ## 功能特性
22
22
 
23
- - **点击即出动画**:WPF 原生动画窗口,不等待浏览器冷启动,双击后立刻播放;
23
+ - **点击即出动画**:以浏览器 splash 动画作为唯一启动画布,双击后立刻打开动画,无双层窗口互相抢层级;
24
24
  - **便携单文件**:`splash.html` 与 SVG/PNG 素材内嵌进 exe,`DSH-GUI.exe` 单独一个文件即可运行;同目录放置同名素材文件可覆盖内嵌版本(自定义动画,无需重编译);
25
25
  - **一笔一划描边**:`HARNESS` 七个字母按 CSS `cubic-bezier(0.25,0.1,0.25,1)` 缓动逐笔书写,笔尖连续、首帧空白;
26
26
  - **后台加载、就绪切换**:启动动画期间,真实 GUI 在浏览器后台(同源 iframe)完成加载,检测到官方启动页(`Loading plugins…`)消失后才淡出动画,不会出现“动画播完又加载”的断档;
27
- - **无 PowerShell**:启动器是单个 C#/WPF 程序,无脚本进程,规避“PowerShell 木马”类安全软件启发式误报;
27
+ - **无 PowerShell**:启动器是单个 C# 程序,无脚本进程,规避“PowerShell 木马”类安全软件启发式误报;
28
28
  - **开即启动、关即退出**:窗口关闭后自动结束本次启动的 `dsh web` 服务;端口已有服务时只开窗口、不接管生命周期;
29
29
  - **可配置**:工作目录与端口可通过环境变量或 `workspace.txt` 修改;
30
30
  - **深浅色跟随**:启动动画跟随 dsh 设置里的“外观”选项(浅色/深色/跟随系统),浅色为接近白色的淡蓝,深色保留原深蓝黑;
@@ -86,15 +86,15 @@ dsh plugin --profile web add dsh-splash-launcher
86
86
  双击 DSH-GUI.exe
87
87
  ├─ 端口已占用 ────────────────→ 直接打开 GUI 窗口(不显示动画、不接管服务)
88
88
  └─ 端口空闲
89
- ├─ 立即显示 WPF 无边框启动动画(≈0ms,不等浏览器)
90
- ├─ 解析 dsh 安装路径(npm root -g + node require.resolve)
91
- ├─ 将 splash 素材同步进 dsh-web-frontend/dist(同源 iframe 探测的前提)
89
+ ├─ 路径解析(缓存命中则 ~0ms;否则已知目录探测 + 并行解析,写入 resolved.cache)
90
+ ├─ 先打开浏览器 `--app` 窗口(file:// splash 动画,作为唯一启动画布,wait=token 模式)
91
+ ├─ 将 splash 素材同步进 dsh-web-frontend/dist(同源 splash.html 的前提)
92
92
  ├─ 后台启动 `node <dsh>/lib/bin.js web`(隐藏窗口)
93
93
  ├─ 轮询端口就绪(最多 90s)
94
- ├─ 以相同尺寸/位置在动画背后打开浏览器 `--app` 窗口
94
+ ├─ 就绪后把 token 写入 splash 同目录 token.js
95
+ ├─ 预热页轮询到 token → 原地自跳转到同源 splash(hold 态,单窗口、无跳窗闪烁)
95
96
  │ └─ splash.html(hold) 用隐藏 iframe 加载 `/`
96
97
  │ └─ 检测到官方启动卡片消失 → 揭示 iframe,并同步标题
97
- ├─ WPF 检测到浏览器标题包含 "DeepSeek Harness" → 淡出动画
98
98
  └─ 浏览器窗口关闭 → taskkill 结束本次 dsh web 进程树
99
99
  ```
100
100
 
@@ -102,7 +102,7 @@ dsh plugin --profile web add dsh-splash-launcher
102
102
 
103
103
  ```
104
104
  dsh-gui/
105
- ├─ DSH-GUI.cs # 启动器全部源码(C# 5 / WPF,单文件)
105
+ ├─ DSH-GUI.cs # 启动器全部源码(C# 5,单文件)
106
106
  ├─ build.cmd # 构建脚本(Windows 自带 csc.exe)
107
107
  ├─ splash.html # 浏览器侧启动页(hold 态 + 后台 iframe 预加载 + 就绪检测)
108
108
  ├─ deepseek-wordmark.svg # 官方 deepseek 字标(提取自 dsh 前端)
@@ -131,19 +131,18 @@ DSH-GUI.exe --selftest
131
131
 
132
132
  | 想调什么 | 位置 |
133
133
  |---|---|
134
- | 描边速度 | `SplashWindow.DrawSeconds`(默认 0.22s/字母,线性速度) |
135
- | 相邻字母起笔间隔 | `SplashWindow.LetterStep`(默认 0.14s,重叠书写) |
136
- | 动画窗口尺寸 | `Program.WinW / WinH`(默认 1100×720 |
137
- | 最短动画时长 | `SplashWindow.MinShowSec`(默认 2.0s) |
138
- | 就绪判定标题 | `SplashWindow.AppTitle`(按“包含”匹配) |
134
+ | 描边速度 | `splash.html` 的 `.splash-harness .letter` `animation` 时长(0.22s/字母) |
135
+ | 相邻字母起笔间隔 | `splash.html` `.splash-harness path:nth-of-type(n)` `animation-delay` |
136
+ | 动画/浏览器窗口尺寸 | `Program.WinW / WinH`(默认 1100×720,`DSH-GUI.cs`) |
137
+ | 版式(鲸鱼描边) | `Program.SplashStyle` 改为 `"&logo=draw"`(`DSH-GUI.cs`) |
139
138
 
140
139
  ## 已知问题
141
140
 
142
141
  ### 1. 任务栏图标会先闪一下 Chrome/Edge 默认图标
143
- 浏览器 `--app` 窗口创建时,任务栏按钮先使用浏览器进程自带图标,页面 `favicon`(黑鲸)加载完成后才切换,因此会出现极短暂的一闪。这是浏览器自身的图标占位机制,没有命令行参数可预设;当前动画窗口会盖住这段过程,实际看到界面时已是黑鲸图标。彻底隐藏需要 Win32 控制窗口显隐,可能引起页面节流、拖慢启动,暂不实现。
142
+ 浏览器 `--app` 窗口创建时,任务栏按钮先使用浏览器进程自带图标,页面 `favicon`(黑鲸)加载完成后才切换,因此会出现极短暂的一闪。这是浏览器自身的图标占位机制,没有命令行参数可预设。启动器不设覆盖层,该一闪在浏览器冷启动阶段会短暂可见。彻底隐藏需要 Win32 控制窗口显隐,可能引起页面节流、拖慢启动,暂不实现。
144
143
 
145
144
  ### 2. 启动有一定等待时间
146
- 启动时长主要来自:`dsh web` 服务引导 + 浏览器冷启动 + 前端插件装载。动画会覆盖全部等待过程并持续到 GUI 就绪,属于“可见但不可压缩”的时间。首次运行、杀毒软件实时扫描、机械硬盘会进一步加长。可通过 WPF 描边速度(见上)让动画与等待时长更匹配。
145
+ 启动时长主要来自:`dsh web` 服务引导 + 浏览器冷启动 + 前端插件装载,动画会覆盖绝大多数等待过程并持续到 GUI 就绪,属于“可见但基本不可压缩”的时间。首次运行、杀毒软件实时扫描、机械硬盘会进一步加长。启动器自身的路径解析已缓存(`resolved.cache`)且最早预热浏览器,能显著缩短总时长;动画节奏可在 `splash.html` 的 CSS 里调节。
147
146
 
148
147
  ### 3. SmartScreen / 安全软件可能误报(未签名 exe)
149
148
  本程序会启动隐藏进程、结束后台进程、复制文件,这类“类管理工具”行为可能触发启发式防护(例如卡巴斯基 PDM:Trojan.Win32.Generic 对旧版 PowerShell 脚本的误报)。建议:
@@ -157,8 +156,8 @@ DSH-GUI.exe --selftest
157
156
  ### 5. 同源预加载依赖 dist 写入权限
158
157
  启动器需要把 `splash.html` 等素材复制到 npm 全局的 `dsh-web-frontend/dist` 目录。若该目录不可写,会自动退回“文件页动画 → 就绪后直接跳转 GUI”模式(素材内嵌于 exe,此模式下自动释放到 `%LocalAppData%\DSH-GUI\assets`),此时会短暂看到官方 HARNESS 加载页。
159
158
 
160
- ### 6. 就绪判定依赖标题
161
- WPF 以浏览器窗口标题包含 `DeepSeek Harness` 判定 GUI 就绪;若官方前端修改标题结构,可能需要同步更新 `SplashWindow.AppTitle`。
159
+ ### 6. 就绪判定依赖官方启动卡片
160
+ 浏览器 splash 以同源 iframe 里官方启动卡片(`_boot_*` class)消失判定 GUI 就绪;若官方前端改了启动卡片的 class 结构,可能需要同步更新 `splash.html` 里的 `appReady()` 判定。
162
161
 
163
162
  ## 许可证
164
163
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-splash-launcher",
3
- "version": "1.3.0",
4
- "description": "DSH Windows one-click launcher with a stroke-by-stroke startup animation: ships a self-contained DSH-GUI.exe and registers the desktop_launch agent tool.",
3
+ "version": "1.4.0",
4
+ "description": "DSH Windows one-click launcher: opens the browser splash animation immediately, boots dsh web, and reveals the GUI when ready; also registers the desktop_launch agent tool.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "scripts": {
package/splash.html CHANGED
@@ -155,14 +155,14 @@
155
155
  filter: var(--logo-filter);
156
156
  will-change: transform, opacity;
157
157
  animation:
158
- splash-rise 0.5s cubic-bezier(0.16, 1, 0.3, 1) both,
158
+ splash-rise 0.4s cubic-bezier(0.16, 1, 0.3, 1) both,
159
159
  splash-flicker 1.8s ease-in-out 0.5s infinite;
160
160
  }
161
161
  .splash-logo.draw {
162
162
  width: 96px;
163
163
  height: 96px;
164
164
  filter: none;
165
- animation: splash-rise 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
165
+ animation: splash-rise 0.4s cubic-bezier(0.16, 1, 0.3, 1) both;
166
166
  }
167
167
 
168
168
  .splash-brand {
@@ -179,7 +179,7 @@
179
179
  margin-top: 4px;
180
180
  filter: var(--wordmark-filter);
181
181
  will-change: transform, opacity;
182
- animation: splash-rise 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.1s both;
182
+ animation: splash-rise 0.4s cubic-bezier(0.16, 1, 0.3, 1) 0.08s both;
183
183
  }
184
184
 
185
185
  /* “HARNESS”七个字母:与 SPlayer-Next 的 Next 相同的描边动画,作为视觉重点 */
@@ -196,16 +196,16 @@
196
196
  stroke-dasharray: 400;
197
197
  stroke-dashoffset: 400;
198
198
  will-change: stroke-dashoffset;
199
- /* 每个字母 0.22s,前一个未写完下一个就开始,linear 线性速度,整体约 1.3s */
200
- animation: splash-draw 0.22s linear forwards;
201
- }
202
- .splash-harness path:nth-of-type(1) { animation-delay: 0.20s; }
203
- .splash-harness path:nth-of-type(2) { animation-delay: 0.34s; }
204
- .splash-harness path:nth-of-type(3) { animation-delay: 0.48s; }
205
- .splash-harness path:nth-of-type(4) { animation-delay: 0.62s; }
206
- .splash-harness path:nth-of-type(5) { animation-delay: 0.76s; }
207
- .splash-harness path:nth-of-type(6) { animation-delay: 0.90s; }
208
- .splash-harness path:nth-of-type(7) { animation-delay: 1.04s; }
199
+ /* 每个字母 0.22s -> 1.25x 加快即 0.176s;前一个未写完下一个就开始,linear 线性速度,整体约 1.0s */
200
+ animation: splash-draw 0.176s linear forwards;
201
+ }
202
+ .splash-harness path:nth-of-type(1) { animation-delay: 0.16s; }
203
+ .splash-harness path:nth-of-type(2) { animation-delay: 0.27s; }
204
+ .splash-harness path:nth-of-type(3) { animation-delay: 0.38s; }
205
+ .splash-harness path:nth-of-type(4) { animation-delay: 0.50s; }
206
+ .splash-harness path:nth-of-type(5) { animation-delay: 0.61s; }
207
+ .splash-harness path:nth-of-type(6) { animation-delay: 0.72s; }
208
+ .splash-harness path:nth-of-type(7) { animation-delay: 0.83s; }
209
209
 
210
210
  /* 浅色模式下换用更深的渐变,保证 HARNESS 在接近白色的背景上清晰可见 */
211
211
  @media (prefers-color-scheme: light) {
@@ -230,7 +230,7 @@
230
230
  font-size: 12px;
231
231
  letter-spacing: 2px;
232
232
  color: var(--muted);
233
- animation: splash-rise-footer 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.36s both;
233
+ animation: splash-rise-footer 0.4s cubic-bezier(0.16, 1, 0.3, 1) 0.288s both;
234
234
  }
235
235
 
236
236
  /* hold 模式:直接呈现动画结束态(WPF 即时启动页 → 浏览器启动页无缝衔接) */
@@ -318,6 +318,11 @@
318
318
  var timeoutSec = Math.max(15, parseInt(qs.get('timeout') || '90', 10) || 90);
319
319
  // 双击 splash.html 打开(无参数)= 动画循环预览
320
320
  var demo = qs.get('demo') === '1' || !qs.has('target');
321
+ // 预热窗口专用:只播放动画,不探测端口、不自行跳转,由启动器手动接管交接
322
+ var stay = qs.get('stay') === '1';
323
+ // wait=token:预热窗口轮询同目录 token.js(由启动器在服务就绪+拿到 token 后写入),
324
+ // 读到 token 即代表服务就绪,原地自跳转到带 token 的同源 splash——单窗口、无缝、无闪烁。
325
+ var waitToken = qs.get('wait') === 'token';
321
326
  // logo=draw:切换成鲸鱼本体一笔一划的旧版式
322
327
  var drawLogo = qs.get('logo') === 'draw';
323
328
  // 由 dsh 服务托管(同源):可后台 iframe 加载真实 GUI 并检测就绪
@@ -343,6 +348,46 @@
343
348
  return;
344
349
  }
345
350
 
351
+ if (stay) {
352
+ // 预热窗口只做动画展示,交给启动器 ClosePrewarmBrowser 关闭并打开正式窗口
353
+ return;
354
+ }
355
+
356
+ if (waitToken) {
357
+ // 单窗口自跳转:轮询同目录 token.js,拿到 token 后原地跳到带 token 的同源 splash。
358
+ // token 本身就是"服务已就绪"的信号(启动器只在 WaitPort+WaitLaunchToken 成功后写入)。
359
+ var base0 = target.replace(/\/+$/, '');
360
+ var navServed = function (token) {
361
+ if (leaving) return;
362
+ leaving = true;
363
+ var servedUrl = base0 + '/splash.html?hold=1&timeout=60'
364
+ + '&target=' + encodeURIComponent(target + '?token=' + encodeURIComponent(token))
365
+ + (drawLogo ? '&logo=draw' : '')
366
+ + (theme ? '&theme=' + encodeURIComponent(theme) : '');
367
+ location.replace(servedUrl);
368
+ };
369
+ var pollToken = function () {
370
+ if (leaving) return;
371
+ if (window.__DSH_TOKEN) { navServed(window.__DSH_TOKEN); return; }
372
+ if ((performance.now() - startedAt) > timeoutSec * 1000) {
373
+ // 兜底:极低概率 token 一直没来,直接进 target(无 token 可能 401,但已属异常兜底)
374
+ leaving = true; location.replace(target);
375
+ return;
376
+ }
377
+ var s = document.createElement('script');
378
+ s.src = 'token.js?v=' + Date.now();
379
+ s.onload = function () {
380
+ if (window.__DSH_TOKEN) { navServed(window.__DSH_TOKEN); }
381
+ else setTimeout(pollToken, 300);
382
+ };
383
+ s.onerror = function () { setTimeout(pollToken, 300); };
384
+ document.head.appendChild(s);
385
+ setTimeout(pollToken, 600);
386
+ };
387
+ pollToken();
388
+ return;
389
+ }
390
+
346
391
  var frame = document.getElementById('appFrame');
347
392
  frame.setAttribute('allow', 'clipboard-read; clipboard-write; fullscreen; autoplay');
348
393
 
@@ -408,8 +453,10 @@
408
453
  }
409
454
 
410
455
  if (served) {
411
- /* 同源模式:后台加载真实 GUI,真正就绪后再揭示 */
412
- frame.src = '/';
456
+ /* 同源模式:后台加载真实 GUI,真正就绪后再揭示。
457
+ alpha.3+ 的根路径需要 token 认证:target 携带 token 时首次加载完成换 cookie,
458
+ 服务端 303 回到干净根路径;无 target 参数时退回原来的 '/'。 */
459
+ frame.src = target;
413
460
  var minBeforeReveal = hold ? 250 : splashAnimMs;
414
461
  var revealDeadline = startedAt + timeoutSec * 1000;
415
462
  (function watch() {
@@ -430,7 +477,8 @@
430
477
  if (up && elapsed >= splashAnimMs) {
431
478
  if (handoff) {
432
479
  var base = target.replace(/\/+$/, '');
433
- fadeAndGo(base + '/splash.html?hold=1&timeout=60' + (drawLogo ? '&logo=draw' : ''));
480
+ // 把带 token target 传给同源 splash,alpha.3+ 的根路径认证才能通过
481
+ fadeAndGo(base + '/splash.html?hold=1&timeout=60&target=' + encodeURIComponent(target) + (drawLogo ? '&logo=draw' : ''));
434
482
  } else {
435
483
  fadeAndGo(target);
436
484
  }