dsh-smooth-stream 0.3.3 → 0.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/README.en.md +23 -18
- package/README.md +23 -18
- package/lib/client.js +2343 -175
- package/lib/client.js.map +1 -1
- package/lib/index.js +123 -6
- package/package.json +15 -10
- package/src/client/DebugPanel.module.css +370 -0
- package/src/client/DebugPanel.tsx +248 -0
- package/src/client/SmoothStreamCard.module.css +8 -0
- package/src/client/SmoothStreamCard.tsx +42 -3
- package/src/client/TypewriterAssistantNodeView.tsx +226 -49
- package/src/client/auto-collapse-controller.ts +977 -0
- package/src/client/debugRuntime.ts +355 -0
- package/src/client/index.ts +121 -30
- package/src/client/locales.ts +122 -0
- package/src/client/smooth-stream-card-controller.ts +97 -14
- package/src/client/smooth-stream-settings-api.ts +53 -3
- package/src/client/teleprompterGlide.ts +160 -31
- package/src/client/useFpsGuard.ts +7 -0
- package/src/client/useProgressiveDomText.ts +99 -17
- package/src/client/useSmoothStreamContent.ts +20 -4
- package/src/plugin.ts +137 -7
- package/src/settings-api.ts +12 -0
- package/src/settings.ts +55 -0
package/lib/client.js
CHANGED
|
@@ -9,41 +9,42 @@ window.__ModuleLoader__.load({
|
|
|
9
9
|
let _deepseek_ai_dsh_client_ui_attachment = require("@deepseek-ai/dsh-client-ui-attachment");
|
|
10
10
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
11
11
|
let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
12
|
+
let react_dom = require("react-dom");
|
|
12
13
|
//#region \0dsh-css:/Users/dzlin/work/project/dsh-smooth-stream/src/client/TypewriterAssistantNodeView.module.css.mjs
|
|
13
|
-
const css$
|
|
14
|
-
const tagId$
|
|
14
|
+
const css$2 = ".I17U7q_root{min-width:0;color:var(--dsw-alias-label-primary);flex-direction:column;font-size:16px;line-height:28px;display:flex}.I17U7q_body{flex-direction:column;gap:16px;min-width:0;display:flex}.I17U7q_think{flex-direction:column;display:flex}.I17U7q_thinkRow{position:relative;overflow:hidden}.I17U7q_think[data-state=running] .I17U7q_thinkRow:after{content:\"\";inset-block:0;background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);pointer-events:none;width:300px;animation:2.6s ease-out infinite I17U7q_dsh-smooth-stream-think-sweep;position:absolute;left:0}@keyframes I17U7q_dsh-smooth-stream-think-sweep{0%{left:-300px}90%,to{left:100%}}.I17U7q_thinkLeading{flex-shrink:0}.I17U7q_thinkChevron{color:var(--dsw-alias-label-secondary)}.I17U7q_thinkTitle{font-weight:400}.I17U7q_thinkSeparator{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.I17U7q_thinkSummary{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:auto;font-size:14px;line-height:24px;overflow:hidden}.I17U7q_thinkSummary[data-follow-end]{text-overflow:clip}.I17U7q_thinkBody{color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;word-break:break-word;padding:4px 0 4px 22px;font-size:14px;line-height:24px}.I17U7q_disclosureRoot{flex-direction:column;width:100%;min-width:0;display:flex}.I17U7q_disclosureRow{cursor:pointer;align-items:center;min-width:0;height:24px;display:flex;position:relative;overflow:hidden}.I17U7q_disclosureLeading{width:16px;height:16px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;margin-right:6px;display:inline-flex;position:relative}.I17U7q_disclosureIconIdle{opacity:1;transition:opacity .1s;display:inline-flex}.I17U7q_disclosureChevronHover{opacity:0;margin:auto;transition:opacity .1s;position:absolute;inset:0}.I17U7q_disclosureRow:hover .I17U7q_disclosureIconIdle{opacity:0}.I17U7q_disclosureRow:hover .I17U7q_disclosureChevronHover{opacity:1}.I17U7q_disclosureTitle{color:var(--dsw-alias-label-secondary);flex:none;font-size:14px;line-height:24px}.I17U7q_disclosureContent{visibility:visible;transition:grid-template-rows var(--ds-transition-duration,.2s) var(--ds-ease-in-out,cubic-bezier(.4, 0, .2, 1)), visibility 0s;grid-template-rows:1fr;display:grid}.I17U7q_disclosureContent[data-collapsed]{visibility:hidden;transition:grid-template-rows var(--ds-transition-duration,.2s) var(--ds-ease-in-out,cubic-bezier(.4, 0, .2, 1)), visibility 0s var(--ds-transition-duration,.2s);grid-template-rows:0fr}.I17U7q_disclosureContent>*{min-height:0;overflow:hidden}@media (prefers-reduced-motion:reduce){.I17U7q_think[data-state=running] .I17U7q_thinkRow:after{animation:none}.I17U7q_disclosureContent,.I17U7q_disclosureContent[data-collapsed]{transition:none}}.I17U7q_stopped{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-tertiary);border-radius:6px;align-self:flex-start;padding:0 6px;font-size:11px;line-height:18px}.I17U7q_visuallyHidden{clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.I17U7q_follow{contain:layout style;min-width:0}@supports (text-box-trim:trim-both){.I17U7q_root :is(p,h1,h2,h3,h4,h5,h6,li,blockquote){text-box-trim:trim-both;text-box-edge:text}}";
|
|
15
|
+
const tagId$2 = "dsh-smooth-stream/TypewriterAssistantNodeView.module.css";
|
|
15
16
|
if (typeof document !== "undefined") {
|
|
16
|
-
let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$
|
|
17
|
+
let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]");
|
|
17
18
|
if (tag === null) {
|
|
18
19
|
tag = document.createElement("style");
|
|
19
20
|
tag.dataset.plugin = "dsh-smooth-stream";
|
|
20
|
-
tag.dataset.pluginCss = tagId$
|
|
21
|
+
tag.dataset.pluginCss = tagId$2;
|
|
21
22
|
document.head.appendChild(tag);
|
|
22
23
|
}
|
|
23
|
-
tag.textContent = css$
|
|
24
|
+
tag.textContent = css$2;
|
|
24
25
|
}
|
|
25
26
|
var TypewriterAssistantNodeView_module_css_default = {
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"disclosureRow": "I17U7q_disclosureRow",
|
|
27
|
+
"think": "I17U7q_think",
|
|
28
|
+
"thinkTitle": "I17U7q_thinkTitle",
|
|
29
|
+
"thinkSummary": "I17U7q_thinkSummary",
|
|
30
|
+
"thinkBody": "I17U7q_thinkBody",
|
|
31
31
|
"disclosureLeading": "I17U7q_disclosureLeading",
|
|
32
|
-
"dsh-smooth-stream-think-sweep": "I17U7q_dsh-smooth-stream-think-sweep",
|
|
33
|
-
"disclosureTitle": "I17U7q_disclosureTitle",
|
|
34
32
|
"disclosureIconIdle": "I17U7q_disclosureIconIdle",
|
|
35
|
-
"
|
|
36
|
-
"thinkSummary": "I17U7q_thinkSummary",
|
|
33
|
+
"disclosureChevronHover": "I17U7q_disclosureChevronHover",
|
|
37
34
|
"follow": "I17U7q_follow",
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"thinkBody": "I17U7q_thinkBody",
|
|
35
|
+
"disclosureRoot": "I17U7q_disclosureRoot",
|
|
36
|
+
"disclosureRow": "I17U7q_disclosureRow",
|
|
41
37
|
"thinkLeading": "I17U7q_thinkLeading",
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
38
|
+
"dsh-smooth-stream-think-sweep": "I17U7q_dsh-smooth-stream-think-sweep",
|
|
39
|
+
"thinkChevron": "I17U7q_thinkChevron",
|
|
40
|
+
"root": "I17U7q_root",
|
|
45
41
|
"body": "I17U7q_body",
|
|
46
|
-
"
|
|
42
|
+
"visuallyHidden": "I17U7q_visuallyHidden",
|
|
43
|
+
"stopped": "I17U7q_stopped",
|
|
44
|
+
"thinkSeparator": "I17U7q_thinkSeparator",
|
|
45
|
+
"disclosureTitle": "I17U7q_disclosureTitle",
|
|
46
|
+
"disclosureContent": "I17U7q_disclosureContent",
|
|
47
|
+
"thinkRow": "I17U7q_thinkRow"
|
|
47
48
|
};
|
|
48
49
|
//#endregion
|
|
49
50
|
//#region src/client/AnimatedDisclosure.tsx
|
|
@@ -98,6 +99,285 @@ window.__ModuleLoader__.load({
|
|
|
98
99
|
});
|
|
99
100
|
}
|
|
100
101
|
//#endregion
|
|
102
|
+
//#region src/settings.ts
|
|
103
|
+
/**
|
|
104
|
+
* User-owned settings for the smooth-stream plugin, exposed to the Host
|
|
105
|
+
* settings service and edited from the Web Settings "plugin configuration"
|
|
106
|
+
* page. This is the runtime-editable complement to {@link StreamConfig}: that
|
|
107
|
+
* contract is composed at load and bridged once through the boot global, while
|
|
108
|
+
* these preferences live in the durable user-settings document and take effect
|
|
109
|
+
* live.
|
|
110
|
+
*/
|
|
111
|
+
/** Settings namespace registered by the Host and served through the plugin RPC. */
|
|
112
|
+
const STREAM_SETTINGS_NS = "smooth-stream";
|
|
113
|
+
/** Defaults preserve the production engine exactly. */
|
|
114
|
+
const DEFAULT_STREAM_DEBUG_TUNING = {
|
|
115
|
+
revealScale: 1,
|
|
116
|
+
queuePressure: .85,
|
|
117
|
+
maxRevealCps: 600,
|
|
118
|
+
springStiffness: 130,
|
|
119
|
+
springDamping: 24,
|
|
120
|
+
springMass: 1,
|
|
121
|
+
runwayPx: 48,
|
|
122
|
+
reserveResponseMs: 180,
|
|
123
|
+
backpressureMinScale: .55
|
|
124
|
+
};
|
|
125
|
+
/** Defaults shared by the Host schema and the client-side fallback. */
|
|
126
|
+
const DEFAULT_STREAM_SETTINGS = {
|
|
127
|
+
enabled: true,
|
|
128
|
+
thinkAutoExpand: true,
|
|
129
|
+
autoCollapse: true,
|
|
130
|
+
debugEnabled: false,
|
|
131
|
+
debugTuning: DEFAULT_STREAM_DEBUG_TUNING
|
|
132
|
+
};
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/client/debugRuntime.ts
|
|
135
|
+
/**
|
|
136
|
+
* Shared browser-side diagnostics state.
|
|
137
|
+
*
|
|
138
|
+
* The settings card owns persistence. This module is the live bridge used by
|
|
139
|
+
* the renderer and the chat-side panel: renderer loops publish measurements,
|
|
140
|
+
* while panel edits are staged through the settings-card controller.
|
|
141
|
+
*/
|
|
142
|
+
const EMPTY_METRICS = {
|
|
143
|
+
fps: null,
|
|
144
|
+
frameMs: null,
|
|
145
|
+
fpsDegraded: false,
|
|
146
|
+
streamActive: false,
|
|
147
|
+
streamBacklog: 0,
|
|
148
|
+
streamSpeedCps: 0,
|
|
149
|
+
streamTargetChars: 0,
|
|
150
|
+
streamDisplayedChars: 0,
|
|
151
|
+
followActive: false,
|
|
152
|
+
followLagPx: 0,
|
|
153
|
+
followVelocityPxPerSec: 0,
|
|
154
|
+
followReservePx: 0,
|
|
155
|
+
followCapacityPx: 0,
|
|
156
|
+
followRevealScale: 1,
|
|
157
|
+
followFollowing: false,
|
|
158
|
+
followConstrained: false,
|
|
159
|
+
scrollTop: null,
|
|
160
|
+
scrollHeight: null,
|
|
161
|
+
clientHeight: null,
|
|
162
|
+
lastUpdatedMs: null
|
|
163
|
+
};
|
|
164
|
+
const INITIAL_STATE = {
|
|
165
|
+
available: false,
|
|
166
|
+
enabled: false,
|
|
167
|
+
writable: false,
|
|
168
|
+
dirty: false,
|
|
169
|
+
status: "loading",
|
|
170
|
+
tuning: { ...DEFAULT_STREAM_DEBUG_TUNING },
|
|
171
|
+
metrics: EMPTY_METRICS
|
|
172
|
+
};
|
|
173
|
+
const store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(INITIAL_STATE);
|
|
174
|
+
const streamMetrics = /* @__PURE__ */ new Map();
|
|
175
|
+
const followMetrics = /* @__PURE__ */ new Map();
|
|
176
|
+
let actions;
|
|
177
|
+
let lastMetricPublish = 0;
|
|
178
|
+
function resetMetrics() {
|
|
179
|
+
streamMetrics.clear();
|
|
180
|
+
followMetrics.clear();
|
|
181
|
+
lastMetricPublish = 0;
|
|
182
|
+
}
|
|
183
|
+
function resetRuntime() {
|
|
184
|
+
actions = void 0;
|
|
185
|
+
resetMetrics();
|
|
186
|
+
store.set({
|
|
187
|
+
...INITIAL_STATE,
|
|
188
|
+
metrics: EMPTY_METRICS
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
function now() {
|
|
192
|
+
return typeof performance === "undefined" ? Date.now() : performance.now();
|
|
193
|
+
}
|
|
194
|
+
function sameTuning(left, right) {
|
|
195
|
+
return left.revealScale === right.revealScale && left.queuePressure === right.queuePressure && left.maxRevealCps === right.maxRevealCps && left.springStiffness === right.springStiffness && left.springDamping === right.springDamping && left.springMass === right.springMass && left.runwayPx === right.runwayPx && left.reserveResponseMs === right.reserveResponseMs && left.backpressureMinScale === right.backpressureMinScale;
|
|
196
|
+
}
|
|
197
|
+
function currentMetrics(timestamp) {
|
|
198
|
+
let stream;
|
|
199
|
+
for (const candidate of streamMetrics.values()) if (stream === void 0 || candidate.updatedAt > stream.updatedAt) stream = candidate;
|
|
200
|
+
let follow;
|
|
201
|
+
for (const candidate of followMetrics.values()) if (follow === void 0 || candidate.updatedAt > follow.updatedAt) follow = candidate;
|
|
202
|
+
return {
|
|
203
|
+
fps: store.getSnapshot().metrics.fps,
|
|
204
|
+
frameMs: store.getSnapshot().metrics.frameMs,
|
|
205
|
+
fpsDegraded: store.getSnapshot().metrics.fpsDegraded,
|
|
206
|
+
streamActive: stream?.active ?? false,
|
|
207
|
+
streamBacklog: stream?.backlog ?? 0,
|
|
208
|
+
streamSpeedCps: stream?.speedCps ?? 0,
|
|
209
|
+
streamTargetChars: stream?.targetChars ?? 0,
|
|
210
|
+
streamDisplayedChars: stream?.displayedChars ?? 0,
|
|
211
|
+
followActive: follow?.active ?? false,
|
|
212
|
+
followLagPx: follow?.lagPx ?? 0,
|
|
213
|
+
followVelocityPxPerSec: follow?.velocityPxPerSec ?? 0,
|
|
214
|
+
followReservePx: follow?.reservePx ?? 0,
|
|
215
|
+
followCapacityPx: follow?.capacityPx ?? 0,
|
|
216
|
+
followRevealScale: follow?.revealScale ?? 1,
|
|
217
|
+
followFollowing: follow?.following ?? false,
|
|
218
|
+
followConstrained: follow?.constrained ?? false,
|
|
219
|
+
scrollTop: follow?.scrollTop ?? null,
|
|
220
|
+
scrollHeight: follow?.scrollHeight ?? null,
|
|
221
|
+
clientHeight: follow?.clientHeight ?? null,
|
|
222
|
+
lastUpdatedMs: timestamp
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function publishMetrics(force = false) {
|
|
226
|
+
const timestamp = now();
|
|
227
|
+
if (!force && timestamp - lastMetricPublish < 80) return;
|
|
228
|
+
lastMetricPublish = timestamp;
|
|
229
|
+
store.set({
|
|
230
|
+
...store.getSnapshot(),
|
|
231
|
+
metrics: currentMetrics(timestamp)
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
const debugRuntime = {
|
|
235
|
+
store,
|
|
236
|
+
getSnapshot() {
|
|
237
|
+
return store.getSnapshot();
|
|
238
|
+
},
|
|
239
|
+
subscribe(listener) {
|
|
240
|
+
return store.subscribe(listener);
|
|
241
|
+
},
|
|
242
|
+
/** Whether hot-path instrumentation should do any work. */
|
|
243
|
+
isEnabled() {
|
|
244
|
+
return store.getSnapshot().enabled;
|
|
245
|
+
},
|
|
246
|
+
tuning() {
|
|
247
|
+
return store.getSnapshot().tuning;
|
|
248
|
+
},
|
|
249
|
+
/** Production values remain untouched until the user explicitly enables diagnostics. */
|
|
250
|
+
activeTuning() {
|
|
251
|
+
return store.getSnapshot().enabled ? store.getSnapshot().tuning : DEFAULT_STREAM_DEBUG_TUNING;
|
|
252
|
+
},
|
|
253
|
+
bindSettings(nextActions) {
|
|
254
|
+
actions = nextActions;
|
|
255
|
+
return () => {
|
|
256
|
+
if (actions !== nextActions) return;
|
|
257
|
+
resetRuntime();
|
|
258
|
+
};
|
|
259
|
+
},
|
|
260
|
+
syncSettings(input) {
|
|
261
|
+
const current = store.getSnapshot();
|
|
262
|
+
const available = input.available ?? true;
|
|
263
|
+
const enabled = available && input.enabled;
|
|
264
|
+
const availabilityChanged = current.available !== available;
|
|
265
|
+
const enabledChanged = current.enabled !== enabled;
|
|
266
|
+
if (availabilityChanged || enabledChanged) resetMetrics();
|
|
267
|
+
const tuning = input.tuning === void 0 ? current.tuning : {
|
|
268
|
+
...DEFAULT_STREAM_DEBUG_TUNING,
|
|
269
|
+
...input.tuning
|
|
270
|
+
};
|
|
271
|
+
if (current.available === available && current.enabled === enabled && current.writable === input.writable && current.dirty === input.dirty && current.status === input.status && sameTuning(current.tuning, tuning)) return;
|
|
272
|
+
store.set({
|
|
273
|
+
...current,
|
|
274
|
+
available,
|
|
275
|
+
enabled,
|
|
276
|
+
writable: input.writable,
|
|
277
|
+
dirty: input.dirty,
|
|
278
|
+
status: input.status,
|
|
279
|
+
tuning,
|
|
280
|
+
metrics: availabilityChanged || enabledChanged || !enabled ? EMPTY_METRICS : current.metrics
|
|
281
|
+
});
|
|
282
|
+
},
|
|
283
|
+
edit(patch) {
|
|
284
|
+
const current = store.getSnapshot();
|
|
285
|
+
const enabled = patch.debugEnabled ?? current.enabled;
|
|
286
|
+
const enabledChanged = current.enabled !== enabled;
|
|
287
|
+
if (enabledChanged) resetMetrics();
|
|
288
|
+
const tuning = patch.debugTuning === void 0 ? current.tuning : {
|
|
289
|
+
...current.tuning,
|
|
290
|
+
...patch.debugTuning
|
|
291
|
+
};
|
|
292
|
+
store.set({
|
|
293
|
+
...current,
|
|
294
|
+
enabled,
|
|
295
|
+
dirty: true,
|
|
296
|
+
tuning,
|
|
297
|
+
metrics: enabledChanged || !enabled ? EMPTY_METRICS : current.metrics
|
|
298
|
+
});
|
|
299
|
+
actions?.edit({
|
|
300
|
+
...patch.debugEnabled === void 0 ? {} : { debugEnabled: patch.debugEnabled },
|
|
301
|
+
...patch.debugTuning === void 0 ? {} : { debugTuning: tuning }
|
|
302
|
+
});
|
|
303
|
+
},
|
|
304
|
+
save() {
|
|
305
|
+
actions?.save();
|
|
306
|
+
},
|
|
307
|
+
discard() {
|
|
308
|
+
actions?.discard();
|
|
309
|
+
},
|
|
310
|
+
reset() {
|
|
311
|
+
this.edit({ debugTuning: { ...DEFAULT_STREAM_DEBUG_TUNING } });
|
|
312
|
+
},
|
|
313
|
+
reportStream(id, metric) {
|
|
314
|
+
if (!this.isEnabled()) return;
|
|
315
|
+
if (metric === null) streamMetrics.delete(id);
|
|
316
|
+
else streamMetrics.set(id, {
|
|
317
|
+
...metric,
|
|
318
|
+
updatedAt: now()
|
|
319
|
+
});
|
|
320
|
+
publishMetrics(metric === null);
|
|
321
|
+
},
|
|
322
|
+
reportFollow(port, metric) {
|
|
323
|
+
if (!this.isEnabled()) return;
|
|
324
|
+
if (metric === null) followMetrics.delete(port);
|
|
325
|
+
else followMetrics.set(port, {
|
|
326
|
+
...metric,
|
|
327
|
+
updatedAt: now()
|
|
328
|
+
});
|
|
329
|
+
publishMetrics(metric === null);
|
|
330
|
+
},
|
|
331
|
+
reportFps(fps, frameMs, degraded) {
|
|
332
|
+
if (!this.isEnabled()) return;
|
|
333
|
+
const current = store.getSnapshot();
|
|
334
|
+
store.set({
|
|
335
|
+
...current,
|
|
336
|
+
metrics: {
|
|
337
|
+
...current.metrics,
|
|
338
|
+
fps,
|
|
339
|
+
frameMs,
|
|
340
|
+
fpsDegraded: degraded,
|
|
341
|
+
lastUpdatedMs: now()
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
},
|
|
345
|
+
clearFps() {
|
|
346
|
+
if (!this.isEnabled()) return;
|
|
347
|
+
const current = store.getSnapshot();
|
|
348
|
+
store.set({
|
|
349
|
+
...current,
|
|
350
|
+
metrics: {
|
|
351
|
+
...current.metrics,
|
|
352
|
+
fps: null,
|
|
353
|
+
frameMs: null,
|
|
354
|
+
fpsDegraded: false,
|
|
355
|
+
lastUpdatedMs: now()
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
},
|
|
359
|
+
panelFace() {
|
|
360
|
+
return {
|
|
361
|
+
hooks: { debugRuntime: store },
|
|
362
|
+
edit: (patch) => {
|
|
363
|
+
this.edit(patch);
|
|
364
|
+
},
|
|
365
|
+
save: () => {
|
|
366
|
+
this.save();
|
|
367
|
+
},
|
|
368
|
+
discard: () => {
|
|
369
|
+
this.discard();
|
|
370
|
+
},
|
|
371
|
+
reset: () => {
|
|
372
|
+
this.reset();
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
},
|
|
376
|
+
resetRuntime() {
|
|
377
|
+
resetRuntime();
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
//#endregion
|
|
101
381
|
//#region src/client/useSmoothStreamContent.ts
|
|
102
382
|
/**
|
|
103
383
|
* Stream-smoothing reveal hook.
|
|
@@ -161,20 +441,19 @@ window.__ModuleLoader__.load({
|
|
|
161
441
|
}
|
|
162
442
|
};
|
|
163
443
|
const QUEUE_ACCEL_EXPONENT = 1.25;
|
|
164
|
-
const QUEUE_PRESSURE_FACTOR = .85;
|
|
165
444
|
const CATCHUP_SECONDS = .15;
|
|
166
445
|
const clamp = (value, min, max) => {
|
|
167
446
|
return Math.min(max, Math.max(min, value));
|
|
168
447
|
};
|
|
169
448
|
/** Float-debt queue integration from `ultimate_stream_physics_scroller.html`. */
|
|
170
|
-
function computeAdaptiveQueueStep(backlog, dtMs, debt, revealScale = 1) {
|
|
449
|
+
function computeAdaptiveQueueStep(backlog, dtMs, debt, revealScale = 1, tuning = DEFAULT_STREAM_DEBUG_TUNING) {
|
|
171
450
|
if (backlog <= 0 || dtMs <= 0) return {
|
|
172
451
|
revealChars: 0,
|
|
173
452
|
debt: 0,
|
|
174
453
|
speedCps: 0
|
|
175
454
|
};
|
|
176
|
-
const speedCps = Math.min(
|
|
177
|
-
const effectiveScale = clamp(revealScale, .05,
|
|
455
|
+
const speedCps = Math.min(tuning.maxRevealCps, 90 + Math.pow(backlog, QUEUE_ACCEL_EXPONENT) * tuning.queuePressure);
|
|
456
|
+
const effectiveScale = clamp(revealScale * tuning.revealScale, .05, 2);
|
|
178
457
|
const accumulated = Math.max(0, debt) + speedCps * effectiveScale * (dtMs / 1e3);
|
|
179
458
|
const revealChars = Math.min(backlog, Math.floor(accumulated));
|
|
180
459
|
return {
|
|
@@ -268,6 +547,7 @@ window.__ModuleLoader__.load({
|
|
|
268
547
|
revealScaleOutRef.current = revealScaleRef;
|
|
269
548
|
const inputCompleteRef = (0, react.useRef)(inputComplete);
|
|
270
549
|
inputCompleteRef.current = inputComplete;
|
|
550
|
+
const streamIdRef = (0, react.useRef)(`stream-${Math.random().toString(36).slice(2)}`);
|
|
271
551
|
(0, react.useEffect)(() => {
|
|
272
552
|
holdBackRef.current = shouldHoldBack;
|
|
273
553
|
}, [shouldHoldBack]);
|
|
@@ -319,6 +599,7 @@ window.__ModuleLoader__.load({
|
|
|
319
599
|
settleCpsRef.current = null;
|
|
320
600
|
const speedOut = speedOutRef.current;
|
|
321
601
|
if (speedOut !== void 0) speedOut.current = seedCps;
|
|
602
|
+
debugRuntime.reportStream(streamIdRef.current, null);
|
|
322
603
|
stopFrameLoop();
|
|
323
604
|
return;
|
|
324
605
|
}
|
|
@@ -332,6 +613,7 @@ window.__ModuleLoader__.load({
|
|
|
332
613
|
lastFrameTsRef.current = now;
|
|
333
614
|
const idleMs = now - lastInputTsRef.current;
|
|
334
615
|
const producerComplete = inputCompleteRef.current;
|
|
616
|
+
const debugTuning = debugRuntime.activeTuning();
|
|
335
617
|
const inputActive = !producerComplete && idleMs <= config.activeInputWindowMs;
|
|
336
618
|
const settling = producerComplete || !inputActive && idleMs >= config.settleAfterMs;
|
|
337
619
|
if (!producerComplete) settleCpsRef.current = null;
|
|
@@ -355,14 +637,21 @@ window.__ModuleLoader__.load({
|
|
|
355
637
|
settling,
|
|
356
638
|
steadyCps
|
|
357
639
|
}, dtSeconds);
|
|
358
|
-
revealChars = Math.min(step.revealChars, backlog);
|
|
640
|
+
revealChars = Math.min(Math.round(step.revealChars * debugTuning.revealScale), backlog);
|
|
359
641
|
revealSpeedCps = frameIntervalMs > 0 ? revealChars * 1e3 / frameIntervalMs : 0;
|
|
360
642
|
} else {
|
|
361
|
-
const step = computeAdaptiveQueueStep(backlog, frameIntervalMs, queueDebtRef.current, revealScaleOutRef.current?.current ?? 1);
|
|
643
|
+
const step = computeAdaptiveQueueStep(backlog, frameIntervalMs, queueDebtRef.current, revealScaleOutRef.current?.current ?? 1, debugTuning);
|
|
362
644
|
revealChars = step.revealChars;
|
|
363
645
|
revealSpeedCps = step.speedCps;
|
|
364
646
|
nextQueueDebt = step.debt;
|
|
365
647
|
}
|
|
648
|
+
debugRuntime.reportStream(streamIdRef.current, {
|
|
649
|
+
backlog,
|
|
650
|
+
speedCps: revealSpeedCps,
|
|
651
|
+
targetChars: targetCount,
|
|
652
|
+
displayedChars: displayedCount,
|
|
653
|
+
active: !producerComplete
|
|
654
|
+
});
|
|
366
655
|
if (holdBackRef.current?.() === true) {
|
|
367
656
|
rafRef.current = requestAnimationFrame(tick);
|
|
368
657
|
return;
|
|
@@ -439,6 +728,7 @@ window.__ModuleLoader__.load({
|
|
|
439
728
|
(0, react.useEffect)(() => {
|
|
440
729
|
return () => {
|
|
441
730
|
stopFrameLoop();
|
|
731
|
+
debugRuntime.reportStream(streamIdRef.current, null);
|
|
442
732
|
};
|
|
443
733
|
}, [stopFrameLoop]);
|
|
444
734
|
return displayedContent;
|
|
@@ -470,6 +760,7 @@ window.__ModuleLoader__.load({
|
|
|
470
760
|
(0, react.useEffect)(() => {
|
|
471
761
|
if (!active) return;
|
|
472
762
|
let rafId = 0;
|
|
763
|
+
let lastDebugReport = 0;
|
|
473
764
|
const frame = (now) => {
|
|
474
765
|
rafId = requestAnimationFrame(frame);
|
|
475
766
|
const fps = fpsRef.current;
|
|
@@ -480,13 +771,18 @@ window.__ModuleLoader__.load({
|
|
|
480
771
|
const delta = Math.min(MAX_FRAME_MS, Math.max(1, now - fps.lastMs));
|
|
481
772
|
fps.lastMs = now;
|
|
482
773
|
fps.emaMs = fps.emaMs === 0 ? delta : fps.emaMs + FPS_ALPHA * (delta - fps.emaMs);
|
|
483
|
-
|
|
774
|
+
const currentFps = 1e3 / fps.emaMs;
|
|
775
|
+
if (currentFps < FPS_THRESHOLD) {
|
|
484
776
|
fps.healthyRun = 0;
|
|
485
777
|
fps.degraded = true;
|
|
486
778
|
} else if (fps.degraded) {
|
|
487
779
|
fps.healthyRun += 1;
|
|
488
780
|
if (fps.healthyRun >= RECOVER_FRAMES) fps.degraded = false;
|
|
489
781
|
}
|
|
782
|
+
if (now - lastDebugReport >= 100) {
|
|
783
|
+
debugRuntime.reportFps(currentFps, fps.emaMs, fps.degraded);
|
|
784
|
+
lastDebugReport = now;
|
|
785
|
+
}
|
|
490
786
|
};
|
|
491
787
|
rafId = requestAnimationFrame(frame);
|
|
492
788
|
return () => {
|
|
@@ -497,6 +793,7 @@ window.__ModuleLoader__.load({
|
|
|
497
793
|
healthyRun: 0,
|
|
498
794
|
degraded: false
|
|
499
795
|
};
|
|
796
|
+
debugRuntime.clearFps();
|
|
500
797
|
};
|
|
501
798
|
}, [active]);
|
|
502
799
|
const ref = (0, react.useCallback)((element) => {
|
|
@@ -519,6 +816,805 @@ window.__ModuleLoader__.load({
|
|
|
519
816
|
};
|
|
520
817
|
}
|
|
521
818
|
//#endregion
|
|
819
|
+
//#region src/client/auto-collapse-controller.ts
|
|
820
|
+
/**
|
|
821
|
+
* Auto-collapse coordinator: folds the work process of finished turns behind
|
|
822
|
+
* one summary row so only the model's final answer stays visible.
|
|
823
|
+
*
|
|
824
|
+
* The Harness ChatView lays a conversation out as a flat list of flow items
|
|
825
|
+
* (`[data-chat-flow] > [data-chat-flow-kind=…]`). This controller watches that
|
|
826
|
+
* list, splits it into turn segments at `user`/`steering` boundaries closed by
|
|
827
|
+
* `turn-tail`, and — once a segment has settled — hides its thinking rows,
|
|
828
|
+
* tool/command/context seats and intermediate replies, leaving one clickable
|
|
829
|
+
* `已处理 {时长}` row plus the final answer. Clicking the row expands the full
|
|
830
|
+
* process again; the choice survives later renders because it lives in the
|
|
831
|
+
* controller, not the DOM.
|
|
832
|
+
*
|
|
833
|
+
* Adapted from the standalone `dsh-auto-collapse` plugin (level-1 folding
|
|
834
|
+
* only): no second-level chips, no merged thinking rows, no status-text
|
|
835
|
+
* rewrite, no transition animation — the collapse decision itself stays
|
|
836
|
+
* instantaneous and reversible.
|
|
837
|
+
*
|
|
838
|
+
* React coexistence follows the upstream design: injected rows are plain
|
|
839
|
+
* siblings/prepend children the vdom diff never claims, native rows are only
|
|
840
|
+
* ever touched through an inline-`display` ledger that records the precise
|
|
841
|
+
* original value and restores it on stop, session switch, or foreign takeover
|
|
842
|
+
* (a written-value comparison plus an ownership sentinel catches another
|
|
843
|
+
* script rewriting styles behind our back). A MutationObserver batches real
|
|
844
|
+
* structure changes into rAF passes; a low-frequency self-rearming audit pass
|
|
845
|
+
* reconciles unobservable style writes; a setTimeout fallback keeps passes
|
|
846
|
+
* flowing when background tabs suspend rAF.
|
|
847
|
+
*/
|
|
848
|
+
const STYLE_ID = "dshss-auto-collapse-style";
|
|
849
|
+
/** Class of the injected per-turn summary row; also consumed by the follow
|
|
850
|
+
* engine so the row rides the same lag transform as native message rows. */
|
|
851
|
+
const PROCESSED_CLASS = "dshss-processed";
|
|
852
|
+
/** Inline custom property marking elements whose display this plugin owns. */
|
|
853
|
+
const DISPLAY_OWNED_PROP = "--dshss-display-owned";
|
|
854
|
+
/** Unobservable external style writes are reconciled by this audit cadence. */
|
|
855
|
+
const AUDIT_TICK_MS = 1e3;
|
|
856
|
+
/** Background-tab fallback that flushes a scheduled pass when rAF is frozen. */
|
|
857
|
+
const PASS_FALLBACK_MS = 60;
|
|
858
|
+
/**
|
|
859
|
+
* Fold motion, deliberately minimal: layout changes land synchronously (the
|
|
860
|
+
* display toggle happens in the same tick as the decision) and nothing
|
|
861
|
+
* animates geometry at all — the earlier container-height squeeze was removed
|
|
862
|
+
* because real sessions showed residual drift after the pin released (other
|
|
863
|
+
* layout participants keep settling around the fold). What remains is one
|
|
864
|
+
* paint-only flourish: the summary row fades its opacity in. A fold is
|
|
865
|
+
* therefore a single reflow followed by a fade — the most stable shape
|
|
866
|
+
* possible.
|
|
867
|
+
*/
|
|
868
|
+
const FOLD_ROW_FADE_MS = 160;
|
|
869
|
+
const FOLD_EASE_OUT = "cubic-bezier(0.22, 0.61, 0.36, 1)";
|
|
870
|
+
const ZH_COPY = {
|
|
871
|
+
processedLabel: "已处理",
|
|
872
|
+
expandTitle: "展开工作过程",
|
|
873
|
+
collapseTitle: "收起工作过程",
|
|
874
|
+
formatDuration: formatZhDuration
|
|
875
|
+
};
|
|
876
|
+
const EN_COPY = {
|
|
877
|
+
processedLabel: "Processed",
|
|
878
|
+
expandTitle: "Show the work process",
|
|
879
|
+
collapseTitle: "Hide the work process",
|
|
880
|
+
formatDuration: formatEnDuration
|
|
881
|
+
};
|
|
882
|
+
function resolveFoldCopy() {
|
|
883
|
+
let lang = "";
|
|
884
|
+
try {
|
|
885
|
+
lang = document.documentElement.lang || navigator.language || "";
|
|
886
|
+
} catch {
|
|
887
|
+
lang = "";
|
|
888
|
+
}
|
|
889
|
+
return lang.toLowerCase().startsWith("zh") ? ZH_COPY : EN_COPY;
|
|
890
|
+
}
|
|
891
|
+
const FOLD_CSS = `
|
|
892
|
+
.${PROCESSED_CLASS} {
|
|
893
|
+
box-sizing: border-box;
|
|
894
|
+
display: inline-flex;
|
|
895
|
+
align-self: flex-start;
|
|
896
|
+
width: fit-content;
|
|
897
|
+
max-width: 100%;
|
|
898
|
+
align-items: center;
|
|
899
|
+
gap: 6px;
|
|
900
|
+
padding: 2px 0;
|
|
901
|
+
border: none;
|
|
902
|
+
background: none;
|
|
903
|
+
font: 400 14px/24px system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
904
|
+
color: var(--dsw-alias-label-tertiary, rgba(127, 127, 127, 0.9));
|
|
905
|
+
cursor: pointer;
|
|
906
|
+
user-select: none;
|
|
907
|
+
border-radius: 4px;
|
|
908
|
+
transition: color 0.15s ease;
|
|
909
|
+
}
|
|
910
|
+
.${PROCESSED_CLASS}:hover {
|
|
911
|
+
color: var(--dsw-alias-label-primary, inherit);
|
|
912
|
+
background: transparent;
|
|
913
|
+
}
|
|
914
|
+
.${PROCESSED_CLASS}:focus-visible {
|
|
915
|
+
outline: 2px solid var(--dsw-alias-state-focus-ring, rgba(77, 107, 254, 0.8));
|
|
916
|
+
outline-offset: 2px;
|
|
917
|
+
}
|
|
918
|
+
.${PROCESSED_CLASS}-chevron {
|
|
919
|
+
display: inline-flex;
|
|
920
|
+
flex: none;
|
|
921
|
+
width: 14px;
|
|
922
|
+
height: 14px;
|
|
923
|
+
opacity: 0.55;
|
|
924
|
+
transform: rotate(-90deg);
|
|
925
|
+
transition: transform 0.12s ease, opacity 0.1s ease;
|
|
926
|
+
}
|
|
927
|
+
.${PROCESSED_CLASS}:hover .${PROCESSED_CLASS}-chevron {
|
|
928
|
+
opacity: 0.9;
|
|
929
|
+
}
|
|
930
|
+
.${PROCESSED_CLASS}[aria-expanded="true"] .${PROCESSED_CLASS}-chevron {
|
|
931
|
+
transform: rotate(0deg);
|
|
932
|
+
}
|
|
933
|
+
@media (prefers-reduced-motion: reduce) {
|
|
934
|
+
.${PROCESSED_CLASS},
|
|
935
|
+
.${PROCESSED_CLASS}-chevron {
|
|
936
|
+
transition: none;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
`;
|
|
940
|
+
var AutoCollapseController = class {
|
|
941
|
+
observer = null;
|
|
942
|
+
raf = 0;
|
|
943
|
+
timer = 0;
|
|
944
|
+
auditTimer = 0;
|
|
945
|
+
/** True between start() and stop(); the preference toggle re-enters both. */
|
|
946
|
+
running = false;
|
|
947
|
+
lastPassError = "";
|
|
948
|
+
flow = null;
|
|
949
|
+
/** Stable segment key → summary row + gesture state. */
|
|
950
|
+
segmentStates = /* @__PURE__ */ new Map();
|
|
951
|
+
/** First sighting of a running segment, for turns without a host duration. */
|
|
952
|
+
runningSince = /* @__PURE__ */ new Map();
|
|
953
|
+
/** Segments that settled once: a resumed run restarts local timing. */
|
|
954
|
+
completedOnce = /* @__PURE__ */ new Set();
|
|
955
|
+
/** Precise pre-plugin display values, restored on stop/switch/takeover. */
|
|
956
|
+
originalDisplay = /* @__PURE__ */ new WeakMap();
|
|
957
|
+
writtenDisplay = /* @__PURE__ */ new WeakMap();
|
|
958
|
+
controlledDisplay = /* @__PURE__ */ new Set();
|
|
959
|
+
/** Reply-presence verdicts cached per message until a mutation hits it. */
|
|
960
|
+
bodyTextCache = /* @__PURE__ */ new WeakMap();
|
|
961
|
+
dirtyMessages = /* @__PURE__ */ new Set();
|
|
962
|
+
onVisibilityChange = () => {
|
|
963
|
+
if (typeof document === "undefined" || document.hidden !== true) this.schedule();
|
|
964
|
+
};
|
|
965
|
+
/** Re-run a pass immediately (settings changes, diagnostics, tests).
|
|
966
|
+
* Reply-presence verdicts are dropped wholesale: manual refreshes are rare,
|
|
967
|
+
* and a conservative rebuild beats a stale classification. */
|
|
968
|
+
refresh() {
|
|
969
|
+
this.bodyTextCache = /* @__PURE__ */ new WeakMap();
|
|
970
|
+
this.dirtyMessages.clear();
|
|
971
|
+
this.schedule();
|
|
972
|
+
}
|
|
973
|
+
start() {
|
|
974
|
+
if (this.running) return;
|
|
975
|
+
this.running = true;
|
|
976
|
+
injectStyle();
|
|
977
|
+
try {
|
|
978
|
+
this.observer = new MutationObserver((records) => {
|
|
979
|
+
if (this.shouldSchedule(records)) {
|
|
980
|
+
this.markDirty(records);
|
|
981
|
+
this.schedule();
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
this.observer.observe(document.body, {
|
|
985
|
+
childList: true,
|
|
986
|
+
subtree: true,
|
|
987
|
+
attributes: true,
|
|
988
|
+
attributeFilter: ["data-state"],
|
|
989
|
+
characterData: true
|
|
990
|
+
});
|
|
991
|
+
this.armAuditLoop();
|
|
992
|
+
this.schedule();
|
|
993
|
+
} catch (error) {
|
|
994
|
+
this.reportError(error);
|
|
995
|
+
throw error;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
stop() {
|
|
999
|
+
this.running = false;
|
|
1000
|
+
if (this.raf !== 0) cancelAnimationFrame(this.raf);
|
|
1001
|
+
this.raf = 0;
|
|
1002
|
+
if (this.timer !== 0) clearTimeout(this.timer);
|
|
1003
|
+
this.timer = 0;
|
|
1004
|
+
if (this.auditTimer !== 0) clearTimeout(this.auditTimer);
|
|
1005
|
+
this.auditTimer = 0;
|
|
1006
|
+
if (typeof document !== "undefined" && typeof document.removeEventListener === "function") document.removeEventListener("visibilitychange", this.onVisibilityChange);
|
|
1007
|
+
this.observer?.disconnect();
|
|
1008
|
+
this.observer = null;
|
|
1009
|
+
this.switchFlow(null);
|
|
1010
|
+
removeStyle();
|
|
1011
|
+
}
|
|
1012
|
+
/** Low-frequency reconciliation for style writes no observer can see. */
|
|
1013
|
+
armAuditLoop() {
|
|
1014
|
+
if (typeof document !== "undefined" && typeof document.addEventListener === "function") document.addEventListener("visibilitychange", this.onVisibilityChange);
|
|
1015
|
+
this.rearmAudit();
|
|
1016
|
+
}
|
|
1017
|
+
rearmAudit() {
|
|
1018
|
+
if (!this.running || this.auditTimer !== 0) return;
|
|
1019
|
+
this.auditTimer = setTimeout(() => {
|
|
1020
|
+
this.auditTimer = 0;
|
|
1021
|
+
if (!this.running) return;
|
|
1022
|
+
if (typeof document !== "undefined" && document.hidden === true) {
|
|
1023
|
+
this.rearmAudit();
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
this.schedule();
|
|
1027
|
+
this.rearmAudit();
|
|
1028
|
+
}, AUDIT_TICK_MS);
|
|
1029
|
+
}
|
|
1030
|
+
/** Body-level observations only need to wake us up near the active flow. */
|
|
1031
|
+
shouldSchedule(records) {
|
|
1032
|
+
if (records.length === 0 || this.flow === null || !this.flow.isConnected) return true;
|
|
1033
|
+
return records.some((record) => nodeWithin(record.target, this.flow) || nodeWithin(this.flow, record.target));
|
|
1034
|
+
}
|
|
1035
|
+
/** Walk each record to its flow child so reply-cache invalidation stays
|
|
1036
|
+
* targeted; anything unattributable invalidates conservatively. */
|
|
1037
|
+
markDirty(records) {
|
|
1038
|
+
const flow = this.flow;
|
|
1039
|
+
if (flow === null || !flow.isConnected) return;
|
|
1040
|
+
if (records.length === 0) {
|
|
1041
|
+
this.bodyTextCache = /* @__PURE__ */ new WeakMap();
|
|
1042
|
+
this.dirtyMessages.clear();
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
for (const record of records) {
|
|
1046
|
+
let current = record.target;
|
|
1047
|
+
while (current !== null && current.parentNode !== flow) current = current.parentNode;
|
|
1048
|
+
if (!(current instanceof HTMLElement)) {
|
|
1049
|
+
this.bodyTextCache = /* @__PURE__ */ new WeakMap();
|
|
1050
|
+
this.dirtyMessages.clear();
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
this.dirtyMessages.add(current);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
schedule() {
|
|
1057
|
+
if (!this.running || this.raf !== 0) return;
|
|
1058
|
+
this.raf = requestAnimationFrame(() => {
|
|
1059
|
+
this.raf = 0;
|
|
1060
|
+
if (this.timer !== 0) {
|
|
1061
|
+
clearTimeout(this.timer);
|
|
1062
|
+
this.timer = 0;
|
|
1063
|
+
}
|
|
1064
|
+
this.runPass();
|
|
1065
|
+
});
|
|
1066
|
+
if (this.timer !== 0) clearTimeout(this.timer);
|
|
1067
|
+
this.timer = setTimeout(() => {
|
|
1068
|
+
this.timer = 0;
|
|
1069
|
+
if (this.raf !== 0) {
|
|
1070
|
+
cancelAnimationFrame(this.raf);
|
|
1071
|
+
this.raf = 0;
|
|
1072
|
+
this.runPass();
|
|
1073
|
+
}
|
|
1074
|
+
}, PASS_FALLBACK_MS);
|
|
1075
|
+
}
|
|
1076
|
+
runPass() {
|
|
1077
|
+
try {
|
|
1078
|
+
this.pass();
|
|
1079
|
+
this.lastPassError = "";
|
|
1080
|
+
} catch (error) {
|
|
1081
|
+
this.reportError(error);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
reportError(error) {
|
|
1085
|
+
const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
1086
|
+
if (message === this.lastPassError) return;
|
|
1087
|
+
this.lastPassError = message;
|
|
1088
|
+
console.error("[dsh-smooth-stream] auto-collapse pass failed", error);
|
|
1089
|
+
}
|
|
1090
|
+
pass() {
|
|
1091
|
+
if (!this.running) return;
|
|
1092
|
+
const nextFlow = findFlow();
|
|
1093
|
+
if (nextFlow !== this.flow) this.switchFlow(nextFlow);
|
|
1094
|
+
const flow = this.flow;
|
|
1095
|
+
if (flow === null) return;
|
|
1096
|
+
for (const el of this.dirtyMessages) this.bodyTextCache.delete(el);
|
|
1097
|
+
this.dirtyMessages.clear();
|
|
1098
|
+
const segments = buildSegments(flow, (el) => this.hasBodyCached(el));
|
|
1099
|
+
const liveSegmentKeys = new Set(segments.map((segment) => segment.key));
|
|
1100
|
+
for (const segment of segments) {
|
|
1101
|
+
if (!segment.running) continue;
|
|
1102
|
+
if (this.completedOnce.has(segment.key)) {
|
|
1103
|
+
this.completedOnce.delete(segment.key);
|
|
1104
|
+
this.runningSince.delete(segment.key);
|
|
1105
|
+
}
|
|
1106
|
+
if (!this.runningSince.has(segment.key)) this.runningSince.set(segment.key, Date.now());
|
|
1107
|
+
}
|
|
1108
|
+
const completedKeys = /* @__PURE__ */ new Set();
|
|
1109
|
+
for (const segment of segments) {
|
|
1110
|
+
if (!segment.closed || segment.running || !segment.hasWork) continue;
|
|
1111
|
+
completedKeys.add(segment.key);
|
|
1112
|
+
this.completedOnce.add(segment.key);
|
|
1113
|
+
let state = this.segmentStates.get(segment.key);
|
|
1114
|
+
if (state === void 0) {
|
|
1115
|
+
state = {
|
|
1116
|
+
key: segment.key,
|
|
1117
|
+
row: null,
|
|
1118
|
+
expanded: false,
|
|
1119
|
+
snapshot: segment
|
|
1120
|
+
};
|
|
1121
|
+
this.segmentStates.set(segment.key, state);
|
|
1122
|
+
} else state.snapshot = segment;
|
|
1123
|
+
const started = this.runningSince.get(segment.key);
|
|
1124
|
+
const parsed = segment.boundary === null ? void 0 : parseTurnDuration(segment.boundary);
|
|
1125
|
+
if (parsed !== void 0) state.duration = parsed;
|
|
1126
|
+
else if (state.duration === void 0 && started !== void 0) state.duration = Date.now() - started;
|
|
1127
|
+
if (state.row === null || !state.row.isConnected) state.row = this.createProcessedRow(state);
|
|
1128
|
+
this.syncProcessedRow(state);
|
|
1129
|
+
}
|
|
1130
|
+
for (const [key, state] of [...this.segmentStates]) {
|
|
1131
|
+
if (completedKeys.has(key)) continue;
|
|
1132
|
+
state.row?.remove();
|
|
1133
|
+
this.segmentStates.delete(key);
|
|
1134
|
+
}
|
|
1135
|
+
const desiredHidden = /* @__PURE__ */ new Set();
|
|
1136
|
+
for (const segment of segments) {
|
|
1137
|
+
const state = this.segmentStates.get(segment.key);
|
|
1138
|
+
if (state !== void 0 && !state.expanded) {
|
|
1139
|
+
for (const seat of segment.hideSeats) this.hideElement(seat, desiredHidden);
|
|
1140
|
+
for (const row of segment.finalThinkRows) this.hideElement(row, desiredHidden);
|
|
1141
|
+
if (segment.finalStep !== null) this.restoreElement(segment.finalStep);
|
|
1142
|
+
} else {
|
|
1143
|
+
for (const seat of segment.hideSeats) this.restoreElement(seat);
|
|
1144
|
+
for (const row of segment.finalThinkRows) this.restoreElement(row);
|
|
1145
|
+
if (segment.finalStep !== null) this.restoreElement(segment.finalStep);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
for (const segment of segments) {
|
|
1149
|
+
if (!hasVisibleSegmentWork(segment)) {
|
|
1150
|
+
const state = this.segmentStates.get(segment.key);
|
|
1151
|
+
if (state !== void 0 && state.row !== null) {
|
|
1152
|
+
state.row.remove();
|
|
1153
|
+
state.row = null;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
for (const seat of segment.hideSeats) this.retainDisplayControl(seat, desiredHidden);
|
|
1157
|
+
for (const row of segment.finalThinkRows) this.retainDisplayControl(row, desiredHidden);
|
|
1158
|
+
if (segment.finalStep !== null) this.retainDisplayControl(segment.finalStep, desiredHidden);
|
|
1159
|
+
}
|
|
1160
|
+
this.restoreUnusedDisplays(desiredHidden);
|
|
1161
|
+
for (const state of this.segmentStates.values()) this.placeProcessedRow(flow, state);
|
|
1162
|
+
for (const key of [...this.runningSince.keys()]) if (!liveSegmentKeys.has(key)) this.runningSince.delete(key);
|
|
1163
|
+
for (const key of [...this.completedOnce]) if (!liveSegmentKeys.has(key)) this.completedOnce.delete(key);
|
|
1164
|
+
}
|
|
1165
|
+
/** Flow swap means a session switch: fully restore the old tree, rebuild. */
|
|
1166
|
+
switchFlow(next) {
|
|
1167
|
+
if (next === this.flow) return;
|
|
1168
|
+
for (const state of this.segmentStates.values()) state.row?.remove();
|
|
1169
|
+
this.segmentStates.clear();
|
|
1170
|
+
this.runningSince.clear();
|
|
1171
|
+
this.completedOnce.clear();
|
|
1172
|
+
this.bodyTextCache = /* @__PURE__ */ new WeakMap();
|
|
1173
|
+
this.dirtyMessages.clear();
|
|
1174
|
+
this.restoreAllDisplays();
|
|
1175
|
+
this.flow = next;
|
|
1176
|
+
}
|
|
1177
|
+
/** Keep already-controlled elements in the desired-hidden set so the sweep
|
|
1178
|
+
* does not flip them back mid-flight of an external change. */
|
|
1179
|
+
retainDisplayControl(el, desiredHidden) {
|
|
1180
|
+
if (this.controlledDisplay.has(el)) desiredHidden.add(el);
|
|
1181
|
+
}
|
|
1182
|
+
/** Restore every element the ledger controls that this pass no longer wants
|
|
1183
|
+
* hidden; elements absent from `desired` come back to their true display. */
|
|
1184
|
+
restoreUnusedDisplays(desired) {
|
|
1185
|
+
for (const el of [...this.controlledDisplay]) if (!desired.has(el)) this.restoreElement(el);
|
|
1186
|
+
}
|
|
1187
|
+
/** Restore every element still under the display ledger, then retire the
|
|
1188
|
+
* ledger itself (stop / session switch). */
|
|
1189
|
+
restoreAllDisplays() {
|
|
1190
|
+
for (const el of [...this.controlledDisplay]) this.restoreElement(el);
|
|
1191
|
+
this.controlledDisplay.clear();
|
|
1192
|
+
this.originalDisplay = /* @__PURE__ */ new WeakMap();
|
|
1193
|
+
this.writtenDisplay = /* @__PURE__ */ new WeakMap();
|
|
1194
|
+
}
|
|
1195
|
+
createProcessedRow(state) {
|
|
1196
|
+
const row = createProcessedRowElement(state.duration);
|
|
1197
|
+
row.addEventListener("click", () => {
|
|
1198
|
+
state.expanded = !state.expanded;
|
|
1199
|
+
this.syncProcessedRow(state);
|
|
1200
|
+
this.schedule();
|
|
1201
|
+
});
|
|
1202
|
+
return row;
|
|
1203
|
+
}
|
|
1204
|
+
syncProcessedRow(state) {
|
|
1205
|
+
const row = state.row;
|
|
1206
|
+
if (row === null) return;
|
|
1207
|
+
const copy = resolveFoldCopy();
|
|
1208
|
+
const text = row.firstElementChild;
|
|
1209
|
+
const label = state.duration === void 0 ? copy.processedLabel : `${copy.processedLabel} ${copy.formatDuration(state.duration)}`;
|
|
1210
|
+
if (text !== null && text.textContent !== label) text.textContent = label;
|
|
1211
|
+
const expanded = String(state.expanded);
|
|
1212
|
+
if (row.getAttribute("aria-expanded") !== expanded) row.setAttribute("aria-expanded", expanded);
|
|
1213
|
+
const title = state.expanded ? copy.collapseTitle : copy.expandTitle;
|
|
1214
|
+
if (row.title !== title) row.title = title;
|
|
1215
|
+
}
|
|
1216
|
+
placeProcessedRow(flow, state) {
|
|
1217
|
+
const row = state.row;
|
|
1218
|
+
if (row === null) return;
|
|
1219
|
+
if (!state.snapshot.hasWork || !hasVisibleSegmentWork(state.snapshot)) {
|
|
1220
|
+
row.remove();
|
|
1221
|
+
state.row = null;
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
let target = state.snapshot.firstWork ?? state.snapshot.finalStep ?? state.snapshot.boundary;
|
|
1225
|
+
if (target === null || target.parentElement !== flow) {
|
|
1226
|
+
row.remove();
|
|
1227
|
+
state.row = null;
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
if (row.parentElement !== flow || row.nextElementSibling !== target) {
|
|
1231
|
+
target.before(row);
|
|
1232
|
+
this.revealVisualOnce(row);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
hasBodyCached(el) {
|
|
1236
|
+
const cached = this.bodyTextCache.get(el);
|
|
1237
|
+
if (cached !== void 0) return cached;
|
|
1238
|
+
const value = hasBodyContent(el);
|
|
1239
|
+
this.bodyTextCache.set(el, value);
|
|
1240
|
+
return value;
|
|
1241
|
+
}
|
|
1242
|
+
/** True when the recorded display was changed (or the ownership sentinel
|
|
1243
|
+
* wiped) behind the ledger's back; callers hand control back to reality. */
|
|
1244
|
+
displayForeign(el) {
|
|
1245
|
+
const written = this.writtenDisplay.get(el);
|
|
1246
|
+
if (written === void 0) return false;
|
|
1247
|
+
return el.style.getPropertyValue(DISPLAY_OWNED_PROP) === "" || el.style.display !== written;
|
|
1248
|
+
}
|
|
1249
|
+
releaseDisplayLedger(el) {
|
|
1250
|
+
this.originalDisplay.delete(el);
|
|
1251
|
+
this.writtenDisplay.delete(el);
|
|
1252
|
+
this.controlledDisplay.delete(el);
|
|
1253
|
+
el.style.removeProperty(DISPLAY_OWNED_PROP);
|
|
1254
|
+
}
|
|
1255
|
+
hideElement(el, desired) {
|
|
1256
|
+
desired.add(el);
|
|
1257
|
+
if (!this.originalDisplay.has(el) && !isDisplayed(el)) return;
|
|
1258
|
+
if (!this.originalDisplay.has(el) || this.displayForeign(el)) {
|
|
1259
|
+
this.originalDisplay.set(el, el.style.display);
|
|
1260
|
+
this.writtenDisplay.set(el, el.style.display);
|
|
1261
|
+
el.style.setProperty(DISPLAY_OWNED_PROP, "1");
|
|
1262
|
+
}
|
|
1263
|
+
this.controlledDisplay.add(el);
|
|
1264
|
+
if (el.style.display === "none") return;
|
|
1265
|
+
el.style.display = "none";
|
|
1266
|
+
this.writtenDisplay.set(el, "none");
|
|
1267
|
+
}
|
|
1268
|
+
restoreElement(el) {
|
|
1269
|
+
if (!this.originalDisplay.has(el)) return;
|
|
1270
|
+
if (this.displayForeign(el)) {
|
|
1271
|
+
this.releaseDisplayLedger(el);
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
const original = this.originalDisplay.get(el);
|
|
1275
|
+
if (el.style.display !== original) el.style.display = original;
|
|
1276
|
+
this.releaseDisplayLedger(el);
|
|
1277
|
+
}
|
|
1278
|
+
/** Whether WAAPI exists and the user has not asked for reduced motion. */
|
|
1279
|
+
canAnimate(el) {
|
|
1280
|
+
if (typeof el.animate !== "function") return false;
|
|
1281
|
+
try {
|
|
1282
|
+
if (typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches) return false;
|
|
1283
|
+
} catch {
|
|
1284
|
+
return false;
|
|
1285
|
+
}
|
|
1286
|
+
return true;
|
|
1287
|
+
}
|
|
1288
|
+
/** First-appearance flourish for the plugin-owned summary row: a pure
|
|
1289
|
+
* opacity fade. Paint-only, so it cannot shift anyone's layout. */
|
|
1290
|
+
revealVisualOnce(row) {
|
|
1291
|
+
if (row.dataset.dshssShown === "1") return;
|
|
1292
|
+
row.dataset.dshssShown = "1";
|
|
1293
|
+
if (!this.canAnimate(row)) return;
|
|
1294
|
+
row.animate([{ opacity: "0" }, { opacity: "1" }], {
|
|
1295
|
+
duration: FOLD_ROW_FADE_MS,
|
|
1296
|
+
easing: FOLD_EASE_OUT
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
1300
|
+
/** Visible chat flow container, falling back to the first one rendered. */
|
|
1301
|
+
function findFlow() {
|
|
1302
|
+
const flows = document.querySelectorAll("[data-chat-flow]");
|
|
1303
|
+
for (const flow of flows) if (flow.offsetParent !== null || flow.getBoundingClientRect().width > 0) return flow;
|
|
1304
|
+
return flows[0] ?? null;
|
|
1305
|
+
}
|
|
1306
|
+
/** parentNode walk that also accepts Text mutation targets. */
|
|
1307
|
+
function nodeWithin(node, ancestor) {
|
|
1308
|
+
for (let current = node; current !== null; current = current.parentNode) if (current === ancestor) return true;
|
|
1309
|
+
return false;
|
|
1310
|
+
}
|
|
1311
|
+
/** Top-level message order, excluding this plugin's own summary rows. */
|
|
1312
|
+
function flowItems(flow) {
|
|
1313
|
+
return [...flow.children].filter((el) => el instanceof HTMLElement && !el.classList.contains("dshss-processed"));
|
|
1314
|
+
}
|
|
1315
|
+
function isDisplayed(el) {
|
|
1316
|
+
if (typeof getComputedStyle === "function") return getComputedStyle(el).display !== "none";
|
|
1317
|
+
return el.style.display !== "none";
|
|
1318
|
+
}
|
|
1319
|
+
function stableElementKey(el, fallbackIndex) {
|
|
1320
|
+
const kind = el.getAttribute("data-chat-flow-kind") ?? "node";
|
|
1321
|
+
return `${kind}:${el.getAttribute("data-chat-flow-key") ?? el.getAttribute("data-chat-anchor-key") ?? `${kind}:${fallbackIndex}`}`;
|
|
1322
|
+
}
|
|
1323
|
+
/** Kinds that can open a history stretch lacking an explicit user marker. */
|
|
1324
|
+
function opensTurnWork(el) {
|
|
1325
|
+
const kind = el.getAttribute("data-chat-flow-kind");
|
|
1326
|
+
return kind === "assistant-step" || kind === "assistant" || kind === "tool-call" || kind === "command" || kind === "manual-compaction";
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Split the flow into turn segments. `user`/`steering` rows close the previous
|
|
1330
|
+
* segment and open the next; `turn-tail` closes and resets the opener. Leading
|
|
1331
|
+
* context before the first user row joins that user's segment, matching how
|
|
1332
|
+
* the host presents context injection.
|
|
1333
|
+
*/
|
|
1334
|
+
function buildSegments(flow, hasBody) {
|
|
1335
|
+
const items = flowItems(flow);
|
|
1336
|
+
const itemIndex = new Map(items.map((el, index) => [el, index]));
|
|
1337
|
+
const snapshots = [];
|
|
1338
|
+
let contentStart = 0;
|
|
1339
|
+
let startMarker = null;
|
|
1340
|
+
const append = (end, boundary, closed) => {
|
|
1341
|
+
if (end < contentStart) return;
|
|
1342
|
+
const range = items.slice(contentStart, end);
|
|
1343
|
+
const hideSeats = /* @__PURE__ */ new Set();
|
|
1344
|
+
const bodySteps = [];
|
|
1345
|
+
let running = false;
|
|
1346
|
+
for (const el of range) {
|
|
1347
|
+
const classified = classifyItem(el, hasBody);
|
|
1348
|
+
if (classified.fold === "body") bodySteps.push(el);
|
|
1349
|
+
else if (classified.fold === "seat") hideSeats.add(el);
|
|
1350
|
+
if (classified.running) running = true;
|
|
1351
|
+
}
|
|
1352
|
+
const finalStep = bodySteps.at(-1) ?? null;
|
|
1353
|
+
const middleSteps = new Set(bodySteps.slice(0, -1));
|
|
1354
|
+
for (const step of middleSteps) hideSeats.add(step);
|
|
1355
|
+
const finalThinkRows = finalStep === null ? [] : thinkRowsIn(finalStep);
|
|
1356
|
+
const firstWork = range.find((el) => hideSeats.has(el)) ?? finalStep;
|
|
1357
|
+
const identity = startMarker ?? range.find((el) => opensTurnWork(el)) ?? boundary;
|
|
1358
|
+
const identityIndex = identity === null ? contentStart : itemIndex.get(identity) ?? contentStart;
|
|
1359
|
+
const key = `${startMarker === null ? "leading" : "segment"}:${identity === null ? `open:${contentStart}` : stableElementKey(identity, identityIndex)}`;
|
|
1360
|
+
snapshots.push({
|
|
1361
|
+
key,
|
|
1362
|
+
boundary,
|
|
1363
|
+
startMarker,
|
|
1364
|
+
hideSeats,
|
|
1365
|
+
middleSteps,
|
|
1366
|
+
finalStep,
|
|
1367
|
+
finalThinkRows,
|
|
1368
|
+
firstWork,
|
|
1369
|
+
closed,
|
|
1370
|
+
running,
|
|
1371
|
+
hasWork: hideSeats.size > 0 || finalThinkRows.length > 0
|
|
1372
|
+
});
|
|
1373
|
+
};
|
|
1374
|
+
items.forEach((el, index) => {
|
|
1375
|
+
const kind = el.getAttribute("data-chat-flow-kind");
|
|
1376
|
+
if (kind === "user" || kind === "steering") {
|
|
1377
|
+
if (startMarker !== null) {
|
|
1378
|
+
append(index, el, true);
|
|
1379
|
+
contentStart = index + 1;
|
|
1380
|
+
} else if (items.slice(contentStart, index).some((el) => opensTurnWork(el))) {
|
|
1381
|
+
append(index, el, true);
|
|
1382
|
+
contentStart = index + 1;
|
|
1383
|
+
}
|
|
1384
|
+
startMarker = el;
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
if (kind === "turn-tail") {
|
|
1388
|
+
append(index, el, true);
|
|
1389
|
+
contentStart = index + 1;
|
|
1390
|
+
startMarker = null;
|
|
1391
|
+
}
|
|
1392
|
+
});
|
|
1393
|
+
if (contentStart < items.length) append(items.length, null, false);
|
|
1394
|
+
return snapshots;
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* Classify one top-level flow item against the Harness's own node vocabulary.
|
|
1398
|
+
* Reply-bearing assistant messages become `body` candidates (last one per
|
|
1399
|
+
* segment wins as the final answer); core work process — tool-call seats,
|
|
1400
|
+
* command/compaction cards, context injection, thinking-only messages —
|
|
1401
|
+
* becomes a hideable `seat`. Everything else is skipped untouched: boundaries,
|
|
1402
|
+
* empty decorations, and crucially any kind this module does not own, so rows
|
|
1403
|
+
* contributed by other plugins' slots are never folded or hidden.
|
|
1404
|
+
*/
|
|
1405
|
+
function classifyItem(el, hasBody) {
|
|
1406
|
+
const kind = el.getAttribute("data-chat-flow-kind");
|
|
1407
|
+
if (kind === "user" || kind === "steering" || kind === "turn-tail") return {
|
|
1408
|
+
fold: "skip",
|
|
1409
|
+
running: false
|
|
1410
|
+
};
|
|
1411
|
+
if (kind === "assistant-step" || kind === "assistant") {
|
|
1412
|
+
if (hasBody(el)) return {
|
|
1413
|
+
fold: "body",
|
|
1414
|
+
running: subtreeRunning(el)
|
|
1415
|
+
};
|
|
1416
|
+
if (thinkRowsIn(el).length > 0) return {
|
|
1417
|
+
fold: "seat",
|
|
1418
|
+
running: subtreeRunning(el)
|
|
1419
|
+
};
|
|
1420
|
+
return {
|
|
1421
|
+
fold: "skip",
|
|
1422
|
+
running: false
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
if (kind === "tool-call") {
|
|
1426
|
+
const rows = topCallRowsIn(el);
|
|
1427
|
+
if (rows.length === 0 || !rows.every((row) => hasNativeCardChrome(row))) return {
|
|
1428
|
+
fold: "skip",
|
|
1429
|
+
running: subtreeRunning(el)
|
|
1430
|
+
};
|
|
1431
|
+
return {
|
|
1432
|
+
fold: "seat",
|
|
1433
|
+
running: subtreeRunning(el)
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
if (kind === "context" || kind === "command" || kind === "manual-compaction") return {
|
|
1437
|
+
fold: "seat",
|
|
1438
|
+
running: subtreeRunning(el)
|
|
1439
|
+
};
|
|
1440
|
+
return {
|
|
1441
|
+
fold: "skip",
|
|
1442
|
+
running: false
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
/** Native work-card shape: every core card family stamps `data-variant`
|
|
1446
|
+
* together with `data-state`, whether composed through ToolRow or hand-rolled
|
|
1447
|
+
* (Bash). Custom toolviews use their own namespaced attributes instead. */
|
|
1448
|
+
function hasNativeCardChrome(row) {
|
|
1449
|
+
return row.querySelector("[data-variant][data-state]") !== null;
|
|
1450
|
+
}
|
|
1451
|
+
/** Whether anything inside the item reports a running lifecycle. Deliberately
|
|
1452
|
+
* broad: staying "running" too long only delays a collapse, collapsing early
|
|
1453
|
+
* would cut off live output. Native cards stamp `data-state` next to
|
|
1454
|
+
* `data-tool`/`data-variant`; custom views use their own vocabulary and are
|
|
1455
|
+
* intentionally invisible here. */
|
|
1456
|
+
function subtreeRunning(el) {
|
|
1457
|
+
if (el.getAttribute("data-state") === "running") return true;
|
|
1458
|
+
for (const root of el.querySelectorAll("[data-tool][data-state], [data-variant][data-state]")) if (root.getAttribute("data-state") === "running") return true;
|
|
1459
|
+
return false;
|
|
1460
|
+
}
|
|
1461
|
+
function hasVisibleSegmentWork(segment) {
|
|
1462
|
+
const candidates = [...segment.hideSeats, ...segment.middleSteps];
|
|
1463
|
+
if (segment.startMarker !== null) candidates.push(segment.startMarker);
|
|
1464
|
+
if (segment.finalStep !== null) candidates.push(segment.finalStep);
|
|
1465
|
+
return candidates.some(isDisplayed);
|
|
1466
|
+
}
|
|
1467
|
+
/** Top-level tool card rows of a seat (excluding sub-dispatch nests). */
|
|
1468
|
+
function topCallRowsIn(el) {
|
|
1469
|
+
const rows = [];
|
|
1470
|
+
for (const row of el.querySelectorAll("[data-chat-call-id]")) {
|
|
1471
|
+
if (row.closest("[data-subcalls]") !== null) continue;
|
|
1472
|
+
if (row.closest("[data-chat-call-id]") !== row) continue;
|
|
1473
|
+
rows.push(row);
|
|
1474
|
+
}
|
|
1475
|
+
return rows;
|
|
1476
|
+
}
|
|
1477
|
+
/** Reasoning rows of a message: native think variants outside tool cards. */
|
|
1478
|
+
function thinkRowsIn(el) {
|
|
1479
|
+
const rows = [];
|
|
1480
|
+
for (const row of el.querySelectorAll("[data-variant=\"think\"]:not([data-tool])")) {
|
|
1481
|
+
if (row.closest("[data-chat-call-id]") !== null) continue;
|
|
1482
|
+
if (row.closest("[data-subcalls]") !== null) continue;
|
|
1483
|
+
rows.push(row);
|
|
1484
|
+
}
|
|
1485
|
+
return rows;
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Reply detection beyond reasoning rows and tool cards: any non-empty text or
|
|
1489
|
+
* media counts, because Markdown paragraphs necessarily carry text nodes.
|
|
1490
|
+
* Rendered markdown class names are build-time hashes, so structure-based
|
|
1491
|
+
* detection is the only stable contract.
|
|
1492
|
+
*/
|
|
1493
|
+
function hasBodyContent(el) {
|
|
1494
|
+
const kind = el.getAttribute("data-chat-flow-kind");
|
|
1495
|
+
if (kind === "command" || kind === "manual-compaction") return false;
|
|
1496
|
+
if (hasBodyText(el)) return true;
|
|
1497
|
+
const excluded = "[data-variant=\"think\"], [data-chat-call-id]";
|
|
1498
|
+
for (const media of el.querySelectorAll("img, video, audio, canvas")) if (media.closest(excluded) === null) return true;
|
|
1499
|
+
return false;
|
|
1500
|
+
}
|
|
1501
|
+
function hasBodyText(el) {
|
|
1502
|
+
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
1503
|
+
let node;
|
|
1504
|
+
while ((node = walker.nextNode()) !== null) {
|
|
1505
|
+
if (node.data.trim() === "") continue;
|
|
1506
|
+
const parent = node.parentElement;
|
|
1507
|
+
if (parent !== null && parent.closest("[data-variant=\"think\"], [data-chat-call-id], [data-variant=\"others\"][data-state]") !== null) continue;
|
|
1508
|
+
return true;
|
|
1509
|
+
}
|
|
1510
|
+
return false;
|
|
1511
|
+
}
|
|
1512
|
+
/**
|
|
1513
|
+
* Official turn duration from the closing element: localized tails read
|
|
1514
|
+
* `用时 33秒` / `用时 2分05秒` or `Ran for 33s` / `Ran for 2m 05s`; newer
|
|
1515
|
+
* tails only carry an end timestamp, which is diffed against the opening user
|
|
1516
|
+
* row's timestamp.
|
|
1517
|
+
*/
|
|
1518
|
+
function parseTurnDuration(boundary) {
|
|
1519
|
+
const text = boundary.textContent ?? "";
|
|
1520
|
+
const zh = text.match(/用时\s*(\d+)分(\d+)秒|用时\s*(\d+)秒/);
|
|
1521
|
+
if (zh !== null) {
|
|
1522
|
+
if (zh[1] !== void 0 && zh[2] !== void 0) return Number(zh[1]) * 6e4 + Number(zh[2]) * 1e3;
|
|
1523
|
+
if (zh[3] !== void 0) return Number(zh[3]) * 1e3;
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
const en = text.match(/Ran for\s*(?:(\d+)m\s*)?(\d+)s/);
|
|
1527
|
+
if (en !== null) return ((en[1] === void 0 ? 0 : Number(en[1])) * 60 + Number(en[2])) * 1e3;
|
|
1528
|
+
const end = parseTimeText(text);
|
|
1529
|
+
const start = findTurnStart(boundary);
|
|
1530
|
+
if (end !== void 0 && start !== void 0 && end > start) return end - start;
|
|
1531
|
+
}
|
|
1532
|
+
/** DSH timestamp text (`8月14日 21:56` / `2026年8月14日 22:11`) → epoch ms. */
|
|
1533
|
+
function parseTimeText(text) {
|
|
1534
|
+
const m = text.match(/(?:(\d{4})年)?(\d{1,2})月(\d{1,2})日\s*(\d{1,2}):(\d{2})/);
|
|
1535
|
+
if (m === null) return void 0;
|
|
1536
|
+
const year = m[1] !== void 0 ? Number(m[1]) : (/* @__PURE__ */ new Date()).getFullYear();
|
|
1537
|
+
const t = new Date(year, Number(m[2]) - 1, Number(m[3]), Number(m[4]), Number(m[5])).getTime();
|
|
1538
|
+
return Number.isNaN(t) ? void 0 : t;
|
|
1539
|
+
}
|
|
1540
|
+
/** Latest turn-opening timestamp at or before the boundary element. */
|
|
1541
|
+
function findTurnStart(boundary) {
|
|
1542
|
+
const flow = boundary.parentElement;
|
|
1543
|
+
if (flow === null) return void 0;
|
|
1544
|
+
let best = null;
|
|
1545
|
+
for (const s of flow.querySelectorAll("[class*=\"timeStart\"]")) {
|
|
1546
|
+
const pos = s.compareDocumentPosition(boundary);
|
|
1547
|
+
if ((pos & Node.DOCUMENT_POSITION_FOLLOWING) !== 0 || (pos & Node.DOCUMENT_POSITION_CONTAINED_BY) !== 0 || s === boundary) best = s;
|
|
1548
|
+
else break;
|
|
1549
|
+
}
|
|
1550
|
+
if (best === null) return void 0;
|
|
1551
|
+
return parseTimeText(best.textContent ?? "");
|
|
1552
|
+
}
|
|
1553
|
+
/** Compact duration pieces shared by both locales: seconds under a minute,
|
|
1554
|
+
* whole minutes drop the seconds, hours drop to minute granularity. */
|
|
1555
|
+
function durationParts(ms) {
|
|
1556
|
+
const s = Math.round(ms / 1e3);
|
|
1557
|
+
return {
|
|
1558
|
+
h: Math.floor(s / 3600),
|
|
1559
|
+
m: Math.floor(s % 3600 / 60),
|
|
1560
|
+
s: s % 60
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
/** 毫秒 → 中文紧凑时长(14秒 / 2分05秒 / 15分 / 3小时2分)。 */
|
|
1564
|
+
function formatZhDuration(ms) {
|
|
1565
|
+
const { h, m, s } = durationParts(ms);
|
|
1566
|
+
if (h > 0) return m > 0 ? `${h}小时${m}分` : `${h}小时`;
|
|
1567
|
+
if (s < 60 && m === 0 && h === 0) return `${s}秒`;
|
|
1568
|
+
if (s === 0) return `${m}分`;
|
|
1569
|
+
return `${m}分${String(s).padStart(2, "0")}秒`;
|
|
1570
|
+
}
|
|
1571
|
+
/** Milliseconds → compact English duration (14s / 2m05s / 15m / 3h2m). */
|
|
1572
|
+
function formatEnDuration(ms) {
|
|
1573
|
+
const { h, m, s } = durationParts(ms);
|
|
1574
|
+
if (h > 0) return m > 0 ? `${h}h${m}m` : `${h}h`;
|
|
1575
|
+
if (m === 0 && h === 0) return `${s}s`;
|
|
1576
|
+
if (s === 0) return `${m}m`;
|
|
1577
|
+
return `${m}m${String(s).padStart(2, "0")}s`;
|
|
1578
|
+
}
|
|
1579
|
+
/** Native disclosure chevron (IconChevronDownOutline14, 14x14). */
|
|
1580
|
+
const CHEVRON_PATH = "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z";
|
|
1581
|
+
function createChevronIcon(className) {
|
|
1582
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1583
|
+
svg.setAttribute("width", "14");
|
|
1584
|
+
svg.setAttribute("height", "14");
|
|
1585
|
+
svg.setAttribute("class", className);
|
|
1586
|
+
svg.setAttribute("viewBox", "0 0 14 14");
|
|
1587
|
+
svg.setAttribute("fill", "none");
|
|
1588
|
+
svg.setAttribute("aria-hidden", "true");
|
|
1589
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1590
|
+
path.setAttribute("d", CHEVRON_PATH);
|
|
1591
|
+
path.setAttribute("fill", "currentColor");
|
|
1592
|
+
svg.appendChild(path);
|
|
1593
|
+
return svg;
|
|
1594
|
+
}
|
|
1595
|
+
function createProcessedRowElement(duration) {
|
|
1596
|
+
const copy = resolveFoldCopy();
|
|
1597
|
+
const btn = document.createElement("button");
|
|
1598
|
+
btn.type = "button";
|
|
1599
|
+
btn.className = PROCESSED_CLASS;
|
|
1600
|
+
btn.setAttribute("aria-expanded", "false");
|
|
1601
|
+
const text = document.createElement("span");
|
|
1602
|
+
text.textContent = duration === void 0 ? copy.processedLabel : `${copy.processedLabel} ${copy.formatDuration(duration)}`;
|
|
1603
|
+
btn.append(text, createChevronIcon(`${PROCESSED_CLASS}-chevron`));
|
|
1604
|
+
btn.title = copy.expandTitle;
|
|
1605
|
+
return btn;
|
|
1606
|
+
}
|
|
1607
|
+
function injectStyle() {
|
|
1608
|
+
if (document.getElementById(STYLE_ID) !== null) return;
|
|
1609
|
+
const style = document.createElement("style");
|
|
1610
|
+
style.id = STYLE_ID;
|
|
1611
|
+
style.textContent = FOLD_CSS;
|
|
1612
|
+
document.head.appendChild(style);
|
|
1613
|
+
}
|
|
1614
|
+
function removeStyle() {
|
|
1615
|
+
document.getElementById(STYLE_ID)?.remove();
|
|
1616
|
+
}
|
|
1617
|
+
//#endregion
|
|
522
1618
|
//#region src/client/teleprompterGlide.ts
|
|
523
1619
|
/**
|
|
524
1620
|
* Conversation-port follow while an assistant reply streams.
|
|
@@ -554,8 +1650,6 @@ window.__ModuleLoader__.load({
|
|
|
554
1650
|
const FOLLOW_OWNED_ATTR = "data-follow-owned";
|
|
555
1651
|
/** Sub-pixel settle threshold; clearing below this cannot produce a visible rebound. */
|
|
556
1652
|
const FOLLOW_SETTLE_EPSILON_PX = .25;
|
|
557
|
-
/** Lowest reveal rate retained while the spring is short on paint room. */
|
|
558
|
-
const FOLLOW_REVEAL_MIN_SCALE = .55;
|
|
559
1653
|
/** Safe-lag occupancy band over which reveal pressure is progressively reduced. */
|
|
560
1654
|
const FOLLOW_BACKPRESSURE_START_RATIO = .25;
|
|
561
1655
|
const GESTURE_EVENTS = [
|
|
@@ -582,14 +1676,15 @@ window.__ModuleLoader__.load({
|
|
|
582
1676
|
* a constrained paint lands at the minimum immediately so the next reveal
|
|
583
1677
|
* commit cannot keep feeding an already-full visual buffer.
|
|
584
1678
|
*/
|
|
585
|
-
function computeFollowRevealScale(lagPx, capacityPx, constrained = false) {
|
|
586
|
-
if (constrained) return
|
|
1679
|
+
function computeFollowRevealScale(lagPx, capacityPx, constrained = false, tuning = DEFAULT_STREAM_DEBUG_TUNING) {
|
|
1680
|
+
if (constrained) return tuning.backpressureMinScale;
|
|
587
1681
|
if (!Number.isFinite(capacityPx)) return 1;
|
|
588
|
-
if (capacityPx <= 0) return lagPx > 0 ?
|
|
1682
|
+
if (capacityPx <= 0) return lagPx > 0 ? tuning.backpressureMinScale : 1;
|
|
589
1683
|
const ratio = Math.min(1, Math.max(0, lagPx / capacityPx));
|
|
590
|
-
if (ratio >= .75) return
|
|
1684
|
+
if (ratio >= .75) return tuning.backpressureMinScale;
|
|
591
1685
|
const progress = Math.min(1, Math.max(0, (ratio - FOLLOW_BACKPRESSURE_START_RATIO) / .5));
|
|
592
|
-
|
|
1686
|
+
const eased = progress * progress * (3 - 2 * progress);
|
|
1687
|
+
return 1 - (1 - tuning.backpressureMinScale) * eased;
|
|
593
1688
|
}
|
|
594
1689
|
/**
|
|
595
1690
|
* Semi-implicit spring integration with four substeps per <=32ms slice.
|
|
@@ -597,7 +1692,7 @@ window.__ModuleLoader__.load({
|
|
|
597
1692
|
* @param input - Current visible lag and carried physics velocity.
|
|
598
1693
|
* @returns The position advance, its fraction, and next velocity.
|
|
599
1694
|
*/
|
|
600
|
-
function computeFollowStep(dtMs, input) {
|
|
1695
|
+
function computeFollowStep(dtMs, input, tuning = DEFAULT_STREAM_DEBUG_TUNING) {
|
|
601
1696
|
if (input.lag <= .1 || dtMs <= 0) return {
|
|
602
1697
|
advancePx: 0,
|
|
603
1698
|
lerpStep: 0,
|
|
@@ -609,7 +1704,7 @@ window.__ModuleLoader__.load({
|
|
|
609
1704
|
const slices = Math.max(1, Math.ceil(elapsedMs / 32));
|
|
610
1705
|
const subDt = elapsedMs / 1e3 / slices / 4;
|
|
611
1706
|
for (let slice = 0; slice < slices; slice += 1) for (let substep = 0; substep < 4; substep += 1) {
|
|
612
|
-
const acceleration = (
|
|
1707
|
+
const acceleration = (tuning.springStiffness * lag - tuning.springDamping * velocity) / tuning.springMass;
|
|
613
1708
|
velocity = Math.max(0, velocity + acceleration * subDt);
|
|
614
1709
|
const advance = velocity * subDt;
|
|
615
1710
|
if (advance >= lag) return {
|
|
@@ -631,10 +1726,11 @@ window.__ModuleLoader__.load({
|
|
|
631
1726
|
return port.querySelector("[data-chat-transcript]") ?? port.querySelector("[data-chat-flow]");
|
|
632
1727
|
}
|
|
633
1728
|
/** Outermost message surfaces; nested tool rows ride their parent. */
|
|
1729
|
+
/** @internal Test seam: the equal-lag transform set for one scrollport. */
|
|
634
1730
|
function shiftSurfacesOf(port) {
|
|
635
1731
|
const transcript = port.querySelector("[data-chat-transcript]");
|
|
636
1732
|
if (transcript !== null) return [transcript];
|
|
637
|
-
return [...port.querySelectorAll(
|
|
1733
|
+
return [...port.querySelectorAll(`[data-chat-anchor-key], .${PROCESSED_CLASS}`)].filter((row) => row.parentElement?.closest("[data-chat-anchor-key]") === null);
|
|
638
1734
|
}
|
|
639
1735
|
function currentShiftOf(element) {
|
|
640
1736
|
return Number(/translate3d\(0(?:px)?,\s*(-?[\d.]+)px,\s*0(?:px)?\)/.exec(element.style.transform)?.[1] ?? 0);
|
|
@@ -667,7 +1763,15 @@ window.__ModuleLoader__.load({
|
|
|
667
1763
|
}
|
|
668
1764
|
return height;
|
|
669
1765
|
}
|
|
670
|
-
|
|
1766
|
+
/**
|
|
1767
|
+
* The plugin can be reinjected without replacing the conversation DOM. Keep
|
|
1768
|
+
* runway ownership in the page realm so a fresh bundle adopts the existing
|
|
1769
|
+
* margin instead of treating it as host layout and adding another 48px.
|
|
1770
|
+
*/
|
|
1771
|
+
const FOLLOW_RUNWAYS_SYMBOL = Symbol.for("dsh-smooth-stream.follow-runways");
|
|
1772
|
+
const followRunwayRegistry = globalThis;
|
|
1773
|
+
const followRunways = followRunwayRegistry[FOLLOW_RUNWAYS_SYMBOL] ?? /* @__PURE__ */ new WeakMap();
|
|
1774
|
+
followRunwayRegistry[FOLLOW_RUNWAYS_SYMBOL] = followRunways;
|
|
671
1775
|
const followPaintLimits = /* @__PURE__ */ new WeakMap();
|
|
672
1776
|
function invalidatePaintLimit(port) {
|
|
673
1777
|
followPaintLimits.delete(port);
|
|
@@ -681,9 +1785,39 @@ window.__ModuleLoader__.load({
|
|
|
681
1785
|
followRunways.delete(port);
|
|
682
1786
|
invalidatePaintLimit(port);
|
|
683
1787
|
}
|
|
684
|
-
function
|
|
1788
|
+
function isLegacyRunway(value) {
|
|
1789
|
+
if (value === "") return false;
|
|
1790
|
+
const terms = [...value.matchAll(/([\d.]+)px/g)];
|
|
1791
|
+
if (terms.length === 0 || value.replaceAll(/calc|px|[\d.+()\s]/g, "") !== "") return false;
|
|
1792
|
+
return terms.every(([, raw]) => {
|
|
1793
|
+
const px = Number(raw);
|
|
1794
|
+
return Number.isFinite(px) && px >= 48 && Math.abs(px % 48) <= Number.EPSILON;
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
/** Remove unowned runway residue written by v0.3.3 and earlier bundles. */
|
|
1798
|
+
function migrateLegacyRunway(port, surfaces, status, composer) {
|
|
1799
|
+
if (followRunways.has(port)) return;
|
|
1800
|
+
let migrated = false;
|
|
1801
|
+
if (status !== null && isLegacyRunway(status.style.marginTop)) {
|
|
1802
|
+
status.style.marginTop = "";
|
|
1803
|
+
migrated = true;
|
|
1804
|
+
}
|
|
1805
|
+
const last = surfaces.at(-1);
|
|
1806
|
+
if (status === null && composer !== null && last !== void 0 && isLegacyRunway(last.style.marginBottom)) {
|
|
1807
|
+
last.style.marginBottom = "";
|
|
1808
|
+
migrated = true;
|
|
1809
|
+
}
|
|
1810
|
+
if (migrated) invalidatePaintLimit(port);
|
|
1811
|
+
}
|
|
1812
|
+
function ensureRunway(port, surfaces, runwayPx = 48) {
|
|
685
1813
|
const status = turnStatusOf(port);
|
|
686
1814
|
const composer = port.querySelector("[data-composer-seat]");
|
|
1815
|
+
migrateLegacyRunway(port, surfaces, status, composer);
|
|
1816
|
+
const naturalHeight = Math.max(0, port.scrollHeight - runwayOffsetOf(port));
|
|
1817
|
+
if (runwayPx <= 0 || port.clientHeight <= 0 || naturalHeight <= port.clientHeight) {
|
|
1818
|
+
restoreRunway(port);
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
687
1821
|
const target = status === null ? {
|
|
688
1822
|
element: composer === null ? void 0 : surfaces.at(-1),
|
|
689
1823
|
property: "marginBottom"
|
|
@@ -697,17 +1831,18 @@ window.__ModuleLoader__.load({
|
|
|
697
1831
|
}
|
|
698
1832
|
const element = target.element;
|
|
699
1833
|
const current = followRunways.get(port);
|
|
700
|
-
if (current?.element === element && current.property === target.property) return;
|
|
1834
|
+
if (current?.element === element && current.property === target.property && current.requestedPx === runwayPx) return;
|
|
701
1835
|
restoreRunway(port);
|
|
702
1836
|
const beforeHeight = port.scrollHeight;
|
|
703
1837
|
const original = element.style[target.property];
|
|
704
|
-
element.style[target.property] = original === "" ? `
|
|
1838
|
+
element.style[target.property] = original === "" ? `${runwayPx}px` : `calc(${original} + ${runwayPx}px)`;
|
|
705
1839
|
const offset = Math.max(0, port.scrollHeight - beforeHeight);
|
|
706
1840
|
followRunways.set(port, {
|
|
707
1841
|
element,
|
|
708
1842
|
offset,
|
|
709
1843
|
property: target.property,
|
|
710
|
-
original
|
|
1844
|
+
original,
|
|
1845
|
+
requestedPx: runwayPx
|
|
711
1846
|
});
|
|
712
1847
|
invalidatePaintLimit(port);
|
|
713
1848
|
}
|
|
@@ -770,9 +1905,9 @@ window.__ModuleLoader__.load({
|
|
|
770
1905
|
* same frame. Carrying that excess in `scrollTop` would move the transcript
|
|
771
1906
|
* toward fixed chrome and also make the host expose jump-to-bottom.
|
|
772
1907
|
*/
|
|
773
|
-
function applyVisual(port, animatedH, reservePx, velocityPxPerSec = 0) {
|
|
1908
|
+
function applyVisual(port, animatedH, reservePx, velocityPxPerSec = 0, runwayPx = 48) {
|
|
774
1909
|
const surfaces = shiftSurfacesOf(port);
|
|
775
|
-
ensureRunway(port, surfaces);
|
|
1910
|
+
ensureRunway(port, surfaces, runwayPx);
|
|
776
1911
|
const contentHeight = Math.max(0, port.scrollHeight);
|
|
777
1912
|
const runwayOffset = runwayOffsetOf(port);
|
|
778
1913
|
const targetHeight = Math.max(0, contentHeight - runwayOffset);
|
|
@@ -918,9 +2053,10 @@ window.__ModuleLoader__.load({
|
|
|
918
2053
|
};
|
|
919
2054
|
const updateRevealScale = (next, elapsedMs, urgent = false) => {
|
|
920
2055
|
if (revealScaleRef === void 0) return;
|
|
2056
|
+
const tuning = debugRuntime.activeTuning();
|
|
921
2057
|
const state = followMotionStates.get(next);
|
|
922
|
-
const target = state === void 0 ? 1 : computeFollowRevealScale(state.lagPx, state.capacityPx, state.constrained);
|
|
923
|
-
const current = Math.min(1, Math.max(
|
|
2058
|
+
const target = state === void 0 ? 1 : computeFollowRevealScale(state.lagPx, state.capacityPx, state.constrained, tuning);
|
|
2059
|
+
const current = Math.min(1, Math.max(tuning.backpressureMinScale, revealScaleRef.current));
|
|
924
2060
|
if (target < current || urgent) {
|
|
925
2061
|
revealScaleRef.current = Math.min(current, target);
|
|
926
2062
|
return;
|
|
@@ -931,6 +2067,22 @@ window.__ModuleLoader__.load({
|
|
|
931
2067
|
const releaseRevealScale = () => {
|
|
932
2068
|
if (revealScaleRef !== void 0) revealScaleRef.current = 1;
|
|
933
2069
|
};
|
|
2070
|
+
const reportFollow = (next, isActive) => {
|
|
2071
|
+
const state = followMotionStates.get(next);
|
|
2072
|
+
debugRuntime.reportFollow(next, {
|
|
2073
|
+
lagPx: state?.lagPx ?? Math.max(0, next.scrollHeight - next.clientHeight - next.scrollTop),
|
|
2074
|
+
velocityPxPerSec: state?.velocityPxPerSec ?? 0,
|
|
2075
|
+
reservePx: state?.reservePx ?? 0,
|
|
2076
|
+
capacityPx: state?.capacityPx ?? 0,
|
|
2077
|
+
revealScale: revealScaleRef?.current ?? 1,
|
|
2078
|
+
following,
|
|
2079
|
+
constrained: state?.constrained ?? false,
|
|
2080
|
+
scrollTop: next.scrollTop,
|
|
2081
|
+
scrollHeight: next.scrollHeight,
|
|
2082
|
+
clientHeight: next.clientHeight,
|
|
2083
|
+
active: isActive
|
|
2084
|
+
});
|
|
2085
|
+
};
|
|
934
2086
|
const isLeader = (next) => followLeaders.get(next)?.owner === owner;
|
|
935
2087
|
const hold = (next) => {
|
|
936
2088
|
followActivityAt.set(next, performance.now());
|
|
@@ -948,11 +2100,14 @@ window.__ModuleLoader__.load({
|
|
|
948
2100
|
clearMotion(next);
|
|
949
2101
|
followMotionStates.delete(next);
|
|
950
2102
|
releaseRevealScale();
|
|
2103
|
+
debugRuntime.reportFollow(next, null);
|
|
951
2104
|
}
|
|
952
2105
|
};
|
|
953
2106
|
const handBackVisual = (next) => {
|
|
954
2107
|
const shift = currentShiftOf(shiftSurfacesOf(next).at(-1) ?? next);
|
|
955
2108
|
const visualTop = Math.max(0, next.scrollTop - Math.max(0, shift));
|
|
2109
|
+
clearMotion(next);
|
|
2110
|
+
restoreRunway(next);
|
|
956
2111
|
const floor = Math.max(0, next.scrollHeight - next.clientHeight);
|
|
957
2112
|
next.scrollTop = Math.min(visualTop, Math.max(0, floor - 26));
|
|
958
2113
|
followScrollLedgers.set(next, next.scrollTop);
|
|
@@ -980,8 +2135,10 @@ window.__ModuleLoader__.load({
|
|
|
980
2135
|
const restoreBeforePaint = () => {
|
|
981
2136
|
if (!following || port === null || !isLeader(port)) return;
|
|
982
2137
|
invalidatePaintLimit(port);
|
|
983
|
-
|
|
2138
|
+
const tuning = debugRuntime.activeTuning();
|
|
2139
|
+
animatedH = applyVisual(port, animatedH, reservePx, velocityPxPerSec, tuning.runwayPx);
|
|
984
2140
|
updateRevealScale(port, 0, true);
|
|
2141
|
+
reportFollow(port, activeRef.current);
|
|
985
2142
|
};
|
|
986
2143
|
const bindPort = (next) => {
|
|
987
2144
|
if (port === next) return;
|
|
@@ -1003,6 +2160,7 @@ window.__ModuleLoader__.load({
|
|
|
1003
2160
|
rafId = requestAnimationFrame(frame);
|
|
1004
2161
|
const elapsedMs = Math.max(.001, now - last);
|
|
1005
2162
|
const dt = Math.min(32, elapsedMs);
|
|
2163
|
+
const tuning = debugRuntime.activeTuning();
|
|
1006
2164
|
last = now;
|
|
1007
2165
|
const root = rootRef.current;
|
|
1008
2166
|
if (root === null) return;
|
|
@@ -1029,8 +2187,9 @@ window.__ModuleLoader__.load({
|
|
|
1029
2187
|
if (following) {
|
|
1030
2188
|
hold(nextPort);
|
|
1031
2189
|
if (isLeader(nextPort)) {
|
|
1032
|
-
animatedH = applyVisual(nextPort, animatedH, reservePx, velocityPxPerSec);
|
|
2190
|
+
animatedH = applyVisual(nextPort, animatedH, reservePx, velocityPxPerSec, tuning.runwayPx);
|
|
1033
2191
|
updateRevealScale(nextPort, elapsedMs);
|
|
2192
|
+
reportFollow(nextPort, activeRef.current);
|
|
1034
2193
|
const runwayOffset = runwayOffsetOf(nextPort);
|
|
1035
2194
|
if (Math.max(0, nextPort.scrollHeight - animatedH - runwayOffset) <= .25) finishEntrance();
|
|
1036
2195
|
} else finishEntrance();
|
|
@@ -1038,7 +2197,7 @@ window.__ModuleLoader__.load({
|
|
|
1038
2197
|
primed = true;
|
|
1039
2198
|
return;
|
|
1040
2199
|
}
|
|
1041
|
-
if (!following && !interacting && reportedLag <= (readerReleased ? 1 : 25)) {
|
|
2200
|
+
if (!following && (!interacting || readerReleased && !readerGestureIntent && reportedLag <= 1) && reportedLag <= (readerReleased ? 1 : 25)) {
|
|
1042
2201
|
following = true;
|
|
1043
2202
|
readerReleased = false;
|
|
1044
2203
|
animatedH = extent;
|
|
@@ -1059,6 +2218,7 @@ window.__ModuleLoader__.load({
|
|
|
1059
2218
|
}
|
|
1060
2219
|
if (!activeRef.current || !following) {
|
|
1061
2220
|
followScrollLedgers.set(nextPort, nextPort.scrollTop);
|
|
2221
|
+
reportFollow(nextPort, activeRef.current);
|
|
1062
2222
|
return;
|
|
1063
2223
|
}
|
|
1064
2224
|
hold(nextPort);
|
|
@@ -1070,14 +2230,14 @@ window.__ModuleLoader__.load({
|
|
|
1070
2230
|
const contentHeight = nextPort.scrollHeight;
|
|
1071
2231
|
const lag = Math.max(0, contentHeight - animatedH - runwayOffset);
|
|
1072
2232
|
const predictGrowth = predictiveRef?.current ?? predictive;
|
|
1073
|
-
const reserveTarget = !predictGrowth ? 0 : computeFollowReserve(speedCpsRef.current,
|
|
1074
|
-
const reserveStep = 1 - Math.exp(-elapsedMs /
|
|
2233
|
+
const reserveTarget = !predictGrowth ? 0 : computeFollowReserve(speedCpsRef.current, tuning.runwayPx);
|
|
2234
|
+
const reserveStep = 1 - Math.exp(-elapsedMs / tuning.reserveResponseMs);
|
|
1075
2235
|
reservePx += (reserveTarget - reservePx) * reserveStep;
|
|
1076
2236
|
const step = computeFollowStep(dt, {
|
|
1077
2237
|
lag,
|
|
1078
2238
|
speedEma: speedCpsRef.current,
|
|
1079
2239
|
velocityPxPerSec
|
|
1080
|
-
});
|
|
2240
|
+
}, tuning);
|
|
1081
2241
|
if (lag <= .1) {
|
|
1082
2242
|
animatedH = contentHeight - runwayOffset;
|
|
1083
2243
|
velocityPxPerSec = 0;
|
|
@@ -1086,8 +2246,9 @@ window.__ModuleLoader__.load({
|
|
|
1086
2246
|
animatedH = Math.min(contentHeight - runwayOffset - minimumLag, animatedH + step.advancePx);
|
|
1087
2247
|
velocityPxPerSec = step.velocityPxPerSec;
|
|
1088
2248
|
}
|
|
1089
|
-
animatedH = applyVisual(nextPort, animatedH, reservePx, velocityPxPerSec);
|
|
2249
|
+
animatedH = applyVisual(nextPort, animatedH, reservePx, velocityPxPerSec, tuning.runwayPx);
|
|
1090
2250
|
updateRevealScale(nextPort, elapsedMs);
|
|
2251
|
+
reportFollow(nextPort, true);
|
|
1091
2252
|
if (Math.max(0, nextPort.scrollHeight - animatedH - runwayOffsetOf(nextPort)) <= .25) finishEntrance();
|
|
1092
2253
|
};
|
|
1093
2254
|
frame(performance.now());
|
|
@@ -1105,6 +2266,7 @@ window.__ModuleLoader__.load({
|
|
|
1105
2266
|
clearVisual(host);
|
|
1106
2267
|
followLeaders.delete(host);
|
|
1107
2268
|
releaseRevealScale();
|
|
2269
|
+
debugRuntime.reportFollow(host, null);
|
|
1108
2270
|
return;
|
|
1109
2271
|
}
|
|
1110
2272
|
if (preserveReader) {
|
|
@@ -1112,19 +2274,23 @@ window.__ModuleLoader__.load({
|
|
|
1112
2274
|
clearVisual(host);
|
|
1113
2275
|
followLeaders.delete(host);
|
|
1114
2276
|
releaseRevealScale();
|
|
2277
|
+
debugRuntime.reportFollow(host, null);
|
|
1115
2278
|
return;
|
|
1116
2279
|
}
|
|
1117
|
-
|
|
2280
|
+
const completionTuning = debugRuntime.activeTuning();
|
|
2281
|
+
ensureRunway(host, shiftSurfacesOf(host), completionTuning.runwayPx);
|
|
1118
2282
|
const completionRunway = runwayOffsetOf(host);
|
|
1119
2283
|
const completionMinimumLag = Math.max(0, reservePx);
|
|
1120
2284
|
animatedH = Math.min(animatedH, host.scrollHeight - completionRunway - completionMinimumLag);
|
|
1121
2285
|
settleAtFloor(host);
|
|
1122
|
-
animatedH = applyVisual(host, animatedH, reservePx, velocityPxPerSec);
|
|
2286
|
+
animatedH = applyVisual(host, animatedH, reservePx, velocityPxPerSec, completionTuning.runwayPx);
|
|
2287
|
+
reportFollow(host, false);
|
|
1123
2288
|
const runwayOffset = runwayOffsetOf(host);
|
|
1124
2289
|
if (Math.max(0, host.scrollHeight - animatedH - runwayOffset) <= .25 && reservePx <= .25) {
|
|
1125
2290
|
finishAtNaturalFloor(host);
|
|
1126
2291
|
followLeaders.delete(host);
|
|
1127
2292
|
releaseRevealScale();
|
|
2293
|
+
debugRuntime.reportFollow(host, null);
|
|
1128
2294
|
return;
|
|
1129
2295
|
}
|
|
1130
2296
|
for (const name of GESTURE_EVENTS) host.addEventListener(name, markGesture, { passive: true });
|
|
@@ -1147,14 +2313,16 @@ window.__ModuleLoader__.load({
|
|
|
1147
2313
|
clearVisual(host);
|
|
1148
2314
|
followLeaders.delete(host);
|
|
1149
2315
|
releaseRevealScale();
|
|
2316
|
+
debugRuntime.reportFollow(host, null);
|
|
1150
2317
|
stopSettleListeners();
|
|
1151
2318
|
return;
|
|
1152
2319
|
}
|
|
1153
2320
|
const dt = Math.min(32, Math.max(0, now - settleLast));
|
|
2321
|
+
const tuning = debugRuntime.activeTuning();
|
|
1154
2322
|
settleLast = now;
|
|
1155
2323
|
const runwayOffset = runwayOffsetOf(host);
|
|
1156
2324
|
const lag = Math.max(0, host.scrollHeight - animatedH - runwayOffset);
|
|
1157
|
-
const reserveStep = 1 - Math.exp(-dt /
|
|
2325
|
+
const reserveStep = 1 - Math.exp(-dt / tuning.reserveResponseMs);
|
|
1158
2326
|
reservePx += (0 - reservePx) * reserveStep;
|
|
1159
2327
|
if (lag <= .25 && reservePx <= .25) {
|
|
1160
2328
|
animatedH = host.scrollHeight - runwayOffset;
|
|
@@ -1163,6 +2331,7 @@ window.__ModuleLoader__.load({
|
|
|
1163
2331
|
finishAtNaturalFloor(host);
|
|
1164
2332
|
followLeaders.delete(host);
|
|
1165
2333
|
releaseRevealScale();
|
|
2334
|
+
debugRuntime.reportFollow(host, null);
|
|
1166
2335
|
stopSettleListeners();
|
|
1167
2336
|
return;
|
|
1168
2337
|
}
|
|
@@ -1170,12 +2339,13 @@ window.__ModuleLoader__.load({
|
|
|
1170
2339
|
lag,
|
|
1171
2340
|
speedEma: speedCpsRef.current,
|
|
1172
2341
|
velocityPxPerSec
|
|
1173
|
-
});
|
|
2342
|
+
}, tuning);
|
|
1174
2343
|
const minimumLag = Math.max(0, reservePx);
|
|
1175
2344
|
animatedH = Math.min(host.scrollHeight - runwayOffset - minimumLag, animatedH + step.advancePx);
|
|
1176
2345
|
velocityPxPerSec = step.velocityPxPerSec;
|
|
1177
2346
|
settleAtFloor(host);
|
|
1178
|
-
animatedH = applyVisual(host, animatedH, reservePx, velocityPxPerSec);
|
|
2347
|
+
animatedH = applyVisual(host, animatedH, reservePx, velocityPxPerSec, tuning.runwayPx);
|
|
2348
|
+
reportFollow(host, false);
|
|
1179
2349
|
requestAnimationFrame(settleFrame);
|
|
1180
2350
|
};
|
|
1181
2351
|
requestAnimationFrame(settleFrame);
|
|
@@ -1240,10 +2410,6 @@ window.__ModuleLoader__.load({
|
|
|
1240
2410
|
*/
|
|
1241
2411
|
const STREAM_BOOT_GLOBAL = "__DSH_SMOOTH_STREAM_CONFIG__";
|
|
1242
2412
|
//#endregion
|
|
1243
|
-
//#region src/settings.ts
|
|
1244
|
-
/** Defaults shared by the Host schema and the client-side fallback. */
|
|
1245
|
-
const DEFAULT_STREAM_SETTINGS = { thinkAutoExpand: true };
|
|
1246
|
-
//#endregion
|
|
1247
2413
|
//#region src/client/TypewriterAssistantNodeView.tsx
|
|
1248
2414
|
function usePrefersReducedMotion() {
|
|
1249
2415
|
const [reduced, setReduced] = (0, react.useState)(() => typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true);
|
|
@@ -1258,6 +2424,8 @@ window.__ModuleLoader__.load({
|
|
|
1258
2424
|
}
|
|
1259
2425
|
/** Conservative fallback before the streaming Markdown tail has geometry. */
|
|
1260
2426
|
const PREDICTIVE_WRAP_FALLBACK_CHARS = 32;
|
|
2427
|
+
const STREAM_ANNOUNCEMENT_INTERVAL_MS = 800;
|
|
2428
|
+
const STREAM_ANNOUNCEMENT_MAX_CHARS = 320;
|
|
1261
2429
|
function approximateInlineWidth(text, emPx) {
|
|
1262
2430
|
let width = 0;
|
|
1263
2431
|
for (const char of text) if (/\s/u.test(char)) width += emPx * .33;
|
|
@@ -1265,36 +2433,189 @@ window.__ModuleLoader__.load({
|
|
|
1265
2433
|
else width += emPx;
|
|
1266
2434
|
return width;
|
|
1267
2435
|
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
2436
|
+
function measurePendingTextGeometry(root, visibleText) {
|
|
2437
|
+
if (typeof document.createTreeWalker !== "function" || typeof NodeFilter === "undefined") return {
|
|
2438
|
+
root,
|
|
2439
|
+
visibleText,
|
|
2440
|
+
fontSize: 14,
|
|
2441
|
+
wrapThresholdWidth: null
|
|
2442
|
+
};
|
|
1274
2443
|
const rootRect = root.getBoundingClientRect();
|
|
1275
2444
|
const rootWidth = Math.max(0, rootRect.width, rootRect.right - rootRect.left, root.clientWidth);
|
|
1276
|
-
if (rootWidth <= 0) return
|
|
2445
|
+
if (rootWidth <= 0) return {
|
|
2446
|
+
root,
|
|
2447
|
+
visibleText,
|
|
2448
|
+
fontSize: 14,
|
|
2449
|
+
wrapThresholdWidth: null
|
|
2450
|
+
};
|
|
1277
2451
|
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
1278
2452
|
let tail = null;
|
|
1279
2453
|
for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) if ((node.textContent ?? "").length > 0) tail = node;
|
|
1280
2454
|
const parent = tail?.parentElement ?? root;
|
|
1281
2455
|
const fontSize = Number.parseFloat(getComputedStyle(parent).fontSize) || 14;
|
|
1282
|
-
|
|
1283
|
-
|
|
2456
|
+
if (tail === null || typeof document.createRange !== "function") return {
|
|
2457
|
+
root,
|
|
2458
|
+
visibleText,
|
|
2459
|
+
fontSize,
|
|
2460
|
+
wrapThresholdWidth: rootWidth
|
|
2461
|
+
};
|
|
1284
2462
|
try {
|
|
1285
2463
|
const length = tail.textContent?.length ?? 0;
|
|
1286
|
-
if (length <= 0) return
|
|
2464
|
+
if (length <= 0) return {
|
|
2465
|
+
root,
|
|
2466
|
+
visibleText,
|
|
2467
|
+
fontSize,
|
|
2468
|
+
wrapThresholdWidth: rootWidth
|
|
2469
|
+
};
|
|
1287
2470
|
const range = document.createRange();
|
|
1288
2471
|
range.setStart(tail, Math.max(0, length - 1));
|
|
1289
2472
|
range.setEnd(tail, length);
|
|
1290
2473
|
const tailRect = range.getBoundingClientRect();
|
|
1291
2474
|
const contentRight = rootRect.right;
|
|
1292
|
-
if (!Number.isFinite(tailRect.right) || tailRect.right <= rootRect.left || contentRight <= rootRect.left) return
|
|
1293
|
-
|
|
2475
|
+
if (!Number.isFinite(tailRect.right) || tailRect.right <= rootRect.left || contentRight <= rootRect.left) return {
|
|
2476
|
+
root,
|
|
2477
|
+
visibleText,
|
|
2478
|
+
fontSize,
|
|
2479
|
+
wrapThresholdWidth: rootWidth
|
|
2480
|
+
};
|
|
2481
|
+
return {
|
|
2482
|
+
root,
|
|
2483
|
+
visibleText,
|
|
2484
|
+
fontSize,
|
|
2485
|
+
wrapThresholdWidth: Math.max(0, contentRight - tailRect.right) + fontSize * .35
|
|
2486
|
+
};
|
|
1294
2487
|
} catch {
|
|
1295
|
-
return
|
|
2488
|
+
return {
|
|
2489
|
+
root,
|
|
2490
|
+
visibleText,
|
|
2491
|
+
fontSize,
|
|
2492
|
+
wrapThresholdWidth: rootWidth
|
|
2493
|
+
};
|
|
1296
2494
|
}
|
|
1297
2495
|
}
|
|
2496
|
+
/** Whether buffered source can reach a new visual line before it drains. */
|
|
2497
|
+
function pendingTextCanGrow(root, visibleText, pending, geometryRef) {
|
|
2498
|
+
if (pending === "") return false;
|
|
2499
|
+
if (/[\r\n]/u.test(pending)) return true;
|
|
2500
|
+
const pendingChars = [...pending];
|
|
2501
|
+
if (root === null) return pendingChars.length >= PREDICTIVE_WRAP_FALLBACK_CHARS;
|
|
2502
|
+
let geometry = geometryRef.current;
|
|
2503
|
+
if (geometry?.root !== root || geometry.visibleText !== visibleText) {
|
|
2504
|
+
geometry = measurePendingTextGeometry(root, visibleText);
|
|
2505
|
+
geometryRef.current = geometry;
|
|
2506
|
+
}
|
|
2507
|
+
if (geometry.wrapThresholdWidth === null) return pendingChars.length >= PREDICTIVE_WRAP_FALLBACK_CHARS;
|
|
2508
|
+
return approximateInlineWidth(pending, geometry.fontSize) >= geometry.wrapThresholdWidth;
|
|
2509
|
+
}
|
|
2510
|
+
function announcementChunkEnd(source, start) {
|
|
2511
|
+
const hardEnd = Math.min(source.length, start + STREAM_ANNOUNCEMENT_MAX_CHARS);
|
|
2512
|
+
if (hardEnd === source.length) return hardEnd;
|
|
2513
|
+
const softStart = start + Math.floor(STREAM_ANNOUNCEMENT_MAX_CHARS * .6);
|
|
2514
|
+
for (let index = hardEnd - 1; index >= softStart; index -= 1) if (/[\s.,;:!?]/u.test(source[index] ?? "")) return index + 1;
|
|
2515
|
+
return hardEnd;
|
|
2516
|
+
}
|
|
2517
|
+
/** A commit-driven live region isolated from the visible Markdown subtree. */
|
|
2518
|
+
const StreamAnnouncement = (0, react.memo)(function StreamAnnouncement({ text, active }) {
|
|
2519
|
+
const [announcement, setAnnouncement] = (0, react.useState)({
|
|
2520
|
+
text: "",
|
|
2521
|
+
revision: 0,
|
|
2522
|
+
present: active
|
|
2523
|
+
});
|
|
2524
|
+
const sourceRef = (0, react.useRef)(text);
|
|
2525
|
+
const activeRef = (0, react.useRef)(active);
|
|
2526
|
+
const announcedOffsetRef = (0, react.useRef)(active ? 0 : text.length);
|
|
2527
|
+
const drainSourceRef = (0, react.useRef)(null);
|
|
2528
|
+
const timerRef = (0, react.useRef)(null);
|
|
2529
|
+
(0, react.useEffect)(() => {
|
|
2530
|
+
const clearTimer = () => {
|
|
2531
|
+
if (timerRef.current === null) return;
|
|
2532
|
+
clearTimeout(timerRef.current);
|
|
2533
|
+
timerRef.current = null;
|
|
2534
|
+
};
|
|
2535
|
+
const publishNext = (source) => {
|
|
2536
|
+
const start = Math.min(announcedOffsetRef.current, source.length);
|
|
2537
|
+
const end = announcementChunkEnd(source, start);
|
|
2538
|
+
if (end <= start) return false;
|
|
2539
|
+
announcedOffsetRef.current = end;
|
|
2540
|
+
setAnnouncement((previous) => ({
|
|
2541
|
+
text: source.slice(start, end),
|
|
2542
|
+
revision: previous.revision + 1,
|
|
2543
|
+
present: true
|
|
2544
|
+
}));
|
|
2545
|
+
return end < source.length;
|
|
2546
|
+
};
|
|
2547
|
+
const hideAfterLinger = () => {
|
|
2548
|
+
timerRef.current = setTimeout(() => {
|
|
2549
|
+
timerRef.current = null;
|
|
2550
|
+
if (activeRef.current) return;
|
|
2551
|
+
drainSourceRef.current = null;
|
|
2552
|
+
setAnnouncement((previous) => ({
|
|
2553
|
+
...previous,
|
|
2554
|
+
present: false
|
|
2555
|
+
}));
|
|
2556
|
+
}, STREAM_ANNOUNCEMENT_INTERVAL_MS);
|
|
2557
|
+
};
|
|
2558
|
+
const drainNext = () => {
|
|
2559
|
+
const source = drainSourceRef.current;
|
|
2560
|
+
if (source === null) return;
|
|
2561
|
+
if (!publishNext(source)) {
|
|
2562
|
+
hideAfterLinger();
|
|
2563
|
+
return;
|
|
2564
|
+
}
|
|
2565
|
+
timerRef.current = setTimeout(() => {
|
|
2566
|
+
timerRef.current = null;
|
|
2567
|
+
if (activeRef.current) return;
|
|
2568
|
+
drainNext();
|
|
2569
|
+
}, STREAM_ANNOUNCEMENT_INTERVAL_MS);
|
|
2570
|
+
};
|
|
2571
|
+
const scheduleLive = () => {
|
|
2572
|
+
if (timerRef.current !== null || announcedOffsetRef.current >= sourceRef.current.length) return;
|
|
2573
|
+
timerRef.current = setTimeout(() => {
|
|
2574
|
+
timerRef.current = null;
|
|
2575
|
+
if (!activeRef.current) return;
|
|
2576
|
+
const source = sourceRef.current;
|
|
2577
|
+
if (publishNext(source)) scheduleLive();
|
|
2578
|
+
}, STREAM_ANNOUNCEMENT_INTERVAL_MS);
|
|
2579
|
+
};
|
|
2580
|
+
const wasActive = activeRef.current;
|
|
2581
|
+
const previousSource = sourceRef.current;
|
|
2582
|
+
if (!(!active && !wasActive && drainSourceRef.current !== null) && (!text.startsWith(previousSource) || announcedOffsetRef.current > text.length)) announcedOffsetRef.current = 0;
|
|
2583
|
+
sourceRef.current = text;
|
|
2584
|
+
activeRef.current = active;
|
|
2585
|
+
if (active) {
|
|
2586
|
+
if (!wasActive) {
|
|
2587
|
+
clearTimer();
|
|
2588
|
+
drainSourceRef.current = null;
|
|
2589
|
+
setAnnouncement((previous) => ({
|
|
2590
|
+
text: "",
|
|
2591
|
+
revision: previous.revision + 1,
|
|
2592
|
+
present: true
|
|
2593
|
+
}));
|
|
2594
|
+
}
|
|
2595
|
+
scheduleLive();
|
|
2596
|
+
return;
|
|
2597
|
+
}
|
|
2598
|
+
if (!wasActive) {
|
|
2599
|
+
if (drainSourceRef.current === null) announcedOffsetRef.current = text.length;
|
|
2600
|
+
return;
|
|
2601
|
+
}
|
|
2602
|
+
clearTimer();
|
|
2603
|
+
drainSourceRef.current = text;
|
|
2604
|
+
drainNext();
|
|
2605
|
+
}, [active, text]);
|
|
2606
|
+
(0, react.useEffect)(() => () => {
|
|
2607
|
+
if (timerRef.current !== null) clearTimeout(timerRef.current);
|
|
2608
|
+
timerRef.current = null;
|
|
2609
|
+
}, []);
|
|
2610
|
+
if (!active && !announcement.present) return null;
|
|
2611
|
+
const activating = active && !activeRef.current;
|
|
2612
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2613
|
+
className: TypewriterAssistantNodeView_module_css_default.visuallyHidden,
|
|
2614
|
+
"aria-live": "polite",
|
|
2615
|
+
"aria-atomic": "true",
|
|
2616
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: activating ? "" : announcement.text }, announcement.revision)
|
|
2617
|
+
});
|
|
2618
|
+
});
|
|
1298
2619
|
/**
|
|
1299
2620
|
* Smooth streaming text arm. While the reply runs, the accumulated source is
|
|
1300
2621
|
* revealed through the smoother at a rate that tracks the model's arrival
|
|
@@ -1306,13 +2627,14 @@ window.__ModuleLoader__.load({
|
|
|
1306
2627
|
* drains, the settled full parse (KaTeX math, fence highlighting, file
|
|
1307
2628
|
* mentions) swaps in exactly once.
|
|
1308
2629
|
*/
|
|
1309
|
-
function AnimatedMarkdownText({ text, codeLabels, fileMentions, streaming,
|
|
2630
|
+
function AnimatedMarkdownText({ text, codeLabels, fileMentions, streaming, ownFollow, followSpeedCpsRef, followRevealScaleRef, onPredictiveChange, preset, shouldHoldBack }) {
|
|
1310
2631
|
const reduced = usePrefersReducedMotion();
|
|
1311
2632
|
const [typing, setTyping] = (0, react.useState)(streaming);
|
|
1312
2633
|
const localSpeedCpsRef = (0, react.useRef)(35);
|
|
1313
2634
|
const followRootRef = (0, react.useRef)(null);
|
|
1314
2635
|
const predictionSourceRef = (0, react.useRef)(null);
|
|
1315
2636
|
const predictionStateRef = (0, react.useRef)(false);
|
|
2637
|
+
const predictionGeometryRef = (0, react.useRef)(null);
|
|
1316
2638
|
const speedCpsRef = followSpeedCpsRef ?? localSpeedCpsRef;
|
|
1317
2639
|
const displayed = useSmoothStreamContent(text, {
|
|
1318
2640
|
enabled: typing && !reduced,
|
|
@@ -1324,11 +2646,22 @@ window.__ModuleLoader__.load({
|
|
|
1324
2646
|
});
|
|
1325
2647
|
const shown = reduced ? text : displayed;
|
|
1326
2648
|
const live = typing && !reduced;
|
|
2649
|
+
(0, react.useEffect)(() => {
|
|
2650
|
+
const root = followRootRef.current;
|
|
2651
|
+
if (root === null || typeof ResizeObserver === "undefined") return;
|
|
2652
|
+
const observer = new ResizeObserver(() => {
|
|
2653
|
+
if (predictionGeometryRef.current?.root === root) predictionGeometryRef.current = null;
|
|
2654
|
+
});
|
|
2655
|
+
observer.observe(root);
|
|
2656
|
+
return () => {
|
|
2657
|
+
observer.disconnect();
|
|
2658
|
+
};
|
|
2659
|
+
}, []);
|
|
1327
2660
|
(0, react.useLayoutEffect)(() => {
|
|
1328
2661
|
if (onPredictiveChange === void 0) return;
|
|
1329
2662
|
const pending = text.slice(shown.length);
|
|
1330
2663
|
const sourceChanged = predictionSourceRef.current !== text;
|
|
1331
|
-
const next = !live || !streaming || pending === "" ? false : sourceChanged ? pendingTextCanGrow(followRootRef.current, pending) : predictionStateRef.current;
|
|
2664
|
+
const next = !live || !streaming || pending === "" ? false : sourceChanged ? pendingTextCanGrow(followRootRef.current, shown, pending, predictionGeometryRef) : predictionStateRef.current;
|
|
1332
2665
|
predictionSourceRef.current = text;
|
|
1333
2666
|
predictionStateRef.current = next;
|
|
1334
2667
|
onPredictiveChange(next);
|
|
@@ -1347,11 +2680,7 @@ window.__ModuleLoader__.load({
|
|
|
1347
2680
|
text,
|
|
1348
2681
|
typing
|
|
1349
2682
|
]);
|
|
1350
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1351
|
-
className: TypewriterAssistantNodeView_module_css_default.visuallyHidden,
|
|
1352
|
-
"aria-live": "polite",
|
|
1353
|
-
children: text
|
|
1354
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FollowHost, {
|
|
2683
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FollowHost, {
|
|
1355
2684
|
active: live && ownFollow,
|
|
1356
2685
|
speedCpsRef,
|
|
1357
2686
|
revealScaleRef: followRevealScaleRef,
|
|
@@ -1363,7 +2692,7 @@ window.__ModuleLoader__.load({
|
|
|
1363
2692
|
codeLabels,
|
|
1364
2693
|
fileMentions: live ? void 0 : fileMentions
|
|
1365
2694
|
})
|
|
1366
|
-
})
|
|
2695
|
+
});
|
|
1367
2696
|
}
|
|
1368
2697
|
function imageLabels(t) {
|
|
1369
2698
|
return {
|
|
@@ -1396,7 +2725,7 @@ window.__ModuleLoader__.load({
|
|
|
1396
2725
|
* or the assistant node settling — not when the rest of the reply is
|
|
1397
2726
|
* still streaming.
|
|
1398
2727
|
*/
|
|
1399
|
-
function AnimatedReasoning({ text, running, preset, thinkAutoExpand, shouldHoldBack, followSpeedCpsRef, followRevealScaleRef,
|
|
2728
|
+
function AnimatedReasoning({ text, running, preset, thinkAutoExpand, shouldHoldBack, followSpeedCpsRef, followRevealScaleRef, t }) {
|
|
1400
2729
|
const reduced = usePrefersReducedMotion();
|
|
1401
2730
|
const [expanded, setExpanded] = (0, react.useState)(running && thinkAutoExpand);
|
|
1402
2731
|
const summaryRef = (0, react.useRef)(null);
|
|
@@ -1412,9 +2741,6 @@ window.__ModuleLoader__.load({
|
|
|
1412
2741
|
(0, react.useLayoutEffect)(() => {
|
|
1413
2742
|
if (thinkAutoExpand) setExpanded(running);
|
|
1414
2743
|
}, [running, thinkAutoExpand]);
|
|
1415
|
-
(0, react.useLayoutEffect)(() => {
|
|
1416
|
-
onExpandedChange?.(expanded);
|
|
1417
|
-
}, [expanded, onExpandedChange]);
|
|
1418
2744
|
(0, react.useEffect)(() => {
|
|
1419
2745
|
const element = summaryRef.current;
|
|
1420
2746
|
if (element === null) return;
|
|
@@ -1479,13 +2805,10 @@ window.__ModuleLoader__.load({
|
|
|
1479
2805
|
const rootPredictiveRef = (0, react.useRef)(false);
|
|
1480
2806
|
const previousReasoningTailRef = (0, react.useRef)(-1);
|
|
1481
2807
|
if (reasoningTailIndex !== previousReasoningTailRef.current) {
|
|
1482
|
-
rootPredictiveRef.current =
|
|
2808
|
+
rootPredictiveRef.current = false;
|
|
1483
2809
|
if (!reasoningOwnsSpeed) rootSpeedRef.current = 35;
|
|
1484
2810
|
previousReasoningTailRef.current = reasoningTailIndex;
|
|
1485
2811
|
}
|
|
1486
|
-
const updateReasoningExpanded = (0, react.useMemo)(() => (expanded) => {
|
|
1487
|
-
rootPredictiveRef.current = expanded;
|
|
1488
|
-
}, []);
|
|
1489
2812
|
const updateTextPrediction = (0, react.useMemo)(() => (predictive) => {
|
|
1490
2813
|
rootPredictiveRef.current = predictive;
|
|
1491
2814
|
}, []);
|
|
@@ -1514,6 +2837,7 @@ window.__ModuleLoader__.load({
|
|
|
1514
2837
|
throw new Error(t("image.serviceUnavailable"));
|
|
1515
2838
|
});
|
|
1516
2839
|
if (!(streaming || data.status === "interrupted" || data.blocks.some((block) => block.kind !== "tool-call"))) return null;
|
|
2840
|
+
const announcementText = data.blocks.filter((block) => block.kind === "text").map((block) => block.text).join("\n");
|
|
1517
2841
|
const rendered = [];
|
|
1518
2842
|
const last = data.blocks.length - 1;
|
|
1519
2843
|
let lastFollow = -1;
|
|
@@ -1531,7 +2855,6 @@ window.__ModuleLoader__.load({
|
|
|
1531
2855
|
codeLabels,
|
|
1532
2856
|
fileMentions: mentions,
|
|
1533
2857
|
streaming,
|
|
1534
|
-
announce: index === last,
|
|
1535
2858
|
ownFollow: !streaming && index === lastFollow,
|
|
1536
2859
|
followSpeedCpsRef: index === lastFollow ? rootSpeedRef : void 0,
|
|
1537
2860
|
followRevealScaleRef: index === lastFollow ? rootRevealScaleRef : void 0,
|
|
@@ -1549,7 +2872,6 @@ window.__ModuleLoader__.load({
|
|
|
1549
2872
|
shouldHoldBack,
|
|
1550
2873
|
followSpeedCpsRef: reasoningOwnsSpeed && index === last ? rootSpeedRef : void 0,
|
|
1551
2874
|
followRevealScaleRef: reasoningOwnsSpeed && index === last ? rootRevealScaleRef : void 0,
|
|
1552
|
-
onExpandedChange: index === reasoningTailIndex ? updateReasoningExpanded : void 0,
|
|
1553
2875
|
t
|
|
1554
2876
|
}, index));
|
|
1555
2877
|
break;
|
|
@@ -1578,11 +2900,14 @@ window.__ModuleLoader__.load({
|
|
|
1578
2900
|
}, index));
|
|
1579
2901
|
}
|
|
1580
2902
|
}
|
|
1581
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
2903
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1582
2904
|
ref: guardRef,
|
|
1583
2905
|
className: TypewriterAssistantNodeView_module_css_default.root,
|
|
1584
2906
|
"data-streaming": streaming || void 0,
|
|
1585
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
2907
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StreamAnnouncement, {
|
|
2908
|
+
text: announcementText,
|
|
2909
|
+
active: streaming && !reduced
|
|
2910
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FollowHost, {
|
|
1586
2911
|
active: streaming && !reduced,
|
|
1587
2912
|
speedCpsRef: rootSpeedRef,
|
|
1588
2913
|
revealScaleRef: rootRevealScaleRef,
|
|
@@ -1594,7 +2919,7 @@ window.__ModuleLoader__.load({
|
|
|
1594
2919
|
children: t("message.stopped")
|
|
1595
2920
|
})]
|
|
1596
2921
|
})
|
|
1597
|
-
})
|
|
2922
|
+
})]
|
|
1598
2923
|
});
|
|
1599
2924
|
});
|
|
1600
2925
|
//#endregion
|
|
@@ -1608,6 +2933,8 @@ window.__ModuleLoader__.load({
|
|
|
1608
2933
|
* while the row belongs to the live Agent turn. No clip, mask, overlay, or
|
|
1609
2934
|
* duplicate accessibility tree is introduced.
|
|
1610
2935
|
*/
|
|
2936
|
+
/** Last presented text per root, retained across follow lifecycle flips. */
|
|
2937
|
+
const ledgerByRoot = /* @__PURE__ */ new WeakMap();
|
|
1611
2938
|
const SKIP_TEXT_SELECTOR = [
|
|
1612
2939
|
"[aria-hidden=\"true\"]",
|
|
1613
2940
|
"[aria-live]",
|
|
@@ -1635,10 +2962,56 @@ window.__ModuleLoader__.load({
|
|
|
1635
2962
|
*/
|
|
1636
2963
|
function useProgressiveDomText(rootRef, enabled, revealInitial, speedCpsRef, onSettled) {
|
|
1637
2964
|
(0, react.useLayoutEffect)(() => {
|
|
1638
|
-
if (!enabled) return;
|
|
1639
2965
|
const root = rootRef.current;
|
|
1640
2966
|
if (root === null || typeof document === "undefined") return;
|
|
1641
|
-
|
|
2967
|
+
let records = ledgerByRoot.get(root);
|
|
2968
|
+
if (!enabled && records === void 0) return;
|
|
2969
|
+
if (records === void 0) {
|
|
2970
|
+
records = /* @__PURE__ */ new Map();
|
|
2971
|
+
ledgerByRoot.set(root, records);
|
|
2972
|
+
}
|
|
2973
|
+
const forEachText = (from, callback) => {
|
|
2974
|
+
if (from.nodeType === Node.TEXT_NODE) {
|
|
2975
|
+
callback(from);
|
|
2976
|
+
return;
|
|
2977
|
+
}
|
|
2978
|
+
const walker = document.createTreeWalker(from, NodeFilter.SHOW_TEXT);
|
|
2979
|
+
let current = walker.nextNode();
|
|
2980
|
+
while (current !== null) {
|
|
2981
|
+
callback(current);
|
|
2982
|
+
current = walker.nextNode();
|
|
2983
|
+
}
|
|
2984
|
+
};
|
|
2985
|
+
const settle = (text) => {
|
|
2986
|
+
if (!revealable(text, root)) return;
|
|
2987
|
+
const chars = [...text.data];
|
|
2988
|
+
records.set(text, {
|
|
2989
|
+
chars,
|
|
2990
|
+
full: text.data,
|
|
2991
|
+
shown: chars.length
|
|
2992
|
+
});
|
|
2993
|
+
};
|
|
2994
|
+
const snapshotVisible = () => {
|
|
2995
|
+
const current = /* @__PURE__ */ new Set();
|
|
2996
|
+
forEachText(root, (text) => {
|
|
2997
|
+
if (!revealable(text, root)) return;
|
|
2998
|
+
current.add(text);
|
|
2999
|
+
settle(text);
|
|
3000
|
+
});
|
|
3001
|
+
for (const text of records.keys()) if (!current.has(text)) records.delete(text);
|
|
3002
|
+
};
|
|
3003
|
+
if (!enabled) {
|
|
3004
|
+
snapshotVisible();
|
|
3005
|
+
const observer = typeof MutationObserver === "undefined" ? null : new MutationObserver(snapshotVisible);
|
|
3006
|
+
observer?.observe(root, {
|
|
3007
|
+
childList: true,
|
|
3008
|
+
characterData: true,
|
|
3009
|
+
subtree: true
|
|
3010
|
+
});
|
|
3011
|
+
return () => {
|
|
3012
|
+
observer?.disconnect();
|
|
3013
|
+
};
|
|
3014
|
+
}
|
|
1642
3015
|
const pending = /* @__PURE__ */ new Set();
|
|
1643
3016
|
const internalWrites = /* @__PURE__ */ new WeakMap();
|
|
1644
3017
|
let rafId = 0;
|
|
@@ -1646,10 +3019,12 @@ window.__ModuleLoader__.load({
|
|
|
1646
3019
|
let debt = 0;
|
|
1647
3020
|
let stopped = false;
|
|
1648
3021
|
let announcedSettled = false;
|
|
3022
|
+
const streamId = `dom-${Math.random().toString(36).slice(2)}`;
|
|
1649
3023
|
const announceSettled = () => {
|
|
1650
3024
|
lastFrame = null;
|
|
1651
3025
|
debt = 0;
|
|
1652
3026
|
speedCpsRef.current = 35;
|
|
3027
|
+
debugRuntime.reportStream(streamId, null);
|
|
1653
3028
|
if (announcedSettled) return;
|
|
1654
3029
|
announcedSettled = true;
|
|
1655
3030
|
onSettled?.();
|
|
@@ -1676,18 +3051,21 @@ window.__ModuleLoader__.load({
|
|
|
1676
3051
|
write(node, chars.slice(0, preserved).join(""));
|
|
1677
3052
|
};
|
|
1678
3053
|
const visit = (from, reveal) => {
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
if (reveal)
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
3054
|
+
forEachText(from, (text) => {
|
|
3055
|
+
if (!revealable(text, root)) return;
|
|
3056
|
+
if (reveal) {
|
|
3057
|
+
enqueue(text, text.data, records.get(text));
|
|
3058
|
+
return;
|
|
3059
|
+
}
|
|
3060
|
+
settle(text);
|
|
3061
|
+
});
|
|
3062
|
+
};
|
|
3063
|
+
const forget = (from) => {
|
|
3064
|
+
forEachText(from, (text) => {
|
|
3065
|
+
if (root.contains(text)) return;
|
|
3066
|
+
records.delete(text);
|
|
3067
|
+
pending.delete(text);
|
|
3068
|
+
});
|
|
1691
3069
|
};
|
|
1692
3070
|
const scheduleFrame = () => {
|
|
1693
3071
|
if (stopped || pending.size === 0 || rafId !== 0) return;
|
|
@@ -1713,7 +3091,7 @@ window.__ModuleLoader__.load({
|
|
|
1713
3091
|
const record = records.get(node);
|
|
1714
3092
|
if (record !== void 0) backlog += record.chars.length - record.shown;
|
|
1715
3093
|
}
|
|
1716
|
-
const step = computeAdaptiveQueueStep(backlog, elapsed, debt);
|
|
3094
|
+
const step = computeAdaptiveQueueStep(backlog, elapsed, debt, 1, debugRuntime.activeTuning());
|
|
1717
3095
|
debt = step.debt;
|
|
1718
3096
|
speedCpsRef.current = step.speedCps;
|
|
1719
3097
|
let remaining = step.revealChars;
|
|
@@ -1722,6 +3100,7 @@ window.__ModuleLoader__.load({
|
|
|
1722
3100
|
const record = records.get(node);
|
|
1723
3101
|
if (record === void 0 || !node.isConnected) {
|
|
1724
3102
|
pending.delete(node);
|
|
3103
|
+
records.delete(node);
|
|
1725
3104
|
continue;
|
|
1726
3105
|
}
|
|
1727
3106
|
const amount = Math.min(remaining, record.chars.length - record.shown);
|
|
@@ -1735,10 +3114,30 @@ window.__ModuleLoader__.load({
|
|
|
1735
3114
|
remaining -= amount;
|
|
1736
3115
|
if (shown >= next.chars.length) pending.delete(node);
|
|
1737
3116
|
}
|
|
3117
|
+
let targetChars = 0;
|
|
3118
|
+
let displayedChars = 0;
|
|
3119
|
+
let nextBacklog = 0;
|
|
3120
|
+
for (const [node, record] of records) {
|
|
3121
|
+
if (!node.isConnected) {
|
|
3122
|
+
records.delete(node);
|
|
3123
|
+
pending.delete(node);
|
|
3124
|
+
continue;
|
|
3125
|
+
}
|
|
3126
|
+
targetChars += record.chars.length;
|
|
3127
|
+
displayedChars += record.shown;
|
|
3128
|
+
if (pending.has(node)) nextBacklog += record.chars.length - record.shown;
|
|
3129
|
+
}
|
|
3130
|
+
debugRuntime.reportStream(streamId, {
|
|
3131
|
+
backlog: nextBacklog,
|
|
3132
|
+
speedCps: step.speedCps,
|
|
3133
|
+
targetChars,
|
|
3134
|
+
displayedChars,
|
|
3135
|
+
active: pending.size > 0
|
|
3136
|
+
});
|
|
1738
3137
|
if (pending.size === 0) announceSettled();
|
|
1739
3138
|
else scheduleFrame();
|
|
1740
3139
|
};
|
|
1741
|
-
|
|
3140
|
+
visit(root, revealInitial);
|
|
1742
3141
|
const observer = typeof MutationObserver === "undefined" ? null : new MutationObserver((mutations) => {
|
|
1743
3142
|
for (const mutation of mutations) {
|
|
1744
3143
|
if (mutation.type === "characterData") {
|
|
@@ -1750,6 +3149,7 @@ window.__ModuleLoader__.load({
|
|
|
1750
3149
|
enqueue(node, node.data, records.get(node));
|
|
1751
3150
|
continue;
|
|
1752
3151
|
}
|
|
3152
|
+
for (const removed of mutation.removedNodes) forget(removed);
|
|
1753
3153
|
for (const added of mutation.addedNodes) visit(added, true);
|
|
1754
3154
|
}
|
|
1755
3155
|
if (pending.size === 0) announceSettled();
|
|
@@ -1772,6 +3172,7 @@ window.__ModuleLoader__.load({
|
|
|
1772
3172
|
}
|
|
1773
3173
|
pending.clear();
|
|
1774
3174
|
speedCpsRef.current = 35;
|
|
3175
|
+
debugRuntime.reportStream(streamId, null);
|
|
1775
3176
|
};
|
|
1776
3177
|
}, [
|
|
1777
3178
|
enabled,
|
|
@@ -1954,50 +3355,51 @@ window.__ModuleLoader__.load({
|
|
|
1954
3355
|
}
|
|
1955
3356
|
//#endregion
|
|
1956
3357
|
//#region \0dsh-css:/Users/dzlin/work/project/dsh-smooth-stream/src/client/SmoothStreamCard.module.css.mjs
|
|
1957
|
-
const css = ".XNGeRG_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;list-style:none;transition:border-color .16s,background .16s;display:flex}.XNGeRG_card:hover{border-color:var(--dsw-alias-label-dimmed)}.XNGeRG_cardOpen{border-color:var(--dsw-alias-label-dimmed);background:var(--dsw-alias-bg-layer-2)}.XNGeRG_header{appearance:none;cursor:pointer;text-align:left;width:100%;font:inherit;color:inherit;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.XNGeRG_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.XNGeRG_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.XNGeRG_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.XNGeRG_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.XNGeRG_version{text-overflow:ellipsis;white-space:nowrap;max-width:16rem;color:var(--dsw-alias-label-tertiary);flex:none;font-size:12px;line-height:1.5;overflow:hidden}.XNGeRG_pending{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.XNGeRG_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.XNGeRG_chevronOpen{transform:rotate(180deg)}.XNGeRG_body{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;margin:0 16px;padding-bottom:8px;display:flex}.XNGeRG_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.XNGeRG_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.XNGeRG_fieldHead{justify-content:space-between;align-items:center;gap:8px;display:flex}.XNGeRG_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.XNGeRG_toggle{width:16px;height:16px;accent-color:var(--dsw-alias-brand-primary);cursor:pointer}.XNGeRG_toggle:disabled{cursor:default}.XNGeRG_toggle:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.XNGeRG_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}.XNGeRG_failure{justify-content:space-between;align-items:center;gap:12px;padding:12px 0;display:flex}.XNGeRG_updateRow{border-top:1px solid var(--dsw-alias-border-l2);justify-content:space-between;align-items:center;gap:12px;padding:12px 0;display:flex}.XNGeRG_updateCopy{flex-direction:column;gap:4px;min-width:0;display:flex}.XNGeRG_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.XNGeRG_failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}.XNGeRG_save,.XNGeRG_discard,.XNGeRG_update{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.XNGeRG_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.XNGeRG_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.XNGeRG_discard:hover:not(:disabled),.XNGeRG_update:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed);color:var(--dsw-alias-label-primary)}.XNGeRG_update{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0;flex:none;align-items:center;gap:6px;display:inline-flex}.XNGeRG_discard:disabled,.XNGeRG_save:disabled,.XNGeRG_update:disabled{opacity:.4;cursor:default}.XNGeRG_discard:focus-visible,.XNGeRG_save:focus-visible,.XNGeRG_update:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}";
|
|
1958
|
-
const tagId = "dsh-smooth-stream/SmoothStreamCard.module.css";
|
|
3358
|
+
const css$1 = ".XNGeRG_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;list-style:none;transition:border-color .16s,background .16s;display:flex}.XNGeRG_card:hover{border-color:var(--dsw-alias-label-dimmed)}.XNGeRG_cardOpen{border-color:var(--dsw-alias-label-dimmed);background:var(--dsw-alias-bg-layer-2)}.XNGeRG_header{appearance:none;cursor:pointer;text-align:left;width:100%;font:inherit;color:inherit;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.XNGeRG_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.XNGeRG_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.XNGeRG_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.XNGeRG_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.XNGeRG_version{text-overflow:ellipsis;white-space:nowrap;max-width:16rem;color:var(--dsw-alias-label-tertiary);flex:none;font-size:12px;line-height:1.5;overflow:hidden}.XNGeRG_pending{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.XNGeRG_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.XNGeRG_chevronOpen{transform:rotate(180deg)}.XNGeRG_body{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;margin:0 16px;padding-bottom:8px;display:flex}.XNGeRG_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.XNGeRG_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.XNGeRG_field+.XNGeRG_field{border-top:1px solid var(--dsw-alias-border-l2)}.XNGeRG_fieldDisabled{opacity:.5}.XNGeRG_fieldHead{justify-content:space-between;align-items:center;gap:8px;display:flex}.XNGeRG_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.XNGeRG_toggle{width:16px;height:16px;accent-color:var(--dsw-alias-brand-primary);cursor:pointer}.XNGeRG_toggle:disabled{cursor:default}.XNGeRG_toggle:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.XNGeRG_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}.XNGeRG_failure{justify-content:space-between;align-items:center;gap:12px;padding:12px 0;display:flex}.XNGeRG_updateRow{border-top:1px solid var(--dsw-alias-border-l2);justify-content:space-between;align-items:center;gap:12px;padding:12px 0;display:flex}.XNGeRG_updateCopy{flex-direction:column;gap:4px;min-width:0;display:flex}.XNGeRG_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.XNGeRG_failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}.XNGeRG_save,.XNGeRG_discard,.XNGeRG_update{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.XNGeRG_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.XNGeRG_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.XNGeRG_discard:hover:not(:disabled),.XNGeRG_update:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed);color:var(--dsw-alias-label-primary)}.XNGeRG_update{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0;flex:none;align-items:center;gap:6px;display:inline-flex}.XNGeRG_discard:disabled,.XNGeRG_save:disabled,.XNGeRG_update:disabled{opacity:.4;cursor:default}.XNGeRG_discard:focus-visible,.XNGeRG_save:focus-visible,.XNGeRG_update:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}";
|
|
3359
|
+
const tagId$1 = "dsh-smooth-stream/SmoothStreamCard.module.css";
|
|
1959
3360
|
if (typeof document !== "undefined") {
|
|
1960
|
-
let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]");
|
|
3361
|
+
let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]");
|
|
1961
3362
|
if (tag === null) {
|
|
1962
3363
|
tag = document.createElement("style");
|
|
1963
3364
|
tag.dataset.plugin = "dsh-smooth-stream";
|
|
1964
|
-
tag.dataset.pluginCss = tagId;
|
|
3365
|
+
tag.dataset.pluginCss = tagId$1;
|
|
1965
3366
|
document.head.appendChild(tag);
|
|
1966
3367
|
}
|
|
1967
|
-
tag.textContent = css;
|
|
3368
|
+
tag.textContent = css$1;
|
|
1968
3369
|
}
|
|
1969
3370
|
var SmoothStreamCard_module_css_default = {
|
|
1970
|
-
"
|
|
3371
|
+
"updateRow": "XNGeRG_updateRow",
|
|
3372
|
+
"fieldDisabled": "XNGeRG_fieldDisabled",
|
|
1971
3373
|
"hint": "XNGeRG_hint",
|
|
1972
|
-
"failed": "XNGeRG_failed",
|
|
1973
|
-
"field": "XNGeRG_field",
|
|
1974
3374
|
"chevron": "XNGeRG_chevron",
|
|
1975
|
-
"
|
|
3375
|
+
"cardOpen": "XNGeRG_cardOpen",
|
|
1976
3376
|
"version": "XNGeRG_version",
|
|
1977
|
-
"body": "XNGeRG_body",
|
|
1978
|
-
"fieldHead": "XNGeRG_fieldHead",
|
|
1979
|
-
"toggle": "XNGeRG_toggle",
|
|
1980
|
-
"readOnly": "XNGeRG_readOnly",
|
|
1981
|
-
"header": "XNGeRG_header",
|
|
1982
3377
|
"pending": "XNGeRG_pending",
|
|
1983
|
-
"label": "XNGeRG_label",
|
|
1984
|
-
"update": "XNGeRG_update",
|
|
1985
|
-
"discard": "XNGeRG_discard",
|
|
1986
|
-
"footer": "XNGeRG_footer",
|
|
1987
3378
|
"failure": "XNGeRG_failure",
|
|
3379
|
+
"header": "XNGeRG_header",
|
|
3380
|
+
"updateCopy": "XNGeRG_updateCopy",
|
|
3381
|
+
"failed": "XNGeRG_failed",
|
|
3382
|
+
"discard": "XNGeRG_discard",
|
|
1988
3383
|
"name": "XNGeRG_name",
|
|
3384
|
+
"save": "XNGeRG_save",
|
|
3385
|
+
"fieldHead": "XNGeRG_fieldHead",
|
|
3386
|
+
"update": "XNGeRG_update",
|
|
3387
|
+
"headText": "XNGeRG_headText",
|
|
3388
|
+
"toggle": "XNGeRG_toggle",
|
|
3389
|
+
"body": "XNGeRG_body",
|
|
3390
|
+
"readOnly": "XNGeRG_readOnly",
|
|
1989
3391
|
"description": "XNGeRG_description",
|
|
1990
|
-
"cardOpen": "XNGeRG_cardOpen",
|
|
1991
3392
|
"chevronOpen": "XNGeRG_chevronOpen",
|
|
1992
|
-
"
|
|
1993
|
-
"
|
|
1994
|
-
"
|
|
3393
|
+
"card": "XNGeRG_card",
|
|
3394
|
+
"field": "XNGeRG_field",
|
|
3395
|
+
"label": "XNGeRG_label",
|
|
3396
|
+
"footer": "XNGeRG_footer"
|
|
1995
3397
|
};
|
|
1996
3398
|
//#endregion
|
|
1997
3399
|
//#region src/client/SmoothStreamCard.tsx
|
|
1998
3400
|
/**
|
|
1999
3401
|
* The smooth-stream plugin configuration card, rendered inside the Web
|
|
2000
|
-
* Settings "plugin configuration" page.
|
|
3402
|
+
* Settings "plugin configuration" page. Preferences are staged until the user
|
|
2001
3403
|
* saves — the same shape as the Host-shipped cards, hand-drawn because the
|
|
2002
3404
|
* Host cards' chrome is not exported for reuse.
|
|
2003
3405
|
*/
|
|
@@ -2067,6 +3469,27 @@ window.__ModuleLoader__.load({
|
|
|
2067
3469
|
}) : null,
|
|
2068
3470
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2069
3471
|
className: SmoothStreamCard_module_css_default.field,
|
|
3472
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3473
|
+
className: SmoothStreamCard_module_css_default.fieldHead,
|
|
3474
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3475
|
+
className: SmoothStreamCard_module_css_default.label,
|
|
3476
|
+
children: t("enabled")
|
|
3477
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3478
|
+
type: "checkbox",
|
|
3479
|
+
className: SmoothStreamCard_module_css_default.toggle,
|
|
3480
|
+
checked: state.enabled,
|
|
3481
|
+
disabled: !state.writable || state.saving,
|
|
3482
|
+
onChange: (event) => {
|
|
3483
|
+
props.edit({ enabled: event.target.checked });
|
|
3484
|
+
}
|
|
3485
|
+
})]
|
|
3486
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3487
|
+
className: SmoothStreamCard_module_css_default.hint,
|
|
3488
|
+
children: t("enabledHint")
|
|
3489
|
+
})]
|
|
3490
|
+
}),
|
|
3491
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3492
|
+
className: state.enabled ? SmoothStreamCard_module_css_default.field : `${SmoothStreamCard_module_css_default.field} ${SmoothStreamCard_module_css_default.fieldDisabled}`,
|
|
2070
3493
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2071
3494
|
className: SmoothStreamCard_module_css_default.fieldHead,
|
|
2072
3495
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -2076,9 +3499,9 @@ window.__ModuleLoader__.load({
|
|
|
2076
3499
|
type: "checkbox",
|
|
2077
3500
|
className: SmoothStreamCard_module_css_default.toggle,
|
|
2078
3501
|
checked: state.thinkAutoExpand,
|
|
2079
|
-
disabled: !state.writable,
|
|
3502
|
+
disabled: !state.writable || state.saving || !state.enabled,
|
|
2080
3503
|
onChange: (event) => {
|
|
2081
|
-
props.edit(event.target.checked);
|
|
3504
|
+
props.edit({ thinkAutoExpand: event.target.checked });
|
|
2082
3505
|
}
|
|
2083
3506
|
})]
|
|
2084
3507
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -2086,6 +3509,48 @@ window.__ModuleLoader__.load({
|
|
|
2086
3509
|
children: t("thinkAutoExpandHint")
|
|
2087
3510
|
})]
|
|
2088
3511
|
}),
|
|
3512
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3513
|
+
className: SmoothStreamCard_module_css_default.field,
|
|
3514
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3515
|
+
className: SmoothStreamCard_module_css_default.fieldHead,
|
|
3516
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3517
|
+
className: SmoothStreamCard_module_css_default.label,
|
|
3518
|
+
children: t("autoCollapse")
|
|
3519
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3520
|
+
type: "checkbox",
|
|
3521
|
+
className: SmoothStreamCard_module_css_default.toggle,
|
|
3522
|
+
checked: state.autoCollapse,
|
|
3523
|
+
disabled: !state.writable || state.saving,
|
|
3524
|
+
onChange: (event) => {
|
|
3525
|
+
props.edit({ autoCollapse: event.target.checked });
|
|
3526
|
+
}
|
|
3527
|
+
})]
|
|
3528
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3529
|
+
className: SmoothStreamCard_module_css_default.hint,
|
|
3530
|
+
children: t("autoCollapseHint")
|
|
3531
|
+
})]
|
|
3532
|
+
}),
|
|
3533
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3534
|
+
className: state.debugAvailable ? SmoothStreamCard_module_css_default.field : `${SmoothStreamCard_module_css_default.field} ${SmoothStreamCard_module_css_default.fieldDisabled}`,
|
|
3535
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3536
|
+
className: SmoothStreamCard_module_css_default.fieldHead,
|
|
3537
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3538
|
+
className: SmoothStreamCard_module_css_default.label,
|
|
3539
|
+
children: t("debugEnabled")
|
|
3540
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3541
|
+
type: "checkbox",
|
|
3542
|
+
className: SmoothStreamCard_module_css_default.toggle,
|
|
3543
|
+
checked: state.debugEnabled,
|
|
3544
|
+
disabled: !state.debugAvailable || !state.writable || state.saving,
|
|
3545
|
+
onChange: (event) => {
|
|
3546
|
+
props.edit({ debugEnabled: event.target.checked });
|
|
3547
|
+
}
|
|
3548
|
+
})]
|
|
3549
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3550
|
+
className: SmoothStreamCard_module_css_default.hint,
|
|
3551
|
+
children: state.debugAvailable ? t("debugEnabledHint") : t("debugUnavailable")
|
|
3552
|
+
})]
|
|
3553
|
+
}),
|
|
2089
3554
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2090
3555
|
className: SmoothStreamCard_module_css_default.updateRow,
|
|
2091
3556
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -2151,7 +3616,9 @@ window.__ModuleLoader__.load({
|
|
|
2151
3616
|
api;
|
|
2152
3617
|
store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(this.projection());
|
|
2153
3618
|
loaded;
|
|
2154
|
-
|
|
3619
|
+
loadedDebug;
|
|
3620
|
+
stagedBase;
|
|
3621
|
+
stagedDebug;
|
|
2155
3622
|
saving = false;
|
|
2156
3623
|
failed = false;
|
|
2157
3624
|
upgrading = false;
|
|
@@ -2182,8 +3649,22 @@ window.__ModuleLoader__.load({
|
|
|
2182
3649
|
inject() {
|
|
2183
3650
|
return {
|
|
2184
3651
|
hooks: { smoothStreamCard: this.store },
|
|
2185
|
-
edit: (
|
|
2186
|
-
this.
|
|
3652
|
+
edit: (patch) => {
|
|
3653
|
+
if (this.saving) return;
|
|
3654
|
+
if (patch.enabled !== void 0 || patch.thinkAutoExpand !== void 0 || patch.autoCollapse !== void 0) this.stagedBase = {
|
|
3655
|
+
...this.baseValues(),
|
|
3656
|
+
...patch.enabled === void 0 ? {} : { enabled: patch.enabled },
|
|
3657
|
+
...patch.thinkAutoExpand === void 0 ? {} : { thinkAutoExpand: patch.thinkAutoExpand },
|
|
3658
|
+
...patch.autoCollapse === void 0 ? {} : { autoCollapse: patch.autoCollapse }
|
|
3659
|
+
};
|
|
3660
|
+
if (this.loadedDebug !== void 0 && (patch.debugEnabled !== void 0 || patch.debugTuning !== void 0)) this.stagedDebug = {
|
|
3661
|
+
...this.debugValues(),
|
|
3662
|
+
...patch.debugEnabled === void 0 ? {} : { debugEnabled: patch.debugEnabled },
|
|
3663
|
+
...patch.debugTuning === void 0 ? {} : { debugTuning: {
|
|
3664
|
+
...this.debugValues().debugTuning,
|
|
3665
|
+
...patch.debugTuning
|
|
3666
|
+
} }
|
|
3667
|
+
};
|
|
2187
3668
|
this.failed = false;
|
|
2188
3669
|
this.publish();
|
|
2189
3670
|
},
|
|
@@ -2191,8 +3672,9 @@ window.__ModuleLoader__.load({
|
|
|
2191
3672
|
this.save();
|
|
2192
3673
|
},
|
|
2193
3674
|
discard: () => {
|
|
2194
|
-
if (this.
|
|
2195
|
-
this.
|
|
3675
|
+
if (this.stagedBase === void 0 && this.stagedDebug === void 0 && !this.failed) return;
|
|
3676
|
+
this.stagedBase = void 0;
|
|
3677
|
+
this.stagedDebug = void 0;
|
|
2196
3678
|
this.failed = false;
|
|
2197
3679
|
this.publish();
|
|
2198
3680
|
},
|
|
@@ -2208,10 +3690,12 @@ window.__ModuleLoader__.load({
|
|
|
2208
3690
|
return {
|
|
2209
3691
|
status: this.loadStatus,
|
|
2210
3692
|
writable: this.loaded?.writable ?? false,
|
|
2211
|
-
dirty: this.
|
|
3693
|
+
dirty: this.stagedBase !== void 0 || this.stagedDebug !== void 0,
|
|
2212
3694
|
saving: this.saving,
|
|
2213
3695
|
failed: this.failed,
|
|
2214
|
-
|
|
3696
|
+
...this.baseValues(),
|
|
3697
|
+
...this.debugValues(),
|
|
3698
|
+
debugAvailable: this.loadedDebug !== void 0,
|
|
2215
3699
|
version: this.loaded?.version,
|
|
2216
3700
|
installation: this.loaded?.installation ?? "unmanaged",
|
|
2217
3701
|
canUpgrade: this.loaded?.canUpgrade ?? false,
|
|
@@ -2220,32 +3704,81 @@ window.__ModuleLoader__.load({
|
|
|
2220
3704
|
restartRequired: this.restartRequired
|
|
2221
3705
|
};
|
|
2222
3706
|
}
|
|
3707
|
+
baseValues() {
|
|
3708
|
+
return this.stagedBase ?? {
|
|
3709
|
+
enabled: this.loaded?.enabled ?? DEFAULT_STREAM_SETTINGS.enabled,
|
|
3710
|
+
thinkAutoExpand: this.loaded?.thinkAutoExpand ?? DEFAULT_STREAM_SETTINGS.thinkAutoExpand,
|
|
3711
|
+
autoCollapse: this.loaded?.autoCollapse ?? DEFAULT_STREAM_SETTINGS.autoCollapse
|
|
3712
|
+
};
|
|
3713
|
+
}
|
|
3714
|
+
debugValues() {
|
|
3715
|
+
return this.stagedDebug ?? {
|
|
3716
|
+
debugEnabled: this.loadedDebug?.debugEnabled ?? DEFAULT_STREAM_SETTINGS.debugEnabled,
|
|
3717
|
+
debugTuning: this.loadedDebug?.tuning ?? DEFAULT_STREAM_DEBUG_TUNING
|
|
3718
|
+
};
|
|
3719
|
+
}
|
|
3720
|
+
/** Complete settings projection consumed by the live SettingsCell bridge. */
|
|
3721
|
+
values() {
|
|
3722
|
+
return {
|
|
3723
|
+
...this.baseValues(),
|
|
3724
|
+
...this.debugValues()
|
|
3725
|
+
};
|
|
3726
|
+
}
|
|
2223
3727
|
async load() {
|
|
2224
3728
|
const generation = ++this.loadGeneration;
|
|
2225
3729
|
this.loadStatus = "loading";
|
|
2226
3730
|
this.loaded = void 0;
|
|
3731
|
+
this.loadedDebug = void 0;
|
|
2227
3732
|
this.publish();
|
|
2228
3733
|
try {
|
|
2229
3734
|
const view = await this.api.read();
|
|
2230
3735
|
if (generation !== this.loadGeneration) return;
|
|
2231
3736
|
this.loaded = view;
|
|
2232
3737
|
this.loadStatus = "ready";
|
|
3738
|
+
this.publish();
|
|
3739
|
+
try {
|
|
3740
|
+
const debug = await this.api.readDebug();
|
|
3741
|
+
if (generation !== this.loadGeneration) return;
|
|
3742
|
+
this.loadedDebug = debug;
|
|
3743
|
+
} catch {
|
|
3744
|
+
if (generation !== this.loadGeneration) return;
|
|
3745
|
+
this.loadedDebug = void 0;
|
|
3746
|
+
}
|
|
2233
3747
|
} catch {
|
|
2234
3748
|
if (generation !== this.loadGeneration) return;
|
|
2235
3749
|
this.loaded = void 0;
|
|
3750
|
+
this.loadedDebug = void 0;
|
|
2236
3751
|
this.loadStatus = "unavailable";
|
|
2237
3752
|
}
|
|
2238
3753
|
this.publish();
|
|
2239
3754
|
}
|
|
2240
3755
|
async save() {
|
|
2241
|
-
if (this.
|
|
2242
|
-
const
|
|
3756
|
+
if (this.stagedBase === void 0 && this.stagedDebug === void 0 || this.saving || this.loaded?.writable !== true) return;
|
|
3757
|
+
const base = this.stagedBase;
|
|
3758
|
+
const debug = this.stagedDebug;
|
|
2243
3759
|
this.saving = true;
|
|
2244
3760
|
this.failed = false;
|
|
2245
3761
|
this.publish();
|
|
2246
3762
|
try {
|
|
2247
|
-
|
|
2248
|
-
|
|
3763
|
+
if (base !== void 0) {
|
|
3764
|
+
const combined = debug !== void 0 && this.loadedDebug !== void 0;
|
|
3765
|
+
this.loaded = await this.api.write(combined ? {
|
|
3766
|
+
...base,
|
|
3767
|
+
...debug
|
|
3768
|
+
} : base);
|
|
3769
|
+
this.stagedBase = void 0;
|
|
3770
|
+
if (combined) {
|
|
3771
|
+
this.loadedDebug = {
|
|
3772
|
+
debugEnabled: debug.debugEnabled,
|
|
3773
|
+
tuning: { ...debug.debugTuning }
|
|
3774
|
+
};
|
|
3775
|
+
this.stagedDebug = void 0;
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3778
|
+
if (debug !== void 0 && this.loadedDebug !== void 0 && this.stagedDebug !== void 0) {
|
|
3779
|
+
this.loadedDebug = await this.api.writeDebug(debug);
|
|
3780
|
+
this.stagedDebug = void 0;
|
|
3781
|
+
}
|
|
2249
3782
|
} catch {
|
|
2250
3783
|
this.failed = true;
|
|
2251
3784
|
}
|
|
@@ -2280,6 +3813,8 @@ window.__ModuleLoader__.load({
|
|
|
2280
3813
|
const STREAM_SETTINGS_RPC = {
|
|
2281
3814
|
read: "settings.read",
|
|
2282
3815
|
write: "settings.write",
|
|
3816
|
+
debugRead: "debug.read",
|
|
3817
|
+
debugWrite: "debug.write",
|
|
2283
3818
|
upgrade: "plugin.upgrade"
|
|
2284
3819
|
};
|
|
2285
3820
|
//#endregion
|
|
@@ -2293,13 +3828,22 @@ window.__ModuleLoader__.load({
|
|
|
2293
3828
|
"npm",
|
|
2294
3829
|
"development",
|
|
2295
3830
|
"unmanaged"
|
|
2296
|
-
].includes(data.installation) || typeof data.writable !== "boolean" || typeof data.thinkAutoExpand !== "boolean" || typeof data.canUpgrade !== "boolean") throw new Error("dsh-smooth-stream: malformed settings response");
|
|
3831
|
+
].includes(data.installation) || typeof data.writable !== "boolean" || typeof data.enabled !== "boolean" || typeof data.thinkAutoExpand !== "boolean" || typeof data.autoCollapse !== "boolean" || typeof data.canUpgrade !== "boolean") throw new Error("dsh-smooth-stream: malformed settings response");
|
|
2297
3832
|
return data;
|
|
2298
3833
|
}
|
|
2299
3834
|
function upgradeView(value) {
|
|
2300
3835
|
if (record(value)?.restartRequired !== true) throw new Error("dsh-smooth-stream: malformed update response");
|
|
2301
3836
|
return { restartRequired: true };
|
|
2302
3837
|
}
|
|
3838
|
+
function debugSettingsView(value) {
|
|
3839
|
+
const data = record(value);
|
|
3840
|
+
const tuning = record(data?.tuning);
|
|
3841
|
+
if (data === void 0 || typeof data.debugEnabled !== "boolean" || tuning === void 0 || typeof tuning.revealScale !== "number" || typeof tuning.queuePressure !== "number" || typeof tuning.maxRevealCps !== "number" || typeof tuning.springStiffness !== "number" || typeof tuning.springDamping !== "number" || typeof tuning.springMass !== "number" || typeof tuning.runwayPx !== "number" || typeof tuning.reserveResponseMs !== "number" || typeof tuning.backpressureMinScale !== "number") throw new Error("dsh-smooth-stream: malformed debug settings response");
|
|
3842
|
+
return {
|
|
3843
|
+
debugEnabled: data.debugEnabled,
|
|
3844
|
+
tuning
|
|
3845
|
+
};
|
|
3846
|
+
}
|
|
2303
3847
|
function accepted(result) {
|
|
2304
3848
|
if (!result.ok) throw new Error(result.error.message);
|
|
2305
3849
|
return result.value;
|
|
@@ -2310,8 +3854,25 @@ window.__ModuleLoader__.load({
|
|
|
2310
3854
|
async read() {
|
|
2311
3855
|
return settingsView(accepted(await connection.rpc.call(STREAM_SETTINGS_RPC_CHANNEL, STREAM_SETTINGS_RPC.read, {})));
|
|
2312
3856
|
},
|
|
2313
|
-
async write(
|
|
2314
|
-
return settingsView(accepted(await connection.rpc.call(STREAM_SETTINGS_RPC_CHANNEL, STREAM_SETTINGS_RPC.write, {
|
|
3857
|
+
async write(settings) {
|
|
3858
|
+
return settingsView(accepted(await connection.rpc.call(STREAM_SETTINGS_RPC_CHANNEL, STREAM_SETTINGS_RPC.write, {
|
|
3859
|
+
enabled: settings.enabled,
|
|
3860
|
+
thinkAutoExpand: settings.thinkAutoExpand,
|
|
3861
|
+
autoCollapse: settings.autoCollapse,
|
|
3862
|
+
...settings.debugEnabled === void 0 || settings.debugTuning === void 0 ? {} : {
|
|
3863
|
+
debugEnabled: settings.debugEnabled,
|
|
3864
|
+
debugTuning: settings.debugTuning
|
|
3865
|
+
}
|
|
3866
|
+
})));
|
|
3867
|
+
},
|
|
3868
|
+
async readDebug() {
|
|
3869
|
+
return debugSettingsView(accepted(await connection.rpc.call(STREAM_SETTINGS_RPC_CHANNEL, STREAM_SETTINGS_RPC.debugRead, {})));
|
|
3870
|
+
},
|
|
3871
|
+
async writeDebug(settings) {
|
|
3872
|
+
return debugSettingsView(accepted(await connection.rpc.call(STREAM_SETTINGS_RPC_CHANNEL, STREAM_SETTINGS_RPC.debugWrite, {
|
|
3873
|
+
debugEnabled: settings.debugEnabled,
|
|
3874
|
+
tuning: settings.debugTuning
|
|
3875
|
+
})));
|
|
2315
3876
|
},
|
|
2316
3877
|
async upgrade() {
|
|
2317
3878
|
return upgradeView(accepted(await connection.rpc.call(STREAM_SETTINGS_RPC_CHANNEL, STREAM_SETTINGS_RPC.upgrade, {})));
|
|
@@ -2319,6 +3880,433 @@ window.__ModuleLoader__.load({
|
|
|
2319
3880
|
};
|
|
2320
3881
|
}
|
|
2321
3882
|
//#endregion
|
|
3883
|
+
//#region \0dsh-css:/Users/dzlin/work/project/dsh-smooth-stream/src/client/DebugPanel.module.css.mjs
|
|
3884
|
+
const css = ".m4_tdG_trigger,.m4_tdG_iconButton{appearance:none;width:32px;height:32px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:1px solid #0000;border-radius:6px;justify-content:center;align-items:center;padding:0;display:inline-flex}.m4_tdG_trigger:hover,.m4_tdG_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.m4_tdG_iconButton:disabled{opacity:.45;cursor:not-allowed}.m4_tdG_triggerActive{border-color:var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-state-business-primary)}.m4_tdG_trigger:focus-visible,.m4_tdG_iconButton:focus-visible,.m4_tdG_secondaryButton:focus-visible,.m4_tdG_primaryButton:focus-visible,.m4_tdG_number:focus-visible,.m4_tdG_range:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.m4_tdG_panel{z-index:30;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);width:min(328px,100vw - 32px);min-height:280px;box-shadow:0 12px 32px color-mix(in srgb, var(--dsw-alias-label-primary) 14%, transparent);color:var(--dsw-alias-label-primary);border-radius:8px;flex-direction:column;display:flex;position:fixed;top:88px;bottom:16px;right:16px;overflow:hidden}.m4_tdG_panelHeader{border-bottom:1px solid var(--dsw-alias-border-l2);flex:none;align-items:center;gap:8px;min-height:48px;padding:0 8px 0 14px;display:flex}.m4_tdG_statusDot{background:var(--dsw-alias-label-caption);border-radius:50%;flex:none;width:7px;height:7px}.m4_tdG_statusLive{background:var(--dsw-alias-state-success-primary);box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-alias-state-success-primary) 15%, transparent)}.m4_tdG_title{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;font-size:14px;font-weight:600;line-height:20px;overflow:hidden}.m4_tdG_state,.m4_tdG_unsaved{color:var(--dsw-alias-label-tertiary);white-space:nowrap;flex:none;font-size:10px;line-height:16px}.m4_tdG_unsaved{color:var(--dsw-alias-state-warn-primary)}.m4_tdG_scrollArea{overscroll-behavior:contain;min-height:0;overflow-y:auto}.m4_tdG_section{padding:14px}.m4_tdG_guide{border-bottom:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);margin:0;padding:12px 14px;font-size:11px;line-height:1.55}.m4_tdG_section+.m4_tdG_section{border-top:1px solid var(--dsw-alias-border-l2)}.m4_tdG_section h2{color:var(--dsw-alias-label-secondary);margin:0 0 10px;font-size:12px;font-weight:600;line-height:18px}.m4_tdG_metrics{grid-template-columns:repeat(2,minmax(0,1fr));gap:0 16px;margin:0;display:grid}.m4_tdG_metric{border-bottom:1px solid color-mix(in srgb, var(--dsw-alias-border-l2) 65%, transparent);justify-content:space-between;align-items:baseline;gap:8px;min-width:0;padding:5px 0;display:flex}.m4_tdG_metric dt{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:11px;line-height:16px;overflow:hidden}.m4_tdG_metric dd{min-width:0;color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;text-overflow:ellipsis;white-space:nowrap;margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;line-height:16px;overflow:hidden}.m4_tdG_metric dd[data-tone=good]{color:var(--dsw-alias-state-success-primary)}.m4_tdG_metric dd[data-tone=warn]{color:var(--dsw-alias-state-warn-primary)}.m4_tdG_control{flex-direction:column;gap:5px;padding:6px 0;display:flex}.m4_tdG_controlHead{color:var(--dsw-alias-label-secondary);justify-content:space-between;align-items:center;gap:10px;font-size:11px;line-height:18px;display:flex}.m4_tdG_controlLabel{align-items:center;gap:4px;min-width:0;display:inline-flex}.m4_tdG_infoButton{appearance:none;width:20px;height:20px;color:var(--dsw-alias-label-tertiary);cursor:help;background:0 0;border:0;border-radius:50%;justify-content:center;align-items:center;padding:0;display:inline-flex}.m4_tdG_infoButton:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.m4_tdG_infoButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.m4_tdG_numberWrap{flex:none;align-items:center;gap:4px;display:inline-flex}.m4_tdG_number{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);width:68px;height:26px;color:var(--dsw-alias-label-primary);font:inherit;font-variant-numeric:tabular-nums;text-align:right;border-radius:5px;padding:2px 6px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.m4_tdG_unit{width:30px;color:var(--dsw-alias-label-tertiary);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10px}.m4_tdG_range{width:100%;height:18px;accent-color:var(--dsw-alias-state-business-primary);cursor:pointer;margin:0}.m4_tdG_range:disabled,.m4_tdG_number:disabled{opacity:.5;cursor:default}.m4_tdG_footer{border-top:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);flex:none;align-items:center;gap:6px;min-height:50px;padding:8px 10px;display:flex}.m4_tdG_footerSpacer{flex:1}.m4_tdG_secondaryButton,.m4_tdG_primaryButton{appearance:none;border:1px solid var(--dsw-alias-border-l2);min-height:30px;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border-radius:6px;justify-content:center;align-items:center;gap:5px;padding:4px 9px;font-size:11px;line-height:18px;display:inline-flex}.m4_tdG_primaryButton{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:#0000}.m4_tdG_secondaryButton:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.m4_tdG_secondaryButton:disabled,.m4_tdG_primaryButton:disabled{opacity:.4;cursor:default}.m4_tdG_visuallyHidden{clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}@media (width<=900px){.m4_tdG_trigger,.m4_tdG_iconButton{width:44px;height:44px}.m4_tdG_panel{top:auto;right:12px;bottom:max(12px, env(safe-area-inset-bottom));width:auto;max-height:min(70dvh,640px);left:12px}.m4_tdG_secondaryButton,.m4_tdG_primaryButton{min-height:44px}}@media (prefers-reduced-motion:reduce){.m4_tdG_statusLive{box-shadow:none}}";
|
|
3885
|
+
const tagId = "dsh-smooth-stream/DebugPanel.module.css";
|
|
3886
|
+
if (typeof document !== "undefined") {
|
|
3887
|
+
let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]");
|
|
3888
|
+
if (tag === null) {
|
|
3889
|
+
tag = document.createElement("style");
|
|
3890
|
+
tag.dataset.plugin = "dsh-smooth-stream";
|
|
3891
|
+
tag.dataset.pluginCss = tagId;
|
|
3892
|
+
document.head.appendChild(tag);
|
|
3893
|
+
}
|
|
3894
|
+
tag.textContent = css;
|
|
3895
|
+
}
|
|
3896
|
+
var DebugPanel_module_css_default = {
|
|
3897
|
+
"infoButton": "m4_tdG_infoButton",
|
|
3898
|
+
"unit": "m4_tdG_unit",
|
|
3899
|
+
"range": "m4_tdG_range",
|
|
3900
|
+
"secondaryButton": "m4_tdG_secondaryButton",
|
|
3901
|
+
"footerSpacer": "m4_tdG_footerSpacer",
|
|
3902
|
+
"visuallyHidden": "m4_tdG_visuallyHidden",
|
|
3903
|
+
"title": "m4_tdG_title",
|
|
3904
|
+
"number": "m4_tdG_number",
|
|
3905
|
+
"numberWrap": "m4_tdG_numberWrap",
|
|
3906
|
+
"footer": "m4_tdG_footer",
|
|
3907
|
+
"triggerActive": "m4_tdG_triggerActive",
|
|
3908
|
+
"iconButton": "m4_tdG_iconButton",
|
|
3909
|
+
"scrollArea": "m4_tdG_scrollArea",
|
|
3910
|
+
"section": "m4_tdG_section",
|
|
3911
|
+
"trigger": "m4_tdG_trigger",
|
|
3912
|
+
"statusDot": "m4_tdG_statusDot",
|
|
3913
|
+
"state": "m4_tdG_state",
|
|
3914
|
+
"panelHeader": "m4_tdG_panelHeader",
|
|
3915
|
+
"panel": "m4_tdG_panel",
|
|
3916
|
+
"primaryButton": "m4_tdG_primaryButton",
|
|
3917
|
+
"unsaved": "m4_tdG_unsaved",
|
|
3918
|
+
"guide": "m4_tdG_guide",
|
|
3919
|
+
"metric": "m4_tdG_metric",
|
|
3920
|
+
"controlHead": "m4_tdG_controlHead",
|
|
3921
|
+
"controlLabel": "m4_tdG_controlLabel",
|
|
3922
|
+
"statusLive": "m4_tdG_statusLive",
|
|
3923
|
+
"control": "m4_tdG_control",
|
|
3924
|
+
"metrics": "m4_tdG_metrics"
|
|
3925
|
+
};
|
|
3926
|
+
//#endregion
|
|
3927
|
+
//#region src/client/DebugPanel.tsx
|
|
3928
|
+
const REVEAL_CONTROLS = [
|
|
3929
|
+
{
|
|
3930
|
+
key: "revealScale",
|
|
3931
|
+
label: "debugRevealMultiplier",
|
|
3932
|
+
tip: "debugTipRevealMultiplier",
|
|
3933
|
+
min: .25,
|
|
3934
|
+
max: 2,
|
|
3935
|
+
step: .05,
|
|
3936
|
+
unit: "x"
|
|
3937
|
+
},
|
|
3938
|
+
{
|
|
3939
|
+
key: "queuePressure",
|
|
3940
|
+
label: "debugQueuePressure",
|
|
3941
|
+
tip: "debugTipQueuePressure",
|
|
3942
|
+
min: 0,
|
|
3943
|
+
max: 2,
|
|
3944
|
+
step: .05,
|
|
3945
|
+
unit: "x"
|
|
3946
|
+
},
|
|
3947
|
+
{
|
|
3948
|
+
key: "maxRevealCps",
|
|
3949
|
+
label: "debugMaxReveal",
|
|
3950
|
+
tip: "debugTipMaxReveal",
|
|
3951
|
+
min: 120,
|
|
3952
|
+
max: 1e3,
|
|
3953
|
+
step: 10,
|
|
3954
|
+
unit: "cps"
|
|
3955
|
+
}
|
|
3956
|
+
];
|
|
3957
|
+
const FOLLOW_CONTROLS = [
|
|
3958
|
+
{
|
|
3959
|
+
key: "springStiffness",
|
|
3960
|
+
label: "debugSpringStiffness",
|
|
3961
|
+
tip: "debugTipSpringStiffness",
|
|
3962
|
+
min: 40,
|
|
3963
|
+
max: 320,
|
|
3964
|
+
step: 5,
|
|
3965
|
+
unit: ""
|
|
3966
|
+
},
|
|
3967
|
+
{
|
|
3968
|
+
key: "springDamping",
|
|
3969
|
+
label: "debugSpringDamping",
|
|
3970
|
+
tip: "debugTipSpringDamping",
|
|
3971
|
+
min: 8,
|
|
3972
|
+
max: 80,
|
|
3973
|
+
step: 1,
|
|
3974
|
+
unit: ""
|
|
3975
|
+
},
|
|
3976
|
+
{
|
|
3977
|
+
key: "springMass",
|
|
3978
|
+
label: "debugSpringMass",
|
|
3979
|
+
tip: "debugTipSpringMass",
|
|
3980
|
+
min: .5,
|
|
3981
|
+
max: 3,
|
|
3982
|
+
step: .05,
|
|
3983
|
+
unit: ""
|
|
3984
|
+
},
|
|
3985
|
+
{
|
|
3986
|
+
key: "runwayPx",
|
|
3987
|
+
label: "debugRunway",
|
|
3988
|
+
tip: "debugTipRunway",
|
|
3989
|
+
min: 0,
|
|
3990
|
+
max: 120,
|
|
3991
|
+
step: 2,
|
|
3992
|
+
unit: "px"
|
|
3993
|
+
},
|
|
3994
|
+
{
|
|
3995
|
+
key: "reserveResponseMs",
|
|
3996
|
+
label: "debugReserveResponse",
|
|
3997
|
+
tip: "debugTipReserveResponse",
|
|
3998
|
+
min: 60,
|
|
3999
|
+
max: 600,
|
|
4000
|
+
step: 10,
|
|
4001
|
+
unit: "ms"
|
|
4002
|
+
},
|
|
4003
|
+
{
|
|
4004
|
+
key: "backpressureMinScale",
|
|
4005
|
+
label: "debugBackpressureMin",
|
|
4006
|
+
tip: "debugTipBackpressureMin",
|
|
4007
|
+
min: .25,
|
|
4008
|
+
max: 1,
|
|
4009
|
+
step: .05,
|
|
4010
|
+
unit: "x"
|
|
4011
|
+
}
|
|
4012
|
+
];
|
|
4013
|
+
function fixed(value, digits = 1) {
|
|
4014
|
+
return value === null || !Number.isFinite(value) ? "-" : value.toFixed(digits);
|
|
4015
|
+
}
|
|
4016
|
+
function Metric({ label, value, tone }) {
|
|
4017
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4018
|
+
className: DebugPanel_module_css_default.metric,
|
|
4019
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
|
|
4020
|
+
"data-tone": tone,
|
|
4021
|
+
children: value
|
|
4022
|
+
})]
|
|
4023
|
+
});
|
|
4024
|
+
}
|
|
4025
|
+
function TuningField({ control, state, edit, label, t }) {
|
|
4026
|
+
const value = state.tuning[control.key];
|
|
4027
|
+
const labelId = `smooth-stream-debug-${control.key}`;
|
|
4028
|
+
const update = (next) => {
|
|
4029
|
+
if (!Number.isFinite(next)) return;
|
|
4030
|
+
const clamped = Math.min(control.max, Math.max(control.min, next));
|
|
4031
|
+
edit({ debugTuning: {
|
|
4032
|
+
...state.tuning,
|
|
4033
|
+
[control.key]: clamped
|
|
4034
|
+
} });
|
|
4035
|
+
};
|
|
4036
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4037
|
+
className: DebugPanel_module_css_default.control,
|
|
4038
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4039
|
+
className: DebugPanel_module_css_default.controlHead,
|
|
4040
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4041
|
+
className: DebugPanel_module_css_default.controlLabel,
|
|
4042
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4043
|
+
id: labelId,
|
|
4044
|
+
children: label
|
|
4045
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
4046
|
+
label: t(control.tip),
|
|
4047
|
+
side: "right",
|
|
4048
|
+
maxWidth: 300,
|
|
4049
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4050
|
+
className: DebugPanel_module_css_default.infoButton,
|
|
4051
|
+
type: "button",
|
|
4052
|
+
"aria-label": label,
|
|
4053
|
+
title: t(control.tip),
|
|
4054
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQuestionOutline14, {})
|
|
4055
|
+
})
|
|
4056
|
+
})]
|
|
4057
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4058
|
+
className: DebugPanel_module_css_default.numberWrap,
|
|
4059
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4060
|
+
className: DebugPanel_module_css_default.number,
|
|
4061
|
+
type: "number",
|
|
4062
|
+
"aria-labelledby": labelId,
|
|
4063
|
+
min: control.min,
|
|
4064
|
+
max: control.max,
|
|
4065
|
+
step: control.step,
|
|
4066
|
+
value,
|
|
4067
|
+
disabled: !state.writable,
|
|
4068
|
+
onChange: (event) => {
|
|
4069
|
+
update(event.currentTarget.valueAsNumber);
|
|
4070
|
+
}
|
|
4071
|
+
}), control.unit === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4072
|
+
className: DebugPanel_module_css_default.unit,
|
|
4073
|
+
children: control.unit
|
|
4074
|
+
})]
|
|
4075
|
+
})]
|
|
4076
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4077
|
+
className: DebugPanel_module_css_default.range,
|
|
4078
|
+
type: "range",
|
|
4079
|
+
"aria-labelledby": labelId,
|
|
4080
|
+
min: control.min,
|
|
4081
|
+
max: control.max,
|
|
4082
|
+
step: control.step,
|
|
4083
|
+
value,
|
|
4084
|
+
disabled: !state.writable,
|
|
4085
|
+
onChange: (event) => {
|
|
4086
|
+
update(event.currentTarget.valueAsNumber);
|
|
4087
|
+
}
|
|
4088
|
+
})]
|
|
4089
|
+
});
|
|
4090
|
+
}
|
|
4091
|
+
function DebugPanel(props) {
|
|
4092
|
+
const { t } = props;
|
|
4093
|
+
const state = props.useDebugRuntime((snapshot) => snapshot);
|
|
4094
|
+
const [open, setOpen] = (0, react.useState)(true);
|
|
4095
|
+
const [copied, setCopied] = (0, react.useState)(false);
|
|
4096
|
+
(0, react.useEffect)(() => {
|
|
4097
|
+
if (!copied) return;
|
|
4098
|
+
const timer = setTimeout(() => {
|
|
4099
|
+
setCopied(false);
|
|
4100
|
+
}, 1400);
|
|
4101
|
+
return () => {
|
|
4102
|
+
clearTimeout(timer);
|
|
4103
|
+
};
|
|
4104
|
+
}, [copied]);
|
|
4105
|
+
(0, react.useEffect)(() => {
|
|
4106
|
+
if (state.enabled) setOpen(true);
|
|
4107
|
+
}, [state.enabled]);
|
|
4108
|
+
if (!state.available || !state.enabled || !open) return null;
|
|
4109
|
+
const metrics = state.metrics;
|
|
4110
|
+
const live = metrics.streamActive || metrics.followActive;
|
|
4111
|
+
const progress = metrics.streamTargetChars <= 0 ? "-" : `${String(metrics.streamDisplayedChars)} / ${String(metrics.streamTargetChars)}`;
|
|
4112
|
+
const copyDiagnostics = async () => {
|
|
4113
|
+
if (await (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(JSON.stringify({
|
|
4114
|
+
tuning: state.tuning,
|
|
4115
|
+
metrics: state.metrics
|
|
4116
|
+
}, null, 2))) setCopied(true);
|
|
4117
|
+
};
|
|
4118
|
+
const panel = !state.enabled || !open ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
|
|
4119
|
+
className: DebugPanel_module_css_default.panel,
|
|
4120
|
+
role: "complementary",
|
|
4121
|
+
"aria-label": t("debugPanelTitle"),
|
|
4122
|
+
children: [
|
|
4123
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
4124
|
+
className: DebugPanel_module_css_default.panelHeader,
|
|
4125
|
+
children: [
|
|
4126
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4127
|
+
className: live ? `${DebugPanel_module_css_default.statusDot} ${DebugPanel_module_css_default.statusLive}` : DebugPanel_module_css_default.statusDot,
|
|
4128
|
+
"aria-hidden": true
|
|
4129
|
+
}),
|
|
4130
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4131
|
+
className: DebugPanel_module_css_default.title,
|
|
4132
|
+
children: t("debugPanelTitle")
|
|
4133
|
+
}),
|
|
4134
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4135
|
+
className: DebugPanel_module_css_default.state,
|
|
4136
|
+
children: t(live ? "debugLive" : "debugIdle")
|
|
4137
|
+
}),
|
|
4138
|
+
state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4139
|
+
className: DebugPanel_module_css_default.unsaved,
|
|
4140
|
+
children: t("debugUnsaved")
|
|
4141
|
+
}) : null,
|
|
4142
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4143
|
+
className: DebugPanel_module_css_default.iconButton,
|
|
4144
|
+
type: "button",
|
|
4145
|
+
title: t("debugCopy"),
|
|
4146
|
+
"aria-label": t("debugCopy"),
|
|
4147
|
+
onClick: () => {
|
|
4148
|
+
copyDiagnostics();
|
|
4149
|
+
},
|
|
4150
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, {})
|
|
4151
|
+
}),
|
|
4152
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4153
|
+
className: DebugPanel_module_css_default.iconButton,
|
|
4154
|
+
type: "button",
|
|
4155
|
+
title: t("debugPanelClose"),
|
|
4156
|
+
"aria-label": t("debugPanelClose"),
|
|
4157
|
+
disabled: !state.writable,
|
|
4158
|
+
onClick: () => {
|
|
4159
|
+
setOpen(false);
|
|
4160
|
+
props.edit({ debugEnabled: false });
|
|
4161
|
+
props.save();
|
|
4162
|
+
},
|
|
4163
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, {})
|
|
4164
|
+
}),
|
|
4165
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4166
|
+
className: DebugPanel_module_css_default.visuallyHidden,
|
|
4167
|
+
"aria-live": "polite",
|
|
4168
|
+
children: copied ? t("debugCopied") : ""
|
|
4169
|
+
})
|
|
4170
|
+
]
|
|
4171
|
+
}),
|
|
4172
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4173
|
+
className: DebugPanel_module_css_default.scrollArea,
|
|
4174
|
+
children: [
|
|
4175
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4176
|
+
className: DebugPanel_module_css_default.guide,
|
|
4177
|
+
children: t("debugGuide")
|
|
4178
|
+
}),
|
|
4179
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4180
|
+
className: DebugPanel_module_css_default.section,
|
|
4181
|
+
"aria-labelledby": "smooth-stream-live-heading",
|
|
4182
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
4183
|
+
id: "smooth-stream-live-heading",
|
|
4184
|
+
children: t("debugSectionLive")
|
|
4185
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
|
|
4186
|
+
className: DebugPanel_module_css_default.metrics,
|
|
4187
|
+
children: [
|
|
4188
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4189
|
+
label: t("debugFps"),
|
|
4190
|
+
value: fixed(metrics.fps, 0),
|
|
4191
|
+
tone: (metrics.fps ?? 60) < 45 ? "warn" : "good"
|
|
4192
|
+
}),
|
|
4193
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4194
|
+
label: t("debugFrameTime"),
|
|
4195
|
+
value: `${fixed(metrics.frameMs)} ms`
|
|
4196
|
+
}),
|
|
4197
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4198
|
+
label: t("debugBacklog"),
|
|
4199
|
+
value: String(metrics.streamBacklog),
|
|
4200
|
+
tone: metrics.streamBacklog > 32 ? "warn" : void 0
|
|
4201
|
+
}),
|
|
4202
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4203
|
+
label: t("debugRevealSpeed"),
|
|
4204
|
+
value: `${fixed(metrics.streamSpeedCps, 0)} cps`
|
|
4205
|
+
}),
|
|
4206
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4207
|
+
label: t("debugProgress"),
|
|
4208
|
+
value: progress
|
|
4209
|
+
}),
|
|
4210
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4211
|
+
label: t("debugFollowState"),
|
|
4212
|
+
value: t(metrics.followFollowing ? "debugFollowing" : "debugReleased")
|
|
4213
|
+
}),
|
|
4214
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4215
|
+
label: t("debugLag"),
|
|
4216
|
+
value: `${fixed(metrics.followLagPx)} px`,
|
|
4217
|
+
tone: metrics.followConstrained ? "warn" : void 0
|
|
4218
|
+
}),
|
|
4219
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4220
|
+
label: t("debugVelocity"),
|
|
4221
|
+
value: `${fixed(metrics.followVelocityPxPerSec, 0)} px/s`
|
|
4222
|
+
}),
|
|
4223
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4224
|
+
label: t("debugReserve"),
|
|
4225
|
+
value: `${fixed(metrics.followReservePx)} px`
|
|
4226
|
+
}),
|
|
4227
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4228
|
+
label: t("debugCapacity"),
|
|
4229
|
+
value: `${fixed(metrics.followCapacityPx)} px`
|
|
4230
|
+
}),
|
|
4231
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4232
|
+
label: t("debugAppliedScale"),
|
|
4233
|
+
value: `${fixed(metrics.followRevealScale, 2)}x`
|
|
4234
|
+
})
|
|
4235
|
+
]
|
|
4236
|
+
})]
|
|
4237
|
+
}),
|
|
4238
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4239
|
+
className: DebugPanel_module_css_default.section,
|
|
4240
|
+
"aria-labelledby": "smooth-stream-reveal-heading",
|
|
4241
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
4242
|
+
id: "smooth-stream-reveal-heading",
|
|
4243
|
+
children: t("debugSectionReveal")
|
|
4244
|
+
}), REVEAL_CONTROLS.map((control) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TuningField, {
|
|
4245
|
+
control,
|
|
4246
|
+
state,
|
|
4247
|
+
edit: props.edit,
|
|
4248
|
+
label: t(control.label),
|
|
4249
|
+
t
|
|
4250
|
+
}, control.key))]
|
|
4251
|
+
}),
|
|
4252
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4253
|
+
className: DebugPanel_module_css_default.section,
|
|
4254
|
+
"aria-labelledby": "smooth-stream-follow-heading",
|
|
4255
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
4256
|
+
id: "smooth-stream-follow-heading",
|
|
4257
|
+
children: t("debugSectionFollow")
|
|
4258
|
+
}), FOLLOW_CONTROLS.map((control) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TuningField, {
|
|
4259
|
+
control,
|
|
4260
|
+
state,
|
|
4261
|
+
edit: props.edit,
|
|
4262
|
+
label: t(control.label),
|
|
4263
|
+
t
|
|
4264
|
+
}, control.key))]
|
|
4265
|
+
})
|
|
4266
|
+
]
|
|
4267
|
+
}),
|
|
4268
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
|
|
4269
|
+
className: DebugPanel_module_css_default.footer,
|
|
4270
|
+
children: [
|
|
4271
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4272
|
+
className: DebugPanel_module_css_default.secondaryButton,
|
|
4273
|
+
type: "button",
|
|
4274
|
+
disabled: !state.writable,
|
|
4275
|
+
onClick: props.reset,
|
|
4276
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, {}), t("debugReset")]
|
|
4277
|
+
}),
|
|
4278
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: DebugPanel_module_css_default.footerSpacer }),
|
|
4279
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4280
|
+
className: DebugPanel_module_css_default.secondaryButton,
|
|
4281
|
+
type: "button",
|
|
4282
|
+
disabled: !state.writable || !state.dirty,
|
|
4283
|
+
onClick: props.discard,
|
|
4284
|
+
children: t("debugDiscard")
|
|
4285
|
+
}),
|
|
4286
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4287
|
+
className: DebugPanel_module_css_default.primaryButton,
|
|
4288
|
+
type: "button",
|
|
4289
|
+
disabled: !state.writable || !state.dirty,
|
|
4290
|
+
onClick: props.save,
|
|
4291
|
+
children: t("debugSave")
|
|
4292
|
+
})
|
|
4293
|
+
]
|
|
4294
|
+
})
|
|
4295
|
+
]
|
|
4296
|
+
});
|
|
4297
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4298
|
+
type: "button",
|
|
4299
|
+
className: state.enabled && open ? `${DebugPanel_module_css_default.trigger} ${DebugPanel_module_css_default.triggerActive}` : DebugPanel_module_css_default.trigger,
|
|
4300
|
+
"aria-expanded": state.enabled && open,
|
|
4301
|
+
"aria-label": t("debugPanelToggle"),
|
|
4302
|
+
title: t("debugPanelToggle"),
|
|
4303
|
+
onClick: () => {
|
|
4304
|
+
setOpen((current) => !current);
|
|
4305
|
+
},
|
|
4306
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCodeOutline16, {})
|
|
4307
|
+
}), typeof document === "undefined" || panel === null ? null : (0, react_dom.createPortal)(panel, document.body)] });
|
|
4308
|
+
}
|
|
4309
|
+
//#endregion
|
|
2322
4310
|
//#region src/client/locales.ts
|
|
2323
4311
|
/** Locale bundles for the smooth-stream plugin configuration card. */
|
|
2324
4312
|
/** Dictionary namespace owned by this plugin's settings card. */
|
|
@@ -2327,8 +4315,61 @@ window.__ModuleLoader__.load({
|
|
|
2327
4315
|
const en = {
|
|
2328
4316
|
title: "Smooth stream",
|
|
2329
4317
|
description: "How replies are revealed while they stream.",
|
|
4318
|
+
enabled: "Enable smooth streaming",
|
|
4319
|
+
enabledHint: "Let this plugin render and follow streaming replies. Turn off to use the built-in Harness renderer.",
|
|
2330
4320
|
thinkAutoExpand: "Auto-expand thinking",
|
|
2331
4321
|
thinkAutoExpandHint: "Open the thinking block while it streams. Turn off to keep it collapsed.",
|
|
4322
|
+
autoCollapse: "Collapse finished work",
|
|
4323
|
+
autoCollapseHint: "When a reply finishes, fold its thinking, tools, and intermediate output behind one “Processed” summary so only the final answer shows. Click the summary to expand again; works with streaming on or off.",
|
|
4324
|
+
debugEnabled: "Show render diagnostics",
|
|
4325
|
+
debugEnabledHint: "Show live streaming and scroll metrics on the right side of the chat. Tune values there, then save them here.",
|
|
4326
|
+
debugUnavailable: "Live diagnostics require a newer plugin Host.",
|
|
4327
|
+
debugPanelTitle: "Render diagnostics",
|
|
4328
|
+
debugPanelToggle: "Toggle render diagnostics",
|
|
4329
|
+
debugPanelClose: "Hide diagnostics panel",
|
|
4330
|
+
debugGuide: "Tune one value at a time while a reply streams. Keep FPS stable and backlog near zero. If text lags, raise the reveal multiplier, queue pressure, or maximum reveal; if a blank gap appears, reduce Predictive runway. Increase damping when the scroll feels springy. Save only after the behavior is stable; Reset restores the production defaults.",
|
|
4331
|
+
debugLive: "Streaming",
|
|
4332
|
+
debugIdle: "Idle",
|
|
4333
|
+
debugUnsaved: "Unsaved tuning",
|
|
4334
|
+
debugSave: "Save tuning",
|
|
4335
|
+
debugDiscard: "Discard changes",
|
|
4336
|
+
debugReset: "Reset tuning",
|
|
4337
|
+
debugCopy: "Copy diagnostics",
|
|
4338
|
+
debugCopied: "Copied",
|
|
4339
|
+
debugSectionLive: "Live renderer",
|
|
4340
|
+
debugSectionReveal: "Reveal tuning",
|
|
4341
|
+
debugSectionFollow: "Scroll tuning",
|
|
4342
|
+
debugFps: "FPS",
|
|
4343
|
+
debugFrameTime: "Frame",
|
|
4344
|
+
debugBacklog: "Backlog",
|
|
4345
|
+
debugRevealSpeed: "Reveal",
|
|
4346
|
+
debugProgress: "Progress",
|
|
4347
|
+
debugFollowState: "Follow",
|
|
4348
|
+
debugFollowing: "Pinned",
|
|
4349
|
+
debugReleased: "Released",
|
|
4350
|
+
debugLag: "Visual lag",
|
|
4351
|
+
debugVelocity: "Velocity",
|
|
4352
|
+
debugReserve: "Reserve",
|
|
4353
|
+
debugCapacity: "Capacity",
|
|
4354
|
+
debugAppliedScale: "Applied scale",
|
|
4355
|
+
debugRevealMultiplier: "Reveal multiplier",
|
|
4356
|
+
debugQueuePressure: "Queue pressure",
|
|
4357
|
+
debugMaxReveal: "Maximum reveal",
|
|
4358
|
+
debugSpringStiffness: "Spring stiffness",
|
|
4359
|
+
debugSpringDamping: "Spring damping",
|
|
4360
|
+
debugSpringMass: "Spring mass",
|
|
4361
|
+
debugRunway: "Predictive runway",
|
|
4362
|
+
debugReserveResponse: "Runway response",
|
|
4363
|
+
debugBackpressureMin: "Minimum backpressure",
|
|
4364
|
+
debugTipRevealMultiplier: "Overall reveal speed multiplier. Higher reveals text faster and clears backlog sooner, but can feel jumpy. Lower is smoother but takes longer to finish.",
|
|
4365
|
+
debugTipQueuePressure: "Backlog acceleration strength. Higher catches up to a growing queue more aggressively; lower keeps speed steadier but may leave backlog.",
|
|
4366
|
+
debugTipMaxReveal: "Hard cap for reveal speed in characters per second. Higher allows faster catch-up; lower limits bursts and keeps motion calmer.",
|
|
4367
|
+
debugTipSpringStiffness: "Scroll spring strength. Higher closes visual lag faster but can feel sharp; lower feels softer but follows more slowly.",
|
|
4368
|
+
debugTipSpringDamping: "Scroll energy damping. Higher suppresses overshoot and jitter but feels heavier; lower feels livelier but may oscillate.",
|
|
4369
|
+
debugTipSpringMass: "Scroll inertia. Higher makes movement slower and heavier; lower makes it react faster but can feel abrupt.",
|
|
4370
|
+
debugTipRunway: "Predictive blank space reserved while content grows. Higher absorbs growth and protects the bottom follow, but can create a larger visible gap; lower reduces blank space but leaves less room to absorb lag.",
|
|
4371
|
+
debugTipReserveResponse: "How quickly the reserved runway opens or closes. Higher changes more gradually; lower reacts faster but can look abrupt.",
|
|
4372
|
+
debugTipBackpressureMin: "Slowest reveal multiplier under scroll pressure. Higher keeps text moving but may increase visual lag; lower slows text more to protect smooth following.",
|
|
2332
4373
|
readOnly: "This deployment stores settings read-only.",
|
|
2333
4374
|
loading: "Loading plugin settings…",
|
|
2334
4375
|
unavailable: "Plugin settings are unavailable in this connection.",
|
|
@@ -2353,8 +4394,61 @@ window.__ModuleLoader__.load({
|
|
|
2353
4394
|
const zh = {
|
|
2354
4395
|
title: "丝滑流式",
|
|
2355
4396
|
description: "回复在流式输出时如何逐字展现。",
|
|
4397
|
+
enabled: "启用丝滑流式渲染",
|
|
4398
|
+
enabledHint: "由本插件渲染并跟随流式回复;关闭后使用 Harness 内置渲染。",
|
|
2356
4399
|
thinkAutoExpand: "自动展开思考",
|
|
2357
4400
|
thinkAutoExpandHint: "思考块在流式时自动展开;关闭后保持折叠,可手动展开。",
|
|
4401
|
+
autoCollapse: "完成后自动折叠",
|
|
4402
|
+
autoCollapseHint: "回复处理完成后,把思考、工具与过程输出折叠为一行“已处理”摘要,只展示最终回复;点击摘要可再次展开。开启或关闭流式渲染均可用,默认开启。",
|
|
4403
|
+
debugEnabled: "显示渲染调试面板",
|
|
4404
|
+
debugEnabledHint: "在聊天右侧显示流式渲染和滚动的实时参数,可在面板中调节并在这里保存。",
|
|
4405
|
+
debugUnavailable: "当前 Host 版本不支持实时调试,请先更新插件 Host。",
|
|
4406
|
+
debugPanelTitle: "渲染诊断",
|
|
4407
|
+
debugPanelToggle: "显示或隐藏渲染诊断",
|
|
4408
|
+
debugPanelClose: "收起诊断面板",
|
|
4409
|
+
debugGuide: "流式输出时一次只调一个参数,观察帧率、积压和视觉滞后。优先保持帧率稳定、积压接近 0;如果文字滞后,提高揭示倍率、队列压力或最大揭示速度;如果底部出现空白,降低“预测预留空间”。滚动有回弹或抖动时提高阻尼。确认表现稳定后再保存;“恢复默认参数”会回到生产默认值。",
|
|
4410
|
+
debugLive: "正在流式输出",
|
|
4411
|
+
debugIdle: "空闲",
|
|
4412
|
+
debugUnsaved: "参数尚未保存",
|
|
4413
|
+
debugSave: "保存参数",
|
|
4414
|
+
debugDiscard: "放弃修改",
|
|
4415
|
+
debugReset: "恢复默认参数",
|
|
4416
|
+
debugCopy: "复制诊断数据",
|
|
4417
|
+
debugCopied: "已复制",
|
|
4418
|
+
debugSectionLive: "实时渲染",
|
|
4419
|
+
debugSectionReveal: "流式参数",
|
|
4420
|
+
debugSectionFollow: "滚动参数",
|
|
4421
|
+
debugFps: "帧率",
|
|
4422
|
+
debugFrameTime: "帧耗时",
|
|
4423
|
+
debugBacklog: "积压字符",
|
|
4424
|
+
debugRevealSpeed: "揭示速度",
|
|
4425
|
+
debugProgress: "渲染进度",
|
|
4426
|
+
debugFollowState: "跟随状态",
|
|
4427
|
+
debugFollowing: "跟随底部",
|
|
4428
|
+
debugReleased: "用户已释放",
|
|
4429
|
+
debugLag: "视觉滞后",
|
|
4430
|
+
debugVelocity: "滚动速度",
|
|
4431
|
+
debugReserve: "预留空间",
|
|
4432
|
+
debugCapacity: "安全容量",
|
|
4433
|
+
debugAppliedScale: "实际倍率",
|
|
4434
|
+
debugRevealMultiplier: "揭示倍率",
|
|
4435
|
+
debugQueuePressure: "队列压力",
|
|
4436
|
+
debugMaxReveal: "最大揭示速度",
|
|
4437
|
+
debugSpringStiffness: "弹簧刚度",
|
|
4438
|
+
debugSpringDamping: "弹簧阻尼",
|
|
4439
|
+
debugSpringMass: "弹簧质量",
|
|
4440
|
+
debugRunway: "预测预留空间",
|
|
4441
|
+
debugReserveResponse: "预留响应时间",
|
|
4442
|
+
debugBackpressureMin: "最小背压倍率",
|
|
4443
|
+
debugTipRevealMultiplier: "整体文字揭示速度倍率。调大能更快清空积压,但可能显得跳;调小更平滑,但完成回复需要更久。",
|
|
4444
|
+
debugTipQueuePressure: "积压对加速的影响强度。调大能更积极追赶增长中的队列;调小速度更稳定,但积压可能持续。",
|
|
4445
|
+
debugTipMaxReveal: "每秒揭示字符数上限。调大允许更快追赶;调小限制突发速度,让动作更平稳。",
|
|
4446
|
+
debugTipSpringStiffness: "滚动弹簧刚度。调大更快收拢视觉滞后,但感觉更硬;调小更柔和,但跟随更慢。",
|
|
4447
|
+
debugTipSpringDamping: "滚动能量阻尼。调大能抑制过冲和抖动,但感觉更沉;调小更灵活,但可能回弹。",
|
|
4448
|
+
debugTipSpringMass: "滚动惯性。调大移动更慢更沉;调小反应更快,但可能显得突兀。",
|
|
4449
|
+
debugTipRunway: "内容增长时预留的预测空白。调大更能吸收增长、保护底部跟随,但可能产生更大的可见空区;调小能减少空白,但可吸收滞后的空间也更少。",
|
|
4450
|
+
debugTipReserveResponse: "预留空间打开或关闭的响应时间。调大变化更渐进;调小反应更快,但可能显得突变。",
|
|
4451
|
+
debugTipBackpressureMin: "滚动压力下允许的最低揭示倍率。调大文字仍会较快前进,但视觉滞后可能增加;调小会更积极减速,以保护跟随平滑。",
|
|
2358
4452
|
readOnly: "本部署的设置为只读。",
|
|
2359
4453
|
loading: "正在加载插件设置…",
|
|
2360
4454
|
unavailable: "当前连接无法访问插件设置。",
|
|
@@ -2467,15 +4561,15 @@ window.__ModuleLoader__.load({
|
|
|
2467
4561
|
};
|
|
2468
4562
|
}
|
|
2469
4563
|
/**
|
|
2470
|
-
* A live
|
|
2471
|
-
* shared
|
|
2472
|
-
*
|
|
2473
|
-
* ships no settings surface keeps the cell on the default.
|
|
4564
|
+
* A live settings cell shared by the renderer lifecycle and React views. It
|
|
4565
|
+
* starts on the shared defaults and follows the plugin-owned controller once
|
|
4566
|
+
* the optional settings services arrive.
|
|
2474
4567
|
*/
|
|
2475
|
-
var
|
|
4568
|
+
var SettingsCell = class {
|
|
2476
4569
|
listeners = /* @__PURE__ */ new Set();
|
|
2477
4570
|
card;
|
|
2478
|
-
value = DEFAULT_STREAM_SETTINGS
|
|
4571
|
+
value = DEFAULT_STREAM_SETTINGS;
|
|
4572
|
+
pending = false;
|
|
2479
4573
|
/** Re-point the cell at the plugin-owned settings controller. */
|
|
2480
4574
|
attach(card) {
|
|
2481
4575
|
this.card = card;
|
|
@@ -2491,14 +4585,26 @@ window.__ModuleLoader__.load({
|
|
|
2491
4585
|
};
|
|
2492
4586
|
}
|
|
2493
4587
|
read() {
|
|
2494
|
-
|
|
4588
|
+
const snapshot = this.card?.getSnapshot();
|
|
4589
|
+
if (snapshot === void 0 || snapshot.status !== "ready") return this.value;
|
|
4590
|
+
return this.card?.values() ?? this.value;
|
|
2495
4591
|
}
|
|
2496
4592
|
refresh() {
|
|
2497
4593
|
const next = this.read();
|
|
2498
|
-
|
|
4594
|
+
const pending = this.card?.getSnapshot().status === "loading";
|
|
4595
|
+
if (pending === this.pending && next.enabled === this.value.enabled && next.thinkAutoExpand === this.value.thinkAutoExpand && next.autoCollapse === this.value.autoCollapse && next.debugEnabled === this.value.debugEnabled && next.debugTuning === this.value.debugTuning) return;
|
|
4596
|
+
this.pending = pending;
|
|
2499
4597
|
this.value = next;
|
|
2500
4598
|
for (const listener of this.listeners) listener();
|
|
2501
4599
|
}
|
|
4600
|
+
/** False while an available settings service is resolving its authority. */
|
|
4601
|
+
takeoverEnabled() {
|
|
4602
|
+
return !this.pending && this.value.enabled;
|
|
4603
|
+
}
|
|
4604
|
+
/** Whether finished turns should fold behind a summary row right now. */
|
|
4605
|
+
autoCollapseActive() {
|
|
4606
|
+
return !this.pending && this.value.autoCollapse;
|
|
4607
|
+
}
|
|
2502
4608
|
getSnapshot = () => this.value;
|
|
2503
4609
|
subscribe = (listener) => {
|
|
2504
4610
|
this.listeners.add(listener);
|
|
@@ -2520,14 +4626,51 @@ window.__ModuleLoader__.load({
|
|
|
2520
4626
|
*/
|
|
2521
4627
|
function apply(ctx) {
|
|
2522
4628
|
const config = readBootConfig();
|
|
2523
|
-
const
|
|
4629
|
+
const settings = new SettingsCell();
|
|
4630
|
+
ctx.inject(["slots"], () => {
|
|
4631
|
+
const autoCollapse = new AutoCollapseController();
|
|
4632
|
+
const syncAutoCollapse = () => {
|
|
4633
|
+
if (settings.autoCollapseActive()) autoCollapse.start();
|
|
4634
|
+
else autoCollapse.stop();
|
|
4635
|
+
};
|
|
4636
|
+
const unsubscribe = settings.subscribe(syncAutoCollapse);
|
|
4637
|
+
syncAutoCollapse();
|
|
4638
|
+
return () => {
|
|
4639
|
+
unsubscribe();
|
|
4640
|
+
autoCollapse.stop();
|
|
4641
|
+
};
|
|
4642
|
+
});
|
|
2524
4643
|
ctx.inject([
|
|
2525
4644
|
"slots",
|
|
2526
4645
|
"locale",
|
|
2527
4646
|
"connection"
|
|
2528
4647
|
], (settingsCtx) => {
|
|
2529
4648
|
const card = new SmoothStreamCardController(createSmoothStreamSettingsApi(settingsCtx.get("connection")));
|
|
2530
|
-
const
|
|
4649
|
+
const detachSettings = settings.attach(card);
|
|
4650
|
+
const syncDebug = () => {
|
|
4651
|
+
const snapshot = card.getSnapshot();
|
|
4652
|
+
debugRuntime.syncSettings({
|
|
4653
|
+
available: snapshot.debugAvailable,
|
|
4654
|
+
enabled: snapshot.debugEnabled,
|
|
4655
|
+
writable: snapshot.writable && !snapshot.saving,
|
|
4656
|
+
dirty: snapshot.dirty,
|
|
4657
|
+
status: snapshot.status,
|
|
4658
|
+
tuning: snapshot.debugTuning
|
|
4659
|
+
});
|
|
4660
|
+
};
|
|
4661
|
+
const detachBinding = debugRuntime.bindSettings({
|
|
4662
|
+
edit: (patch) => {
|
|
4663
|
+
card.inject().edit(patch);
|
|
4664
|
+
},
|
|
4665
|
+
save: () => {
|
|
4666
|
+
card.inject().save();
|
|
4667
|
+
},
|
|
4668
|
+
discard: () => {
|
|
4669
|
+
card.inject().discard();
|
|
4670
|
+
}
|
|
4671
|
+
});
|
|
4672
|
+
const detachDebug = card.subscribe(syncDebug);
|
|
4673
|
+
syncDebug();
|
|
2531
4674
|
card.start();
|
|
2532
4675
|
settingsCtx.effect(() => settingsCtx.locale.register(NS, {
|
|
2533
4676
|
zh,
|
|
@@ -2536,17 +4679,27 @@ window.__ModuleLoader__.load({
|
|
|
2536
4679
|
settingsCtx.slots.inject("settings.plugin.item", () => settingsCtx.slots.register({
|
|
2537
4680
|
name: "settings.plugin.item",
|
|
2538
4681
|
id: "smooth-stream",
|
|
4682
|
+
key: STREAM_SETTINGS_NS,
|
|
2539
4683
|
order: 30,
|
|
2540
4684
|
locale: NS,
|
|
2541
4685
|
inject: () => card.inject()
|
|
2542
4686
|
}, SmoothStreamCard));
|
|
4687
|
+
settingsCtx.slots.inject("conversation.session.header.utilities", () => settingsCtx.slots.register({
|
|
4688
|
+
name: "conversation.session.header.utilities",
|
|
4689
|
+
id: "smooth-stream-debug",
|
|
4690
|
+
order: 40,
|
|
4691
|
+
locale: NS,
|
|
4692
|
+
inject: () => debugRuntime.panelFace()
|
|
4693
|
+
}, DebugPanel));
|
|
2543
4694
|
return () => {
|
|
2544
4695
|
card.stop();
|
|
2545
|
-
|
|
4696
|
+
detachDebug();
|
|
4697
|
+
detachSettings();
|
|
4698
|
+
detachBinding();
|
|
2546
4699
|
};
|
|
2547
4700
|
});
|
|
2548
4701
|
const configured = function StreamConfiguredView(props) {
|
|
2549
|
-
const
|
|
4702
|
+
const preferences = (0, react.useSyncExternalStore)(settings.subscribe, settings.getSnapshot, settings.getSnapshot);
|
|
2550
4703
|
return (0, react.createElement)(TypewriterAssistantNodeView, {
|
|
2551
4704
|
...props,
|
|
2552
4705
|
mode: config.mode,
|
|
@@ -2554,21 +4707,36 @@ window.__ModuleLoader__.load({
|
|
|
2554
4707
|
revealCharsPerSec: config.revealCharsPerSec,
|
|
2555
4708
|
scrollSpeedPxPerSec: config.scrollSpeedPxPerSec,
|
|
2556
4709
|
maxScrollSpeedPxPerSec: config.maxScrollSpeedPxPerSec,
|
|
2557
|
-
thinkAutoExpand
|
|
4710
|
+
thinkAutoExpand: preferences.thinkAutoExpand
|
|
2558
4711
|
});
|
|
2559
4712
|
};
|
|
2560
4713
|
ctx.slots.inject("conversation.chat.node", () => {
|
|
2561
|
-
|
|
2562
|
-
const
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
4714
|
+
let releaseTakeover;
|
|
4715
|
+
const syncTakeover = () => {
|
|
4716
|
+
if (!settings.takeoverEnabled()) {
|
|
4717
|
+
releaseTakeover?.();
|
|
4718
|
+
releaseTakeover = void 0;
|
|
4719
|
+
return;
|
|
4720
|
+
}
|
|
4721
|
+
if (releaseTakeover !== void 0) return;
|
|
4722
|
+
const unwrap = wrapAgentChatRows(ctx);
|
|
4723
|
+
const unshadow = ctx.slots.register({
|
|
4724
|
+
name: "conversation.chat.node",
|
|
4725
|
+
key: "assistant-step",
|
|
4726
|
+
priority: -100,
|
|
4727
|
+
locale: "conversation",
|
|
4728
|
+
registrant: "dsh-smooth-stream"
|
|
4729
|
+
}, configured);
|
|
4730
|
+
releaseTakeover = () => {
|
|
4731
|
+
unwrap();
|
|
4732
|
+
unshadow();
|
|
4733
|
+
};
|
|
4734
|
+
};
|
|
4735
|
+
const unsubscribe = settings.subscribe(syncTakeover);
|
|
4736
|
+
syncTakeover();
|
|
2569
4737
|
return () => {
|
|
2570
|
-
|
|
2571
|
-
|
|
4738
|
+
unsubscribe();
|
|
4739
|
+
releaseTakeover?.();
|
|
2572
4740
|
};
|
|
2573
4741
|
});
|
|
2574
4742
|
}
|