dsh-smooth-stream 0.3.4 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +14 -4
- package/README.md +14 -4
- package/lib/client.js +2130 -142
- package/lib/client.js.map +1 -1
- package/lib/index.js +123 -6
- package/package.json +9 -9
- 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/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 +117 -32
- 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
|
+
"thinkSeparator": "I17U7q_thinkSeparator",
|
|
27
28
|
"body": "I17U7q_body",
|
|
28
|
-
"thinkRow": "I17U7q_thinkRow",
|
|
29
|
-
"thinkLeading": "I17U7q_thinkLeading",
|
|
30
|
-
"disclosureLeading": "I17U7q_disclosureLeading",
|
|
31
|
-
"disclosureTitle": "I17U7q_disclosureTitle",
|
|
32
|
-
"follow": "I17U7q_follow",
|
|
33
|
-
"disclosureRoot": "I17U7q_disclosureRoot",
|
|
34
29
|
"disclosureIconIdle": "I17U7q_disclosureIconIdle",
|
|
35
|
-
"
|
|
36
|
-
"disclosureRow": "I17U7q_disclosureRow",
|
|
37
|
-
"disclosureChevronHover": "I17U7q_disclosureChevronHover",
|
|
38
|
-
"visuallyHidden": "I17U7q_visuallyHidden",
|
|
39
|
-
"thinkSeparator": "I17U7q_thinkSeparator",
|
|
40
|
-
"root": "I17U7q_root",
|
|
30
|
+
"disclosureTitle": "I17U7q_disclosureTitle",
|
|
41
31
|
"think": "I17U7q_think",
|
|
42
|
-
"
|
|
43
|
-
"
|
|
32
|
+
"disclosureContent": "I17U7q_disclosureContent",
|
|
33
|
+
"thinkLeading": "I17U7q_thinkLeading",
|
|
34
|
+
"disclosureRow": "I17U7q_disclosureRow",
|
|
44
35
|
"stopped": "I17U7q_stopped",
|
|
36
|
+
"thinkChevron": "I17U7q_thinkChevron",
|
|
45
37
|
"thinkSummary": "I17U7q_thinkSummary",
|
|
46
|
-
"
|
|
38
|
+
"dsh-smooth-stream-think-sweep": "I17U7q_dsh-smooth-stream-think-sweep",
|
|
39
|
+
"visuallyHidden": "I17U7q_visuallyHidden",
|
|
40
|
+
"root": "I17U7q_root",
|
|
41
|
+
"disclosureRoot": "I17U7q_disclosureRoot",
|
|
42
|
+
"disclosureLeading": "I17U7q_disclosureLeading",
|
|
43
|
+
"thinkRow": "I17U7q_thinkRow",
|
|
44
|
+
"follow": "I17U7q_follow",
|
|
45
|
+
"disclosureChevronHover": "I17U7q_disclosureChevronHover",
|
|
46
|
+
"thinkBody": "I17U7q_thinkBody",
|
|
47
|
+
"thinkTitle": "I17U7q_thinkTitle"
|
|
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) => {
|
|
@@ -554,8 +851,6 @@ window.__ModuleLoader__.load({
|
|
|
554
851
|
const FOLLOW_OWNED_ATTR = "data-follow-owned";
|
|
555
852
|
/** Sub-pixel settle threshold; clearing below this cannot produce a visible rebound. */
|
|
556
853
|
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
854
|
/** Safe-lag occupancy band over which reveal pressure is progressively reduced. */
|
|
560
855
|
const FOLLOW_BACKPRESSURE_START_RATIO = .25;
|
|
561
856
|
const GESTURE_EVENTS = [
|
|
@@ -582,14 +877,15 @@ window.__ModuleLoader__.load({
|
|
|
582
877
|
* a constrained paint lands at the minimum immediately so the next reveal
|
|
583
878
|
* commit cannot keep feeding an already-full visual buffer.
|
|
584
879
|
*/
|
|
585
|
-
function computeFollowRevealScale(lagPx, capacityPx, constrained = false) {
|
|
586
|
-
if (constrained) return
|
|
880
|
+
function computeFollowRevealScale(lagPx, capacityPx, constrained = false, tuning = DEFAULT_STREAM_DEBUG_TUNING) {
|
|
881
|
+
if (constrained) return tuning.backpressureMinScale;
|
|
587
882
|
if (!Number.isFinite(capacityPx)) return 1;
|
|
588
|
-
if (capacityPx <= 0) return lagPx > 0 ?
|
|
883
|
+
if (capacityPx <= 0) return lagPx > 0 ? tuning.backpressureMinScale : 1;
|
|
589
884
|
const ratio = Math.min(1, Math.max(0, lagPx / capacityPx));
|
|
590
|
-
if (ratio >= .75) return
|
|
885
|
+
if (ratio >= .75) return tuning.backpressureMinScale;
|
|
591
886
|
const progress = Math.min(1, Math.max(0, (ratio - FOLLOW_BACKPRESSURE_START_RATIO) / .5));
|
|
592
|
-
|
|
887
|
+
const eased = progress * progress * (3 - 2 * progress);
|
|
888
|
+
return 1 - (1 - tuning.backpressureMinScale) * eased;
|
|
593
889
|
}
|
|
594
890
|
/**
|
|
595
891
|
* Semi-implicit spring integration with four substeps per <=32ms slice.
|
|
@@ -597,7 +893,7 @@ window.__ModuleLoader__.load({
|
|
|
597
893
|
* @param input - Current visible lag and carried physics velocity.
|
|
598
894
|
* @returns The position advance, its fraction, and next velocity.
|
|
599
895
|
*/
|
|
600
|
-
function computeFollowStep(dtMs, input) {
|
|
896
|
+
function computeFollowStep(dtMs, input, tuning = DEFAULT_STREAM_DEBUG_TUNING) {
|
|
601
897
|
if (input.lag <= .1 || dtMs <= 0) return {
|
|
602
898
|
advancePx: 0,
|
|
603
899
|
lerpStep: 0,
|
|
@@ -609,7 +905,7 @@ window.__ModuleLoader__.load({
|
|
|
609
905
|
const slices = Math.max(1, Math.ceil(elapsedMs / 32));
|
|
610
906
|
const subDt = elapsedMs / 1e3 / slices / 4;
|
|
611
907
|
for (let slice = 0; slice < slices; slice += 1) for (let substep = 0; substep < 4; substep += 1) {
|
|
612
|
-
const acceleration = (
|
|
908
|
+
const acceleration = (tuning.springStiffness * lag - tuning.springDamping * velocity) / tuning.springMass;
|
|
613
909
|
velocity = Math.max(0, velocity + acceleration * subDt);
|
|
614
910
|
const advance = velocity * subDt;
|
|
615
911
|
if (advance >= lag) return {
|
|
@@ -630,11 +926,29 @@ window.__ModuleLoader__.load({
|
|
|
630
926
|
function resizeProxyOf(port) {
|
|
631
927
|
return port.querySelector("[data-chat-transcript]") ?? port.querySelector("[data-chat-flow]");
|
|
632
928
|
}
|
|
633
|
-
/**
|
|
929
|
+
/**
|
|
930
|
+
* Outermost message surfaces; nested tool rows ride their parent.
|
|
931
|
+
*
|
|
932
|
+
* Another plugin may insert its own element as a flow sibling of the Chat rows
|
|
933
|
+
* (meow-memory's fold bar is one; so is this plugin's own fold summary row).
|
|
934
|
+
* Such a row carries no `data-chat-anchor-key`, so selecting only anchored
|
|
935
|
+
* rows would shift the conversation while leaving the foreign row at its
|
|
936
|
+
* natural offset, letting the shifted rows paint over it. Every direct flow
|
|
937
|
+
* child therefore rides the same transform, keeping the visual order of the
|
|
938
|
+
* column intact.
|
|
939
|
+
*/
|
|
634
940
|
function shiftSurfacesOf(port) {
|
|
635
941
|
const transcript = port.querySelector("[data-chat-transcript]");
|
|
636
942
|
if (transcript !== null) return [transcript];
|
|
637
|
-
|
|
943
|
+
const anchored = [...port.querySelectorAll("[data-chat-anchor-key]")].filter((row) => row.parentElement?.closest("[data-chat-anchor-key]") === null);
|
|
944
|
+
const flow = port.querySelector("[data-chat-flow]");
|
|
945
|
+
if (flow === null) return anchored;
|
|
946
|
+
const status = turnStatusOf(port);
|
|
947
|
+
const anchoredSet = new Set(anchored);
|
|
948
|
+
const __dbg = [...flow.children].filter((child) => child instanceof HTMLElement && child !== status && (anchoredSet.has(child) || child.querySelector("[data-chat-anchor-key]") === null));
|
|
949
|
+
console.log("[engine] reached end of shiftSurfacesOf, n=", __dbg.length);
|
|
950
|
+
globalThis.__dshssProbe = __dbg.map((e) => `${e.tagName}:${e.getAttribute("data-chat-anchor-key") ?? e.className}`);
|
|
951
|
+
return __dbg;
|
|
638
952
|
}
|
|
639
953
|
function currentShiftOf(element) {
|
|
640
954
|
return Number(/translate3d\(0(?:px)?,\s*(-?[\d.]+)px,\s*0(?:px)?\)/.exec(element.style.transform)?.[1] ?? 0);
|
|
@@ -713,12 +1027,12 @@ window.__ModuleLoader__.load({
|
|
|
713
1027
|
}
|
|
714
1028
|
if (migrated) invalidatePaintLimit(port);
|
|
715
1029
|
}
|
|
716
|
-
function ensureRunway(port, surfaces) {
|
|
1030
|
+
function ensureRunway(port, surfaces, runwayPx = 48) {
|
|
717
1031
|
const status = turnStatusOf(port);
|
|
718
1032
|
const composer = port.querySelector("[data-composer-seat]");
|
|
719
1033
|
migrateLegacyRunway(port, surfaces, status, composer);
|
|
720
1034
|
const naturalHeight = Math.max(0, port.scrollHeight - runwayOffsetOf(port));
|
|
721
|
-
if (port.clientHeight <= 0 || naturalHeight <= port.clientHeight) {
|
|
1035
|
+
if (runwayPx <= 0 || port.clientHeight <= 0 || naturalHeight <= port.clientHeight) {
|
|
722
1036
|
restoreRunway(port);
|
|
723
1037
|
return;
|
|
724
1038
|
}
|
|
@@ -735,17 +1049,18 @@ window.__ModuleLoader__.load({
|
|
|
735
1049
|
}
|
|
736
1050
|
const element = target.element;
|
|
737
1051
|
const current = followRunways.get(port);
|
|
738
|
-
if (current?.element === element && current.property === target.property) return;
|
|
1052
|
+
if (current?.element === element && current.property === target.property && current.requestedPx === runwayPx) return;
|
|
739
1053
|
restoreRunway(port);
|
|
740
1054
|
const beforeHeight = port.scrollHeight;
|
|
741
1055
|
const original = element.style[target.property];
|
|
742
|
-
element.style[target.property] = original === "" ? `
|
|
1056
|
+
element.style[target.property] = original === "" ? `${runwayPx}px` : `calc(${original} + ${runwayPx}px)`;
|
|
743
1057
|
const offset = Math.max(0, port.scrollHeight - beforeHeight);
|
|
744
1058
|
followRunways.set(port, {
|
|
745
1059
|
element,
|
|
746
1060
|
offset,
|
|
747
1061
|
property: target.property,
|
|
748
|
-
original
|
|
1062
|
+
original,
|
|
1063
|
+
requestedPx: runwayPx
|
|
749
1064
|
});
|
|
750
1065
|
invalidatePaintLimit(port);
|
|
751
1066
|
}
|
|
@@ -808,9 +1123,9 @@ window.__ModuleLoader__.load({
|
|
|
808
1123
|
* same frame. Carrying that excess in `scrollTop` would move the transcript
|
|
809
1124
|
* toward fixed chrome and also make the host expose jump-to-bottom.
|
|
810
1125
|
*/
|
|
811
|
-
function applyVisual(port, animatedH, reservePx, velocityPxPerSec = 0) {
|
|
1126
|
+
function applyVisual(port, animatedH, reservePx, velocityPxPerSec = 0, runwayPx = 48) {
|
|
812
1127
|
const surfaces = shiftSurfacesOf(port);
|
|
813
|
-
ensureRunway(port, surfaces);
|
|
1128
|
+
ensureRunway(port, surfaces, runwayPx);
|
|
814
1129
|
const contentHeight = Math.max(0, port.scrollHeight);
|
|
815
1130
|
const runwayOffset = runwayOffsetOf(port);
|
|
816
1131
|
const targetHeight = Math.max(0, contentHeight - runwayOffset);
|
|
@@ -956,9 +1271,10 @@ window.__ModuleLoader__.load({
|
|
|
956
1271
|
};
|
|
957
1272
|
const updateRevealScale = (next, elapsedMs, urgent = false) => {
|
|
958
1273
|
if (revealScaleRef === void 0) return;
|
|
1274
|
+
const tuning = debugRuntime.activeTuning();
|
|
959
1275
|
const state = followMotionStates.get(next);
|
|
960
|
-
const target = state === void 0 ? 1 : computeFollowRevealScale(state.lagPx, state.capacityPx, state.constrained);
|
|
961
|
-
const current = Math.min(1, Math.max(
|
|
1276
|
+
const target = state === void 0 ? 1 : computeFollowRevealScale(state.lagPx, state.capacityPx, state.constrained, tuning);
|
|
1277
|
+
const current = Math.min(1, Math.max(tuning.backpressureMinScale, revealScaleRef.current));
|
|
962
1278
|
if (target < current || urgent) {
|
|
963
1279
|
revealScaleRef.current = Math.min(current, target);
|
|
964
1280
|
return;
|
|
@@ -969,6 +1285,22 @@ window.__ModuleLoader__.load({
|
|
|
969
1285
|
const releaseRevealScale = () => {
|
|
970
1286
|
if (revealScaleRef !== void 0) revealScaleRef.current = 1;
|
|
971
1287
|
};
|
|
1288
|
+
const reportFollow = (next, isActive) => {
|
|
1289
|
+
const state = followMotionStates.get(next);
|
|
1290
|
+
debugRuntime.reportFollow(next, {
|
|
1291
|
+
lagPx: state?.lagPx ?? Math.max(0, next.scrollHeight - next.clientHeight - next.scrollTop),
|
|
1292
|
+
velocityPxPerSec: state?.velocityPxPerSec ?? 0,
|
|
1293
|
+
reservePx: state?.reservePx ?? 0,
|
|
1294
|
+
capacityPx: state?.capacityPx ?? 0,
|
|
1295
|
+
revealScale: revealScaleRef?.current ?? 1,
|
|
1296
|
+
following,
|
|
1297
|
+
constrained: state?.constrained ?? false,
|
|
1298
|
+
scrollTop: next.scrollTop,
|
|
1299
|
+
scrollHeight: next.scrollHeight,
|
|
1300
|
+
clientHeight: next.clientHeight,
|
|
1301
|
+
active: isActive
|
|
1302
|
+
});
|
|
1303
|
+
};
|
|
972
1304
|
const isLeader = (next) => followLeaders.get(next)?.owner === owner;
|
|
973
1305
|
const hold = (next) => {
|
|
974
1306
|
followActivityAt.set(next, performance.now());
|
|
@@ -986,11 +1318,14 @@ window.__ModuleLoader__.load({
|
|
|
986
1318
|
clearMotion(next);
|
|
987
1319
|
followMotionStates.delete(next);
|
|
988
1320
|
releaseRevealScale();
|
|
1321
|
+
debugRuntime.reportFollow(next, null);
|
|
989
1322
|
}
|
|
990
1323
|
};
|
|
991
1324
|
const handBackVisual = (next) => {
|
|
992
1325
|
const shift = currentShiftOf(shiftSurfacesOf(next).at(-1) ?? next);
|
|
993
1326
|
const visualTop = Math.max(0, next.scrollTop - Math.max(0, shift));
|
|
1327
|
+
clearMotion(next);
|
|
1328
|
+
restoreRunway(next);
|
|
994
1329
|
const floor = Math.max(0, next.scrollHeight - next.clientHeight);
|
|
995
1330
|
next.scrollTop = Math.min(visualTop, Math.max(0, floor - 26));
|
|
996
1331
|
followScrollLedgers.set(next, next.scrollTop);
|
|
@@ -1018,8 +1353,10 @@ window.__ModuleLoader__.load({
|
|
|
1018
1353
|
const restoreBeforePaint = () => {
|
|
1019
1354
|
if (!following || port === null || !isLeader(port)) return;
|
|
1020
1355
|
invalidatePaintLimit(port);
|
|
1021
|
-
|
|
1356
|
+
const tuning = debugRuntime.activeTuning();
|
|
1357
|
+
animatedH = applyVisual(port, animatedH, reservePx, velocityPxPerSec, tuning.runwayPx);
|
|
1022
1358
|
updateRevealScale(port, 0, true);
|
|
1359
|
+
reportFollow(port, activeRef.current);
|
|
1023
1360
|
};
|
|
1024
1361
|
const bindPort = (next) => {
|
|
1025
1362
|
if (port === next) return;
|
|
@@ -1041,6 +1378,7 @@ window.__ModuleLoader__.load({
|
|
|
1041
1378
|
rafId = requestAnimationFrame(frame);
|
|
1042
1379
|
const elapsedMs = Math.max(.001, now - last);
|
|
1043
1380
|
const dt = Math.min(32, elapsedMs);
|
|
1381
|
+
const tuning = debugRuntime.activeTuning();
|
|
1044
1382
|
last = now;
|
|
1045
1383
|
const root = rootRef.current;
|
|
1046
1384
|
if (root === null) return;
|
|
@@ -1067,8 +1405,9 @@ window.__ModuleLoader__.load({
|
|
|
1067
1405
|
if (following) {
|
|
1068
1406
|
hold(nextPort);
|
|
1069
1407
|
if (isLeader(nextPort)) {
|
|
1070
|
-
animatedH = applyVisual(nextPort, animatedH, reservePx, velocityPxPerSec);
|
|
1408
|
+
animatedH = applyVisual(nextPort, animatedH, reservePx, velocityPxPerSec, tuning.runwayPx);
|
|
1071
1409
|
updateRevealScale(nextPort, elapsedMs);
|
|
1410
|
+
reportFollow(nextPort, activeRef.current);
|
|
1072
1411
|
const runwayOffset = runwayOffsetOf(nextPort);
|
|
1073
1412
|
if (Math.max(0, nextPort.scrollHeight - animatedH - runwayOffset) <= .25) finishEntrance();
|
|
1074
1413
|
} else finishEntrance();
|
|
@@ -1076,7 +1415,7 @@ window.__ModuleLoader__.load({
|
|
|
1076
1415
|
primed = true;
|
|
1077
1416
|
return;
|
|
1078
1417
|
}
|
|
1079
|
-
if (!following && !interacting && reportedLag <= (readerReleased ? 1 : 25)) {
|
|
1418
|
+
if (!following && (!interacting || readerReleased && !readerGestureIntent && reportedLag <= 1) && reportedLag <= (readerReleased ? 1 : 25)) {
|
|
1080
1419
|
following = true;
|
|
1081
1420
|
readerReleased = false;
|
|
1082
1421
|
animatedH = extent;
|
|
@@ -1097,6 +1436,7 @@ window.__ModuleLoader__.load({
|
|
|
1097
1436
|
}
|
|
1098
1437
|
if (!activeRef.current || !following) {
|
|
1099
1438
|
followScrollLedgers.set(nextPort, nextPort.scrollTop);
|
|
1439
|
+
reportFollow(nextPort, activeRef.current);
|
|
1100
1440
|
return;
|
|
1101
1441
|
}
|
|
1102
1442
|
hold(nextPort);
|
|
@@ -1108,14 +1448,14 @@ window.__ModuleLoader__.load({
|
|
|
1108
1448
|
const contentHeight = nextPort.scrollHeight;
|
|
1109
1449
|
const lag = Math.max(0, contentHeight - animatedH - runwayOffset);
|
|
1110
1450
|
const predictGrowth = predictiveRef?.current ?? predictive;
|
|
1111
|
-
const reserveTarget = !predictGrowth ? 0 : computeFollowReserve(speedCpsRef.current,
|
|
1112
|
-
const reserveStep = 1 - Math.exp(-elapsedMs /
|
|
1451
|
+
const reserveTarget = !predictGrowth ? 0 : computeFollowReserve(speedCpsRef.current, tuning.runwayPx);
|
|
1452
|
+
const reserveStep = 1 - Math.exp(-elapsedMs / tuning.reserveResponseMs);
|
|
1113
1453
|
reservePx += (reserveTarget - reservePx) * reserveStep;
|
|
1114
1454
|
const step = computeFollowStep(dt, {
|
|
1115
1455
|
lag,
|
|
1116
1456
|
speedEma: speedCpsRef.current,
|
|
1117
1457
|
velocityPxPerSec
|
|
1118
|
-
});
|
|
1458
|
+
}, tuning);
|
|
1119
1459
|
if (lag <= .1) {
|
|
1120
1460
|
animatedH = contentHeight - runwayOffset;
|
|
1121
1461
|
velocityPxPerSec = 0;
|
|
@@ -1124,8 +1464,9 @@ window.__ModuleLoader__.load({
|
|
|
1124
1464
|
animatedH = Math.min(contentHeight - runwayOffset - minimumLag, animatedH + step.advancePx);
|
|
1125
1465
|
velocityPxPerSec = step.velocityPxPerSec;
|
|
1126
1466
|
}
|
|
1127
|
-
animatedH = applyVisual(nextPort, animatedH, reservePx, velocityPxPerSec);
|
|
1467
|
+
animatedH = applyVisual(nextPort, animatedH, reservePx, velocityPxPerSec, tuning.runwayPx);
|
|
1128
1468
|
updateRevealScale(nextPort, elapsedMs);
|
|
1469
|
+
reportFollow(nextPort, true);
|
|
1129
1470
|
if (Math.max(0, nextPort.scrollHeight - animatedH - runwayOffsetOf(nextPort)) <= .25) finishEntrance();
|
|
1130
1471
|
};
|
|
1131
1472
|
frame(performance.now());
|
|
@@ -1143,6 +1484,7 @@ window.__ModuleLoader__.load({
|
|
|
1143
1484
|
clearVisual(host);
|
|
1144
1485
|
followLeaders.delete(host);
|
|
1145
1486
|
releaseRevealScale();
|
|
1487
|
+
debugRuntime.reportFollow(host, null);
|
|
1146
1488
|
return;
|
|
1147
1489
|
}
|
|
1148
1490
|
if (preserveReader) {
|
|
@@ -1150,19 +1492,23 @@ window.__ModuleLoader__.load({
|
|
|
1150
1492
|
clearVisual(host);
|
|
1151
1493
|
followLeaders.delete(host);
|
|
1152
1494
|
releaseRevealScale();
|
|
1495
|
+
debugRuntime.reportFollow(host, null);
|
|
1153
1496
|
return;
|
|
1154
1497
|
}
|
|
1155
|
-
|
|
1498
|
+
const completionTuning = debugRuntime.activeTuning();
|
|
1499
|
+
ensureRunway(host, shiftSurfacesOf(host), completionTuning.runwayPx);
|
|
1156
1500
|
const completionRunway = runwayOffsetOf(host);
|
|
1157
1501
|
const completionMinimumLag = Math.max(0, reservePx);
|
|
1158
1502
|
animatedH = Math.min(animatedH, host.scrollHeight - completionRunway - completionMinimumLag);
|
|
1159
1503
|
settleAtFloor(host);
|
|
1160
|
-
animatedH = applyVisual(host, animatedH, reservePx, velocityPxPerSec);
|
|
1504
|
+
animatedH = applyVisual(host, animatedH, reservePx, velocityPxPerSec, completionTuning.runwayPx);
|
|
1505
|
+
reportFollow(host, false);
|
|
1161
1506
|
const runwayOffset = runwayOffsetOf(host);
|
|
1162
1507
|
if (Math.max(0, host.scrollHeight - animatedH - runwayOffset) <= .25 && reservePx <= .25) {
|
|
1163
1508
|
finishAtNaturalFloor(host);
|
|
1164
1509
|
followLeaders.delete(host);
|
|
1165
1510
|
releaseRevealScale();
|
|
1511
|
+
debugRuntime.reportFollow(host, null);
|
|
1166
1512
|
return;
|
|
1167
1513
|
}
|
|
1168
1514
|
for (const name of GESTURE_EVENTS) host.addEventListener(name, markGesture, { passive: true });
|
|
@@ -1185,14 +1531,16 @@ window.__ModuleLoader__.load({
|
|
|
1185
1531
|
clearVisual(host);
|
|
1186
1532
|
followLeaders.delete(host);
|
|
1187
1533
|
releaseRevealScale();
|
|
1534
|
+
debugRuntime.reportFollow(host, null);
|
|
1188
1535
|
stopSettleListeners();
|
|
1189
1536
|
return;
|
|
1190
1537
|
}
|
|
1191
1538
|
const dt = Math.min(32, Math.max(0, now - settleLast));
|
|
1539
|
+
const tuning = debugRuntime.activeTuning();
|
|
1192
1540
|
settleLast = now;
|
|
1193
1541
|
const runwayOffset = runwayOffsetOf(host);
|
|
1194
1542
|
const lag = Math.max(0, host.scrollHeight - animatedH - runwayOffset);
|
|
1195
|
-
const reserveStep = 1 - Math.exp(-dt /
|
|
1543
|
+
const reserveStep = 1 - Math.exp(-dt / tuning.reserveResponseMs);
|
|
1196
1544
|
reservePx += (0 - reservePx) * reserveStep;
|
|
1197
1545
|
if (lag <= .25 && reservePx <= .25) {
|
|
1198
1546
|
animatedH = host.scrollHeight - runwayOffset;
|
|
@@ -1201,6 +1549,7 @@ window.__ModuleLoader__.load({
|
|
|
1201
1549
|
finishAtNaturalFloor(host);
|
|
1202
1550
|
followLeaders.delete(host);
|
|
1203
1551
|
releaseRevealScale();
|
|
1552
|
+
debugRuntime.reportFollow(host, null);
|
|
1204
1553
|
stopSettleListeners();
|
|
1205
1554
|
return;
|
|
1206
1555
|
}
|
|
@@ -1208,12 +1557,13 @@ window.__ModuleLoader__.load({
|
|
|
1208
1557
|
lag,
|
|
1209
1558
|
speedEma: speedCpsRef.current,
|
|
1210
1559
|
velocityPxPerSec
|
|
1211
|
-
});
|
|
1560
|
+
}, tuning);
|
|
1212
1561
|
const minimumLag = Math.max(0, reservePx);
|
|
1213
1562
|
animatedH = Math.min(host.scrollHeight - runwayOffset - minimumLag, animatedH + step.advancePx);
|
|
1214
1563
|
velocityPxPerSec = step.velocityPxPerSec;
|
|
1215
1564
|
settleAtFloor(host);
|
|
1216
|
-
animatedH = applyVisual(host, animatedH, reservePx, velocityPxPerSec);
|
|
1565
|
+
animatedH = applyVisual(host, animatedH, reservePx, velocityPxPerSec, tuning.runwayPx);
|
|
1566
|
+
reportFollow(host, false);
|
|
1217
1567
|
requestAnimationFrame(settleFrame);
|
|
1218
1568
|
};
|
|
1219
1569
|
requestAnimationFrame(settleFrame);
|
|
@@ -1278,10 +1628,6 @@ window.__ModuleLoader__.load({
|
|
|
1278
1628
|
*/
|
|
1279
1629
|
const STREAM_BOOT_GLOBAL = "__DSH_SMOOTH_STREAM_CONFIG__";
|
|
1280
1630
|
//#endregion
|
|
1281
|
-
//#region src/settings.ts
|
|
1282
|
-
/** Defaults shared by the Host schema and the client-side fallback. */
|
|
1283
|
-
const DEFAULT_STREAM_SETTINGS = { thinkAutoExpand: true };
|
|
1284
|
-
//#endregion
|
|
1285
1631
|
//#region src/client/TypewriterAssistantNodeView.tsx
|
|
1286
1632
|
function usePrefersReducedMotion() {
|
|
1287
1633
|
const [reduced, setReduced] = (0, react.useState)(() => typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true);
|
|
@@ -1795,6 +2141,805 @@ window.__ModuleLoader__.load({
|
|
|
1795
2141
|
});
|
|
1796
2142
|
});
|
|
1797
2143
|
//#endregion
|
|
2144
|
+
//#region src/client/auto-collapse-controller.ts
|
|
2145
|
+
/**
|
|
2146
|
+
* Auto-collapse coordinator: folds the work process of finished turns behind
|
|
2147
|
+
* one summary row so only the model's final answer stays visible.
|
|
2148
|
+
*
|
|
2149
|
+
* The Harness ChatView lays a conversation out as a flat list of flow items
|
|
2150
|
+
* (`[data-chat-flow] > [data-chat-flow-kind=…]`). This controller watches that
|
|
2151
|
+
* list, splits it into turn segments at `user`/`steering` boundaries closed by
|
|
2152
|
+
* `turn-tail`, and — once a segment has settled — hides its thinking rows,
|
|
2153
|
+
* tool/command/context seats and intermediate replies, leaving one clickable
|
|
2154
|
+
* `已处理 {时长}` row plus the final answer. Clicking the row expands the full
|
|
2155
|
+
* process again; the choice survives later renders because it lives in the
|
|
2156
|
+
* controller, not the DOM.
|
|
2157
|
+
*
|
|
2158
|
+
* Adapted from the standalone `dsh-auto-collapse` plugin (level-1 folding
|
|
2159
|
+
* only): no second-level chips, no merged thinking rows, no status-text
|
|
2160
|
+
* rewrite, no transition animation — the collapse decision itself stays
|
|
2161
|
+
* instantaneous and reversible.
|
|
2162
|
+
*
|
|
2163
|
+
* React coexistence follows the upstream design: injected rows are plain
|
|
2164
|
+
* siblings/prepend children the vdom diff never claims, native rows are only
|
|
2165
|
+
* ever touched through an inline-`display` ledger that records the precise
|
|
2166
|
+
* original value and restores it on stop, session switch, or foreign takeover
|
|
2167
|
+
* (a written-value comparison plus an ownership sentinel catches another
|
|
2168
|
+
* script rewriting styles behind our back). A MutationObserver batches real
|
|
2169
|
+
* structure changes into rAF passes; a low-frequency self-rearming audit pass
|
|
2170
|
+
* reconciles unobservable style writes; a setTimeout fallback keeps passes
|
|
2171
|
+
* flowing when background tabs suspend rAF.
|
|
2172
|
+
*/
|
|
2173
|
+
const STYLE_ID = "dshss-auto-collapse-style";
|
|
2174
|
+
/** Class of the injected per-turn summary row; the follow engine's generalized
|
|
2175
|
+
* surface walk picks it up as an ordinary foreign flow sibling. */
|
|
2176
|
+
const PROCESSED_CLASS = "dshss-processed";
|
|
2177
|
+
/** Inline custom property marking elements whose display this plugin owns. */
|
|
2178
|
+
const DISPLAY_OWNED_PROP = "--dshss-display-owned";
|
|
2179
|
+
/** Unobservable external style writes are reconciled by this audit cadence. */
|
|
2180
|
+
const AUDIT_TICK_MS = 1e3;
|
|
2181
|
+
/** Background-tab fallback that flushes a scheduled pass when rAF is frozen. */
|
|
2182
|
+
const PASS_FALLBACK_MS = 60;
|
|
2183
|
+
/**
|
|
2184
|
+
* Fold motion, deliberately minimal: layout changes land synchronously (the
|
|
2185
|
+
* display toggle happens in the same tick as the decision) and nothing
|
|
2186
|
+
* animates geometry at all — the earlier container-height squeeze was removed
|
|
2187
|
+
* because real sessions showed residual drift after the pin released (other
|
|
2188
|
+
* layout participants keep settling around the fold). What remains is one
|
|
2189
|
+
* paint-only flourish: the summary row fades its opacity in. A fold is
|
|
2190
|
+
* therefore a single reflow followed by a fade — the most stable shape
|
|
2191
|
+
* possible.
|
|
2192
|
+
*/
|
|
2193
|
+
const FOLD_ROW_FADE_MS = 160;
|
|
2194
|
+
const FOLD_EASE_OUT = "cubic-bezier(0.22, 0.61, 0.36, 1)";
|
|
2195
|
+
const ZH_COPY = {
|
|
2196
|
+
processedLabel: "已处理",
|
|
2197
|
+
expandTitle: "展开工作过程",
|
|
2198
|
+
collapseTitle: "收起工作过程",
|
|
2199
|
+
formatDuration: formatZhDuration
|
|
2200
|
+
};
|
|
2201
|
+
const EN_COPY = {
|
|
2202
|
+
processedLabel: "Processed",
|
|
2203
|
+
expandTitle: "Show the work process",
|
|
2204
|
+
collapseTitle: "Hide the work process",
|
|
2205
|
+
formatDuration: formatEnDuration
|
|
2206
|
+
};
|
|
2207
|
+
function resolveFoldCopy() {
|
|
2208
|
+
let lang = "";
|
|
2209
|
+
try {
|
|
2210
|
+
lang = document.documentElement.lang || navigator.language || "";
|
|
2211
|
+
} catch {
|
|
2212
|
+
lang = "";
|
|
2213
|
+
}
|
|
2214
|
+
return lang.toLowerCase().startsWith("zh") ? ZH_COPY : EN_COPY;
|
|
2215
|
+
}
|
|
2216
|
+
const FOLD_CSS = `
|
|
2217
|
+
.${PROCESSED_CLASS} {
|
|
2218
|
+
box-sizing: border-box;
|
|
2219
|
+
display: inline-flex;
|
|
2220
|
+
align-self: flex-start;
|
|
2221
|
+
width: fit-content;
|
|
2222
|
+
max-width: 100%;
|
|
2223
|
+
align-items: center;
|
|
2224
|
+
gap: 6px;
|
|
2225
|
+
padding: 2px 0;
|
|
2226
|
+
border: none;
|
|
2227
|
+
background: none;
|
|
2228
|
+
font: 400 14px/24px system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
2229
|
+
color: var(--dsw-alias-label-tertiary, rgba(127, 127, 127, 0.9));
|
|
2230
|
+
cursor: pointer;
|
|
2231
|
+
user-select: none;
|
|
2232
|
+
border-radius: 4px;
|
|
2233
|
+
transition: color 0.15s ease;
|
|
2234
|
+
}
|
|
2235
|
+
.${PROCESSED_CLASS}:hover {
|
|
2236
|
+
color: var(--dsw-alias-label-primary, inherit);
|
|
2237
|
+
background: transparent;
|
|
2238
|
+
}
|
|
2239
|
+
.${PROCESSED_CLASS}:focus-visible {
|
|
2240
|
+
outline: 2px solid var(--dsw-alias-state-focus-ring, rgba(77, 107, 254, 0.8));
|
|
2241
|
+
outline-offset: 2px;
|
|
2242
|
+
}
|
|
2243
|
+
.${PROCESSED_CLASS}-chevron {
|
|
2244
|
+
display: inline-flex;
|
|
2245
|
+
flex: none;
|
|
2246
|
+
width: 14px;
|
|
2247
|
+
height: 14px;
|
|
2248
|
+
opacity: 0.55;
|
|
2249
|
+
transform: rotate(-90deg);
|
|
2250
|
+
transition: transform 0.12s ease, opacity 0.1s ease;
|
|
2251
|
+
}
|
|
2252
|
+
.${PROCESSED_CLASS}:hover .${PROCESSED_CLASS}-chevron {
|
|
2253
|
+
opacity: 0.9;
|
|
2254
|
+
}
|
|
2255
|
+
.${PROCESSED_CLASS}[aria-expanded="true"] .${PROCESSED_CLASS}-chevron {
|
|
2256
|
+
transform: rotate(0deg);
|
|
2257
|
+
}
|
|
2258
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2259
|
+
.${PROCESSED_CLASS},
|
|
2260
|
+
.${PROCESSED_CLASS}-chevron {
|
|
2261
|
+
transition: none;
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
`;
|
|
2265
|
+
var AutoCollapseController = class {
|
|
2266
|
+
observer = null;
|
|
2267
|
+
raf = 0;
|
|
2268
|
+
timer = 0;
|
|
2269
|
+
auditTimer = 0;
|
|
2270
|
+
/** True between start() and stop(); the preference toggle re-enters both. */
|
|
2271
|
+
running = false;
|
|
2272
|
+
lastPassError = "";
|
|
2273
|
+
flow = null;
|
|
2274
|
+
/** Stable segment key → summary row + gesture state. */
|
|
2275
|
+
segmentStates = /* @__PURE__ */ new Map();
|
|
2276
|
+
/** First sighting of a running segment, for turns without a host duration. */
|
|
2277
|
+
runningSince = /* @__PURE__ */ new Map();
|
|
2278
|
+
/** Segments that settled once: a resumed run restarts local timing. */
|
|
2279
|
+
completedOnce = /* @__PURE__ */ new Set();
|
|
2280
|
+
/** Precise pre-plugin display values, restored on stop/switch/takeover. */
|
|
2281
|
+
originalDisplay = /* @__PURE__ */ new WeakMap();
|
|
2282
|
+
writtenDisplay = /* @__PURE__ */ new WeakMap();
|
|
2283
|
+
controlledDisplay = /* @__PURE__ */ new Set();
|
|
2284
|
+
/** Reply-presence verdicts cached per message until a mutation hits it. */
|
|
2285
|
+
bodyTextCache = /* @__PURE__ */ new WeakMap();
|
|
2286
|
+
dirtyMessages = /* @__PURE__ */ new Set();
|
|
2287
|
+
onVisibilityChange = () => {
|
|
2288
|
+
if (typeof document === "undefined" || document.hidden !== true) this.schedule();
|
|
2289
|
+
};
|
|
2290
|
+
/** Re-run a pass immediately (settings changes, diagnostics, tests).
|
|
2291
|
+
* Reply-presence verdicts are dropped wholesale: manual refreshes are rare,
|
|
2292
|
+
* and a conservative rebuild beats a stale classification. */
|
|
2293
|
+
refresh() {
|
|
2294
|
+
this.bodyTextCache = /* @__PURE__ */ new WeakMap();
|
|
2295
|
+
this.dirtyMessages.clear();
|
|
2296
|
+
this.schedule();
|
|
2297
|
+
}
|
|
2298
|
+
start() {
|
|
2299
|
+
if (this.running) return;
|
|
2300
|
+
this.running = true;
|
|
2301
|
+
injectStyle();
|
|
2302
|
+
try {
|
|
2303
|
+
this.observer = new MutationObserver((records) => {
|
|
2304
|
+
if (this.shouldSchedule(records)) {
|
|
2305
|
+
this.markDirty(records);
|
|
2306
|
+
this.schedule();
|
|
2307
|
+
}
|
|
2308
|
+
});
|
|
2309
|
+
this.observer.observe(document.body, {
|
|
2310
|
+
childList: true,
|
|
2311
|
+
subtree: true,
|
|
2312
|
+
attributes: true,
|
|
2313
|
+
attributeFilter: ["data-state"],
|
|
2314
|
+
characterData: true
|
|
2315
|
+
});
|
|
2316
|
+
this.armAuditLoop();
|
|
2317
|
+
this.schedule();
|
|
2318
|
+
} catch (error) {
|
|
2319
|
+
this.reportError(error);
|
|
2320
|
+
throw error;
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
stop() {
|
|
2324
|
+
this.running = false;
|
|
2325
|
+
if (this.raf !== 0) cancelAnimationFrame(this.raf);
|
|
2326
|
+
this.raf = 0;
|
|
2327
|
+
if (this.timer !== 0) clearTimeout(this.timer);
|
|
2328
|
+
this.timer = 0;
|
|
2329
|
+
if (this.auditTimer !== 0) clearTimeout(this.auditTimer);
|
|
2330
|
+
this.auditTimer = 0;
|
|
2331
|
+
if (typeof document !== "undefined" && typeof document.removeEventListener === "function") document.removeEventListener("visibilitychange", this.onVisibilityChange);
|
|
2332
|
+
this.observer?.disconnect();
|
|
2333
|
+
this.observer = null;
|
|
2334
|
+
this.switchFlow(null);
|
|
2335
|
+
removeStyle();
|
|
2336
|
+
}
|
|
2337
|
+
/** Low-frequency reconciliation for style writes no observer can see. */
|
|
2338
|
+
armAuditLoop() {
|
|
2339
|
+
if (typeof document !== "undefined" && typeof document.addEventListener === "function") document.addEventListener("visibilitychange", this.onVisibilityChange);
|
|
2340
|
+
this.rearmAudit();
|
|
2341
|
+
}
|
|
2342
|
+
rearmAudit() {
|
|
2343
|
+
if (!this.running || this.auditTimer !== 0) return;
|
|
2344
|
+
this.auditTimer = setTimeout(() => {
|
|
2345
|
+
this.auditTimer = 0;
|
|
2346
|
+
if (!this.running) return;
|
|
2347
|
+
if (typeof document !== "undefined" && document.hidden === true) {
|
|
2348
|
+
this.rearmAudit();
|
|
2349
|
+
return;
|
|
2350
|
+
}
|
|
2351
|
+
this.schedule();
|
|
2352
|
+
this.rearmAudit();
|
|
2353
|
+
}, AUDIT_TICK_MS);
|
|
2354
|
+
}
|
|
2355
|
+
/** Body-level observations only need to wake us up near the active flow. */
|
|
2356
|
+
shouldSchedule(records) {
|
|
2357
|
+
if (records.length === 0 || this.flow === null || !this.flow.isConnected) return true;
|
|
2358
|
+
return records.some((record) => nodeWithin(record.target, this.flow) || nodeWithin(this.flow, record.target));
|
|
2359
|
+
}
|
|
2360
|
+
/** Walk each record to its flow child so reply-cache invalidation stays
|
|
2361
|
+
* targeted; anything unattributable invalidates conservatively. */
|
|
2362
|
+
markDirty(records) {
|
|
2363
|
+
const flow = this.flow;
|
|
2364
|
+
if (flow === null || !flow.isConnected) return;
|
|
2365
|
+
if (records.length === 0) {
|
|
2366
|
+
this.bodyTextCache = /* @__PURE__ */ new WeakMap();
|
|
2367
|
+
this.dirtyMessages.clear();
|
|
2368
|
+
return;
|
|
2369
|
+
}
|
|
2370
|
+
for (const record of records) {
|
|
2371
|
+
let current = record.target;
|
|
2372
|
+
while (current !== null && current.parentNode !== flow) current = current.parentNode;
|
|
2373
|
+
if (!(current instanceof HTMLElement)) {
|
|
2374
|
+
this.bodyTextCache = /* @__PURE__ */ new WeakMap();
|
|
2375
|
+
this.dirtyMessages.clear();
|
|
2376
|
+
return;
|
|
2377
|
+
}
|
|
2378
|
+
this.dirtyMessages.add(current);
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
schedule() {
|
|
2382
|
+
if (!this.running || this.raf !== 0) return;
|
|
2383
|
+
this.raf = requestAnimationFrame(() => {
|
|
2384
|
+
this.raf = 0;
|
|
2385
|
+
if (this.timer !== 0) {
|
|
2386
|
+
clearTimeout(this.timer);
|
|
2387
|
+
this.timer = 0;
|
|
2388
|
+
}
|
|
2389
|
+
this.runPass();
|
|
2390
|
+
});
|
|
2391
|
+
if (this.timer !== 0) clearTimeout(this.timer);
|
|
2392
|
+
this.timer = setTimeout(() => {
|
|
2393
|
+
this.timer = 0;
|
|
2394
|
+
if (this.raf !== 0) {
|
|
2395
|
+
cancelAnimationFrame(this.raf);
|
|
2396
|
+
this.raf = 0;
|
|
2397
|
+
this.runPass();
|
|
2398
|
+
}
|
|
2399
|
+
}, PASS_FALLBACK_MS);
|
|
2400
|
+
}
|
|
2401
|
+
runPass() {
|
|
2402
|
+
try {
|
|
2403
|
+
this.pass();
|
|
2404
|
+
this.lastPassError = "";
|
|
2405
|
+
} catch (error) {
|
|
2406
|
+
this.reportError(error);
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
reportError(error) {
|
|
2410
|
+
const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
2411
|
+
if (message === this.lastPassError) return;
|
|
2412
|
+
this.lastPassError = message;
|
|
2413
|
+
console.error("[dsh-smooth-stream] auto-collapse pass failed", error);
|
|
2414
|
+
}
|
|
2415
|
+
pass() {
|
|
2416
|
+
if (!this.running) return;
|
|
2417
|
+
const nextFlow = findFlow();
|
|
2418
|
+
if (nextFlow !== this.flow) this.switchFlow(nextFlow);
|
|
2419
|
+
const flow = this.flow;
|
|
2420
|
+
if (flow === null) return;
|
|
2421
|
+
for (const el of this.dirtyMessages) this.bodyTextCache.delete(el);
|
|
2422
|
+
this.dirtyMessages.clear();
|
|
2423
|
+
const segments = buildSegments(flow, (el) => this.hasBodyCached(el));
|
|
2424
|
+
const liveSegmentKeys = new Set(segments.map((segment) => segment.key));
|
|
2425
|
+
for (const segment of segments) {
|
|
2426
|
+
if (!segment.running) continue;
|
|
2427
|
+
if (this.completedOnce.has(segment.key)) {
|
|
2428
|
+
this.completedOnce.delete(segment.key);
|
|
2429
|
+
this.runningSince.delete(segment.key);
|
|
2430
|
+
}
|
|
2431
|
+
if (!this.runningSince.has(segment.key)) this.runningSince.set(segment.key, Date.now());
|
|
2432
|
+
}
|
|
2433
|
+
const completedKeys = /* @__PURE__ */ new Set();
|
|
2434
|
+
for (const segment of segments) {
|
|
2435
|
+
if (!segment.closed || segment.running || !segment.hasWork) continue;
|
|
2436
|
+
completedKeys.add(segment.key);
|
|
2437
|
+
this.completedOnce.add(segment.key);
|
|
2438
|
+
let state = this.segmentStates.get(segment.key);
|
|
2439
|
+
if (state === void 0) {
|
|
2440
|
+
state = {
|
|
2441
|
+
key: segment.key,
|
|
2442
|
+
row: null,
|
|
2443
|
+
expanded: false,
|
|
2444
|
+
snapshot: segment
|
|
2445
|
+
};
|
|
2446
|
+
this.segmentStates.set(segment.key, state);
|
|
2447
|
+
} else state.snapshot = segment;
|
|
2448
|
+
const started = this.runningSince.get(segment.key);
|
|
2449
|
+
const parsed = segment.boundary === null ? void 0 : parseTurnDuration(segment.boundary);
|
|
2450
|
+
if (parsed !== void 0) state.duration = parsed;
|
|
2451
|
+
else if (state.duration === void 0 && started !== void 0) state.duration = Date.now() - started;
|
|
2452
|
+
if (state.row === null || !state.row.isConnected) state.row = this.createProcessedRow(state);
|
|
2453
|
+
this.syncProcessedRow(state);
|
|
2454
|
+
}
|
|
2455
|
+
for (const [key, state] of [...this.segmentStates]) {
|
|
2456
|
+
if (completedKeys.has(key)) continue;
|
|
2457
|
+
state.row?.remove();
|
|
2458
|
+
this.segmentStates.delete(key);
|
|
2459
|
+
}
|
|
2460
|
+
const desiredHidden = /* @__PURE__ */ new Set();
|
|
2461
|
+
for (const segment of segments) {
|
|
2462
|
+
const state = this.segmentStates.get(segment.key);
|
|
2463
|
+
if (state !== void 0 && !state.expanded) {
|
|
2464
|
+
for (const seat of segment.hideSeats) this.hideElement(seat, desiredHidden);
|
|
2465
|
+
for (const row of segment.finalThinkRows) this.hideElement(row, desiredHidden);
|
|
2466
|
+
if (segment.finalStep !== null) this.restoreElement(segment.finalStep);
|
|
2467
|
+
} else {
|
|
2468
|
+
for (const seat of segment.hideSeats) this.restoreElement(seat);
|
|
2469
|
+
for (const row of segment.finalThinkRows) this.restoreElement(row);
|
|
2470
|
+
if (segment.finalStep !== null) this.restoreElement(segment.finalStep);
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
for (const segment of segments) {
|
|
2474
|
+
if (!hasVisibleSegmentWork(segment)) {
|
|
2475
|
+
const state = this.segmentStates.get(segment.key);
|
|
2476
|
+
if (state !== void 0 && state.row !== null) {
|
|
2477
|
+
state.row.remove();
|
|
2478
|
+
state.row = null;
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
for (const seat of segment.hideSeats) this.retainDisplayControl(seat, desiredHidden);
|
|
2482
|
+
for (const row of segment.finalThinkRows) this.retainDisplayControl(row, desiredHidden);
|
|
2483
|
+
if (segment.finalStep !== null) this.retainDisplayControl(segment.finalStep, desiredHidden);
|
|
2484
|
+
}
|
|
2485
|
+
this.restoreUnusedDisplays(desiredHidden);
|
|
2486
|
+
for (const state of this.segmentStates.values()) this.placeProcessedRow(flow, state);
|
|
2487
|
+
for (const key of [...this.runningSince.keys()]) if (!liveSegmentKeys.has(key)) this.runningSince.delete(key);
|
|
2488
|
+
for (const key of [...this.completedOnce]) if (!liveSegmentKeys.has(key)) this.completedOnce.delete(key);
|
|
2489
|
+
}
|
|
2490
|
+
/** Flow swap means a session switch: fully restore the old tree, rebuild. */
|
|
2491
|
+
switchFlow(next) {
|
|
2492
|
+
if (next === this.flow) return;
|
|
2493
|
+
for (const state of this.segmentStates.values()) state.row?.remove();
|
|
2494
|
+
this.segmentStates.clear();
|
|
2495
|
+
this.runningSince.clear();
|
|
2496
|
+
this.completedOnce.clear();
|
|
2497
|
+
this.bodyTextCache = /* @__PURE__ */ new WeakMap();
|
|
2498
|
+
this.dirtyMessages.clear();
|
|
2499
|
+
this.restoreAllDisplays();
|
|
2500
|
+
this.flow = next;
|
|
2501
|
+
}
|
|
2502
|
+
/** Keep already-controlled elements in the desired-hidden set so the sweep
|
|
2503
|
+
* does not flip them back mid-flight of an external change. */
|
|
2504
|
+
retainDisplayControl(el, desiredHidden) {
|
|
2505
|
+
if (this.controlledDisplay.has(el)) desiredHidden.add(el);
|
|
2506
|
+
}
|
|
2507
|
+
/** Restore every element the ledger controls that this pass no longer wants
|
|
2508
|
+
* hidden; elements absent from `desired` come back to their true display. */
|
|
2509
|
+
restoreUnusedDisplays(desired) {
|
|
2510
|
+
for (const el of [...this.controlledDisplay]) if (!desired.has(el)) this.restoreElement(el);
|
|
2511
|
+
}
|
|
2512
|
+
/** Restore every element still under the display ledger, then retire the
|
|
2513
|
+
* ledger itself (stop / session switch). */
|
|
2514
|
+
restoreAllDisplays() {
|
|
2515
|
+
for (const el of [...this.controlledDisplay]) this.restoreElement(el);
|
|
2516
|
+
this.controlledDisplay.clear();
|
|
2517
|
+
this.originalDisplay = /* @__PURE__ */ new WeakMap();
|
|
2518
|
+
this.writtenDisplay = /* @__PURE__ */ new WeakMap();
|
|
2519
|
+
}
|
|
2520
|
+
createProcessedRow(state) {
|
|
2521
|
+
const row = createProcessedRowElement(state.duration);
|
|
2522
|
+
row.addEventListener("click", () => {
|
|
2523
|
+
state.expanded = !state.expanded;
|
|
2524
|
+
this.syncProcessedRow(state);
|
|
2525
|
+
this.schedule();
|
|
2526
|
+
});
|
|
2527
|
+
return row;
|
|
2528
|
+
}
|
|
2529
|
+
syncProcessedRow(state) {
|
|
2530
|
+
const row = state.row;
|
|
2531
|
+
if (row === null) return;
|
|
2532
|
+
const copy = resolveFoldCopy();
|
|
2533
|
+
const text = row.firstElementChild;
|
|
2534
|
+
const label = state.duration === void 0 ? copy.processedLabel : `${copy.processedLabel} ${copy.formatDuration(state.duration)}`;
|
|
2535
|
+
if (text !== null && text.textContent !== label) text.textContent = label;
|
|
2536
|
+
const expanded = String(state.expanded);
|
|
2537
|
+
if (row.getAttribute("aria-expanded") !== expanded) row.setAttribute("aria-expanded", expanded);
|
|
2538
|
+
const title = state.expanded ? copy.collapseTitle : copy.expandTitle;
|
|
2539
|
+
if (row.title !== title) row.title = title;
|
|
2540
|
+
}
|
|
2541
|
+
placeProcessedRow(flow, state) {
|
|
2542
|
+
const row = state.row;
|
|
2543
|
+
if (row === null) return;
|
|
2544
|
+
if (!state.snapshot.hasWork || !hasVisibleSegmentWork(state.snapshot)) {
|
|
2545
|
+
row.remove();
|
|
2546
|
+
state.row = null;
|
|
2547
|
+
return;
|
|
2548
|
+
}
|
|
2549
|
+
let target = state.snapshot.firstWork ?? state.snapshot.finalStep ?? state.snapshot.boundary;
|
|
2550
|
+
if (target === null || target.parentElement !== flow) {
|
|
2551
|
+
row.remove();
|
|
2552
|
+
state.row = null;
|
|
2553
|
+
return;
|
|
2554
|
+
}
|
|
2555
|
+
if (row.parentElement !== flow || row.nextElementSibling !== target) {
|
|
2556
|
+
target.before(row);
|
|
2557
|
+
this.revealVisualOnce(row);
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
hasBodyCached(el) {
|
|
2561
|
+
const cached = this.bodyTextCache.get(el);
|
|
2562
|
+
if (cached !== void 0) return cached;
|
|
2563
|
+
const value = hasBodyContent(el);
|
|
2564
|
+
this.bodyTextCache.set(el, value);
|
|
2565
|
+
return value;
|
|
2566
|
+
}
|
|
2567
|
+
/** True when the recorded display was changed (or the ownership sentinel
|
|
2568
|
+
* wiped) behind the ledger's back; callers hand control back to reality. */
|
|
2569
|
+
displayForeign(el) {
|
|
2570
|
+
const written = this.writtenDisplay.get(el);
|
|
2571
|
+
if (written === void 0) return false;
|
|
2572
|
+
return el.style.getPropertyValue(DISPLAY_OWNED_PROP) === "" || el.style.display !== written;
|
|
2573
|
+
}
|
|
2574
|
+
releaseDisplayLedger(el) {
|
|
2575
|
+
this.originalDisplay.delete(el);
|
|
2576
|
+
this.writtenDisplay.delete(el);
|
|
2577
|
+
this.controlledDisplay.delete(el);
|
|
2578
|
+
el.style.removeProperty(DISPLAY_OWNED_PROP);
|
|
2579
|
+
}
|
|
2580
|
+
hideElement(el, desired) {
|
|
2581
|
+
desired.add(el);
|
|
2582
|
+
if (!this.originalDisplay.has(el) && !isDisplayed(el)) return;
|
|
2583
|
+
if (!this.originalDisplay.has(el) || this.displayForeign(el)) {
|
|
2584
|
+
this.originalDisplay.set(el, el.style.display);
|
|
2585
|
+
this.writtenDisplay.set(el, el.style.display);
|
|
2586
|
+
el.style.setProperty(DISPLAY_OWNED_PROP, "1");
|
|
2587
|
+
}
|
|
2588
|
+
this.controlledDisplay.add(el);
|
|
2589
|
+
if (el.style.display === "none") return;
|
|
2590
|
+
el.style.display = "none";
|
|
2591
|
+
this.writtenDisplay.set(el, "none");
|
|
2592
|
+
}
|
|
2593
|
+
restoreElement(el) {
|
|
2594
|
+
if (!this.originalDisplay.has(el)) return;
|
|
2595
|
+
if (this.displayForeign(el)) {
|
|
2596
|
+
this.releaseDisplayLedger(el);
|
|
2597
|
+
return;
|
|
2598
|
+
}
|
|
2599
|
+
const original = this.originalDisplay.get(el);
|
|
2600
|
+
if (el.style.display !== original) el.style.display = original;
|
|
2601
|
+
this.releaseDisplayLedger(el);
|
|
2602
|
+
}
|
|
2603
|
+
/** Whether WAAPI exists and the user has not asked for reduced motion. */
|
|
2604
|
+
canAnimate(el) {
|
|
2605
|
+
if (typeof el.animate !== "function") return false;
|
|
2606
|
+
try {
|
|
2607
|
+
if (typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches) return false;
|
|
2608
|
+
} catch {
|
|
2609
|
+
return false;
|
|
2610
|
+
}
|
|
2611
|
+
return true;
|
|
2612
|
+
}
|
|
2613
|
+
/** First-appearance flourish for the plugin-owned summary row: a pure
|
|
2614
|
+
* opacity fade. Paint-only, so it cannot shift anyone's layout. */
|
|
2615
|
+
revealVisualOnce(row) {
|
|
2616
|
+
if (row.dataset.dshssShown === "1") return;
|
|
2617
|
+
row.dataset.dshssShown = "1";
|
|
2618
|
+
if (!this.canAnimate(row)) return;
|
|
2619
|
+
row.animate([{ opacity: "0" }, { opacity: "1" }], {
|
|
2620
|
+
duration: FOLD_ROW_FADE_MS,
|
|
2621
|
+
easing: FOLD_EASE_OUT
|
|
2622
|
+
});
|
|
2623
|
+
}
|
|
2624
|
+
};
|
|
2625
|
+
/** Visible chat flow container, falling back to the first one rendered. */
|
|
2626
|
+
function findFlow() {
|
|
2627
|
+
const flows = document.querySelectorAll("[data-chat-flow]");
|
|
2628
|
+
for (const flow of flows) if (flow.offsetParent !== null || flow.getBoundingClientRect().width > 0) return flow;
|
|
2629
|
+
return flows[0] ?? null;
|
|
2630
|
+
}
|
|
2631
|
+
/** parentNode walk that also accepts Text mutation targets. */
|
|
2632
|
+
function nodeWithin(node, ancestor) {
|
|
2633
|
+
for (let current = node; current !== null; current = current.parentNode) if (current === ancestor) return true;
|
|
2634
|
+
return false;
|
|
2635
|
+
}
|
|
2636
|
+
/** Top-level message order, excluding this plugin's own summary rows. */
|
|
2637
|
+
function flowItems(flow) {
|
|
2638
|
+
return [...flow.children].filter((el) => el instanceof HTMLElement && !el.classList.contains(PROCESSED_CLASS));
|
|
2639
|
+
}
|
|
2640
|
+
function isDisplayed(el) {
|
|
2641
|
+
if (typeof getComputedStyle === "function") return getComputedStyle(el).display !== "none";
|
|
2642
|
+
return el.style.display !== "none";
|
|
2643
|
+
}
|
|
2644
|
+
function stableElementKey(el, fallbackIndex) {
|
|
2645
|
+
const kind = el.getAttribute("data-chat-flow-kind") ?? "node";
|
|
2646
|
+
return `${kind}:${el.getAttribute("data-chat-flow-key") ?? el.getAttribute("data-chat-anchor-key") ?? `${kind}:${fallbackIndex}`}`;
|
|
2647
|
+
}
|
|
2648
|
+
/** Kinds that can open a history stretch lacking an explicit user marker. */
|
|
2649
|
+
function opensTurnWork(el) {
|
|
2650
|
+
const kind = el.getAttribute("data-chat-flow-kind");
|
|
2651
|
+
return kind === "assistant-step" || kind === "assistant" || kind === "tool-call" || kind === "command" || kind === "manual-compaction";
|
|
2652
|
+
}
|
|
2653
|
+
/**
|
|
2654
|
+
* Split the flow into turn segments. `user`/`steering` rows close the previous
|
|
2655
|
+
* segment and open the next; `turn-tail` closes and resets the opener. Leading
|
|
2656
|
+
* context before the first user row joins that user's segment, matching how
|
|
2657
|
+
* the host presents context injection.
|
|
2658
|
+
*/
|
|
2659
|
+
function buildSegments(flow, hasBody) {
|
|
2660
|
+
const items = flowItems(flow);
|
|
2661
|
+
const itemIndex = new Map(items.map((el, index) => [el, index]));
|
|
2662
|
+
const snapshots = [];
|
|
2663
|
+
let contentStart = 0;
|
|
2664
|
+
let startMarker = null;
|
|
2665
|
+
const append = (end, boundary, closed) => {
|
|
2666
|
+
if (end < contentStart) return;
|
|
2667
|
+
const range = items.slice(contentStart, end);
|
|
2668
|
+
const hideSeats = /* @__PURE__ */ new Set();
|
|
2669
|
+
const bodySteps = [];
|
|
2670
|
+
let running = false;
|
|
2671
|
+
for (const el of range) {
|
|
2672
|
+
const classified = classifyItem(el, hasBody);
|
|
2673
|
+
if (classified.fold === "body") bodySteps.push(el);
|
|
2674
|
+
else if (classified.fold === "seat") hideSeats.add(el);
|
|
2675
|
+
if (classified.running) running = true;
|
|
2676
|
+
}
|
|
2677
|
+
const finalStep = bodySteps.at(-1) ?? null;
|
|
2678
|
+
const middleSteps = new Set(bodySteps.slice(0, -1));
|
|
2679
|
+
for (const step of middleSteps) hideSeats.add(step);
|
|
2680
|
+
const finalThinkRows = finalStep === null ? [] : thinkRowsIn(finalStep);
|
|
2681
|
+
const firstWork = range.find((el) => hideSeats.has(el)) ?? finalStep;
|
|
2682
|
+
const identity = startMarker ?? range.find((el) => opensTurnWork(el)) ?? boundary;
|
|
2683
|
+
const identityIndex = identity === null ? contentStart : itemIndex.get(identity) ?? contentStart;
|
|
2684
|
+
const key = `${startMarker === null ? "leading" : "segment"}:${identity === null ? `open:${contentStart}` : stableElementKey(identity, identityIndex)}`;
|
|
2685
|
+
snapshots.push({
|
|
2686
|
+
key,
|
|
2687
|
+
boundary,
|
|
2688
|
+
startMarker,
|
|
2689
|
+
hideSeats,
|
|
2690
|
+
middleSteps,
|
|
2691
|
+
finalStep,
|
|
2692
|
+
finalThinkRows,
|
|
2693
|
+
firstWork,
|
|
2694
|
+
closed,
|
|
2695
|
+
running,
|
|
2696
|
+
hasWork: hideSeats.size > 0 || finalThinkRows.length > 0
|
|
2697
|
+
});
|
|
2698
|
+
};
|
|
2699
|
+
items.forEach((el, index) => {
|
|
2700
|
+
const kind = el.getAttribute("data-chat-flow-kind");
|
|
2701
|
+
if (kind === "user" || kind === "steering") {
|
|
2702
|
+
if (startMarker !== null) {
|
|
2703
|
+
append(index, el, true);
|
|
2704
|
+
contentStart = index + 1;
|
|
2705
|
+
} else if (items.slice(contentStart, index).some((el) => opensTurnWork(el))) {
|
|
2706
|
+
append(index, el, true);
|
|
2707
|
+
contentStart = index + 1;
|
|
2708
|
+
}
|
|
2709
|
+
startMarker = el;
|
|
2710
|
+
return;
|
|
2711
|
+
}
|
|
2712
|
+
if (kind === "turn-tail") {
|
|
2713
|
+
append(index, el, true);
|
|
2714
|
+
contentStart = index + 1;
|
|
2715
|
+
startMarker = null;
|
|
2716
|
+
}
|
|
2717
|
+
});
|
|
2718
|
+
if (contentStart < items.length) append(items.length, null, false);
|
|
2719
|
+
return snapshots;
|
|
2720
|
+
}
|
|
2721
|
+
/**
|
|
2722
|
+
* Classify one top-level flow item against the Harness's own node vocabulary.
|
|
2723
|
+
* Reply-bearing assistant messages become `body` candidates (last one per
|
|
2724
|
+
* segment wins as the final answer); core work process — tool-call seats,
|
|
2725
|
+
* command/compaction cards, context injection, thinking-only messages —
|
|
2726
|
+
* becomes a hideable `seat`. Everything else is skipped untouched: boundaries,
|
|
2727
|
+
* empty decorations, and crucially any kind this module does not own, so rows
|
|
2728
|
+
* contributed by other plugins' slots are never folded or hidden.
|
|
2729
|
+
*/
|
|
2730
|
+
function classifyItem(el, hasBody) {
|
|
2731
|
+
const kind = el.getAttribute("data-chat-flow-kind");
|
|
2732
|
+
if (kind === "user" || kind === "steering" || kind === "turn-tail") return {
|
|
2733
|
+
fold: "skip",
|
|
2734
|
+
running: false
|
|
2735
|
+
};
|
|
2736
|
+
if (kind === "assistant-step" || kind === "assistant") {
|
|
2737
|
+
if (hasBody(el)) return {
|
|
2738
|
+
fold: "body",
|
|
2739
|
+
running: subtreeRunning(el)
|
|
2740
|
+
};
|
|
2741
|
+
if (thinkRowsIn(el).length > 0) return {
|
|
2742
|
+
fold: "seat",
|
|
2743
|
+
running: subtreeRunning(el)
|
|
2744
|
+
};
|
|
2745
|
+
return {
|
|
2746
|
+
fold: "skip",
|
|
2747
|
+
running: false
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
if (kind === "tool-call") {
|
|
2751
|
+
const rows = topCallRowsIn(el);
|
|
2752
|
+
if (rows.length === 0 || !rows.every((row) => hasNativeCardChrome(row))) return {
|
|
2753
|
+
fold: "skip",
|
|
2754
|
+
running: subtreeRunning(el)
|
|
2755
|
+
};
|
|
2756
|
+
return {
|
|
2757
|
+
fold: "seat",
|
|
2758
|
+
running: subtreeRunning(el)
|
|
2759
|
+
};
|
|
2760
|
+
}
|
|
2761
|
+
if (kind === "context" || kind === "command" || kind === "manual-compaction") return {
|
|
2762
|
+
fold: "seat",
|
|
2763
|
+
running: subtreeRunning(el)
|
|
2764
|
+
};
|
|
2765
|
+
return {
|
|
2766
|
+
fold: "skip",
|
|
2767
|
+
running: false
|
|
2768
|
+
};
|
|
2769
|
+
}
|
|
2770
|
+
/** Native work-card shape: every core card family stamps `data-variant`
|
|
2771
|
+
* together with `data-state`, whether composed through ToolRow or hand-rolled
|
|
2772
|
+
* (Bash). Custom toolviews use their own namespaced attributes instead. */
|
|
2773
|
+
function hasNativeCardChrome(row) {
|
|
2774
|
+
return row.querySelector("[data-variant][data-state]") !== null;
|
|
2775
|
+
}
|
|
2776
|
+
/** Whether anything inside the item reports a running lifecycle. Deliberately
|
|
2777
|
+
* broad: staying "running" too long only delays a collapse, collapsing early
|
|
2778
|
+
* would cut off live output. Native cards stamp `data-state` next to
|
|
2779
|
+
* `data-tool`/`data-variant`; custom views use their own vocabulary and are
|
|
2780
|
+
* intentionally invisible here. */
|
|
2781
|
+
function subtreeRunning(el) {
|
|
2782
|
+
if (el.getAttribute("data-state") === "running") return true;
|
|
2783
|
+
for (const root of el.querySelectorAll("[data-tool][data-state], [data-variant][data-state]")) if (root.getAttribute("data-state") === "running") return true;
|
|
2784
|
+
return false;
|
|
2785
|
+
}
|
|
2786
|
+
function hasVisibleSegmentWork(segment) {
|
|
2787
|
+
const candidates = [...segment.hideSeats, ...segment.middleSteps];
|
|
2788
|
+
if (segment.startMarker !== null) candidates.push(segment.startMarker);
|
|
2789
|
+
if (segment.finalStep !== null) candidates.push(segment.finalStep);
|
|
2790
|
+
return candidates.some(isDisplayed);
|
|
2791
|
+
}
|
|
2792
|
+
/** Top-level tool card rows of a seat (excluding sub-dispatch nests). */
|
|
2793
|
+
function topCallRowsIn(el) {
|
|
2794
|
+
const rows = [];
|
|
2795
|
+
for (const row of el.querySelectorAll("[data-chat-call-id]")) {
|
|
2796
|
+
if (row.closest("[data-subcalls]") !== null) continue;
|
|
2797
|
+
if (row.closest("[data-chat-call-id]") !== row) continue;
|
|
2798
|
+
rows.push(row);
|
|
2799
|
+
}
|
|
2800
|
+
return rows;
|
|
2801
|
+
}
|
|
2802
|
+
/** Reasoning rows of a message: native think variants outside tool cards. */
|
|
2803
|
+
function thinkRowsIn(el) {
|
|
2804
|
+
const rows = [];
|
|
2805
|
+
for (const row of el.querySelectorAll("[data-variant=\"think\"]:not([data-tool])")) {
|
|
2806
|
+
if (row.closest("[data-chat-call-id]") !== null) continue;
|
|
2807
|
+
if (row.closest("[data-subcalls]") !== null) continue;
|
|
2808
|
+
rows.push(row);
|
|
2809
|
+
}
|
|
2810
|
+
return rows;
|
|
2811
|
+
}
|
|
2812
|
+
/**
|
|
2813
|
+
* Reply detection beyond reasoning rows and tool cards: any non-empty text or
|
|
2814
|
+
* media counts, because Markdown paragraphs necessarily carry text nodes.
|
|
2815
|
+
* Rendered markdown class names are build-time hashes, so structure-based
|
|
2816
|
+
* detection is the only stable contract.
|
|
2817
|
+
*/
|
|
2818
|
+
function hasBodyContent(el) {
|
|
2819
|
+
const kind = el.getAttribute("data-chat-flow-kind");
|
|
2820
|
+
if (kind === "command" || kind === "manual-compaction") return false;
|
|
2821
|
+
if (hasBodyText(el)) return true;
|
|
2822
|
+
const excluded = "[data-variant=\"think\"], [data-chat-call-id]";
|
|
2823
|
+
for (const media of el.querySelectorAll("img, video, audio, canvas")) if (media.closest(excluded) === null) return true;
|
|
2824
|
+
return false;
|
|
2825
|
+
}
|
|
2826
|
+
function hasBodyText(el) {
|
|
2827
|
+
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
2828
|
+
let node;
|
|
2829
|
+
while ((node = walker.nextNode()) !== null) {
|
|
2830
|
+
if (node.data.trim() === "") continue;
|
|
2831
|
+
const parent = node.parentElement;
|
|
2832
|
+
if (parent !== null && parent.closest("[data-variant=\"think\"], [data-chat-call-id], [data-variant=\"others\"][data-state]") !== null) continue;
|
|
2833
|
+
return true;
|
|
2834
|
+
}
|
|
2835
|
+
return false;
|
|
2836
|
+
}
|
|
2837
|
+
/**
|
|
2838
|
+
* Official turn duration from the closing element: localized tails read
|
|
2839
|
+
* `用时 33秒` / `用时 2分05秒` or `Ran for 33s` / `Ran for 2m 05s`; newer
|
|
2840
|
+
* tails only carry an end timestamp, which is diffed against the opening user
|
|
2841
|
+
* row's timestamp.
|
|
2842
|
+
*/
|
|
2843
|
+
function parseTurnDuration(boundary) {
|
|
2844
|
+
const text = boundary.textContent ?? "";
|
|
2845
|
+
const zh = text.match(/用时\s*(\d+)分(\d+)秒|用时\s*(\d+)秒/);
|
|
2846
|
+
if (zh !== null) {
|
|
2847
|
+
if (zh[1] !== void 0 && zh[2] !== void 0) return Number(zh[1]) * 6e4 + Number(zh[2]) * 1e3;
|
|
2848
|
+
if (zh[3] !== void 0) return Number(zh[3]) * 1e3;
|
|
2849
|
+
return;
|
|
2850
|
+
}
|
|
2851
|
+
const en = text.match(/Ran for\s*(?:(\d+)m\s*)?(\d+)s/);
|
|
2852
|
+
if (en !== null) return ((en[1] === void 0 ? 0 : Number(en[1])) * 60 + Number(en[2])) * 1e3;
|
|
2853
|
+
const end = parseTimeText(text);
|
|
2854
|
+
const start = findTurnStart(boundary);
|
|
2855
|
+
if (end !== void 0 && start !== void 0 && end > start) return end - start;
|
|
2856
|
+
}
|
|
2857
|
+
/** DSH timestamp text (`8月14日 21:56` / `2026年8月14日 22:11`) → epoch ms. */
|
|
2858
|
+
function parseTimeText(text) {
|
|
2859
|
+
const m = text.match(/(?:(\d{4})年)?(\d{1,2})月(\d{1,2})日\s*(\d{1,2}):(\d{2})/);
|
|
2860
|
+
if (m === null) return void 0;
|
|
2861
|
+
const year = m[1] !== void 0 ? Number(m[1]) : (/* @__PURE__ */ new Date()).getFullYear();
|
|
2862
|
+
const t = new Date(year, Number(m[2]) - 1, Number(m[3]), Number(m[4]), Number(m[5])).getTime();
|
|
2863
|
+
return Number.isNaN(t) ? void 0 : t;
|
|
2864
|
+
}
|
|
2865
|
+
/** Latest turn-opening timestamp at or before the boundary element. */
|
|
2866
|
+
function findTurnStart(boundary) {
|
|
2867
|
+
const flow = boundary.parentElement;
|
|
2868
|
+
if (flow === null) return void 0;
|
|
2869
|
+
let best = null;
|
|
2870
|
+
for (const s of flow.querySelectorAll("[class*=\"timeStart\"]")) {
|
|
2871
|
+
const pos = s.compareDocumentPosition(boundary);
|
|
2872
|
+
if ((pos & Node.DOCUMENT_POSITION_FOLLOWING) !== 0 || (pos & Node.DOCUMENT_POSITION_CONTAINED_BY) !== 0 || s === boundary) best = s;
|
|
2873
|
+
else break;
|
|
2874
|
+
}
|
|
2875
|
+
if (best === null) return void 0;
|
|
2876
|
+
return parseTimeText(best.textContent ?? "");
|
|
2877
|
+
}
|
|
2878
|
+
/** Compact duration pieces shared by both locales: seconds under a minute,
|
|
2879
|
+
* whole minutes drop the seconds, hours drop to minute granularity. */
|
|
2880
|
+
function durationParts(ms) {
|
|
2881
|
+
const s = Math.round(ms / 1e3);
|
|
2882
|
+
return {
|
|
2883
|
+
h: Math.floor(s / 3600),
|
|
2884
|
+
m: Math.floor(s % 3600 / 60),
|
|
2885
|
+
s: s % 60
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2888
|
+
/** 毫秒 → 中文紧凑时长(14秒 / 2分05秒 / 15分 / 3小时2分)。 */
|
|
2889
|
+
function formatZhDuration(ms) {
|
|
2890
|
+
const { h, m, s } = durationParts(ms);
|
|
2891
|
+
if (h > 0) return m > 0 ? `${h}小时${m}分` : `${h}小时`;
|
|
2892
|
+
if (s < 60 && m === 0 && h === 0) return `${s}秒`;
|
|
2893
|
+
if (s === 0) return `${m}分`;
|
|
2894
|
+
return `${m}分${String(s).padStart(2, "0")}秒`;
|
|
2895
|
+
}
|
|
2896
|
+
/** Milliseconds → compact English duration (14s / 2m05s / 15m / 3h2m). */
|
|
2897
|
+
function formatEnDuration(ms) {
|
|
2898
|
+
const { h, m, s } = durationParts(ms);
|
|
2899
|
+
if (h > 0) return m > 0 ? `${h}h${m}m` : `${h}h`;
|
|
2900
|
+
if (m === 0 && h === 0) return `${s}s`;
|
|
2901
|
+
if (s === 0) return `${m}m`;
|
|
2902
|
+
return `${m}m${String(s).padStart(2, "0")}s`;
|
|
2903
|
+
}
|
|
2904
|
+
/** Native disclosure chevron (IconChevronDownOutline14, 14x14). */
|
|
2905
|
+
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";
|
|
2906
|
+
function createChevronIcon(className) {
|
|
2907
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
2908
|
+
svg.setAttribute("width", "14");
|
|
2909
|
+
svg.setAttribute("height", "14");
|
|
2910
|
+
svg.setAttribute("class", className);
|
|
2911
|
+
svg.setAttribute("viewBox", "0 0 14 14");
|
|
2912
|
+
svg.setAttribute("fill", "none");
|
|
2913
|
+
svg.setAttribute("aria-hidden", "true");
|
|
2914
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
2915
|
+
path.setAttribute("d", CHEVRON_PATH);
|
|
2916
|
+
path.setAttribute("fill", "currentColor");
|
|
2917
|
+
svg.appendChild(path);
|
|
2918
|
+
return svg;
|
|
2919
|
+
}
|
|
2920
|
+
function createProcessedRowElement(duration) {
|
|
2921
|
+
const copy = resolveFoldCopy();
|
|
2922
|
+
const btn = document.createElement("button");
|
|
2923
|
+
btn.type = "button";
|
|
2924
|
+
btn.className = PROCESSED_CLASS;
|
|
2925
|
+
btn.setAttribute("aria-expanded", "false");
|
|
2926
|
+
const text = document.createElement("span");
|
|
2927
|
+
text.textContent = duration === void 0 ? copy.processedLabel : `${copy.processedLabel} ${copy.formatDuration(duration)}`;
|
|
2928
|
+
btn.append(text, createChevronIcon(`${PROCESSED_CLASS}-chevron`));
|
|
2929
|
+
btn.title = copy.expandTitle;
|
|
2930
|
+
return btn;
|
|
2931
|
+
}
|
|
2932
|
+
function injectStyle() {
|
|
2933
|
+
if (document.getElementById(STYLE_ID) !== null) return;
|
|
2934
|
+
const style = document.createElement("style");
|
|
2935
|
+
style.id = STYLE_ID;
|
|
2936
|
+
style.textContent = FOLD_CSS;
|
|
2937
|
+
document.head.appendChild(style);
|
|
2938
|
+
}
|
|
2939
|
+
function removeStyle() {
|
|
2940
|
+
document.getElementById(STYLE_ID)?.remove();
|
|
2941
|
+
}
|
|
2942
|
+
//#endregion
|
|
1798
2943
|
//#region src/client/useProgressiveDomText.ts
|
|
1799
2944
|
/**
|
|
1800
2945
|
* Progressive text reveal for opaque Agent renderers.
|
|
@@ -1805,6 +2950,8 @@ window.__ModuleLoader__.load({
|
|
|
1805
2950
|
* while the row belongs to the live Agent turn. No clip, mask, overlay, or
|
|
1806
2951
|
* duplicate accessibility tree is introduced.
|
|
1807
2952
|
*/
|
|
2953
|
+
/** Last presented text per root, retained across follow lifecycle flips. */
|
|
2954
|
+
const ledgerByRoot = /* @__PURE__ */ new WeakMap();
|
|
1808
2955
|
const SKIP_TEXT_SELECTOR = [
|
|
1809
2956
|
"[aria-hidden=\"true\"]",
|
|
1810
2957
|
"[aria-live]",
|
|
@@ -1832,10 +2979,56 @@ window.__ModuleLoader__.load({
|
|
|
1832
2979
|
*/
|
|
1833
2980
|
function useProgressiveDomText(rootRef, enabled, revealInitial, speedCpsRef, onSettled) {
|
|
1834
2981
|
(0, react.useLayoutEffect)(() => {
|
|
1835
|
-
if (!enabled) return;
|
|
1836
2982
|
const root = rootRef.current;
|
|
1837
2983
|
if (root === null || typeof document === "undefined") return;
|
|
1838
|
-
|
|
2984
|
+
let records = ledgerByRoot.get(root);
|
|
2985
|
+
if (!enabled && records === void 0) return;
|
|
2986
|
+
if (records === void 0) {
|
|
2987
|
+
records = /* @__PURE__ */ new Map();
|
|
2988
|
+
ledgerByRoot.set(root, records);
|
|
2989
|
+
}
|
|
2990
|
+
const forEachText = (from, callback) => {
|
|
2991
|
+
if (from.nodeType === Node.TEXT_NODE) {
|
|
2992
|
+
callback(from);
|
|
2993
|
+
return;
|
|
2994
|
+
}
|
|
2995
|
+
const walker = document.createTreeWalker(from, NodeFilter.SHOW_TEXT);
|
|
2996
|
+
let current = walker.nextNode();
|
|
2997
|
+
while (current !== null) {
|
|
2998
|
+
callback(current);
|
|
2999
|
+
current = walker.nextNode();
|
|
3000
|
+
}
|
|
3001
|
+
};
|
|
3002
|
+
const settle = (text) => {
|
|
3003
|
+
if (!revealable(text, root)) return;
|
|
3004
|
+
const chars = [...text.data];
|
|
3005
|
+
records.set(text, {
|
|
3006
|
+
chars,
|
|
3007
|
+
full: text.data,
|
|
3008
|
+
shown: chars.length
|
|
3009
|
+
});
|
|
3010
|
+
};
|
|
3011
|
+
const snapshotVisible = () => {
|
|
3012
|
+
const current = /* @__PURE__ */ new Set();
|
|
3013
|
+
forEachText(root, (text) => {
|
|
3014
|
+
if (!revealable(text, root)) return;
|
|
3015
|
+
current.add(text);
|
|
3016
|
+
settle(text);
|
|
3017
|
+
});
|
|
3018
|
+
for (const text of records.keys()) if (!current.has(text)) records.delete(text);
|
|
3019
|
+
};
|
|
3020
|
+
if (!enabled) {
|
|
3021
|
+
snapshotVisible();
|
|
3022
|
+
const observer = typeof MutationObserver === "undefined" ? null : new MutationObserver(snapshotVisible);
|
|
3023
|
+
observer?.observe(root, {
|
|
3024
|
+
childList: true,
|
|
3025
|
+
characterData: true,
|
|
3026
|
+
subtree: true
|
|
3027
|
+
});
|
|
3028
|
+
return () => {
|
|
3029
|
+
observer?.disconnect();
|
|
3030
|
+
};
|
|
3031
|
+
}
|
|
1839
3032
|
const pending = /* @__PURE__ */ new Set();
|
|
1840
3033
|
const internalWrites = /* @__PURE__ */ new WeakMap();
|
|
1841
3034
|
let rafId = 0;
|
|
@@ -1843,10 +3036,12 @@ window.__ModuleLoader__.load({
|
|
|
1843
3036
|
let debt = 0;
|
|
1844
3037
|
let stopped = false;
|
|
1845
3038
|
let announcedSettled = false;
|
|
3039
|
+
const streamId = `dom-${Math.random().toString(36).slice(2)}`;
|
|
1846
3040
|
const announceSettled = () => {
|
|
1847
3041
|
lastFrame = null;
|
|
1848
3042
|
debt = 0;
|
|
1849
3043
|
speedCpsRef.current = 35;
|
|
3044
|
+
debugRuntime.reportStream(streamId, null);
|
|
1850
3045
|
if (announcedSettled) return;
|
|
1851
3046
|
announcedSettled = true;
|
|
1852
3047
|
onSettled?.();
|
|
@@ -1873,18 +3068,21 @@ window.__ModuleLoader__.load({
|
|
|
1873
3068
|
write(node, chars.slice(0, preserved).join(""));
|
|
1874
3069
|
};
|
|
1875
3070
|
const visit = (from, reveal) => {
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
if (reveal)
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
3071
|
+
forEachText(from, (text) => {
|
|
3072
|
+
if (!revealable(text, root)) return;
|
|
3073
|
+
if (reveal) {
|
|
3074
|
+
enqueue(text, text.data, records.get(text));
|
|
3075
|
+
return;
|
|
3076
|
+
}
|
|
3077
|
+
settle(text);
|
|
3078
|
+
});
|
|
3079
|
+
};
|
|
3080
|
+
const forget = (from) => {
|
|
3081
|
+
forEachText(from, (text) => {
|
|
3082
|
+
if (root.contains(text)) return;
|
|
3083
|
+
records.delete(text);
|
|
3084
|
+
pending.delete(text);
|
|
3085
|
+
});
|
|
1888
3086
|
};
|
|
1889
3087
|
const scheduleFrame = () => {
|
|
1890
3088
|
if (stopped || pending.size === 0 || rafId !== 0) return;
|
|
@@ -1910,7 +3108,7 @@ window.__ModuleLoader__.load({
|
|
|
1910
3108
|
const record = records.get(node);
|
|
1911
3109
|
if (record !== void 0) backlog += record.chars.length - record.shown;
|
|
1912
3110
|
}
|
|
1913
|
-
const step = computeAdaptiveQueueStep(backlog, elapsed, debt);
|
|
3111
|
+
const step = computeAdaptiveQueueStep(backlog, elapsed, debt, 1, debugRuntime.activeTuning());
|
|
1914
3112
|
debt = step.debt;
|
|
1915
3113
|
speedCpsRef.current = step.speedCps;
|
|
1916
3114
|
let remaining = step.revealChars;
|
|
@@ -1919,6 +3117,7 @@ window.__ModuleLoader__.load({
|
|
|
1919
3117
|
const record = records.get(node);
|
|
1920
3118
|
if (record === void 0 || !node.isConnected) {
|
|
1921
3119
|
pending.delete(node);
|
|
3120
|
+
records.delete(node);
|
|
1922
3121
|
continue;
|
|
1923
3122
|
}
|
|
1924
3123
|
const amount = Math.min(remaining, record.chars.length - record.shown);
|
|
@@ -1932,10 +3131,30 @@ window.__ModuleLoader__.load({
|
|
|
1932
3131
|
remaining -= amount;
|
|
1933
3132
|
if (shown >= next.chars.length) pending.delete(node);
|
|
1934
3133
|
}
|
|
3134
|
+
let targetChars = 0;
|
|
3135
|
+
let displayedChars = 0;
|
|
3136
|
+
let nextBacklog = 0;
|
|
3137
|
+
for (const [node, record] of records) {
|
|
3138
|
+
if (!node.isConnected) {
|
|
3139
|
+
records.delete(node);
|
|
3140
|
+
pending.delete(node);
|
|
3141
|
+
continue;
|
|
3142
|
+
}
|
|
3143
|
+
targetChars += record.chars.length;
|
|
3144
|
+
displayedChars += record.shown;
|
|
3145
|
+
if (pending.has(node)) nextBacklog += record.chars.length - record.shown;
|
|
3146
|
+
}
|
|
3147
|
+
debugRuntime.reportStream(streamId, {
|
|
3148
|
+
backlog: nextBacklog,
|
|
3149
|
+
speedCps: step.speedCps,
|
|
3150
|
+
targetChars,
|
|
3151
|
+
displayedChars,
|
|
3152
|
+
active: pending.size > 0
|
|
3153
|
+
});
|
|
1935
3154
|
if (pending.size === 0) announceSettled();
|
|
1936
3155
|
else scheduleFrame();
|
|
1937
3156
|
};
|
|
1938
|
-
|
|
3157
|
+
visit(root, revealInitial);
|
|
1939
3158
|
const observer = typeof MutationObserver === "undefined" ? null : new MutationObserver((mutations) => {
|
|
1940
3159
|
for (const mutation of mutations) {
|
|
1941
3160
|
if (mutation.type === "characterData") {
|
|
@@ -1947,6 +3166,7 @@ window.__ModuleLoader__.load({
|
|
|
1947
3166
|
enqueue(node, node.data, records.get(node));
|
|
1948
3167
|
continue;
|
|
1949
3168
|
}
|
|
3169
|
+
for (const removed of mutation.removedNodes) forget(removed);
|
|
1950
3170
|
for (const added of mutation.addedNodes) visit(added, true);
|
|
1951
3171
|
}
|
|
1952
3172
|
if (pending.size === 0) announceSettled();
|
|
@@ -1969,6 +3189,7 @@ window.__ModuleLoader__.load({
|
|
|
1969
3189
|
}
|
|
1970
3190
|
pending.clear();
|
|
1971
3191
|
speedCpsRef.current = 35;
|
|
3192
|
+
debugRuntime.reportStream(streamId, null);
|
|
1972
3193
|
};
|
|
1973
3194
|
}, [
|
|
1974
3195
|
enabled,
|
|
@@ -2151,50 +3372,51 @@ window.__ModuleLoader__.load({
|
|
|
2151
3372
|
}
|
|
2152
3373
|
//#endregion
|
|
2153
3374
|
//#region \0dsh-css:/Users/dzlin/work/project/dsh-smooth-stream/src/client/SmoothStreamCard.module.css.mjs
|
|
2154
|
-
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}";
|
|
2155
|
-
const tagId = "dsh-smooth-stream/SmoothStreamCard.module.css";
|
|
3375
|
+
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}";
|
|
3376
|
+
const tagId$1 = "dsh-smooth-stream/SmoothStreamCard.module.css";
|
|
2156
3377
|
if (typeof document !== "undefined") {
|
|
2157
|
-
let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]");
|
|
3378
|
+
let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]");
|
|
2158
3379
|
if (tag === null) {
|
|
2159
3380
|
tag = document.createElement("style");
|
|
2160
3381
|
tag.dataset.plugin = "dsh-smooth-stream";
|
|
2161
|
-
tag.dataset.pluginCss = tagId;
|
|
3382
|
+
tag.dataset.pluginCss = tagId$1;
|
|
2162
3383
|
document.head.appendChild(tag);
|
|
2163
3384
|
}
|
|
2164
|
-
tag.textContent = css;
|
|
3385
|
+
tag.textContent = css$1;
|
|
2165
3386
|
}
|
|
2166
3387
|
var SmoothStreamCard_module_css_default = {
|
|
2167
|
-
"header": "XNGeRG_header",
|
|
2168
|
-
"version": "XNGeRG_version",
|
|
2169
|
-
"readOnly": "XNGeRG_readOnly",
|
|
2170
|
-
"footer": "XNGeRG_footer",
|
|
2171
|
-
"label": "XNGeRG_label",
|
|
2172
|
-
"hint": "XNGeRG_hint",
|
|
2173
3388
|
"fieldHead": "XNGeRG_fieldHead",
|
|
3389
|
+
"label": "XNGeRG_label",
|
|
3390
|
+
"version": "XNGeRG_version",
|
|
3391
|
+
"header": "XNGeRG_header",
|
|
3392
|
+
"body": "XNGeRG_body",
|
|
2174
3393
|
"failure": "XNGeRG_failure",
|
|
2175
|
-
"
|
|
3394
|
+
"updateRow": "XNGeRG_updateRow",
|
|
3395
|
+
"updateCopy": "XNGeRG_updateCopy",
|
|
2176
3396
|
"field": "XNGeRG_field",
|
|
2177
|
-
"
|
|
2178
|
-
"pending": "XNGeRG_pending",
|
|
2179
|
-
"card": "XNGeRG_card",
|
|
2180
|
-
"cardOpen": "XNGeRG_cardOpen",
|
|
2181
|
-
"chevron": "XNGeRG_chevron",
|
|
2182
|
-
"toggle": "XNGeRG_toggle",
|
|
2183
|
-
"body": "XNGeRG_body",
|
|
3397
|
+
"footer": "XNGeRG_footer",
|
|
2184
3398
|
"name": "XNGeRG_name",
|
|
2185
3399
|
"failed": "XNGeRG_failed",
|
|
2186
|
-
"updateRow": "XNGeRG_updateRow",
|
|
2187
3400
|
"discard": "XNGeRG_discard",
|
|
2188
|
-
"
|
|
3401
|
+
"toggle": "XNGeRG_toggle",
|
|
3402
|
+
"fieldDisabled": "XNGeRG_fieldDisabled",
|
|
3403
|
+
"readOnly": "XNGeRG_readOnly",
|
|
2189
3404
|
"headText": "XNGeRG_headText",
|
|
2190
|
-
"
|
|
3405
|
+
"cardOpen": "XNGeRG_cardOpen",
|
|
3406
|
+
"save": "XNGeRG_save",
|
|
3407
|
+
"hint": "XNGeRG_hint",
|
|
3408
|
+
"chevron": "XNGeRG_chevron",
|
|
3409
|
+
"update": "XNGeRG_update",
|
|
3410
|
+
"pending": "XNGeRG_pending",
|
|
3411
|
+
"description": "XNGeRG_description",
|
|
3412
|
+
"card": "XNGeRG_card",
|
|
2191
3413
|
"chevronOpen": "XNGeRG_chevronOpen"
|
|
2192
3414
|
};
|
|
2193
3415
|
//#endregion
|
|
2194
3416
|
//#region src/client/SmoothStreamCard.tsx
|
|
2195
3417
|
/**
|
|
2196
3418
|
* The smooth-stream plugin configuration card, rendered inside the Web
|
|
2197
|
-
* Settings "plugin configuration" page.
|
|
3419
|
+
* Settings "plugin configuration" page. Preferences are staged until the user
|
|
2198
3420
|
* saves — the same shape as the Host-shipped cards, hand-drawn because the
|
|
2199
3421
|
* Host cards' chrome is not exported for reuse.
|
|
2200
3422
|
*/
|
|
@@ -2264,6 +3486,27 @@ window.__ModuleLoader__.load({
|
|
|
2264
3486
|
}) : null,
|
|
2265
3487
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2266
3488
|
className: SmoothStreamCard_module_css_default.field,
|
|
3489
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3490
|
+
className: SmoothStreamCard_module_css_default.fieldHead,
|
|
3491
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3492
|
+
className: SmoothStreamCard_module_css_default.label,
|
|
3493
|
+
children: t("enabled")
|
|
3494
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3495
|
+
type: "checkbox",
|
|
3496
|
+
className: SmoothStreamCard_module_css_default.toggle,
|
|
3497
|
+
checked: state.enabled,
|
|
3498
|
+
disabled: !state.writable || state.saving,
|
|
3499
|
+
onChange: (event) => {
|
|
3500
|
+
props.edit({ enabled: event.target.checked });
|
|
3501
|
+
}
|
|
3502
|
+
})]
|
|
3503
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3504
|
+
className: SmoothStreamCard_module_css_default.hint,
|
|
3505
|
+
children: t("enabledHint")
|
|
3506
|
+
})]
|
|
3507
|
+
}),
|
|
3508
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3509
|
+
className: state.enabled ? SmoothStreamCard_module_css_default.field : `${SmoothStreamCard_module_css_default.field} ${SmoothStreamCard_module_css_default.fieldDisabled}`,
|
|
2267
3510
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2268
3511
|
className: SmoothStreamCard_module_css_default.fieldHead,
|
|
2269
3512
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -2273,9 +3516,9 @@ window.__ModuleLoader__.load({
|
|
|
2273
3516
|
type: "checkbox",
|
|
2274
3517
|
className: SmoothStreamCard_module_css_default.toggle,
|
|
2275
3518
|
checked: state.thinkAutoExpand,
|
|
2276
|
-
disabled: !state.writable,
|
|
3519
|
+
disabled: !state.writable || state.saving || !state.enabled,
|
|
2277
3520
|
onChange: (event) => {
|
|
2278
|
-
props.edit(event.target.checked);
|
|
3521
|
+
props.edit({ thinkAutoExpand: event.target.checked });
|
|
2279
3522
|
}
|
|
2280
3523
|
})]
|
|
2281
3524
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -2283,6 +3526,48 @@ window.__ModuleLoader__.load({
|
|
|
2283
3526
|
children: t("thinkAutoExpandHint")
|
|
2284
3527
|
})]
|
|
2285
3528
|
}),
|
|
3529
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3530
|
+
className: SmoothStreamCard_module_css_default.field,
|
|
3531
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3532
|
+
className: SmoothStreamCard_module_css_default.fieldHead,
|
|
3533
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3534
|
+
className: SmoothStreamCard_module_css_default.label,
|
|
3535
|
+
children: t("autoCollapse")
|
|
3536
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3537
|
+
type: "checkbox",
|
|
3538
|
+
className: SmoothStreamCard_module_css_default.toggle,
|
|
3539
|
+
checked: state.autoCollapse,
|
|
3540
|
+
disabled: !state.writable || state.saving,
|
|
3541
|
+
onChange: (event) => {
|
|
3542
|
+
props.edit({ autoCollapse: event.target.checked });
|
|
3543
|
+
}
|
|
3544
|
+
})]
|
|
3545
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3546
|
+
className: SmoothStreamCard_module_css_default.hint,
|
|
3547
|
+
children: t("autoCollapseHint")
|
|
3548
|
+
})]
|
|
3549
|
+
}),
|
|
3550
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3551
|
+
className: state.debugAvailable ? SmoothStreamCard_module_css_default.field : `${SmoothStreamCard_module_css_default.field} ${SmoothStreamCard_module_css_default.fieldDisabled}`,
|
|
3552
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3553
|
+
className: SmoothStreamCard_module_css_default.fieldHead,
|
|
3554
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3555
|
+
className: SmoothStreamCard_module_css_default.label,
|
|
3556
|
+
children: t("debugEnabled")
|
|
3557
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3558
|
+
type: "checkbox",
|
|
3559
|
+
className: SmoothStreamCard_module_css_default.toggle,
|
|
3560
|
+
checked: state.debugEnabled,
|
|
3561
|
+
disabled: !state.debugAvailable || !state.writable || state.saving,
|
|
3562
|
+
onChange: (event) => {
|
|
3563
|
+
props.edit({ debugEnabled: event.target.checked });
|
|
3564
|
+
}
|
|
3565
|
+
})]
|
|
3566
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3567
|
+
className: SmoothStreamCard_module_css_default.hint,
|
|
3568
|
+
children: state.debugAvailable ? t("debugEnabledHint") : t("debugUnavailable")
|
|
3569
|
+
})]
|
|
3570
|
+
}),
|
|
2286
3571
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2287
3572
|
className: SmoothStreamCard_module_css_default.updateRow,
|
|
2288
3573
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -2348,7 +3633,9 @@ window.__ModuleLoader__.load({
|
|
|
2348
3633
|
api;
|
|
2349
3634
|
store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(this.projection());
|
|
2350
3635
|
loaded;
|
|
2351
|
-
|
|
3636
|
+
loadedDebug;
|
|
3637
|
+
stagedBase;
|
|
3638
|
+
stagedDebug;
|
|
2352
3639
|
saving = false;
|
|
2353
3640
|
failed = false;
|
|
2354
3641
|
upgrading = false;
|
|
@@ -2379,8 +3666,22 @@ window.__ModuleLoader__.load({
|
|
|
2379
3666
|
inject() {
|
|
2380
3667
|
return {
|
|
2381
3668
|
hooks: { smoothStreamCard: this.store },
|
|
2382
|
-
edit: (
|
|
2383
|
-
this.
|
|
3669
|
+
edit: (patch) => {
|
|
3670
|
+
if (this.saving) return;
|
|
3671
|
+
if (patch.enabled !== void 0 || patch.thinkAutoExpand !== void 0 || patch.autoCollapse !== void 0) this.stagedBase = {
|
|
3672
|
+
...this.baseValues(),
|
|
3673
|
+
...patch.enabled === void 0 ? {} : { enabled: patch.enabled },
|
|
3674
|
+
...patch.thinkAutoExpand === void 0 ? {} : { thinkAutoExpand: patch.thinkAutoExpand },
|
|
3675
|
+
...patch.autoCollapse === void 0 ? {} : { autoCollapse: patch.autoCollapse }
|
|
3676
|
+
};
|
|
3677
|
+
if (this.loadedDebug !== void 0 && (patch.debugEnabled !== void 0 || patch.debugTuning !== void 0)) this.stagedDebug = {
|
|
3678
|
+
...this.debugValues(),
|
|
3679
|
+
...patch.debugEnabled === void 0 ? {} : { debugEnabled: patch.debugEnabled },
|
|
3680
|
+
...patch.debugTuning === void 0 ? {} : { debugTuning: {
|
|
3681
|
+
...this.debugValues().debugTuning,
|
|
3682
|
+
...patch.debugTuning
|
|
3683
|
+
} }
|
|
3684
|
+
};
|
|
2384
3685
|
this.failed = false;
|
|
2385
3686
|
this.publish();
|
|
2386
3687
|
},
|
|
@@ -2388,8 +3689,9 @@ window.__ModuleLoader__.load({
|
|
|
2388
3689
|
this.save();
|
|
2389
3690
|
},
|
|
2390
3691
|
discard: () => {
|
|
2391
|
-
if (this.
|
|
2392
|
-
this.
|
|
3692
|
+
if (this.stagedBase === void 0 && this.stagedDebug === void 0 && !this.failed) return;
|
|
3693
|
+
this.stagedBase = void 0;
|
|
3694
|
+
this.stagedDebug = void 0;
|
|
2393
3695
|
this.failed = false;
|
|
2394
3696
|
this.publish();
|
|
2395
3697
|
},
|
|
@@ -2405,10 +3707,12 @@ window.__ModuleLoader__.load({
|
|
|
2405
3707
|
return {
|
|
2406
3708
|
status: this.loadStatus,
|
|
2407
3709
|
writable: this.loaded?.writable ?? false,
|
|
2408
|
-
dirty: this.
|
|
3710
|
+
dirty: this.stagedBase !== void 0 || this.stagedDebug !== void 0,
|
|
2409
3711
|
saving: this.saving,
|
|
2410
3712
|
failed: this.failed,
|
|
2411
|
-
|
|
3713
|
+
...this.baseValues(),
|
|
3714
|
+
...this.debugValues(),
|
|
3715
|
+
debugAvailable: this.loadedDebug !== void 0,
|
|
2412
3716
|
version: this.loaded?.version,
|
|
2413
3717
|
installation: this.loaded?.installation ?? "unmanaged",
|
|
2414
3718
|
canUpgrade: this.loaded?.canUpgrade ?? false,
|
|
@@ -2417,32 +3721,81 @@ window.__ModuleLoader__.load({
|
|
|
2417
3721
|
restartRequired: this.restartRequired
|
|
2418
3722
|
};
|
|
2419
3723
|
}
|
|
3724
|
+
baseValues() {
|
|
3725
|
+
return this.stagedBase ?? {
|
|
3726
|
+
enabled: this.loaded?.enabled ?? DEFAULT_STREAM_SETTINGS.enabled,
|
|
3727
|
+
thinkAutoExpand: this.loaded?.thinkAutoExpand ?? DEFAULT_STREAM_SETTINGS.thinkAutoExpand,
|
|
3728
|
+
autoCollapse: this.loaded?.autoCollapse ?? DEFAULT_STREAM_SETTINGS.autoCollapse
|
|
3729
|
+
};
|
|
3730
|
+
}
|
|
3731
|
+
debugValues() {
|
|
3732
|
+
return this.stagedDebug ?? {
|
|
3733
|
+
debugEnabled: this.loadedDebug?.debugEnabled ?? DEFAULT_STREAM_SETTINGS.debugEnabled,
|
|
3734
|
+
debugTuning: this.loadedDebug?.tuning ?? DEFAULT_STREAM_DEBUG_TUNING
|
|
3735
|
+
};
|
|
3736
|
+
}
|
|
3737
|
+
/** Complete settings projection consumed by the live SettingsCell bridge. */
|
|
3738
|
+
values() {
|
|
3739
|
+
return {
|
|
3740
|
+
...this.baseValues(),
|
|
3741
|
+
...this.debugValues()
|
|
3742
|
+
};
|
|
3743
|
+
}
|
|
2420
3744
|
async load() {
|
|
2421
3745
|
const generation = ++this.loadGeneration;
|
|
2422
3746
|
this.loadStatus = "loading";
|
|
2423
3747
|
this.loaded = void 0;
|
|
3748
|
+
this.loadedDebug = void 0;
|
|
2424
3749
|
this.publish();
|
|
2425
3750
|
try {
|
|
2426
3751
|
const view = await this.api.read();
|
|
2427
3752
|
if (generation !== this.loadGeneration) return;
|
|
2428
3753
|
this.loaded = view;
|
|
2429
3754
|
this.loadStatus = "ready";
|
|
3755
|
+
this.publish();
|
|
3756
|
+
try {
|
|
3757
|
+
const debug = await this.api.readDebug();
|
|
3758
|
+
if (generation !== this.loadGeneration) return;
|
|
3759
|
+
this.loadedDebug = debug;
|
|
3760
|
+
} catch {
|
|
3761
|
+
if (generation !== this.loadGeneration) return;
|
|
3762
|
+
this.loadedDebug = void 0;
|
|
3763
|
+
}
|
|
2430
3764
|
} catch {
|
|
2431
3765
|
if (generation !== this.loadGeneration) return;
|
|
2432
3766
|
this.loaded = void 0;
|
|
3767
|
+
this.loadedDebug = void 0;
|
|
2433
3768
|
this.loadStatus = "unavailable";
|
|
2434
3769
|
}
|
|
2435
3770
|
this.publish();
|
|
2436
3771
|
}
|
|
2437
3772
|
async save() {
|
|
2438
|
-
if (this.
|
|
2439
|
-
const
|
|
3773
|
+
if (this.stagedBase === void 0 && this.stagedDebug === void 0 || this.saving || this.loaded?.writable !== true) return;
|
|
3774
|
+
const base = this.stagedBase;
|
|
3775
|
+
const debug = this.stagedDebug;
|
|
2440
3776
|
this.saving = true;
|
|
2441
3777
|
this.failed = false;
|
|
2442
3778
|
this.publish();
|
|
2443
3779
|
try {
|
|
2444
|
-
|
|
2445
|
-
|
|
3780
|
+
if (base !== void 0) {
|
|
3781
|
+
const combined = debug !== void 0 && this.loadedDebug !== void 0;
|
|
3782
|
+
this.loaded = await this.api.write(combined ? {
|
|
3783
|
+
...base,
|
|
3784
|
+
...debug
|
|
3785
|
+
} : base);
|
|
3786
|
+
this.stagedBase = void 0;
|
|
3787
|
+
if (combined) {
|
|
3788
|
+
this.loadedDebug = {
|
|
3789
|
+
debugEnabled: debug.debugEnabled,
|
|
3790
|
+
tuning: { ...debug.debugTuning }
|
|
3791
|
+
};
|
|
3792
|
+
this.stagedDebug = void 0;
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
if (debug !== void 0 && this.loadedDebug !== void 0 && this.stagedDebug !== void 0) {
|
|
3796
|
+
this.loadedDebug = await this.api.writeDebug(debug);
|
|
3797
|
+
this.stagedDebug = void 0;
|
|
3798
|
+
}
|
|
2446
3799
|
} catch {
|
|
2447
3800
|
this.failed = true;
|
|
2448
3801
|
}
|
|
@@ -2477,6 +3830,8 @@ window.__ModuleLoader__.load({
|
|
|
2477
3830
|
const STREAM_SETTINGS_RPC = {
|
|
2478
3831
|
read: "settings.read",
|
|
2479
3832
|
write: "settings.write",
|
|
3833
|
+
debugRead: "debug.read",
|
|
3834
|
+
debugWrite: "debug.write",
|
|
2480
3835
|
upgrade: "plugin.upgrade"
|
|
2481
3836
|
};
|
|
2482
3837
|
//#endregion
|
|
@@ -2490,13 +3845,22 @@ window.__ModuleLoader__.load({
|
|
|
2490
3845
|
"npm",
|
|
2491
3846
|
"development",
|
|
2492
3847
|
"unmanaged"
|
|
2493
|
-
].includes(data.installation) || typeof data.writable !== "boolean" || typeof data.thinkAutoExpand !== "boolean" || typeof data.canUpgrade !== "boolean") throw new Error("dsh-smooth-stream: malformed settings response");
|
|
3848
|
+
].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");
|
|
2494
3849
|
return data;
|
|
2495
3850
|
}
|
|
2496
3851
|
function upgradeView(value) {
|
|
2497
3852
|
if (record(value)?.restartRequired !== true) throw new Error("dsh-smooth-stream: malformed update response");
|
|
2498
3853
|
return { restartRequired: true };
|
|
2499
3854
|
}
|
|
3855
|
+
function debugSettingsView(value) {
|
|
3856
|
+
const data = record(value);
|
|
3857
|
+
const tuning = record(data?.tuning);
|
|
3858
|
+
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");
|
|
3859
|
+
return {
|
|
3860
|
+
debugEnabled: data.debugEnabled,
|
|
3861
|
+
tuning
|
|
3862
|
+
};
|
|
3863
|
+
}
|
|
2500
3864
|
function accepted(result) {
|
|
2501
3865
|
if (!result.ok) throw new Error(result.error.message);
|
|
2502
3866
|
return result.value;
|
|
@@ -2507,8 +3871,25 @@ window.__ModuleLoader__.load({
|
|
|
2507
3871
|
async read() {
|
|
2508
3872
|
return settingsView(accepted(await connection.rpc.call(STREAM_SETTINGS_RPC_CHANNEL, STREAM_SETTINGS_RPC.read, {})));
|
|
2509
3873
|
},
|
|
2510
|
-
async write(
|
|
2511
|
-
return settingsView(accepted(await connection.rpc.call(STREAM_SETTINGS_RPC_CHANNEL, STREAM_SETTINGS_RPC.write, {
|
|
3874
|
+
async write(settings) {
|
|
3875
|
+
return settingsView(accepted(await connection.rpc.call(STREAM_SETTINGS_RPC_CHANNEL, STREAM_SETTINGS_RPC.write, {
|
|
3876
|
+
enabled: settings.enabled,
|
|
3877
|
+
thinkAutoExpand: settings.thinkAutoExpand,
|
|
3878
|
+
autoCollapse: settings.autoCollapse,
|
|
3879
|
+
...settings.debugEnabled === void 0 || settings.debugTuning === void 0 ? {} : {
|
|
3880
|
+
debugEnabled: settings.debugEnabled,
|
|
3881
|
+
debugTuning: settings.debugTuning
|
|
3882
|
+
}
|
|
3883
|
+
})));
|
|
3884
|
+
},
|
|
3885
|
+
async readDebug() {
|
|
3886
|
+
return debugSettingsView(accepted(await connection.rpc.call(STREAM_SETTINGS_RPC_CHANNEL, STREAM_SETTINGS_RPC.debugRead, {})));
|
|
3887
|
+
},
|
|
3888
|
+
async writeDebug(settings) {
|
|
3889
|
+
return debugSettingsView(accepted(await connection.rpc.call(STREAM_SETTINGS_RPC_CHANNEL, STREAM_SETTINGS_RPC.debugWrite, {
|
|
3890
|
+
debugEnabled: settings.debugEnabled,
|
|
3891
|
+
tuning: settings.debugTuning
|
|
3892
|
+
})));
|
|
2512
3893
|
},
|
|
2513
3894
|
async upgrade() {
|
|
2514
3895
|
return upgradeView(accepted(await connection.rpc.call(STREAM_SETTINGS_RPC_CHANNEL, STREAM_SETTINGS_RPC.upgrade, {})));
|
|
@@ -2516,6 +3897,433 @@ window.__ModuleLoader__.load({
|
|
|
2516
3897
|
};
|
|
2517
3898
|
}
|
|
2518
3899
|
//#endregion
|
|
3900
|
+
//#region \0dsh-css:/Users/dzlin/work/project/dsh-smooth-stream/src/client/DebugPanel.module.css.mjs
|
|
3901
|
+
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}}";
|
|
3902
|
+
const tagId = "dsh-smooth-stream/DebugPanel.module.css";
|
|
3903
|
+
if (typeof document !== "undefined") {
|
|
3904
|
+
let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]");
|
|
3905
|
+
if (tag === null) {
|
|
3906
|
+
tag = document.createElement("style");
|
|
3907
|
+
tag.dataset.plugin = "dsh-smooth-stream";
|
|
3908
|
+
tag.dataset.pluginCss = tagId;
|
|
3909
|
+
document.head.appendChild(tag);
|
|
3910
|
+
}
|
|
3911
|
+
tag.textContent = css;
|
|
3912
|
+
}
|
|
3913
|
+
var DebugPanel_module_css_default = {
|
|
3914
|
+
"control": "m4_tdG_control",
|
|
3915
|
+
"secondaryButton": "m4_tdG_secondaryButton",
|
|
3916
|
+
"infoButton": "m4_tdG_infoButton",
|
|
3917
|
+
"footerSpacer": "m4_tdG_footerSpacer",
|
|
3918
|
+
"visuallyHidden": "m4_tdG_visuallyHidden",
|
|
3919
|
+
"panelHeader": "m4_tdG_panelHeader",
|
|
3920
|
+
"metric": "m4_tdG_metric",
|
|
3921
|
+
"section": "m4_tdG_section",
|
|
3922
|
+
"unsaved": "m4_tdG_unsaved",
|
|
3923
|
+
"state": "m4_tdG_state",
|
|
3924
|
+
"statusDot": "m4_tdG_statusDot",
|
|
3925
|
+
"controlLabel": "m4_tdG_controlLabel",
|
|
3926
|
+
"scrollArea": "m4_tdG_scrollArea",
|
|
3927
|
+
"numberWrap": "m4_tdG_numberWrap",
|
|
3928
|
+
"title": "m4_tdG_title",
|
|
3929
|
+
"trigger": "m4_tdG_trigger",
|
|
3930
|
+
"footer": "m4_tdG_footer",
|
|
3931
|
+
"iconButton": "m4_tdG_iconButton",
|
|
3932
|
+
"panel": "m4_tdG_panel",
|
|
3933
|
+
"controlHead": "m4_tdG_controlHead",
|
|
3934
|
+
"triggerActive": "m4_tdG_triggerActive",
|
|
3935
|
+
"range": "m4_tdG_range",
|
|
3936
|
+
"unit": "m4_tdG_unit",
|
|
3937
|
+
"primaryButton": "m4_tdG_primaryButton",
|
|
3938
|
+
"number": "m4_tdG_number",
|
|
3939
|
+
"metrics": "m4_tdG_metrics",
|
|
3940
|
+
"statusLive": "m4_tdG_statusLive",
|
|
3941
|
+
"guide": "m4_tdG_guide"
|
|
3942
|
+
};
|
|
3943
|
+
//#endregion
|
|
3944
|
+
//#region src/client/DebugPanel.tsx
|
|
3945
|
+
const REVEAL_CONTROLS = [
|
|
3946
|
+
{
|
|
3947
|
+
key: "revealScale",
|
|
3948
|
+
label: "debugRevealMultiplier",
|
|
3949
|
+
tip: "debugTipRevealMultiplier",
|
|
3950
|
+
min: .25,
|
|
3951
|
+
max: 2,
|
|
3952
|
+
step: .05,
|
|
3953
|
+
unit: "x"
|
|
3954
|
+
},
|
|
3955
|
+
{
|
|
3956
|
+
key: "queuePressure",
|
|
3957
|
+
label: "debugQueuePressure",
|
|
3958
|
+
tip: "debugTipQueuePressure",
|
|
3959
|
+
min: 0,
|
|
3960
|
+
max: 2,
|
|
3961
|
+
step: .05,
|
|
3962
|
+
unit: "x"
|
|
3963
|
+
},
|
|
3964
|
+
{
|
|
3965
|
+
key: "maxRevealCps",
|
|
3966
|
+
label: "debugMaxReveal",
|
|
3967
|
+
tip: "debugTipMaxReveal",
|
|
3968
|
+
min: 120,
|
|
3969
|
+
max: 1e3,
|
|
3970
|
+
step: 10,
|
|
3971
|
+
unit: "cps"
|
|
3972
|
+
}
|
|
3973
|
+
];
|
|
3974
|
+
const FOLLOW_CONTROLS = [
|
|
3975
|
+
{
|
|
3976
|
+
key: "springStiffness",
|
|
3977
|
+
label: "debugSpringStiffness",
|
|
3978
|
+
tip: "debugTipSpringStiffness",
|
|
3979
|
+
min: 40,
|
|
3980
|
+
max: 320,
|
|
3981
|
+
step: 5,
|
|
3982
|
+
unit: ""
|
|
3983
|
+
},
|
|
3984
|
+
{
|
|
3985
|
+
key: "springDamping",
|
|
3986
|
+
label: "debugSpringDamping",
|
|
3987
|
+
tip: "debugTipSpringDamping",
|
|
3988
|
+
min: 8,
|
|
3989
|
+
max: 80,
|
|
3990
|
+
step: 1,
|
|
3991
|
+
unit: ""
|
|
3992
|
+
},
|
|
3993
|
+
{
|
|
3994
|
+
key: "springMass",
|
|
3995
|
+
label: "debugSpringMass",
|
|
3996
|
+
tip: "debugTipSpringMass",
|
|
3997
|
+
min: .5,
|
|
3998
|
+
max: 3,
|
|
3999
|
+
step: .05,
|
|
4000
|
+
unit: ""
|
|
4001
|
+
},
|
|
4002
|
+
{
|
|
4003
|
+
key: "runwayPx",
|
|
4004
|
+
label: "debugRunway",
|
|
4005
|
+
tip: "debugTipRunway",
|
|
4006
|
+
min: 0,
|
|
4007
|
+
max: 120,
|
|
4008
|
+
step: 2,
|
|
4009
|
+
unit: "px"
|
|
4010
|
+
},
|
|
4011
|
+
{
|
|
4012
|
+
key: "reserveResponseMs",
|
|
4013
|
+
label: "debugReserveResponse",
|
|
4014
|
+
tip: "debugTipReserveResponse",
|
|
4015
|
+
min: 60,
|
|
4016
|
+
max: 600,
|
|
4017
|
+
step: 10,
|
|
4018
|
+
unit: "ms"
|
|
4019
|
+
},
|
|
4020
|
+
{
|
|
4021
|
+
key: "backpressureMinScale",
|
|
4022
|
+
label: "debugBackpressureMin",
|
|
4023
|
+
tip: "debugTipBackpressureMin",
|
|
4024
|
+
min: .25,
|
|
4025
|
+
max: 1,
|
|
4026
|
+
step: .05,
|
|
4027
|
+
unit: "x"
|
|
4028
|
+
}
|
|
4029
|
+
];
|
|
4030
|
+
function fixed(value, digits = 1) {
|
|
4031
|
+
return value === null || !Number.isFinite(value) ? "-" : value.toFixed(digits);
|
|
4032
|
+
}
|
|
4033
|
+
function Metric({ label, value, tone }) {
|
|
4034
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4035
|
+
className: DebugPanel_module_css_default.metric,
|
|
4036
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
|
|
4037
|
+
"data-tone": tone,
|
|
4038
|
+
children: value
|
|
4039
|
+
})]
|
|
4040
|
+
});
|
|
4041
|
+
}
|
|
4042
|
+
function TuningField({ control, state, edit, label, t }) {
|
|
4043
|
+
const value = state.tuning[control.key];
|
|
4044
|
+
const labelId = `smooth-stream-debug-${control.key}`;
|
|
4045
|
+
const update = (next) => {
|
|
4046
|
+
if (!Number.isFinite(next)) return;
|
|
4047
|
+
const clamped = Math.min(control.max, Math.max(control.min, next));
|
|
4048
|
+
edit({ debugTuning: {
|
|
4049
|
+
...state.tuning,
|
|
4050
|
+
[control.key]: clamped
|
|
4051
|
+
} });
|
|
4052
|
+
};
|
|
4053
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4054
|
+
className: DebugPanel_module_css_default.control,
|
|
4055
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4056
|
+
className: DebugPanel_module_css_default.controlHead,
|
|
4057
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4058
|
+
className: DebugPanel_module_css_default.controlLabel,
|
|
4059
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4060
|
+
id: labelId,
|
|
4061
|
+
children: label
|
|
4062
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
4063
|
+
label: t(control.tip),
|
|
4064
|
+
side: "right",
|
|
4065
|
+
maxWidth: 300,
|
|
4066
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4067
|
+
className: DebugPanel_module_css_default.infoButton,
|
|
4068
|
+
type: "button",
|
|
4069
|
+
"aria-label": label,
|
|
4070
|
+
title: t(control.tip),
|
|
4071
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQuestionOutline14, {})
|
|
4072
|
+
})
|
|
4073
|
+
})]
|
|
4074
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4075
|
+
className: DebugPanel_module_css_default.numberWrap,
|
|
4076
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4077
|
+
className: DebugPanel_module_css_default.number,
|
|
4078
|
+
type: "number",
|
|
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
|
+
}), control.unit === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4089
|
+
className: DebugPanel_module_css_default.unit,
|
|
4090
|
+
children: control.unit
|
|
4091
|
+
})]
|
|
4092
|
+
})]
|
|
4093
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4094
|
+
className: DebugPanel_module_css_default.range,
|
|
4095
|
+
type: "range",
|
|
4096
|
+
"aria-labelledby": labelId,
|
|
4097
|
+
min: control.min,
|
|
4098
|
+
max: control.max,
|
|
4099
|
+
step: control.step,
|
|
4100
|
+
value,
|
|
4101
|
+
disabled: !state.writable,
|
|
4102
|
+
onChange: (event) => {
|
|
4103
|
+
update(event.currentTarget.valueAsNumber);
|
|
4104
|
+
}
|
|
4105
|
+
})]
|
|
4106
|
+
});
|
|
4107
|
+
}
|
|
4108
|
+
function DebugPanel(props) {
|
|
4109
|
+
const { t } = props;
|
|
4110
|
+
const state = props.useDebugRuntime((snapshot) => snapshot);
|
|
4111
|
+
const [open, setOpen] = (0, react.useState)(true);
|
|
4112
|
+
const [copied, setCopied] = (0, react.useState)(false);
|
|
4113
|
+
(0, react.useEffect)(() => {
|
|
4114
|
+
if (!copied) return;
|
|
4115
|
+
const timer = setTimeout(() => {
|
|
4116
|
+
setCopied(false);
|
|
4117
|
+
}, 1400);
|
|
4118
|
+
return () => {
|
|
4119
|
+
clearTimeout(timer);
|
|
4120
|
+
};
|
|
4121
|
+
}, [copied]);
|
|
4122
|
+
(0, react.useEffect)(() => {
|
|
4123
|
+
if (state.enabled) setOpen(true);
|
|
4124
|
+
}, [state.enabled]);
|
|
4125
|
+
if (!state.available || !state.enabled || !open) return null;
|
|
4126
|
+
const metrics = state.metrics;
|
|
4127
|
+
const live = metrics.streamActive || metrics.followActive;
|
|
4128
|
+
const progress = metrics.streamTargetChars <= 0 ? "-" : `${String(metrics.streamDisplayedChars)} / ${String(metrics.streamTargetChars)}`;
|
|
4129
|
+
const copyDiagnostics = async () => {
|
|
4130
|
+
if (await (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(JSON.stringify({
|
|
4131
|
+
tuning: state.tuning,
|
|
4132
|
+
metrics: state.metrics
|
|
4133
|
+
}, null, 2))) setCopied(true);
|
|
4134
|
+
};
|
|
4135
|
+
const panel = !state.enabled || !open ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
|
|
4136
|
+
className: DebugPanel_module_css_default.panel,
|
|
4137
|
+
role: "complementary",
|
|
4138
|
+
"aria-label": t("debugPanelTitle"),
|
|
4139
|
+
children: [
|
|
4140
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
4141
|
+
className: DebugPanel_module_css_default.panelHeader,
|
|
4142
|
+
children: [
|
|
4143
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4144
|
+
className: live ? `${DebugPanel_module_css_default.statusDot} ${DebugPanel_module_css_default.statusLive}` : DebugPanel_module_css_default.statusDot,
|
|
4145
|
+
"aria-hidden": true
|
|
4146
|
+
}),
|
|
4147
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4148
|
+
className: DebugPanel_module_css_default.title,
|
|
4149
|
+
children: t("debugPanelTitle")
|
|
4150
|
+
}),
|
|
4151
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4152
|
+
className: DebugPanel_module_css_default.state,
|
|
4153
|
+
children: t(live ? "debugLive" : "debugIdle")
|
|
4154
|
+
}),
|
|
4155
|
+
state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4156
|
+
className: DebugPanel_module_css_default.unsaved,
|
|
4157
|
+
children: t("debugUnsaved")
|
|
4158
|
+
}) : null,
|
|
4159
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4160
|
+
className: DebugPanel_module_css_default.iconButton,
|
|
4161
|
+
type: "button",
|
|
4162
|
+
title: t("debugCopy"),
|
|
4163
|
+
"aria-label": t("debugCopy"),
|
|
4164
|
+
onClick: () => {
|
|
4165
|
+
copyDiagnostics();
|
|
4166
|
+
},
|
|
4167
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, {})
|
|
4168
|
+
}),
|
|
4169
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4170
|
+
className: DebugPanel_module_css_default.iconButton,
|
|
4171
|
+
type: "button",
|
|
4172
|
+
title: t("debugPanelClose"),
|
|
4173
|
+
"aria-label": t("debugPanelClose"),
|
|
4174
|
+
disabled: !state.writable,
|
|
4175
|
+
onClick: () => {
|
|
4176
|
+
setOpen(false);
|
|
4177
|
+
props.edit({ debugEnabled: false });
|
|
4178
|
+
props.save();
|
|
4179
|
+
},
|
|
4180
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, {})
|
|
4181
|
+
}),
|
|
4182
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4183
|
+
className: DebugPanel_module_css_default.visuallyHidden,
|
|
4184
|
+
"aria-live": "polite",
|
|
4185
|
+
children: copied ? t("debugCopied") : ""
|
|
4186
|
+
})
|
|
4187
|
+
]
|
|
4188
|
+
}),
|
|
4189
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4190
|
+
className: DebugPanel_module_css_default.scrollArea,
|
|
4191
|
+
children: [
|
|
4192
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4193
|
+
className: DebugPanel_module_css_default.guide,
|
|
4194
|
+
children: t("debugGuide")
|
|
4195
|
+
}),
|
|
4196
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4197
|
+
className: DebugPanel_module_css_default.section,
|
|
4198
|
+
"aria-labelledby": "smooth-stream-live-heading",
|
|
4199
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
4200
|
+
id: "smooth-stream-live-heading",
|
|
4201
|
+
children: t("debugSectionLive")
|
|
4202
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
|
|
4203
|
+
className: DebugPanel_module_css_default.metrics,
|
|
4204
|
+
children: [
|
|
4205
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4206
|
+
label: t("debugFps"),
|
|
4207
|
+
value: fixed(metrics.fps, 0),
|
|
4208
|
+
tone: (metrics.fps ?? 60) < 45 ? "warn" : "good"
|
|
4209
|
+
}),
|
|
4210
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4211
|
+
label: t("debugFrameTime"),
|
|
4212
|
+
value: `${fixed(metrics.frameMs)} ms`
|
|
4213
|
+
}),
|
|
4214
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4215
|
+
label: t("debugBacklog"),
|
|
4216
|
+
value: String(metrics.streamBacklog),
|
|
4217
|
+
tone: metrics.streamBacklog > 32 ? "warn" : void 0
|
|
4218
|
+
}),
|
|
4219
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4220
|
+
label: t("debugRevealSpeed"),
|
|
4221
|
+
value: `${fixed(metrics.streamSpeedCps, 0)} cps`
|
|
4222
|
+
}),
|
|
4223
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4224
|
+
label: t("debugProgress"),
|
|
4225
|
+
value: progress
|
|
4226
|
+
}),
|
|
4227
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4228
|
+
label: t("debugFollowState"),
|
|
4229
|
+
value: t(metrics.followFollowing ? "debugFollowing" : "debugReleased")
|
|
4230
|
+
}),
|
|
4231
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4232
|
+
label: t("debugLag"),
|
|
4233
|
+
value: `${fixed(metrics.followLagPx)} px`,
|
|
4234
|
+
tone: metrics.followConstrained ? "warn" : void 0
|
|
4235
|
+
}),
|
|
4236
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4237
|
+
label: t("debugVelocity"),
|
|
4238
|
+
value: `${fixed(metrics.followVelocityPxPerSec, 0)} px/s`
|
|
4239
|
+
}),
|
|
4240
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4241
|
+
label: t("debugReserve"),
|
|
4242
|
+
value: `${fixed(metrics.followReservePx)} px`
|
|
4243
|
+
}),
|
|
4244
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4245
|
+
label: t("debugCapacity"),
|
|
4246
|
+
value: `${fixed(metrics.followCapacityPx)} px`
|
|
4247
|
+
}),
|
|
4248
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4249
|
+
label: t("debugAppliedScale"),
|
|
4250
|
+
value: `${fixed(metrics.followRevealScale, 2)}x`
|
|
4251
|
+
})
|
|
4252
|
+
]
|
|
4253
|
+
})]
|
|
4254
|
+
}),
|
|
4255
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4256
|
+
className: DebugPanel_module_css_default.section,
|
|
4257
|
+
"aria-labelledby": "smooth-stream-reveal-heading",
|
|
4258
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
4259
|
+
id: "smooth-stream-reveal-heading",
|
|
4260
|
+
children: t("debugSectionReveal")
|
|
4261
|
+
}), REVEAL_CONTROLS.map((control) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TuningField, {
|
|
4262
|
+
control,
|
|
4263
|
+
state,
|
|
4264
|
+
edit: props.edit,
|
|
4265
|
+
label: t(control.label),
|
|
4266
|
+
t
|
|
4267
|
+
}, control.key))]
|
|
4268
|
+
}),
|
|
4269
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4270
|
+
className: DebugPanel_module_css_default.section,
|
|
4271
|
+
"aria-labelledby": "smooth-stream-follow-heading",
|
|
4272
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
4273
|
+
id: "smooth-stream-follow-heading",
|
|
4274
|
+
children: t("debugSectionFollow")
|
|
4275
|
+
}), FOLLOW_CONTROLS.map((control) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TuningField, {
|
|
4276
|
+
control,
|
|
4277
|
+
state,
|
|
4278
|
+
edit: props.edit,
|
|
4279
|
+
label: t(control.label),
|
|
4280
|
+
t
|
|
4281
|
+
}, control.key))]
|
|
4282
|
+
})
|
|
4283
|
+
]
|
|
4284
|
+
}),
|
|
4285
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
|
|
4286
|
+
className: DebugPanel_module_css_default.footer,
|
|
4287
|
+
children: [
|
|
4288
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4289
|
+
className: DebugPanel_module_css_default.secondaryButton,
|
|
4290
|
+
type: "button",
|
|
4291
|
+
disabled: !state.writable,
|
|
4292
|
+
onClick: props.reset,
|
|
4293
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, {}), t("debugReset")]
|
|
4294
|
+
}),
|
|
4295
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: DebugPanel_module_css_default.footerSpacer }),
|
|
4296
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4297
|
+
className: DebugPanel_module_css_default.secondaryButton,
|
|
4298
|
+
type: "button",
|
|
4299
|
+
disabled: !state.writable || !state.dirty,
|
|
4300
|
+
onClick: props.discard,
|
|
4301
|
+
children: t("debugDiscard")
|
|
4302
|
+
}),
|
|
4303
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4304
|
+
className: DebugPanel_module_css_default.primaryButton,
|
|
4305
|
+
type: "button",
|
|
4306
|
+
disabled: !state.writable || !state.dirty,
|
|
4307
|
+
onClick: props.save,
|
|
4308
|
+
children: t("debugSave")
|
|
4309
|
+
})
|
|
4310
|
+
]
|
|
4311
|
+
})
|
|
4312
|
+
]
|
|
4313
|
+
});
|
|
4314
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4315
|
+
type: "button",
|
|
4316
|
+
className: state.enabled && open ? `${DebugPanel_module_css_default.trigger} ${DebugPanel_module_css_default.triggerActive}` : DebugPanel_module_css_default.trigger,
|
|
4317
|
+
"aria-expanded": state.enabled && open,
|
|
4318
|
+
"aria-label": t("debugPanelToggle"),
|
|
4319
|
+
title: t("debugPanelToggle"),
|
|
4320
|
+
onClick: () => {
|
|
4321
|
+
setOpen((current) => !current);
|
|
4322
|
+
},
|
|
4323
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCodeOutline16, {})
|
|
4324
|
+
}), typeof document === "undefined" || panel === null ? null : (0, react_dom.createPortal)(panel, document.body)] });
|
|
4325
|
+
}
|
|
4326
|
+
//#endregion
|
|
2519
4327
|
//#region src/client/locales.ts
|
|
2520
4328
|
/** Locale bundles for the smooth-stream plugin configuration card. */
|
|
2521
4329
|
/** Dictionary namespace owned by this plugin's settings card. */
|
|
@@ -2524,8 +4332,61 @@ window.__ModuleLoader__.load({
|
|
|
2524
4332
|
const en = {
|
|
2525
4333
|
title: "Smooth stream",
|
|
2526
4334
|
description: "How replies are revealed while they stream.",
|
|
4335
|
+
enabled: "Enable smooth streaming",
|
|
4336
|
+
enabledHint: "Let this plugin render and follow streaming replies. Turn off to use the built-in Harness renderer.",
|
|
2527
4337
|
thinkAutoExpand: "Auto-expand thinking",
|
|
2528
4338
|
thinkAutoExpandHint: "Open the thinking block while it streams. Turn off to keep it collapsed.",
|
|
4339
|
+
autoCollapse: "Collapse finished work",
|
|
4340
|
+
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.",
|
|
4341
|
+
debugEnabled: "Show render diagnostics",
|
|
4342
|
+
debugEnabledHint: "Show live streaming and scroll metrics on the right side of the chat. Tune values there, then save them here.",
|
|
4343
|
+
debugUnavailable: "Live diagnostics require a newer plugin Host.",
|
|
4344
|
+
debugPanelTitle: "Render diagnostics",
|
|
4345
|
+
debugPanelToggle: "Toggle render diagnostics",
|
|
4346
|
+
debugPanelClose: "Hide diagnostics panel",
|
|
4347
|
+
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.",
|
|
4348
|
+
debugLive: "Streaming",
|
|
4349
|
+
debugIdle: "Idle",
|
|
4350
|
+
debugUnsaved: "Unsaved tuning",
|
|
4351
|
+
debugSave: "Save tuning",
|
|
4352
|
+
debugDiscard: "Discard changes",
|
|
4353
|
+
debugReset: "Reset tuning",
|
|
4354
|
+
debugCopy: "Copy diagnostics",
|
|
4355
|
+
debugCopied: "Copied",
|
|
4356
|
+
debugSectionLive: "Live renderer",
|
|
4357
|
+
debugSectionReveal: "Reveal tuning",
|
|
4358
|
+
debugSectionFollow: "Scroll tuning",
|
|
4359
|
+
debugFps: "FPS",
|
|
4360
|
+
debugFrameTime: "Frame",
|
|
4361
|
+
debugBacklog: "Backlog",
|
|
4362
|
+
debugRevealSpeed: "Reveal",
|
|
4363
|
+
debugProgress: "Progress",
|
|
4364
|
+
debugFollowState: "Follow",
|
|
4365
|
+
debugFollowing: "Pinned",
|
|
4366
|
+
debugReleased: "Released",
|
|
4367
|
+
debugLag: "Visual lag",
|
|
4368
|
+
debugVelocity: "Velocity",
|
|
4369
|
+
debugReserve: "Reserve",
|
|
4370
|
+
debugCapacity: "Capacity",
|
|
4371
|
+
debugAppliedScale: "Applied scale",
|
|
4372
|
+
debugRevealMultiplier: "Reveal multiplier",
|
|
4373
|
+
debugQueuePressure: "Queue pressure",
|
|
4374
|
+
debugMaxReveal: "Maximum reveal",
|
|
4375
|
+
debugSpringStiffness: "Spring stiffness",
|
|
4376
|
+
debugSpringDamping: "Spring damping",
|
|
4377
|
+
debugSpringMass: "Spring mass",
|
|
4378
|
+
debugRunway: "Predictive runway",
|
|
4379
|
+
debugReserveResponse: "Runway response",
|
|
4380
|
+
debugBackpressureMin: "Minimum backpressure",
|
|
4381
|
+
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.",
|
|
4382
|
+
debugTipQueuePressure: "Backlog acceleration strength. Higher catches up to a growing queue more aggressively; lower keeps speed steadier but may leave backlog.",
|
|
4383
|
+
debugTipMaxReveal: "Hard cap for reveal speed in characters per second. Higher allows faster catch-up; lower limits bursts and keeps motion calmer.",
|
|
4384
|
+
debugTipSpringStiffness: "Scroll spring strength. Higher closes visual lag faster but can feel sharp; lower feels softer but follows more slowly.",
|
|
4385
|
+
debugTipSpringDamping: "Scroll energy damping. Higher suppresses overshoot and jitter but feels heavier; lower feels livelier but may oscillate.",
|
|
4386
|
+
debugTipSpringMass: "Scroll inertia. Higher makes movement slower and heavier; lower makes it react faster but can feel abrupt.",
|
|
4387
|
+
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.",
|
|
4388
|
+
debugTipReserveResponse: "How quickly the reserved runway opens or closes. Higher changes more gradually; lower reacts faster but can look abrupt.",
|
|
4389
|
+
debugTipBackpressureMin: "Slowest reveal multiplier under scroll pressure. Higher keeps text moving but may increase visual lag; lower slows text more to protect smooth following.",
|
|
2529
4390
|
readOnly: "This deployment stores settings read-only.",
|
|
2530
4391
|
loading: "Loading plugin settings…",
|
|
2531
4392
|
unavailable: "Plugin settings are unavailable in this connection.",
|
|
@@ -2550,8 +4411,61 @@ window.__ModuleLoader__.load({
|
|
|
2550
4411
|
const zh = {
|
|
2551
4412
|
title: "丝滑流式",
|
|
2552
4413
|
description: "回复在流式输出时如何逐字展现。",
|
|
4414
|
+
enabled: "启用丝滑流式渲染",
|
|
4415
|
+
enabledHint: "由本插件渲染并跟随流式回复;关闭后使用 Harness 内置渲染。",
|
|
2553
4416
|
thinkAutoExpand: "自动展开思考",
|
|
2554
4417
|
thinkAutoExpandHint: "思考块在流式时自动展开;关闭后保持折叠,可手动展开。",
|
|
4418
|
+
autoCollapse: "完成后自动折叠",
|
|
4419
|
+
autoCollapseHint: "回复处理完成后,把思考、工具与过程输出折叠为一行“已处理”摘要,只展示最终回复;点击摘要可再次展开。开启或关闭流式渲染均可用,默认开启。",
|
|
4420
|
+
debugEnabled: "显示渲染调试面板",
|
|
4421
|
+
debugEnabledHint: "在聊天右侧显示流式渲染和滚动的实时参数,可在面板中调节并在这里保存。",
|
|
4422
|
+
debugUnavailable: "当前 Host 版本不支持实时调试,请先更新插件 Host。",
|
|
4423
|
+
debugPanelTitle: "渲染诊断",
|
|
4424
|
+
debugPanelToggle: "显示或隐藏渲染诊断",
|
|
4425
|
+
debugPanelClose: "收起诊断面板",
|
|
4426
|
+
debugGuide: "流式输出时一次只调一个参数,观察帧率、积压和视觉滞后。优先保持帧率稳定、积压接近 0;如果文字滞后,提高揭示倍率、队列压力或最大揭示速度;如果底部出现空白,降低“预测预留空间”。滚动有回弹或抖动时提高阻尼。确认表现稳定后再保存;“恢复默认参数”会回到生产默认值。",
|
|
4427
|
+
debugLive: "正在流式输出",
|
|
4428
|
+
debugIdle: "空闲",
|
|
4429
|
+
debugUnsaved: "参数尚未保存",
|
|
4430
|
+
debugSave: "保存参数",
|
|
4431
|
+
debugDiscard: "放弃修改",
|
|
4432
|
+
debugReset: "恢复默认参数",
|
|
4433
|
+
debugCopy: "复制诊断数据",
|
|
4434
|
+
debugCopied: "已复制",
|
|
4435
|
+
debugSectionLive: "实时渲染",
|
|
4436
|
+
debugSectionReveal: "流式参数",
|
|
4437
|
+
debugSectionFollow: "滚动参数",
|
|
4438
|
+
debugFps: "帧率",
|
|
4439
|
+
debugFrameTime: "帧耗时",
|
|
4440
|
+
debugBacklog: "积压字符",
|
|
4441
|
+
debugRevealSpeed: "揭示速度",
|
|
4442
|
+
debugProgress: "渲染进度",
|
|
4443
|
+
debugFollowState: "跟随状态",
|
|
4444
|
+
debugFollowing: "跟随底部",
|
|
4445
|
+
debugReleased: "用户已释放",
|
|
4446
|
+
debugLag: "视觉滞后",
|
|
4447
|
+
debugVelocity: "滚动速度",
|
|
4448
|
+
debugReserve: "预留空间",
|
|
4449
|
+
debugCapacity: "安全容量",
|
|
4450
|
+
debugAppliedScale: "实际倍率",
|
|
4451
|
+
debugRevealMultiplier: "揭示倍率",
|
|
4452
|
+
debugQueuePressure: "队列压力",
|
|
4453
|
+
debugMaxReveal: "最大揭示速度",
|
|
4454
|
+
debugSpringStiffness: "弹簧刚度",
|
|
4455
|
+
debugSpringDamping: "弹簧阻尼",
|
|
4456
|
+
debugSpringMass: "弹簧质量",
|
|
4457
|
+
debugRunway: "预测预留空间",
|
|
4458
|
+
debugReserveResponse: "预留响应时间",
|
|
4459
|
+
debugBackpressureMin: "最小背压倍率",
|
|
4460
|
+
debugTipRevealMultiplier: "整体文字揭示速度倍率。调大能更快清空积压,但可能显得跳;调小更平滑,但完成回复需要更久。",
|
|
4461
|
+
debugTipQueuePressure: "积压对加速的影响强度。调大能更积极追赶增长中的队列;调小速度更稳定,但积压可能持续。",
|
|
4462
|
+
debugTipMaxReveal: "每秒揭示字符数上限。调大允许更快追赶;调小限制突发速度,让动作更平稳。",
|
|
4463
|
+
debugTipSpringStiffness: "滚动弹簧刚度。调大更快收拢视觉滞后,但感觉更硬;调小更柔和,但跟随更慢。",
|
|
4464
|
+
debugTipSpringDamping: "滚动能量阻尼。调大能抑制过冲和抖动,但感觉更沉;调小更灵活,但可能回弹。",
|
|
4465
|
+
debugTipSpringMass: "滚动惯性。调大移动更慢更沉;调小反应更快,但可能显得突兀。",
|
|
4466
|
+
debugTipRunway: "内容增长时预留的预测空白。调大更能吸收增长、保护底部跟随,但可能产生更大的可见空区;调小能减少空白,但可吸收滞后的空间也更少。",
|
|
4467
|
+
debugTipReserveResponse: "预留空间打开或关闭的响应时间。调大变化更渐进;调小反应更快,但可能显得突变。",
|
|
4468
|
+
debugTipBackpressureMin: "滚动压力下允许的最低揭示倍率。调大文字仍会较快前进,但视觉滞后可能增加;调小会更积极减速,以保护跟随平滑。",
|
|
2555
4469
|
readOnly: "本部署的设置为只读。",
|
|
2556
4470
|
loading: "正在加载插件设置…",
|
|
2557
4471
|
unavailable: "当前连接无法访问插件设置。",
|
|
@@ -2664,15 +4578,15 @@ window.__ModuleLoader__.load({
|
|
|
2664
4578
|
};
|
|
2665
4579
|
}
|
|
2666
4580
|
/**
|
|
2667
|
-
* A live
|
|
2668
|
-
* shared
|
|
2669
|
-
*
|
|
2670
|
-
* ships no settings surface keeps the cell on the default.
|
|
4581
|
+
* A live settings cell shared by the renderer lifecycle and React views. It
|
|
4582
|
+
* starts on the shared defaults and follows the plugin-owned controller once
|
|
4583
|
+
* the optional settings services arrive.
|
|
2671
4584
|
*/
|
|
2672
|
-
var
|
|
4585
|
+
var SettingsCell = class {
|
|
2673
4586
|
listeners = /* @__PURE__ */ new Set();
|
|
2674
4587
|
card;
|
|
2675
|
-
value = DEFAULT_STREAM_SETTINGS
|
|
4588
|
+
value = DEFAULT_STREAM_SETTINGS;
|
|
4589
|
+
pending = false;
|
|
2676
4590
|
/** Re-point the cell at the plugin-owned settings controller. */
|
|
2677
4591
|
attach(card) {
|
|
2678
4592
|
this.card = card;
|
|
@@ -2688,14 +4602,26 @@ window.__ModuleLoader__.load({
|
|
|
2688
4602
|
};
|
|
2689
4603
|
}
|
|
2690
4604
|
read() {
|
|
2691
|
-
|
|
4605
|
+
const snapshot = this.card?.getSnapshot();
|
|
4606
|
+
if (snapshot === void 0 || snapshot.status !== "ready") return this.value;
|
|
4607
|
+
return this.card?.values() ?? this.value;
|
|
2692
4608
|
}
|
|
2693
4609
|
refresh() {
|
|
2694
4610
|
const next = this.read();
|
|
2695
|
-
|
|
4611
|
+
const pending = this.card?.getSnapshot().status === "loading";
|
|
4612
|
+
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;
|
|
4613
|
+
this.pending = pending;
|
|
2696
4614
|
this.value = next;
|
|
2697
4615
|
for (const listener of this.listeners) listener();
|
|
2698
4616
|
}
|
|
4617
|
+
/** False while an available settings service is resolving its authority. */
|
|
4618
|
+
takeoverEnabled() {
|
|
4619
|
+
return !this.pending && this.value.enabled;
|
|
4620
|
+
}
|
|
4621
|
+
/** Whether finished turns should fold behind a summary row right now. */
|
|
4622
|
+
autoCollapseActive() {
|
|
4623
|
+
return !this.pending && this.value.autoCollapse;
|
|
4624
|
+
}
|
|
2699
4625
|
getSnapshot = () => this.value;
|
|
2700
4626
|
subscribe = (listener) => {
|
|
2701
4627
|
this.listeners.add(listener);
|
|
@@ -2717,14 +4643,51 @@ window.__ModuleLoader__.load({
|
|
|
2717
4643
|
*/
|
|
2718
4644
|
function apply(ctx) {
|
|
2719
4645
|
const config = readBootConfig();
|
|
2720
|
-
const
|
|
4646
|
+
const settings = new SettingsCell();
|
|
4647
|
+
ctx.inject(["slots"], () => {
|
|
4648
|
+
const autoCollapse = new AutoCollapseController();
|
|
4649
|
+
const syncAutoCollapse = () => {
|
|
4650
|
+
if (settings.autoCollapseActive()) autoCollapse.start();
|
|
4651
|
+
else autoCollapse.stop();
|
|
4652
|
+
};
|
|
4653
|
+
const unsubscribe = settings.subscribe(syncAutoCollapse);
|
|
4654
|
+
syncAutoCollapse();
|
|
4655
|
+
return () => {
|
|
4656
|
+
unsubscribe();
|
|
4657
|
+
autoCollapse.stop();
|
|
4658
|
+
};
|
|
4659
|
+
});
|
|
2721
4660
|
ctx.inject([
|
|
2722
4661
|
"slots",
|
|
2723
4662
|
"locale",
|
|
2724
4663
|
"connection"
|
|
2725
4664
|
], (settingsCtx) => {
|
|
2726
4665
|
const card = new SmoothStreamCardController(createSmoothStreamSettingsApi(settingsCtx.get("connection")));
|
|
2727
|
-
const
|
|
4666
|
+
const detachSettings = settings.attach(card);
|
|
4667
|
+
const syncDebug = () => {
|
|
4668
|
+
const snapshot = card.getSnapshot();
|
|
4669
|
+
debugRuntime.syncSettings({
|
|
4670
|
+
available: snapshot.debugAvailable,
|
|
4671
|
+
enabled: snapshot.debugEnabled,
|
|
4672
|
+
writable: snapshot.writable && !snapshot.saving,
|
|
4673
|
+
dirty: snapshot.dirty,
|
|
4674
|
+
status: snapshot.status,
|
|
4675
|
+
tuning: snapshot.debugTuning
|
|
4676
|
+
});
|
|
4677
|
+
};
|
|
4678
|
+
const detachBinding = debugRuntime.bindSettings({
|
|
4679
|
+
edit: (patch) => {
|
|
4680
|
+
card.inject().edit(patch);
|
|
4681
|
+
},
|
|
4682
|
+
save: () => {
|
|
4683
|
+
card.inject().save();
|
|
4684
|
+
},
|
|
4685
|
+
discard: () => {
|
|
4686
|
+
card.inject().discard();
|
|
4687
|
+
}
|
|
4688
|
+
});
|
|
4689
|
+
const detachDebug = card.subscribe(syncDebug);
|
|
4690
|
+
syncDebug();
|
|
2728
4691
|
card.start();
|
|
2729
4692
|
settingsCtx.effect(() => settingsCtx.locale.register(NS, {
|
|
2730
4693
|
zh,
|
|
@@ -2733,17 +4696,27 @@ window.__ModuleLoader__.load({
|
|
|
2733
4696
|
settingsCtx.slots.inject("settings.plugin.item", () => settingsCtx.slots.register({
|
|
2734
4697
|
name: "settings.plugin.item",
|
|
2735
4698
|
id: "smooth-stream",
|
|
4699
|
+
key: STREAM_SETTINGS_NS,
|
|
2736
4700
|
order: 30,
|
|
2737
4701
|
locale: NS,
|
|
2738
4702
|
inject: () => card.inject()
|
|
2739
4703
|
}, SmoothStreamCard));
|
|
4704
|
+
settingsCtx.slots.inject("conversation.session.header.utilities", () => settingsCtx.slots.register({
|
|
4705
|
+
name: "conversation.session.header.utilities",
|
|
4706
|
+
id: "smooth-stream-debug",
|
|
4707
|
+
order: 40,
|
|
4708
|
+
locale: NS,
|
|
4709
|
+
inject: () => debugRuntime.panelFace()
|
|
4710
|
+
}, DebugPanel));
|
|
2740
4711
|
return () => {
|
|
2741
4712
|
card.stop();
|
|
2742
|
-
|
|
4713
|
+
detachDebug();
|
|
4714
|
+
detachSettings();
|
|
4715
|
+
detachBinding();
|
|
2743
4716
|
};
|
|
2744
4717
|
});
|
|
2745
4718
|
const configured = function StreamConfiguredView(props) {
|
|
2746
|
-
const
|
|
4719
|
+
const preferences = (0, react.useSyncExternalStore)(settings.subscribe, settings.getSnapshot, settings.getSnapshot);
|
|
2747
4720
|
return (0, react.createElement)(TypewriterAssistantNodeView, {
|
|
2748
4721
|
...props,
|
|
2749
4722
|
mode: config.mode,
|
|
@@ -2751,21 +4724,36 @@ window.__ModuleLoader__.load({
|
|
|
2751
4724
|
revealCharsPerSec: config.revealCharsPerSec,
|
|
2752
4725
|
scrollSpeedPxPerSec: config.scrollSpeedPxPerSec,
|
|
2753
4726
|
maxScrollSpeedPxPerSec: config.maxScrollSpeedPxPerSec,
|
|
2754
|
-
thinkAutoExpand
|
|
4727
|
+
thinkAutoExpand: preferences.thinkAutoExpand
|
|
2755
4728
|
});
|
|
2756
4729
|
};
|
|
2757
4730
|
ctx.slots.inject("conversation.chat.node", () => {
|
|
2758
|
-
|
|
2759
|
-
const
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
4731
|
+
let releaseTakeover;
|
|
4732
|
+
const syncTakeover = () => {
|
|
4733
|
+
if (!settings.takeoverEnabled()) {
|
|
4734
|
+
releaseTakeover?.();
|
|
4735
|
+
releaseTakeover = void 0;
|
|
4736
|
+
return;
|
|
4737
|
+
}
|
|
4738
|
+
if (releaseTakeover !== void 0) return;
|
|
4739
|
+
const unwrap = wrapAgentChatRows(ctx);
|
|
4740
|
+
const unshadow = ctx.slots.register({
|
|
4741
|
+
name: "conversation.chat.node",
|
|
4742
|
+
key: "assistant-step",
|
|
4743
|
+
priority: -100,
|
|
4744
|
+
locale: "conversation",
|
|
4745
|
+
registrant: "dsh-smooth-stream"
|
|
4746
|
+
}, configured);
|
|
4747
|
+
releaseTakeover = () => {
|
|
4748
|
+
unwrap();
|
|
4749
|
+
unshadow();
|
|
4750
|
+
};
|
|
4751
|
+
};
|
|
4752
|
+
const unsubscribe = settings.subscribe(syncTakeover);
|
|
4753
|
+
syncTakeover();
|
|
2766
4754
|
return () => {
|
|
2767
|
-
|
|
2768
|
-
|
|
4755
|
+
unsubscribe();
|
|
4756
|
+
releaseTakeover?.();
|
|
2769
4757
|
};
|
|
2770
4758
|
});
|
|
2771
4759
|
}
|