dsh-smooth-stream 0.1.0 → 0.3.2
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 +103 -0
- package/README.md +62 -47
- package/lib/client.js +608 -75
- package/lib/client.js.map +1 -1
- package/lib/index.js +229 -0
- package/package.json +25 -3
- package/src/client/SmoothStreamCard.module.css +253 -0
- package/src/client/SmoothStreamCard.tsx +122 -0
- package/src/client/TypewriterAssistantNodeView.module.css +2 -2
- package/src/client/TypewriterAssistantNodeView.tsx +26 -11
- package/src/client/index.ts +96 -3
- package/src/client/locales.ts +74 -0
- package/src/client/smooth-stream-card-controller.ts +162 -0
- package/src/client/smooth-stream-settings-api.ts +65 -0
- package/src/client/teleprompterGlide.ts +71 -41
- package/src/package-meta.ts +39 -0
- package/src/plugin.ts +101 -0
- package/src/profile-installation.ts +121 -0
- package/src/settings-api.ts +33 -0
- package/src/settings.ts +30 -0
- package/README.zh.md +0 -88
package/lib/client.js
CHANGED
|
@@ -8,41 +8,42 @@ window.__ModuleLoader__.load({
|
|
|
8
8
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
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
|
+
let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
11
12
|
//#region \0dsh-css:/Users/dzlin/work/project/dsh-smooth-stream/src/client/TypewriterAssistantNodeView.module.css.mjs
|
|
12
|
-
const css = ".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{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}}";
|
|
13
|
-
const tagId = "dsh-smooth-stream/TypewriterAssistantNodeView.module.css";
|
|
13
|
+
const css$1 = ".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{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}}";
|
|
14
|
+
const tagId$1 = "dsh-smooth-stream/TypewriterAssistantNodeView.module.css";
|
|
14
15
|
if (typeof document !== "undefined") {
|
|
15
|
-
let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]");
|
|
16
|
+
let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]");
|
|
16
17
|
if (tag === null) {
|
|
17
18
|
tag = document.createElement("style");
|
|
18
19
|
tag.dataset.plugin = "dsh-smooth-stream";
|
|
19
|
-
tag.dataset.pluginCss = tagId;
|
|
20
|
+
tag.dataset.pluginCss = tagId$1;
|
|
20
21
|
document.head.appendChild(tag);
|
|
21
22
|
}
|
|
22
|
-
tag.textContent = css;
|
|
23
|
+
tag.textContent = css$1;
|
|
23
24
|
}
|
|
24
25
|
var TypewriterAssistantNodeView_module_css_default = {
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"thinkSeparator": "I17U7q_thinkSeparator",
|
|
28
|
-
"disclosureTitle": "I17U7q_disclosureTitle",
|
|
29
|
-
"thinkSummary": "I17U7q_thinkSummary",
|
|
30
|
-
"dsh-smooth-stream-think-sweep": "I17U7q_dsh-smooth-stream-think-sweep",
|
|
31
|
-
"disclosureRoot": "I17U7q_disclosureRoot",
|
|
26
|
+
"body": "I17U7q_body",
|
|
27
|
+
"disclosureRow": "I17U7q_disclosureRow",
|
|
32
28
|
"disclosureIconIdle": "I17U7q_disclosureIconIdle",
|
|
33
|
-
"
|
|
34
|
-
"
|
|
29
|
+
"stopped": "I17U7q_stopped",
|
|
30
|
+
"root": "I17U7q_root",
|
|
31
|
+
"disclosureRoot": "I17U7q_disclosureRoot",
|
|
35
32
|
"thinkRow": "I17U7q_thinkRow",
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
33
|
+
"thinkLeading": "I17U7q_thinkLeading",
|
|
34
|
+
"follow": "I17U7q_follow",
|
|
35
|
+
"disclosureTitle": "I17U7q_disclosureTitle",
|
|
36
|
+
"visuallyHidden": "I17U7q_visuallyHidden",
|
|
37
|
+
"thinkSeparator": "I17U7q_thinkSeparator",
|
|
39
38
|
"thinkTitle": "I17U7q_thinkTitle",
|
|
40
|
-
"
|
|
39
|
+
"disclosureContent": "I17U7q_disclosureContent",
|
|
40
|
+
"think": "I17U7q_think",
|
|
41
41
|
"thinkBody": "I17U7q_thinkBody",
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
42
|
+
"disclosureChevronHover": "I17U7q_disclosureChevronHover",
|
|
43
|
+
"thinkChevron": "I17U7q_thinkChevron",
|
|
44
|
+
"dsh-smooth-stream-think-sweep": "I17U7q_dsh-smooth-stream-think-sweep",
|
|
45
|
+
"disclosureLeading": "I17U7q_disclosureLeading",
|
|
46
|
+
"thinkSummary": "I17U7q_thinkSummary"
|
|
46
47
|
};
|
|
47
48
|
//#endregion
|
|
48
49
|
//#region src/client/AnimatedDisclosure.tsx
|
|
@@ -505,17 +506,15 @@ window.__ModuleLoader__.load({
|
|
|
505
506
|
* - while the port has scroll room, writes the interpolated lag as
|
|
506
507
|
* `translate3d` on `[data-chat-transcript]` (message rows only), so the
|
|
507
508
|
* turn-status chrome stays pinned at the floor;
|
|
508
|
-
* -
|
|
509
|
-
*
|
|
510
|
-
*
|
|
511
|
-
*
|
|
509
|
+
* - clips only the part of the translated surface that crosses the
|
|
510
|
+
* turn-status line, preserving the normal gap above that line;
|
|
511
|
+
* - leaves `[data-chat-turn-status]` in normal flow at every height, so its
|
|
512
|
+
* layout gap cannot be consumed by an interpolated transform.
|
|
512
513
|
*
|
|
513
|
-
*
|
|
514
|
-
*
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
* `FOLLOW_THRESHOLD`, releasing its native snap-follow instead of fighting
|
|
518
|
-
* the gesture for the port.
|
|
514
|
+
* A real reader unpin first writes the effective visual top (`engine - lag`)
|
|
515
|
+
* into `scrollTop` before clearing the transform, so the frame stays
|
|
516
|
+
* continuous. Lifecycle completion is different: without a reader gesture,
|
|
517
|
+
* the last owner settles at the floor and preserves bottom-follow.
|
|
519
518
|
*
|
|
520
519
|
* Unpin is a real gesture (wheel / touch / pointer / key) that leaves the
|
|
521
520
|
* floor; a `scrollTop` delta from our own write must not release the pin.
|
|
@@ -586,33 +585,48 @@ window.__ModuleLoader__.load({
|
|
|
586
585
|
function hasShiftSurface(port) {
|
|
587
586
|
return shiftRootOf(port) !== null || shiftRowsOf(port).length > 0;
|
|
588
587
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
588
|
+
/** Cached natural gap from a shifted surface's bottom to the status line. */
|
|
589
|
+
const statusGapCache = /* @__PURE__ */ new WeakMap();
|
|
590
|
+
function currentShiftOf(element) {
|
|
591
|
+
return Number(/translate3d\(0, ([\d.]+)px, 0\)/.exec(element.style.transform)?.[1] ?? 0);
|
|
592
|
+
}
|
|
593
|
+
function statusGapBelow(surface, chrome) {
|
|
594
|
+
const cached = statusGapCache.get(surface);
|
|
595
|
+
if (cached?.chrome === chrome) return cached.px;
|
|
596
|
+
const naturalBottom = surface.getBoundingClientRect().bottom - currentShiftOf(surface);
|
|
597
|
+
const px = Math.max(0, chrome.getBoundingClientRect().top - naturalBottom);
|
|
598
|
+
statusGapCache.set(surface, {
|
|
599
|
+
chrome,
|
|
600
|
+
px
|
|
601
|
+
});
|
|
602
|
+
return px;
|
|
603
|
+
}
|
|
604
|
+
function setShift(element, px, paintAllowance) {
|
|
605
|
+
if (Math.abs(px) > .01) {
|
|
606
|
+
element.style.transform = `translate3d(0, ${px}px, 0)`;
|
|
593
607
|
element.style.willChange = "transform";
|
|
608
|
+
const bottomClip = paintAllowance === void 0 ? 0 : Math.max(0, px - paintAllowance);
|
|
609
|
+
element.style.clipPath = bottomClip > .01 ? `inset(0 0 ${bottomClip}px 0)` : "";
|
|
594
610
|
} else {
|
|
595
611
|
element.style.transform = "";
|
|
596
612
|
element.style.willChange = "";
|
|
613
|
+
element.style.clipPath = "";
|
|
597
614
|
}
|
|
598
615
|
}
|
|
599
616
|
/**
|
|
600
|
-
* The running-turn label.
|
|
601
|
-
*
|
|
602
|
-
* expressed as a negative translate on this sibling: the label descends
|
|
603
|
-
* smoothly with each wrap instead of hopping down a line at a time.
|
|
617
|
+
* The running-turn label. It stays in normal flow so the host column's gap
|
|
618
|
+
* always separates it from the transcript.
|
|
604
619
|
*/
|
|
605
|
-
function
|
|
606
|
-
return port.querySelector("[data-chat-turn-status]");
|
|
620
|
+
function statusChromeOf(port) {
|
|
621
|
+
return port.querySelector("[data-chat-turn-status], [data-chat-flow] > [role=\"status\"]");
|
|
607
622
|
}
|
|
608
623
|
/**
|
|
609
|
-
* Render the single smoothed extent `animatedH`.
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
-
*
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
* shift surface the engine itself carries the interpolated top.
|
|
624
|
+
* Render the single smoothed extent `animatedH`. Once the port has scroll
|
|
625
|
+
* room the engine is pinned at the floor and the lag rides
|
|
626
|
+
* `[data-chat-transcript]` (or each message row when the host has no
|
|
627
|
+
* transcript box). Before that, message rows and status chrome remain in
|
|
628
|
+
* normal flow. Without any shift surface the engine carries the interpolated
|
|
629
|
+
* top itself.
|
|
616
630
|
*/
|
|
617
631
|
function applyVisual(port, animatedH) {
|
|
618
632
|
const contentHeight = Math.max(0, port.scrollHeight);
|
|
@@ -621,19 +635,19 @@ window.__ModuleLoader__.load({
|
|
|
621
635
|
const lag = contentHeight - extent;
|
|
622
636
|
port.style.overflowAnchor = "none";
|
|
623
637
|
port.style.scrollBehavior = "auto";
|
|
638
|
+
const chrome = statusChromeOf(port);
|
|
639
|
+
if (chrome !== null) setShift(chrome, 0);
|
|
624
640
|
const shiftRoot = shiftRootOf(port);
|
|
625
641
|
if (shiftRoot !== null) {
|
|
626
642
|
if (floor <= 0) {
|
|
627
643
|
port.scrollTop = 0;
|
|
628
644
|
port.setAttribute(FOLLOW_OWNED_ATTR, String(port.scrollTop));
|
|
629
|
-
const chrome = chromeShiftOf(port);
|
|
630
|
-
if (chrome !== null) setShift(chrome, lag, true);
|
|
631
645
|
setShift(shiftRoot, 0);
|
|
632
646
|
return;
|
|
633
647
|
}
|
|
634
648
|
port.scrollTop = floor;
|
|
635
649
|
port.setAttribute(FOLLOW_OWNED_ATTR, String(port.scrollTop));
|
|
636
|
-
setShift(shiftRoot, lag);
|
|
650
|
+
setShift(shiftRoot, lag, chrome === null ? void 0 : statusGapBelow(shiftRoot, chrome));
|
|
637
651
|
return;
|
|
638
652
|
}
|
|
639
653
|
const rows = shiftRowsOf(port);
|
|
@@ -641,14 +655,13 @@ window.__ModuleLoader__.load({
|
|
|
641
655
|
if (floor <= 0) {
|
|
642
656
|
port.scrollTop = 0;
|
|
643
657
|
port.setAttribute(FOLLOW_OWNED_ATTR, String(port.scrollTop));
|
|
644
|
-
const chrome = chromeShiftOf(port);
|
|
645
|
-
if (chrome !== null) setShift(chrome, lag, true);
|
|
646
658
|
for (const row of rows) setShift(row, 0);
|
|
647
659
|
return;
|
|
648
660
|
}
|
|
649
661
|
port.scrollTop = floor;
|
|
650
662
|
port.setAttribute(FOLLOW_OWNED_ATTR, String(port.scrollTop));
|
|
651
|
-
|
|
663
|
+
const last = rows.length - 1;
|
|
664
|
+
for (const [index, row] of rows.entries()) setShift(row, lag, index === last && chrome !== null ? statusGapBelow(row, chrome) : void 0);
|
|
652
665
|
return;
|
|
653
666
|
}
|
|
654
667
|
port.scrollTop = Math.min(floor, Math.max(0, extent));
|
|
@@ -660,18 +673,27 @@ window.__ModuleLoader__.load({
|
|
|
660
673
|
port.style.scrollBehavior = "";
|
|
661
674
|
const shiftRoot = shiftRootOf(port);
|
|
662
675
|
if (shiftRoot !== null) {
|
|
676
|
+
statusGapCache.delete(shiftRoot);
|
|
663
677
|
shiftRoot.style.transform = "";
|
|
664
678
|
shiftRoot.style.willChange = "";
|
|
679
|
+
shiftRoot.style.clipPath = "";
|
|
665
680
|
} else for (const row of shiftRowsOf(port)) {
|
|
681
|
+
statusGapCache.delete(row);
|
|
666
682
|
row.style.transform = "";
|
|
667
683
|
row.style.willChange = "";
|
|
684
|
+
row.style.clipPath = "";
|
|
668
685
|
}
|
|
669
|
-
const chrome =
|
|
686
|
+
const chrome = statusChromeOf(port);
|
|
670
687
|
if (chrome !== null) {
|
|
671
688
|
chrome.style.transform = "";
|
|
672
689
|
chrome.style.willChange = "";
|
|
690
|
+
chrome.style.clipPath = "";
|
|
673
691
|
}
|
|
674
692
|
}
|
|
693
|
+
function settleAtFloor(port) {
|
|
694
|
+
port.scrollTop = Math.max(0, port.scrollHeight - port.clientHeight);
|
|
695
|
+
port.setAttribute(FOLLOW_OWNED_ATTR, String(port.scrollTop));
|
|
696
|
+
}
|
|
675
697
|
/** Live follow hosts per conversation port so one unmount does not clear another. */
|
|
676
698
|
const followOwners = /* @__PURE__ */ new WeakMap();
|
|
677
699
|
function acquireFollow(port) {
|
|
@@ -821,10 +843,15 @@ window.__ModuleLoader__.load({
|
|
|
821
843
|
if (port !== null) for (const name of GESTURE_EVENTS) port.removeEventListener(name, markGesture);
|
|
822
844
|
const host = rootRef.current?.closest("[data-conversation-scroll]") ?? port;
|
|
823
845
|
if (host === null) return;
|
|
824
|
-
if (following) handBackVisual(host);
|
|
825
846
|
const remaining = holding === host ? releaseFollow(host) : followOwners.get(host) ?? 0;
|
|
826
847
|
holding = null;
|
|
827
|
-
if (remaining === 0)
|
|
848
|
+
if (remaining === 0) {
|
|
849
|
+
if (following) {
|
|
850
|
+
if (interacting && awayPx >= 8) handBackVisual(host);
|
|
851
|
+
else settleAtFloor(host);
|
|
852
|
+
}
|
|
853
|
+
clearVisual(host);
|
|
854
|
+
}
|
|
828
855
|
};
|
|
829
856
|
}, [
|
|
830
857
|
active,
|
|
@@ -864,6 +891,10 @@ window.__ModuleLoader__.load({
|
|
|
864
891
|
*/
|
|
865
892
|
const STREAM_BOOT_GLOBAL = "__DSH_SMOOTH_STREAM_CONFIG__";
|
|
866
893
|
//#endregion
|
|
894
|
+
//#region src/settings.ts
|
|
895
|
+
/** Defaults shared by the Host schema and the client-side fallback. */
|
|
896
|
+
const DEFAULT_STREAM_SETTINGS = { thinkAutoExpand: true };
|
|
897
|
+
//#endregion
|
|
867
898
|
//#region src/client/TypewriterAssistantNodeView.tsx
|
|
868
899
|
function usePrefersReducedMotion() {
|
|
869
900
|
const [reduced, setReduced] = (0, react.useState)(() => typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true);
|
|
@@ -957,30 +988,28 @@ window.__ModuleLoader__.load({
|
|
|
957
988
|
* or the assistant node settling — not when the rest of the reply is
|
|
958
989
|
* still streaming.
|
|
959
990
|
*/
|
|
960
|
-
function AnimatedReasoning({ text, running, preset, shouldHoldBack, t }) {
|
|
991
|
+
function AnimatedReasoning({ text, running, preset, thinkAutoExpand, shouldHoldBack, followSpeedCpsRef, t }) {
|
|
961
992
|
const reduced = usePrefersReducedMotion();
|
|
962
|
-
const [expanded, setExpanded] = (0, react.useState)(running);
|
|
993
|
+
const [expanded, setExpanded] = (0, react.useState)(running && thinkAutoExpand);
|
|
963
994
|
const summaryRef = (0, react.useRef)(null);
|
|
964
|
-
const speedCpsRef = (0, react.useRef)(35);
|
|
965
995
|
const displayed = useSmoothStreamContent(text, {
|
|
966
996
|
enabled: running && !reduced,
|
|
967
997
|
preset,
|
|
968
998
|
shouldHoldBack,
|
|
969
|
-
speedCpsRef
|
|
999
|
+
speedCpsRef: followSpeedCpsRef
|
|
970
1000
|
});
|
|
971
1001
|
const shown = running && !reduced ? displayed : text;
|
|
972
1002
|
const summary = running ? latestLine(shown) : firstLine(text);
|
|
973
1003
|
(0, react.useEffect)(() => {
|
|
974
|
-
setExpanded(running);
|
|
975
|
-
}, [running]);
|
|
1004
|
+
if (thinkAutoExpand) setExpanded(running);
|
|
1005
|
+
}, [running, thinkAutoExpand]);
|
|
976
1006
|
(0, react.useEffect)(() => {
|
|
977
1007
|
const element = summaryRef.current;
|
|
978
1008
|
if (element === null) return;
|
|
979
1009
|
element.scrollLeft = running ? element.scrollWidth - element.clientWidth : 0;
|
|
980
1010
|
}, [running, summary]);
|
|
981
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
982
|
-
|
|
983
|
-
speedCpsRef,
|
|
1011
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1012
|
+
className: TypewriterAssistantNodeView_module_css_default.follow,
|
|
984
1013
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
985
1014
|
className: TypewriterAssistantNodeView_module_css_default.think,
|
|
986
1015
|
"data-variant": "think",
|
|
@@ -1021,16 +1050,20 @@ window.__ModuleLoader__.load({
|
|
|
1021
1050
|
* streaming is revealed by the smoother through the Harness Markdown
|
|
1022
1051
|
* renderer at a rate that tracks arrival. Reasoning blocks keep the
|
|
1023
1052
|
* built-in Think disclosure and only receive a smoothed text feed; the
|
|
1024
|
-
*
|
|
1025
|
-
*
|
|
1026
|
-
* reveals when the frame rate is degraded. Settled text
|
|
1027
|
-
* full Markdown pipeline.
|
|
1053
|
+
* outer node owns conversation-port follow while streaming; the final text
|
|
1054
|
+
* block keeps ownership while its settled reveal queue drains. The FPS guard
|
|
1055
|
+
* holds offscreen reveals when the frame rate is degraded. Settled text
|
|
1056
|
+
* renders with the full Markdown pipeline.
|
|
1028
1057
|
*/
|
|
1029
|
-
const TypewriterAssistantNodeView = (0, react.memo)(function TypewriterAssistantNodeView({ mode: _mode = DEFAULT_STREAM_CONFIG.mode, preset = DEFAULT_STREAM_CONFIG.preset, revealCharsPerSec: _revealCharsPerSec = DEFAULT_STREAM_CONFIG.revealCharsPerSec, scrollSpeedPxPerSec: _scrollSpeedPxPerSec = DEFAULT_STREAM_CONFIG.scrollSpeedPxPerSec, maxScrollSpeedPxPerSec: _maxScrollSpeedPxPerSec = DEFAULT_STREAM_CONFIG.maxScrollSpeedPxPerSec, node, useTurnData, openFile, loadImage, fileMentions, t }) {
|
|
1058
|
+
const TypewriterAssistantNodeView = (0, react.memo)(function TypewriterAssistantNodeView({ mode: _mode = DEFAULT_STREAM_CONFIG.mode, preset = DEFAULT_STREAM_CONFIG.preset, revealCharsPerSec: _revealCharsPerSec = DEFAULT_STREAM_CONFIG.revealCharsPerSec, scrollSpeedPxPerSec: _scrollSpeedPxPerSec = DEFAULT_STREAM_CONFIG.scrollSpeedPxPerSec, maxScrollSpeedPxPerSec: _maxScrollSpeedPxPerSec = DEFAULT_STREAM_CONFIG.maxScrollSpeedPxPerSec, thinkAutoExpand = DEFAULT_STREAM_SETTINGS.thinkAutoExpand, node, useTurnData, openFile, loadImage, fileMentions, t }) {
|
|
1030
1059
|
const data = node.data;
|
|
1031
1060
|
const streaming = data.status === "running";
|
|
1032
1061
|
const { ref: guardRef, shouldHoldBack } = useFpsGuard(streaming);
|
|
1033
1062
|
const rootSpeedRef = (0, react.useRef)(35);
|
|
1063
|
+
const reasoningOwnsSpeed = streaming && data.blocks[data.blocks.length - 1]?.kind === "reasoning";
|
|
1064
|
+
(0, react.useEffect)(() => {
|
|
1065
|
+
if (!reasoningOwnsSpeed) rootSpeedRef.current = 35;
|
|
1066
|
+
}, [reasoningOwnsSpeed]);
|
|
1034
1067
|
const turn = node.location.kind === "turn" || node.location.kind === "step" ? node.location.turn : void 0;
|
|
1035
1068
|
const tail = useTurnData("turn-tail");
|
|
1036
1069
|
const owner = (0, react.useMemo)(() => {
|
|
@@ -1084,7 +1117,9 @@ window.__ModuleLoader__.load({
|
|
|
1084
1117
|
text: block.text,
|
|
1085
1118
|
running: streaming && index === last,
|
|
1086
1119
|
preset,
|
|
1120
|
+
thinkAutoExpand,
|
|
1087
1121
|
shouldHoldBack,
|
|
1122
|
+
followSpeedCpsRef: reasoningOwnsSpeed && index === last ? rootSpeedRef : void 0,
|
|
1088
1123
|
t
|
|
1089
1124
|
}, index));
|
|
1090
1125
|
break;
|
|
@@ -1172,8 +1207,436 @@ window.__ModuleLoader__.load({
|
|
|
1172
1207
|
};
|
|
1173
1208
|
}
|
|
1174
1209
|
//#endregion
|
|
1210
|
+
//#region \0dsh-css:/Users/dzlin/work/project/dsh-smooth-stream/src/client/SmoothStreamCard.module.css.mjs
|
|
1211
|
+
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}";
|
|
1212
|
+
const tagId = "dsh-smooth-stream/SmoothStreamCard.module.css";
|
|
1213
|
+
if (typeof document !== "undefined") {
|
|
1214
|
+
let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]");
|
|
1215
|
+
if (tag === null) {
|
|
1216
|
+
tag = document.createElement("style");
|
|
1217
|
+
tag.dataset.plugin = "dsh-smooth-stream";
|
|
1218
|
+
tag.dataset.pluginCss = tagId;
|
|
1219
|
+
document.head.appendChild(tag);
|
|
1220
|
+
}
|
|
1221
|
+
tag.textContent = css;
|
|
1222
|
+
}
|
|
1223
|
+
var SmoothStreamCard_module_css_default = {
|
|
1224
|
+
"name": "XNGeRG_name",
|
|
1225
|
+
"failure": "XNGeRG_failure",
|
|
1226
|
+
"save": "XNGeRG_save",
|
|
1227
|
+
"footer": "XNGeRG_footer",
|
|
1228
|
+
"update": "XNGeRG_update",
|
|
1229
|
+
"headText": "XNGeRG_headText",
|
|
1230
|
+
"fieldHead": "XNGeRG_fieldHead",
|
|
1231
|
+
"chevronOpen": "XNGeRG_chevronOpen",
|
|
1232
|
+
"cardOpen": "XNGeRG_cardOpen",
|
|
1233
|
+
"updateCopy": "XNGeRG_updateCopy",
|
|
1234
|
+
"readOnly": "XNGeRG_readOnly",
|
|
1235
|
+
"pending": "XNGeRG_pending",
|
|
1236
|
+
"body": "XNGeRG_body",
|
|
1237
|
+
"label": "XNGeRG_label",
|
|
1238
|
+
"updateRow": "XNGeRG_updateRow",
|
|
1239
|
+
"failed": "XNGeRG_failed",
|
|
1240
|
+
"toggle": "XNGeRG_toggle",
|
|
1241
|
+
"version": "XNGeRG_version",
|
|
1242
|
+
"hint": "XNGeRG_hint",
|
|
1243
|
+
"chevron": "XNGeRG_chevron",
|
|
1244
|
+
"description": "XNGeRG_description",
|
|
1245
|
+
"header": "XNGeRG_header",
|
|
1246
|
+
"discard": "XNGeRG_discard",
|
|
1247
|
+
"field": "XNGeRG_field",
|
|
1248
|
+
"card": "XNGeRG_card"
|
|
1249
|
+
};
|
|
1250
|
+
//#endregion
|
|
1251
|
+
//#region src/client/SmoothStreamCard.tsx
|
|
1252
|
+
/**
|
|
1253
|
+
* The smooth-stream plugin configuration card, rendered inside the Web
|
|
1254
|
+
* Settings "plugin configuration" page. One toggle, staged until the user
|
|
1255
|
+
* saves — the same shape as the Host-shipped cards, hand-drawn because the
|
|
1256
|
+
* Host cards' chrome is not exported for reuse.
|
|
1257
|
+
*/
|
|
1258
|
+
/** Render the smooth-stream card independently of the core settings namespace allowlist. */
|
|
1259
|
+
function SmoothStreamCard(props) {
|
|
1260
|
+
const { t } = props;
|
|
1261
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
1262
|
+
const state = props.useSmoothStreamCard((snapshot) => snapshot);
|
|
1263
|
+
const blocked = !state.dirty || state.saving || state.status !== "ready";
|
|
1264
|
+
const versionLabel = state.version === void 0 ? null : t(state.installation === "development" ? "developmentVersion" : "version").replace("{version}", state.version);
|
|
1265
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
1266
|
+
className: open ? `${SmoothStreamCard_module_css_default.card} ${SmoothStreamCard_module_css_default.cardOpen}` : SmoothStreamCard_module_css_default.card,
|
|
1267
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1268
|
+
type: "button",
|
|
1269
|
+
className: SmoothStreamCard_module_css_default.header,
|
|
1270
|
+
"aria-expanded": open,
|
|
1271
|
+
onClick: () => {
|
|
1272
|
+
setOpen(!open);
|
|
1273
|
+
},
|
|
1274
|
+
children: [
|
|
1275
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1276
|
+
className: SmoothStreamCard_module_css_default.headText,
|
|
1277
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1278
|
+
className: SmoothStreamCard_module_css_default.name,
|
|
1279
|
+
children: t("title")
|
|
1280
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1281
|
+
className: SmoothStreamCard_module_css_default.description,
|
|
1282
|
+
children: t("description")
|
|
1283
|
+
})]
|
|
1284
|
+
}),
|
|
1285
|
+
versionLabel === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1286
|
+
className: SmoothStreamCard_module_css_default.version,
|
|
1287
|
+
children: versionLabel
|
|
1288
|
+
}),
|
|
1289
|
+
state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1290
|
+
className: SmoothStreamCard_module_css_default.pending,
|
|
1291
|
+
children: t("unsaved")
|
|
1292
|
+
}) : null,
|
|
1293
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: open ? `${SmoothStreamCard_module_css_default.chevron} ${SmoothStreamCard_module_css_default.chevronOpen}` : SmoothStreamCard_module_css_default.chevron })
|
|
1294
|
+
]
|
|
1295
|
+
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1296
|
+
className: SmoothStreamCard_module_css_default.body,
|
|
1297
|
+
children: [
|
|
1298
|
+
state.status === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1299
|
+
className: SmoothStreamCard_module_css_default.readOnly,
|
|
1300
|
+
role: "status",
|
|
1301
|
+
children: t("loading")
|
|
1302
|
+
}) : null,
|
|
1303
|
+
state.status === "unavailable" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1304
|
+
className: SmoothStreamCard_module_css_default.failure,
|
|
1305
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1306
|
+
className: SmoothStreamCard_module_css_default.readOnly,
|
|
1307
|
+
role: "status",
|
|
1308
|
+
children: t("unavailable")
|
|
1309
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1310
|
+
type: "button",
|
|
1311
|
+
className: SmoothStreamCard_module_css_default.discard,
|
|
1312
|
+
onClick: props.reload,
|
|
1313
|
+
children: t("retry")
|
|
1314
|
+
})]
|
|
1315
|
+
}) : null,
|
|
1316
|
+
state.status === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1317
|
+
!state.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1318
|
+
className: SmoothStreamCard_module_css_default.readOnly,
|
|
1319
|
+
role: "status",
|
|
1320
|
+
children: t("readOnly")
|
|
1321
|
+
}) : null,
|
|
1322
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1323
|
+
className: SmoothStreamCard_module_css_default.field,
|
|
1324
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1325
|
+
className: SmoothStreamCard_module_css_default.fieldHead,
|
|
1326
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1327
|
+
className: SmoothStreamCard_module_css_default.label,
|
|
1328
|
+
children: t("thinkAutoExpand")
|
|
1329
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1330
|
+
type: "checkbox",
|
|
1331
|
+
className: SmoothStreamCard_module_css_default.toggle,
|
|
1332
|
+
checked: state.thinkAutoExpand,
|
|
1333
|
+
disabled: !state.writable,
|
|
1334
|
+
onChange: (event) => {
|
|
1335
|
+
props.edit(event.target.checked);
|
|
1336
|
+
}
|
|
1337
|
+
})]
|
|
1338
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1339
|
+
className: SmoothStreamCard_module_css_default.hint,
|
|
1340
|
+
children: t("thinkAutoExpandHint")
|
|
1341
|
+
})]
|
|
1342
|
+
}),
|
|
1343
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1344
|
+
className: SmoothStreamCard_module_css_default.updateRow,
|
|
1345
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1346
|
+
className: SmoothStreamCard_module_css_default.updateCopy,
|
|
1347
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1348
|
+
className: SmoothStreamCard_module_css_default.label,
|
|
1349
|
+
children: t("updates")
|
|
1350
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1351
|
+
className: SmoothStreamCard_module_css_default.hint,
|
|
1352
|
+
children: state.restartRequired ? t("restartRequired") : state.installation === "npm" ? t("updateHint") : state.installation === "development" ? t("developmentBuild") : t("updateUnavailable")
|
|
1353
|
+
})]
|
|
1354
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1355
|
+
type: "button",
|
|
1356
|
+
className: SmoothStreamCard_module_css_default.update,
|
|
1357
|
+
disabled: !state.canUpgrade || state.upgrading || state.restartRequired,
|
|
1358
|
+
title: state.canUpgrade ? void 0 : t("updateUnavailable"),
|
|
1359
|
+
onClick: props.upgrade,
|
|
1360
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1361
|
+
"aria-hidden": "true",
|
|
1362
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline14, {})
|
|
1363
|
+
}), t(state.upgrading ? "updating" : "update")]
|
|
1364
|
+
})]
|
|
1365
|
+
}),
|
|
1366
|
+
state.upgradeFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1367
|
+
className: SmoothStreamCard_module_css_default.failed,
|
|
1368
|
+
role: "status",
|
|
1369
|
+
children: t("updateFailed")
|
|
1370
|
+
}) : null,
|
|
1371
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1372
|
+
className: SmoothStreamCard_module_css_default.footer,
|
|
1373
|
+
children: [
|
|
1374
|
+
state.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1375
|
+
className: SmoothStreamCard_module_css_default.failed,
|
|
1376
|
+
role: "status",
|
|
1377
|
+
children: t("saveFailed")
|
|
1378
|
+
}) : null,
|
|
1379
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1380
|
+
type: "button",
|
|
1381
|
+
className: SmoothStreamCard_module_css_default.discard,
|
|
1382
|
+
disabled: !state.dirty || state.saving,
|
|
1383
|
+
onClick: props.discard,
|
|
1384
|
+
children: t("discard")
|
|
1385
|
+
}),
|
|
1386
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1387
|
+
type: "button",
|
|
1388
|
+
className: SmoothStreamCard_module_css_default.save,
|
|
1389
|
+
disabled: blocked,
|
|
1390
|
+
onClick: props.save,
|
|
1391
|
+
children: t(state.saving ? "saving" : "save")
|
|
1392
|
+
})
|
|
1393
|
+
]
|
|
1394
|
+
})
|
|
1395
|
+
] }) : null
|
|
1396
|
+
]
|
|
1397
|
+
}) : null]
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
//#endregion
|
|
1401
|
+
//#region src/client/smooth-stream-card-controller.ts
|
|
1402
|
+
/** Staged form state for the plugin-owned smooth-stream settings RPC. */
|
|
1403
|
+
/** Bridges the plugin's protected Host interface onto a staged settings form. */
|
|
1404
|
+
var SmoothStreamCardController = class {
|
|
1405
|
+
api;
|
|
1406
|
+
store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(this.projection());
|
|
1407
|
+
loaded;
|
|
1408
|
+
staged;
|
|
1409
|
+
saving = false;
|
|
1410
|
+
failed = false;
|
|
1411
|
+
upgrading = false;
|
|
1412
|
+
upgradeFailed = false;
|
|
1413
|
+
restartRequired = false;
|
|
1414
|
+
loadGeneration = 0;
|
|
1415
|
+
loadStatus = "loading";
|
|
1416
|
+
constructor(api) {
|
|
1417
|
+
this.api = api;
|
|
1418
|
+
}
|
|
1419
|
+
/** Begin the background read after the card has been registered. */
|
|
1420
|
+
start() {
|
|
1421
|
+
this.load();
|
|
1422
|
+
}
|
|
1423
|
+
/** Ignore a late response after the surrounding optional services unload. */
|
|
1424
|
+
stop() {
|
|
1425
|
+
this.loadGeneration += 1;
|
|
1426
|
+
}
|
|
1427
|
+
/** Current card snapshot, also consumed by the streaming preference cell. */
|
|
1428
|
+
getSnapshot() {
|
|
1429
|
+
return this.store.getSnapshot();
|
|
1430
|
+
}
|
|
1431
|
+
/** Subscribe to state changes. */
|
|
1432
|
+
subscribe(listener) {
|
|
1433
|
+
return this.store.subscribe(listener);
|
|
1434
|
+
}
|
|
1435
|
+
/** Build the face consumed by the settings slot renderer. */
|
|
1436
|
+
inject() {
|
|
1437
|
+
return {
|
|
1438
|
+
hooks: { smoothStreamCard: this.store },
|
|
1439
|
+
edit: (value) => {
|
|
1440
|
+
this.staged = value;
|
|
1441
|
+
this.failed = false;
|
|
1442
|
+
this.publish();
|
|
1443
|
+
},
|
|
1444
|
+
save: () => {
|
|
1445
|
+
this.save();
|
|
1446
|
+
},
|
|
1447
|
+
discard: () => {
|
|
1448
|
+
if (this.staged === void 0 && !this.failed) return;
|
|
1449
|
+
this.staged = void 0;
|
|
1450
|
+
this.failed = false;
|
|
1451
|
+
this.publish();
|
|
1452
|
+
},
|
|
1453
|
+
reload: () => {
|
|
1454
|
+
this.load();
|
|
1455
|
+
},
|
|
1456
|
+
upgrade: () => {
|
|
1457
|
+
this.upgrade();
|
|
1458
|
+
}
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
projection() {
|
|
1462
|
+
return {
|
|
1463
|
+
status: this.loadStatus,
|
|
1464
|
+
writable: this.loaded?.writable ?? false,
|
|
1465
|
+
dirty: this.staged !== void 0,
|
|
1466
|
+
saving: this.saving,
|
|
1467
|
+
failed: this.failed,
|
|
1468
|
+
thinkAutoExpand: this.staged ?? this.loaded?.thinkAutoExpand ?? DEFAULT_STREAM_SETTINGS.thinkAutoExpand,
|
|
1469
|
+
version: this.loaded?.version,
|
|
1470
|
+
installation: this.loaded?.installation ?? "unmanaged",
|
|
1471
|
+
canUpgrade: this.loaded?.canUpgrade ?? false,
|
|
1472
|
+
upgrading: this.upgrading,
|
|
1473
|
+
upgradeFailed: this.upgradeFailed,
|
|
1474
|
+
restartRequired: this.restartRequired
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
async load() {
|
|
1478
|
+
const generation = ++this.loadGeneration;
|
|
1479
|
+
this.loadStatus = "loading";
|
|
1480
|
+
this.loaded = void 0;
|
|
1481
|
+
this.publish();
|
|
1482
|
+
try {
|
|
1483
|
+
const view = await this.api.read();
|
|
1484
|
+
if (generation !== this.loadGeneration) return;
|
|
1485
|
+
this.loaded = view;
|
|
1486
|
+
this.loadStatus = "ready";
|
|
1487
|
+
} catch {
|
|
1488
|
+
if (generation !== this.loadGeneration) return;
|
|
1489
|
+
this.loaded = void 0;
|
|
1490
|
+
this.loadStatus = "unavailable";
|
|
1491
|
+
}
|
|
1492
|
+
this.publish();
|
|
1493
|
+
}
|
|
1494
|
+
async save() {
|
|
1495
|
+
if (this.staged === void 0 || this.saving || this.loaded?.writable !== true) return;
|
|
1496
|
+
const value = this.staged;
|
|
1497
|
+
this.saving = true;
|
|
1498
|
+
this.failed = false;
|
|
1499
|
+
this.publish();
|
|
1500
|
+
try {
|
|
1501
|
+
this.loaded = await this.api.write(value);
|
|
1502
|
+
this.staged = void 0;
|
|
1503
|
+
} catch {
|
|
1504
|
+
this.failed = true;
|
|
1505
|
+
}
|
|
1506
|
+
this.saving = false;
|
|
1507
|
+
this.publish();
|
|
1508
|
+
}
|
|
1509
|
+
async upgrade() {
|
|
1510
|
+
if (this.loaded?.canUpgrade !== true || this.upgrading) return;
|
|
1511
|
+
this.upgrading = true;
|
|
1512
|
+
this.upgradeFailed = false;
|
|
1513
|
+
this.restartRequired = false;
|
|
1514
|
+
this.publish();
|
|
1515
|
+
try {
|
|
1516
|
+
const result = await this.api.upgrade();
|
|
1517
|
+
this.restartRequired = result.restartRequired;
|
|
1518
|
+
} catch {
|
|
1519
|
+
this.upgradeFailed = true;
|
|
1520
|
+
}
|
|
1521
|
+
this.upgrading = false;
|
|
1522
|
+
this.publish();
|
|
1523
|
+
}
|
|
1524
|
+
publish() {
|
|
1525
|
+
this.store.set(this.projection());
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
1528
|
+
//#endregion
|
|
1529
|
+
//#region src/settings-api.ts
|
|
1530
|
+
/** Shared wire vocabulary for the plugin-owned settings RPC channel. */
|
|
1531
|
+
/** Dedicated, loopback-only RPC channel registered by the Host half. */
|
|
1532
|
+
const STREAM_SETTINGS_RPC_CHANNEL = "/smooth-stream";
|
|
1533
|
+
/** Endpoints accepted by {@link STREAM_SETTINGS_RPC_CHANNEL}. */
|
|
1534
|
+
const STREAM_SETTINGS_RPC = {
|
|
1535
|
+
read: "settings.read",
|
|
1536
|
+
write: "settings.write",
|
|
1537
|
+
upgrade: "plugin.upgrade"
|
|
1538
|
+
};
|
|
1539
|
+
//#endregion
|
|
1540
|
+
//#region src/client/smooth-stream-settings-api.ts
|
|
1541
|
+
function record(value) {
|
|
1542
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
1543
|
+
}
|
|
1544
|
+
function settingsView(value) {
|
|
1545
|
+
const data = record(value);
|
|
1546
|
+
if (data === void 0 || typeof data.version !== "string" || ![
|
|
1547
|
+
"npm",
|
|
1548
|
+
"development",
|
|
1549
|
+
"unmanaged"
|
|
1550
|
+
].includes(data.installation) || typeof data.writable !== "boolean" || typeof data.thinkAutoExpand !== "boolean" || typeof data.canUpgrade !== "boolean") throw new Error("dsh-smooth-stream: malformed settings response");
|
|
1551
|
+
return data;
|
|
1552
|
+
}
|
|
1553
|
+
function upgradeView(value) {
|
|
1554
|
+
if (record(value)?.restartRequired !== true) throw new Error("dsh-smooth-stream: malformed update response");
|
|
1555
|
+
return { restartRequired: true };
|
|
1556
|
+
}
|
|
1557
|
+
function accepted(result) {
|
|
1558
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
1559
|
+
return result.value;
|
|
1560
|
+
}
|
|
1561
|
+
/** Build the typed facade over the generic Connection RPC service. */
|
|
1562
|
+
function createSmoothStreamSettingsApi(connection) {
|
|
1563
|
+
return {
|
|
1564
|
+
async read() {
|
|
1565
|
+
return settingsView(accepted(await connection.rpc.call(STREAM_SETTINGS_RPC_CHANNEL, STREAM_SETTINGS_RPC.read, {})));
|
|
1566
|
+
},
|
|
1567
|
+
async write(thinkAutoExpand) {
|
|
1568
|
+
return settingsView(accepted(await connection.rpc.call(STREAM_SETTINGS_RPC_CHANNEL, STREAM_SETTINGS_RPC.write, { thinkAutoExpand })));
|
|
1569
|
+
},
|
|
1570
|
+
async upgrade() {
|
|
1571
|
+
return upgradeView(accepted(await connection.rpc.call(STREAM_SETTINGS_RPC_CHANNEL, STREAM_SETTINGS_RPC.upgrade, {})));
|
|
1572
|
+
}
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
//#endregion
|
|
1576
|
+
//#region src/client/locales.ts
|
|
1577
|
+
/** Locale bundles for the smooth-stream plugin configuration card. */
|
|
1578
|
+
/** Dictionary namespace owned by this plugin's settings card. */
|
|
1579
|
+
const NS = "settings.smoothStream";
|
|
1580
|
+
/** English copy. */
|
|
1581
|
+
const en = {
|
|
1582
|
+
title: "Smooth stream",
|
|
1583
|
+
description: "How replies are revealed while they stream.",
|
|
1584
|
+
thinkAutoExpand: "Auto-expand thinking",
|
|
1585
|
+
thinkAutoExpandHint: "Open the thinking block while it streams. Turn off to keep it collapsed.",
|
|
1586
|
+
readOnly: "This deployment stores settings read-only.",
|
|
1587
|
+
loading: "Loading plugin settings…",
|
|
1588
|
+
unavailable: "Plugin settings are unavailable in this connection.",
|
|
1589
|
+
retry: "Retry",
|
|
1590
|
+
version: "Version {version}",
|
|
1591
|
+
developmentVersion: "Development version {version}",
|
|
1592
|
+
updates: "Updates",
|
|
1593
|
+
updateHint: "Install the newest npm version, then restart Harness.",
|
|
1594
|
+
developmentBuild: "Linked source; updates are managed in the checkout.",
|
|
1595
|
+
updateUnavailable: "Updates are available only for an npm profile installation.",
|
|
1596
|
+
update: "Update",
|
|
1597
|
+
updating: "Updating…",
|
|
1598
|
+
restartRequired: "Updated. Restart Harness to load the new version.",
|
|
1599
|
+
updateFailed: "The package update failed; your current version is unchanged.",
|
|
1600
|
+
save: "Save",
|
|
1601
|
+
saving: "Saving…",
|
|
1602
|
+
discard: "Discard",
|
|
1603
|
+
unsaved: "Unsaved",
|
|
1604
|
+
saveFailed: "The deployment did not accept these values; they were left for you to correct."
|
|
1605
|
+
};
|
|
1606
|
+
/** Simplified Chinese copy. */
|
|
1607
|
+
const zh = {
|
|
1608
|
+
title: "丝滑流式",
|
|
1609
|
+
description: "回复在流式输出时如何逐字展现。",
|
|
1610
|
+
thinkAutoExpand: "自动展开思考",
|
|
1611
|
+
thinkAutoExpandHint: "思考块在流式时自动展开;关闭后保持折叠,可手动展开。",
|
|
1612
|
+
readOnly: "本部署的设置为只读。",
|
|
1613
|
+
loading: "正在加载插件设置…",
|
|
1614
|
+
unavailable: "当前连接无法访问插件设置。",
|
|
1615
|
+
retry: "重试",
|
|
1616
|
+
version: "版本 {version}",
|
|
1617
|
+
developmentVersion: "开发版本 {version}",
|
|
1618
|
+
updates: "更新",
|
|
1619
|
+
updateHint: "安装最新 npm 版本后重启 Harness。",
|
|
1620
|
+
developmentBuild: "当前为本地链接版本,请在源码目录管理更新。",
|
|
1621
|
+
updateUnavailable: "只有 profile 使用 npm 包时才能更新。",
|
|
1622
|
+
update: "更新",
|
|
1623
|
+
updating: "更新中…",
|
|
1624
|
+
restartRequired: "已更新;重启 Harness 后加载新版本。",
|
|
1625
|
+
updateFailed: "包更新失败,当前版本未改变。",
|
|
1626
|
+
save: "保存",
|
|
1627
|
+
saving: "保存中…",
|
|
1628
|
+
discard: "放弃修改",
|
|
1629
|
+
unsaved: "未保存",
|
|
1630
|
+
saveFailed: "本部署没有接受这些值,已保留供你修改。"
|
|
1631
|
+
};
|
|
1632
|
+
//#endregion
|
|
1175
1633
|
//#region src/client/index.ts
|
|
1176
|
-
/**
|
|
1634
|
+
/**
|
|
1635
|
+
* Cordis services required by the browser half. Only `slots` is load-bearing
|
|
1636
|
+
* for the stream itself; locale and Connection power the configuration card
|
|
1637
|
+
* and are wired through `ctx.inject` below so a deployment without them still
|
|
1638
|
+
* streams with defaults.
|
|
1639
|
+
*/
|
|
1177
1640
|
const inject = ["slots"];
|
|
1178
1641
|
const STREAM_MODES = ["typewriter", "teleprompter"];
|
|
1179
1642
|
const STREAM_PRESETS = [
|
|
@@ -1244,23 +1707,93 @@ window.__ModuleLoader__.load({
|
|
|
1244
1707
|
};
|
|
1245
1708
|
}
|
|
1246
1709
|
/**
|
|
1710
|
+
* A live preference cell read by `useSyncExternalStore`. It starts on the
|
|
1711
|
+
* shared default and, once the plugin-owned settings controller is attached,
|
|
1712
|
+
* tracks the resolved `thinkAutoExpand` value. A composition (or test) that
|
|
1713
|
+
* ships no settings surface keeps the cell on the default.
|
|
1714
|
+
*/
|
|
1715
|
+
var PreferenceCell = class {
|
|
1716
|
+
listeners = /* @__PURE__ */ new Set();
|
|
1717
|
+
card;
|
|
1718
|
+
value = DEFAULT_STREAM_SETTINGS.thinkAutoExpand;
|
|
1719
|
+
/** Re-point the cell at the plugin-owned settings controller. */
|
|
1720
|
+
attach(card) {
|
|
1721
|
+
this.card = card;
|
|
1722
|
+
this.refresh();
|
|
1723
|
+
const unsubscribe = card.subscribe(() => {
|
|
1724
|
+
this.refresh();
|
|
1725
|
+
});
|
|
1726
|
+
return () => {
|
|
1727
|
+
unsubscribe();
|
|
1728
|
+
if (this.card !== card) return;
|
|
1729
|
+
this.card = void 0;
|
|
1730
|
+
this.refresh();
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
read() {
|
|
1734
|
+
return this.card?.getSnapshot().thinkAutoExpand ?? DEFAULT_STREAM_SETTINGS.thinkAutoExpand;
|
|
1735
|
+
}
|
|
1736
|
+
refresh() {
|
|
1737
|
+
const next = this.read();
|
|
1738
|
+
if (next === this.value) return;
|
|
1739
|
+
this.value = next;
|
|
1740
|
+
for (const listener of this.listeners) listener();
|
|
1741
|
+
}
|
|
1742
|
+
getSnapshot = () => this.value;
|
|
1743
|
+
subscribe = (listener) => {
|
|
1744
|
+
this.listeners.add(listener);
|
|
1745
|
+
return () => {
|
|
1746
|
+
this.listeners.delete(listener);
|
|
1747
|
+
};
|
|
1748
|
+
};
|
|
1749
|
+
};
|
|
1750
|
+
/**
|
|
1247
1751
|
* Register the typewriter renderer after the conversation package declares the
|
|
1248
1752
|
* keyed Chat node seat. A lower priority shadows the built-in assistant row;
|
|
1249
1753
|
* every other growing row is wrapped in place so Tool cards, retries, and
|
|
1250
1754
|
* workflow runs share conversation follow. The Host-bridged configuration
|
|
1251
|
-
* selects the render direction, smoothing preset, and glide speed
|
|
1755
|
+
* selects the render direction, smoothing preset, and glide speed; the
|
|
1756
|
+
* plugin-owned settings RPC supplies the live auto-expand preference when the
|
|
1757
|
+
* settings surface is composed.
|
|
1252
1758
|
* @param ctx - Browser context carrying the shared slot registry.
|
|
1253
1759
|
*/
|
|
1254
1760
|
function apply(ctx) {
|
|
1255
1761
|
const config = readBootConfig();
|
|
1762
|
+
const preference = new PreferenceCell();
|
|
1763
|
+
ctx.inject([
|
|
1764
|
+
"slots",
|
|
1765
|
+
"locale",
|
|
1766
|
+
"connection"
|
|
1767
|
+
], (settingsCtx) => {
|
|
1768
|
+
const card = new SmoothStreamCardController(createSmoothStreamSettingsApi(settingsCtx.get("connection")));
|
|
1769
|
+
const detachPreference = preference.attach(card);
|
|
1770
|
+
card.start();
|
|
1771
|
+
settingsCtx.effect(() => settingsCtx.locale.register(NS, {
|
|
1772
|
+
zh,
|
|
1773
|
+
en
|
|
1774
|
+
}), "dsh-smooth-stream: settings dictionaries");
|
|
1775
|
+
settingsCtx.slots.inject("settings.plugin.item", () => settingsCtx.slots.register({
|
|
1776
|
+
name: "settings.plugin.item",
|
|
1777
|
+
id: "smooth-stream",
|
|
1778
|
+
order: 30,
|
|
1779
|
+
locale: NS,
|
|
1780
|
+
inject: () => card.inject()
|
|
1781
|
+
}, SmoothStreamCard));
|
|
1782
|
+
return () => {
|
|
1783
|
+
card.stop();
|
|
1784
|
+
detachPreference();
|
|
1785
|
+
};
|
|
1786
|
+
});
|
|
1256
1787
|
const configured = function StreamConfiguredView(props) {
|
|
1788
|
+
const thinkAutoExpand = (0, react.useSyncExternalStore)(preference.subscribe, preference.getSnapshot, preference.getSnapshot);
|
|
1257
1789
|
return (0, react.createElement)(TypewriterAssistantNodeView, {
|
|
1258
1790
|
...props,
|
|
1259
1791
|
mode: config.mode,
|
|
1260
1792
|
preset: config.preset,
|
|
1261
1793
|
revealCharsPerSec: config.revealCharsPerSec,
|
|
1262
1794
|
scrollSpeedPxPerSec: config.scrollSpeedPxPerSec,
|
|
1263
|
-
maxScrollSpeedPxPerSec: config.maxScrollSpeedPxPerSec
|
|
1795
|
+
maxScrollSpeedPxPerSec: config.maxScrollSpeedPxPerSec,
|
|
1796
|
+
thinkAutoExpand
|
|
1264
1797
|
});
|
|
1265
1798
|
};
|
|
1266
1799
|
ctx.slots.inject("conversation.chat.node", () => {
|