dsh-visualization 0.0.0-stage → 0.1.1
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/LICENSE +76 -0
- package/README.md +262 -2
- package/build.cjs +20 -0
- package/client.js +96 -0
- package/cordis.patch.yml +3 -0
- package/fold.cjs +835 -0
- package/icon.svg +16 -0
- package/index.js +30 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +58 -4
- package/web/index.html +1887 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Required Notice: Copyright (c) 2026 Ryder191 (RYDER CODE / Ms MAIKEBAIs CAT)
|
|
2
|
+
Required Notice: Visual language and layout derived from HALOFLUX — https://github.com/Ryder191/haloflux
|
|
3
|
+
|
|
4
|
+
# PolyForm Noncommercial License 1.0.0
|
|
5
|
+
|
|
6
|
+
<https://polyformproject.org/licenses/noncommercial/1.0.0>
|
|
7
|
+
|
|
8
|
+
## Acceptance
|
|
9
|
+
|
|
10
|
+
In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
|
|
11
|
+
|
|
12
|
+
## Copyright License
|
|
13
|
+
|
|
14
|
+
The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license).
|
|
15
|
+
|
|
16
|
+
## Distribution License
|
|
17
|
+
|
|
18
|
+
The licensor grants you an additional copyright license to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license).
|
|
19
|
+
|
|
20
|
+
## Notices
|
|
21
|
+
|
|
22
|
+
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example:
|
|
23
|
+
|
|
24
|
+
> Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
|
|
25
|
+
|
|
26
|
+
## Changes and New Works License
|
|
27
|
+
|
|
28
|
+
The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.
|
|
29
|
+
|
|
30
|
+
## Patent License
|
|
31
|
+
|
|
32
|
+
The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
|
|
33
|
+
|
|
34
|
+
## Noncommercial Purposes
|
|
35
|
+
|
|
36
|
+
Any noncommercial purpose is a permitted purpose.
|
|
37
|
+
|
|
38
|
+
## Personal Uses
|
|
39
|
+
|
|
40
|
+
Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, is use for a permitted purpose.
|
|
41
|
+
|
|
42
|
+
## Noncommercial Organizations
|
|
43
|
+
|
|
44
|
+
Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution is use for a permitted purpose regardless of the source of funding or obligations resulting from the funding.
|
|
45
|
+
|
|
46
|
+
## Fair Use
|
|
47
|
+
|
|
48
|
+
You may have "fair use" rights for the software under the law. These terms do not limit them.
|
|
49
|
+
|
|
50
|
+
## No Other Rights
|
|
51
|
+
|
|
52
|
+
These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
|
|
53
|
+
|
|
54
|
+
## Patent Defense
|
|
55
|
+
|
|
56
|
+
If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
|
|
57
|
+
|
|
58
|
+
## Violations
|
|
59
|
+
|
|
60
|
+
The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
|
|
61
|
+
|
|
62
|
+
## No Liability
|
|
63
|
+
|
|
64
|
+
***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
|
|
65
|
+
|
|
66
|
+
## Definitions
|
|
67
|
+
|
|
68
|
+
The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
|
|
69
|
+
|
|
70
|
+
**You** refers to the individual or entity agreeing to these terms.
|
|
71
|
+
|
|
72
|
+
**Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
|
|
73
|
+
|
|
74
|
+
**Your licenses** are all the licenses granted to you for the software under these terms.
|
|
75
|
+
|
|
76
|
+
**Use** means anything you do with the software requiring one of your licenses.
|
package/README.md
CHANGED
|
@@ -1,3 +1,263 @@
|
|
|
1
|
-
#
|
|
1
|
+
# DSH Visualization — session instrument for DeepSeek Harness
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A layered instrument for a live DSH session: a sidebar icon, a full panel showing the session's
|
|
4
|
+
rings, turns, tools and background jobs.
|
|
5
|
+
|
|
6
|
+
**English** (below) · **[中文说明](#中文)** at the bottom.
|
|
7
|
+
|
|
8
|
+
> The standalone version (open `standalone/start.cmd`, no DSH plugin needed) lives in [`standalone/`](standalone/).
|
|
9
|
+
|
|
10
|
+
- **Seven rings**: ① session · ② steps · ③ skills · ④ tools · ⑤ jobs · ⑥ approvals · ⑦ changes — each one toggleable
|
|
11
|
+
- **Turn timeline**: two lines per turn — number + steps/tools/duration, then the prompt you typed
|
|
12
|
+
- **Connections**: per-step ladders (①→②→③→④) with dots running along them, or beams from the core
|
|
13
|
+
- **Hover / click**: every node on every ring answers — hover for its name, click for details (tools list the last six actual commands)
|
|
14
|
+
- **Turn scoping**: `show turns` / `line turns` knobs narrow the view to the last N turns
|
|
15
|
+
- Every display option is remembered in `localStorage`
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# For users
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
- **Node ≥ 22.15** in the host: session logs are zstd-compressed. Without support the panel says so
|
|
24
|
+
explicitly instead of rendering blank.
|
|
25
|
+
- Session directory defaults to `~/.dsh/sessions`; if DSH stores data elsewhere, set `DSH_HOME`
|
|
26
|
+
or `DSHVIZ_SESSIONS_ROOT`.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```powershell
|
|
31
|
+
# local directory (development)
|
|
32
|
+
# plugin_manager install_bundle, target = this directory
|
|
33
|
+
# from npm (after publishing)
|
|
34
|
+
# search "dsh-visualization" on the Plugins page
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
A ring icon appears in the left rail; click it to open the instrument.
|
|
38
|
+
|
|
39
|
+
## Reading the panel
|
|
40
|
+
|
|
41
|
+
| Area | What it tells you |
|
|
42
|
+
|---|---|
|
|
43
|
+
| Centre number | current turn; `TOOL` / `THINKING` / `IDLE` above it |
|
|
44
|
+
| ① session ring | context-window usage + the last 12 turn ticks |
|
|
45
|
+
| ② step ring | time buckets; thicker = more time spent in that slice |
|
|
46
|
+
| ③ skill ring | 16 slots; one lights up per loaded skill |
|
|
47
|
+
| ④ tool ring | composition by call count (or by time — see `share`) |
|
|
48
|
+
| ⑤ job ring | one arc per background job, length ∝ runtime |
|
|
49
|
+
| ⑥ approval ring | one tick per approval, colour = outcome |
|
|
50
|
+
| ⑦ change ring | one tick per file change; deliverables are taller |
|
|
51
|
+
| Right column | session flow, context, jobs, turn timeline, tool totals |
|
|
52
|
+
| Bottom | event log, grouped by turn |
|
|
53
|
+
|
|
54
|
+
Direction matters: on ②–⑦ the newest data sits at 12 o'clock and older data goes clockwise.
|
|
55
|
+
|
|
56
|
+
## The controls (bottom left)
|
|
57
|
+
|
|
58
|
+
| Control | Options |
|
|
59
|
+
|---|---|
|
|
60
|
+
| rings `①…⑦` | show/hide each layer; radii re-spread to fill the space |
|
|
61
|
+
| `spin` | slow rotation of ②③④ |
|
|
62
|
+
| `lines` | `ladders` / `beams` / `both` |
|
|
63
|
+
| `share` | tool ring sized by `count` or by `time` |
|
|
64
|
+
| `line turns` | how many turns get connectors (`2` by default) |
|
|
65
|
+
| `show turns` | how many turns every panel covers |
|
|
66
|
+
| `⧉ window` | open a separate window you can drag to another monitor |
|
|
67
|
+
| `中 / EN` | switch language |
|
|
68
|
+
|
|
69
|
+
`line turns` can never exceed `show turns` — the connector count is clamped automatically.
|
|
70
|
+
|
|
71
|
+
## Language
|
|
72
|
+
|
|
73
|
+
The UI ships in Chinese and English. It starts from `navigator.language`, remembers your choice,
|
|
74
|
+
and can be forced per page with `?lang=en` / `?lang=zh`.
|
|
75
|
+
|
|
76
|
+
## When something looks wrong
|
|
77
|
+
|
|
78
|
+
| Symptom | Cause |
|
|
79
|
+
|---|---|
|
|
80
|
+
| Panel says "this runtime does not support zstd" | Node < 22.15 in the host |
|
|
81
|
+
| Panel says the session directory is empty | no session has run in this profile yet |
|
|
82
|
+
| Panel is empty with 403 in the network tab | you are not on loopback; set `allowRemote: true` (only on trusted machines) |
|
|
83
|
+
| A step ring segment is dim and has no line | that step is outside `line turns` — raise it, or lower `show turns` |
|
|
84
|
+
| A bright segment has no line | that step made no tool calls (a text-only step) |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
# For plugin developers and agents
|
|
89
|
+
|
|
90
|
+
## Files
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
index.js host entry: registers the /<basePath> route, injects origin + policy
|
|
94
|
+
fold.cjs the whole host brain: session discovery, zstd frames, incremental fold, HTTP/SSE routes
|
|
95
|
+
client.js client half: sidebar icon + a main-panel iframe pointing at the page
|
|
96
|
+
web/index.html the instrument page (GENERATED — do not edit by hand)
|
|
97
|
+
build.cjs regenerates web/index.html from ../instrument.html
|
|
98
|
+
cordis.patch.yml / locale/*.json / icon.svg metadata
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Editing rules (the part that bites)
|
|
102
|
+
|
|
103
|
+
1. **The page has a single source: `instrument.html`** in the repository root. Edit that, then run
|
|
104
|
+
`node build.cjs`. The build only rewrites absolute endpoints (`/state` → `state`) so the page works
|
|
105
|
+
under a path prefix. Editing `web/index.html` directly will be overwritten.
|
|
106
|
+
2. **Host changes need a DSH restart.** `fold.cjs` and `index.js` are ESM modules; toggling the plugin
|
|
107
|
+
only remounts the route — the loaded module is not re-evaluated. `client.js` and `web/*` pick up on
|
|
108
|
+
a page refresh.
|
|
109
|
+
3. **Debug globals are gated**: `window.__M` / `__HIT` / `__CHAIN` only exist with `?debug=1`
|
|
110
|
+
(or `#debug`, or `localStorage.dshviz.debug=1`).
|
|
111
|
+
4. **Never rewrite the page with a one-line shell command.** It has been destroyed that way once.
|
|
112
|
+
Use edits, or author a small `.cjs` patch script and run it.
|
|
113
|
+
|
|
114
|
+
## Data flow
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
~/.dsh/sessions/<ws>/<session>/session.v<N>.jsonl.zstd
|
|
118
|
+
└─ zstdFrames() parse RFC 8878 headers+blocks → exact frame boundaries
|
|
119
|
+
└─ unzstdAll() decode only the frames appended since last read
|
|
120
|
+
└─ makeFolder() reducer: push(newEvents) → result() (never re-scans history)
|
|
121
|
+
└─ /dsh-visualization/state (one-shot) /dsh-visualization/events (SSE, pushes only on change)
|
|
122
|
+
└─ page: applyState() → render() (DOM) + draw() (canvas)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Cost at ~8.7k events: first fold 240–260 ms, then 7–12 ms per change. Frame parsing is exact, so a
|
|
126
|
+
partially-written tail frame simply stops the walk and is picked up next time.
|
|
127
|
+
|
|
128
|
+
## Adding things
|
|
129
|
+
|
|
130
|
+
**A new ring** — three places, all inside `instrument.html`:
|
|
131
|
+
`RINGVIS` / `RING_NAME` / `RING_COL` (state + labels), the `if (RINGVIS[n])` drawing block in `draw()`,
|
|
132
|
+
and `hitReg({ kind, r, a0, a1, label, data })` so hover/click works. Add a detail renderer branch in
|
|
133
|
+
`showDetail()` if the card needs fields.
|
|
134
|
+
|
|
135
|
+
**A new language** — `EN` map (whole-string matches), `EN_RULES` (templated phrases), `EN_FN`
|
|
136
|
+
(replacements that need logic). `LANG` currently accepts `zh` | `en`; to add a third, turn it into a
|
|
137
|
+
list and key the dictionaries by code.
|
|
138
|
+
|
|
139
|
+
**A new tool kind colour** — `toolColor(name)` in `instrument.html`.
|
|
140
|
+
|
|
141
|
+
## Config
|
|
142
|
+
|
|
143
|
+
| Field | Default | Meaning |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| `basePath` | `/dsh-visualization` | route prefix |
|
|
146
|
+
| `allowRemote` | `false` | allow non-loopback peers. **This exposes session logs to anyone who can reach the port.** |
|
|
147
|
+
|
|
148
|
+
## Routes
|
|
149
|
+
|
|
150
|
+
| Path | Purpose |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `/dsh-visualization/` | the page |
|
|
153
|
+
| `/dsh-visualization/state` | folded state, one-shot (`?session=<id>` to pin a session) |
|
|
154
|
+
| `/dsh-visualization/events` | same payload over SSE; pushed only when something changed (250 ms check) |
|
|
155
|
+
| `/dsh-visualization/sessions` | session list |
|
|
156
|
+
| `/dsh-visualization/select?session=` | switch the pinned session |
|
|
157
|
+
| `/dsh-visualization/calls?tool=&limit=` | recent calls of one tool, fetched on demand |
|
|
158
|
+
| `/dsh-visualization/replay?action=` | replay a session's history |
|
|
159
|
+
|
|
160
|
+
Limits: loopback-only by default, gzip on state, SSE capped at 8 concurrent clients.
|
|
161
|
+
|
|
162
|
+
## Performance notes
|
|
163
|
+
|
|
164
|
+
| Stage | Before | Now |
|
|
165
|
+
|---|---|---|
|
|
166
|
+
| Transport | 566 KB every 900 ms | 51 KB gzip, only on change |
|
|
167
|
+
| Decompression | whole log each time | only appended zstd frames |
|
|
168
|
+
| Fold | every event each time | only new events |
|
|
169
|
+
| Idle traffic | continuous polling | zero (15 s heartbeat only) |
|
|
170
|
+
|
|
171
|
+
## Known limitations
|
|
172
|
+
|
|
173
|
+
- **Memory grows with the session**: the reducer keeps derived state for every event. Steps/tools/log are
|
|
174
|
+
capped; counters are cumulative. At ~100k events `result()` still sorts 1.2k steps and 1.5k tools,
|
|
175
|
+
roughly 10–20 ms per frame.
|
|
176
|
+
- **Two UI languages only** (zh/en).
|
|
177
|
+
- **Read-only**: the instrument never writes session files.
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
**[PolyForm Noncommercial License 1.0.0](./LICENSE)** — Copyright (c) 2026 Ryder191 (RYDER CODE / Ms MAIKEBAIs CAT).
|
|
182
|
+
|
|
183
|
+
- **Noncommercial only.** Personal use, study, research, teaching and hobby projects are free.
|
|
184
|
+
Selling it, bundling it into a paid product, using it inside a company, or paid support is **not** permitted.
|
|
185
|
+
- **This is a derivative of HALOFLUX** (visual language, layout, rings, palette —
|
|
186
|
+
https://github.com/Ryder191/haloflux). Per HALOFLUX's terms, derivatives must stay noncommercial and keep
|
|
187
|
+
the attribution.
|
|
188
|
+
- **Keep the notices.** If you redistribute, pass along this LICENSE and its two `Required Notice:` lines.
|
|
189
|
+
- The page's bottom-right credit line (`DSH Visualization by Ryder191 · PolyForm NC · visual language: HALOFLUX`)
|
|
190
|
+
must stay.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
# 中文
|
|
195
|
+
|
|
196
|
+
把 DeepSeek Harness 的会话画成一台**分层仪表**:侧栏点一下,中间面板显示当前会话的环、轮次、工具和后台任务。
|
|
197
|
+
|
|
198
|
+
## 使用说明
|
|
199
|
+
|
|
200
|
+
**环境要求**:宿主 Node ≥ 22.15(会话日志是 zstd)。会话目录默认 `~/.dsh/sessions`,
|
|
201
|
+
DSH 装在别处时用环境变量 `DSH_HOME` 或 `DSHVIZ_SESSIONS_ROOT` 指过去。
|
|
202
|
+
|
|
203
|
+
**安装**:本地目录用 `plugin_manager install_bundle` 指向本目录;发布后可在插件页搜索 `dsh-visualization`。
|
|
204
|
+
安装后左侧栏出现环图标,点开即是仪表盘。
|
|
205
|
+
|
|
206
|
+
**怎么读**:中心数字是当前轮次,上方是状态(`TOOL`/`THINKING`/`IDLE`)。① 会话环是上下文占用 + 最近
|
|
207
|
+
12 轮刻度;② 步骤环按时间分格,格子越粗表示那段时间花得越久;③ 技能环 16 格,加载一个亮一个;
|
|
208
|
+
④ 工具环按调用次数(或耗时)分扇区;⑤ 任务环每个后台任务一段弧,弧长 = 运行时长;⑥ 审批环每次审批一根
|
|
209
|
+
刻度;⑦ 变更环每次文件改动一根刻度。**12 点方向是最新的,越旧越顺时针**。
|
|
210
|
+
|
|
211
|
+
**控件**(左下角):七个环开关、环旋转、连线模式(阶梯/光束/两者)、占比口径、连线轮数、显示轮数、
|
|
212
|
+
`⧉ 新窗口`(拖到副屏)、`中 / EN`。`连线轮数` 不会超过 `显示轮数`,超了会自动钳制。
|
|
213
|
+
|
|
214
|
+
**语言**:默认跟随 `navigator.language`,选了就记住;也可以 `?lang=en` / `?lang=zh` 临时指定。
|
|
215
|
+
|
|
216
|
+
**常见现象**:
|
|
217
|
+
|
|
218
|
+
| 现象 | 原因 |
|
|
219
|
+
|---|---|
|
|
220
|
+
| 面板说"不支持 zstd" | 宿主 Node < 22.15 |
|
|
221
|
+
| 面板说会话目录是空的 | 这个 profile 还没跑过对话 |
|
|
222
|
+
| 面板空白、网络面板里 403 | 不是本机访问;需要远程看才把 `allowRemote` 打开 |
|
|
223
|
+
| 某个格子暗、没有线 | 它不在「连线轮数」范围内 |
|
|
224
|
+
| 格子亮、没有线 | 这一步没有工具调用(纯文字步) |
|
|
225
|
+
|
|
226
|
+
## 给 agent / 开发者的编辑说明
|
|
227
|
+
|
|
228
|
+
**文件**:`index.js`(宿主入口,注册路由 + 注入 origin/策略)、`fold.cjs`(会话发现、zstd 帧解析、
|
|
229
|
+
增量折叠、HTTP/SSE 路由)、`client.js`(侧栏图标 + 面板 iframe)、`web/index.html`(**生成物**)、
|
|
230
|
+
`build.cjs`(从主仓库 `instrument.html` 重新生成)。
|
|
231
|
+
|
|
232
|
+
**四条规矩**:
|
|
233
|
+
|
|
234
|
+
1. **页面只有一个源**:改 `../instrument.html`,再跑 `node build.cjs`。直接改 `web/index.html` 会被覆盖。
|
|
235
|
+
2. **改宿主必须重启 DSH**:`fold.cjs` / `index.js` 是 ESM,插件开关只会重挂路由,不会重新求值。
|
|
236
|
+
`client.js` 和 `web/*` 刷新页面即可。
|
|
237
|
+
3. **调试全局是门控的**:`window.__M` / `__HIT` / `__CHAIN` 只在 `?debug=1` 时存在。
|
|
238
|
+
4. **绝不用单行命令整篇覆盖页面**——它被这样毁过一次。用编辑工具,或写一个 `.cjs` 补丁脚本再执行。
|
|
239
|
+
|
|
240
|
+
**数据流**:`张日志 → zstdFrames()(按 RFC 8878 精确切帧)→ 只解新帧 → makeFolder().push(新事件) →
|
|
241
|
+
/dsh-visualization/state(一次性)或 /dsh-visualization/events(SSE,变化才推)→ applyState() → render() + draw()`。
|
|
242
|
+
|
|
243
|
+
**8.7k 事件的开销**:首次 240~260ms,之后每次变化 7~12ms。
|
|
244
|
+
|
|
245
|
+
**加东西**:新环 = `RINGVIS`/`RING_NAME`/`RING_COL` + `draw()` 里的绘制块 + `hitReg(...)` 登记 +
|
|
246
|
+
`showDetail()` 的详情分支;新语言 = `EN` 词典 + `EN_RULES` 正则 + `EN_FN` 逻辑替换,并把 `LANG`
|
|
247
|
+
从 `'zh'|'en'` 改成列表;新工具配色 = `toolColor()`。
|
|
248
|
+
|
|
249
|
+
**配置**:`basePath`(默认 `/dsh-visualization`)、`allowRemote`(默认 `false`,打开等于把会话日志暴露给能访问该端口的人)。
|
|
250
|
+
|
|
251
|
+
**已知限制**:内存随会话线性增长(十万条时每帧 `result()` 约 10~20ms);界面只有中英两门语言;
|
|
252
|
+
仪表只读,从不写会话文件。
|
|
253
|
+
|
|
254
|
+
## 许可证
|
|
255
|
+
|
|
256
|
+
**[PolyForm Noncommercial License 1.0.0](./LICENSE)** —— Copyright (c) 2026 Ryder191(RYDER CODE / Ms MAIKEBAIs CAT)。
|
|
257
|
+
|
|
258
|
+
- **仅限非商业用途**:个人学习、兴趣项目、研究、教学可自由使用、修改、分享。
|
|
259
|
+
**禁止**:出售、集成进付费产品、在公司内部或代表公司使用、接单或有偿项目。
|
|
260
|
+
- **本项目是 HALOFLUX 的衍生作品**(视觉语言、布局、环与配色均来自
|
|
261
|
+
https://github.com/Ryder191/haloflux)。按 HALOFLUX 的条款,衍生作品必须同为非商业并保留署名。
|
|
262
|
+
- **保留声明**:转发时附带本 LICENSE 与其中的两行 `Required Notice:`。
|
|
263
|
+
- 页面右下角署名(`DSH Visualization by Ryder191 · PolyForm NC · visual language: HALOFLUX`)不得删除。
|
package/build.cjs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// 把工作区的 instrument.html 生成为插件里的页面(单一源,避免两份漂移)
|
|
2
|
+
// 用法:node dsh-visualization/build.cjs
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
|
|
6
|
+
const src = path.join(__dirname, 'standalone', 'instrument.html')
|
|
7
|
+
const out = path.join(__dirname, 'web', 'index.html')
|
|
8
|
+
|
|
9
|
+
if (!fs.existsSync(src)) {
|
|
10
|
+
console.log('(包内不含 instrument.html —— 这是仓库里的构建脚本,安装后的包不需要跑它)')
|
|
11
|
+
process.exit(0)
|
|
12
|
+
}
|
|
13
|
+
let h = fs.readFileSync(src, 'utf8')
|
|
14
|
+
/* 插件挂在 /dsh-visualization 前缀下,接口要改成相对路径 */
|
|
15
|
+
for (const ep of ['sessions', 'state', 'savehtml']) h = h.split("'/" + ep + "'").join("'" + ep + "'")
|
|
16
|
+
for (const ep of ['replay', 'calls', 'select']) h = h.split("'/" + ep + "?").join("'" + ep + "?")
|
|
17
|
+
|
|
18
|
+
fs.mkdirSync(path.dirname(out), { recursive: true })
|
|
19
|
+
fs.writeFileSync(out, h, 'utf8')
|
|
20
|
+
console.log('build → ' + path.relative(process.cwd(), out) + ' ' + h.length + ' bytes')
|
package/client.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// DSH VISUALIZATION client half:侧栏一个环图标,点开在主面板里显示会话仪表盘。
|
|
2
|
+
// 面板用 iframe 承载 /dsh-visualization/ 页面 —— 页面本身就是完整的仪表盘,
|
|
3
|
+
// 这样绘制、交互、表格都只有一份实现,插件这边只负责"挂上去"。
|
|
4
|
+
window.__ModuleLoader__.load({
|
|
5
|
+
id: 'dsh-visualization',
|
|
6
|
+
factory: (require) => {
|
|
7
|
+
var module = { exports: {} }
|
|
8
|
+
var exports = module.exports
|
|
9
|
+
const React = require('react')
|
|
10
|
+
const h = React.createElement
|
|
11
|
+
|
|
12
|
+
const URL = '/dsh-visualization/'
|
|
13
|
+
|
|
14
|
+
/* 宿主页面地址里通常带着当前会话 id(uuid);带着就让面板跟随它。
|
|
15
|
+
读不到就退回"自动跟最新会话"。 */
|
|
16
|
+
function sessionFromUrl() {
|
|
17
|
+
try {
|
|
18
|
+
const m = String(location.href).match(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i)
|
|
19
|
+
return m ? m[1] : ''
|
|
20
|
+
} catch (e) { return '' }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* 侧栏图标:三层同心环 + 中心点,和仪表盘的环一致 */
|
|
24
|
+
function Icon(props) {
|
|
25
|
+
const size = (props && props.size) || 20
|
|
26
|
+
const active = !!(props && props.active)
|
|
27
|
+
return h('svg', {
|
|
28
|
+
viewBox: '0 0 32 32', width: size, height: size, 'aria-hidden': true,
|
|
29
|
+
style: { display: 'block', opacity: active ? 1 : 0.72 },
|
|
30
|
+
},
|
|
31
|
+
h('circle', { cx: 16, cy: 16, r: 13.2, fill: 'none', stroke: 'currentColor', strokeWidth: 1.4, opacity: 0.35 }),
|
|
32
|
+
h('circle', { cx: 16, cy: 16, r: 8.4, fill: 'none', stroke: 'currentColor', strokeWidth: 1.6, opacity: 0.6 }),
|
|
33
|
+
h('circle', { cx: 16, cy: 16, r: 4, fill: 'none', stroke: 'currentColor', strokeWidth: 1.8 }),
|
|
34
|
+
h('circle', { cx: 16, cy: 16, r: 1.7, fill: 'currentColor' }))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* 主面板:整块 iframe,仪表盘是独立页面,交互全部在里面 */
|
|
38
|
+
function Panel() {
|
|
39
|
+
const [sid, setSid] = React.useState(sessionFromUrl())
|
|
40
|
+
/* 宿主切会话时地址会变,但 pushState 不触发 popstate —— 轻量轮询兜住 */
|
|
41
|
+
React.useEffect(() => {
|
|
42
|
+
const id = setInterval(() => {
|
|
43
|
+
const next = sessionFromUrl()
|
|
44
|
+
setSid((cur) => (cur === next ? cur : next))
|
|
45
|
+
}, 1000)
|
|
46
|
+
return () => clearInterval(id)
|
|
47
|
+
}, [])
|
|
48
|
+
const src = sid ? URL + '?session=' + encodeURIComponent(sid) : URL
|
|
49
|
+
return h('div', {
|
|
50
|
+
style: {
|
|
51
|
+
position: 'relative', display: 'flex', flexDirection: 'column',
|
|
52
|
+
width: '100%', height: '100%', minHeight: 0, background: '#0b0d11',
|
|
53
|
+
},
|
|
54
|
+
}, h('iframe', {
|
|
55
|
+
src: src,
|
|
56
|
+
title: 'DSH VISUALIZATION',
|
|
57
|
+
style: { flex: 1, width: '100%', minHeight: 0, border: 0, display: 'block', background: '#0b0d11' },
|
|
58
|
+
}))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function waitSlots(ctx) {
|
|
62
|
+
for (let i = 0; i < 80; i++) {
|
|
63
|
+
try {
|
|
64
|
+
const sv = ctx.get('slots')
|
|
65
|
+
if (sv) return sv
|
|
66
|
+
} catch (e) { /* 还没挂上,继续等 */ }
|
|
67
|
+
await new Promise((r) => setTimeout(r, 100))
|
|
68
|
+
}
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function apply(ctx) {
|
|
73
|
+
try {
|
|
74
|
+
const slots = await waitSlots(ctx)
|
|
75
|
+
if (!slots) {
|
|
76
|
+
console.warn('[dsh-visualization] slots 服务等不到,放弃注册')
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
slots.inject('sidebar.panellist', () => slots.register({
|
|
80
|
+
name: 'sidebar.panellist', id: 'dsh-visualization', order: 30,
|
|
81
|
+
label: () => 'DSH VISUALIZATION',
|
|
82
|
+
}, Icon))
|
|
83
|
+
slots.inject('main', () => slots.register({
|
|
84
|
+
name: 'main', key: 'dsh-visualization',
|
|
85
|
+
}, Panel))
|
|
86
|
+
} catch (e) {
|
|
87
|
+
console.error('[dsh-visualization] apply 失败:', e)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
exports.apply = apply
|
|
92
|
+
exports.inject = []
|
|
93
|
+
exports.name = 'dsh-visualization'
|
|
94
|
+
return module.exports
|
|
95
|
+
},
|
|
96
|
+
})
|
package/cordis.patch.yml
ADDED