dsh-plugin-subscriptions 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/README.zh.md +11 -2
- package/lib/auth/rpc.d.ts +14 -0
- package/lib/auth/rpc.js +18 -0
- package/lib/client/VideoGenerateToolview.d.ts +42 -0
- package/lib/client/VideoGenerateToolview.js +178 -0
- package/lib/client/index.d.ts +1 -0
- package/lib/client/index.js +10 -0
- package/lib/client/locales.d.ts +6 -0
- package/lib/client/locales.js +6 -0
- package/lib/client.js +282 -54
- package/lib/client.js.map +1 -1
- package/lib/index.js +408 -41
- package/lib/providers/codex.d.ts +5 -2
- package/lib/providers/codex.js +34 -4
- package/lib/tools/image-generate.d.ts +55 -19
- package/lib/tools/image-generate.js +130 -31
- package/lib/tools/video-generate.d.ts +89 -0
- package/lib/tools/video-generate.js +255 -0
- package/package.json +8 -6
package/lib/client.js
CHANGED
|
@@ -71,7 +71,10 @@ const en = {
|
|
|
71
71
|
imageLoading: "Loading…",
|
|
72
72
|
imageLoadFailed: "Retry",
|
|
73
73
|
imagePreview: "Image preview",
|
|
74
|
-
imageClose: "Close"
|
|
74
|
+
imageClose: "Close",
|
|
75
|
+
generatingVideo: "Generating video…",
|
|
76
|
+
videoLoading: "Loading video…",
|
|
77
|
+
videoLoadFailed: "Video failed to load: {message}"
|
|
75
78
|
};
|
|
76
79
|
/** zh strings, one per {@link en} key. */
|
|
77
80
|
const zh = {
|
|
@@ -110,13 +113,16 @@ const zh = {
|
|
|
110
113
|
imageLoading: "加载中…",
|
|
111
114
|
imageLoadFailed: "重试",
|
|
112
115
|
imagePreview: "图片预览",
|
|
113
|
-
imageClose: "关闭"
|
|
116
|
+
imageClose: "关闭",
|
|
117
|
+
generatingVideo: "正在生成视频…",
|
|
118
|
+
videoLoading: "视频加载中…",
|
|
119
|
+
videoLoadFailed: "视频加载失败:{message}"
|
|
114
120
|
};
|
|
115
121
|
|
|
116
122
|
//#endregion
|
|
117
123
|
//#region src/client/SubscriptionsSection.tsx
|
|
118
124
|
/** Logical RPC channel served by the node half of this plugin. */
|
|
119
|
-
const SUBSCRIPTIONS_AUTH_CHANNEL$
|
|
125
|
+
const SUBSCRIPTIONS_AUTH_CHANNEL$2 = "/subscriptions-auth";
|
|
120
126
|
/** Poll cadence while a provider login attempt is busy. */
|
|
121
127
|
const POLL_INTERVAL_MS = 2e3;
|
|
122
128
|
/** Card display metadata, in page order (names are brand names, not translated). */
|
|
@@ -146,7 +152,7 @@ var SubscriptionsAuthError = class extends Error {};
|
|
|
146
152
|
async function callSubscriptionsAuth$1(rpc, endpoint, payload) {
|
|
147
153
|
let result;
|
|
148
154
|
try {
|
|
149
|
-
result = await rpc.call(SUBSCRIPTIONS_AUTH_CHANNEL$
|
|
155
|
+
result = await rpc.call(SUBSCRIPTIONS_AUTH_CHANNEL$2, endpoint, payload);
|
|
150
156
|
} catch (error) {
|
|
151
157
|
throw new SubscriptionsAuthError(error instanceof Error ? error.message : String(error));
|
|
152
158
|
}
|
|
@@ -164,12 +170,12 @@ function messageOf(error) {
|
|
|
164
170
|
* @param params - `{name}` template params.
|
|
165
171
|
* @returns the template with params substituted.
|
|
166
172
|
*/
|
|
167
|
-
function fallbackTranslate$
|
|
173
|
+
function fallbackTranslate$2(key, params) {
|
|
168
174
|
let text = en[key];
|
|
169
175
|
for (const [name, value] of Object.entries(params ?? {})) text = text.replaceAll(`{${name}}`, String(value));
|
|
170
176
|
return text;
|
|
171
177
|
}
|
|
172
|
-
const styles$
|
|
178
|
+
const styles$2 = {
|
|
173
179
|
section: {
|
|
174
180
|
display: "flex",
|
|
175
181
|
flexDirection: "column",
|
|
@@ -382,7 +388,7 @@ function usageBarColor(usedPercent) {
|
|
|
382
388
|
*/
|
|
383
389
|
function SubscriptionsSection(props) {
|
|
384
390
|
const { rpc } = props;
|
|
385
|
-
const t = props.t ?? fallbackTranslate$
|
|
391
|
+
const t = props.t ?? fallbackTranslate$2;
|
|
386
392
|
const [statuses, setStatuses] = (0, react.useState)({});
|
|
387
393
|
const [errors, setErrors] = (0, react.useState)({});
|
|
388
394
|
const [manualDrafts, setManualDrafts] = (0, react.useState)({
|
|
@@ -589,13 +595,13 @@ function SubscriptionsSection(props) {
|
|
|
589
595
|
refresh
|
|
590
596
|
]);
|
|
591
597
|
if (rpc === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
592
|
-
style: styles$
|
|
598
|
+
style: styles$2.intro,
|
|
593
599
|
children: t("unavailable")
|
|
594
600
|
});
|
|
595
601
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
596
|
-
style: styles$
|
|
602
|
+
style: styles$2.section,
|
|
597
603
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
598
|
-
style: styles$
|
|
604
|
+
style: styles$2.intro,
|
|
599
605
|
children: t("intro")
|
|
600
606
|
}), PROVIDERS.map(({ id, name }) => {
|
|
601
607
|
const status = statuses[id];
|
|
@@ -604,36 +610,36 @@ function SubscriptionsSection(props) {
|
|
|
604
610
|
const usageError = usageErrors[id];
|
|
605
611
|
const showUsage = status?.loggedIn === true && usage?.supported !== false && (usage !== void 0 || usageError !== void 0 || usageLoading[id] === true);
|
|
606
612
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
607
|
-
style: styles$
|
|
613
|
+
style: styles$2.card,
|
|
608
614
|
children: [
|
|
609
615
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
610
|
-
style: styles$
|
|
616
|
+
style: styles$2.cardHeader,
|
|
611
617
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
|
|
612
|
-
...styles$
|
|
618
|
+
...styles$2.dot,
|
|
613
619
|
background: dotColor(status)
|
|
614
620
|
} }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
615
|
-
style: styles$
|
|
621
|
+
style: styles$2.name,
|
|
616
622
|
children: name
|
|
617
623
|
})]
|
|
618
624
|
}),
|
|
619
625
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
620
|
-
style: styles$
|
|
626
|
+
style: styles$2.statusLine,
|
|
621
627
|
children: statusText(t, status)
|
|
622
628
|
}),
|
|
623
629
|
status?.detail !== void 0 && status.detail !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
624
|
-
style: styles$
|
|
630
|
+
style: styles$2.statusLine,
|
|
625
631
|
children: status.detail
|
|
626
632
|
}),
|
|
627
633
|
errors[id] !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
628
|
-
style: styles$
|
|
634
|
+
style: styles$2.errorLine,
|
|
629
635
|
children: errors[id]
|
|
630
636
|
}),
|
|
631
637
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
632
|
-
style: styles$
|
|
638
|
+
style: styles$2.actions,
|
|
633
639
|
children: [
|
|
634
640
|
!busy && status?.loggedIn !== true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
635
641
|
type: "button",
|
|
636
|
-
style: styles$
|
|
642
|
+
style: styles$2.button,
|
|
637
643
|
onClick: () => {
|
|
638
644
|
login(id);
|
|
639
645
|
},
|
|
@@ -641,7 +647,7 @@ function SubscriptionsSection(props) {
|
|
|
641
647
|
}),
|
|
642
648
|
busy && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
643
649
|
type: "button",
|
|
644
|
-
style: styles$
|
|
650
|
+
style: styles$2.button,
|
|
645
651
|
onClick: () => {
|
|
646
652
|
cancel(id);
|
|
647
653
|
},
|
|
@@ -649,7 +655,7 @@ function SubscriptionsSection(props) {
|
|
|
649
655
|
}),
|
|
650
656
|
status?.loggedIn === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
651
657
|
type: "button",
|
|
652
|
-
style: styles$
|
|
658
|
+
style: styles$2.button,
|
|
653
659
|
onClick: () => {
|
|
654
660
|
logout(id, name);
|
|
655
661
|
},
|
|
@@ -658,23 +664,23 @@ function SubscriptionsSection(props) {
|
|
|
658
664
|
]
|
|
659
665
|
}),
|
|
660
666
|
showUsage && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
661
|
-
style: styles$
|
|
667
|
+
style: styles$2.usage,
|
|
662
668
|
children: [
|
|
663
669
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
664
|
-
style: styles$
|
|
670
|
+
style: styles$2.usageHeader,
|
|
665
671
|
children: [
|
|
666
672
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
667
|
-
style: styles$
|
|
673
|
+
style: styles$2.usageTitle,
|
|
668
674
|
children: t("usageTitle")
|
|
669
675
|
}),
|
|
670
676
|
usage?.plan !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
671
|
-
style: styles$
|
|
677
|
+
style: styles$2.usagePlan,
|
|
672
678
|
children: t("usagePlan", { plan: usage.plan })
|
|
673
679
|
}),
|
|
674
680
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
675
681
|
type: "button",
|
|
676
682
|
style: {
|
|
677
|
-
...styles$
|
|
683
|
+
...styles$2.usageRefresh,
|
|
678
684
|
...usageLoading[id] === true ? {
|
|
679
685
|
opacity: .5,
|
|
680
686
|
cursor: "default"
|
|
@@ -689,28 +695,28 @@ function SubscriptionsSection(props) {
|
|
|
689
695
|
]
|
|
690
696
|
}),
|
|
691
697
|
usage === void 0 && usageError === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
692
|
-
style: styles$
|
|
698
|
+
style: styles$2.statusLine,
|
|
693
699
|
children: t("usageLoading")
|
|
694
700
|
}),
|
|
695
701
|
usageError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
696
|
-
style: styles$
|
|
702
|
+
style: styles$2.errorLine,
|
|
697
703
|
children: t("usageError", { message: usageError })
|
|
698
704
|
}),
|
|
699
705
|
usage?.windows !== void 0 && usage.windows.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
700
|
-
style: styles$
|
|
706
|
+
style: styles$2.statusLine,
|
|
701
707
|
children: t("usageEmpty")
|
|
702
708
|
}),
|
|
703
709
|
(usage?.windows ?? []).map((window$1, index) => {
|
|
704
710
|
const percent = Math.min(100, Math.max(0, window$1.usedPercent));
|
|
705
711
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
706
|
-
style: styles$
|
|
712
|
+
style: styles$2.usageRow,
|
|
707
713
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
708
|
-
style: styles$
|
|
714
|
+
style: styles$2.usageMeta,
|
|
709
715
|
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() })}`] })]
|
|
710
716
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
711
|
-
style: styles$
|
|
717
|
+
style: styles$2.usageTrack,
|
|
712
718
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
|
|
713
|
-
...styles$
|
|
719
|
+
...styles$2.usageFill,
|
|
714
720
|
width: `${String(percent)}%`,
|
|
715
721
|
background: usageBarColor(percent)
|
|
716
722
|
} })
|
|
@@ -720,11 +726,11 @@ function SubscriptionsSection(props) {
|
|
|
720
726
|
]
|
|
721
727
|
}),
|
|
722
728
|
busy && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
723
|
-
style: styles$
|
|
729
|
+
style: styles$2.manual,
|
|
724
730
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("manualSummary") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
725
|
-
style: styles$
|
|
731
|
+
style: styles$2.manualRow,
|
|
726
732
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
727
|
-
style: styles$
|
|
733
|
+
style: styles$2.manualInput,
|
|
728
734
|
value: manualDrafts[id],
|
|
729
735
|
placeholder: t("manualPlaceholder"),
|
|
730
736
|
onChange: (event) => setManualDrafts((prev) => ({
|
|
@@ -733,7 +739,7 @@ function SubscriptionsSection(props) {
|
|
|
733
739
|
}))
|
|
734
740
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
735
741
|
type: "button",
|
|
736
|
-
style: styles$
|
|
742
|
+
style: styles$2.button,
|
|
737
743
|
onClick: () => {
|
|
738
744
|
submitManual(id);
|
|
739
745
|
},
|
|
@@ -750,9 +756,9 @@ function SubscriptionsSection(props) {
|
|
|
750
756
|
//#endregion
|
|
751
757
|
//#region src/client/ImageGenerateToolview.tsx
|
|
752
758
|
/** Logical RPC channel served by the node half of this plugin. */
|
|
753
|
-
const SUBSCRIPTIONS_AUTH_CHANNEL = "/subscriptions-auth";
|
|
759
|
+
const SUBSCRIPTIONS_AUTH_CHANNEL$1 = "/subscriptions-auth";
|
|
754
760
|
/** Title prompt truncation budget (characters). */
|
|
755
|
-
const PROMPT_MAX_LENGTH = 60;
|
|
761
|
+
const PROMPT_MAX_LENGTH$1 = 60;
|
|
756
762
|
/**
|
|
757
763
|
* Call one `/subscriptions-auth` endpoint and unwrap the business result.
|
|
758
764
|
* @param rpc - Connection RPC caller.
|
|
@@ -761,7 +767,7 @@ const PROMPT_MAX_LENGTH = 60;
|
|
|
761
767
|
* @returns the success value, cast by the caller to the endpoint's shape.
|
|
762
768
|
*/
|
|
763
769
|
async function callSubscriptionsAuth(rpc, endpoint, payload) {
|
|
764
|
-
const result = await rpc.call(SUBSCRIPTIONS_AUTH_CHANNEL, endpoint, payload);
|
|
770
|
+
const result = await rpc.call(SUBSCRIPTIONS_AUTH_CHANNEL$1, endpoint, payload);
|
|
765
771
|
if (!result.ok) throw new Error(result.error.message);
|
|
766
772
|
return result.value;
|
|
767
773
|
}
|
|
@@ -780,13 +786,13 @@ function createImageLoader(rpc) {
|
|
|
780
786
|
* @param params - `{name}` template params.
|
|
781
787
|
* @returns the template with params substituted.
|
|
782
788
|
*/
|
|
783
|
-
function fallbackTranslate(key, params) {
|
|
789
|
+
function fallbackTranslate$1(key, params) {
|
|
784
790
|
let text = en[key];
|
|
785
791
|
for (const [name, value] of Object.entries(params ?? {})) text = text.replaceAll(`{${name}}`, String(value));
|
|
786
792
|
return text;
|
|
787
793
|
}
|
|
788
794
|
/** Extract the prompt from the call's raw args JSON; falls back to the first string value, then the raw line. */
|
|
789
|
-
function derivePrompt(argsRaw) {
|
|
795
|
+
function derivePrompt$1(argsRaw) {
|
|
790
796
|
let parsed;
|
|
791
797
|
try {
|
|
792
798
|
parsed = JSON.parse(argsRaw);
|
|
@@ -803,10 +809,10 @@ function derivePrompt(argsRaw) {
|
|
|
803
809
|
}
|
|
804
810
|
}
|
|
805
811
|
const line = (prompt ?? argsRaw).split("\n", 1)[0] ?? "";
|
|
806
|
-
return line.length > PROMPT_MAX_LENGTH ? `${line.slice(0, PROMPT_MAX_LENGTH)}…` : line;
|
|
812
|
+
return line.length > PROMPT_MAX_LENGTH$1 ? `${line.slice(0, PROMPT_MAX_LENGTH$1)}…` : line;
|
|
807
813
|
}
|
|
808
814
|
/** Flatten a settled result's text blocks (the degraded text-only route and the error line). */
|
|
809
|
-
function resultText(block) {
|
|
815
|
+
function resultText$1(block) {
|
|
810
816
|
if (!("kind" in block)) return "";
|
|
811
817
|
const parts = [];
|
|
812
818
|
for (const part of block.content) if (part.type === "text") parts.push(part.text);
|
|
@@ -820,7 +826,7 @@ function resultImages(block) {
|
|
|
820
826
|
for (const part of block.content) if (part.type === "image") images.push({ attachment: part.attachment });
|
|
821
827
|
return images;
|
|
822
828
|
}
|
|
823
|
-
const styles = {
|
|
829
|
+
const styles$1 = {
|
|
824
830
|
container: {
|
|
825
831
|
display: "flex",
|
|
826
832
|
flexDirection: "column",
|
|
@@ -874,12 +880,12 @@ const styles = {
|
|
|
874
880
|
*/
|
|
875
881
|
function ImageGenerateToolview(props) {
|
|
876
882
|
const { block, load } = props;
|
|
877
|
-
const t = props.t ?? fallbackTranslate;
|
|
883
|
+
const t = props.t ?? fallbackTranslate$1;
|
|
878
884
|
if (block === void 0) return null;
|
|
879
885
|
const settled = "kind" in block;
|
|
880
|
-
const title = `image_generate: ${derivePrompt((settled ? block.call?.argsRaw : block.argsRaw) ?? "")}`;
|
|
886
|
+
const title = `image_generate: ${derivePrompt$1((settled ? block.call?.argsRaw : block.argsRaw) ?? "")}`;
|
|
881
887
|
const images = resultImages(block);
|
|
882
|
-
const text = settled ? resultText(block) : "";
|
|
888
|
+
const text = settled ? resultText$1(block) : "";
|
|
883
889
|
const labels = {
|
|
884
890
|
image: t("image"),
|
|
885
891
|
open: t("viewImage"),
|
|
@@ -892,24 +898,24 @@ function ImageGenerateToolview(props) {
|
|
|
892
898
|
}
|
|
893
899
|
};
|
|
894
900
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
895
|
-
style: styles.container,
|
|
901
|
+
style: styles$1.container,
|
|
896
902
|
children: [
|
|
897
903
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
898
|
-
style: styles.row,
|
|
904
|
+
style: styles$1.row,
|
|
899
905
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
900
|
-
style: styles.icon,
|
|
906
|
+
style: styles$1.icon,
|
|
901
907
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconSparkle16, { size: 14 })
|
|
902
908
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
903
|
-
style: styles.title,
|
|
909
|
+
style: styles$1.title,
|
|
904
910
|
children: title
|
|
905
911
|
})]
|
|
906
912
|
}),
|
|
907
913
|
!settled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
908
|
-
style: styles.subtle,
|
|
914
|
+
style: styles$1.subtle,
|
|
909
915
|
children: t("generating")
|
|
910
916
|
}),
|
|
911
917
|
settled && block.isError && text !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
912
|
-
style: styles.error,
|
|
918
|
+
style: styles$1.error,
|
|
913
919
|
children: text.split("\n", 1)[0]
|
|
914
920
|
}),
|
|
915
921
|
settled && !block.isError && images.length > 0 && load !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_attachment.ImageGallery, {
|
|
@@ -919,6 +925,221 @@ function ImageGenerateToolview(props) {
|
|
|
919
925
|
labels
|
|
920
926
|
}),
|
|
921
927
|
settled && !block.isError && images.length === 0 && text !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
928
|
+
style: styles$1.output,
|
|
929
|
+
children: text
|
|
930
|
+
})
|
|
931
|
+
]
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
//#endregion
|
|
936
|
+
//#region src/client/VideoGenerateToolview.tsx
|
|
937
|
+
/** Logical RPC channel served by the node half of this plugin. */
|
|
938
|
+
const SUBSCRIPTIONS_AUTH_CHANNEL = "/subscriptions-auth";
|
|
939
|
+
/** Title prompt truncation budget (characters). */
|
|
940
|
+
const PROMPT_MAX_LENGTH = 60;
|
|
941
|
+
/**
|
|
942
|
+
* Build the video loader over the `/subscriptions-auth` `video` endpoint.
|
|
943
|
+
* @param rpc - Connection RPC caller.
|
|
944
|
+
* @returns loader resolving a bare file name to the decoded bytes.
|
|
945
|
+
*/
|
|
946
|
+
function createVideoLoader(rpc) {
|
|
947
|
+
return async (name) => {
|
|
948
|
+
const result = await rpc.call(SUBSCRIPTIONS_AUTH_CHANNEL, "video", { name });
|
|
949
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
950
|
+
return result.value;
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* English-dictionary fallback for a missing locale seat (standalone renders);
|
|
955
|
+
* the framework always supplies the namespace-bound one.
|
|
956
|
+
*/
|
|
957
|
+
function fallbackTranslate(key, params) {
|
|
958
|
+
let text = en[key];
|
|
959
|
+
for (const [name, value] of Object.entries(params ?? {})) text = text.replaceAll(`{${name}}`, String(value));
|
|
960
|
+
return text;
|
|
961
|
+
}
|
|
962
|
+
/** Extract the prompt from the call's raw args JSON; falls back to the first string value, then the raw line. */
|
|
963
|
+
function derivePrompt(argsRaw) {
|
|
964
|
+
let parsed;
|
|
965
|
+
try {
|
|
966
|
+
parsed = JSON.parse(argsRaw);
|
|
967
|
+
} catch {
|
|
968
|
+
parsed = void 0;
|
|
969
|
+
}
|
|
970
|
+
let prompt;
|
|
971
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
972
|
+
const args = parsed;
|
|
973
|
+
if (typeof args.prompt === "string" && args.prompt !== "") prompt = args.prompt;
|
|
974
|
+
else for (const value of Object.values(args)) if (typeof value === "string" && value !== "") {
|
|
975
|
+
prompt = value;
|
|
976
|
+
break;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
const line = (prompt ?? argsRaw).split("\n", 1)[0] ?? "";
|
|
980
|
+
return line.length > PROMPT_MAX_LENGTH ? `${line.slice(0, PROMPT_MAX_LENGTH)}…` : line;
|
|
981
|
+
}
|
|
982
|
+
/** Flatten a settled result's text blocks (the fallback body and the error line). */
|
|
983
|
+
function resultText(block) {
|
|
984
|
+
if (!("kind" in block)) return "";
|
|
985
|
+
const parts = [];
|
|
986
|
+
for (const part of block.content) if (part.type === "text") parts.push(part.text);
|
|
987
|
+
if (parts.length === 0 && block.error !== void 0) parts.push(`${block.error.name}: ${block.error.code}`);
|
|
988
|
+
return parts.join("\n");
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* The generated video's bare file name: presentation meta first (top-level
|
|
992
|
+
* dispatches), then the render text's "Saved video to …" line (nested
|
|
993
|
+
* dispatches compute no meta).
|
|
994
|
+
*/
|
|
995
|
+
function resolveFileName(block) {
|
|
996
|
+
if (!("kind" in block)) return void 0;
|
|
997
|
+
const meta = block.meta;
|
|
998
|
+
if (typeof meta === "object" && meta !== null) {
|
|
999
|
+
const fileName = meta.fileName;
|
|
1000
|
+
if (typeof fileName === "string" && fileName.length > 0) return fileName;
|
|
1001
|
+
}
|
|
1002
|
+
const match = /^Saved video to (.+\.mp4)/m.exec(resultText(block));
|
|
1003
|
+
if (match === null) return void 0;
|
|
1004
|
+
const path = match[1];
|
|
1005
|
+
return path.slice(path.lastIndexOf("/") + 1);
|
|
1006
|
+
}
|
|
1007
|
+
/** Decode a base64 payload into bytes (browser-side; no Buffer). */
|
|
1008
|
+
function base64Bytes(dataBase64) {
|
|
1009
|
+
const binary = atob(dataBase64);
|
|
1010
|
+
const bytes = new Uint8Array(binary.length);
|
|
1011
|
+
for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index);
|
|
1012
|
+
return bytes;
|
|
1013
|
+
}
|
|
1014
|
+
const styles = {
|
|
1015
|
+
container: {
|
|
1016
|
+
display: "flex",
|
|
1017
|
+
flexDirection: "column",
|
|
1018
|
+
gap: 6,
|
|
1019
|
+
padding: "4px 0"
|
|
1020
|
+
},
|
|
1021
|
+
row: {
|
|
1022
|
+
display: "flex",
|
|
1023
|
+
alignItems: "center",
|
|
1024
|
+
gap: 6,
|
|
1025
|
+
minWidth: 0
|
|
1026
|
+
},
|
|
1027
|
+
icon: {
|
|
1028
|
+
display: "inline-flex",
|
|
1029
|
+
flexShrink: 0,
|
|
1030
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
1031
|
+
},
|
|
1032
|
+
title: {
|
|
1033
|
+
fontSize: 13,
|
|
1034
|
+
lineHeight: "20px",
|
|
1035
|
+
color: "var(--dsw-alias-label-primary)",
|
|
1036
|
+
overflow: "hidden",
|
|
1037
|
+
textOverflow: "ellipsis",
|
|
1038
|
+
whiteSpace: "nowrap"
|
|
1039
|
+
},
|
|
1040
|
+
subtle: {
|
|
1041
|
+
margin: 0,
|
|
1042
|
+
fontSize: 12,
|
|
1043
|
+
lineHeight: "18px",
|
|
1044
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
1045
|
+
},
|
|
1046
|
+
output: {
|
|
1047
|
+
margin: 0,
|
|
1048
|
+
fontSize: 12,
|
|
1049
|
+
lineHeight: "18px",
|
|
1050
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
1051
|
+
whiteSpace: "pre-wrap",
|
|
1052
|
+
overflowWrap: "anywhere"
|
|
1053
|
+
},
|
|
1054
|
+
error: {
|
|
1055
|
+
margin: 0,
|
|
1056
|
+
fontSize: 12,
|
|
1057
|
+
lineHeight: "18px",
|
|
1058
|
+
color: "var(--dsw-alias-state-error-primary)"
|
|
1059
|
+
},
|
|
1060
|
+
video: {
|
|
1061
|
+
display: "block",
|
|
1062
|
+
maxWidth: 480,
|
|
1063
|
+
width: "100%",
|
|
1064
|
+
borderRadius: 8,
|
|
1065
|
+
backgroundColor: "var(--dsw-alias-fill-tertiary)"
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
/**
|
|
1069
|
+
* The `video_generate` keyed toolview component.
|
|
1070
|
+
* @param props - owner share, inject face, and locale seat (spread flat).
|
|
1071
|
+
* @returns the call row plus, once settled, the player / text / error body.
|
|
1072
|
+
*/
|
|
1073
|
+
function VideoGenerateToolview(props) {
|
|
1074
|
+
const { block, loadVideo } = props;
|
|
1075
|
+
const t = props.t ?? fallbackTranslate;
|
|
1076
|
+
const settled = block !== void 0 && "kind" in block;
|
|
1077
|
+
const isError = settled && block.isError;
|
|
1078
|
+
const fileName = block !== void 0 && settled && !isError ? resolveFileName(block) : void 0;
|
|
1079
|
+
const [load, setLoad] = (0, react.useState)({ phase: "loading" });
|
|
1080
|
+
(0, react.useEffect)(() => {
|
|
1081
|
+
if (fileName === void 0 || loadVideo === void 0) return;
|
|
1082
|
+
let cancelled = false;
|
|
1083
|
+
let objectUrl;
|
|
1084
|
+
setLoad({ phase: "loading" });
|
|
1085
|
+
loadVideo(fileName).then((video) => {
|
|
1086
|
+
if (cancelled) return;
|
|
1087
|
+
objectUrl = URL.createObjectURL(new Blob([base64Bytes(video.dataBase64).slice()], { type: video.mediaType }));
|
|
1088
|
+
setLoad({
|
|
1089
|
+
phase: "ready",
|
|
1090
|
+
url: objectUrl
|
|
1091
|
+
});
|
|
1092
|
+
}, (error) => {
|
|
1093
|
+
if (cancelled) return;
|
|
1094
|
+
setLoad({
|
|
1095
|
+
phase: "failed",
|
|
1096
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1097
|
+
});
|
|
1098
|
+
});
|
|
1099
|
+
return () => {
|
|
1100
|
+
cancelled = true;
|
|
1101
|
+
if (objectUrl !== void 0) URL.revokeObjectURL(objectUrl);
|
|
1102
|
+
};
|
|
1103
|
+
}, [fileName, loadVideo]);
|
|
1104
|
+
if (block === void 0) return null;
|
|
1105
|
+
const title = `video_generate: ${derivePrompt((settled ? block.call?.argsRaw : block.argsRaw) ?? "")}`;
|
|
1106
|
+
const text = settled ? resultText(block) : "";
|
|
1107
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1108
|
+
style: styles.container,
|
|
1109
|
+
children: [
|
|
1110
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1111
|
+
style: styles.row,
|
|
1112
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1113
|
+
style: styles.icon,
|
|
1114
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconSparkle16, { size: 14 })
|
|
1115
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1116
|
+
style: styles.title,
|
|
1117
|
+
children: title
|
|
1118
|
+
})]
|
|
1119
|
+
}),
|
|
1120
|
+
!settled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1121
|
+
style: styles.subtle,
|
|
1122
|
+
children: t("generatingVideo")
|
|
1123
|
+
}),
|
|
1124
|
+
settled && isError && text !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1125
|
+
style: styles.error,
|
|
1126
|
+
children: text.split("\n", 1)[0]
|
|
1127
|
+
}),
|
|
1128
|
+
settled && !isError && fileName !== void 0 && load.phase === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1129
|
+
style: styles.subtle,
|
|
1130
|
+
children: t("videoLoading")
|
|
1131
|
+
}),
|
|
1132
|
+
settled && !isError && fileName !== void 0 && load.phase === "failed" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1133
|
+
style: styles.error,
|
|
1134
|
+
children: t("videoLoadFailed", { message: load.message })
|
|
1135
|
+
}),
|
|
1136
|
+
settled && !isError && fileName !== void 0 && load.phase === "ready" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("video", {
|
|
1137
|
+
style: styles.video,
|
|
1138
|
+
src: load.url,
|
|
1139
|
+
controls: true,
|
|
1140
|
+
preload: "metadata"
|
|
1141
|
+
}),
|
|
1142
|
+
settled && !isError && fileName === void 0 && text !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
922
1143
|
style: styles.output,
|
|
923
1144
|
children: text
|
|
924
1145
|
})
|
|
@@ -971,6 +1192,13 @@ function apply(ctx) {
|
|
|
971
1192
|
locale: NS,
|
|
972
1193
|
inject: toolviewInjected
|
|
973
1194
|
}, ImageGenerateToolview));
|
|
1195
|
+
const videoToolviewInjected = () => ({ loadVideo: createVideoLoader(connection.rpc) });
|
|
1196
|
+
ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
|
|
1197
|
+
name: "tool.call.toolview",
|
|
1198
|
+
key: "video_generate",
|
|
1199
|
+
locale: NS,
|
|
1200
|
+
inject: videoToolviewInjected
|
|
1201
|
+
}, VideoGenerateToolview));
|
|
974
1202
|
}
|
|
975
1203
|
|
|
976
1204
|
//#endregion
|