dsh-smooth-stream 0.4.2 → 0.4.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/README.en.md +88 -92
- package/README.md +85 -90
- package/lib/client.js +52 -52
- package/lib/index.js +2 -2
- package/package.json +1 -1
- package/src/plugin.ts +10 -2
package/README.en.md
CHANGED
|
@@ -1,144 +1,140 @@
|
|
|
1
|
-
# dsh-smooth-stream
|
|
1
|
+
# dsh-smooth-stream
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> **Transform jumpy AI outputs into a calm, teleprompter-smooth reading experience.**
|
|
4
|
+
> Physics-based stream rendering and zero-reflow viewport tracking for the DeepSeek Harness (`dsh`) Web UI.
|
|
4
5
|
|
|
5
|
-
English
|
|
6
|
+
English · [中文](README.md) · [Homepage](https://laplace-bit.github.io/dsh-smooth-stream/) · [How It Works](https://laplace-bit.github.io/dsh-smooth-stream/how-it-works.html) · [npm](https://www.npmjs.com/package/dsh-smooth-stream)
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
---
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
## The Silky Smooth Feel
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
Whether reviewing hundreds of lines of complex reasoning or watching fast-paced code generation, `dsh-smooth-stream` delivers a **calm, continuous, and fatigue-free** reading experience:
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
- **Organic, fluid text expansion**: No more walls of text abruptly snapping onto your screen. Words glide in with an organic rhythm that feels alive yet unhurried.
|
|
15
|
+
- **Effortless eye tracking**: The viewport glides as if on a precision-damped rail. Line wraps and code blocks no longer jar your eyes, eliminating cognitive friction.
|
|
16
|
+
- **Adaptive cadence**: Leisurely during slow arrivals, smoothly accelerating during high-volume bursts—keeping your screen composed no matter how fast tokens arrive.
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
- **0-jank reveal and wrap.** Reveal speed adapts to queue pressure — measured for slow output, catching up with fast output — and any single-frame visual displacement from a wrap or new block is capped at **≤8px** (`FOLLOW_PAINT_SHIFT_MAX_STEP_PX`). Even high-speed output never jumps.
|
|
17
|
-
- **0-burst completion.** When a turn finishes, the queue drains at one fixed velocity: the ending does not "dump", it does not grab an extra frame, and body / reasoning / tool output hand off cleanly.
|
|
18
|
+
---
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
## Why this exists
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
Large language models emit tokens in discrete network bursts: hundreds of characters can arrive within milliseconds, followed by tens of milliseconds of silence.
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
Traditional chat UIs bind DOM rendering and scrolling directly to arrival events, causing two jarring failure modes:
|
|
25
|
+
1. **Visual snapping**: Text blocks, tables, and code snippets pop in abruptly, forcing the reader's eye to constantly re-acquire focus.
|
|
26
|
+
2. **Scroll jitter and reflow storms**: Hard `scrollTop = scrollHeight` jumps or interrupted `scroll-behavior: smooth` animations restart their easing curves on every chunk, leading to sluggish lag and severe layout thrashing.
|
|
24
27
|
|
|
25
|
-
-
|
|
26
|
-
- The **follow engine** holds the current height and the rendered position in a damped spring (**stiffness/damping/mass ≈ 130/24/1**) integrated every frame; wraps, code blocks, tables, and tool results all feed one continuous trajectory.
|
|
27
|
-
- After a long main-thread stall, physical time is clamped instead of replaying the whole missed interval in a single paint — so the settle time is nearly identical on 60 Hz and 120 Hz: **refresh-rate-independent feel**.
|
|
28
|
+
`dsh-smooth-stream` decouples **text reveal cadence** from **viewport motion** into two independent dynamical systems, integrating them per animation frame (`requestAnimationFrame`) to ensure uninterrupted continuity.
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Architecture & Mechanics
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
[ Model SSE Stream ]
|
|
36
|
+
│
|
|
37
|
+
▼
|
|
38
|
+
┌─────────────────┐ Backpressure Damping (0.55x ~ 1.0x) ┌─────────────────┐
|
|
39
|
+
│ Reveal Engine │ ◄────────────────────────────────────────────── │ Follow Engine │
|
|
40
|
+
└────────┬────────┘ └────────┬────────┘
|
|
41
|
+
│ Fractional character debt integration │ 2nd-order damped spring (k=130, c=24)
|
|
42
|
+
▼ ▼
|
|
43
|
+
[ Progressive DOM Reveal ] ────────────────────────────────────────► [ GPU Compositor Transform ]
|
|
44
|
+
(Per-frame visual delta ≤ 8px) (Zero Reflow / Pure Composite)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 1. Dynamic Adaptive Reveal Engine
|
|
48
|
+
- **Fractional character debt**: Evaluates reveal velocity from current backlog ($v = 90 + \text{backlog}^{1.25} \times P$). Leisurely when arrival is slow; accelerates smoothly during bursts without ever dumping text walls.
|
|
49
|
+
- **Wrap smoothing**: Caps per-frame visual displacement to $\le 8\text{px}$ during line wraps and new block arrivals, spreading sudden $24\text{--}28\text{px}$ layout steps across several frames.
|
|
50
|
+
- **Uniform completion drain**: When the generation finishes, the residual queue drains at a steady speed, creating clean transitions between body, reasoning, and tool calls.
|
|
51
|
+
|
|
52
|
+
### 2. GPU-Driven Damped Spring Follower
|
|
53
|
+
- **Second-order spring physics**: Uses a sub-stepped damped spring ($k=130, c=24, m=1$) to convert discrete height changes into a continuous trajectory.
|
|
54
|
+
- **Zero-reflow viewport tracking**: Keeps the real scrollport pinned to the bottom while absorbing residual visual lag entirely via `transform: translate3d` on the message container. No layout-triggering properties are touched during follow.
|
|
55
|
+
- **Closed-loop backpressure**: If visual lag fills the predictive runway ($\approx 72\text{px}$), the follower throttles reveal speed (down to $0.55\times$), ensuring text expansion never outpaces the viewport spring.
|
|
56
|
+
- **Stall resilience & ProMotion parity**: Clamps elapsed physical time ($\Delta t \le 32\text{ms}$) during main-thread stalls to prevent teleporting catches. Settling dynamics are identical across 60Hz and 120Hz (ProMotion) displays.
|
|
57
|
+
|
|
58
|
+
### 3. Turn Lifecycle Auto-Collapse
|
|
59
|
+
- Reasoning and tool executions remain expanded while streaming.
|
|
60
|
+
- Once a turn settles, intermediate processes cleanly fold behind a minimalist `Processed in Xs` summary row, keeping the conversation view focused on final answers.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Visual Comparison
|
|
30
65
|
|
|
31
66
|
Left: default Web UI. Right: dsh-smooth-stream.
|
|
32
67
|
|
|
33
|
-

|
|
34
69
|
|
|
35
|
-
|
|
70
|
+
---
|
|
36
71
|
|
|
37
|
-
|
|
38
|
-
- **Silky scrolling.** As the content grows, the page follows one continuous scroll path and keeps the reader close to the generated output.
|
|
39
|
-
- **Consistent transitions.** Line wraps, code blocks, tables, and tool results use the same motion treatment, so text, reasoning, and tools flow together.
|
|
40
|
-
- **Adaptive cadence.** Reveal speed responds to arrival rate and pending content, staying measured for slow output and catching up with fast output.
|
|
72
|
+
## Performance & Test Benchmarks
|
|
41
73
|
|
|
42
|
-
|
|
74
|
+
Verified by local browser-level audit suites:
|
|
43
75
|
|
|
44
|
-
|
|
76
|
+
| Gate | Command | Passing Standard |
|
|
77
|
+
| :--- | :--- | :--- |
|
|
78
|
+
| **Stream Render Audit** | `node scripts/run-render-audit.mjs` | 10/10 clean across 5 streaming patterns; zero regressions |
|
|
79
|
+
| **Overflow & Rebound Gate** | `node scripts/verify-overflow.mjs` | Zero over-scroll, zero bounce under burst load |
|
|
80
|
+
| **Tail Vibration Probe** | `pnpm test` | Single-frame shift $\le 30\text{px}$, 7-frame amplitude $\le 32\text{px}$ |
|
|
45
81
|
|
|
46
|
-
|
|
47
|
-
| --- | --- |
|
|
48
|
-
| Render audit `run-render-audit` (5 streaming scenarios) | **10/10 clean**, zero movement regression |
|
|
49
|
-
| Overflow gate `verify-overflow` | **3/3** no over-scroll, no rebound |
|
|
50
|
-
| Tail smoothing `probe-tailbob` | single-frame ≤30px, 7-frame amplitude ≤32px **PASS** |
|
|
51
|
-
| Inline-code wrap `probe:overlap` | Issue #13 fixture across static/reveal/engine arms **PASS** |
|
|
82
|
+
- Core ESM bundle is approximately **4.7 kB** (gzipped). See [How It Works](https://laplace-bit.github.io/dsh-smooth-stream/how-it-works.html) for full benchmarks.
|
|
52
83
|
|
|
53
|
-
|
|
84
|
+
---
|
|
54
85
|
|
|
55
|
-
##
|
|
86
|
+
## Quick Start
|
|
56
87
|
|
|
57
|
-
|
|
88
|
+
### Installation
|
|
89
|
+
|
|
90
|
+
Inside your DeepSeek Harness repository checkout:
|
|
58
91
|
|
|
59
92
|
```sh
|
|
60
93
|
pnpm dsh plugin --profile web add dsh-smooth-stream
|
|
61
94
|
```
|
|
62
95
|
|
|
63
|
-
If `dsh` is
|
|
96
|
+
If `dsh` is in your system `PATH`:
|
|
64
97
|
|
|
65
98
|
```sh
|
|
66
99
|
dsh plugin --profile web add dsh-smooth-stream
|
|
67
100
|
```
|
|
68
101
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
Start the UI:
|
|
102
|
+
Start the interface:
|
|
72
103
|
|
|
73
104
|
```sh
|
|
74
105
|
pnpm dsh web
|
|
75
106
|
```
|
|
76
107
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
Remove it with `pnpm dsh plugin --profile web remove dsh-smooth-stream` (or `dsh plugin --profile web remove dsh-smooth-stream`).
|
|
80
|
-
|
|
81
|
-
## Configuration
|
|
82
|
-
|
|
83
|
-
The bundle installs with `preset: balanced`. Change it in the profile `cordis.patch.yml` if you want a different cadence:
|
|
108
|
+
Verify that `[dsh-smooth-stream] plugin loaded!` appears in the host startup logs.
|
|
84
109
|
|
|
85
|
-
|
|
86
|
-
| --- | --- |
|
|
87
|
-
| `realtime` | Keeps closer to the model |
|
|
88
|
-
| `balanced` | Default |
|
|
89
|
-
| `silky` | More buffer, slower catch-up |
|
|
110
|
+
To uninstall: `pnpm dsh plugin --profile web remove dsh-smooth-stream`.
|
|
90
111
|
|
|
91
|
-
|
|
112
|
+
---
|
|
92
113
|
|
|
93
|
-
##
|
|
114
|
+
## Presets & Configuration
|
|
94
115
|
|
|
95
|
-
|
|
116
|
+
The plugin defaults to `preset: balanced`. You can tune the cadence in your profile's `cordis.patch.yml`:
|
|
96
117
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
118
|
+
| `preset` | Characteristics |
|
|
119
|
+
| :--- | :--- |
|
|
120
|
+
| `realtime` | Low buffer; closely follows model token arrival |
|
|
121
|
+
| `balanced` | Recommended default; balances smoothness and latency |
|
|
122
|
+
| `silky` | Generous buffer with gentler acceleration curves |
|
|
101
123
|
|
|
102
|
-
|
|
124
|
+
---
|
|
103
125
|
|
|
104
|
-
|
|
126
|
+
## User Preferences
|
|
105
127
|
|
|
106
|
-
|
|
128
|
+
Open **Settings → Plugins → Plugin Configuration** in the Web UI:
|
|
107
129
|
|
|
108
|
-
|
|
130
|
+
- **Enable smooth streaming** (default on): Toggles custom stream rendering and follow. Disabling instantly falls back to built-in Harness rendering.
|
|
131
|
+
- **Auto-expand thinking**: Controls whether reasoning opens automatically while streaming.
|
|
132
|
+
- **Collapse finished work** (default on): Folds thoughts and tool steps into a summary line once the turn settles.
|
|
133
|
+
- **Show render diagnostics** (default off): Opens a live HUD on the right side to inspect FPS, character backlog, spring state, and adjust physical parameters in real time.
|
|
109
134
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
- **Version / homepage / license**: see the top of this page and the `version`, `homepage`, `repository`, and `license` fields in [package.json](package.json). Installed plugins are listed under **Settings → Plugins → All**.
|
|
113
|
-
- **Updates**: the card shows the version loaded by the Host. When the active profile declares `dsh-smooth-stream` as an npm dependency, its **Update** button runs the same fixed package update for that profile and then asks you to restart Harness. A `link:` or `file:` development install is shown as a development version and deliberately leaves the button disabled, so it cannot replace your checkout.
|
|
114
|
-
|
|
115
|
-
You can also update an npm-installed profile from the command line:
|
|
116
|
-
|
|
117
|
-
```sh
|
|
118
|
-
dsh plugin --profile web update dsh-smooth-stream
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
(`dsh plugin --profile web outdated` shows whether a newer version exists.)
|
|
122
|
-
|
|
123
|
-
## FAQ
|
|
124
|
-
|
|
125
|
-
**Is this an official DeepSeek plugin?**
|
|
126
|
-
No. It is independently maintained, MIT-licensed software for the DeepSeek Harness (`dsh`) Web UI and is not affiliated with DeepSeek.
|
|
127
|
-
|
|
128
|
-
**Is "0 reflow" literal?**
|
|
129
|
-
It refers to the animation path. The follow writes only a compositor `transform` and never reads or writes layout properties, so the follow phase causes no reflow/repaint storm; text reveal still writes character increments (the only layout that new content genuinely needs), but reveal cadence is capped so single-frame visual displacement stays ≤8px with no jumps.
|
|
130
|
-
|
|
131
|
-
**How do I install a DeepSeek Harness plugin?**
|
|
132
|
-
Use the built-in plugin command: `dsh plugin --profile web add dsh-smooth-stream` from a dsh source checkout (see [Install](#install)).
|
|
133
|
-
|
|
134
|
-
**Can I install it from npm?**
|
|
135
|
-
Yes — `dsh-smooth-stream` is published to [npm](https://www.npmjs.com/package/dsh-smooth-stream). `dsh plugin --profile web add dsh-smooth-stream` installs the prebuilt package.
|
|
136
|
-
|
|
137
|
-
**Does it respect `prefers-reduced-motion`?**
|
|
138
|
-
Yes. With reduced motion enabled the finished text is shown at once and the plugin does not take over follow. If the frame rate drops below 30 fps while the reply is off-screen, reveal pauses and catches up later.
|
|
139
|
-
|
|
140
|
-
[](https://whyihaveyou.github.io/dsh-suite/)
|
|
135
|
+
---
|
|
141
136
|
|
|
142
137
|
## License
|
|
143
138
|
|
|
144
139
|
[MIT](LICENSE)
|
|
140
|
+
|
package/README.md
CHANGED
|
@@ -1,144 +1,139 @@
|
|
|
1
|
-
# dsh-smooth-stream
|
|
1
|
+
# dsh-smooth-stream
|
|
2
2
|
|
|
3
|
-
> AI
|
|
3
|
+
> **让 AI 的长篇生成如提词器般温润流淌。**
|
|
4
|
+
> 为 DeepSeek Harness(`dsh`)打造的二阶弹簧物理流式渲染与零重排跟随引擎。
|
|
4
5
|
|
|
5
|
-
[English](README.en.md)
|
|
6
|
+
[English](README.en.md) · [项目主页](https://laplace-bit.github.io/dsh-smooth-stream/) · [工作原理与基准](https://laplace-bit.github.io/dsh-smooth-stream/how-it-works.html) · [npm](https://www.npmjs.com/package/dsh-smooth-stream)
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
---
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
## 极致丝滑的阅读手感
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
无论是阅读数百行的长篇推演,还是紧盯高速吐字的代码生成,`dsh-smooth-stream` 带来的是一种**沉浸、连贯且零视觉负担**的阅读质感:
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
- **如水流般自然铺展**:告别大段文本突然“砸”在屏幕上的视觉压迫,字句如打字机般富有呼吸感地逐字涌现;
|
|
15
|
+
- **视线无需追赶跳动**:视口如同搭载了高精度阻尼滑轨,随文字增长平稳匀速推移,彻底终结换行时的突发踢移;
|
|
16
|
+
- **呼吸感与实时性的平衡**:慢速输出时从容优雅,高并发爆发时平稳追赶,无论模型吐字多快,画面始终从容自若。
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
- **0 跳变的揭示与换行。** 揭示速度按队列积压自适应,慢速输出从容呈现、快速输出及时追上;换行和新块的单帧视觉位移被限幅到 ≤8px(`FOLLOW_PAINT_SHIFT_MAX_STEP_PX`),高速输出同样无跳帧。
|
|
17
|
-
- **0 突进的完成收敛。** 回合结束时以固定速度匀速倾倒队列,结尾「不倾泻」、不多抓一帧,正文与思考/工具输出衔接干净。
|
|
18
|
+
---
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
## 为什么需要它?
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
大模型输出是通过网络分块到达的。一个数据包可能在几毫秒内送达数百字符,下一个分块却需要数十毫秒。
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
如果直接将 DOM 渲染和视口滚动绑定在离散的到达事件上,通常会引发两个阅读体验问题:
|
|
25
|
+
1. **视觉跳跃与撕裂**:段落、代码块和表格整段突发呈现,视线被迫频繁重新寻焦;
|
|
26
|
+
2. **滚动抖动与重排风暴**:依靠 `scrollTop = scrollHeight` 进行瞬时硬跳;即使用 CSS `scroll-behavior: smooth`,高频写入也会不断重置缓动曲线,导致动画永远无法收敛,高速流下伴随剧烈的主线程重排。
|
|
24
27
|
|
|
25
|
-
-
|
|
26
|
-
- **跟随引擎**用一个阻尼弹簧(stiffness/damping/mass ≈ **130/24/1**)持有当前高度与现实位移,逐帧积分;换行、代码块、表格、工具结果全都喂进同一条连续轨迹。
|
|
27
|
-
- 主线程卡顿后物理时间被 clamp,而不是把错过的一整段间隔在一帧里补完——所以 60Hz 与 120Hz 下收束时间几乎一致,**帧率无关的手感**。
|
|
28
|
+
`dsh-smooth-stream` 将**内容呈现**与**视口运动**拆分为两个独立的物理状态机,在每一帧动画回调(rAF)中连续积分,彻底消除跳跃感。
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 核心机理与架构
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
[ 模型 SSE 分块流 ]
|
|
36
|
+
│
|
|
37
|
+
▼
|
|
38
|
+
┌─────────────────┐ 背压阻尼 (0.55x ~ 1.0x) ┌─────────────────┐
|
|
39
|
+
│ 揭示节奏引擎 │ ◄──────────────────────────────── │ 弹簧跟随引擎 │
|
|
40
|
+
│ (Reveal Engine) │ │ (Follow Engine) │
|
|
41
|
+
└────────┬────────┘ └────────┬────────┘
|
|
42
|
+
│ 字符积压与分数积分 │ 二阶阻尼弹簧积分 (k=130, c=24)
|
|
43
|
+
▼ ▼
|
|
44
|
+
[ 逐帧平滑展开 DOM ] ───────────────────────────────► [ 合成层 Transform 补偿 ]
|
|
45
|
+
(单帧视觉位移 ≤ 8px) (0 Reflow / 纯 GPU 合成)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 1. 动态自适应的揭示引擎(Reveal Engine)
|
|
49
|
+
- **分数级字符积分**:根据积压队列深度自适应调节速度($v = 90 + \text{backlog}^{1.25} \times P$),低负载时从容自然,高积压时平稳追赶,绝不倾泻整段文本。
|
|
50
|
+
- **折行限幅平滑**:长文本和代码块发生换行时,单帧视觉位移被限制在 8px 以内,将原本 24~28px 的单帧跳跃平摊至数帧完成。
|
|
51
|
+
- **尾部平稳收束**:回合结束标记到达后,残余缓冲区以恒定速率释放,正文、思考链与工具调用之间自然衔接。
|
|
52
|
+
|
|
53
|
+
### 2. 纯合成层驱动的跟随引擎(Follow Engine)
|
|
54
|
+
- **二阶阻尼物理系统**:采用 $k=130, c=24, m=1$ 的亚步进物理弹簧,将内容高度的变化转化为平滑连续的速度与位移轨迹。
|
|
55
|
+
- **零重排(Zero-Reflow)位移补偿**:真实滚动容器始终锚定在底部,剩余的视觉滞后完全由外层 DOM 的 `transform: translate3d` 吸收。跟随过程不读写任何触发 Layout 的属性,杜绝重排风暴。
|
|
56
|
+
- **闭环背压控制(Closed-Loop Backpressure)**:当跟随滞后接近预留空间时,反向向揭示引擎施加阻尼(最低降至 0.55 倍速),防止文本增长超出视口弹簧范围。
|
|
57
|
+
- **掉帧自愈与跨刷新率一致**:主线程卡顿(Stall)时自动钳位物理时间($\le 32\text{ms}$),避免画面恢复后的突进瞬移;在 60Hz 与 120Hz(ProMotion)屏幕下拥有近乎一致的物理收敛时间。
|
|
58
|
+
|
|
59
|
+
### 3. 会话生命周期自动折叠(Turn Auto-Collapse)
|
|
60
|
+
- 对话进行时,思考链与工具调用保持实时展开;
|
|
61
|
+
- 回合结算完成并稳定后,自动将执行过程收敛为一行极简的 `已处理 X 秒` 摘要,保持工作区专注;支持随时点击无缝展开。
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 效果对比
|
|
30
66
|
|
|
31
67
|
左:默认 Web UI。右:dsh-smooth-stream。
|
|
32
68
|
|
|
33
69
|

|
|
34
70
|
|
|
35
|
-
|
|
71
|
+
---
|
|
36
72
|
|
|
37
|
-
|
|
38
|
-
- **丝滑滚动。** 内容逐步变高时,页面沿连续的滚动轨迹平稳跟随,视线始终贴着正在生成的内容。
|
|
39
|
-
- **统一过渡。** 换行、代码块、表格和工具结果使用一致的过渡方式,正文、思考过程与工具输出衔接自然。
|
|
40
|
-
- **自适应节奏。** 渲染速度会根据输出速度和待显示内容调整,让慢速输出从容呈现,快速输出及时跟上。
|
|
73
|
+
## 性能与基准
|
|
41
74
|
|
|
42
|
-
|
|
75
|
+
平滑度与稳定性基于本地自动化审计台架验证:
|
|
43
76
|
|
|
44
|
-
|
|
77
|
+
| 验证闸门 | 测试项目 | 指标要求 |
|
|
78
|
+
| :--- | :--- | :--- |
|
|
79
|
+
| **流式渲染审计** | `node scripts/run-render-audit.mjs` | 5 种典型生成场景下 10/10 Clean,零位移回退 |
|
|
80
|
+
| **视口溢出闸门** | `node scripts/verify-overflow.mjs` | 极限输出场景下零过滚、零反弹 |
|
|
81
|
+
| **尾行晃动抑制** | `pnpm test` | 单帧跳变 ≤30px,7 帧振幅峰值 ≤32px |
|
|
45
82
|
|
|
46
|
-
|
|
47
|
-
| --- | --- |
|
|
48
|
-
| 渲染审计 `run-render-audit`(5 种流式场景) | **10/10 clean**,零位移突变、零回归 |
|
|
49
|
-
| 溢出闸门 `verify-overflow` | **3/3** 无过滚、无回弹 |
|
|
50
|
-
| 尾行平滑 `probe-tailbob` | 单帧位移 ≤30px、7 帧振幅 ≤32px **PASS** |
|
|
51
|
-
| 行内代码折行 `probe:overlap` | Issue #13 fixture,静态/揭示/引擎三臂 **PASS** |
|
|
83
|
+
- 核心 ESM 产物经 Gzip 压缩后仅约 **4.7 kB**。详见[工作原理与基准](https://laplace-bit.github.io/dsh-smooth-stream/how-it-works.html)。
|
|
52
84
|
|
|
53
|
-
|
|
85
|
+
---
|
|
54
86
|
|
|
55
|
-
##
|
|
87
|
+
## 安装与使用
|
|
56
88
|
|
|
57
|
-
在 DeepSeek Harness
|
|
89
|
+
在 DeepSeek Harness 源码根目录运行:
|
|
58
90
|
|
|
59
91
|
```sh
|
|
60
92
|
pnpm dsh plugin --profile web add dsh-smooth-stream
|
|
61
93
|
```
|
|
62
94
|
|
|
63
|
-
|
|
95
|
+
如果系统 `PATH` 中已有 `dsh`:
|
|
64
96
|
|
|
65
97
|
```sh
|
|
66
98
|
dsh plugin --profile web add dsh-smooth-stream
|
|
67
99
|
```
|
|
68
100
|
|
|
69
|
-
npm 包带预构建的 `lib/`,无需 pnpm ≥10 的构建脚本授权,直接可装。
|
|
70
|
-
|
|
71
101
|
启动界面:
|
|
72
102
|
|
|
73
103
|
```sh
|
|
74
104
|
pnpm dsh web
|
|
75
105
|
```
|
|
76
106
|
|
|
77
|
-
Host
|
|
78
|
-
|
|
79
|
-
卸载:`pnpm dsh plugin --profile web remove dsh-smooth-stream`(或 `dsh plugin --profile web remove dsh-smooth-stream`)。
|
|
80
|
-
|
|
81
|
-
## 配置
|
|
82
|
-
|
|
83
|
-
组合包默认 `preset: balanced`。要换节拍,在 profile 的 `cordis.patch.yml` 里改:
|
|
84
|
-
|
|
85
|
-
| `preset` | 手感 |
|
|
86
|
-
| --- | --- |
|
|
87
|
-
| `realtime` | 更贴模型到达 |
|
|
88
|
-
| `balanced` | 默认 |
|
|
89
|
-
| `silky` | 缓冲更大,追上更慢 |
|
|
107
|
+
Host 日志中显示 `[dsh-smooth-stream] plugin loaded!` 即表示已成功加载。
|
|
90
108
|
|
|
91
|
-
|
|
109
|
+
卸载命令:`pnpm dsh plugin --profile web remove dsh-smooth-stream`。
|
|
92
110
|
|
|
93
|
-
|
|
111
|
+
---
|
|
94
112
|
|
|
95
|
-
|
|
113
|
+
## 配置与手感预设
|
|
96
114
|
|
|
97
|
-
|
|
98
|
-
- **自动展开思考**:控制思考块在流式期间是否自动展开。主开关关闭时此选项不会生效。
|
|
99
|
-
- **完成后自动折叠**(默认开启):回合处理完成后,把思考过程、工具调用、上下文注入和中间输出折叠为一行「已处理 X秒」摘要,只展示最终回复;点击摘要可随时展开或收起完整工作过程。
|
|
100
|
-
- **显示渲染调试面板**(默认关闭):在聊天页右侧显示实时渲染、帧率和滚动跟随数据,并开放流式揭示与滚动弹簧参数。
|
|
115
|
+
插件默认采用 `preset: balanced`。如需切换手感,可在对应 profile 的 `cordis.patch.yml` 中修改:
|
|
101
116
|
|
|
102
|
-
|
|
117
|
+
| `preset` | 动态特性 |
|
|
118
|
+
| :--- | :--- |
|
|
119
|
+
| `realtime` | 紧跟模型分块到达节奏,缓冲更小 |
|
|
120
|
+
| `balanced` | 默认推荐,兼顾阅读流畅度与响应延迟 |
|
|
121
|
+
| `silky` | 增大缓冲区,追赶更平缓柔和 |
|
|
103
122
|
|
|
104
|
-
|
|
123
|
+
---
|
|
105
124
|
|
|
106
|
-
|
|
125
|
+
## 用户偏好设置
|
|
107
126
|
|
|
108
|
-
|
|
127
|
+
在 Web 界面打开 **设置 → 插件 → 插件配置**,可对 **丝滑流式(Smooth stream)** 卡片进行个性化调节:
|
|
109
128
|
|
|
110
|
-
|
|
129
|
+
- **启用丝滑流式渲染**(默认开启):接管回复和工具行的渲染与跟随;关闭后即时恢复 Harness 内置渲染。
|
|
130
|
+
- **自动展开思考**:流式生成期间是否自动展开思考过程。
|
|
131
|
+
- **完成后自动折叠**(默认开启):回合处理完成后,将思考过程与工具调用折叠为摘要行。
|
|
132
|
+
- **显示渲染调试面板**(默认关闭):在界面右侧开启实时 HUD,观测 FPS、字符积压、弹性曲线并微调物理参数。
|
|
111
133
|
|
|
112
|
-
|
|
113
|
-
- **更新**:卡片会显示 Host 当前加载的版本。只有当前 profile 明确把 `dsh-smooth-stream` 声明为 npm 依赖时,**更新**按钮才会对该 profile 执行固定的包更新,并提示重启 Harness。`link:` 或 `file:` 本地开发安装会显示为开发版本,更新按钮会保持禁用,避免覆盖你的源码目录。
|
|
114
|
-
|
|
115
|
-
也可以通过命令行更新 npm 安装的 profile:
|
|
116
|
-
|
|
117
|
-
```sh
|
|
118
|
-
dsh plugin --profile web update dsh-smooth-stream
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
(也可用 `dsh plugin --profile web outdated` 查看是否有新版本。)
|
|
122
|
-
|
|
123
|
-
## 常见问题
|
|
124
|
-
|
|
125
|
-
**这是 DeepSeek 官方插件吗?**
|
|
126
|
-
不是。它是面向 DeepSeek Harness(`dsh`)Web UI 的独立维护项目,采用 MIT 许可证,和 DeepSeek 没有从属关系。
|
|
127
|
-
|
|
128
|
-
**「0 重排」是字面意思吗?**
|
|
129
|
-
指动画路径本身。滚动跟随只写合成层 `transform`,不读写布局属性,因此跟随阶段不会引发重排/重绘风暴;文本揭示仍按字符增量写入(这是产生新内容的唯一必要布局),但揭示节奏被限幅,单帧视觉位移 ≤8px,避免跳帧。
|
|
130
|
-
|
|
131
|
-
**dsh 插件怎么安装?**
|
|
132
|
-
用内置插件命令:在 dsh 源码目录运行 `dsh plugin --profile web add dsh-smooth-stream`(见[安装](#安装))。
|
|
133
|
-
|
|
134
|
-
**能用 npm 安装吗?**
|
|
135
|
-
能。`dsh-smooth-stream` 已发布到 [npm](https://www.npmjs.com/package/dsh-smooth-stream),`dsh plugin --profile web add dsh-smooth-stream` 安装的就是预构建的 npm 包。
|
|
136
|
-
|
|
137
|
-
**支持 `prefers-reduced-motion` 吗?**
|
|
138
|
-
支持。系统开启减少动态效果时直接显示完整文本、不接管跟随;帧率低于 30 fps 且回复在屏外时,揭示自动暂停、恢复后再补上。
|
|
139
|
-
|
|
140
|
-
[](https://whyihaveyou.github.io/dsh-suite/)
|
|
134
|
+
---
|
|
141
135
|
|
|
142
136
|
## 许可证
|
|
143
137
|
|
|
144
138
|
[MIT](LICENSE)
|
|
139
|
+
|
package/lib/client.js
CHANGED
|
@@ -23,27 +23,27 @@ window.__ModuleLoader__.load({
|
|
|
23
23
|
tag.textContent = css$2;
|
|
24
24
|
}
|
|
25
25
|
var TypewriterAssistantNodeView_module_css_default = {
|
|
26
|
-
"
|
|
27
|
-
"
|
|
26
|
+
"thinkTitle": "I17U7q_thinkTitle",
|
|
27
|
+
"thinkLeading": "I17U7q_thinkLeading",
|
|
28
|
+
"thinkSeparator": "I17U7q_thinkSeparator",
|
|
29
|
+
"disclosureRoot": "I17U7q_disclosureRoot",
|
|
30
|
+
"dsh-smooth-stream-think-sweep": "I17U7q_dsh-smooth-stream-think-sweep",
|
|
28
31
|
"root": "I17U7q_root",
|
|
29
|
-
"
|
|
30
|
-
"disclosureChevronHover": "I17U7q_disclosureChevronHover",
|
|
32
|
+
"disclosureTitle": "I17U7q_disclosureTitle",
|
|
31
33
|
"visuallyHidden": "I17U7q_visuallyHidden",
|
|
32
|
-
"
|
|
33
|
-
"disclosureLeading": "I17U7q_disclosureLeading",
|
|
34
|
-
"thinkRow": "I17U7q_thinkRow",
|
|
34
|
+
"follow": "I17U7q_follow",
|
|
35
35
|
"disclosureContent": "I17U7q_disclosureContent",
|
|
36
|
-
"disclosureRoot": "I17U7q_disclosureRoot",
|
|
37
|
-
"thinkTitle": "I17U7q_thinkTitle",
|
|
38
|
-
"think": "I17U7q_think",
|
|
39
36
|
"stopped": "I17U7q_stopped",
|
|
40
|
-
"
|
|
41
|
-
"body": "I17U7q_body",
|
|
37
|
+
"thinkSummary": "I17U7q_thinkSummary",
|
|
42
38
|
"disclosureRow": "I17U7q_disclosureRow",
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
39
|
+
"disclosureIconIdle": "I17U7q_disclosureIconIdle",
|
|
40
|
+
"thinkRow": "I17U7q_thinkRow",
|
|
41
|
+
"thinkChevron": "I17U7q_thinkChevron",
|
|
42
|
+
"think": "I17U7q_think",
|
|
43
|
+
"body": "I17U7q_body",
|
|
44
|
+
"disclosureLeading": "I17U7q_disclosureLeading",
|
|
45
|
+
"disclosureChevronHover": "I17U7q_disclosureChevronHover",
|
|
46
|
+
"thinkBody": "I17U7q_thinkBody"
|
|
47
47
|
};
|
|
48
48
|
//#endregion
|
|
49
49
|
//#region src/client/AnimatedDisclosure.tsx
|
|
@@ -3899,32 +3899,32 @@ window.__ModuleLoader__.load({
|
|
|
3899
3899
|
tag.textContent = css$1;
|
|
3900
3900
|
}
|
|
3901
3901
|
var SmoothStreamCard_module_css_default = {
|
|
3902
|
+
"fieldHead": "XNGeRG_fieldHead",
|
|
3903
|
+
"label": "XNGeRG_label",
|
|
3904
|
+
"toggle": "XNGeRG_toggle",
|
|
3905
|
+
"cardOpen": "XNGeRG_cardOpen",
|
|
3906
|
+
"chevron": "XNGeRG_chevron",
|
|
3907
|
+
"header": "XNGeRG_header",
|
|
3908
|
+
"field": "XNGeRG_field",
|
|
3902
3909
|
"failure": "XNGeRG_failure",
|
|
3910
|
+
"discard": "XNGeRG_discard",
|
|
3911
|
+
"headText": "XNGeRG_headText",
|
|
3903
3912
|
"updateRow": "XNGeRG_updateRow",
|
|
3904
3913
|
"footer": "XNGeRG_footer",
|
|
3905
|
-
"update": "XNGeRG_update",
|
|
3906
|
-
"cardOpen": "XNGeRG_cardOpen",
|
|
3907
3914
|
"fieldDisabled": "XNGeRG_fieldDisabled",
|
|
3908
|
-
"header": "XNGeRG_header",
|
|
3909
|
-
"chevronOpen": "XNGeRG_chevronOpen",
|
|
3910
|
-
"field": "XNGeRG_field",
|
|
3911
|
-
"version": "XNGeRG_version",
|
|
3912
3915
|
"pending": "XNGeRG_pending",
|
|
3913
|
-
"
|
|
3916
|
+
"body": "XNGeRG_body",
|
|
3917
|
+
"save": "XNGeRG_save",
|
|
3918
|
+
"update": "XNGeRG_update",
|
|
3919
|
+
"chevronOpen": "XNGeRG_chevronOpen",
|
|
3914
3920
|
"hint": "XNGeRG_hint",
|
|
3915
3921
|
"failed": "XNGeRG_failed",
|
|
3916
|
-
"description": "XNGeRG_description",
|
|
3917
|
-
"label": "XNGeRG_label",
|
|
3918
|
-
"fieldHead": "XNGeRG_fieldHead",
|
|
3919
3922
|
"updateCopy": "XNGeRG_updateCopy",
|
|
3920
|
-
"
|
|
3921
|
-
"discard": "XNGeRG_discard",
|
|
3922
|
-
"body": "XNGeRG_body",
|
|
3923
|
-
"name": "XNGeRG_name",
|
|
3923
|
+
"description": "XNGeRG_description",
|
|
3924
3924
|
"card": "XNGeRG_card",
|
|
3925
|
+
"version": "XNGeRG_version",
|
|
3925
3926
|
"readOnly": "XNGeRG_readOnly",
|
|
3926
|
-
"
|
|
3927
|
-
"toggle": "XNGeRG_toggle"
|
|
3927
|
+
"name": "XNGeRG_name"
|
|
3928
3928
|
};
|
|
3929
3929
|
//#endregion
|
|
3930
3930
|
//#region src/client/SmoothStreamCard.tsx
|
|
@@ -4425,34 +4425,34 @@ window.__ModuleLoader__.load({
|
|
|
4425
4425
|
tag.textContent = css;
|
|
4426
4426
|
}
|
|
4427
4427
|
var DebugPanel_module_css_default = {
|
|
4428
|
-
"section": "m4_tdG_section",
|
|
4429
|
-
"panelHeader": "m4_tdG_panelHeader",
|
|
4430
|
-
"statusDot": "m4_tdG_statusDot",
|
|
4431
|
-
"iconButton": "m4_tdG_iconButton",
|
|
4432
|
-
"unsaved": "m4_tdG_unsaved",
|
|
4433
|
-
"infoButton": "m4_tdG_infoButton",
|
|
4434
4428
|
"triggerActive": "m4_tdG_triggerActive",
|
|
4435
|
-
"
|
|
4436
|
-
"primaryButton": "m4_tdG_primaryButton",
|
|
4437
|
-
"statusLive": "m4_tdG_statusLive",
|
|
4429
|
+
"guide": "m4_tdG_guide",
|
|
4438
4430
|
"metric": "m4_tdG_metric",
|
|
4439
|
-
"
|
|
4440
|
-
"
|
|
4431
|
+
"range": "m4_tdG_range",
|
|
4432
|
+
"iconButton": "m4_tdG_iconButton",
|
|
4433
|
+
"unsaved": "m4_tdG_unsaved",
|
|
4434
|
+
"section": "m4_tdG_section",
|
|
4435
|
+
"metrics": "m4_tdG_metrics",
|
|
4441
4436
|
"panel": "m4_tdG_panel",
|
|
4437
|
+
"unit": "m4_tdG_unit",
|
|
4438
|
+
"trigger": "m4_tdG_trigger",
|
|
4439
|
+
"state": "m4_tdG_state",
|
|
4440
|
+
"secondaryButton": "m4_tdG_secondaryButton",
|
|
4441
|
+
"title": "m4_tdG_title",
|
|
4442
4442
|
"controlHead": "m4_tdG_controlHead",
|
|
4443
|
-
"guide": "m4_tdG_guide",
|
|
4444
4443
|
"controlLabel": "m4_tdG_controlLabel",
|
|
4445
4444
|
"numberWrap": "m4_tdG_numberWrap",
|
|
4445
|
+
"primaryButton": "m4_tdG_primaryButton",
|
|
4446
|
+
"panelHeader": "m4_tdG_panelHeader",
|
|
4447
|
+
"statusLive": "m4_tdG_statusLive",
|
|
4448
|
+
"scrollArea": "m4_tdG_scrollArea",
|
|
4449
|
+
"footerSpacer": "m4_tdG_footerSpacer",
|
|
4446
4450
|
"visuallyHidden": "m4_tdG_visuallyHidden",
|
|
4451
|
+
"statusDot": "m4_tdG_statusDot",
|
|
4452
|
+
"infoButton": "m4_tdG_infoButton",
|
|
4447
4453
|
"footer": "m4_tdG_footer",
|
|
4448
|
-
"
|
|
4449
|
-
"
|
|
4450
|
-
"metrics": "m4_tdG_metrics",
|
|
4451
|
-
"unit": "m4_tdG_unit",
|
|
4452
|
-
"footerSpacer": "m4_tdG_footerSpacer",
|
|
4453
|
-
"range": "m4_tdG_range",
|
|
4454
|
-
"state": "m4_tdG_state",
|
|
4455
|
-
"scrollArea": "m4_tdG_scrollArea"
|
|
4454
|
+
"number": "m4_tdG_number",
|
|
4455
|
+
"control": "m4_tdG_control"
|
|
4456
4456
|
};
|
|
4457
4457
|
//#endregion
|
|
4458
4458
|
//#region src/client/DebugPanel.tsx
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
2
1
|
import Schema from "@deepseek-ai/schemastery";
|
|
3
2
|
import { readFileSync } from "node:fs";
|
|
4
3
|
import { basename, dirname, isAbsolute, join } from "node:path";
|
|
@@ -229,7 +228,8 @@ function apply(ctx, config) {
|
|
|
229
228
|
httpCtx.effect(() => httpCtx.webServer.tapIndex((html) => injectStreamConfig(html, config)), "dsh-smooth-stream: boot config bridge");
|
|
230
229
|
});
|
|
231
230
|
ctx.inject(["settings"], (settingsCtx) => {
|
|
232
|
-
const
|
|
231
|
+
const settingsNamespace = STREAM_SETTINGS_NS;
|
|
232
|
+
const scope = settingsCtx.settings.register(settingsNamespace, StreamSettingsSchema, { applies: "live" });
|
|
233
233
|
settingsCtx.inject(["connection"], (connectionCtx) => {
|
|
234
234
|
let upgrade;
|
|
235
235
|
const view = () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-smooth-stream",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Fluid streaming rendering and silky scrolling for DeepSeek Harness replies, including Markdown, code blocks, tables, and tool results.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/plugin.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis'
|
|
2
2
|
import type {} from '@deepseek-ai/dsh-host-webserver'
|
|
3
3
|
import type { ConnectionRpcHandler } from '@deepseek-ai/dsh-client-connection'
|
|
4
|
-
|
|
4
|
+
// Type-only: erased at runtime, so the host entry never link-fails on kernels
|
|
5
|
+
// whose dsh-settings no longer ships the value-side helper (issue #17).
|
|
6
|
+
import type { SettingsNamespace } from '@deepseek-ai/dsh-settings'
|
|
5
7
|
import Schema from '@deepseek-ai/schemastery'
|
|
6
8
|
import { DEFAULT_STREAM_CONFIG, type StreamConfig } from './config.ts'
|
|
7
9
|
import { injectStreamConfig } from './boot-config.ts'
|
|
@@ -93,8 +95,14 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
93
95
|
// the durable provider as the authority, but expose this one schema through
|
|
94
96
|
// the plugin's own loopback-only connection channel instead.
|
|
95
97
|
ctx.inject(['settings'], (settingsCtx) => {
|
|
98
|
+
// 0.1.2 kernels dropped the `settingsNamespace()` helper — a validating
|
|
99
|
+
// identity on ≤ 0.1.1 — and take the raw string, so the rc-era brand is
|
|
100
|
+
// reproduced locally instead of statically importing a removed symbol.
|
|
101
|
+
// The namespace is a compile-time constant matching the kernel's
|
|
102
|
+
// /^[a-z][a-z0-9-]*$/ pattern.
|
|
103
|
+
const settingsNamespace = STREAM_SETTINGS_NS as SettingsNamespace
|
|
96
104
|
const scope = settingsCtx.settings.register(
|
|
97
|
-
settingsNamespace
|
|
105
|
+
settingsNamespace,
|
|
98
106
|
StreamSettingsSchema,
|
|
99
107
|
{ applies: 'live' },
|
|
100
108
|
)
|