dsh-plugin-subscriptions 0.4.2 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -6
- package/README.zh.md +18 -6
- package/lib/auth/device-flow.d.ts +64 -0
- package/lib/auth/device-flow.js +176 -0
- package/lib/auth/oauth-flow.js +1 -1
- package/lib/auth/rpc.d.ts +21 -2
- package/lib/auth/rpc.js +23 -3
- package/lib/auth/store.d.ts +20 -2
- package/lib/auth/store.js +45 -9
- 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 +10 -1
- package/lib/client/SubscriptionsSection.js +48 -5
- package/lib/client/index.d.ts +1 -0
- package/lib/client/index.js +42 -0
- package/lib/client/locales.d.ts +22 -0
- package/lib/client/locales.js +22 -0
- package/lib/client.js +679 -77
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.js +1868 -183
- package/lib/providers/catalog-store.js +19 -0
- package/lib/providers/claude.d.ts +20 -1
- package/lib/providers/claude.js +58 -31
- package/lib/providers/codex.d.ts +27 -0
- package/lib/providers/codex.js +117 -27
- package/lib/providers/common.d.ts +34 -1
- package/lib/providers/common.js +48 -1
- package/lib/providers/copilot.d.ts +315 -0
- package/lib/providers/copilot.js +786 -0
- package/lib/providers/grok.d.ts +7 -2
- package/lib/providers/grok.js +46 -18
- package/lib/tools/image-generate.js +3 -10
- package/lib/translate/anthropic.d.ts +47 -6
- package/lib/translate/anthropic.js +135 -20
- package/lib/translate/chat-completions.d.ts +120 -0
- package/lib/translate/chat-completions.js +363 -0
- package/lib/translate/responses.d.ts +49 -5
- package/lib/translate/responses.js +40 -7
- package/package.json +6 -3
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. */
|
|
@@ -54,6 +52,10 @@ const en = {
|
|
|
54
52
|
manualPlaceholder: "Paste the callback URL or code",
|
|
55
53
|
submit: "Submit",
|
|
56
54
|
loginMissingUrl: "login answered without an authorizeUrl",
|
|
55
|
+
deviceCodePrompt: "Enter this code on the GitHub verification page:",
|
|
56
|
+
deviceCodeCopy: "Copy code",
|
|
57
|
+
deviceCodeCopied: "Copied",
|
|
58
|
+
deviceCodeOpenPage: "Open GitHub verification page",
|
|
57
59
|
usageTitle: "Usage",
|
|
58
60
|
usageRefresh: "Refresh",
|
|
59
61
|
usageLoading: "Loading usage…",
|
|
@@ -74,7 +76,14 @@ const en = {
|
|
|
74
76
|
imageClose: "Close",
|
|
75
77
|
generatingVideo: "Generating video…",
|
|
76
78
|
videoLoading: "Loading video…",
|
|
77
|
-
videoLoadFailed: "Video failed to load: {message}"
|
|
79
|
+
videoLoadFailed: "Video failed to load: {message}",
|
|
80
|
+
speed: "Speed",
|
|
81
|
+
speedStandard: "Standard",
|
|
82
|
+
speedStandardDescription: "Default speed",
|
|
83
|
+
speedFast: "Fast",
|
|
84
|
+
speedFastDescription: "1.5x speed, more usage",
|
|
85
|
+
commandFast: "Switch the Codex speed tier (Standard/Fast)",
|
|
86
|
+
commandFastUnavailable: "The current model has no fast tier; /fast only works on Codex models whose catalog advertises one"
|
|
78
87
|
};
|
|
79
88
|
/** zh strings, one per {@link en} key. */
|
|
80
89
|
const zh = {
|
|
@@ -96,6 +105,10 @@ const zh = {
|
|
|
96
105
|
manualPlaceholder: "粘贴回调 URL 或授权码",
|
|
97
106
|
submit: "提交",
|
|
98
107
|
loginMissingUrl: "login 响应缺少 authorizeUrl",
|
|
108
|
+
deviceCodePrompt: "在 GitHub 验证页面输入此验证码:",
|
|
109
|
+
deviceCodeCopy: "复制验证码",
|
|
110
|
+
deviceCodeCopied: "已复制",
|
|
111
|
+
deviceCodeOpenPage: "打开 GitHub 验证页面",
|
|
99
112
|
usageTitle: "用量",
|
|
100
113
|
usageRefresh: "刷新",
|
|
101
114
|
usageLoading: "用量加载中…",
|
|
@@ -116,7 +129,14 @@ const zh = {
|
|
|
116
129
|
imageClose: "关闭",
|
|
117
130
|
generatingVideo: "正在生成视频…",
|
|
118
131
|
videoLoading: "视频加载中…",
|
|
119
|
-
videoLoadFailed: "视频加载失败:{message}"
|
|
132
|
+
videoLoadFailed: "视频加载失败:{message}",
|
|
133
|
+
speed: "速度",
|
|
134
|
+
speedStandard: "标准",
|
|
135
|
+
speedStandardDescription: "默认速度",
|
|
136
|
+
speedFast: "快速",
|
|
137
|
+
speedFastDescription: "约 1.5 倍速度,消耗更多用量",
|
|
138
|
+
commandFast: "切换 Codex 速度档(标准/快速)",
|
|
139
|
+
commandFastUnavailable: "当前模型不支持快速档;/fast 仅对目录声明了 fast tier 的 Codex 模型可用"
|
|
120
140
|
};
|
|
121
141
|
|
|
122
142
|
//#endregion
|
|
@@ -124,7 +144,7 @@ const zh = {
|
|
|
124
144
|
/** Logical RPC channel served by the node half of this plugin. */
|
|
125
145
|
const SUBSCRIPTIONS_AUTH_CHANNEL$2 = "/subscriptions-auth";
|
|
126
146
|
/** Poll cadence while a provider login attempt is busy. */
|
|
127
|
-
const POLL_INTERVAL_MS = 2e3;
|
|
147
|
+
const POLL_INTERVAL_MS$1 = 2e3;
|
|
128
148
|
/** Card display metadata, in page order (names are brand names, not translated). */
|
|
129
149
|
const PROVIDERS = [
|
|
130
150
|
{
|
|
@@ -138,18 +158,23 @@ const PROVIDERS = [
|
|
|
138
158
|
{
|
|
139
159
|
id: "grok",
|
|
140
160
|
name: "Grok (X Premium)"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: "copilot",
|
|
164
|
+
name: "GitHub Copilot"
|
|
141
165
|
}
|
|
142
166
|
];
|
|
143
167
|
/** Business error returned by the `/subscriptions-auth` channel (error branch message). */
|
|
144
168
|
var SubscriptionsAuthError = class extends Error {};
|
|
145
169
|
/**
|
|
146
170
|
* Call one `/subscriptions-auth` endpoint and unwrap the business result.
|
|
171
|
+
* Shared by the settings section and the composer Speed toggle.
|
|
147
172
|
* @param rpc - Connection RPC caller.
|
|
148
173
|
* @param endpoint - channel-relative endpoint.
|
|
149
174
|
* @param payload - channel-owned request payload.
|
|
150
175
|
* @returns the success value, cast by the caller to the endpoint's shape.
|
|
151
176
|
*/
|
|
152
|
-
async function callSubscriptionsAuth
|
|
177
|
+
async function callSubscriptionsAuth(rpc, endpoint, payload) {
|
|
153
178
|
let result;
|
|
154
179
|
try {
|
|
155
180
|
result = await rpc.call(SUBSCRIPTIONS_AUTH_CHANNEL$2, endpoint, payload);
|
|
@@ -170,12 +195,12 @@ function messageOf(error) {
|
|
|
170
195
|
* @param params - `{name}` template params.
|
|
171
196
|
* @returns the template with params substituted.
|
|
172
197
|
*/
|
|
173
|
-
function fallbackTranslate$
|
|
198
|
+
function fallbackTranslate$3(key, params) {
|
|
174
199
|
let text = en[key];
|
|
175
200
|
for (const [name, value] of Object.entries(params ?? {})) text = text.replaceAll(`{${name}}`, String(value));
|
|
176
201
|
return text;
|
|
177
202
|
}
|
|
178
|
-
const styles$
|
|
203
|
+
const styles$4 = {
|
|
179
204
|
section: {
|
|
180
205
|
display: "flex",
|
|
181
206
|
flexDirection: "column",
|
|
@@ -337,6 +362,24 @@ const styles$2 = {
|
|
|
337
362
|
lineHeight: "22px",
|
|
338
363
|
background: "var(--dsw-alias-bg-layer-1)",
|
|
339
364
|
color: "var(--dsw-alias-label-primary)"
|
|
365
|
+
},
|
|
366
|
+
deviceCode: {
|
|
367
|
+
marginTop: 4,
|
|
368
|
+
display: "flex",
|
|
369
|
+
flexDirection: "column",
|
|
370
|
+
gap: 6,
|
|
371
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
372
|
+
borderRadius: 8,
|
|
373
|
+
padding: "10px 12px",
|
|
374
|
+
background: "var(--dsw-alias-bg-layer-1)"
|
|
375
|
+
},
|
|
376
|
+
deviceCodeText: {
|
|
377
|
+
fontFamily: "monospace",
|
|
378
|
+
fontSize: 18,
|
|
379
|
+
lineHeight: "24px",
|
|
380
|
+
letterSpacing: 2,
|
|
381
|
+
color: "var(--dsw-alias-label-primary)",
|
|
382
|
+
userSelect: "all"
|
|
340
383
|
}
|
|
341
384
|
};
|
|
342
385
|
/** Status dot color for one provider state. */
|
|
@@ -388,14 +431,18 @@ function usageBarColor(usedPercent) {
|
|
|
388
431
|
*/
|
|
389
432
|
function SubscriptionsSection(props) {
|
|
390
433
|
const { rpc } = props;
|
|
391
|
-
const t = props.t ?? fallbackTranslate$
|
|
434
|
+
const t = props.t ?? fallbackTranslate$3;
|
|
392
435
|
const [statuses, setStatuses] = (0, react.useState)({});
|
|
393
436
|
const [errors, setErrors] = (0, react.useState)({});
|
|
394
437
|
const [manualDrafts, setManualDrafts] = (0, react.useState)({
|
|
395
438
|
codex: "",
|
|
396
439
|
claude: "",
|
|
397
|
-
grok: ""
|
|
440
|
+
grok: "",
|
|
441
|
+
copilot: ""
|
|
398
442
|
});
|
|
443
|
+
/** Pending device-flow codes (copilot), shown while the attempt polls. */
|
|
444
|
+
const [deviceCodes, setDeviceCodes] = (0, react.useState)({});
|
|
445
|
+
const [copiedCode, setCopiedCode] = (0, react.useState)(void 0);
|
|
399
446
|
const [usages, setUsages] = (0, react.useState)({});
|
|
400
447
|
const [usageErrors, setUsageErrors] = (0, react.useState)({});
|
|
401
448
|
const [usageLoading, setUsageLoading] = (0, react.useState)({});
|
|
@@ -424,7 +471,7 @@ function SubscriptionsSection(props) {
|
|
|
424
471
|
if (rpc === void 0) return;
|
|
425
472
|
let response;
|
|
426
473
|
try {
|
|
427
|
-
response = await callSubscriptionsAuth
|
|
474
|
+
response = await callSubscriptionsAuth(rpc, "status", {});
|
|
428
475
|
} catch {
|
|
429
476
|
return;
|
|
430
477
|
}
|
|
@@ -432,14 +479,22 @@ function SubscriptionsSection(props) {
|
|
|
432
479
|
setStatuses(response.providers);
|
|
433
480
|
for (const { id } of PROVIDERS) {
|
|
434
481
|
const status = response.providers[id];
|
|
435
|
-
if (status.loggedIn || !status.busy)
|
|
482
|
+
if (status.loggedIn || !status.busy) {
|
|
483
|
+
stopPolling(id);
|
|
484
|
+
setDeviceCodes((prev) => {
|
|
485
|
+
if (prev[id] === void 0) return prev;
|
|
486
|
+
const next = { ...prev };
|
|
487
|
+
delete next[id];
|
|
488
|
+
return next;
|
|
489
|
+
});
|
|
490
|
+
}
|
|
436
491
|
}
|
|
437
492
|
}, [rpc, stopPolling]);
|
|
438
493
|
const startPolling = (0, react.useCallback)((provider) => {
|
|
439
494
|
if (pollersRef.current.has(provider)) return;
|
|
440
495
|
pollersRef.current.set(provider, setInterval(() => {
|
|
441
496
|
refresh();
|
|
442
|
-
}, POLL_INTERVAL_MS));
|
|
497
|
+
}, POLL_INTERVAL_MS$1));
|
|
443
498
|
}, [refresh]);
|
|
444
499
|
(0, react.useEffect)(() => {
|
|
445
500
|
mountedRef.current = true;
|
|
@@ -464,7 +519,7 @@ function SubscriptionsSection(props) {
|
|
|
464
519
|
[provider]: true
|
|
465
520
|
}));
|
|
466
521
|
try {
|
|
467
|
-
const usage = await callSubscriptionsAuth
|
|
522
|
+
const usage = await callSubscriptionsAuth(rpc, "usage", { provider });
|
|
468
523
|
if (!mountedRef.current) return;
|
|
469
524
|
setUsages((prev) => ({
|
|
470
525
|
...prev,
|
|
@@ -517,13 +572,12 @@ function SubscriptionsSection(props) {
|
|
|
517
572
|
if (rpc === void 0) return;
|
|
518
573
|
setProviderError(provider, void 0);
|
|
519
574
|
try {
|
|
520
|
-
const response = await callSubscriptionsAuth
|
|
575
|
+
const response = await callSubscriptionsAuth(rpc, "login", { provider });
|
|
521
576
|
if (typeof response.authorizeUrl === "string" && response.authorizeUrl === "") {
|
|
522
577
|
await refresh();
|
|
523
578
|
return;
|
|
524
579
|
}
|
|
525
580
|
if (typeof response.authorizeUrl !== "string") throw new SubscriptionsAuthError(t("loginMissingUrl"));
|
|
526
|
-
window.open(response.authorizeUrl, "_blank", "noopener");
|
|
527
581
|
if (!mountedRef.current) return;
|
|
528
582
|
setStatuses((prev) => ({
|
|
529
583
|
...prev,
|
|
@@ -533,6 +587,14 @@ function SubscriptionsSection(props) {
|
|
|
533
587
|
loggedIn: false
|
|
534
588
|
}
|
|
535
589
|
}));
|
|
590
|
+
if (typeof response.userCode === "string" && response.userCode.length > 0) setDeviceCodes((prev) => ({
|
|
591
|
+
...prev,
|
|
592
|
+
[provider]: {
|
|
593
|
+
userCode: response.userCode,
|
|
594
|
+
verificationUrl: response.authorizeUrl
|
|
595
|
+
}
|
|
596
|
+
}));
|
|
597
|
+
else window.open(response.authorizeUrl, "_blank", "noopener");
|
|
536
598
|
startPolling(provider);
|
|
537
599
|
} catch (error) {
|
|
538
600
|
setProviderError(provider, messageOf(error));
|
|
@@ -547,7 +609,7 @@ function SubscriptionsSection(props) {
|
|
|
547
609
|
if (rpc === void 0) return;
|
|
548
610
|
stopPolling(provider);
|
|
549
611
|
try {
|
|
550
|
-
await callSubscriptionsAuth
|
|
612
|
+
await callSubscriptionsAuth(rpc, "cancel", { provider });
|
|
551
613
|
} catch (error) {
|
|
552
614
|
setProviderError(provider, messageOf(error));
|
|
553
615
|
}
|
|
@@ -564,7 +626,7 @@ function SubscriptionsSection(props) {
|
|
|
564
626
|
if (input === "") return;
|
|
565
627
|
setProviderError(provider, void 0);
|
|
566
628
|
try {
|
|
567
|
-
await callSubscriptionsAuth
|
|
629
|
+
await callSubscriptionsAuth(rpc, "manual", {
|
|
568
630
|
provider,
|
|
569
631
|
input
|
|
570
632
|
});
|
|
@@ -587,7 +649,7 @@ function SubscriptionsSection(props) {
|
|
|
587
649
|
if (!window.confirm(t("logoutConfirm", { provider: name }))) return;
|
|
588
650
|
setProviderError(provider, void 0);
|
|
589
651
|
try {
|
|
590
|
-
await callSubscriptionsAuth
|
|
652
|
+
await callSubscriptionsAuth(rpc, "logout", { provider });
|
|
591
653
|
} catch (error) {
|
|
592
654
|
setProviderError(provider, messageOf(error));
|
|
593
655
|
}
|
|
@@ -598,52 +660,62 @@ function SubscriptionsSection(props) {
|
|
|
598
660
|
setProviderError,
|
|
599
661
|
refresh
|
|
600
662
|
]);
|
|
663
|
+
const copyDeviceCode = (0, react.useCallback)((provider, userCode) => {
|
|
664
|
+
navigator.clipboard?.writeText(userCode).then(() => {
|
|
665
|
+
if (!mountedRef.current) return;
|
|
666
|
+
setCopiedCode(provider);
|
|
667
|
+
setTimeout(() => {
|
|
668
|
+
if (mountedRef.current) setCopiedCode((current) => current === provider ? void 0 : current);
|
|
669
|
+
}, 1500);
|
|
670
|
+
}).catch(() => void 0);
|
|
671
|
+
}, []);
|
|
601
672
|
if (rpc === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
602
|
-
style: styles$
|
|
673
|
+
style: styles$4.intro,
|
|
603
674
|
children: t("unavailable")
|
|
604
675
|
});
|
|
605
676
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
606
|
-
style: styles$
|
|
677
|
+
style: styles$4.section,
|
|
607
678
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
608
|
-
style: styles$
|
|
679
|
+
style: styles$4.intro,
|
|
609
680
|
children: t("intro")
|
|
610
681
|
}), PROVIDERS.map(({ id, name }) => {
|
|
611
682
|
const status = statuses[id];
|
|
612
683
|
const busy = status?.busy === true;
|
|
684
|
+
const deviceCode = deviceCodes[id];
|
|
613
685
|
const usage = usages[id];
|
|
614
686
|
const usageError = usageErrors[id];
|
|
615
687
|
const showUsage = status?.loggedIn === true && usage?.supported !== false && (usage !== void 0 || usageError !== void 0 || usageLoading[id] === true);
|
|
616
688
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
617
|
-
style: styles$
|
|
689
|
+
style: styles$4.card,
|
|
618
690
|
children: [
|
|
619
691
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
620
|
-
style: styles$
|
|
692
|
+
style: styles$4.cardHeader,
|
|
621
693
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
|
|
622
|
-
...styles$
|
|
694
|
+
...styles$4.dot,
|
|
623
695
|
background: dotColor(status)
|
|
624
696
|
} }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
625
|
-
style: styles$
|
|
697
|
+
style: styles$4.name,
|
|
626
698
|
children: name
|
|
627
699
|
})]
|
|
628
700
|
}),
|
|
629
701
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
630
|
-
style: styles$
|
|
702
|
+
style: styles$4.statusLine,
|
|
631
703
|
children: statusText(t, status)
|
|
632
704
|
}),
|
|
633
705
|
status?.detail !== void 0 && status.detail !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
634
|
-
style: styles$
|
|
706
|
+
style: styles$4.statusLine,
|
|
635
707
|
children: status.detail
|
|
636
708
|
}),
|
|
637
709
|
errors[id] !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
638
|
-
style: styles$
|
|
710
|
+
style: styles$4.errorLine,
|
|
639
711
|
children: errors[id]
|
|
640
712
|
}),
|
|
641
713
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
642
|
-
style: styles$
|
|
714
|
+
style: styles$4.actions,
|
|
643
715
|
children: [
|
|
644
716
|
!busy && status?.loggedIn !== true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
645
717
|
type: "button",
|
|
646
|
-
style: styles$
|
|
718
|
+
style: styles$4.button,
|
|
647
719
|
onClick: () => {
|
|
648
720
|
login(id);
|
|
649
721
|
},
|
|
@@ -651,7 +723,7 @@ function SubscriptionsSection(props) {
|
|
|
651
723
|
}),
|
|
652
724
|
busy && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
653
725
|
type: "button",
|
|
654
|
-
style: styles$
|
|
726
|
+
style: styles$4.button,
|
|
655
727
|
onClick: () => {
|
|
656
728
|
cancel(id);
|
|
657
729
|
},
|
|
@@ -659,7 +731,7 @@ function SubscriptionsSection(props) {
|
|
|
659
731
|
}),
|
|
660
732
|
status?.loggedIn === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
661
733
|
type: "button",
|
|
662
|
-
style: styles$
|
|
734
|
+
style: styles$4.button,
|
|
663
735
|
onClick: () => {
|
|
664
736
|
logout(id, name);
|
|
665
737
|
},
|
|
@@ -668,23 +740,23 @@ function SubscriptionsSection(props) {
|
|
|
668
740
|
]
|
|
669
741
|
}),
|
|
670
742
|
showUsage && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
671
|
-
style: styles$
|
|
743
|
+
style: styles$4.usage,
|
|
672
744
|
children: [
|
|
673
745
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
674
|
-
style: styles$
|
|
746
|
+
style: styles$4.usageHeader,
|
|
675
747
|
children: [
|
|
676
748
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
677
|
-
style: styles$
|
|
749
|
+
style: styles$4.usageTitle,
|
|
678
750
|
children: t("usageTitle")
|
|
679
751
|
}),
|
|
680
752
|
usage?.plan !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
681
|
-
style: styles$
|
|
753
|
+
style: styles$4.usagePlan,
|
|
682
754
|
children: t("usagePlan", { plan: usage.plan })
|
|
683
755
|
}),
|
|
684
756
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
685
757
|
type: "button",
|
|
686
758
|
style: {
|
|
687
|
-
...styles$
|
|
759
|
+
...styles$4.usageRefresh,
|
|
688
760
|
...usageLoading[id] === true ? {
|
|
689
761
|
opacity: .5,
|
|
690
762
|
cursor: "default"
|
|
@@ -699,28 +771,28 @@ function SubscriptionsSection(props) {
|
|
|
699
771
|
]
|
|
700
772
|
}),
|
|
701
773
|
usage === void 0 && usageError === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
702
|
-
style: styles$
|
|
774
|
+
style: styles$4.statusLine,
|
|
703
775
|
children: t("usageLoading")
|
|
704
776
|
}),
|
|
705
777
|
usageError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
706
|
-
style: styles$
|
|
778
|
+
style: styles$4.errorLine,
|
|
707
779
|
children: t("usageError", { message: usageError })
|
|
708
780
|
}),
|
|
709
781
|
usage?.windows !== void 0 && usage.windows.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
710
|
-
style: styles$
|
|
782
|
+
style: styles$4.statusLine,
|
|
711
783
|
children: t("usageEmpty")
|
|
712
784
|
}),
|
|
713
785
|
(usage?.windows ?? []).map((window$1, index) => {
|
|
714
786
|
const percent = Math.min(100, Math.max(0, window$1.usedPercent));
|
|
715
787
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
716
|
-
style: styles$
|
|
788
|
+
style: styles$4.usageRow,
|
|
717
789
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
718
|
-
style: styles$
|
|
790
|
+
style: styles$4.usageMeta,
|
|
719
791
|
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
792
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
721
|
-
style: styles$
|
|
793
|
+
style: styles$4.usageTrack,
|
|
722
794
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
|
|
723
|
-
...styles$
|
|
795
|
+
...styles$4.usageFill,
|
|
724
796
|
width: `${String(percent)}%`,
|
|
725
797
|
background: usageBarColor(percent)
|
|
726
798
|
} })
|
|
@@ -729,12 +801,43 @@ function SubscriptionsSection(props) {
|
|
|
729
801
|
})
|
|
730
802
|
]
|
|
731
803
|
}),
|
|
732
|
-
busy && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
733
|
-
style: styles$
|
|
804
|
+
busy && deviceCode !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
805
|
+
style: styles$4.deviceCode,
|
|
806
|
+
children: [
|
|
807
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
808
|
+
style: styles$4.statusLine,
|
|
809
|
+
children: t("deviceCodePrompt")
|
|
810
|
+
}),
|
|
811
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
812
|
+
style: styles$4.deviceCodeText,
|
|
813
|
+
children: deviceCode.userCode
|
|
814
|
+
}),
|
|
815
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
816
|
+
style: styles$4.actions,
|
|
817
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
818
|
+
type: "button",
|
|
819
|
+
style: styles$4.button,
|
|
820
|
+
onClick: () => {
|
|
821
|
+
copyDeviceCode(id, deviceCode.userCode);
|
|
822
|
+
},
|
|
823
|
+
children: copiedCode === id ? t("deviceCodeCopied") : t("deviceCodeCopy")
|
|
824
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
825
|
+
type: "button",
|
|
826
|
+
style: styles$4.button,
|
|
827
|
+
onClick: () => {
|
|
828
|
+
window.open(deviceCode.verificationUrl, "_blank", "noopener");
|
|
829
|
+
},
|
|
830
|
+
children: t("deviceCodeOpenPage")
|
|
831
|
+
})]
|
|
832
|
+
})
|
|
833
|
+
]
|
|
834
|
+
}),
|
|
835
|
+
busy && deviceCode === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
836
|
+
style: styles$4.manual,
|
|
734
837
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("manualSummary") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
735
|
-
style: styles$
|
|
838
|
+
style: styles$4.manualRow,
|
|
736
839
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
737
|
-
style: styles$
|
|
840
|
+
style: styles$4.manualInput,
|
|
738
841
|
value: manualDrafts[id],
|
|
739
842
|
placeholder: t("manualPlaceholder"),
|
|
740
843
|
onChange: (event) => setManualDrafts((prev) => ({
|
|
@@ -743,7 +846,7 @@ function SubscriptionsSection(props) {
|
|
|
743
846
|
}))
|
|
744
847
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
745
848
|
type: "button",
|
|
746
|
-
style: styles$
|
|
849
|
+
style: styles$4.button,
|
|
747
850
|
onClick: () => {
|
|
748
851
|
submitManual(id);
|
|
749
852
|
},
|
|
@@ -757,6 +860,229 @@ function SubscriptionsSection(props) {
|
|
|
757
860
|
});
|
|
758
861
|
}
|
|
759
862
|
|
|
863
|
+
//#endregion
|
|
864
|
+
//#region src/client/ImageGallery.tsx
|
|
865
|
+
/** Display box for a lone image (platform rule): long edge 240px with the
|
|
866
|
+
* rendered aspect ratio clamped to [0.25, 4] — the overflow is cropped by
|
|
867
|
+
* `object-fit: cover` — and never upscaled past the image's natural size. The
|
|
868
|
+
* crop anchor keeps the top of very tall images and the left of very wide
|
|
869
|
+
* ones, where the informative content usually starts. */
|
|
870
|
+
function singleFit(attachment) {
|
|
871
|
+
const natural = attachment.width / attachment.height;
|
|
872
|
+
const ratio = Math.min(4, Math.max(.25, natural));
|
|
873
|
+
const box = ratio >= 1 ? {
|
|
874
|
+
width: 240,
|
|
875
|
+
height: 240 / ratio
|
|
876
|
+
} : {
|
|
877
|
+
width: 240 * ratio,
|
|
878
|
+
height: 240
|
|
879
|
+
};
|
|
880
|
+
const scale = Math.min(1, attachment.width / box.width, attachment.height / box.height);
|
|
881
|
+
return {
|
|
882
|
+
width: Math.max(1, Math.round(box.width * scale)),
|
|
883
|
+
height: Math.max(1, Math.round(box.height * scale)),
|
|
884
|
+
objectPosition: natural < .25 ? "center top" : natural > 4 ? "left center" : "center"
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
const styles$3 = {
|
|
888
|
+
gallery: {
|
|
889
|
+
display: "flex",
|
|
890
|
+
flexWrap: "wrap",
|
|
891
|
+
gap: 8,
|
|
892
|
+
justifyContent: "flex-start"
|
|
893
|
+
},
|
|
894
|
+
frame: {
|
|
895
|
+
display: "grid",
|
|
896
|
+
placeItems: "center",
|
|
897
|
+
overflow: "hidden",
|
|
898
|
+
padding: 0,
|
|
899
|
+
border: "1px solid var(--dsw-alias-border-l2-darkmode-thin)",
|
|
900
|
+
borderRadius: 8,
|
|
901
|
+
background: "var(--dsw-alias-interactive-bg-hover-solid)",
|
|
902
|
+
cursor: "zoom-in"
|
|
903
|
+
},
|
|
904
|
+
tile: {
|
|
905
|
+
width: 64,
|
|
906
|
+
height: 64
|
|
907
|
+
},
|
|
908
|
+
img: {
|
|
909
|
+
width: "100%",
|
|
910
|
+
height: "100%",
|
|
911
|
+
objectFit: "cover",
|
|
912
|
+
display: "block"
|
|
913
|
+
},
|
|
914
|
+
loading: {
|
|
915
|
+
fontSize: 12,
|
|
916
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
917
|
+
padding: "0 8px"
|
|
918
|
+
},
|
|
919
|
+
error: {
|
|
920
|
+
fontSize: 12,
|
|
921
|
+
color: "var(--dsw-alias-state-error-primary)",
|
|
922
|
+
cursor: "pointer",
|
|
923
|
+
border: "1px solid var(--dsw-alias-border-l2-darkmode-thin)",
|
|
924
|
+
borderRadius: 8,
|
|
925
|
+
background: "transparent",
|
|
926
|
+
padding: "6px 10px"
|
|
927
|
+
},
|
|
928
|
+
overlay: {
|
|
929
|
+
position: "fixed",
|
|
930
|
+
inset: 0,
|
|
931
|
+
zIndex: 1e3,
|
|
932
|
+
display: "grid",
|
|
933
|
+
placeItems: "center",
|
|
934
|
+
background: "rgba(0, 0, 0, 0.72)",
|
|
935
|
+
padding: 24
|
|
936
|
+
},
|
|
937
|
+
overlayImg: {
|
|
938
|
+
maxWidth: "92vw",
|
|
939
|
+
maxHeight: "92vh",
|
|
940
|
+
objectFit: "contain",
|
|
941
|
+
borderRadius: 4
|
|
942
|
+
},
|
|
943
|
+
close: {
|
|
944
|
+
position: "absolute",
|
|
945
|
+
top: 12,
|
|
946
|
+
right: 12,
|
|
947
|
+
width: 32,
|
|
948
|
+
height: 32,
|
|
949
|
+
display: "grid",
|
|
950
|
+
placeItems: "center",
|
|
951
|
+
border: "none",
|
|
952
|
+
borderRadius: "50%",
|
|
953
|
+
cursor: "pointer",
|
|
954
|
+
background: "rgba(255, 255, 255, 0.16)",
|
|
955
|
+
color: "#fff",
|
|
956
|
+
fontSize: 16,
|
|
957
|
+
lineHeight: 1
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
/**
|
|
961
|
+
* Full-viewport original-image preview: backdrop or close-control click and
|
|
962
|
+
* Escape all dismiss; the image itself is inert so a click on it does not
|
|
963
|
+
* fall through to the backdrop dismissal.
|
|
964
|
+
*/
|
|
965
|
+
function ImageLightbox({ src, alt, labels, onClose }) {
|
|
966
|
+
(0, react.useEffect)(() => {
|
|
967
|
+
const onKey = (event) => {
|
|
968
|
+
if (event.key === "Escape") onClose();
|
|
969
|
+
};
|
|
970
|
+
window.addEventListener("keydown", onKey);
|
|
971
|
+
return () => {
|
|
972
|
+
window.removeEventListener("keydown", onKey);
|
|
973
|
+
};
|
|
974
|
+
}, [onClose]);
|
|
975
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
976
|
+
role: "dialog",
|
|
977
|
+
"aria-label": labels.dialog,
|
|
978
|
+
style: styles$3.overlay,
|
|
979
|
+
onClick: onClose,
|
|
980
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
981
|
+
src,
|
|
982
|
+
alt,
|
|
983
|
+
style: styles$3.overlayImg,
|
|
984
|
+
onClick: (event) => {
|
|
985
|
+
event.stopPropagation();
|
|
986
|
+
}
|
|
987
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
988
|
+
type: "button",
|
|
989
|
+
"aria-label": labels.close,
|
|
990
|
+
style: styles$3.close,
|
|
991
|
+
onClick: onClose,
|
|
992
|
+
children: "×"
|
|
993
|
+
})]
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Compact history renderer with retryable loading and click-to-open original
|
|
998
|
+
* preview. A lone image renders at its `singleFit` size; an image among
|
|
999
|
+
* several renders as a fixed 64px square tile.
|
|
1000
|
+
*/
|
|
1001
|
+
function MessageImage({ attachment, load, variant, labels }) {
|
|
1002
|
+
const [src, setSrc] = (0, react.useState)(null);
|
|
1003
|
+
const [error, setError] = (0, react.useState)(false);
|
|
1004
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
1005
|
+
const [attempt, setAttempt] = (0, react.useState)(0);
|
|
1006
|
+
const retry = (0, react.useCallback)(() => {
|
|
1007
|
+
setAttempt((a) => a + 1);
|
|
1008
|
+
}, []);
|
|
1009
|
+
const close = (0, react.useCallback)(() => {
|
|
1010
|
+
setOpen(false);
|
|
1011
|
+
}, []);
|
|
1012
|
+
const fit = (0, react.useMemo)(() => variant === "single" ? singleFit(attachment) : void 0, [attachment, variant]);
|
|
1013
|
+
(0, react.useEffect)(() => {
|
|
1014
|
+
let live = true;
|
|
1015
|
+
setError(false);
|
|
1016
|
+
setSrc(null);
|
|
1017
|
+
load(attachment).then((url) => {
|
|
1018
|
+
if (live) setSrc(url);
|
|
1019
|
+
}).catch(() => {
|
|
1020
|
+
if (live) setError(true);
|
|
1021
|
+
});
|
|
1022
|
+
return () => {
|
|
1023
|
+
live = false;
|
|
1024
|
+
};
|
|
1025
|
+
}, [
|
|
1026
|
+
attachment,
|
|
1027
|
+
load,
|
|
1028
|
+
attempt
|
|
1029
|
+
]);
|
|
1030
|
+
const label = attachment.name ?? labels.image;
|
|
1031
|
+
if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1032
|
+
type: "button",
|
|
1033
|
+
style: styles$3.error,
|
|
1034
|
+
onClick: retry,
|
|
1035
|
+
children: labels.loadFailed
|
|
1036
|
+
});
|
|
1037
|
+
const box = fit === void 0 ? styles$3.tile : {
|
|
1038
|
+
width: fit.width,
|
|
1039
|
+
height: fit.height
|
|
1040
|
+
};
|
|
1041
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1042
|
+
type: "button",
|
|
1043
|
+
style: {
|
|
1044
|
+
...styles$3.frame,
|
|
1045
|
+
...box
|
|
1046
|
+
},
|
|
1047
|
+
title: labels.open,
|
|
1048
|
+
"aria-label": labels.openNamed(label),
|
|
1049
|
+
onClick: () => {
|
|
1050
|
+
if (src !== null) setOpen(true);
|
|
1051
|
+
},
|
|
1052
|
+
children: src === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1053
|
+
style: styles$3.loading,
|
|
1054
|
+
children: labels.loading
|
|
1055
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
1056
|
+
src,
|
|
1057
|
+
alt: label,
|
|
1058
|
+
style: {
|
|
1059
|
+
...styles$3.img,
|
|
1060
|
+
objectPosition: fit?.objectPosition
|
|
1061
|
+
}
|
|
1062
|
+
})
|
|
1063
|
+
}), open && src !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImageLightbox, {
|
|
1064
|
+
src,
|
|
1065
|
+
alt: label,
|
|
1066
|
+
labels: labels.lightbox,
|
|
1067
|
+
onClose: close
|
|
1068
|
+
})] });
|
|
1069
|
+
}
|
|
1070
|
+
/** Wrapping image group: a lone image renders large, several render as 64px
|
|
1071
|
+
* square tiles (same rule as the platform gallery). */
|
|
1072
|
+
function ImageGallery({ images, load, labels }) {
|
|
1073
|
+
if (images.length === 0) return null;
|
|
1074
|
+
const variant = images.length === 1 ? "single" : "tile";
|
|
1075
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1076
|
+
style: styles$3.gallery,
|
|
1077
|
+
children: images.map((image, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageImage, {
|
|
1078
|
+
attachment: image.attachment,
|
|
1079
|
+
load,
|
|
1080
|
+
variant,
|
|
1081
|
+
labels
|
|
1082
|
+
}, `${image.attachment.attachmentId}:${index}`))
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
|
|
760
1086
|
//#endregion
|
|
761
1087
|
//#region src/client/ImageGenerateToolview.tsx
|
|
762
1088
|
/** Logical RPC channel served by the node half of this plugin. */
|
|
@@ -770,7 +1096,7 @@ const PROMPT_MAX_LENGTH$1 = 60;
|
|
|
770
1096
|
* @param payload - channel-owned request payload.
|
|
771
1097
|
* @returns the success value, cast by the caller to the endpoint's shape.
|
|
772
1098
|
*/
|
|
773
|
-
async function callSubscriptionsAuth(rpc, endpoint, payload) {
|
|
1099
|
+
async function callSubscriptionsAuth$1(rpc, endpoint, payload) {
|
|
774
1100
|
const result = await rpc.call(SUBSCRIPTIONS_AUTH_CHANNEL$1, endpoint, payload);
|
|
775
1101
|
if (!result.ok) throw new Error(result.error.message);
|
|
776
1102
|
return result.value;
|
|
@@ -781,7 +1107,7 @@ async function callSubscriptionsAuth(rpc, endpoint, payload) {
|
|
|
781
1107
|
* @returns loader resolving an attachment ref to a data URL.
|
|
782
1108
|
*/
|
|
783
1109
|
function createImageLoader(rpc) {
|
|
784
|
-
return (attachment) => callSubscriptionsAuth(rpc, "image", { ...attachment }).then((result) => `data:${result.mediaType};base64,${result.dataBase64}`);
|
|
1110
|
+
return (attachment) => callSubscriptionsAuth$1(rpc, "image", { ...attachment }).then((result) => `data:${result.mediaType};base64,${result.dataBase64}`);
|
|
785
1111
|
}
|
|
786
1112
|
/**
|
|
787
1113
|
* English-dictionary fallback for a missing locale seat (standalone renders);
|
|
@@ -790,7 +1116,7 @@ function createImageLoader(rpc) {
|
|
|
790
1116
|
* @param params - `{name}` template params.
|
|
791
1117
|
* @returns the template with params substituted.
|
|
792
1118
|
*/
|
|
793
|
-
function fallbackTranslate$
|
|
1119
|
+
function fallbackTranslate$2(key, params) {
|
|
794
1120
|
let text = en[key];
|
|
795
1121
|
for (const [name, value] of Object.entries(params ?? {})) text = text.replaceAll(`{${name}}`, String(value));
|
|
796
1122
|
return text;
|
|
@@ -830,7 +1156,7 @@ function resultImages(block) {
|
|
|
830
1156
|
for (const part of block.content) if (part.type === "image") images.push({ attachment: part.attachment });
|
|
831
1157
|
return images;
|
|
832
1158
|
}
|
|
833
|
-
const styles$
|
|
1159
|
+
const styles$2 = {
|
|
834
1160
|
container: {
|
|
835
1161
|
display: "flex",
|
|
836
1162
|
flexDirection: "column",
|
|
@@ -884,7 +1210,7 @@ const styles$1 = {
|
|
|
884
1210
|
*/
|
|
885
1211
|
function ImageGenerateToolview(props) {
|
|
886
1212
|
const { block, load } = props;
|
|
887
|
-
const t = props.t ?? fallbackTranslate$
|
|
1213
|
+
const t = props.t ?? fallbackTranslate$2;
|
|
888
1214
|
if (block === void 0) return null;
|
|
889
1215
|
const settled = "kind" in block;
|
|
890
1216
|
const title = `image_generate: ${derivePrompt$1((settled ? block.call?.argsRaw : block.argsRaw) ?? "")}`;
|
|
@@ -902,34 +1228,33 @@ function ImageGenerateToolview(props) {
|
|
|
902
1228
|
}
|
|
903
1229
|
};
|
|
904
1230
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
905
|
-
style: styles$
|
|
1231
|
+
style: styles$2.container,
|
|
906
1232
|
children: [
|
|
907
1233
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
908
|
-
style: styles$
|
|
1234
|
+
style: styles$2.row,
|
|
909
1235
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
910
|
-
style: styles$
|
|
1236
|
+
style: styles$2.icon,
|
|
911
1237
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconSparkle16, { size: 14 })
|
|
912
1238
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
913
|
-
style: styles$
|
|
1239
|
+
style: styles$2.title,
|
|
914
1240
|
children: title
|
|
915
1241
|
})]
|
|
916
1242
|
}),
|
|
917
1243
|
!settled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
918
|
-
style: styles$
|
|
1244
|
+
style: styles$2.subtle,
|
|
919
1245
|
children: t("generating")
|
|
920
1246
|
}),
|
|
921
1247
|
settled && block.isError && text !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
922
|
-
style: styles$
|
|
1248
|
+
style: styles$2.error,
|
|
923
1249
|
children: text.split("\n", 1)[0]
|
|
924
1250
|
}),
|
|
925
|
-
settled && !block.isError && images.length > 0 && load !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1251
|
+
settled && !block.isError && images.length > 0 && load !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImageGallery, {
|
|
926
1252
|
images,
|
|
927
1253
|
load,
|
|
928
|
-
align: "start",
|
|
929
1254
|
labels
|
|
930
1255
|
}),
|
|
931
1256
|
settled && !block.isError && images.length === 0 && text !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
932
|
-
style: styles$
|
|
1257
|
+
style: styles$2.output,
|
|
933
1258
|
children: text
|
|
934
1259
|
})
|
|
935
1260
|
]
|
|
@@ -958,7 +1283,7 @@ function createVideoLoader(rpc) {
|
|
|
958
1283
|
* English-dictionary fallback for a missing locale seat (standalone renders);
|
|
959
1284
|
* the framework always supplies the namespace-bound one.
|
|
960
1285
|
*/
|
|
961
|
-
function fallbackTranslate(key, params) {
|
|
1286
|
+
function fallbackTranslate$1(key, params) {
|
|
962
1287
|
let text = en[key];
|
|
963
1288
|
for (const [name, value] of Object.entries(params ?? {})) text = text.replaceAll(`{${name}}`, String(value));
|
|
964
1289
|
return text;
|
|
@@ -1015,7 +1340,7 @@ function base64Bytes(dataBase64) {
|
|
|
1015
1340
|
for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index);
|
|
1016
1341
|
return bytes;
|
|
1017
1342
|
}
|
|
1018
|
-
const styles = {
|
|
1343
|
+
const styles$1 = {
|
|
1019
1344
|
container: {
|
|
1020
1345
|
display: "flex",
|
|
1021
1346
|
flexDirection: "column",
|
|
@@ -1076,7 +1401,7 @@ const styles = {
|
|
|
1076
1401
|
*/
|
|
1077
1402
|
function VideoGenerateToolview(props) {
|
|
1078
1403
|
const { block, loadVideo } = props;
|
|
1079
|
-
const t = props.t ?? fallbackTranslate;
|
|
1404
|
+
const t = props.t ?? fallbackTranslate$1;
|
|
1080
1405
|
const settled = block !== void 0 && "kind" in block;
|
|
1081
1406
|
const isError = settled && block.isError;
|
|
1082
1407
|
const fileName = block !== void 0 && settled && !isError ? resolveFileName(block) : void 0;
|
|
@@ -1109,48 +1434,285 @@ function VideoGenerateToolview(props) {
|
|
|
1109
1434
|
const title = `video_generate: ${derivePrompt((settled ? block.call?.argsRaw : block.argsRaw) ?? "")}`;
|
|
1110
1435
|
const text = settled ? resultText(block) : "";
|
|
1111
1436
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1112
|
-
style: styles.container,
|
|
1437
|
+
style: styles$1.container,
|
|
1113
1438
|
children: [
|
|
1114
1439
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1115
|
-
style: styles.row,
|
|
1440
|
+
style: styles$1.row,
|
|
1116
1441
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1117
|
-
style: styles.icon,
|
|
1442
|
+
style: styles$1.icon,
|
|
1118
1443
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconSparkle16, { size: 14 })
|
|
1119
1444
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1120
|
-
style: styles.title,
|
|
1445
|
+
style: styles$1.title,
|
|
1121
1446
|
children: title
|
|
1122
1447
|
})]
|
|
1123
1448
|
}),
|
|
1124
1449
|
!settled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1125
|
-
style: styles.subtle,
|
|
1450
|
+
style: styles$1.subtle,
|
|
1126
1451
|
children: t("generatingVideo")
|
|
1127
1452
|
}),
|
|
1128
1453
|
settled && isError && text !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1129
|
-
style: styles.error,
|
|
1454
|
+
style: styles$1.error,
|
|
1130
1455
|
children: text.split("\n", 1)[0]
|
|
1131
1456
|
}),
|
|
1132
1457
|
settled && !isError && fileName !== void 0 && load.phase === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1133
|
-
style: styles.subtle,
|
|
1458
|
+
style: styles$1.subtle,
|
|
1134
1459
|
children: t("videoLoading")
|
|
1135
1460
|
}),
|
|
1136
1461
|
settled && !isError && fileName !== void 0 && load.phase === "failed" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1137
|
-
style: styles.error,
|
|
1462
|
+
style: styles$1.error,
|
|
1138
1463
|
children: t("videoLoadFailed", { message: load.message })
|
|
1139
1464
|
}),
|
|
1140
1465
|
settled && !isError && fileName !== void 0 && load.phase === "ready" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("video", {
|
|
1141
|
-
style: styles.video,
|
|
1466
|
+
style: styles$1.video,
|
|
1142
1467
|
src: load.url,
|
|
1143
1468
|
controls: true,
|
|
1144
1469
|
preload: "metadata"
|
|
1145
1470
|
}),
|
|
1146
1471
|
settled && !isError && fileName === void 0 && text !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1147
|
-
style: styles.output,
|
|
1472
|
+
style: styles$1.output,
|
|
1148
1473
|
children: text
|
|
1149
1474
|
})
|
|
1150
1475
|
]
|
|
1151
1476
|
});
|
|
1152
1477
|
}
|
|
1153
1478
|
|
|
1479
|
+
//#endregion
|
|
1480
|
+
//#region src/client/SpeedSelect.tsx
|
|
1481
|
+
/**
|
|
1482
|
+
* The `loadSpeed` half of the inject face: the plugin's own speed state plus
|
|
1483
|
+
* the host's current model selection (the visibility gate). A model-RPC
|
|
1484
|
+
* failure throws rather than answering "hidden" — the caller keeps its last
|
|
1485
|
+
* known state, so a transient failure never locks the toggle away.
|
|
1486
|
+
*
|
|
1487
|
+
* `sessionId` is a plain string: slot and command contexts brand it through
|
|
1488
|
+
* different dsh-session copies, and only the API-client boundary needs one.
|
|
1489
|
+
*/
|
|
1490
|
+
function createSpeedLoader(connection, sessionId) {
|
|
1491
|
+
return async () => {
|
|
1492
|
+
const state = await callSubscriptionsAuth(connection.rpc, "speed", { sessionId });
|
|
1493
|
+
const { result } = await connection.api.sessions.models({ sessionId });
|
|
1494
|
+
if (!result.ok) throw new Error(`session.models failed: ${result.error.code}: ${result.error.message}`);
|
|
1495
|
+
const current = result.value.current;
|
|
1496
|
+
return {
|
|
1497
|
+
visible: current !== null && current.provider === "codex" && state.fastModels.includes(current.model),
|
|
1498
|
+
tier: state.tier
|
|
1499
|
+
};
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
/** The `setSpeed` half of the inject face: boolean outcome for the component's busy state. */
|
|
1503
|
+
function createSpeedSetter(connection, sessionId) {
|
|
1504
|
+
return (tier) => callSubscriptionsAuth(connection.rpc, "setSpeed", {
|
|
1505
|
+
sessionId,
|
|
1506
|
+
tier
|
|
1507
|
+
}).then(() => true, () => false);
|
|
1508
|
+
}
|
|
1509
|
+
/** English-dictionary fallback for a missing inject `t` (standalone renders). */
|
|
1510
|
+
function fallbackTranslate(key) {
|
|
1511
|
+
return en[key];
|
|
1512
|
+
}
|
|
1513
|
+
const TIERS = ["standard", "fast"];
|
|
1514
|
+
/**
|
|
1515
|
+
* The composer Speed control: a trigger reading `速度 · 快速`/`速度 · 标准`
|
|
1516
|
+
* that opens a two-row menu (standard/fast with descriptions, check mark on
|
|
1517
|
+
* the current tier). Mount and every open reload the host state so a model
|
|
1518
|
+
* switch made since the last open self-corrects.
|
|
1519
|
+
*/
|
|
1520
|
+
/** How often the control re-reads the host state (model switches arrive only by asking). */
|
|
1521
|
+
const POLL_INTERVAL_MS = 3e3;
|
|
1522
|
+
/**
|
|
1523
|
+
* The composer Speed control: a trigger reading `速度 · 快速`/`速度 · 标准`
|
|
1524
|
+
* that opens a two-row menu (standard/fast with descriptions, check mark on
|
|
1525
|
+
* the current tier). The host pushes nothing on a model switch, so the
|
|
1526
|
+
* control re-reads on a slow poll with a single-flight guard; a failed read
|
|
1527
|
+
* keeps the last known state, so a transient RPC failure can never lock the
|
|
1528
|
+
* toggle away (the earlier mount-only load had no recovery path).
|
|
1529
|
+
*/
|
|
1530
|
+
function SpeedSelect({ loadSpeed, setSpeed, t }) {
|
|
1531
|
+
const translate = t ?? fallbackTranslate;
|
|
1532
|
+
const [state, setState] = (0, react.useState)(null);
|
|
1533
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
1534
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
1535
|
+
const rootRef = (0, react.useRef)(null);
|
|
1536
|
+
const loadRef = (0, react.useRef)(loadSpeed);
|
|
1537
|
+
loadRef.current = loadSpeed;
|
|
1538
|
+
(0, react.useEffect)(() => {
|
|
1539
|
+
if (loadRef.current === void 0) return;
|
|
1540
|
+
let cancelled = false;
|
|
1541
|
+
let inflight = false;
|
|
1542
|
+
const reload = () => {
|
|
1543
|
+
const load = loadRef.current;
|
|
1544
|
+
if (load === void 0 || inflight) return;
|
|
1545
|
+
inflight = true;
|
|
1546
|
+
load().then((loaded) => {
|
|
1547
|
+
if (!cancelled) setState(loaded);
|
|
1548
|
+
}, () => {}).finally(() => {
|
|
1549
|
+
inflight = false;
|
|
1550
|
+
});
|
|
1551
|
+
};
|
|
1552
|
+
reload();
|
|
1553
|
+
const timer = setInterval(reload, POLL_INTERVAL_MS);
|
|
1554
|
+
return () => {
|
|
1555
|
+
cancelled = true;
|
|
1556
|
+
clearInterval(timer);
|
|
1557
|
+
};
|
|
1558
|
+
}, []);
|
|
1559
|
+
(0, react.useEffect)(() => {
|
|
1560
|
+
if (!open) return;
|
|
1561
|
+
const closeOutside = (event) => {
|
|
1562
|
+
if (!rootRef.current?.contains(event.target)) setOpen(false);
|
|
1563
|
+
};
|
|
1564
|
+
document.addEventListener("mousedown", closeOutside);
|
|
1565
|
+
return () => {
|
|
1566
|
+
document.removeEventListener("mousedown", closeOutside);
|
|
1567
|
+
};
|
|
1568
|
+
}, [open]);
|
|
1569
|
+
if (loadSpeed === void 0 || setSpeed === void 0 || state === null || !state.visible) return null;
|
|
1570
|
+
const choose = (tier) => {
|
|
1571
|
+
if (busy) return;
|
|
1572
|
+
if (tier === state.tier) {
|
|
1573
|
+
setOpen(false);
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
setBusy(true);
|
|
1577
|
+
setSpeed(tier).then((ok) => {
|
|
1578
|
+
setBusy(false);
|
|
1579
|
+
if (ok) {
|
|
1580
|
+
setState({
|
|
1581
|
+
visible: true,
|
|
1582
|
+
tier
|
|
1583
|
+
});
|
|
1584
|
+
setOpen(false);
|
|
1585
|
+
}
|
|
1586
|
+
});
|
|
1587
|
+
};
|
|
1588
|
+
const show = () => {
|
|
1589
|
+
setOpen(true);
|
|
1590
|
+
const load = loadRef.current;
|
|
1591
|
+
if (load === void 0) return;
|
|
1592
|
+
load().then(setState, () => {});
|
|
1593
|
+
};
|
|
1594
|
+
const tierName = (tier) => translate(tier === "fast" ? "speedFast" : "speedStandard");
|
|
1595
|
+
const tierDescription = (tier) => translate(tier === "fast" ? "speedFastDescription" : "speedStandardDescription");
|
|
1596
|
+
const triggerLabel = `${translate("speed")} · ${tierName(state.tier)}`;
|
|
1597
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1598
|
+
ref: rootRef,
|
|
1599
|
+
style: styles.root,
|
|
1600
|
+
onKeyDown: (event) => {
|
|
1601
|
+
if (event.key === "Escape" && open) {
|
|
1602
|
+
event.preventDefault();
|
|
1603
|
+
setOpen(false);
|
|
1604
|
+
}
|
|
1605
|
+
},
|
|
1606
|
+
children: [open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1607
|
+
style: styles.menu,
|
|
1608
|
+
role: "menu",
|
|
1609
|
+
"aria-label": translate("speed"),
|
|
1610
|
+
children: TIERS.map((tier) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1611
|
+
type: "button",
|
|
1612
|
+
role: "menuitemradio",
|
|
1613
|
+
"aria-checked": tier === state.tier,
|
|
1614
|
+
style: styles.item,
|
|
1615
|
+
disabled: busy,
|
|
1616
|
+
onClick: () => {
|
|
1617
|
+
choose(tier);
|
|
1618
|
+
},
|
|
1619
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1620
|
+
style: styles.itemCheck,
|
|
1621
|
+
children: tier === state.tier ? "✓" : ""
|
|
1622
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1623
|
+
style: styles.itemText,
|
|
1624
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1625
|
+
style: styles.itemName,
|
|
1626
|
+
children: tierName(tier)
|
|
1627
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1628
|
+
style: styles.itemDescription,
|
|
1629
|
+
children: tierDescription(tier)
|
|
1630
|
+
})]
|
|
1631
|
+
})]
|
|
1632
|
+
}, tier))
|
|
1633
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1634
|
+
type: "button",
|
|
1635
|
+
style: styles.trigger,
|
|
1636
|
+
"aria-haspopup": "menu",
|
|
1637
|
+
"aria-expanded": open,
|
|
1638
|
+
title: triggerLabel,
|
|
1639
|
+
disabled: busy,
|
|
1640
|
+
onClick: () => {
|
|
1641
|
+
if (open) setOpen(false);
|
|
1642
|
+
else show();
|
|
1643
|
+
},
|
|
1644
|
+
children: triggerLabel
|
|
1645
|
+
})]
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
const styles = {
|
|
1649
|
+
root: {
|
|
1650
|
+
position: "relative",
|
|
1651
|
+
display: "inline-flex"
|
|
1652
|
+
},
|
|
1653
|
+
trigger: {
|
|
1654
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
1655
|
+
borderRadius: 8,
|
|
1656
|
+
background: "transparent",
|
|
1657
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
1658
|
+
font: "inherit",
|
|
1659
|
+
fontSize: 12,
|
|
1660
|
+
lineHeight: "18px",
|
|
1661
|
+
padding: "2px 8px",
|
|
1662
|
+
cursor: "pointer",
|
|
1663
|
+
whiteSpace: "nowrap"
|
|
1664
|
+
},
|
|
1665
|
+
menu: {
|
|
1666
|
+
position: "absolute",
|
|
1667
|
+
bottom: "100%",
|
|
1668
|
+
right: 0,
|
|
1669
|
+
marginBottom: 4,
|
|
1670
|
+
minWidth: 180,
|
|
1671
|
+
padding: 4,
|
|
1672
|
+
zIndex: 20,
|
|
1673
|
+
background: "var(--dsw-alias-bg-layer-1)",
|
|
1674
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
1675
|
+
borderRadius: 8,
|
|
1676
|
+
display: "flex",
|
|
1677
|
+
flexDirection: "column",
|
|
1678
|
+
gap: 2
|
|
1679
|
+
},
|
|
1680
|
+
item: {
|
|
1681
|
+
display: "flex",
|
|
1682
|
+
alignItems: "flex-start",
|
|
1683
|
+
gap: 6,
|
|
1684
|
+
width: "100%",
|
|
1685
|
+
border: "none",
|
|
1686
|
+
borderRadius: 6,
|
|
1687
|
+
background: "transparent",
|
|
1688
|
+
padding: "6px 8px",
|
|
1689
|
+
cursor: "pointer",
|
|
1690
|
+
font: "inherit",
|
|
1691
|
+
textAlign: "left"
|
|
1692
|
+
},
|
|
1693
|
+
itemCheck: {
|
|
1694
|
+
width: 14,
|
|
1695
|
+
flexShrink: 0,
|
|
1696
|
+
fontSize: 12,
|
|
1697
|
+
lineHeight: "18px",
|
|
1698
|
+
color: "var(--dsw-alias-label-primary)"
|
|
1699
|
+
},
|
|
1700
|
+
itemText: {
|
|
1701
|
+
display: "flex",
|
|
1702
|
+
flexDirection: "column"
|
|
1703
|
+
},
|
|
1704
|
+
itemName: {
|
|
1705
|
+
fontSize: 12,
|
|
1706
|
+
lineHeight: "18px",
|
|
1707
|
+
color: "var(--dsw-alias-label-primary)"
|
|
1708
|
+
},
|
|
1709
|
+
itemDescription: {
|
|
1710
|
+
fontSize: 11,
|
|
1711
|
+
lineHeight: "16px",
|
|
1712
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
1713
|
+
}
|
|
1714
|
+
};
|
|
1715
|
+
|
|
1154
1716
|
//#endregion
|
|
1155
1717
|
//#region src/client/index.ts
|
|
1156
1718
|
/** Dictionary namespace owned by this plugin. */
|
|
@@ -1203,6 +1765,46 @@ function apply(ctx) {
|
|
|
1203
1765
|
locale: NS,
|
|
1204
1766
|
inject: videoToolviewInjected
|
|
1205
1767
|
}, VideoGenerateToolview));
|
|
1768
|
+
ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
|
|
1769
|
+
name: "conversation.input.right",
|
|
1770
|
+
id: "codex-speed",
|
|
1771
|
+
order: 0,
|
|
1772
|
+
locale: NS,
|
|
1773
|
+
inject: (sessionId) => ({
|
|
1774
|
+
loadSpeed: createSpeedLoader(connection, sessionId),
|
|
1775
|
+
setSpeed: createSpeedSetter(connection, sessionId)
|
|
1776
|
+
})
|
|
1777
|
+
}, SpeedSelect));
|
|
1778
|
+
ctx.inject(["commandUi"], (scope) => {
|
|
1779
|
+
const command = scope.get("commandUi");
|
|
1780
|
+
scope.effect(() => command.register({
|
|
1781
|
+
name: "fast",
|
|
1782
|
+
description: t("commandFast"),
|
|
1783
|
+
available: () => true,
|
|
1784
|
+
ui: {
|
|
1785
|
+
kind: "popupSelect",
|
|
1786
|
+
options: async (session) => {
|
|
1787
|
+
const state = await createSpeedLoader(connection, session.sessionId)();
|
|
1788
|
+
if (!state.visible) throw new Error(t("commandFastUnavailable"));
|
|
1789
|
+
return [{
|
|
1790
|
+
id: "standard",
|
|
1791
|
+
label: t("speedStandard"),
|
|
1792
|
+
detail: t("speedStandardDescription")
|
|
1793
|
+
}, {
|
|
1794
|
+
id: "fast",
|
|
1795
|
+
label: t("speedFast"),
|
|
1796
|
+
detail: t("speedFastDescription")
|
|
1797
|
+
}].map((option) => ({
|
|
1798
|
+
...option,
|
|
1799
|
+
active: option.id === state.tier
|
|
1800
|
+
}));
|
|
1801
|
+
},
|
|
1802
|
+
onSelect: async (option, session) => {
|
|
1803
|
+
await createSpeedSetter(connection, session.sessionId)(option.id);
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
}), "dsh-plugin-subscriptions: /fast contribution");
|
|
1807
|
+
});
|
|
1206
1808
|
}
|
|
1207
1809
|
|
|
1208
1810
|
//#endregion
|