dsh-plugin-subscriptions 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/README.zh.md +4 -0
- package/lib/auth/rpc.d.ts +18 -1
- package/lib/auth/rpc.js +23 -3
- package/lib/client/ImageGallery.d.ts +54 -0
- package/lib/client/ImageGallery.js +112 -0
- package/lib/client/ImageGenerateToolview.d.ts +1 -1
- package/lib/client/ImageGenerateToolview.js +2 -2
- package/lib/client/SpeedSelect.d.ts +48 -0
- package/lib/client/SpeedSelect.js +173 -0
- package/lib/client/SubscriptionsSection.d.ts +9 -0
- package/lib/client/SubscriptionsSection.js +2 -1
- package/lib/client/index.d.ts +1 -0
- package/lib/client/index.js +42 -0
- package/lib/client/locales.d.ts +14 -0
- package/lib/client/locales.js +14 -0
- package/lib/client.js +585 -73
- package/lib/client.js.map +1 -1
- package/lib/index.js +113 -42
- package/lib/providers/catalog-store.js +4 -0
- package/lib/providers/claude.js +14 -4
- package/lib/providers/codex.d.ts +27 -0
- package/lib/providers/codex.js +66 -20
- package/lib/providers/common.d.ts +2 -0
- package/lib/tools/image-generate.js +3 -10
- package/package.json +10 -6
package/lib/client.js
CHANGED
|
@@ -29,8 +29,6 @@ let react_jsx_runtime = require("react/jsx-runtime");
|
|
|
29
29
|
react_jsx_runtime = __toESM(react_jsx_runtime);
|
|
30
30
|
let __deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
31
31
|
__deepseek_ai_dsh_client_ui_primitives = __toESM(__deepseek_ai_dsh_client_ui_primitives);
|
|
32
|
-
let __deepseek_ai_dsh_client_ui_attachment = require("@deepseek-ai/dsh-client-ui-attachment");
|
|
33
|
-
__deepseek_ai_dsh_client_ui_attachment = __toESM(__deepseek_ai_dsh_client_ui_attachment);
|
|
34
32
|
|
|
35
33
|
//#region src/client/locales.ts
|
|
36
34
|
/** Copy dictionaries for the Subscriptions settings section. */
|
|
@@ -74,7 +72,14 @@ const en = {
|
|
|
74
72
|
imageClose: "Close",
|
|
75
73
|
generatingVideo: "Generating video…",
|
|
76
74
|
videoLoading: "Loading video…",
|
|
77
|
-
videoLoadFailed: "Video failed to load: {message}"
|
|
75
|
+
videoLoadFailed: "Video failed to load: {message}",
|
|
76
|
+
speed: "Speed",
|
|
77
|
+
speedStandard: "Standard",
|
|
78
|
+
speedStandardDescription: "Default speed",
|
|
79
|
+
speedFast: "Fast",
|
|
80
|
+
speedFastDescription: "1.5x speed, more usage",
|
|
81
|
+
commandFast: "Switch the Codex speed tier (Standard/Fast)",
|
|
82
|
+
commandFastUnavailable: "The current model has no fast tier; /fast only works on Codex models whose catalog advertises one"
|
|
78
83
|
};
|
|
79
84
|
/** zh strings, one per {@link en} key. */
|
|
80
85
|
const zh = {
|
|
@@ -116,7 +121,14 @@ const zh = {
|
|
|
116
121
|
imageClose: "关闭",
|
|
117
122
|
generatingVideo: "正在生成视频…",
|
|
118
123
|
videoLoading: "视频加载中…",
|
|
119
|
-
videoLoadFailed: "视频加载失败:{message}"
|
|
124
|
+
videoLoadFailed: "视频加载失败:{message}",
|
|
125
|
+
speed: "速度",
|
|
126
|
+
speedStandard: "标准",
|
|
127
|
+
speedStandardDescription: "默认速度",
|
|
128
|
+
speedFast: "快速",
|
|
129
|
+
speedFastDescription: "约 1.5 倍速度,消耗更多用量",
|
|
130
|
+
commandFast: "切换 Codex 速度档(标准/快速)",
|
|
131
|
+
commandFastUnavailable: "当前模型不支持快速档;/fast 仅对目录声明了 fast tier 的 Codex 模型可用"
|
|
120
132
|
};
|
|
121
133
|
|
|
122
134
|
//#endregion
|
|
@@ -124,7 +136,7 @@ const zh = {
|
|
|
124
136
|
/** Logical RPC channel served by the node half of this plugin. */
|
|
125
137
|
const SUBSCRIPTIONS_AUTH_CHANNEL$2 = "/subscriptions-auth";
|
|
126
138
|
/** Poll cadence while a provider login attempt is busy. */
|
|
127
|
-
const POLL_INTERVAL_MS = 2e3;
|
|
139
|
+
const POLL_INTERVAL_MS$1 = 2e3;
|
|
128
140
|
/** Card display metadata, in page order (names are brand names, not translated). */
|
|
129
141
|
const PROVIDERS = [
|
|
130
142
|
{
|
|
@@ -144,12 +156,13 @@ const PROVIDERS = [
|
|
|
144
156
|
var SubscriptionsAuthError = class extends Error {};
|
|
145
157
|
/**
|
|
146
158
|
* Call one `/subscriptions-auth` endpoint and unwrap the business result.
|
|
159
|
+
* Shared by the settings section and the composer Speed toggle.
|
|
147
160
|
* @param rpc - Connection RPC caller.
|
|
148
161
|
* @param endpoint - channel-relative endpoint.
|
|
149
162
|
* @param payload - channel-owned request payload.
|
|
150
163
|
* @returns the success value, cast by the caller to the endpoint's shape.
|
|
151
164
|
*/
|
|
152
|
-
async function callSubscriptionsAuth
|
|
165
|
+
async function callSubscriptionsAuth(rpc, endpoint, payload) {
|
|
153
166
|
let result;
|
|
154
167
|
try {
|
|
155
168
|
result = await rpc.call(SUBSCRIPTIONS_AUTH_CHANNEL$2, endpoint, payload);
|
|
@@ -170,12 +183,12 @@ function messageOf(error) {
|
|
|
170
183
|
* @param params - `{name}` template params.
|
|
171
184
|
* @returns the template with params substituted.
|
|
172
185
|
*/
|
|
173
|
-
function fallbackTranslate$
|
|
186
|
+
function fallbackTranslate$3(key, params) {
|
|
174
187
|
let text = en[key];
|
|
175
188
|
for (const [name, value] of Object.entries(params ?? {})) text = text.replaceAll(`{${name}}`, String(value));
|
|
176
189
|
return text;
|
|
177
190
|
}
|
|
178
|
-
const styles$
|
|
191
|
+
const styles$4 = {
|
|
179
192
|
section: {
|
|
180
193
|
display: "flex",
|
|
181
194
|
flexDirection: "column",
|
|
@@ -388,7 +401,7 @@ function usageBarColor(usedPercent) {
|
|
|
388
401
|
*/
|
|
389
402
|
function SubscriptionsSection(props) {
|
|
390
403
|
const { rpc } = props;
|
|
391
|
-
const t = props.t ?? fallbackTranslate$
|
|
404
|
+
const t = props.t ?? fallbackTranslate$3;
|
|
392
405
|
const [statuses, setStatuses] = (0, react.useState)({});
|
|
393
406
|
const [errors, setErrors] = (0, react.useState)({});
|
|
394
407
|
const [manualDrafts, setManualDrafts] = (0, react.useState)({
|
|
@@ -424,7 +437,7 @@ function SubscriptionsSection(props) {
|
|
|
424
437
|
if (rpc === void 0) return;
|
|
425
438
|
let response;
|
|
426
439
|
try {
|
|
427
|
-
response = await callSubscriptionsAuth
|
|
440
|
+
response = await callSubscriptionsAuth(rpc, "status", {});
|
|
428
441
|
} catch {
|
|
429
442
|
return;
|
|
430
443
|
}
|
|
@@ -439,7 +452,7 @@ function SubscriptionsSection(props) {
|
|
|
439
452
|
if (pollersRef.current.has(provider)) return;
|
|
440
453
|
pollersRef.current.set(provider, setInterval(() => {
|
|
441
454
|
refresh();
|
|
442
|
-
}, POLL_INTERVAL_MS));
|
|
455
|
+
}, POLL_INTERVAL_MS$1));
|
|
443
456
|
}, [refresh]);
|
|
444
457
|
(0, react.useEffect)(() => {
|
|
445
458
|
mountedRef.current = true;
|
|
@@ -464,7 +477,7 @@ function SubscriptionsSection(props) {
|
|
|
464
477
|
[provider]: true
|
|
465
478
|
}));
|
|
466
479
|
try {
|
|
467
|
-
const usage = await callSubscriptionsAuth
|
|
480
|
+
const usage = await callSubscriptionsAuth(rpc, "usage", { provider });
|
|
468
481
|
if (!mountedRef.current) return;
|
|
469
482
|
setUsages((prev) => ({
|
|
470
483
|
...prev,
|
|
@@ -517,7 +530,7 @@ function SubscriptionsSection(props) {
|
|
|
517
530
|
if (rpc === void 0) return;
|
|
518
531
|
setProviderError(provider, void 0);
|
|
519
532
|
try {
|
|
520
|
-
const response = await callSubscriptionsAuth
|
|
533
|
+
const response = await callSubscriptionsAuth(rpc, "login", { provider });
|
|
521
534
|
if (typeof response.authorizeUrl === "string" && response.authorizeUrl === "") {
|
|
522
535
|
await refresh();
|
|
523
536
|
return;
|
|
@@ -547,7 +560,7 @@ function SubscriptionsSection(props) {
|
|
|
547
560
|
if (rpc === void 0) return;
|
|
548
561
|
stopPolling(provider);
|
|
549
562
|
try {
|
|
550
|
-
await callSubscriptionsAuth
|
|
563
|
+
await callSubscriptionsAuth(rpc, "cancel", { provider });
|
|
551
564
|
} catch (error) {
|
|
552
565
|
setProviderError(provider, messageOf(error));
|
|
553
566
|
}
|
|
@@ -564,7 +577,7 @@ function SubscriptionsSection(props) {
|
|
|
564
577
|
if (input === "") return;
|
|
565
578
|
setProviderError(provider, void 0);
|
|
566
579
|
try {
|
|
567
|
-
await callSubscriptionsAuth
|
|
580
|
+
await callSubscriptionsAuth(rpc, "manual", {
|
|
568
581
|
provider,
|
|
569
582
|
input
|
|
570
583
|
});
|
|
@@ -587,7 +600,7 @@ function SubscriptionsSection(props) {
|
|
|
587
600
|
if (!window.confirm(t("logoutConfirm", { provider: name }))) return;
|
|
588
601
|
setProviderError(provider, void 0);
|
|
589
602
|
try {
|
|
590
|
-
await callSubscriptionsAuth
|
|
603
|
+
await callSubscriptionsAuth(rpc, "logout", { provider });
|
|
591
604
|
} catch (error) {
|
|
592
605
|
setProviderError(provider, messageOf(error));
|
|
593
606
|
}
|
|
@@ -599,13 +612,13 @@ function SubscriptionsSection(props) {
|
|
|
599
612
|
refresh
|
|
600
613
|
]);
|
|
601
614
|
if (rpc === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
602
|
-
style: styles$
|
|
615
|
+
style: styles$4.intro,
|
|
603
616
|
children: t("unavailable")
|
|
604
617
|
});
|
|
605
618
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
606
|
-
style: styles$
|
|
619
|
+
style: styles$4.section,
|
|
607
620
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
608
|
-
style: styles$
|
|
621
|
+
style: styles$4.intro,
|
|
609
622
|
children: t("intro")
|
|
610
623
|
}), PROVIDERS.map(({ id, name }) => {
|
|
611
624
|
const status = statuses[id];
|
|
@@ -614,36 +627,36 @@ function SubscriptionsSection(props) {
|
|
|
614
627
|
const usageError = usageErrors[id];
|
|
615
628
|
const showUsage = status?.loggedIn === true && usage?.supported !== false && (usage !== void 0 || usageError !== void 0 || usageLoading[id] === true);
|
|
616
629
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
617
|
-
style: styles$
|
|
630
|
+
style: styles$4.card,
|
|
618
631
|
children: [
|
|
619
632
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
620
|
-
style: styles$
|
|
633
|
+
style: styles$4.cardHeader,
|
|
621
634
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
|
|
622
|
-
...styles$
|
|
635
|
+
...styles$4.dot,
|
|
623
636
|
background: dotColor(status)
|
|
624
637
|
} }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
625
|
-
style: styles$
|
|
638
|
+
style: styles$4.name,
|
|
626
639
|
children: name
|
|
627
640
|
})]
|
|
628
641
|
}),
|
|
629
642
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
630
|
-
style: styles$
|
|
643
|
+
style: styles$4.statusLine,
|
|
631
644
|
children: statusText(t, status)
|
|
632
645
|
}),
|
|
633
646
|
status?.detail !== void 0 && status.detail !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
634
|
-
style: styles$
|
|
647
|
+
style: styles$4.statusLine,
|
|
635
648
|
children: status.detail
|
|
636
649
|
}),
|
|
637
650
|
errors[id] !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
638
|
-
style: styles$
|
|
651
|
+
style: styles$4.errorLine,
|
|
639
652
|
children: errors[id]
|
|
640
653
|
}),
|
|
641
654
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
642
|
-
style: styles$
|
|
655
|
+
style: styles$4.actions,
|
|
643
656
|
children: [
|
|
644
657
|
!busy && status?.loggedIn !== true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
645
658
|
type: "button",
|
|
646
|
-
style: styles$
|
|
659
|
+
style: styles$4.button,
|
|
647
660
|
onClick: () => {
|
|
648
661
|
login(id);
|
|
649
662
|
},
|
|
@@ -651,7 +664,7 @@ function SubscriptionsSection(props) {
|
|
|
651
664
|
}),
|
|
652
665
|
busy && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
653
666
|
type: "button",
|
|
654
|
-
style: styles$
|
|
667
|
+
style: styles$4.button,
|
|
655
668
|
onClick: () => {
|
|
656
669
|
cancel(id);
|
|
657
670
|
},
|
|
@@ -659,7 +672,7 @@ function SubscriptionsSection(props) {
|
|
|
659
672
|
}),
|
|
660
673
|
status?.loggedIn === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
661
674
|
type: "button",
|
|
662
|
-
style: styles$
|
|
675
|
+
style: styles$4.button,
|
|
663
676
|
onClick: () => {
|
|
664
677
|
logout(id, name);
|
|
665
678
|
},
|
|
@@ -668,23 +681,23 @@ function SubscriptionsSection(props) {
|
|
|
668
681
|
]
|
|
669
682
|
}),
|
|
670
683
|
showUsage && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
671
|
-
style: styles$
|
|
684
|
+
style: styles$4.usage,
|
|
672
685
|
children: [
|
|
673
686
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
674
|
-
style: styles$
|
|
687
|
+
style: styles$4.usageHeader,
|
|
675
688
|
children: [
|
|
676
689
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
677
|
-
style: styles$
|
|
690
|
+
style: styles$4.usageTitle,
|
|
678
691
|
children: t("usageTitle")
|
|
679
692
|
}),
|
|
680
693
|
usage?.plan !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
681
|
-
style: styles$
|
|
694
|
+
style: styles$4.usagePlan,
|
|
682
695
|
children: t("usagePlan", { plan: usage.plan })
|
|
683
696
|
}),
|
|
684
697
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
685
698
|
type: "button",
|
|
686
699
|
style: {
|
|
687
|
-
...styles$
|
|
700
|
+
...styles$4.usageRefresh,
|
|
688
701
|
...usageLoading[id] === true ? {
|
|
689
702
|
opacity: .5,
|
|
690
703
|
cursor: "default"
|
|
@@ -699,28 +712,28 @@ function SubscriptionsSection(props) {
|
|
|
699
712
|
]
|
|
700
713
|
}),
|
|
701
714
|
usage === void 0 && usageError === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
702
|
-
style: styles$
|
|
715
|
+
style: styles$4.statusLine,
|
|
703
716
|
children: t("usageLoading")
|
|
704
717
|
}),
|
|
705
718
|
usageError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
706
|
-
style: styles$
|
|
719
|
+
style: styles$4.errorLine,
|
|
707
720
|
children: t("usageError", { message: usageError })
|
|
708
721
|
}),
|
|
709
722
|
usage?.windows !== void 0 && usage.windows.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
710
|
-
style: styles$
|
|
723
|
+
style: styles$4.statusLine,
|
|
711
724
|
children: t("usageEmpty")
|
|
712
725
|
}),
|
|
713
726
|
(usage?.windows ?? []).map((window$1, index) => {
|
|
714
727
|
const percent = Math.min(100, Math.max(0, window$1.usedPercent));
|
|
715
728
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
716
|
-
style: styles$
|
|
729
|
+
style: styles$4.usageRow,
|
|
717
730
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
718
|
-
style: styles$
|
|
731
|
+
style: styles$4.usageMeta,
|
|
719
732
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: usageWindowLabel(t, window$1) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [`${String(Math.round(percent))}%`, window$1.resetsAt !== void 0 && ` · ${t("usageResets", { date: new Date(window$1.resetsAt).toLocaleString() })}`] })]
|
|
720
733
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
721
|
-
style: styles$
|
|
734
|
+
style: styles$4.usageTrack,
|
|
722
735
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
|
|
723
|
-
...styles$
|
|
736
|
+
...styles$4.usageFill,
|
|
724
737
|
width: `${String(percent)}%`,
|
|
725
738
|
background: usageBarColor(percent)
|
|
726
739
|
} })
|
|
@@ -730,11 +743,11 @@ function SubscriptionsSection(props) {
|
|
|
730
743
|
]
|
|
731
744
|
}),
|
|
732
745
|
busy && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
733
|
-
style: styles$
|
|
746
|
+
style: styles$4.manual,
|
|
734
747
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("manualSummary") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
735
|
-
style: styles$
|
|
748
|
+
style: styles$4.manualRow,
|
|
736
749
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
737
|
-
style: styles$
|
|
750
|
+
style: styles$4.manualInput,
|
|
738
751
|
value: manualDrafts[id],
|
|
739
752
|
placeholder: t("manualPlaceholder"),
|
|
740
753
|
onChange: (event) => setManualDrafts((prev) => ({
|
|
@@ -743,7 +756,7 @@ function SubscriptionsSection(props) {
|
|
|
743
756
|
}))
|
|
744
757
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
745
758
|
type: "button",
|
|
746
|
-
style: styles$
|
|
759
|
+
style: styles$4.button,
|
|
747
760
|
onClick: () => {
|
|
748
761
|
submitManual(id);
|
|
749
762
|
},
|
|
@@ -757,6 +770,229 @@ function SubscriptionsSection(props) {
|
|
|
757
770
|
});
|
|
758
771
|
}
|
|
759
772
|
|
|
773
|
+
//#endregion
|
|
774
|
+
//#region src/client/ImageGallery.tsx
|
|
775
|
+
/** Display box for a lone image (platform rule): long edge 240px with the
|
|
776
|
+
* rendered aspect ratio clamped to [0.25, 4] — the overflow is cropped by
|
|
777
|
+
* `object-fit: cover` — and never upscaled past the image's natural size. The
|
|
778
|
+
* crop anchor keeps the top of very tall images and the left of very wide
|
|
779
|
+
* ones, where the informative content usually starts. */
|
|
780
|
+
function singleFit(attachment) {
|
|
781
|
+
const natural = attachment.width / attachment.height;
|
|
782
|
+
const ratio = Math.min(4, Math.max(.25, natural));
|
|
783
|
+
const box = ratio >= 1 ? {
|
|
784
|
+
width: 240,
|
|
785
|
+
height: 240 / ratio
|
|
786
|
+
} : {
|
|
787
|
+
width: 240 * ratio,
|
|
788
|
+
height: 240
|
|
789
|
+
};
|
|
790
|
+
const scale = Math.min(1, attachment.width / box.width, attachment.height / box.height);
|
|
791
|
+
return {
|
|
792
|
+
width: Math.max(1, Math.round(box.width * scale)),
|
|
793
|
+
height: Math.max(1, Math.round(box.height * scale)),
|
|
794
|
+
objectPosition: natural < .25 ? "center top" : natural > 4 ? "left center" : "center"
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
const styles$3 = {
|
|
798
|
+
gallery: {
|
|
799
|
+
display: "flex",
|
|
800
|
+
flexWrap: "wrap",
|
|
801
|
+
gap: 8,
|
|
802
|
+
justifyContent: "flex-start"
|
|
803
|
+
},
|
|
804
|
+
frame: {
|
|
805
|
+
display: "grid",
|
|
806
|
+
placeItems: "center",
|
|
807
|
+
overflow: "hidden",
|
|
808
|
+
padding: 0,
|
|
809
|
+
border: "1px solid var(--dsw-alias-border-l2-darkmode-thin)",
|
|
810
|
+
borderRadius: 8,
|
|
811
|
+
background: "var(--dsw-alias-interactive-bg-hover-solid)",
|
|
812
|
+
cursor: "zoom-in"
|
|
813
|
+
},
|
|
814
|
+
tile: {
|
|
815
|
+
width: 64,
|
|
816
|
+
height: 64
|
|
817
|
+
},
|
|
818
|
+
img: {
|
|
819
|
+
width: "100%",
|
|
820
|
+
height: "100%",
|
|
821
|
+
objectFit: "cover",
|
|
822
|
+
display: "block"
|
|
823
|
+
},
|
|
824
|
+
loading: {
|
|
825
|
+
fontSize: 12,
|
|
826
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
827
|
+
padding: "0 8px"
|
|
828
|
+
},
|
|
829
|
+
error: {
|
|
830
|
+
fontSize: 12,
|
|
831
|
+
color: "var(--dsw-alias-state-error-primary)",
|
|
832
|
+
cursor: "pointer",
|
|
833
|
+
border: "1px solid var(--dsw-alias-border-l2-darkmode-thin)",
|
|
834
|
+
borderRadius: 8,
|
|
835
|
+
background: "transparent",
|
|
836
|
+
padding: "6px 10px"
|
|
837
|
+
},
|
|
838
|
+
overlay: {
|
|
839
|
+
position: "fixed",
|
|
840
|
+
inset: 0,
|
|
841
|
+
zIndex: 1e3,
|
|
842
|
+
display: "grid",
|
|
843
|
+
placeItems: "center",
|
|
844
|
+
background: "rgba(0, 0, 0, 0.72)",
|
|
845
|
+
padding: 24
|
|
846
|
+
},
|
|
847
|
+
overlayImg: {
|
|
848
|
+
maxWidth: "92vw",
|
|
849
|
+
maxHeight: "92vh",
|
|
850
|
+
objectFit: "contain",
|
|
851
|
+
borderRadius: 4
|
|
852
|
+
},
|
|
853
|
+
close: {
|
|
854
|
+
position: "absolute",
|
|
855
|
+
top: 12,
|
|
856
|
+
right: 12,
|
|
857
|
+
width: 32,
|
|
858
|
+
height: 32,
|
|
859
|
+
display: "grid",
|
|
860
|
+
placeItems: "center",
|
|
861
|
+
border: "none",
|
|
862
|
+
borderRadius: "50%",
|
|
863
|
+
cursor: "pointer",
|
|
864
|
+
background: "rgba(255, 255, 255, 0.16)",
|
|
865
|
+
color: "#fff",
|
|
866
|
+
fontSize: 16,
|
|
867
|
+
lineHeight: 1
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
/**
|
|
871
|
+
* Full-viewport original-image preview: backdrop or close-control click and
|
|
872
|
+
* Escape all dismiss; the image itself is inert so a click on it does not
|
|
873
|
+
* fall through to the backdrop dismissal.
|
|
874
|
+
*/
|
|
875
|
+
function ImageLightbox({ src, alt, labels, onClose }) {
|
|
876
|
+
(0, react.useEffect)(() => {
|
|
877
|
+
const onKey = (event) => {
|
|
878
|
+
if (event.key === "Escape") onClose();
|
|
879
|
+
};
|
|
880
|
+
window.addEventListener("keydown", onKey);
|
|
881
|
+
return () => {
|
|
882
|
+
window.removeEventListener("keydown", onKey);
|
|
883
|
+
};
|
|
884
|
+
}, [onClose]);
|
|
885
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
886
|
+
role: "dialog",
|
|
887
|
+
"aria-label": labels.dialog,
|
|
888
|
+
style: styles$3.overlay,
|
|
889
|
+
onClick: onClose,
|
|
890
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
891
|
+
src,
|
|
892
|
+
alt,
|
|
893
|
+
style: styles$3.overlayImg,
|
|
894
|
+
onClick: (event) => {
|
|
895
|
+
event.stopPropagation();
|
|
896
|
+
}
|
|
897
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
898
|
+
type: "button",
|
|
899
|
+
"aria-label": labels.close,
|
|
900
|
+
style: styles$3.close,
|
|
901
|
+
onClick: onClose,
|
|
902
|
+
children: "×"
|
|
903
|
+
})]
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Compact history renderer with retryable loading and click-to-open original
|
|
908
|
+
* preview. A lone image renders at its `singleFit` size; an image among
|
|
909
|
+
* several renders as a fixed 64px square tile.
|
|
910
|
+
*/
|
|
911
|
+
function MessageImage({ attachment, load, variant, labels }) {
|
|
912
|
+
const [src, setSrc] = (0, react.useState)(null);
|
|
913
|
+
const [error, setError] = (0, react.useState)(false);
|
|
914
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
915
|
+
const [attempt, setAttempt] = (0, react.useState)(0);
|
|
916
|
+
const retry = (0, react.useCallback)(() => {
|
|
917
|
+
setAttempt((a) => a + 1);
|
|
918
|
+
}, []);
|
|
919
|
+
const close = (0, react.useCallback)(() => {
|
|
920
|
+
setOpen(false);
|
|
921
|
+
}, []);
|
|
922
|
+
const fit = (0, react.useMemo)(() => variant === "single" ? singleFit(attachment) : void 0, [attachment, variant]);
|
|
923
|
+
(0, react.useEffect)(() => {
|
|
924
|
+
let live = true;
|
|
925
|
+
setError(false);
|
|
926
|
+
setSrc(null);
|
|
927
|
+
load(attachment).then((url) => {
|
|
928
|
+
if (live) setSrc(url);
|
|
929
|
+
}).catch(() => {
|
|
930
|
+
if (live) setError(true);
|
|
931
|
+
});
|
|
932
|
+
return () => {
|
|
933
|
+
live = false;
|
|
934
|
+
};
|
|
935
|
+
}, [
|
|
936
|
+
attachment,
|
|
937
|
+
load,
|
|
938
|
+
attempt
|
|
939
|
+
]);
|
|
940
|
+
const label = attachment.name ?? labels.image;
|
|
941
|
+
if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
942
|
+
type: "button",
|
|
943
|
+
style: styles$3.error,
|
|
944
|
+
onClick: retry,
|
|
945
|
+
children: labels.loadFailed
|
|
946
|
+
});
|
|
947
|
+
const box = fit === void 0 ? styles$3.tile : {
|
|
948
|
+
width: fit.width,
|
|
949
|
+
height: fit.height
|
|
950
|
+
};
|
|
951
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
952
|
+
type: "button",
|
|
953
|
+
style: {
|
|
954
|
+
...styles$3.frame,
|
|
955
|
+
...box
|
|
956
|
+
},
|
|
957
|
+
title: labels.open,
|
|
958
|
+
"aria-label": labels.openNamed(label),
|
|
959
|
+
onClick: () => {
|
|
960
|
+
if (src !== null) setOpen(true);
|
|
961
|
+
},
|
|
962
|
+
children: src === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
963
|
+
style: styles$3.loading,
|
|
964
|
+
children: labels.loading
|
|
965
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
966
|
+
src,
|
|
967
|
+
alt: label,
|
|
968
|
+
style: {
|
|
969
|
+
...styles$3.img,
|
|
970
|
+
objectPosition: fit?.objectPosition
|
|
971
|
+
}
|
|
972
|
+
})
|
|
973
|
+
}), open && src !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImageLightbox, {
|
|
974
|
+
src,
|
|
975
|
+
alt: label,
|
|
976
|
+
labels: labels.lightbox,
|
|
977
|
+
onClose: close
|
|
978
|
+
})] });
|
|
979
|
+
}
|
|
980
|
+
/** Wrapping image group: a lone image renders large, several render as 64px
|
|
981
|
+
* square tiles (same rule as the platform gallery). */
|
|
982
|
+
function ImageGallery({ images, load, labels }) {
|
|
983
|
+
if (images.length === 0) return null;
|
|
984
|
+
const variant = images.length === 1 ? "single" : "tile";
|
|
985
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
986
|
+
style: styles$3.gallery,
|
|
987
|
+
children: images.map((image, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageImage, {
|
|
988
|
+
attachment: image.attachment,
|
|
989
|
+
load,
|
|
990
|
+
variant,
|
|
991
|
+
labels
|
|
992
|
+
}, `${image.attachment.attachmentId}:${index}`))
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
|
|
760
996
|
//#endregion
|
|
761
997
|
//#region src/client/ImageGenerateToolview.tsx
|
|
762
998
|
/** Logical RPC channel served by the node half of this plugin. */
|
|
@@ -770,7 +1006,7 @@ const PROMPT_MAX_LENGTH$1 = 60;
|
|
|
770
1006
|
* @param payload - channel-owned request payload.
|
|
771
1007
|
* @returns the success value, cast by the caller to the endpoint's shape.
|
|
772
1008
|
*/
|
|
773
|
-
async function callSubscriptionsAuth(rpc, endpoint, payload) {
|
|
1009
|
+
async function callSubscriptionsAuth$1(rpc, endpoint, payload) {
|
|
774
1010
|
const result = await rpc.call(SUBSCRIPTIONS_AUTH_CHANNEL$1, endpoint, payload);
|
|
775
1011
|
if (!result.ok) throw new Error(result.error.message);
|
|
776
1012
|
return result.value;
|
|
@@ -781,7 +1017,7 @@ async function callSubscriptionsAuth(rpc, endpoint, payload) {
|
|
|
781
1017
|
* @returns loader resolving an attachment ref to a data URL.
|
|
782
1018
|
*/
|
|
783
1019
|
function createImageLoader(rpc) {
|
|
784
|
-
return (attachment) => callSubscriptionsAuth(rpc, "image", { ...attachment }).then((result) => `data:${result.mediaType};base64,${result.dataBase64}`);
|
|
1020
|
+
return (attachment) => callSubscriptionsAuth$1(rpc, "image", { ...attachment }).then((result) => `data:${result.mediaType};base64,${result.dataBase64}`);
|
|
785
1021
|
}
|
|
786
1022
|
/**
|
|
787
1023
|
* English-dictionary fallback for a missing locale seat (standalone renders);
|
|
@@ -790,7 +1026,7 @@ function createImageLoader(rpc) {
|
|
|
790
1026
|
* @param params - `{name}` template params.
|
|
791
1027
|
* @returns the template with params substituted.
|
|
792
1028
|
*/
|
|
793
|
-
function fallbackTranslate$
|
|
1029
|
+
function fallbackTranslate$2(key, params) {
|
|
794
1030
|
let text = en[key];
|
|
795
1031
|
for (const [name, value] of Object.entries(params ?? {})) text = text.replaceAll(`{${name}}`, String(value));
|
|
796
1032
|
return text;
|
|
@@ -830,7 +1066,7 @@ function resultImages(block) {
|
|
|
830
1066
|
for (const part of block.content) if (part.type === "image") images.push({ attachment: part.attachment });
|
|
831
1067
|
return images;
|
|
832
1068
|
}
|
|
833
|
-
const styles$
|
|
1069
|
+
const styles$2 = {
|
|
834
1070
|
container: {
|
|
835
1071
|
display: "flex",
|
|
836
1072
|
flexDirection: "column",
|
|
@@ -884,7 +1120,7 @@ const styles$1 = {
|
|
|
884
1120
|
*/
|
|
885
1121
|
function ImageGenerateToolview(props) {
|
|
886
1122
|
const { block, load } = props;
|
|
887
|
-
const t = props.t ?? fallbackTranslate$
|
|
1123
|
+
const t = props.t ?? fallbackTranslate$2;
|
|
888
1124
|
if (block === void 0) return null;
|
|
889
1125
|
const settled = "kind" in block;
|
|
890
1126
|
const title = `image_generate: ${derivePrompt$1((settled ? block.call?.argsRaw : block.argsRaw) ?? "")}`;
|
|
@@ -902,34 +1138,33 @@ function ImageGenerateToolview(props) {
|
|
|
902
1138
|
}
|
|
903
1139
|
};
|
|
904
1140
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
905
|
-
style: styles$
|
|
1141
|
+
style: styles$2.container,
|
|
906
1142
|
children: [
|
|
907
1143
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
908
|
-
style: styles$
|
|
1144
|
+
style: styles$2.row,
|
|
909
1145
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
910
|
-
style: styles$
|
|
1146
|
+
style: styles$2.icon,
|
|
911
1147
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconSparkle16, { size: 14 })
|
|
912
1148
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
913
|
-
style: styles$
|
|
1149
|
+
style: styles$2.title,
|
|
914
1150
|
children: title
|
|
915
1151
|
})]
|
|
916
1152
|
}),
|
|
917
1153
|
!settled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
918
|
-
style: styles$
|
|
1154
|
+
style: styles$2.subtle,
|
|
919
1155
|
children: t("generating")
|
|
920
1156
|
}),
|
|
921
1157
|
settled && block.isError && text !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
922
|
-
style: styles$
|
|
1158
|
+
style: styles$2.error,
|
|
923
1159
|
children: text.split("\n", 1)[0]
|
|
924
1160
|
}),
|
|
925
|
-
settled && !block.isError && images.length > 0 && load !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1161
|
+
settled && !block.isError && images.length > 0 && load !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImageGallery, {
|
|
926
1162
|
images,
|
|
927
1163
|
load,
|
|
928
|
-
align: "start",
|
|
929
1164
|
labels
|
|
930
1165
|
}),
|
|
931
1166
|
settled && !block.isError && images.length === 0 && text !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
932
|
-
style: styles$
|
|
1167
|
+
style: styles$2.output,
|
|
933
1168
|
children: text
|
|
934
1169
|
})
|
|
935
1170
|
]
|
|
@@ -958,7 +1193,7 @@ function createVideoLoader(rpc) {
|
|
|
958
1193
|
* English-dictionary fallback for a missing locale seat (standalone renders);
|
|
959
1194
|
* the framework always supplies the namespace-bound one.
|
|
960
1195
|
*/
|
|
961
|
-
function fallbackTranslate(key, params) {
|
|
1196
|
+
function fallbackTranslate$1(key, params) {
|
|
962
1197
|
let text = en[key];
|
|
963
1198
|
for (const [name, value] of Object.entries(params ?? {})) text = text.replaceAll(`{${name}}`, String(value));
|
|
964
1199
|
return text;
|
|
@@ -1015,7 +1250,7 @@ function base64Bytes(dataBase64) {
|
|
|
1015
1250
|
for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index);
|
|
1016
1251
|
return bytes;
|
|
1017
1252
|
}
|
|
1018
|
-
const styles = {
|
|
1253
|
+
const styles$1 = {
|
|
1019
1254
|
container: {
|
|
1020
1255
|
display: "flex",
|
|
1021
1256
|
flexDirection: "column",
|
|
@@ -1076,7 +1311,7 @@ const styles = {
|
|
|
1076
1311
|
*/
|
|
1077
1312
|
function VideoGenerateToolview(props) {
|
|
1078
1313
|
const { block, loadVideo } = props;
|
|
1079
|
-
const t = props.t ?? fallbackTranslate;
|
|
1314
|
+
const t = props.t ?? fallbackTranslate$1;
|
|
1080
1315
|
const settled = block !== void 0 && "kind" in block;
|
|
1081
1316
|
const isError = settled && block.isError;
|
|
1082
1317
|
const fileName = block !== void 0 && settled && !isError ? resolveFileName(block) : void 0;
|
|
@@ -1109,48 +1344,285 @@ function VideoGenerateToolview(props) {
|
|
|
1109
1344
|
const title = `video_generate: ${derivePrompt((settled ? block.call?.argsRaw : block.argsRaw) ?? "")}`;
|
|
1110
1345
|
const text = settled ? resultText(block) : "";
|
|
1111
1346
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1112
|
-
style: styles.container,
|
|
1347
|
+
style: styles$1.container,
|
|
1113
1348
|
children: [
|
|
1114
1349
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1115
|
-
style: styles.row,
|
|
1350
|
+
style: styles$1.row,
|
|
1116
1351
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1117
|
-
style: styles.icon,
|
|
1352
|
+
style: styles$1.icon,
|
|
1118
1353
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconSparkle16, { size: 14 })
|
|
1119
1354
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1120
|
-
style: styles.title,
|
|
1355
|
+
style: styles$1.title,
|
|
1121
1356
|
children: title
|
|
1122
1357
|
})]
|
|
1123
1358
|
}),
|
|
1124
1359
|
!settled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1125
|
-
style: styles.subtle,
|
|
1360
|
+
style: styles$1.subtle,
|
|
1126
1361
|
children: t("generatingVideo")
|
|
1127
1362
|
}),
|
|
1128
1363
|
settled && isError && text !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1129
|
-
style: styles.error,
|
|
1364
|
+
style: styles$1.error,
|
|
1130
1365
|
children: text.split("\n", 1)[0]
|
|
1131
1366
|
}),
|
|
1132
1367
|
settled && !isError && fileName !== void 0 && load.phase === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1133
|
-
style: styles.subtle,
|
|
1368
|
+
style: styles$1.subtle,
|
|
1134
1369
|
children: t("videoLoading")
|
|
1135
1370
|
}),
|
|
1136
1371
|
settled && !isError && fileName !== void 0 && load.phase === "failed" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1137
|
-
style: styles.error,
|
|
1372
|
+
style: styles$1.error,
|
|
1138
1373
|
children: t("videoLoadFailed", { message: load.message })
|
|
1139
1374
|
}),
|
|
1140
1375
|
settled && !isError && fileName !== void 0 && load.phase === "ready" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("video", {
|
|
1141
|
-
style: styles.video,
|
|
1376
|
+
style: styles$1.video,
|
|
1142
1377
|
src: load.url,
|
|
1143
1378
|
controls: true,
|
|
1144
1379
|
preload: "metadata"
|
|
1145
1380
|
}),
|
|
1146
1381
|
settled && !isError && fileName === void 0 && text !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1147
|
-
style: styles.output,
|
|
1382
|
+
style: styles$1.output,
|
|
1148
1383
|
children: text
|
|
1149
1384
|
})
|
|
1150
1385
|
]
|
|
1151
1386
|
});
|
|
1152
1387
|
}
|
|
1153
1388
|
|
|
1389
|
+
//#endregion
|
|
1390
|
+
//#region src/client/SpeedSelect.tsx
|
|
1391
|
+
/**
|
|
1392
|
+
* The `loadSpeed` half of the inject face: the plugin's own speed state plus
|
|
1393
|
+
* the host's current model selection (the visibility gate). A model-RPC
|
|
1394
|
+
* failure throws rather than answering "hidden" — the caller keeps its last
|
|
1395
|
+
* known state, so a transient failure never locks the toggle away.
|
|
1396
|
+
*
|
|
1397
|
+
* `sessionId` is a plain string: slot and command contexts brand it through
|
|
1398
|
+
* different dsh-session copies, and only the API-client boundary needs one.
|
|
1399
|
+
*/
|
|
1400
|
+
function createSpeedLoader(connection, sessionId) {
|
|
1401
|
+
return async () => {
|
|
1402
|
+
const state = await callSubscriptionsAuth(connection.rpc, "speed", { sessionId });
|
|
1403
|
+
const { result } = await connection.api.sessions.models({ sessionId });
|
|
1404
|
+
if (!result.ok) throw new Error(`session.models failed: ${result.error.code}: ${result.error.message}`);
|
|
1405
|
+
const current = result.value.current;
|
|
1406
|
+
return {
|
|
1407
|
+
visible: current !== null && current.provider === "codex" && state.fastModels.includes(current.model),
|
|
1408
|
+
tier: state.tier
|
|
1409
|
+
};
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
/** The `setSpeed` half of the inject face: boolean outcome for the component's busy state. */
|
|
1413
|
+
function createSpeedSetter(connection, sessionId) {
|
|
1414
|
+
return (tier) => callSubscriptionsAuth(connection.rpc, "setSpeed", {
|
|
1415
|
+
sessionId,
|
|
1416
|
+
tier
|
|
1417
|
+
}).then(() => true, () => false);
|
|
1418
|
+
}
|
|
1419
|
+
/** English-dictionary fallback for a missing inject `t` (standalone renders). */
|
|
1420
|
+
function fallbackTranslate(key) {
|
|
1421
|
+
return en[key];
|
|
1422
|
+
}
|
|
1423
|
+
const TIERS = ["standard", "fast"];
|
|
1424
|
+
/**
|
|
1425
|
+
* The composer Speed control: a trigger reading `速度 · 快速`/`速度 · 标准`
|
|
1426
|
+
* that opens a two-row menu (standard/fast with descriptions, check mark on
|
|
1427
|
+
* the current tier). Mount and every open reload the host state so a model
|
|
1428
|
+
* switch made since the last open self-corrects.
|
|
1429
|
+
*/
|
|
1430
|
+
/** How often the control re-reads the host state (model switches arrive only by asking). */
|
|
1431
|
+
const POLL_INTERVAL_MS = 3e3;
|
|
1432
|
+
/**
|
|
1433
|
+
* The composer Speed control: a trigger reading `速度 · 快速`/`速度 · 标准`
|
|
1434
|
+
* that opens a two-row menu (standard/fast with descriptions, check mark on
|
|
1435
|
+
* the current tier). The host pushes nothing on a model switch, so the
|
|
1436
|
+
* control re-reads on a slow poll with a single-flight guard; a failed read
|
|
1437
|
+
* keeps the last known state, so a transient RPC failure can never lock the
|
|
1438
|
+
* toggle away (the earlier mount-only load had no recovery path).
|
|
1439
|
+
*/
|
|
1440
|
+
function SpeedSelect({ loadSpeed, setSpeed, t }) {
|
|
1441
|
+
const translate = t ?? fallbackTranslate;
|
|
1442
|
+
const [state, setState] = (0, react.useState)(null);
|
|
1443
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
1444
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
1445
|
+
const rootRef = (0, react.useRef)(null);
|
|
1446
|
+
const loadRef = (0, react.useRef)(loadSpeed);
|
|
1447
|
+
loadRef.current = loadSpeed;
|
|
1448
|
+
(0, react.useEffect)(() => {
|
|
1449
|
+
if (loadRef.current === void 0) return;
|
|
1450
|
+
let cancelled = false;
|
|
1451
|
+
let inflight = false;
|
|
1452
|
+
const reload = () => {
|
|
1453
|
+
const load = loadRef.current;
|
|
1454
|
+
if (load === void 0 || inflight) return;
|
|
1455
|
+
inflight = true;
|
|
1456
|
+
load().then((loaded) => {
|
|
1457
|
+
if (!cancelled) setState(loaded);
|
|
1458
|
+
}, () => {}).finally(() => {
|
|
1459
|
+
inflight = false;
|
|
1460
|
+
});
|
|
1461
|
+
};
|
|
1462
|
+
reload();
|
|
1463
|
+
const timer = setInterval(reload, POLL_INTERVAL_MS);
|
|
1464
|
+
return () => {
|
|
1465
|
+
cancelled = true;
|
|
1466
|
+
clearInterval(timer);
|
|
1467
|
+
};
|
|
1468
|
+
}, []);
|
|
1469
|
+
(0, react.useEffect)(() => {
|
|
1470
|
+
if (!open) return;
|
|
1471
|
+
const closeOutside = (event) => {
|
|
1472
|
+
if (!rootRef.current?.contains(event.target)) setOpen(false);
|
|
1473
|
+
};
|
|
1474
|
+
document.addEventListener("mousedown", closeOutside);
|
|
1475
|
+
return () => {
|
|
1476
|
+
document.removeEventListener("mousedown", closeOutside);
|
|
1477
|
+
};
|
|
1478
|
+
}, [open]);
|
|
1479
|
+
if (loadSpeed === void 0 || setSpeed === void 0 || state === null || !state.visible) return null;
|
|
1480
|
+
const choose = (tier) => {
|
|
1481
|
+
if (busy) return;
|
|
1482
|
+
if (tier === state.tier) {
|
|
1483
|
+
setOpen(false);
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
setBusy(true);
|
|
1487
|
+
setSpeed(tier).then((ok) => {
|
|
1488
|
+
setBusy(false);
|
|
1489
|
+
if (ok) {
|
|
1490
|
+
setState({
|
|
1491
|
+
visible: true,
|
|
1492
|
+
tier
|
|
1493
|
+
});
|
|
1494
|
+
setOpen(false);
|
|
1495
|
+
}
|
|
1496
|
+
});
|
|
1497
|
+
};
|
|
1498
|
+
const show = () => {
|
|
1499
|
+
setOpen(true);
|
|
1500
|
+
const load = loadRef.current;
|
|
1501
|
+
if (load === void 0) return;
|
|
1502
|
+
load().then(setState, () => {});
|
|
1503
|
+
};
|
|
1504
|
+
const tierName = (tier) => translate(tier === "fast" ? "speedFast" : "speedStandard");
|
|
1505
|
+
const tierDescription = (tier) => translate(tier === "fast" ? "speedFastDescription" : "speedStandardDescription");
|
|
1506
|
+
const triggerLabel = `${translate("speed")} · ${tierName(state.tier)}`;
|
|
1507
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1508
|
+
ref: rootRef,
|
|
1509
|
+
style: styles.root,
|
|
1510
|
+
onKeyDown: (event) => {
|
|
1511
|
+
if (event.key === "Escape" && open) {
|
|
1512
|
+
event.preventDefault();
|
|
1513
|
+
setOpen(false);
|
|
1514
|
+
}
|
|
1515
|
+
},
|
|
1516
|
+
children: [open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1517
|
+
style: styles.menu,
|
|
1518
|
+
role: "menu",
|
|
1519
|
+
"aria-label": translate("speed"),
|
|
1520
|
+
children: TIERS.map((tier) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1521
|
+
type: "button",
|
|
1522
|
+
role: "menuitemradio",
|
|
1523
|
+
"aria-checked": tier === state.tier,
|
|
1524
|
+
style: styles.item,
|
|
1525
|
+
disabled: busy,
|
|
1526
|
+
onClick: () => {
|
|
1527
|
+
choose(tier);
|
|
1528
|
+
},
|
|
1529
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1530
|
+
style: styles.itemCheck,
|
|
1531
|
+
children: tier === state.tier ? "✓" : ""
|
|
1532
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1533
|
+
style: styles.itemText,
|
|
1534
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1535
|
+
style: styles.itemName,
|
|
1536
|
+
children: tierName(tier)
|
|
1537
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1538
|
+
style: styles.itemDescription,
|
|
1539
|
+
children: tierDescription(tier)
|
|
1540
|
+
})]
|
|
1541
|
+
})]
|
|
1542
|
+
}, tier))
|
|
1543
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1544
|
+
type: "button",
|
|
1545
|
+
style: styles.trigger,
|
|
1546
|
+
"aria-haspopup": "menu",
|
|
1547
|
+
"aria-expanded": open,
|
|
1548
|
+
title: triggerLabel,
|
|
1549
|
+
disabled: busy,
|
|
1550
|
+
onClick: () => {
|
|
1551
|
+
if (open) setOpen(false);
|
|
1552
|
+
else show();
|
|
1553
|
+
},
|
|
1554
|
+
children: triggerLabel
|
|
1555
|
+
})]
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
const styles = {
|
|
1559
|
+
root: {
|
|
1560
|
+
position: "relative",
|
|
1561
|
+
display: "inline-flex"
|
|
1562
|
+
},
|
|
1563
|
+
trigger: {
|
|
1564
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
1565
|
+
borderRadius: 8,
|
|
1566
|
+
background: "transparent",
|
|
1567
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
1568
|
+
font: "inherit",
|
|
1569
|
+
fontSize: 12,
|
|
1570
|
+
lineHeight: "18px",
|
|
1571
|
+
padding: "2px 8px",
|
|
1572
|
+
cursor: "pointer",
|
|
1573
|
+
whiteSpace: "nowrap"
|
|
1574
|
+
},
|
|
1575
|
+
menu: {
|
|
1576
|
+
position: "absolute",
|
|
1577
|
+
bottom: "100%",
|
|
1578
|
+
right: 0,
|
|
1579
|
+
marginBottom: 4,
|
|
1580
|
+
minWidth: 180,
|
|
1581
|
+
padding: 4,
|
|
1582
|
+
zIndex: 20,
|
|
1583
|
+
background: "var(--dsw-alias-bg-layer-1)",
|
|
1584
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
1585
|
+
borderRadius: 8,
|
|
1586
|
+
display: "flex",
|
|
1587
|
+
flexDirection: "column",
|
|
1588
|
+
gap: 2
|
|
1589
|
+
},
|
|
1590
|
+
item: {
|
|
1591
|
+
display: "flex",
|
|
1592
|
+
alignItems: "flex-start",
|
|
1593
|
+
gap: 6,
|
|
1594
|
+
width: "100%",
|
|
1595
|
+
border: "none",
|
|
1596
|
+
borderRadius: 6,
|
|
1597
|
+
background: "transparent",
|
|
1598
|
+
padding: "6px 8px",
|
|
1599
|
+
cursor: "pointer",
|
|
1600
|
+
font: "inherit",
|
|
1601
|
+
textAlign: "left"
|
|
1602
|
+
},
|
|
1603
|
+
itemCheck: {
|
|
1604
|
+
width: 14,
|
|
1605
|
+
flexShrink: 0,
|
|
1606
|
+
fontSize: 12,
|
|
1607
|
+
lineHeight: "18px",
|
|
1608
|
+
color: "var(--dsw-alias-label-primary)"
|
|
1609
|
+
},
|
|
1610
|
+
itemText: {
|
|
1611
|
+
display: "flex",
|
|
1612
|
+
flexDirection: "column"
|
|
1613
|
+
},
|
|
1614
|
+
itemName: {
|
|
1615
|
+
fontSize: 12,
|
|
1616
|
+
lineHeight: "18px",
|
|
1617
|
+
color: "var(--dsw-alias-label-primary)"
|
|
1618
|
+
},
|
|
1619
|
+
itemDescription: {
|
|
1620
|
+
fontSize: 11,
|
|
1621
|
+
lineHeight: "16px",
|
|
1622
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
1623
|
+
}
|
|
1624
|
+
};
|
|
1625
|
+
|
|
1154
1626
|
//#endregion
|
|
1155
1627
|
//#region src/client/index.ts
|
|
1156
1628
|
/** Dictionary namespace owned by this plugin. */
|
|
@@ -1203,6 +1675,46 @@ function apply(ctx) {
|
|
|
1203
1675
|
locale: NS,
|
|
1204
1676
|
inject: videoToolviewInjected
|
|
1205
1677
|
}, VideoGenerateToolview));
|
|
1678
|
+
ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
|
|
1679
|
+
name: "conversation.input.right",
|
|
1680
|
+
id: "codex-speed",
|
|
1681
|
+
order: 0,
|
|
1682
|
+
locale: NS,
|
|
1683
|
+
inject: (sessionId) => ({
|
|
1684
|
+
loadSpeed: createSpeedLoader(connection, sessionId),
|
|
1685
|
+
setSpeed: createSpeedSetter(connection, sessionId)
|
|
1686
|
+
})
|
|
1687
|
+
}, SpeedSelect));
|
|
1688
|
+
ctx.inject(["commandUi"], (scope) => {
|
|
1689
|
+
const command = scope.get("commandUi");
|
|
1690
|
+
scope.effect(() => command.register({
|
|
1691
|
+
name: "fast",
|
|
1692
|
+
description: t("commandFast"),
|
|
1693
|
+
available: () => true,
|
|
1694
|
+
ui: {
|
|
1695
|
+
kind: "popupSelect",
|
|
1696
|
+
options: async (session) => {
|
|
1697
|
+
const state = await createSpeedLoader(connection, session.sessionId)();
|
|
1698
|
+
if (!state.visible) throw new Error(t("commandFastUnavailable"));
|
|
1699
|
+
return [{
|
|
1700
|
+
id: "standard",
|
|
1701
|
+
label: t("speedStandard"),
|
|
1702
|
+
detail: t("speedStandardDescription")
|
|
1703
|
+
}, {
|
|
1704
|
+
id: "fast",
|
|
1705
|
+
label: t("speedFast"),
|
|
1706
|
+
detail: t("speedFastDescription")
|
|
1707
|
+
}].map((option) => ({
|
|
1708
|
+
...option,
|
|
1709
|
+
active: option.id === state.tier
|
|
1710
|
+
}));
|
|
1711
|
+
},
|
|
1712
|
+
onSelect: async (option, session) => {
|
|
1713
|
+
await createSpeedSetter(connection, session.sessionId)(option.id);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
}), "dsh-plugin-subscriptions: /fast contribution");
|
|
1717
|
+
});
|
|
1206
1718
|
}
|
|
1207
1719
|
|
|
1208
1720
|
//#endregion
|