@klarkxy/dsh-git-commit 0.3.2 → 0.3.4
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 +2 -1
- package/docs/README.zh-CN.md +2 -1
- package/lib/client.inner.cjs +574 -93
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +574 -93
- package/lib/contracts.d.ts +76 -28
- package/lib/contracts.js +2 -2
- package/lib/contracts2.d.ts +2 -2
- package/lib/contracts2.js +1 -2
- package/lib/contracts2.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +17 -12
- package/lib/index.js.map +1 -1
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +16 -11
package/lib/client.js
CHANGED
|
@@ -102,6 +102,7 @@ function parseModelMenuChoices(value, bound) {
|
|
|
102
102
|
provider,
|
|
103
103
|
model,
|
|
104
104
|
label: `${providerName} / ${name}`,
|
|
105
|
+
providerLabel: providerName,
|
|
105
106
|
efforts
|
|
106
107
|
});
|
|
107
108
|
}
|
|
@@ -148,11 +149,11 @@ function modelMenuShortName(choice) {
|
|
|
148
149
|
/** Group catalog choices by provider and filter them by a case-insensitive query. */
|
|
149
150
|
function groupModelMenuChoices(choices, query) {
|
|
150
151
|
const needle = query.trim().toLowerCase();
|
|
151
|
-
const matched = needle ? choices.filter((choice) => choice.label.toLowerCase().includes(needle) || choice.model.toLowerCase().includes(needle) || choice.provider.toLowerCase().includes(needle)) : choices;
|
|
152
|
+
const matched = needle ? choices.filter((choice) => choice.label.toLowerCase().includes(needle) || choice.model.toLowerCase().includes(needle) || choice.provider.toLowerCase().includes(needle) || (choice.providerLabel?.toLowerCase().includes(needle) ?? false)) : choices;
|
|
152
153
|
const groups = [];
|
|
153
154
|
for (const choice of matched) {
|
|
154
155
|
const sep = choice.label.indexOf(" / ");
|
|
155
|
-
const name = sep > 0 ? choice.label.slice(0, sep) : choice.provider;
|
|
156
|
+
const name = choice.providerLabel ?? (sep > 0 ? choice.label.slice(0, sep) : choice.provider);
|
|
156
157
|
const last = groups.at(-1);
|
|
157
158
|
if (last && last.provider === choice.provider) last.items.push(choice);
|
|
158
159
|
else groups.push({
|
|
@@ -379,7 +380,7 @@ function ModelMenu(props) {
|
|
|
379
380
|
empty: "No matching models",
|
|
380
381
|
clear: "Clear search"
|
|
381
382
|
};
|
|
382
|
-
const { open, onOpenChange, anchor, choices, selected, onPick, leading = [], onPickLeading, efforts = [], selectedEffort, onPickEffort, modelLabel = copy.model, modelValue = "—", effortLabel = copy.effort, effortValue, defaultEffortLabel = copy.default, searchPlaceholder = copy.search, emptyLabel = copy.empty, clearSearchLabel = copy.clear, className, listClassName } = props;
|
|
383
|
+
const { open, onOpenChange, anchor, choices, selected, onPick, leading = [], onPickLeading, efforts = [], selectedEffort, onPickEffort, modelLabel = copy.model, modelValue = "—", effortLabel = copy.effort, effortValue, defaultEffortLabel = copy.default, searchPlaceholder = copy.search, emptyLabel = copy.empty, clearSearchLabel = copy.clear, initialPane = "root", alwaysShowSearch = false, reasoningSlider = false, menuHeader, disabled = false, busy = false, closeOnPick = true, closeOnDisable = true, className, listClassName } = props;
|
|
383
384
|
const rootRef = (0, react.useRef)(null);
|
|
384
385
|
const openRef = (0, react.useRef)(open);
|
|
385
386
|
openRef.current = open;
|
|
@@ -394,9 +395,10 @@ function ModelMenu(props) {
|
|
|
394
395
|
const [pane, setPane] = (0, react.useState)("root");
|
|
395
396
|
const [query, setQuery] = (0, react.useState)("");
|
|
396
397
|
const [highlightedIndex, setHighlightedIndex] = (0, react.useState)(null);
|
|
398
|
+
const [keyNav, setKeyNav] = (0, react.useState)(0);
|
|
397
399
|
const [menuPos, setMenuPos] = (0, react.useState)(null);
|
|
398
400
|
const id = (0, react.useId)();
|
|
399
|
-
const showSearch = choices.length > 4;
|
|
401
|
+
const showSearch = alwaysShowSearch || choices.length > 4;
|
|
400
402
|
const effortRows = (0, react.useMemo)(() => [{
|
|
401
403
|
id: "",
|
|
402
404
|
name: defaultEffortLabel
|
|
@@ -413,7 +415,12 @@ function ModelMenu(props) {
|
|
|
413
415
|
items: (0, _deepseek_ai_dsh_client_ui_primitives.rankByName)(group.items.map((choice) => ({
|
|
414
416
|
choice,
|
|
415
417
|
name: modelMenuShortName(choice),
|
|
416
|
-
label:
|
|
418
|
+
label: [
|
|
419
|
+
choice.provider,
|
|
420
|
+
choice.providerLabel,
|
|
421
|
+
choice.model,
|
|
422
|
+
choice.label
|
|
423
|
+
].filter(Boolean).join(" ")
|
|
417
424
|
})), showSearch ? query.trim() : "")
|
|
418
425
|
})).filter((group) => group.items.length > 0), [
|
|
419
426
|
choices,
|
|
@@ -445,10 +452,12 @@ function ModelMenu(props) {
|
|
|
445
452
|
paneFocus.current = "drill";
|
|
446
453
|
setPane(next);
|
|
447
454
|
};
|
|
448
|
-
/** Leave a drilled pane for the
|
|
455
|
+
/** Leave a drilled pane for the entry pane, handing the keyboard back to it.
|
|
456
|
+
* A root entry refocuses the cell that opened the pane; a search-first
|
|
457
|
+
* (model) entry refocuses that pane's own intent. */
|
|
449
458
|
const back = (from) => {
|
|
450
|
-
paneFocus.current = from;
|
|
451
|
-
setPane(
|
|
459
|
+
paneFocus.current = initialPane === "root" ? from : "drill";
|
|
460
|
+
setPane(initialPane);
|
|
452
461
|
};
|
|
453
462
|
const moveFocus = (offset) => {
|
|
454
463
|
const items = itemRefs.current.filter((item) => item !== null && !item.disabled);
|
|
@@ -488,10 +497,18 @@ function ModelMenu(props) {
|
|
|
488
497
|
}, [requestedOpen, open]);
|
|
489
498
|
(0, react.useEffect)(() => {
|
|
490
499
|
if (!open) return;
|
|
491
|
-
setPane(
|
|
500
|
+
setPane(initialPane);
|
|
501
|
+
if (initialPane === "model") paneFocus.current = "drill";
|
|
492
502
|
setQuery("");
|
|
493
503
|
setHighlightedIndex(null);
|
|
494
|
-
}, [open]);
|
|
504
|
+
}, [open, initialPane]);
|
|
505
|
+
(0, react.useEffect)(() => {
|
|
506
|
+
if (disabled && open && closeOnDisable) changeOpen(false);
|
|
507
|
+
}, [
|
|
508
|
+
disabled,
|
|
509
|
+
open,
|
|
510
|
+
closeOnDisable
|
|
511
|
+
]);
|
|
495
512
|
(0, react.useEffect)(() => {
|
|
496
513
|
if (!open) return;
|
|
497
514
|
const closeOutside = (event) => {
|
|
@@ -499,9 +516,25 @@ function ModelMenu(props) {
|
|
|
499
516
|
if (rootRef.current?.contains(target) || menuRef.current?.contains(target)) return;
|
|
500
517
|
changeOpen(false);
|
|
501
518
|
};
|
|
519
|
+
const escapeOutside = (event) => {
|
|
520
|
+
if (event.key !== "Escape" || event.defaultPrevented || event.isComposing) return;
|
|
521
|
+
if (!openRef.current) return;
|
|
522
|
+
event.preventDefault();
|
|
523
|
+
if (pane !== initialPane) back(pane);
|
|
524
|
+
else close(true);
|
|
525
|
+
};
|
|
502
526
|
document.addEventListener("mousedown", closeOutside);
|
|
503
|
-
|
|
504
|
-
|
|
527
|
+
document.addEventListener("keydown", escapeOutside);
|
|
528
|
+
return () => {
|
|
529
|
+
document.removeEventListener("mousedown", closeOutside);
|
|
530
|
+
document.removeEventListener("keydown", escapeOutside);
|
|
531
|
+
};
|
|
532
|
+
}, [
|
|
533
|
+
open,
|
|
534
|
+
onOpenChange,
|
|
535
|
+
pane,
|
|
536
|
+
initialPane
|
|
537
|
+
]);
|
|
505
538
|
(0, react.useEffect)(() => {
|
|
506
539
|
const intent = paneFocus.current;
|
|
507
540
|
if (!open || menuPos === null || intent === null) return;
|
|
@@ -531,13 +564,21 @@ function ModelMenu(props) {
|
|
|
531
564
|
pane,
|
|
532
565
|
filteredGroups
|
|
533
566
|
]);
|
|
567
|
+
const modelPaneOpen = open && pane === "model";
|
|
568
|
+
const scrollSignal = !modelPaneOpen ? "closed" : keyNav === 0 ? "open" : `key:${keyNav}`;
|
|
569
|
+
const scrollSignalRef = (0, react.useRef)(null);
|
|
534
570
|
(0, react.useLayoutEffect)(() => {
|
|
535
|
-
if (
|
|
571
|
+
if (!modelPaneOpen || activeModelIndex < 0) {
|
|
572
|
+
if (!modelPaneOpen) scrollSignalRef.current = null;
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
if (scrollSignalRef.current === scrollSignal) return;
|
|
576
|
+
scrollSignalRef.current = scrollSignal;
|
|
577
|
+
itemRefs.current[leading.length + activeModelIndex]?.scrollIntoView({ block: "nearest" });
|
|
536
578
|
}, [
|
|
537
|
-
|
|
538
|
-
|
|
579
|
+
modelPaneOpen,
|
|
580
|
+
scrollSignal,
|
|
539
581
|
activeModelIndex,
|
|
540
|
-
visibleModels,
|
|
541
582
|
leading.length
|
|
542
583
|
]);
|
|
543
584
|
(0, react.useLayoutEffect)(() => {
|
|
@@ -552,7 +593,7 @@ function ModelMenu(props) {
|
|
|
552
593
|
let x = rect.right - lw, y = rect.top - 8 - lh;
|
|
553
594
|
if (lw > 0) x = Math.min(Math.max(x, margin), window.innerWidth - lw - margin);
|
|
554
595
|
if (lh > 0) y = Math.min(Math.max(y, margin), window.innerHeight - lh - margin);
|
|
555
|
-
setMenuPos({
|
|
596
|
+
setMenuPos((prev) => prev && prev.left === x && prev.top === y ? prev : {
|
|
556
597
|
left: x,
|
|
557
598
|
top: y
|
|
558
599
|
});
|
|
@@ -572,6 +613,13 @@ function ModelMenu(props) {
|
|
|
572
613
|
]);
|
|
573
614
|
function onKeyDown(event) {
|
|
574
615
|
if (event.defaultPrevented || event.nativeEvent.isComposing) return;
|
|
616
|
+
if (event.key === "Escape" && open) {
|
|
617
|
+
event.preventDefault();
|
|
618
|
+
if (pane !== initialPane) back(pane);
|
|
619
|
+
else close(true);
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
if (disabled) return;
|
|
575
623
|
if (!open && [
|
|
576
624
|
"Enter",
|
|
577
625
|
" ",
|
|
@@ -585,17 +633,12 @@ function ModelMenu(props) {
|
|
|
585
633
|
requestOpen();
|
|
586
634
|
return;
|
|
587
635
|
}
|
|
588
|
-
if (event.key === "Escape" && open) {
|
|
589
|
-
event.preventDefault();
|
|
590
|
-
if (pane !== "root") back(pane);
|
|
591
|
-
else close(true);
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
636
|
if (!open) return;
|
|
595
637
|
if (pane === "model" && showSearch && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
|
|
596
638
|
event.preventDefault();
|
|
597
639
|
if (visibleModels.length > 0) {
|
|
598
640
|
setHighlightedIndex((activeModelIndex + (event.key === "ArrowDown" ? 1 : -1) + visibleModels.length) % visibleModels.length);
|
|
641
|
+
setKeyNav((n) => n + 1);
|
|
599
642
|
searchRef.current?.focus();
|
|
600
643
|
}
|
|
601
644
|
return;
|
|
@@ -604,19 +647,19 @@ function ModelMenu(props) {
|
|
|
604
647
|
if (event.key === "Tab" && visibleModels.length === 0) return;
|
|
605
648
|
event.preventDefault();
|
|
606
649
|
const highlighted = visibleModels[activeModelIndex];
|
|
607
|
-
if (highlighted) {
|
|
650
|
+
if (highlighted && !busy) {
|
|
608
651
|
onPick({
|
|
609
652
|
provider: highlighted.provider,
|
|
610
653
|
model: highlighted.model
|
|
611
654
|
});
|
|
612
|
-
close(true);
|
|
655
|
+
if (closeOnPick) close(true);
|
|
613
656
|
}
|
|
614
657
|
return;
|
|
615
658
|
}
|
|
616
659
|
if (event.key === "Tab") {
|
|
617
660
|
if (event.shiftKey) {
|
|
618
661
|
event.preventDefault();
|
|
619
|
-
if (pane !==
|
|
662
|
+
if (pane !== initialPane) back(pane);
|
|
620
663
|
else close(true);
|
|
621
664
|
return;
|
|
622
665
|
}
|
|
@@ -640,7 +683,10 @@ function ModelMenu(props) {
|
|
|
640
683
|
}
|
|
641
684
|
}
|
|
642
685
|
function onBlur(event) {
|
|
643
|
-
|
|
686
|
+
const related = event.relatedTarget;
|
|
687
|
+
if (related instanceof Node && (rootRef.current?.contains(related) || menuRef.current?.contains(related) || !related.isConnected)) return;
|
|
688
|
+
if (!closeOnPick && !(related instanceof Node)) return;
|
|
689
|
+
if (busy && related == null) return;
|
|
644
690
|
if (open) changeOpen(false);
|
|
645
691
|
}
|
|
646
692
|
itemRefs.current = [];
|
|
@@ -661,11 +707,16 @@ function ModelMenu(props) {
|
|
|
661
707
|
"aria-label": modelLabel,
|
|
662
708
|
lang: language,
|
|
663
709
|
children: [
|
|
710
|
+
menuHeader ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
711
|
+
className: "dsh-model-menu-header",
|
|
712
|
+
children: menuHeader
|
|
713
|
+
}) : null,
|
|
664
714
|
pane === "root" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
665
715
|
ref: itemRef(),
|
|
666
716
|
type: "button",
|
|
667
717
|
role: "menuitem",
|
|
668
718
|
className: "dsh-model-menu-cell",
|
|
719
|
+
disabled,
|
|
669
720
|
onClick: () => drill("model"),
|
|
670
721
|
children: [
|
|
671
722
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -678,11 +729,12 @@ function ModelMenu(props) {
|
|
|
678
729
|
}),
|
|
679
730
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, { className: "dsh-model-menu-cellChevron" })
|
|
680
731
|
]
|
|
681
|
-
}), efforts.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
732
|
+
}), efforts.length > 0 && !reasoningSlider ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
682
733
|
ref: itemRef(),
|
|
683
734
|
type: "button",
|
|
684
735
|
role: "menuitem",
|
|
685
736
|
className: "dsh-model-menu-cell",
|
|
737
|
+
disabled,
|
|
686
738
|
onClick: () => drill("effort"),
|
|
687
739
|
children: [
|
|
688
740
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -701,6 +753,7 @@ function ModelMenu(props) {
|
|
|
701
753
|
className: "dsh-model-menu-searchRow",
|
|
702
754
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
703
755
|
ref: searchRef,
|
|
756
|
+
disabled,
|
|
704
757
|
className: `dsh-model-menu-search${query !== "" ? " dsh-model-menu-searchWithQuery" : ""}`,
|
|
705
758
|
type: "text",
|
|
706
759
|
role: "searchbox",
|
|
@@ -717,6 +770,7 @@ function ModelMenu(props) {
|
|
|
717
770
|
type: "button",
|
|
718
771
|
className: "dsh-model-menu-searchClear",
|
|
719
772
|
"aria-label": clearSearchLabel,
|
|
773
|
+
disabled,
|
|
720
774
|
onClick: () => {
|
|
721
775
|
setQuery("");
|
|
722
776
|
setHighlightedIndex(null);
|
|
@@ -739,10 +793,12 @@ function ModelMenu(props) {
|
|
|
739
793
|
type: "button",
|
|
740
794
|
role: "menuitemradio",
|
|
741
795
|
"aria-checked": item.selected === true,
|
|
796
|
+
disabled: disabled || busy,
|
|
742
797
|
className: `dsh-model-menu-option${item.selected === true ? " dsh-model-menu-selected" : ""}`,
|
|
743
798
|
onClick: () => {
|
|
799
|
+
if (disabled || busy) return;
|
|
744
800
|
onPickLeading?.(item.id);
|
|
745
|
-
close(true);
|
|
801
|
+
if (closeOnPick) close(true);
|
|
746
802
|
},
|
|
747
803
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
748
804
|
className: "dsh-model-menu-optionCopy",
|
|
@@ -768,6 +824,7 @@ function ModelMenu(props) {
|
|
|
768
824
|
"aria-checked": checked,
|
|
769
825
|
id: `${id}-model-${index}`,
|
|
770
826
|
tabIndex: showSearch ? -1 : 0,
|
|
827
|
+
disabled: disabled || busy,
|
|
771
828
|
onFocus: () => setHighlightedIndex(index),
|
|
772
829
|
"data-highlighted": index === activeModelIndex ? "" : void 0,
|
|
773
830
|
className: `dsh-model-menu-option dsh-model-menu-modelOption${checked ? " dsh-model-menu-selected" : ""}${index === activeModelIndex ? " dsh-model-menu-optionActive" : ""}`,
|
|
@@ -777,11 +834,12 @@ function ModelMenu(props) {
|
|
|
777
834
|
},
|
|
778
835
|
title: choice.label,
|
|
779
836
|
onClick: () => {
|
|
837
|
+
if (disabled || busy) return;
|
|
780
838
|
onPick({
|
|
781
839
|
provider: choice.provider,
|
|
782
840
|
model: choice.model
|
|
783
841
|
});
|
|
784
|
-
close(true);
|
|
842
|
+
if (closeOnPick) close(true);
|
|
785
843
|
},
|
|
786
844
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
787
845
|
className: "dsh-model-menu-optionCopy",
|
|
@@ -797,6 +855,14 @@ function ModelMenu(props) {
|
|
|
797
855
|
})
|
|
798
856
|
}, group.provider))]
|
|
799
857
|
}),
|
|
858
|
+
reasoningSlider && efforts.length > 0 && filteredGroups.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EffortSlider, {
|
|
859
|
+
label: effortLabel,
|
|
860
|
+
rows: effortRows,
|
|
861
|
+
selected: selectedEffort ?? "",
|
|
862
|
+
disabled,
|
|
863
|
+
busy,
|
|
864
|
+
onPick: (value) => onPickEffort?.(value)
|
|
865
|
+
}) : null,
|
|
800
866
|
filteredGroups.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
801
867
|
className: "dsh-model-menu-empty",
|
|
802
868
|
role: "status",
|
|
@@ -810,10 +876,12 @@ function ModelMenu(props) {
|
|
|
810
876
|
type: "button",
|
|
811
877
|
role: "menuitemradio",
|
|
812
878
|
"aria-checked": checked,
|
|
879
|
+
disabled: disabled || busy,
|
|
813
880
|
className: `dsh-model-menu-option${checked ? " dsh-model-menu-selected" : ""}`,
|
|
814
881
|
onClick: () => {
|
|
882
|
+
if (disabled || busy) return;
|
|
815
883
|
onPickEffort?.(row.id || void 0);
|
|
816
|
-
close(true);
|
|
884
|
+
if (closeOnPick) close(true);
|
|
817
885
|
},
|
|
818
886
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
819
887
|
className: "dsh-model-menu-optionCopy",
|
|
@@ -832,7 +900,8 @@ function ModelMenu(props) {
|
|
|
832
900
|
const trigger = (0, react.isValidElement)(anchor) ? (0, react.cloneElement)(anchor, {
|
|
833
901
|
"aria-haspopup": "menu",
|
|
834
902
|
"aria-expanded": open,
|
|
835
|
-
"aria-controls": open ? `${id}-menu` : void 0
|
|
903
|
+
"aria-controls": open ? `${id}-menu` : void 0,
|
|
904
|
+
"aria-disabled": disabled || void 0
|
|
836
905
|
}) : anchor;
|
|
837
906
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
838
907
|
ref: rootRef,
|
|
@@ -840,7 +909,7 @@ function ModelMenu(props) {
|
|
|
840
909
|
onKeyDown,
|
|
841
910
|
onBlur,
|
|
842
911
|
onClick: (event) => {
|
|
843
|
-
if (event.defaultPrevented) return;
|
|
912
|
+
if (disabled || event.defaultPrevented) return;
|
|
844
913
|
const button = event.target instanceof Element ? event.target.closest("button") : null;
|
|
845
914
|
if (!(button instanceof HTMLButtonElement) || button.disabled || !rootRef.current?.contains(button)) return;
|
|
846
915
|
requestOpen();
|
|
@@ -852,6 +921,102 @@ function ModelMenu(props) {
|
|
|
852
921
|
});
|
|
853
922
|
}
|
|
854
923
|
/**
|
|
924
|
+
* Discrete reasoning-effort slider shown under the model list in enhanced mode.
|
|
925
|
+
*
|
|
926
|
+
* A native range input (`step=1`) over exactly the rows the caller supplies —
|
|
927
|
+
* the default row plus each effort the selected model advertises — so it can
|
|
928
|
+
* never invent a level. It previews locally while dragging or arrowing and
|
|
929
|
+
* commits only when the gesture ends (pointer up, key up, or blur), so a drag
|
|
930
|
+
* does not write on every step. The menu stays open, so adjusting the effort
|
|
931
|
+
* never loses the model selection or the search query.
|
|
932
|
+
*
|
|
933
|
+
* The input itself stays the entire interaction and accessibility surface —
|
|
934
|
+
* keyboard, ARIA, disabled and the native step snapping all come from it —
|
|
935
|
+
* while the track, gradient fill and thumb below are decorative layers painted
|
|
936
|
+
* from the `--_p` percentage the preview position sets on the rail. Value
|
|
937
|
+
* changes only update that custom property, so the CSS transitions animate
|
|
938
|
+
* between levels without remounting nodes or restarting an animation.
|
|
939
|
+
*/
|
|
940
|
+
function EffortSlider(props) {
|
|
941
|
+
const { label, rows, selected, disabled = false, busy = false, onPick } = props;
|
|
942
|
+
const active = Math.max(0, rows.findIndex((row) => row.id === selected));
|
|
943
|
+
const [position, setPosition] = (0, react.useState)(active);
|
|
944
|
+
const positionRef = (0, react.useRef)(position);
|
|
945
|
+
(0, react.useEffect)(() => {
|
|
946
|
+
positionRef.current = active;
|
|
947
|
+
setPosition(active);
|
|
948
|
+
}, [active]);
|
|
949
|
+
const preview = (next) => {
|
|
950
|
+
positionRef.current = next;
|
|
951
|
+
setPosition(next);
|
|
952
|
+
};
|
|
953
|
+
const commit = () => {
|
|
954
|
+
if (disabled || busy) return;
|
|
955
|
+
const row = rows[positionRef.current];
|
|
956
|
+
if (row !== void 0) onPick(row.id === "" ? void 0 : row.id);
|
|
957
|
+
};
|
|
958
|
+
const max = Math.max(0, rows.length - 1);
|
|
959
|
+
const percent = max > 0 ? Math.round(position / max * 100) : 0;
|
|
960
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
961
|
+
className: "dsh-model-menu-sliderGroup",
|
|
962
|
+
role: "group",
|
|
963
|
+
"aria-label": label,
|
|
964
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
965
|
+
className: "dsh-model-menu-sliderHead",
|
|
966
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
967
|
+
className: "dsh-model-menu-sliderTitle",
|
|
968
|
+
children: label
|
|
969
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
970
|
+
className: "dsh-model-menu-sliderValue",
|
|
971
|
+
children: rows[position]?.name
|
|
972
|
+
})]
|
|
973
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
974
|
+
className: "dsh-model-menu-sliderRail",
|
|
975
|
+
style: { "--_p": percent },
|
|
976
|
+
children: [
|
|
977
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
978
|
+
type: "range",
|
|
979
|
+
className: "dsh-model-menu-sliderInput",
|
|
980
|
+
min: 0,
|
|
981
|
+
max,
|
|
982
|
+
step: 1,
|
|
983
|
+
value: position,
|
|
984
|
+
disabled,
|
|
985
|
+
"aria-label": label,
|
|
986
|
+
"aria-disabled": disabled || busy || void 0,
|
|
987
|
+
"aria-valuetext": rows[position]?.name,
|
|
988
|
+
onChange: (event) => {
|
|
989
|
+
const value = Number(event.target.value);
|
|
990
|
+
if (Number.isFinite(value)) preview(value);
|
|
991
|
+
},
|
|
992
|
+
onPointerUp: commit,
|
|
993
|
+
onKeyUp: commit,
|
|
994
|
+
onBlur: commit
|
|
995
|
+
}),
|
|
996
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
997
|
+
className: "dsh-model-menu-sliderTrack",
|
|
998
|
+
"aria-hidden": "true",
|
|
999
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1000
|
+
className: "dsh-model-menu-sliderFill",
|
|
1001
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dsh-model-menu-sliderFillBar" })
|
|
1002
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1003
|
+
className: "dsh-model-menu-sliderNodes",
|
|
1004
|
+
children: rows.map((row, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1005
|
+
"data-covered": index <= position ? "" : void 0,
|
|
1006
|
+
className: "dsh-model-menu-sliderNode",
|
|
1007
|
+
style: { "--_n": max > 0 ? Math.round(index / max * 100) : 0 }
|
|
1008
|
+
}, row.id || "__default"))
|
|
1009
|
+
})]
|
|
1010
|
+
}),
|
|
1011
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1012
|
+
className: "dsh-model-menu-sliderThumb",
|
|
1013
|
+
"aria-hidden": "true"
|
|
1014
|
+
})
|
|
1015
|
+
]
|
|
1016
|
+
})]
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
855
1020
|
* ModelMenu styles, transcribed from the official ModelSelect.module.css (the
|
|
856
1021
|
* published dsh-client-ui-model-selection bundle): card capped at
|
|
857
1022
|
* min(360px, 100vh - 96px), 34px option cells at 13/18 with 5px 7px padding,
|
|
@@ -910,6 +1075,7 @@ const modelMenuCss = `
|
|
|
910
1075
|
overflow: hidden;
|
|
911
1076
|
}
|
|
912
1077
|
.dsh-model-menu-status, .dsh-model-menu-empty { color: var(--dsw-alias-label-tertiary); padding: 8px; font-size: 12px; line-height: 18px; }
|
|
1078
|
+
.dsh-model-menu-header { flex: none; padding: 2px 2px 4px; }
|
|
913
1079
|
.dsh-model-menu-searchRow { flex-shrink: 0; margin: 2px 0 3px; position: relative; }
|
|
914
1080
|
.dsh-model-menu-searchRow .dsh-model-menu-search { border-radius: var(--dsw-radius-md); background: none; border: 0 solid transparent; height: auto; padding: 5px 7px; display: flex; }
|
|
915
1081
|
.dsh-model-menu-searchRow .dsh-model-menu-search:focus-within { border-color: transparent; }
|
|
@@ -988,7 +1154,235 @@ const modelMenuCss = `
|
|
|
988
1154
|
.dsh-model-menu-cellLabel { white-space: nowrap; flex: none; }
|
|
989
1155
|
.dsh-model-menu-cellValue { text-overflow: ellipsis; white-space: nowrap; text-align: right; min-width: 0; color: var(--dsw-alias-label-tertiary); flex: auto; overflow: hidden; }
|
|
990
1156
|
.dsh-model-menu-cellChevron { width: 12px; height: 12px; color: var(--dsw-alias-menu-icon); flex: none; }
|
|
1157
|
+
/* Discrete reasoning-effort slider under the model list (enhanced mode).
|
|
1158
|
+
* The painted control is layered over a fully functional native range input:
|
|
1159
|
+
* the input stays on top, transparent, and owns pointer/keyboard/ARIA, while
|
|
1160
|
+
* the decorative track, fill and thumb render beneath it at pointer-events:
|
|
1161
|
+
* none. Position lives in one custom property (--_p, 0–100) on the rail, so
|
|
1162
|
+
* the only animated work a level change triggers is a clip-path/left
|
|
1163
|
+
* transition — no keyframes, no element churn. */
|
|
1164
|
+
.dsh-model-menu-sliderGroup { border-top: 0.5px solid var(--dsw-alias-border-l2); flex: none; margin-top: 4px; padding: 6px 8px 8px; }
|
|
1165
|
+
.dsh-model-menu-sliderHead { align-items: baseline; justify-content: space-between; gap: 8px; margin: 0 2px 4px; display: flex; }
|
|
1166
|
+
.dsh-model-menu-sliderTitle { color: var(--dsw-alias-label-tertiary); font-size: 12px; line-height: 18px; }
|
|
1167
|
+
.dsh-model-menu-sliderValue { color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 600; line-height: 20px; transition: color var(--ds-transition-duration) var(--ds-ease-in-out); }
|
|
1168
|
+
.dsh-model-menu-sliderRail { position: relative; direction: ltr; height: 38px; margin-top: 2px; }
|
|
1169
|
+
.dsh-model-menu-sliderRail:has(:disabled) { opacity: 0.45; }
|
|
1170
|
+
.dsh-model-menu-sliderInput {
|
|
1171
|
+
appearance: none;
|
|
1172
|
+
-webkit-appearance: none;
|
|
1173
|
+
box-sizing: border-box;
|
|
1174
|
+
border: 0;
|
|
1175
|
+
cursor: pointer;
|
|
1176
|
+
direction: ltr;
|
|
1177
|
+
opacity: 0;
|
|
1178
|
+
margin: 0;
|
|
1179
|
+
padding: 0;
|
|
1180
|
+
width: 100%;
|
|
1181
|
+
height: 100%;
|
|
1182
|
+
position: absolute;
|
|
1183
|
+
inset: 0;
|
|
1184
|
+
z-index: 1;
|
|
1185
|
+
touch-action: pan-y;
|
|
1186
|
+
}
|
|
1187
|
+
.dsh-model-menu-sliderInput:disabled { cursor: default; }
|
|
1188
|
+
/* The transparent input must keep the painted thumb's geometry: an
|
|
1189
|
+
* appearance:none webkit thumb defaults to 16px, which would shift the
|
|
1190
|
+
* end-point positions 7px off the decorative 30px disc and misplace the
|
|
1191
|
+
* native drag hit target. Track gets the full rail height so the whole band
|
|
1192
|
+
* maps to values identically. */
|
|
1193
|
+
.dsh-model-menu-sliderInput::-webkit-slider-runnable-track { width: 100%; height: 100%; }
|
|
1194
|
+
.dsh-model-menu-sliderInput::-webkit-slider-thumb { appearance: none; -webkit-appearance: none; width: 30px; height: 30px; margin: 0; }
|
|
1195
|
+
.dsh-model-menu-sliderInput::-moz-range-track { width: 100%; height: 100%; }
|
|
1196
|
+
.dsh-model-menu-sliderInput::-moz-range-thumb { width: 30px; height: 30px; margin: 0; border: 0; }
|
|
1197
|
+
.dsh-model-menu-sliderTrack {
|
|
1198
|
+
pointer-events: none;
|
|
1199
|
+
border-radius: 999px;
|
|
1200
|
+
width: 100%;
|
|
1201
|
+
height: 26px;
|
|
1202
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
1203
|
+
box-shadow: inset 0 0 0 0.5px var(--dsw-alias-border-l2);
|
|
1204
|
+
position: absolute;
|
|
1205
|
+
inset-inline: 0;
|
|
1206
|
+
top: 50%;
|
|
1207
|
+
transform: translateY(-50%);
|
|
1208
|
+
/* No overflow:hidden — it would clip the fill's drop-shadow glow; the bar's
|
|
1209
|
+
* own clip-path already supplies the rounded reveal shape. */
|
|
1210
|
+
}
|
|
1211
|
+
/* The accent ramp paints the full track width once and is revealed by an
|
|
1212
|
+
* animated clip-path, so raising the effort unwraps more of the spectrum
|
|
1213
|
+
* instead of squashing the same gradient into a narrower bar. The glow lives
|
|
1214
|
+
* on the un-clipped parent: a filter on the clipped element itself would be
|
|
1215
|
+
* clipped away with it. */
|
|
1216
|
+
.dsh-model-menu-sliderFill {
|
|
1217
|
+
display: block;
|
|
1218
|
+
height: 100%;
|
|
1219
|
+
filter: drop-shadow(0 0 5px var(--dsw-alias-state-business-primary));
|
|
1220
|
+
transition: filter var(--ds-transition-duration) var(--ds-ease-in-out);
|
|
1221
|
+
}
|
|
1222
|
+
.dsh-model-menu-sliderInput:hover:not(:disabled) ~ .dsh-model-menu-sliderTrack .dsh-model-menu-sliderFill,
|
|
1223
|
+
.dsh-model-menu-sliderInput:active:not(:disabled) ~ .dsh-model-menu-sliderTrack .dsh-model-menu-sliderFill {
|
|
1224
|
+
filter: drop-shadow(0 0 9px var(--dsw-alias-state-business-primary));
|
|
1225
|
+
}
|
|
1226
|
+
/* The fill stops at the thumb's center, not at the track edge — at a 30px
|
|
1227
|
+
* thumb the end stops sit 15px in from each side, matching the node dots. */
|
|
1228
|
+
.dsh-model-menu-sliderFillBar {
|
|
1229
|
+
display: block;
|
|
1230
|
+
height: 100%;
|
|
1231
|
+
background: linear-gradient(90deg,
|
|
1232
|
+
var(--dsw-alias-state-business-primary),
|
|
1233
|
+
var(--dsw-alias-state-success-primary),
|
|
1234
|
+
var(--dsw-alias-state-warn-primary),
|
|
1235
|
+
var(--dsw-alias-state-error-primary));
|
|
1236
|
+
clip-path: inset(0 calc(100% - 15px - (100% - 30px) * var(--_p, 0) / 100) 0 0 round 999px);
|
|
1237
|
+
transition: clip-path var(--ds-transition-duration) var(--ds-ease-in-out);
|
|
1238
|
+
}
|
|
1239
|
+
/* Stop dots: one per declared row (default + every advertised effort), evenly
|
|
1240
|
+
* spread across the band between the two thumb-center extremes (inset 15px,
|
|
1241
|
+
* i.e. half the 30px thumb). --_n is 0-100 along that band. A covered dot
|
|
1242
|
+
* reads as a pale pin through the gradient; an uncovered one stays a neutral
|
|
1243
|
+
* notch on the empty track. */
|
|
1244
|
+
.dsh-model-menu-sliderNodes {
|
|
1245
|
+
pointer-events: none;
|
|
1246
|
+
display: block;
|
|
1247
|
+
position: absolute;
|
|
1248
|
+
inset-inline: 15px;
|
|
1249
|
+
inset-block: 0;
|
|
1250
|
+
}
|
|
1251
|
+
.dsh-model-menu-sliderNode {
|
|
1252
|
+
pointer-events: none;
|
|
1253
|
+
box-sizing: border-box;
|
|
1254
|
+
width: 4px;
|
|
1255
|
+
height: 4px;
|
|
1256
|
+
border-radius: 50%;
|
|
1257
|
+
background: color-mix(in srgb, var(--dsw-alias-label-secondary) 35%, transparent);
|
|
1258
|
+
position: absolute;
|
|
1259
|
+
left: calc(var(--_n, 0) * 1%);
|
|
1260
|
+
top: 50%;
|
|
1261
|
+
transform: translate(-50%, -50%);
|
|
1262
|
+
transition: background-color var(--ds-transition-duration) var(--ds-ease-in-out),
|
|
1263
|
+
transform var(--ds-transition-duration) var(--ds-ease-in-out);
|
|
1264
|
+
}
|
|
1265
|
+
.dsh-model-menu-sliderNode[data-covered] { background: color-mix(in srgb, var(--dsw-alias-label-primary) 60%, transparent); }
|
|
1266
|
+
.dsh-model-menu-sliderThumb {
|
|
1267
|
+
pointer-events: none;
|
|
1268
|
+
box-sizing: border-box;
|
|
1269
|
+
border-radius: 50%;
|
|
1270
|
+
width: 30px;
|
|
1271
|
+
height: 30px;
|
|
1272
|
+
background: var(--dsw-alias-button-primary-fill);
|
|
1273
|
+
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
1274
|
+
box-shadow: 0 0 0 3px var(--dsw-alias-interactive-bg-hover), var(--dsw-elevation-soft);
|
|
1275
|
+
position: absolute;
|
|
1276
|
+
left: calc((100% - 30px) * var(--_p, 0) / 100);
|
|
1277
|
+
top: 50%;
|
|
1278
|
+
transform: translateY(-50%) scale(0.92);
|
|
1279
|
+
transition: left var(--ds-transition-duration) var(--ds-ease-in-out),
|
|
1280
|
+
transform var(--ds-transition-duration) var(--ds-ease-in-out),
|
|
1281
|
+
box-shadow var(--ds-transition-duration) var(--ds-ease-in-out);
|
|
1282
|
+
}
|
|
1283
|
+
.dsh-model-menu-sliderInput:hover:not(:disabled) ~ .dsh-model-menu-sliderThumb { transform: translateY(-50%) scale(1); }
|
|
1284
|
+
.dsh-model-menu-sliderInput:active:not(:disabled) ~ .dsh-model-menu-sliderThumb {
|
|
1285
|
+
transform: translateY(-50%) scale(1.1);
|
|
1286
|
+
box-shadow: 0 0 0 4px var(--dsw-alias-interactive-bg-hover), 0 0 12px var(--dsw-alias-state-business-primary);
|
|
1287
|
+
}
|
|
1288
|
+
.dsh-model-menu-sliderInput:focus-visible ~ .dsh-model-menu-sliderThumb {
|
|
1289
|
+
box-shadow: 0 0 0 3px var(--dsw-alias-interactive-bg-hover),
|
|
1290
|
+
0 0 0 calc(3px + var(--dsw-focus-ring-width)) var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
1291
|
+
}
|
|
1292
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1293
|
+
.dsh-model-menu-sliderFill, .dsh-model-menu-sliderFillBar, .dsh-model-menu-sliderThumb,
|
|
1294
|
+
.dsh-model-menu-sliderNode, .dsh-model-menu-sliderValue { transition: none; }
|
|
1295
|
+
}
|
|
991
1296
|
`;
|
|
1297
|
+
//#endregion
|
|
1298
|
+
//#region ../dsh-plugin-kit/src/model-picker.tsx
|
|
1299
|
+
/**
|
|
1300
|
+
* Optional shared model picker (`klarkxy.model-picker` Factory).
|
|
1301
|
+
*
|
|
1302
|
+
* `@klarkxy/dsh-model-picker`, when installed, registers one root-scoped
|
|
1303
|
+
* Component Factory under `MODEL_PICKER_FACTORY`: an enhanced model menu with
|
|
1304
|
+
* a searchable provider-grouped list and a reasoning-effort slider, built on
|
|
1305
|
+
* the same ModelMenu recipe. Consumers keep their own ModelMenu as the
|
|
1306
|
+
* `fallback`; when no definition is registered the renderer resolves that
|
|
1307
|
+
* fallback verbatim, so the shared picker is strictly opt-in and every saved
|
|
1308
|
+
* value keeps its existing write path.
|
|
1309
|
+
*
|
|
1310
|
+
* `renderFactorySlot` is a framework-composed prop, never a context: the
|
|
1311
|
+
* component a plugin registers into any slot receives it automatically, but
|
|
1312
|
+
* the plugin must pass it down to whichever child renders the menu (or to the
|
|
1313
|
+
* render callback handed to registerSessionSettingsItem).
|
|
1314
|
+
*
|
|
1315
|
+
* The Factory manages its own open state; a consumer's `open`/`onOpenChange`
|
|
1316
|
+
* pair belongs to the fallback menu only and must not be fed to both.
|
|
1317
|
+
*/
|
|
1318
|
+
/**
|
|
1319
|
+
* The trigger every consumer used to hand its own ModelMenu, rebuilt as the
|
|
1320
|
+
* factory's `anchor` local component: identical classes and chevron, the menu
|
|
1321
|
+
* state coming from the occurrence's props instead of local open state.
|
|
1322
|
+
*
|
|
1323
|
+
* `ariaLabel` and `title` are the whole accessible name and tooltip (the
|
|
1324
|
+
* caller formats "field: current route" itself because the factory's resolved
|
|
1325
|
+
* caption may intentionally be a short name). The component identity stays
|
|
1326
|
+
* put across label changes — a new function would unmount the trigger, and
|
|
1327
|
+
* its cleanup would close a menu that `closeOnPick={false}` is keeping open.
|
|
1328
|
+
* The latest label, title, and `onClose` are read from refs.
|
|
1329
|
+
*/
|
|
1330
|
+
function useModelMenuAnchor(ariaLabel, title, onClose) {
|
|
1331
|
+
const closeRef = (0, react.useRef)(onClose);
|
|
1332
|
+
const labelRef = (0, react.useRef)(ariaLabel);
|
|
1333
|
+
const titleRef = (0, react.useRef)(title);
|
|
1334
|
+
closeRef.current = onClose;
|
|
1335
|
+
labelRef.current = ariaLabel;
|
|
1336
|
+
titleRef.current = title;
|
|
1337
|
+
return (0, react.useMemo)(() => {
|
|
1338
|
+
const ModelPickerTrigger = (props) => {
|
|
1339
|
+
(0, react.useEffect)(() => () => closeRef.current?.(), []);
|
|
1340
|
+
const record = props;
|
|
1341
|
+
const injected = {};
|
|
1342
|
+
for (const [key, value] of Object.entries(record)) if (key.startsWith("aria-")) injected[key] = value;
|
|
1343
|
+
const extraClass = typeof record.className === "string" ? ` ${record.className}` : "";
|
|
1344
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1345
|
+
type: "button",
|
|
1346
|
+
className: `dsh-model-menu-trigger${extraClass}`,
|
|
1347
|
+
disabled: props.disabled,
|
|
1348
|
+
"aria-haspopup": "menu",
|
|
1349
|
+
"aria-expanded": props.open,
|
|
1350
|
+
...injected,
|
|
1351
|
+
"aria-label": labelRef.current,
|
|
1352
|
+
title: titleRef.current,
|
|
1353
|
+
children: [
|
|
1354
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1355
|
+
className: "dsh-model-menu-triggerLabel",
|
|
1356
|
+
children: props.label
|
|
1357
|
+
}),
|
|
1358
|
+
props.effortLabel ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1359
|
+
className: "dsh-model-menu-triggerEffort",
|
|
1360
|
+
children: props.effortLabel
|
|
1361
|
+
}) : null,
|
|
1362
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${props.open ? " dsh-model-menu-chevronOpen" : ""}` })
|
|
1363
|
+
]
|
|
1364
|
+
});
|
|
1365
|
+
};
|
|
1366
|
+
return ModelPickerTrigger;
|
|
1367
|
+
}, []);
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* Render the shared model picker while it is registered; `options.fallback`
|
|
1371
|
+
* whenever it is not (plugin absent, disabled, or unloaded).
|
|
1372
|
+
*
|
|
1373
|
+
* `options.anchor` is optional: the factory ships a default trigger, so only
|
|
1374
|
+
* consumers that need their own chrome (custom aria-label, tooltip, class)
|
|
1375
|
+
* pass one built with {@link useModelMenuAnchor}. The returned node is never
|
|
1376
|
+
* assumed to be the factory's own output — a defensive `?? fallback` covers a
|
|
1377
|
+
* renderer that yields null for an unregistered definition.
|
|
1378
|
+
*/
|
|
1379
|
+
function renderModelPicker(renderFactorySlot, props, options) {
|
|
1380
|
+
if (typeof renderFactorySlot !== "function") return options.fallback;
|
|
1381
|
+
return renderFactorySlot("klarkxy.model-picker", props, {
|
|
1382
|
+
...options.anchor ? { slots: { ["anchor"]: options.anchor } } : {},
|
|
1383
|
+
fallback: options.fallback
|
|
1384
|
+
}) ?? options.fallback;
|
|
1385
|
+
}
|
|
992
1386
|
Object.values({
|
|
993
1387
|
/** Surfaces, from the page up to the topmost nested card. */
|
|
994
1388
|
surface: [
|
|
@@ -1078,7 +1472,9 @@ Object.values({
|
|
|
1078
1472
|
"--dsh-scrollbar-thumb-hover",
|
|
1079
1473
|
"--dsh-content-font-size-secondary",
|
|
1080
1474
|
"--dsh-content-font-delta",
|
|
1081
|
-
"--dsh-frame-top-clearance"
|
|
1475
|
+
"--dsh-frame-top-clearance",
|
|
1476
|
+
"--dsh-composer-model-icon-display",
|
|
1477
|
+
"--dsh-composer-model-text-display"
|
|
1082
1478
|
]
|
|
1083
1479
|
}).flat();
|
|
1084
1480
|
/**
|
|
@@ -1805,7 +2201,7 @@ async function callAuxiliaryRpc(client, endpoint, payload = {}) {
|
|
|
1805
2201
|
* The shared card. All state comes from the shared host service over the
|
|
1806
2202
|
* plugin-kit RPC channel; the card only renders the stored row.
|
|
1807
2203
|
*/
|
|
1808
|
-
function AuxiliaryModelCard({ client, subscribe }) {
|
|
2204
|
+
function AuxiliaryModelCard({ client, subscribe, renderFactorySlot }) {
|
|
1809
2205
|
const language = useAuxiliaryLocale(client);
|
|
1810
2206
|
const text = auxiliaryCopy[language];
|
|
1811
2207
|
const [settings, setSettings] = (0, react.useState)();
|
|
@@ -1926,6 +2322,17 @@ function AuxiliaryModelCard({ client, subscribe }) {
|
|
|
1926
2322
|
refreshCatalog
|
|
1927
2323
|
]);
|
|
1928
2324
|
const locked = busy;
|
|
2325
|
+
const current = settings?.model ?? {
|
|
2326
|
+
provider: "",
|
|
2327
|
+
model: ""
|
|
2328
|
+
};
|
|
2329
|
+
const follow = !current.provider || !current.model;
|
|
2330
|
+
const selected = follow ? void 0 : choices.find((item) => item.provider === current.provider && item.model === current.model);
|
|
2331
|
+
const efforts = follow ? [] : modelMenuEffortOptions(selected, current.reasoningEffort);
|
|
2332
|
+
const currentLabel = follow ? text.modelDefault : selected?.label ?? `${current.provider} / ${current.model}`;
|
|
2333
|
+
const selectedEffort = efforts.find((item) => item.id === (current.reasoningEffort || ""));
|
|
2334
|
+
const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : void 0;
|
|
2335
|
+
const anchor = useModelMenuAnchor(`${text.model}: ${settings ? currentLabel : ""}`, void 0, () => setMenuOpen(false));
|
|
1929
2336
|
if (!settings) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1930
2337
|
className: "ams-root dsh-ui-card",
|
|
1931
2338
|
"data-testid": "auxiliary-model-settings",
|
|
@@ -1964,16 +2371,6 @@ function AuxiliaryModelCard({ client, subscribe }) {
|
|
|
1964
2371
|
})
|
|
1965
2372
|
]
|
|
1966
2373
|
});
|
|
1967
|
-
const current = settings.model ?? {
|
|
1968
|
-
provider: "",
|
|
1969
|
-
model: ""
|
|
1970
|
-
};
|
|
1971
|
-
const follow = !current.provider || !current.model;
|
|
1972
|
-
const selected = follow ? void 0 : choices.find((item) => item.provider === current.provider && item.model === current.model);
|
|
1973
|
-
const efforts = follow ? [] : modelMenuEffortOptions(selected, current.reasoningEffort);
|
|
1974
|
-
const currentLabel = follow ? text.modelDefault : selected?.label ?? `${current.provider} / ${current.model}`;
|
|
1975
|
-
const selectedEffort = efforts.find((item) => item.id === (current.reasoningEffort || ""));
|
|
1976
|
-
const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : void 0;
|
|
1977
2374
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1978
2375
|
className: "ams-root dsh-ui-card",
|
|
1979
2376
|
"data-testid": "auxiliary-model-settings",
|
|
@@ -1997,28 +2394,12 @@ function AuxiliaryModelCard({ client, subscribe }) {
|
|
|
1997
2394
|
state: catalogState,
|
|
1998
2395
|
locale: language
|
|
1999
2396
|
}),
|
|
2000
|
-
|
|
2397
|
+
renderModelPicker(renderFactorySlot, {
|
|
2001
2398
|
locale: language,
|
|
2002
|
-
|
|
2399
|
+
disabled: locked,
|
|
2400
|
+
busy: locked,
|
|
2003
2401
|
onOpenChange: (next) => setMenuOpen(next && !locked),
|
|
2004
2402
|
listClassName: "ams-root ams-menu-portal",
|
|
2005
|
-
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2006
|
-
type: "button",
|
|
2007
|
-
className: "dsh-model-menu-trigger",
|
|
2008
|
-
disabled: locked,
|
|
2009
|
-
"aria-label": `${text.model}: ${currentLabel}`,
|
|
2010
|
-
children: [
|
|
2011
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2012
|
-
className: "dsh-model-menu-triggerLabel",
|
|
2013
|
-
children: currentLabel
|
|
2014
|
-
}),
|
|
2015
|
-
!follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2016
|
-
className: "dsh-model-menu-triggerEffort",
|
|
2017
|
-
children: effortName ?? text.effortDefault
|
|
2018
|
-
}) : null,
|
|
2019
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen && !locked ? " dsh-model-menu-chevronOpen" : ""}` })
|
|
2020
|
-
]
|
|
2021
|
-
}),
|
|
2022
2403
|
choices,
|
|
2023
2404
|
selected: follow ? void 0 : {
|
|
2024
2405
|
provider: current.provider,
|
|
@@ -2041,9 +2422,61 @@ function AuxiliaryModelCard({ client, subscribe }) {
|
|
|
2041
2422
|
model: current.model,
|
|
2042
2423
|
...effort ? { reasoningEffort: effort } : {}
|
|
2043
2424
|
}),
|
|
2425
|
+
triggerLabel: currentLabel,
|
|
2426
|
+
triggerEffortLabel: !follow && efforts.length ? effortName ?? text.effortDefault : void 0,
|
|
2044
2427
|
modelLabel: text.model,
|
|
2045
2428
|
modelValue: currentLabel,
|
|
2046
2429
|
defaultEffortLabel: text.effortDefault
|
|
2430
|
+
}, {
|
|
2431
|
+
anchor,
|
|
2432
|
+
fallback: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
|
|
2433
|
+
locale: language,
|
|
2434
|
+
open: menuOpen && !locked,
|
|
2435
|
+
onOpenChange: (next) => setMenuOpen(next && !locked),
|
|
2436
|
+
listClassName: "ams-root ams-menu-portal",
|
|
2437
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2438
|
+
type: "button",
|
|
2439
|
+
className: "dsh-model-menu-trigger",
|
|
2440
|
+
disabled: locked,
|
|
2441
|
+
"aria-label": `${text.model}: ${currentLabel}`,
|
|
2442
|
+
children: [
|
|
2443
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2444
|
+
className: "dsh-model-menu-triggerLabel",
|
|
2445
|
+
children: currentLabel
|
|
2446
|
+
}),
|
|
2447
|
+
!follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2448
|
+
className: "dsh-model-menu-triggerEffort",
|
|
2449
|
+
children: effortName ?? text.effortDefault
|
|
2450
|
+
}) : null,
|
|
2451
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen && !locked ? " dsh-model-menu-chevronOpen" : ""}` })
|
|
2452
|
+
]
|
|
2453
|
+
}),
|
|
2454
|
+
choices,
|
|
2455
|
+
selected: follow ? void 0 : {
|
|
2456
|
+
provider: current.provider,
|
|
2457
|
+
model: current.model
|
|
2458
|
+
},
|
|
2459
|
+
onPick: (picked) => save({
|
|
2460
|
+
provider: picked.provider,
|
|
2461
|
+
model: picked.model
|
|
2462
|
+
}),
|
|
2463
|
+
leading: [{
|
|
2464
|
+
id: "default",
|
|
2465
|
+
label: text.modelDefault,
|
|
2466
|
+
selected: follow
|
|
2467
|
+
}],
|
|
2468
|
+
onPickLeading: () => save(null),
|
|
2469
|
+
efforts,
|
|
2470
|
+
selectedEffort: current.reasoningEffort || "",
|
|
2471
|
+
onPickEffort: (effort) => save(follow ? null : {
|
|
2472
|
+
provider: current.provider,
|
|
2473
|
+
model: current.model,
|
|
2474
|
+
...effort ? { reasoningEffort: effort } : {}
|
|
2475
|
+
}),
|
|
2476
|
+
modelLabel: text.model,
|
|
2477
|
+
modelValue: currentLabel,
|
|
2478
|
+
defaultEffortLabel: text.effortDefault
|
|
2479
|
+
})
|
|
2047
2480
|
}),
|
|
2048
2481
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2049
2482
|
className: "dsh-ui-hint",
|
|
@@ -2145,9 +2578,10 @@ function acquireAuxiliaryModelCard(root, ctx) {
|
|
|
2145
2578
|
let unprovide;
|
|
2146
2579
|
const watchLease = acquireWatchCoordinator(root).mount();
|
|
2147
2580
|
const subscriber = createModelSettingsSubscriber(ctx.root, AUXILIARY_MODEL_PACKAGE, AUXILIARY_MODEL_CHANGES_IDENTITY, (ns) => ns === AUXILIARY_MODEL_NS);
|
|
2148
|
-
const render = () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuxiliaryModelCard, {
|
|
2581
|
+
const render = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuxiliaryModelCard, {
|
|
2149
2582
|
client: root,
|
|
2150
|
-
subscribe: subscriber.subscribe
|
|
2583
|
+
subscribe: subscriber.subscribe,
|
|
2584
|
+
renderFactorySlot: props.renderFactorySlot
|
|
2151
2585
|
});
|
|
2152
2586
|
const service = {
|
|
2153
2587
|
retain() {
|
|
@@ -2403,7 +2837,7 @@ function GitBranchIcon() {
|
|
|
2403
2837
|
]
|
|
2404
2838
|
});
|
|
2405
2839
|
}
|
|
2406
|
-
function GitCommitAction({ sessionId, useSession, call, t, client, subscribeSettings }) {
|
|
2840
|
+
function GitCommitAction({ sessionId, useSession, call, t, client, subscribeSettings, renderFactorySlot }) {
|
|
2407
2841
|
const [open, setOpen] = (0, react.useState)(false);
|
|
2408
2842
|
const [status, setStatus] = (0, react.useState)();
|
|
2409
2843
|
const [loading, setLoading] = (0, react.useState)(false);
|
|
@@ -2619,6 +3053,7 @@ function GitCommitAction({ sessionId, useSession, call, t, client, subscribeSett
|
|
|
2619
3053
|
client,
|
|
2620
3054
|
subscribeSettings,
|
|
2621
3055
|
disabled: running,
|
|
3056
|
+
renderFactorySlot,
|
|
2622
3057
|
onSaving: markWrite,
|
|
2623
3058
|
onWriteFailed: markWriteFailed,
|
|
2624
3059
|
onReady: markSeen,
|
|
@@ -2919,6 +3354,17 @@ function GitCommitSettingsPanel(props) {
|
|
|
2919
3354
|
props.disabled
|
|
2920
3355
|
]);
|
|
2921
3356
|
const locked = busy || controlsLocked;
|
|
3357
|
+
const current = settings?.model ?? {
|
|
3358
|
+
provider: "",
|
|
3359
|
+
model: ""
|
|
3360
|
+
};
|
|
3361
|
+
const follow = !current.provider || !current.model;
|
|
3362
|
+
const selected = follow ? void 0 : choices.find((item) => item.provider === current.provider && item.model === current.model);
|
|
3363
|
+
const efforts = follow ? [] : modelMenuEffortOptions(selected, current.reasoningEffort);
|
|
3364
|
+
const currentLabel = follow ? text.modelDefault : selected?.label ?? `${current.provider} / ${current.model}`;
|
|
3365
|
+
const selectedEffort = efforts.find((item) => item.id === (current.reasoningEffort || ""));
|
|
3366
|
+
const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : void 0;
|
|
3367
|
+
const anchor = useModelMenuAnchor(`${text.model}: ${settings ? currentLabel : ""}`, void 0, () => setMenuOpen(false));
|
|
2922
3368
|
if (!settings) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2923
3369
|
className: "gcm-root gcm-settings dsh-ui-stack",
|
|
2924
3370
|
children: [
|
|
@@ -2945,13 +3391,6 @@ function GitCommitSettingsPanel(props) {
|
|
|
2945
3391
|
})
|
|
2946
3392
|
]
|
|
2947
3393
|
});
|
|
2948
|
-
const current = settings.model;
|
|
2949
|
-
const follow = !current.provider || !current.model;
|
|
2950
|
-
const selected = follow ? void 0 : choices.find((item) => item.provider === current.provider && item.model === current.model);
|
|
2951
|
-
const efforts = follow ? [] : modelMenuEffortOptions(selected, current.reasoningEffort);
|
|
2952
|
-
const currentLabel = follow ? text.modelDefault : selected?.label ?? `${current.provider} / ${current.model}`;
|
|
2953
|
-
const selectedEffort = efforts.find((item) => item.id === (current.reasoningEffort || ""));
|
|
2954
|
-
const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : void 0;
|
|
2955
3394
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2956
3395
|
className: "gcm-root gcm-settings dsh-ui-stack",
|
|
2957
3396
|
"data-testid": "git-commit-settings",
|
|
@@ -2967,28 +3406,12 @@ function GitCommitSettingsPanel(props) {
|
|
|
2967
3406
|
state: catalogState,
|
|
2968
3407
|
locale: language
|
|
2969
3408
|
}),
|
|
2970
|
-
|
|
3409
|
+
renderModelPicker(props.renderFactorySlot, {
|
|
2971
3410
|
locale: language,
|
|
2972
|
-
|
|
3411
|
+
disabled: locked,
|
|
3412
|
+
busy: locked,
|
|
2973
3413
|
onOpenChange: (next) => setMenuOpen(next && !locked),
|
|
2974
3414
|
listClassName: "gcm-root gcm-menu-portal",
|
|
2975
|
-
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2976
|
-
type: "button",
|
|
2977
|
-
className: "dsh-model-menu-trigger",
|
|
2978
|
-
disabled: locked,
|
|
2979
|
-
"aria-label": `${text.model}: ${currentLabel}`,
|
|
2980
|
-
children: [
|
|
2981
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2982
|
-
className: "dsh-model-menu-triggerLabel",
|
|
2983
|
-
children: currentLabel
|
|
2984
|
-
}),
|
|
2985
|
-
!follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2986
|
-
className: "dsh-model-menu-triggerEffort",
|
|
2987
|
-
children: effortName ?? text.modelDefault
|
|
2988
|
-
}) : null,
|
|
2989
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen && !locked ? " dsh-model-menu-chevronOpen" : ""}` })
|
|
2990
|
-
]
|
|
2991
|
-
}),
|
|
2992
3415
|
choices,
|
|
2993
3416
|
selected: follow ? void 0 : {
|
|
2994
3417
|
provider: current.provider,
|
|
@@ -3017,9 +3440,67 @@ function GitCommitSettingsPanel(props) {
|
|
|
3017
3440
|
model: current.model,
|
|
3018
3441
|
...effort ? { reasoningEffort: effort } : {}
|
|
3019
3442
|
} }),
|
|
3443
|
+
triggerLabel: currentLabel,
|
|
3444
|
+
triggerEffortLabel: !follow && efforts.length ? effortName ?? text.modelDefault : void 0,
|
|
3020
3445
|
modelLabel: text.model,
|
|
3021
3446
|
modelValue: currentLabel,
|
|
3022
3447
|
defaultEffortLabel: text.modelDefault
|
|
3448
|
+
}, {
|
|
3449
|
+
anchor,
|
|
3450
|
+
fallback: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
|
|
3451
|
+
locale: language,
|
|
3452
|
+
open: menuOpen && !locked,
|
|
3453
|
+
onOpenChange: (next) => setMenuOpen(next && !locked),
|
|
3454
|
+
listClassName: "gcm-root gcm-menu-portal",
|
|
3455
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3456
|
+
type: "button",
|
|
3457
|
+
className: "dsh-model-menu-trigger",
|
|
3458
|
+
disabled: locked,
|
|
3459
|
+
"aria-label": `${text.model}: ${currentLabel}`,
|
|
3460
|
+
children: [
|
|
3461
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3462
|
+
className: "dsh-model-menu-triggerLabel",
|
|
3463
|
+
children: currentLabel
|
|
3464
|
+
}),
|
|
3465
|
+
!follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3466
|
+
className: "dsh-model-menu-triggerEffort",
|
|
3467
|
+
children: effortName ?? text.modelDefault
|
|
3468
|
+
}) : null,
|
|
3469
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen && !locked ? " dsh-model-menu-chevronOpen" : ""}` })
|
|
3470
|
+
]
|
|
3471
|
+
}),
|
|
3472
|
+
choices,
|
|
3473
|
+
selected: follow ? void 0 : {
|
|
3474
|
+
provider: current.provider,
|
|
3475
|
+
model: current.model
|
|
3476
|
+
},
|
|
3477
|
+
onPick: (picked) => save({ model: {
|
|
3478
|
+
provider: picked.provider,
|
|
3479
|
+
model: picked.model
|
|
3480
|
+
} }),
|
|
3481
|
+
leading: [{
|
|
3482
|
+
id: "default",
|
|
3483
|
+
label: text.modelDefault,
|
|
3484
|
+
selected: follow
|
|
3485
|
+
}],
|
|
3486
|
+
onPickLeading: () => save({ model: {
|
|
3487
|
+
provider: "",
|
|
3488
|
+
model: ""
|
|
3489
|
+
} }),
|
|
3490
|
+
efforts,
|
|
3491
|
+
selectedEffort: current.reasoningEffort || "",
|
|
3492
|
+
onPickEffort: (effort) => save({ model: follow ? {
|
|
3493
|
+
provider: "",
|
|
3494
|
+
model: ""
|
|
3495
|
+
} : {
|
|
3496
|
+
provider: current.provider,
|
|
3497
|
+
model: current.model,
|
|
3498
|
+
...effort ? { reasoningEffort: effort } : {}
|
|
3499
|
+
} }),
|
|
3500
|
+
modelLabel: text.model,
|
|
3501
|
+
modelValue: currentLabel,
|
|
3502
|
+
defaultEffortLabel: text.modelDefault
|
|
3503
|
+
})
|
|
3023
3504
|
})
|
|
3024
3505
|
]
|
|
3025
3506
|
}),
|