dsh-plugin-subscriptions 0.4.1 → 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 +9 -5
- package/README.zh.md +9 -5
- package/lib/auth/claude-code-creds.d.ts +23 -0
- package/lib/auth/claude-code-creds.js +167 -0
- package/lib/auth/pkce.js +1 -1
- 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 +8 -2
- 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 +590 -74
- package/lib/client.js.map +1 -1
- package/lib/index.js +504 -90
- package/lib/providers/catalog-store.js +8 -0
- package/lib/providers/claude.d.ts +35 -3
- package/lib/providers/claude.js +193 -29
- package/lib/providers/codex.d.ts +27 -0
- package/lib/providers/codex.js +66 -20
- package/lib/providers/common.d.ts +4 -0
- package/lib/tools/image-generate.js +3 -10
- package/package.json +3 -1
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,8 +530,12 @@ function SubscriptionsSection(props) {
|
|
|
517
530
|
if (rpc === void 0) return;
|
|
518
531
|
setProviderError(provider, void 0);
|
|
519
532
|
try {
|
|
520
|
-
const response = await callSubscriptionsAuth
|
|
521
|
-
if (typeof response.authorizeUrl
|
|
533
|
+
const response = await callSubscriptionsAuth(rpc, "login", { provider });
|
|
534
|
+
if (typeof response.authorizeUrl === "string" && response.authorizeUrl === "") {
|
|
535
|
+
await refresh();
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (typeof response.authorizeUrl !== "string") throw new SubscriptionsAuthError(t("loginMissingUrl"));
|
|
522
539
|
window.open(response.authorizeUrl, "_blank", "noopener");
|
|
523
540
|
if (!mountedRef.current) return;
|
|
524
541
|
setStatuses((prev) => ({
|
|
@@ -543,7 +560,7 @@ function SubscriptionsSection(props) {
|
|
|
543
560
|
if (rpc === void 0) return;
|
|
544
561
|
stopPolling(provider);
|
|
545
562
|
try {
|
|
546
|
-
await callSubscriptionsAuth
|
|
563
|
+
await callSubscriptionsAuth(rpc, "cancel", { provider });
|
|
547
564
|
} catch (error) {
|
|
548
565
|
setProviderError(provider, messageOf(error));
|
|
549
566
|
}
|
|
@@ -560,7 +577,7 @@ function SubscriptionsSection(props) {
|
|
|
560
577
|
if (input === "") return;
|
|
561
578
|
setProviderError(provider, void 0);
|
|
562
579
|
try {
|
|
563
|
-
await callSubscriptionsAuth
|
|
580
|
+
await callSubscriptionsAuth(rpc, "manual", {
|
|
564
581
|
provider,
|
|
565
582
|
input
|
|
566
583
|
});
|
|
@@ -583,7 +600,7 @@ function SubscriptionsSection(props) {
|
|
|
583
600
|
if (!window.confirm(t("logoutConfirm", { provider: name }))) return;
|
|
584
601
|
setProviderError(provider, void 0);
|
|
585
602
|
try {
|
|
586
|
-
await callSubscriptionsAuth
|
|
603
|
+
await callSubscriptionsAuth(rpc, "logout", { provider });
|
|
587
604
|
} catch (error) {
|
|
588
605
|
setProviderError(provider, messageOf(error));
|
|
589
606
|
}
|
|
@@ -595,13 +612,13 @@ function SubscriptionsSection(props) {
|
|
|
595
612
|
refresh
|
|
596
613
|
]);
|
|
597
614
|
if (rpc === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
598
|
-
style: styles$
|
|
615
|
+
style: styles$4.intro,
|
|
599
616
|
children: t("unavailable")
|
|
600
617
|
});
|
|
601
618
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
602
|
-
style: styles$
|
|
619
|
+
style: styles$4.section,
|
|
603
620
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
604
|
-
style: styles$
|
|
621
|
+
style: styles$4.intro,
|
|
605
622
|
children: t("intro")
|
|
606
623
|
}), PROVIDERS.map(({ id, name }) => {
|
|
607
624
|
const status = statuses[id];
|
|
@@ -610,36 +627,36 @@ function SubscriptionsSection(props) {
|
|
|
610
627
|
const usageError = usageErrors[id];
|
|
611
628
|
const showUsage = status?.loggedIn === true && usage?.supported !== false && (usage !== void 0 || usageError !== void 0 || usageLoading[id] === true);
|
|
612
629
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
613
|
-
style: styles$
|
|
630
|
+
style: styles$4.card,
|
|
614
631
|
children: [
|
|
615
632
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
616
|
-
style: styles$
|
|
633
|
+
style: styles$4.cardHeader,
|
|
617
634
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
|
|
618
|
-
...styles$
|
|
635
|
+
...styles$4.dot,
|
|
619
636
|
background: dotColor(status)
|
|
620
637
|
} }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
621
|
-
style: styles$
|
|
638
|
+
style: styles$4.name,
|
|
622
639
|
children: name
|
|
623
640
|
})]
|
|
624
641
|
}),
|
|
625
642
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
626
|
-
style: styles$
|
|
643
|
+
style: styles$4.statusLine,
|
|
627
644
|
children: statusText(t, status)
|
|
628
645
|
}),
|
|
629
646
|
status?.detail !== void 0 && status.detail !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
630
|
-
style: styles$
|
|
647
|
+
style: styles$4.statusLine,
|
|
631
648
|
children: status.detail
|
|
632
649
|
}),
|
|
633
650
|
errors[id] !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
634
|
-
style: styles$
|
|
651
|
+
style: styles$4.errorLine,
|
|
635
652
|
children: errors[id]
|
|
636
653
|
}),
|
|
637
654
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
638
|
-
style: styles$
|
|
655
|
+
style: styles$4.actions,
|
|
639
656
|
children: [
|
|
640
657
|
!busy && status?.loggedIn !== true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
641
658
|
type: "button",
|
|
642
|
-
style: styles$
|
|
659
|
+
style: styles$4.button,
|
|
643
660
|
onClick: () => {
|
|
644
661
|
login(id);
|
|
645
662
|
},
|
|
@@ -647,7 +664,7 @@ function SubscriptionsSection(props) {
|
|
|
647
664
|
}),
|
|
648
665
|
busy && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
649
666
|
type: "button",
|
|
650
|
-
style: styles$
|
|
667
|
+
style: styles$4.button,
|
|
651
668
|
onClick: () => {
|
|
652
669
|
cancel(id);
|
|
653
670
|
},
|
|
@@ -655,7 +672,7 @@ function SubscriptionsSection(props) {
|
|
|
655
672
|
}),
|
|
656
673
|
status?.loggedIn === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
657
674
|
type: "button",
|
|
658
|
-
style: styles$
|
|
675
|
+
style: styles$4.button,
|
|
659
676
|
onClick: () => {
|
|
660
677
|
logout(id, name);
|
|
661
678
|
},
|
|
@@ -664,23 +681,23 @@ function SubscriptionsSection(props) {
|
|
|
664
681
|
]
|
|
665
682
|
}),
|
|
666
683
|
showUsage && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
667
|
-
style: styles$
|
|
684
|
+
style: styles$4.usage,
|
|
668
685
|
children: [
|
|
669
686
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
670
|
-
style: styles$
|
|
687
|
+
style: styles$4.usageHeader,
|
|
671
688
|
children: [
|
|
672
689
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
673
|
-
style: styles$
|
|
690
|
+
style: styles$4.usageTitle,
|
|
674
691
|
children: t("usageTitle")
|
|
675
692
|
}),
|
|
676
693
|
usage?.plan !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
677
|
-
style: styles$
|
|
694
|
+
style: styles$4.usagePlan,
|
|
678
695
|
children: t("usagePlan", { plan: usage.plan })
|
|
679
696
|
}),
|
|
680
697
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
681
698
|
type: "button",
|
|
682
699
|
style: {
|
|
683
|
-
...styles$
|
|
700
|
+
...styles$4.usageRefresh,
|
|
684
701
|
...usageLoading[id] === true ? {
|
|
685
702
|
opacity: .5,
|
|
686
703
|
cursor: "default"
|
|
@@ -695,28 +712,28 @@ function SubscriptionsSection(props) {
|
|
|
695
712
|
]
|
|
696
713
|
}),
|
|
697
714
|
usage === void 0 && usageError === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
698
|
-
style: styles$
|
|
715
|
+
style: styles$4.statusLine,
|
|
699
716
|
children: t("usageLoading")
|
|
700
717
|
}),
|
|
701
718
|
usageError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
702
|
-
style: styles$
|
|
719
|
+
style: styles$4.errorLine,
|
|
703
720
|
children: t("usageError", { message: usageError })
|
|
704
721
|
}),
|
|
705
722
|
usage?.windows !== void 0 && usage.windows.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
706
|
-
style: styles$
|
|
723
|
+
style: styles$4.statusLine,
|
|
707
724
|
children: t("usageEmpty")
|
|
708
725
|
}),
|
|
709
726
|
(usage?.windows ?? []).map((window$1, index) => {
|
|
710
727
|
const percent = Math.min(100, Math.max(0, window$1.usedPercent));
|
|
711
728
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
712
|
-
style: styles$
|
|
729
|
+
style: styles$4.usageRow,
|
|
713
730
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
714
|
-
style: styles$
|
|
731
|
+
style: styles$4.usageMeta,
|
|
715
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() })}`] })]
|
|
716
733
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
717
|
-
style: styles$
|
|
734
|
+
style: styles$4.usageTrack,
|
|
718
735
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
|
|
719
|
-
...styles$
|
|
736
|
+
...styles$4.usageFill,
|
|
720
737
|
width: `${String(percent)}%`,
|
|
721
738
|
background: usageBarColor(percent)
|
|
722
739
|
} })
|
|
@@ -726,11 +743,11 @@ function SubscriptionsSection(props) {
|
|
|
726
743
|
]
|
|
727
744
|
}),
|
|
728
745
|
busy && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
729
|
-
style: styles$
|
|
746
|
+
style: styles$4.manual,
|
|
730
747
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("manualSummary") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
731
|
-
style: styles$
|
|
748
|
+
style: styles$4.manualRow,
|
|
732
749
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
733
|
-
style: styles$
|
|
750
|
+
style: styles$4.manualInput,
|
|
734
751
|
value: manualDrafts[id],
|
|
735
752
|
placeholder: t("manualPlaceholder"),
|
|
736
753
|
onChange: (event) => setManualDrafts((prev) => ({
|
|
@@ -739,7 +756,7 @@ function SubscriptionsSection(props) {
|
|
|
739
756
|
}))
|
|
740
757
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
741
758
|
type: "button",
|
|
742
|
-
style: styles$
|
|
759
|
+
style: styles$4.button,
|
|
743
760
|
onClick: () => {
|
|
744
761
|
submitManual(id);
|
|
745
762
|
},
|
|
@@ -753,6 +770,229 @@ function SubscriptionsSection(props) {
|
|
|
753
770
|
});
|
|
754
771
|
}
|
|
755
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
|
+
|
|
756
996
|
//#endregion
|
|
757
997
|
//#region src/client/ImageGenerateToolview.tsx
|
|
758
998
|
/** Logical RPC channel served by the node half of this plugin. */
|
|
@@ -766,7 +1006,7 @@ const PROMPT_MAX_LENGTH$1 = 60;
|
|
|
766
1006
|
* @param payload - channel-owned request payload.
|
|
767
1007
|
* @returns the success value, cast by the caller to the endpoint's shape.
|
|
768
1008
|
*/
|
|
769
|
-
async function callSubscriptionsAuth(rpc, endpoint, payload) {
|
|
1009
|
+
async function callSubscriptionsAuth$1(rpc, endpoint, payload) {
|
|
770
1010
|
const result = await rpc.call(SUBSCRIPTIONS_AUTH_CHANNEL$1, endpoint, payload);
|
|
771
1011
|
if (!result.ok) throw new Error(result.error.message);
|
|
772
1012
|
return result.value;
|
|
@@ -777,7 +1017,7 @@ async function callSubscriptionsAuth(rpc, endpoint, payload) {
|
|
|
777
1017
|
* @returns loader resolving an attachment ref to a data URL.
|
|
778
1018
|
*/
|
|
779
1019
|
function createImageLoader(rpc) {
|
|
780
|
-
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}`);
|
|
781
1021
|
}
|
|
782
1022
|
/**
|
|
783
1023
|
* English-dictionary fallback for a missing locale seat (standalone renders);
|
|
@@ -786,7 +1026,7 @@ function createImageLoader(rpc) {
|
|
|
786
1026
|
* @param params - `{name}` template params.
|
|
787
1027
|
* @returns the template with params substituted.
|
|
788
1028
|
*/
|
|
789
|
-
function fallbackTranslate$
|
|
1029
|
+
function fallbackTranslate$2(key, params) {
|
|
790
1030
|
let text = en[key];
|
|
791
1031
|
for (const [name, value] of Object.entries(params ?? {})) text = text.replaceAll(`{${name}}`, String(value));
|
|
792
1032
|
return text;
|
|
@@ -826,7 +1066,7 @@ function resultImages(block) {
|
|
|
826
1066
|
for (const part of block.content) if (part.type === "image") images.push({ attachment: part.attachment });
|
|
827
1067
|
return images;
|
|
828
1068
|
}
|
|
829
|
-
const styles$
|
|
1069
|
+
const styles$2 = {
|
|
830
1070
|
container: {
|
|
831
1071
|
display: "flex",
|
|
832
1072
|
flexDirection: "column",
|
|
@@ -880,7 +1120,7 @@ const styles$1 = {
|
|
|
880
1120
|
*/
|
|
881
1121
|
function ImageGenerateToolview(props) {
|
|
882
1122
|
const { block, load } = props;
|
|
883
|
-
const t = props.t ?? fallbackTranslate$
|
|
1123
|
+
const t = props.t ?? fallbackTranslate$2;
|
|
884
1124
|
if (block === void 0) return null;
|
|
885
1125
|
const settled = "kind" in block;
|
|
886
1126
|
const title = `image_generate: ${derivePrompt$1((settled ? block.call?.argsRaw : block.argsRaw) ?? "")}`;
|
|
@@ -898,34 +1138,33 @@ function ImageGenerateToolview(props) {
|
|
|
898
1138
|
}
|
|
899
1139
|
};
|
|
900
1140
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
901
|
-
style: styles$
|
|
1141
|
+
style: styles$2.container,
|
|
902
1142
|
children: [
|
|
903
1143
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
904
|
-
style: styles$
|
|
1144
|
+
style: styles$2.row,
|
|
905
1145
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
906
|
-
style: styles$
|
|
1146
|
+
style: styles$2.icon,
|
|
907
1147
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconSparkle16, { size: 14 })
|
|
908
1148
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
909
|
-
style: styles$
|
|
1149
|
+
style: styles$2.title,
|
|
910
1150
|
children: title
|
|
911
1151
|
})]
|
|
912
1152
|
}),
|
|
913
1153
|
!settled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
914
|
-
style: styles$
|
|
1154
|
+
style: styles$2.subtle,
|
|
915
1155
|
children: t("generating")
|
|
916
1156
|
}),
|
|
917
1157
|
settled && block.isError && text !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
918
|
-
style: styles$
|
|
1158
|
+
style: styles$2.error,
|
|
919
1159
|
children: text.split("\n", 1)[0]
|
|
920
1160
|
}),
|
|
921
|
-
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, {
|
|
922
1162
|
images,
|
|
923
1163
|
load,
|
|
924
|
-
align: "start",
|
|
925
1164
|
labels
|
|
926
1165
|
}),
|
|
927
1166
|
settled && !block.isError && images.length === 0 && text !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
928
|
-
style: styles$
|
|
1167
|
+
style: styles$2.output,
|
|
929
1168
|
children: text
|
|
930
1169
|
})
|
|
931
1170
|
]
|
|
@@ -954,7 +1193,7 @@ function createVideoLoader(rpc) {
|
|
|
954
1193
|
* English-dictionary fallback for a missing locale seat (standalone renders);
|
|
955
1194
|
* the framework always supplies the namespace-bound one.
|
|
956
1195
|
*/
|
|
957
|
-
function fallbackTranslate(key, params) {
|
|
1196
|
+
function fallbackTranslate$1(key, params) {
|
|
958
1197
|
let text = en[key];
|
|
959
1198
|
for (const [name, value] of Object.entries(params ?? {})) text = text.replaceAll(`{${name}}`, String(value));
|
|
960
1199
|
return text;
|
|
@@ -1011,7 +1250,7 @@ function base64Bytes(dataBase64) {
|
|
|
1011
1250
|
for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index);
|
|
1012
1251
|
return bytes;
|
|
1013
1252
|
}
|
|
1014
|
-
const styles = {
|
|
1253
|
+
const styles$1 = {
|
|
1015
1254
|
container: {
|
|
1016
1255
|
display: "flex",
|
|
1017
1256
|
flexDirection: "column",
|
|
@@ -1072,7 +1311,7 @@ const styles = {
|
|
|
1072
1311
|
*/
|
|
1073
1312
|
function VideoGenerateToolview(props) {
|
|
1074
1313
|
const { block, loadVideo } = props;
|
|
1075
|
-
const t = props.t ?? fallbackTranslate;
|
|
1314
|
+
const t = props.t ?? fallbackTranslate$1;
|
|
1076
1315
|
const settled = block !== void 0 && "kind" in block;
|
|
1077
1316
|
const isError = settled && block.isError;
|
|
1078
1317
|
const fileName = block !== void 0 && settled && !isError ? resolveFileName(block) : void 0;
|
|
@@ -1105,48 +1344,285 @@ function VideoGenerateToolview(props) {
|
|
|
1105
1344
|
const title = `video_generate: ${derivePrompt((settled ? block.call?.argsRaw : block.argsRaw) ?? "")}`;
|
|
1106
1345
|
const text = settled ? resultText(block) : "";
|
|
1107
1346
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1108
|
-
style: styles.container,
|
|
1347
|
+
style: styles$1.container,
|
|
1109
1348
|
children: [
|
|
1110
1349
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1111
|
-
style: styles.row,
|
|
1350
|
+
style: styles$1.row,
|
|
1112
1351
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1113
|
-
style: styles.icon,
|
|
1352
|
+
style: styles$1.icon,
|
|
1114
1353
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconSparkle16, { size: 14 })
|
|
1115
1354
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1116
|
-
style: styles.title,
|
|
1355
|
+
style: styles$1.title,
|
|
1117
1356
|
children: title
|
|
1118
1357
|
})]
|
|
1119
1358
|
}),
|
|
1120
1359
|
!settled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1121
|
-
style: styles.subtle,
|
|
1360
|
+
style: styles$1.subtle,
|
|
1122
1361
|
children: t("generatingVideo")
|
|
1123
1362
|
}),
|
|
1124
1363
|
settled && isError && text !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1125
|
-
style: styles.error,
|
|
1364
|
+
style: styles$1.error,
|
|
1126
1365
|
children: text.split("\n", 1)[0]
|
|
1127
1366
|
}),
|
|
1128
1367
|
settled && !isError && fileName !== void 0 && load.phase === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1129
|
-
style: styles.subtle,
|
|
1368
|
+
style: styles$1.subtle,
|
|
1130
1369
|
children: t("videoLoading")
|
|
1131
1370
|
}),
|
|
1132
1371
|
settled && !isError && fileName !== void 0 && load.phase === "failed" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1133
|
-
style: styles.error,
|
|
1372
|
+
style: styles$1.error,
|
|
1134
1373
|
children: t("videoLoadFailed", { message: load.message })
|
|
1135
1374
|
}),
|
|
1136
1375
|
settled && !isError && fileName !== void 0 && load.phase === "ready" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("video", {
|
|
1137
|
-
style: styles.video,
|
|
1376
|
+
style: styles$1.video,
|
|
1138
1377
|
src: load.url,
|
|
1139
1378
|
controls: true,
|
|
1140
1379
|
preload: "metadata"
|
|
1141
1380
|
}),
|
|
1142
1381
|
settled && !isError && fileName === void 0 && text !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1143
|
-
style: styles.output,
|
|
1382
|
+
style: styles$1.output,
|
|
1144
1383
|
children: text
|
|
1145
1384
|
})
|
|
1146
1385
|
]
|
|
1147
1386
|
});
|
|
1148
1387
|
}
|
|
1149
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
|
+
|
|
1150
1626
|
//#endregion
|
|
1151
1627
|
//#region src/client/index.ts
|
|
1152
1628
|
/** Dictionary namespace owned by this plugin. */
|
|
@@ -1199,6 +1675,46 @@ function apply(ctx) {
|
|
|
1199
1675
|
locale: NS,
|
|
1200
1676
|
inject: videoToolviewInjected
|
|
1201
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
|
+
});
|
|
1202
1718
|
}
|
|
1203
1719
|
|
|
1204
1720
|
//#endregion
|