dsh-visualization 0.1.1 → 0.1.3
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/FAQ.md +85 -0
- package/README.md +59 -1
- package/fold.cjs +44 -2
- package/index.js +7 -0
- package/package.json +4 -2
- package/wallpaper/index.html +62 -0
- package/wallpaper/preview.jpg +0 -0
- package/wallpaper/project.json +13 -0
- package/web/index.html +51 -5
package/FAQ.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# FAQ / 常见问题
|
|
2
|
+
|
|
3
|
+
## Why is the license not OSI-approved? / 为什么不用 OSI 许可?
|
|
4
|
+
|
|
5
|
+
**EN** — By design, not by accident. The visual language — the rings, the palette, the layered
|
|
6
|
+
layout — derives from [HALOFLUX](https://github.com/Ryder191/haloflux), which is licensed under
|
|
7
|
+
PolyForm Noncommercial 1.0.0. Those terms require any derivative to stay noncommercial and to keep
|
|
8
|
+
the attribution. An MIT/Apache/BSD badge here would be a promise this project cannot keep, so the
|
|
9
|
+
plugin adopts the same license: free for personal use, study, research, teaching and hobby
|
|
10
|
+
projects; commercial use is not granted.
|
|
11
|
+
|
|
12
|
+
The *code* (host folding, zstd frame parsing, SSE, the page) is original, written for this plugin.
|
|
13
|
+
Nothing was copied from HALOFLUX's source; what it borrows is the design language, and that is
|
|
14
|
+
credited in both the LICENSE and the page footer.
|
|
15
|
+
|
|
16
|
+
**中文** — 这是刻意的,不是疏漏。视觉语言(环、配色、分层布局)来自
|
|
17
|
+
[HALOFLUX](https://github.com/Ryder191/haloflux),它使用 PolyForm Noncommercial 1.0.0。
|
|
18
|
+
该许可要求衍生作品同为非商业并保留署名。这里挂 MIT/Apache 就等于是本项目兑现不了的承诺,
|
|
19
|
+
所以插件沿用同一许可:个人使用、学习、研究、教学、兴趣项目免费;不授予商业用途。
|
|
20
|
+
|
|
21
|
+
代码本身(宿主折叠、zstd 帧解析、SSE、页面)是为本插件新写的,没有复制 HALOFLUX 的源码;
|
|
22
|
+
借用的是设计语言,LICENSE 与页面右下角都写明了出处。
|
|
23
|
+
|
|
24
|
+
> Marketplace listings do not require an OSI-approved license — other listed plugins in this catalog
|
|
25
|
+
> also ship non-OSI terms.
|
|
26
|
+
> 插件市场并不要求 OSI 许可——本目录里已有其他非 OSI 许可的插件。
|
|
27
|
+
|
|
28
|
+
## Does it send my data anywhere? / 会把数据发出去吗?
|
|
29
|
+
|
|
30
|
+
**EN** — No. The host half reads `~/.dsh/sessions/**` read-only, folds it in memory, and serves it on
|
|
31
|
+
`/dsh-visualization/*` to the local page. No outbound network request is made by either half, and no
|
|
32
|
+
file is written except the OS-level cache the browser keeps for the page itself.
|
|
33
|
+
|
|
34
|
+
**中文** — 不会。宿主侧只读 `~/.dsh/sessions/**`,在内存里折叠,通过 `/dsh-visualization/*`
|
|
35
|
+
提供给本地页面。前后两端都不发起任何对外网络请求,也不写任何文件(浏览器自身的页面缓存除外)。
|
|
36
|
+
|
|
37
|
+
## Why can it only be reached from localhost? / 为什么只能本机访问?
|
|
38
|
+
|
|
39
|
+
**EN** — Session logs contain every command you ran and every prompt you typed. The routes answer
|
|
40
|
+
loopback addresses only, so exposing the Web UI on a LAN does not expose the instrument. If you
|
|
41
|
+
really need remote access, set `allowRemote: true` in the plugin config and understand that anyone
|
|
42
|
+
who can reach the port can read those logs.
|
|
43
|
+
|
|
44
|
+
**中文** — 会话日志里有你执行过的每条命令和输入的每句话。路由只响应回环地址,所以把 Web 界面暴露到
|
|
45
|
+
局域网并不会顺带暴露仪表盘。确实需要远程访问时,在插件配置里写 `allowRemote: true`——
|
|
46
|
+
但要清楚:任何能访问该端口的人都能读到这些日志。
|
|
47
|
+
|
|
48
|
+
## The panel is blank / says "no session log" / 面板空白、提示没有会话
|
|
49
|
+
|
|
50
|
+
| 提示 | 原因 |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `这个运行环境不支持 zstd 解压(需要 Node ≥ 22.15)` | 宿主 Node 太老 |
|
|
53
|
+
| `会话目录是空的 —— 先在这个 profile 里跑一轮对话` | 这个 profile 还没聊过 |
|
|
54
|
+
| `还没找到 DSH 会话目录(…),确认 DSH_HOME 是否正确` | DSH 数据不在默认位置;设 `DSH_HOME` 或 `DSHVIZ_SESSIONS_ROOT` |
|
|
55
|
+
| 面板空白 + 网络面板 403 | 不是本机访问;见上一节 |
|
|
56
|
+
|
|
57
|
+
## Does it slow the session down? / 会拖慢会话吗?
|
|
58
|
+
|
|
59
|
+
**EN** — The fold is incremental: only newly appended zstd frames are decoded, and only new events
|
|
60
|
+
are pushed into the accumulator. At ~8.7k events the first fold costs 240–260 ms; afterwards each
|
|
61
|
+
change costs 7–12 ms. When nothing changes, the SSE connection sends zero bytes (except a 15 s
|
|
62
|
+
heartbeat).
|
|
63
|
+
|
|
64
|
+
**中文** — 折叠是增量的:只解新追加的 zstd 帧,只把新事件 push 进累加器。约 8.7k 事件时首次
|
|
65
|
+
240~260ms,之后每次变化 7~12ms。没有变化时 SSE 一个字节都不发(只有 15 秒一次的心跳)。
|
|
66
|
+
|
|
67
|
+
## How do I uninstall it? / 怎么卸载?
|
|
68
|
+
|
|
69
|
+
```powershell
|
|
70
|
+
# DSH 插件页里关掉,或:
|
|
71
|
+
dsh plugin remove dsh-visualization
|
|
72
|
+
# npm 装的:
|
|
73
|
+
npm uninstall -g dsh-visualization
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
仪表盘不写任何持久状态;删除后 `~/.dsh/sessions` 里的日志原样不动。
|
|
77
|
+
|
|
78
|
+
## Can I use it at work? / 能在公司用吗?
|
|
79
|
+
|
|
80
|
+
**EN** — No. PolyForm Noncommercial excludes use by a for-profit company, including internal use
|
|
81
|
+
and use on behalf of a company. Personal study and noncommercial organisations (charities,
|
|
82
|
+
educational and public research institutions) are permitted by the license text.
|
|
83
|
+
|
|
84
|
+
**中文** — 不行。PolyForm Noncommercial 排除了在营利性公司内部或代表公司的使用。个人学习、
|
|
85
|
+
以及慈善、教育、公共研究机构等非商业组织,按许可条文是允许的。
|
package/README.md
CHANGED
|
@@ -36,6 +36,8 @@ rings, turns, tools and background jobs.
|
|
|
36
36
|
|
|
37
37
|
A ring icon appears in the left rail; click it to open the instrument.
|
|
38
38
|
|
|
39
|
+
Or from npm: pm i dsh-visualization (package: https://www.npmjs.com/package/dsh-visualization).
|
|
40
|
+
|
|
39
41
|
## Reading the panel
|
|
40
42
|
|
|
41
43
|
| Area | What it tells you |
|
|
@@ -73,6 +75,34 @@ Direction matters: on ②–⑦ the newest data sits at 12 o'clock and older dat
|
|
|
73
75
|
The UI ships in Chinese and English. It starts from `navigator.language`, remembers your choice,
|
|
74
76
|
and can be forced per page with `?lang=en` / `?lang=zh`.
|
|
75
77
|
|
|
78
|
+
## Using it as a desktop wallpaper
|
|
79
|
+
|
|
80
|
+
The panel can also run inside **Wallpaper Engine** (Steam). The plugin opens a small fixed-port
|
|
81
|
+
endpoint — `http://127.0.0.1:8799/` — because the DSH Web port changes on every launch and a
|
|
82
|
+
wallpaper cannot guess it.
|
|
83
|
+
|
|
84
|
+
**Requirements**: the plugin installed, and DSH running. If DSH is off the wallpaper shows a hint
|
|
85
|
+
and retries every 5 seconds; when DSH comes back on a different port the wallpaper finds it again.
|
|
86
|
+
|
|
87
|
+
**Install the wallpaper**
|
|
88
|
+
|
|
89
|
+
1. In Wallpaper Engine: **Create Wallpaper** → pick **Web** as the type
|
|
90
|
+
2. When it asks for a file, choose the `wallpaper/index.html` from this repository (or
|
|
91
|
+
`wallpaper/project.json` if your version of WE asks for a project)
|
|
92
|
+
3. Save the project somewhere permanent and preview it. It connects to `127.0.0.1:8799` by itself.
|
|
93
|
+
|
|
94
|
+
**Changing the port**: the plugin tries 8799, then 8798, 8797, 8796, 8795. To use a different base
|
|
95
|
+
port, set `wallpaperPort` in the plugin config; set it to `0` to turn the endpoint off.
|
|
96
|
+
|
|
97
|
+
**If it stays on the "not connected" screen**
|
|
98
|
+
|
|
99
|
+
| Cause | Fix |
|
|
100
|
+
|---|---|
|
|
101
|
+
| DSH is not running | start it |
|
|
102
|
+
| the plugin is disabled | enable `dsh-visualization` on the Plugins page |
|
|
103
|
+
| 8799–8795 are all taken | set `wallpaperPort` to a free port and edit `PORTS` in `wallpaper/index.html` |
|
|
104
|
+
| CPU spikes while gaming | in Wallpaper Engine, enable "pause when a fullscreen app runs" |
|
|
105
|
+
|
|
76
106
|
## When something looks wrong
|
|
77
107
|
|
|
78
108
|
| Symptom | Cause |
|
|
@@ -144,6 +174,7 @@ list and key the dictionaries by code.
|
|
|
144
174
|
|---|---|---|
|
|
145
175
|
| `basePath` | `/dsh-visualization` | route prefix |
|
|
146
176
|
| `allowRemote` | `false` | allow non-loopback peers. **This exposes session logs to anyone who can reach the port.** |
|
|
177
|
+
| `wallpaperPort` | `8799` | fixed local port for the wallpaper entry; tries 8799 down to 8795. `0` disables it |
|
|
147
178
|
|
|
148
179
|
## Routes
|
|
149
180
|
|
|
@@ -223,6 +254,32 @@ DSH 装在别处时用环境变量 `DSH_HOME` 或 `DSHVIZ_SESSIONS_ROOT` 指过
|
|
|
223
254
|
| 某个格子暗、没有线 | 它不在「连线轮数」范围内 |
|
|
224
255
|
| 格子亮、没有线 | 这一步没有工具调用(纯文字步) |
|
|
225
256
|
|
|
257
|
+
## 当桌面壁纸用(Wallpaper Engine)
|
|
258
|
+
|
|
259
|
+
这个面板也能放进 **Wallpaper Engine**(Steam)当桌面壁纸。插件会在本机开一个**固定端口**——
|
|
260
|
+
`http://127.0.0.1:8799/`——因为 DSH 的 Web 端口每次启动都会变,壁纸没法猜。
|
|
261
|
+
|
|
262
|
+
**前提**:装了插件,且 DSH 正在运行。DSH 关掉时壁纸显示提示并每 5 秒重试;DSH 换了端口重启,
|
|
263
|
+
壁纸会自己重新找到。
|
|
264
|
+
|
|
265
|
+
**导入步骤**
|
|
266
|
+
|
|
267
|
+
1. Wallpaper Engine 里点 **创建壁纸(Create Wallpaper)** → 类型选 **网页(Web)**
|
|
268
|
+
2. 选文件时指向本仓库的 `wallpaper/index.html`(如果你的版本要选工程,就选 `wallpaper/project.json`)
|
|
269
|
+
3. 存到固定位置,应用即可。它会自己连上 `127.0.0.1:8799`
|
|
270
|
+
|
|
271
|
+
**改端口**:插件会依次尝试 8799 → 8798 → 8797 → 8796 → 8795。想换起始端口,在插件配置里写
|
|
272
|
+
`wallpaperPort`;写 `0` 就是关掉这个入口。
|
|
273
|
+
|
|
274
|
+
**一直显示"没有连上"怎么办**
|
|
275
|
+
|
|
276
|
+
| 原因 | 处理 |
|
|
277
|
+
|---|---|
|
|
278
|
+
| DSH 没开 | 启动 DSH |
|
|
279
|
+
| 插件被禁用了 | 在插件页启用 `dsh-visualization` |
|
|
280
|
+
| 8795–8799 都被占用 | 配置里换个端口,同时改 `wallpaper/index.html` 里的 `PORTS` 数组 |
|
|
281
|
+
| 玩游戏时卡顿 | 在 Wallpaper Engine 里打开"其他应用全屏时暂停" |
|
|
282
|
+
|
|
226
283
|
## 给 agent / 开发者的编辑说明
|
|
227
284
|
|
|
228
285
|
**文件**:`index.js`(宿主入口,注册路由 + 注入 origin/策略)、`fold.cjs`(会话发现、zstd 帧解析、
|
|
@@ -246,7 +303,8 @@ DSH 装在别处时用环境变量 `DSH_HOME` 或 `DSHVIZ_SESSIONS_ROOT` 指过
|
|
|
246
303
|
`showDetail()` 的详情分支;新语言 = `EN` 词典 + `EN_RULES` 正则 + `EN_FN` 逻辑替换,并把 `LANG`
|
|
247
304
|
从 `'zh'|'en'` 改成列表;新工具配色 = `toolColor()`。
|
|
248
305
|
|
|
249
|
-
**配置**:`basePath`(默认 `/dsh-visualization`)、`allowRemote`(默认 `false
|
|
306
|
+
**配置**:`basePath`(默认 `/dsh-visualization`)、`allowRemote`(默认 `false`,打开等于把会话日志暴露给能访问该端口的人)、
|
|
307
|
+
`wallpaperPort`(默认 `8799`,壁纸用的固定端口,写 0 关闭)。
|
|
250
308
|
|
|
251
309
|
**已知限制**:内存随会话线性增长(十万条时每帧 `result()` 约 10~20ms);界面只有中英两门语言;
|
|
252
310
|
仪表只读,从不写会话文件。
|
package/fold.cjs
CHANGED
|
@@ -658,6 +658,40 @@ function setPolicy(policy) {
|
|
|
658
658
|
if (policy && typeof policy.allowRemote === 'boolean') ALLOW_REMOTE = policy.allowRemote
|
|
659
659
|
}
|
|
660
660
|
|
|
661
|
+
|
|
662
|
+
/* ---------------- 固定端口的"壁纸入口" ----------------
|
|
663
|
+
DSH 的 Web 端口每次启动都会变,壁纸里写死端口就必然失效。
|
|
664
|
+
这里在 127.0.0.1 上开一个固定端口(默认 8799,被占用就依次退),
|
|
665
|
+
只转发到本进程的 handle(),所以壁纸永远访问同一个地址。 */
|
|
666
|
+
let HELPER = null
|
|
667
|
+
|
|
668
|
+
function startHelper(preferredPort) {
|
|
669
|
+
if (HELPER) return HELPER
|
|
670
|
+
const http = require('node:http')
|
|
671
|
+
const base = Number(preferredPort) || 8799
|
|
672
|
+
let attempt = 0
|
|
673
|
+
const tryListen = () => {
|
|
674
|
+
if (attempt > 4) return null
|
|
675
|
+
const port = base - attempt
|
|
676
|
+
attempt++
|
|
677
|
+
const srv = http.createServer((rq, rs) => {
|
|
678
|
+
/* 把根路径映射成插件前缀,handle() 内部按 /dsh-visualization 路由 */
|
|
679
|
+
rq.url = '/dsh-visualization' + (rq.url || '/')
|
|
680
|
+
handle(rq, rs)
|
|
681
|
+
})
|
|
682
|
+
srv.on('error', () => { try { srv.close() } catch (e) { } tryListen() })
|
|
683
|
+
srv.listen(port, '127.0.0.1', () => {
|
|
684
|
+
HELPER = { port: port, server: srv }
|
|
685
|
+
try { console.log('[dsh-visualization] wallpaper entry: http://127.0.0.1:' + port + '/') } catch (e) { }
|
|
686
|
+
})
|
|
687
|
+
return srv
|
|
688
|
+
}
|
|
689
|
+
tryListen()
|
|
690
|
+
return HELPER
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function helperPort() { return HELPER ? HELPER.port : 0 }
|
|
694
|
+
|
|
661
695
|
function handle(req, res) {
|
|
662
696
|
if (!ALLOW_REMOTE && !isLoopback(req)) {
|
|
663
697
|
res.writeHead(403, { 'content-type': 'text/plain; charset=utf-8' })
|
|
@@ -677,11 +711,16 @@ function handle(req, res) {
|
|
|
677
711
|
'cache-control': 'no-store',
|
|
678
712
|
'content-encoding': 'gzip',
|
|
679
713
|
vary: 'accept-encoding',
|
|
714
|
+
'access-control-allow-origin': '*',
|
|
680
715
|
})
|
|
681
716
|
res.end(zlib.gzipSync(body))
|
|
682
717
|
return
|
|
683
718
|
}
|
|
684
|
-
res.writeHead(200, {
|
|
719
|
+
res.writeHead(200, {
|
|
720
|
+
'content-type': 'application/json; charset=utf-8',
|
|
721
|
+
'cache-control': 'no-store',
|
|
722
|
+
'access-control-allow-origin': '*',
|
|
723
|
+
})
|
|
685
724
|
res.end(body)
|
|
686
725
|
}
|
|
687
726
|
/* 救急:把浏览器里还开着的那一页 DOM 存回磁盘(instrument.html 被误覆盖时用) */
|
|
@@ -832,4 +871,7 @@ function handle(req, res) {
|
|
|
832
871
|
res.writeHead(404); res.end('not found')
|
|
833
872
|
}
|
|
834
873
|
|
|
835
|
-
module.exports = {
|
|
874
|
+
module.exports = {
|
|
875
|
+
handle, listSessions, pickSession, refresh, fold, makeFolder,
|
|
876
|
+
setOrigin, setPolicy, startHelper, helperPort, hasZstd: HAS_ZSTD,
|
|
877
|
+
}
|
package/index.js
CHANGED
|
@@ -27,4 +27,11 @@ export function apply(ctx, config = {}) {
|
|
|
27
27
|
path: prefix,
|
|
28
28
|
handler: (req2, res) => core.handle(req2, res),
|
|
29
29
|
}))
|
|
30
|
+
|
|
31
|
+
/* 固定端口的壁纸入口(默认 8799):桌面壁纸没法知道 DSH 每次随机分配的端口。
|
|
32
|
+
config.wallpaperPort 可以改,写 0 关闭。 */
|
|
33
|
+
try {
|
|
34
|
+
const port = config && config.wallpaperPort !== undefined ? Number(config.wallpaperPort) : 8799
|
|
35
|
+
if (port > 0) ctx.effect(() => (core.startHelper(port), () => { }))
|
|
36
|
+
} catch (e) { /* 端口被占之类的情况不影响主路由 */ }
|
|
30
37
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-visualization",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "DSH Visualization — a session instrument: rings, turns, tools and background jobs of a live DeepSeek Harness session.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
"locale",
|
|
27
27
|
"icon.svg",
|
|
28
28
|
"LICENSE",
|
|
29
|
-
"README.md"
|
|
29
|
+
"README.md",
|
|
30
|
+
"FAQ.md",
|
|
31
|
+
"wallpaper"
|
|
30
32
|
],
|
|
31
33
|
"keywords": [
|
|
32
34
|
"dsh",
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>DSH Visualization</title>
|
|
6
|
+
<style>
|
|
7
|
+
html, body { margin: 0; height: 100%; background: #0b0d11; overflow: hidden; }
|
|
8
|
+
iframe { width: 100vw; height: 100vh; border: 0; display: block; }
|
|
9
|
+
#off {
|
|
10
|
+
position: fixed; inset: 0; display: none; flex-direction: column;
|
|
11
|
+
align-items: center; justify-content: center; gap: 14px;
|
|
12
|
+
color: #8d97ab; font: 15px/1.7 "Segoe UI", "Microsoft YaHei", sans-serif;
|
|
13
|
+
background: radial-gradient(circle at 50% 45%, #141a24, #0b0d11 70%);
|
|
14
|
+
text-align: center;
|
|
15
|
+
}
|
|
16
|
+
#off b { color: #e6edfa; font-size: 19px; font-weight: 600; letter-spacing: .04em; }
|
|
17
|
+
#off code { color: #ffc46b; background: #1a1f29; padding: 2px 7px; border-radius: 4px; font-size: 13px; }
|
|
18
|
+
</style>
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
<iframe id="frame" allow="cross-origin-isolated"></iframe>
|
|
22
|
+
<div id="off">
|
|
23
|
+
<b>DSH Visualization</b>
|
|
24
|
+
<div>没有连上 DeepSeek Harness。</div>
|
|
25
|
+
<div>请确认:装了 <code>dsh-visualization</code> 插件,并且 DSH 正在运行。</div>
|
|
26
|
+
<div style="opacity:.65">每 5 秒自动重试</div>
|
|
27
|
+
</div>
|
|
28
|
+
<script>
|
|
29
|
+
/* 插件的壁纸入口固定在这个区间(8799 起,被占用依次往下) */
|
|
30
|
+
var PORTS = [8799, 8798, 8797, 8796, 8795];
|
|
31
|
+
var frame = document.getElementById('frame');
|
|
32
|
+
var off = document.getElementById('off');
|
|
33
|
+
var chosen = 0;
|
|
34
|
+
|
|
35
|
+
function probe() {
|
|
36
|
+
var i = 0;
|
|
37
|
+
function next() {
|
|
38
|
+
if (i >= PORTS.length) { frame.style.display = 'none'; off.style.display = 'flex'; chosen = 0; return; }
|
|
39
|
+
var p = PORTS[i++];
|
|
40
|
+
var ctl = new AbortController();
|
|
41
|
+
var timer = setTimeout(function () { ctl.abort() }, 1200);
|
|
42
|
+
fetch('http://127.0.0.1:' + p + '/state', { signal: ctl.signal, cache: 'no-store' })
|
|
43
|
+
.then(function (r) { return r.ok ? r.json() : null })
|
|
44
|
+
.then(function (j) {
|
|
45
|
+
clearTimeout(timer);
|
|
46
|
+
if (!j) return next();
|
|
47
|
+
chosen = p;
|
|
48
|
+
off.style.display = 'none';
|
|
49
|
+
frame.style.display = 'block';
|
|
50
|
+
if (frame.src.indexOf(':' + p + '/') < 0) frame.src = 'http://127.0.0.1:' + p + '/';
|
|
51
|
+
})
|
|
52
|
+
.catch(function () { clearTimeout(timer); next(); });
|
|
53
|
+
}
|
|
54
|
+
next();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
probe();
|
|
58
|
+
/* 断线后自动重连:DSH 重启会换端口,所以每次都重新探测 */
|
|
59
|
+
setInterval(function () { if (!chosen) probe(); }, 5000);
|
|
60
|
+
</script>
|
|
61
|
+
</body>
|
|
62
|
+
</html>
|
|
Binary file
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contentrating": "Everyone",
|
|
3
|
+
"description": "A live session instrument for DeepSeek Harness: seven rings, a turn timeline, per-step ladders. Needs the dsh-visualization plugin installed and DSH running — the wallpaper talks to http://127.0.0.1:8799/, which the plugin opens on a fixed port. Noncommercial; visual language derived from HALOFLUX by Ryder191.",
|
|
4
|
+
"file": "index.html",
|
|
5
|
+
"preview": "preview.jpg",
|
|
6
|
+
"tags": [
|
|
7
|
+
"Abstract",
|
|
8
|
+
"Technology"
|
|
9
|
+
],
|
|
10
|
+
"title": "DSH Visualization",
|
|
11
|
+
"type": "web",
|
|
12
|
+
"version": 1
|
|
13
|
+
}
|
package/web/index.html
CHANGED
|
@@ -979,6 +979,21 @@ html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--ink);
|
|
|
979
979
|
for (var cq = 0; cq < CHAIN_PREV.length; cq++) {
|
|
980
980
|
var ce = CHAIN_PREV[cq], cps = ce.pts;
|
|
981
981
|
for (var cp = 1; cp < cps.length; cp++) segLine(cps[cp - 1], cps[cp], ce.bright, ce.fr);
|
|
982
|
+
/* 用到技能的那一步:③ 上加发光环 + 两段高亮,否则技能环上永远是个空环 */
|
|
983
|
+
if (ce.skill) {
|
|
984
|
+
var sp = ce.skill, si = cps.indexOf(sp);
|
|
985
|
+
var hg = X.createRadialGradient(sp.x, sp.y, 0, sp.x, sp.y, 15);
|
|
986
|
+
hg.addColorStop(0, 'rgba(150,200,255,.42)');
|
|
987
|
+
hg.addColorStop(1, 'rgba(150,200,255,0)');
|
|
988
|
+
X.fillStyle = hg; X.beginPath(); X.arc(sp.x, sp.y, 15, 0, TAU); X.fill();
|
|
989
|
+
X.strokeStyle = 'rgba(160,205,255,.9)'; X.lineWidth = 1.8;
|
|
990
|
+
X.beginPath(); X.arc(sp.x, sp.y, 3.6, 0, TAU); X.stroke();
|
|
991
|
+
X.strokeStyle = 'rgba(150,200,255,.6)'; X.lineWidth = 2;
|
|
992
|
+
X.beginPath();
|
|
993
|
+
if (si > 0) { X.moveTo(cps[si - 1].x, cps[si - 1].y); X.lineTo(sp.x, sp.y); }
|
|
994
|
+
if (si >= 0 && si < cps.length - 1) { X.moveTo(sp.x, sp.y); X.lineTo(cps[si + 1].x, cps[si + 1].y); }
|
|
995
|
+
X.stroke();
|
|
996
|
+
}
|
|
982
997
|
/* 移动光点:按路径长度插值 */
|
|
983
998
|
var lens = [], tot = 0;
|
|
984
999
|
for (var cl = 1; cl < cps.length; cl++) {
|
|
@@ -1130,6 +1145,32 @@ html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--ink);
|
|
|
1130
1145
|
});
|
|
1131
1146
|
}
|
|
1132
1147
|
}
|
|
1148
|
+
/* 技能是被调用的稀疏事件:给它一条自己的连线,别指望"某一步用了 skill"那条阶梯 */
|
|
1149
|
+
if (M.skills.length) {
|
|
1150
|
+
var nowSk = clockNow();
|
|
1151
|
+
var spanSk = RSPAN[1] || (M.sSteps && M.sSteps.length
|
|
1152
|
+
? Math.max(120000, nowSk - M.sSteps[0].t) : 120000);
|
|
1153
|
+
for (i = 0; i < M.skills.length && i < 16; i++) {
|
|
1154
|
+
var skI = M.skills[i];
|
|
1155
|
+
var aSk = rotOf(2, t) - Math.PI / 2 + TAU * i / 16;
|
|
1156
|
+
var px = ox + Math.cos(aSk) * rr, py = oy + Math.sin(aSk) * rr;
|
|
1157
|
+
var age = nowSk - (skI.last || 0);
|
|
1158
|
+
if (age >= 0 && age <= spanSk) {
|
|
1159
|
+
var aTo = -Math.PI / 2 + (age / spanSk) * TAU + rotOf(1, t);
|
|
1160
|
+
var qx = ox + Math.cos(aTo) * R[1], qy = oy + Math.sin(aTo) * R[1];
|
|
1161
|
+
X.strokeStyle = rgba(COL.skill, 0.8); X.lineWidth = 2.6;
|
|
1162
|
+
X.beginPath(); X.moveTo(px, py); X.lineTo(qx, qy); X.stroke();
|
|
1163
|
+
X.beginPath(); X.arc(qx, qy, 3, 0, TAU);
|
|
1164
|
+
X.fillStyle = rgba(COL.skill, 0.95); X.fill();
|
|
1165
|
+
}
|
|
1166
|
+
var hgS = X.createRadialGradient(px, py, 0, px, py, 19);
|
|
1167
|
+
hgS.addColorStop(0, rgba(COL.skill, 0.75));
|
|
1168
|
+
hgS.addColorStop(1, rgba(COL.skill, 0));
|
|
1169
|
+
X.fillStyle = hgS; X.beginPath(); X.arc(px, py, 19, 0, TAU); X.fill();
|
|
1170
|
+
X.beginPath(); X.arc(px, py, 4.2, 0, TAU);
|
|
1171
|
+
X.fillStyle = rgba(COL.skill, 0.95); X.fill();
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1133
1174
|
}
|
|
1134
1175
|
|
|
1135
1176
|
/* ④ 工具环:不看时间,看「构成」
|
|
@@ -1404,15 +1445,20 @@ html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--ink);
|
|
|
1404
1445
|
return x5.turn === s5.turn && x5.step === s5.step;
|
|
1405
1446
|
});
|
|
1406
1447
|
var usedSkill = stepTools.some(function (x5) { return x5.name === 'skill'; });
|
|
1448
|
+
var skillPt = null;
|
|
1449
|
+
if (usedSkill) {
|
|
1450
|
+
/* ③ 技能环只落一个点,再向 ④ 扇开(原来每个工具都插一个 ③ 点,会来回跳) */
|
|
1451
|
+
var aK0 = (SKILL_ANG == null)
|
|
1452
|
+
? (segMap['skill'] != null ? segMap['skill'] : -Math.PI / 2)
|
|
1453
|
+
: SKILL_ANG;
|
|
1454
|
+
skillPt = { x: ox + Math.cos(aK0) * R[2], y: oy + Math.sin(aK0) * R[2] };
|
|
1455
|
+
path.push(skillPt);
|
|
1456
|
+
}
|
|
1407
1457
|
var used = 0;
|
|
1408
1458
|
for (var tj = 0; tj < stepTools.length && used < 12; tj++) {
|
|
1409
1459
|
var tl5 = stepTools[tj];
|
|
1410
1460
|
var aT5 = segMap[tl5.name];
|
|
1411
1461
|
if (aT5 == null) continue;
|
|
1412
|
-
if (usedSkill) {
|
|
1413
|
-
var aK = (SKILL_ANG == null) ? aT5 : SKILL_ANG;
|
|
1414
|
-
path.push({ x: ox + Math.cos(aK) * R[2], y: oy + Math.sin(aK) * R[2] });
|
|
1415
|
-
}
|
|
1416
1462
|
path.push({ x: ox + Math.cos(aT5) * R[3], y: oy + Math.sin(aT5) * R[3] });
|
|
1417
1463
|
used++;
|
|
1418
1464
|
}
|
|
@@ -1443,7 +1489,7 @@ html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--ink);
|
|
|
1443
1489
|
/* 这里只攒几何,真正的绘制在下一帧开头(画环之前)完成 —— 线在环下面 */
|
|
1444
1490
|
if (path.length > 1) {
|
|
1445
1491
|
CHAIN_PREV.push({
|
|
1446
|
-
pts: path, bright: bright, fr: fr5,
|
|
1492
|
+
pts: path, bright: bright, fr: fr5, skill: skillPt,
|
|
1447
1493
|
phase: (i / Math.max(1, arr3.length)) * 0.8
|
|
1448
1494
|
});
|
|
1449
1495
|
}
|