@klarkxy/dsh-git-commit 0.3.3 → 0.3.5
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 +828 -63
- package/lib/index.js.map +1 -1
- package/package.json +10 -9
package/lib/client.inner.cjs
CHANGED
|
@@ -96,6 +96,7 @@ function parseModelMenuChoices(value, bound) {
|
|
|
96
96
|
provider,
|
|
97
97
|
model,
|
|
98
98
|
label: `${providerName} / ${name}`,
|
|
99
|
+
providerLabel: providerName,
|
|
99
100
|
efforts
|
|
100
101
|
});
|
|
101
102
|
}
|
|
@@ -142,11 +143,11 @@ function modelMenuShortName(choice) {
|
|
|
142
143
|
/** Group catalog choices by provider and filter them by a case-insensitive query. */
|
|
143
144
|
function groupModelMenuChoices(choices, query) {
|
|
144
145
|
const needle = query.trim().toLowerCase();
|
|
145
|
-
const matched = needle ? choices.filter((choice) => choice.label.toLowerCase().includes(needle) || choice.model.toLowerCase().includes(needle) || choice.provider.toLowerCase().includes(needle)) : choices;
|
|
146
|
+
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;
|
|
146
147
|
const groups = [];
|
|
147
148
|
for (const choice of matched) {
|
|
148
149
|
const sep = choice.label.indexOf(" / ");
|
|
149
|
-
const name = sep > 0 ? choice.label.slice(0, sep) : choice.provider;
|
|
150
|
+
const name = choice.providerLabel ?? (sep > 0 ? choice.label.slice(0, sep) : choice.provider);
|
|
150
151
|
const last = groups.at(-1);
|
|
151
152
|
if (last && last.provider === choice.provider) last.items.push(choice);
|
|
152
153
|
else groups.push({
|
|
@@ -373,7 +374,7 @@ function ModelMenu(props) {
|
|
|
373
374
|
empty: "No matching models",
|
|
374
375
|
clear: "Clear search"
|
|
375
376
|
};
|
|
376
|
-
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;
|
|
377
|
+
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;
|
|
377
378
|
const rootRef = (0, react.useRef)(null);
|
|
378
379
|
const openRef = (0, react.useRef)(open);
|
|
379
380
|
openRef.current = open;
|
|
@@ -388,9 +389,10 @@ function ModelMenu(props) {
|
|
|
388
389
|
const [pane, setPane] = (0, react.useState)("root");
|
|
389
390
|
const [query, setQuery] = (0, react.useState)("");
|
|
390
391
|
const [highlightedIndex, setHighlightedIndex] = (0, react.useState)(null);
|
|
392
|
+
const [keyNav, setKeyNav] = (0, react.useState)(0);
|
|
391
393
|
const [menuPos, setMenuPos] = (0, react.useState)(null);
|
|
392
394
|
const id = (0, react.useId)();
|
|
393
|
-
const showSearch = choices.length > 4;
|
|
395
|
+
const showSearch = alwaysShowSearch || choices.length > 4;
|
|
394
396
|
const effortRows = (0, react.useMemo)(() => [{
|
|
395
397
|
id: "",
|
|
396
398
|
name: defaultEffortLabel
|
|
@@ -407,7 +409,12 @@ function ModelMenu(props) {
|
|
|
407
409
|
items: (0, _deepseek_ai_dsh_client_ui_primitives.rankByName)(group.items.map((choice) => ({
|
|
408
410
|
choice,
|
|
409
411
|
name: modelMenuShortName(choice),
|
|
410
|
-
label:
|
|
412
|
+
label: [
|
|
413
|
+
choice.provider,
|
|
414
|
+
choice.providerLabel,
|
|
415
|
+
choice.model,
|
|
416
|
+
choice.label
|
|
417
|
+
].filter(Boolean).join(" ")
|
|
411
418
|
})), showSearch ? query.trim() : "")
|
|
412
419
|
})).filter((group) => group.items.length > 0), [
|
|
413
420
|
choices,
|
|
@@ -439,10 +446,12 @@ function ModelMenu(props) {
|
|
|
439
446
|
paneFocus.current = "drill";
|
|
440
447
|
setPane(next);
|
|
441
448
|
};
|
|
442
|
-
/** Leave a drilled pane for the
|
|
449
|
+
/** Leave a drilled pane for the entry pane, handing the keyboard back to it.
|
|
450
|
+
* A root entry refocuses the cell that opened the pane; a search-first
|
|
451
|
+
* (model) entry refocuses that pane's own intent. */
|
|
443
452
|
const back = (from) => {
|
|
444
|
-
paneFocus.current = from;
|
|
445
|
-
setPane(
|
|
453
|
+
paneFocus.current = initialPane === "root" ? from : "drill";
|
|
454
|
+
setPane(initialPane);
|
|
446
455
|
};
|
|
447
456
|
const moveFocus = (offset) => {
|
|
448
457
|
const items = itemRefs.current.filter((item) => item !== null && !item.disabled);
|
|
@@ -482,10 +491,18 @@ function ModelMenu(props) {
|
|
|
482
491
|
}, [requestedOpen, open]);
|
|
483
492
|
(0, react.useEffect)(() => {
|
|
484
493
|
if (!open) return;
|
|
485
|
-
setPane(
|
|
494
|
+
setPane(initialPane);
|
|
495
|
+
if (initialPane === "model") paneFocus.current = "drill";
|
|
486
496
|
setQuery("");
|
|
487
497
|
setHighlightedIndex(null);
|
|
488
|
-
}, [open]);
|
|
498
|
+
}, [open, initialPane]);
|
|
499
|
+
(0, react.useEffect)(() => {
|
|
500
|
+
if (disabled && open && closeOnDisable) changeOpen(false);
|
|
501
|
+
}, [
|
|
502
|
+
disabled,
|
|
503
|
+
open,
|
|
504
|
+
closeOnDisable
|
|
505
|
+
]);
|
|
489
506
|
(0, react.useEffect)(() => {
|
|
490
507
|
if (!open) return;
|
|
491
508
|
const closeOutside = (event) => {
|
|
@@ -493,9 +510,25 @@ function ModelMenu(props) {
|
|
|
493
510
|
if (rootRef.current?.contains(target) || menuRef.current?.contains(target)) return;
|
|
494
511
|
changeOpen(false);
|
|
495
512
|
};
|
|
513
|
+
const escapeOutside = (event) => {
|
|
514
|
+
if (event.key !== "Escape" || event.defaultPrevented || event.isComposing) return;
|
|
515
|
+
if (!openRef.current) return;
|
|
516
|
+
event.preventDefault();
|
|
517
|
+
if (pane !== initialPane) back(pane);
|
|
518
|
+
else close(true);
|
|
519
|
+
};
|
|
496
520
|
document.addEventListener("mousedown", closeOutside);
|
|
497
|
-
|
|
498
|
-
|
|
521
|
+
document.addEventListener("keydown", escapeOutside);
|
|
522
|
+
return () => {
|
|
523
|
+
document.removeEventListener("mousedown", closeOutside);
|
|
524
|
+
document.removeEventListener("keydown", escapeOutside);
|
|
525
|
+
};
|
|
526
|
+
}, [
|
|
527
|
+
open,
|
|
528
|
+
onOpenChange,
|
|
529
|
+
pane,
|
|
530
|
+
initialPane
|
|
531
|
+
]);
|
|
499
532
|
(0, react.useEffect)(() => {
|
|
500
533
|
const intent = paneFocus.current;
|
|
501
534
|
if (!open || menuPos === null || intent === null) return;
|
|
@@ -525,13 +558,21 @@ function ModelMenu(props) {
|
|
|
525
558
|
pane,
|
|
526
559
|
filteredGroups
|
|
527
560
|
]);
|
|
561
|
+
const modelPaneOpen = open && pane === "model";
|
|
562
|
+
const scrollSignal = !modelPaneOpen ? "closed" : keyNav === 0 ? "open" : `key:${keyNav}`;
|
|
563
|
+
const scrollSignalRef = (0, react.useRef)(null);
|
|
528
564
|
(0, react.useLayoutEffect)(() => {
|
|
529
|
-
if (
|
|
565
|
+
if (!modelPaneOpen || activeModelIndex < 0) {
|
|
566
|
+
if (!modelPaneOpen) scrollSignalRef.current = null;
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
if (scrollSignalRef.current === scrollSignal) return;
|
|
570
|
+
scrollSignalRef.current = scrollSignal;
|
|
571
|
+
itemRefs.current[leading.length + activeModelIndex]?.scrollIntoView({ block: "nearest" });
|
|
530
572
|
}, [
|
|
531
|
-
|
|
532
|
-
|
|
573
|
+
modelPaneOpen,
|
|
574
|
+
scrollSignal,
|
|
533
575
|
activeModelIndex,
|
|
534
|
-
visibleModels,
|
|
535
576
|
leading.length
|
|
536
577
|
]);
|
|
537
578
|
(0, react.useLayoutEffect)(() => {
|
|
@@ -546,7 +587,7 @@ function ModelMenu(props) {
|
|
|
546
587
|
let x = rect.right - lw, y = rect.top - 8 - lh;
|
|
547
588
|
if (lw > 0) x = Math.min(Math.max(x, margin), window.innerWidth - lw - margin);
|
|
548
589
|
if (lh > 0) y = Math.min(Math.max(y, margin), window.innerHeight - lh - margin);
|
|
549
|
-
setMenuPos({
|
|
590
|
+
setMenuPos((prev) => prev && prev.left === x && prev.top === y ? prev : {
|
|
550
591
|
left: x,
|
|
551
592
|
top: y
|
|
552
593
|
});
|
|
@@ -566,6 +607,13 @@ function ModelMenu(props) {
|
|
|
566
607
|
]);
|
|
567
608
|
function onKeyDown(event) {
|
|
568
609
|
if (event.defaultPrevented || event.nativeEvent.isComposing) return;
|
|
610
|
+
if (event.key === "Escape" && open) {
|
|
611
|
+
event.preventDefault();
|
|
612
|
+
if (pane !== initialPane) back(pane);
|
|
613
|
+
else close(true);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
if (disabled) return;
|
|
569
617
|
if (!open && [
|
|
570
618
|
"Enter",
|
|
571
619
|
" ",
|
|
@@ -579,17 +627,12 @@ function ModelMenu(props) {
|
|
|
579
627
|
requestOpen();
|
|
580
628
|
return;
|
|
581
629
|
}
|
|
582
|
-
if (event.key === "Escape" && open) {
|
|
583
|
-
event.preventDefault();
|
|
584
|
-
if (pane !== "root") back(pane);
|
|
585
|
-
else close(true);
|
|
586
|
-
return;
|
|
587
|
-
}
|
|
588
630
|
if (!open) return;
|
|
589
631
|
if (pane === "model" && showSearch && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
|
|
590
632
|
event.preventDefault();
|
|
591
633
|
if (visibleModels.length > 0) {
|
|
592
634
|
setHighlightedIndex((activeModelIndex + (event.key === "ArrowDown" ? 1 : -1) + visibleModels.length) % visibleModels.length);
|
|
635
|
+
setKeyNav((n) => n + 1);
|
|
593
636
|
searchRef.current?.focus();
|
|
594
637
|
}
|
|
595
638
|
return;
|
|
@@ -598,19 +641,19 @@ function ModelMenu(props) {
|
|
|
598
641
|
if (event.key === "Tab" && visibleModels.length === 0) return;
|
|
599
642
|
event.preventDefault();
|
|
600
643
|
const highlighted = visibleModels[activeModelIndex];
|
|
601
|
-
if (highlighted) {
|
|
644
|
+
if (highlighted && !busy) {
|
|
602
645
|
onPick({
|
|
603
646
|
provider: highlighted.provider,
|
|
604
647
|
model: highlighted.model
|
|
605
648
|
});
|
|
606
|
-
close(true);
|
|
649
|
+
if (closeOnPick) close(true);
|
|
607
650
|
}
|
|
608
651
|
return;
|
|
609
652
|
}
|
|
610
653
|
if (event.key === "Tab") {
|
|
611
654
|
if (event.shiftKey) {
|
|
612
655
|
event.preventDefault();
|
|
613
|
-
if (pane !==
|
|
656
|
+
if (pane !== initialPane) back(pane);
|
|
614
657
|
else close(true);
|
|
615
658
|
return;
|
|
616
659
|
}
|
|
@@ -634,7 +677,10 @@ function ModelMenu(props) {
|
|
|
634
677
|
}
|
|
635
678
|
}
|
|
636
679
|
function onBlur(event) {
|
|
637
|
-
|
|
680
|
+
const related = event.relatedTarget;
|
|
681
|
+
if (related instanceof Node && (rootRef.current?.contains(related) || menuRef.current?.contains(related) || !related.isConnected)) return;
|
|
682
|
+
if (!closeOnPick && !(related instanceof Node)) return;
|
|
683
|
+
if (busy && related == null) return;
|
|
638
684
|
if (open) changeOpen(false);
|
|
639
685
|
}
|
|
640
686
|
itemRefs.current = [];
|
|
@@ -655,11 +701,16 @@ function ModelMenu(props) {
|
|
|
655
701
|
"aria-label": modelLabel,
|
|
656
702
|
lang: language,
|
|
657
703
|
children: [
|
|
704
|
+
menuHeader ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
705
|
+
className: "dsh-model-menu-header",
|
|
706
|
+
children: menuHeader
|
|
707
|
+
}) : null,
|
|
658
708
|
pane === "root" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
659
709
|
ref: itemRef(),
|
|
660
710
|
type: "button",
|
|
661
711
|
role: "menuitem",
|
|
662
712
|
className: "dsh-model-menu-cell",
|
|
713
|
+
disabled,
|
|
663
714
|
onClick: () => drill("model"),
|
|
664
715
|
children: [
|
|
665
716
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -672,11 +723,12 @@ function ModelMenu(props) {
|
|
|
672
723
|
}),
|
|
673
724
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, { className: "dsh-model-menu-cellChevron" })
|
|
674
725
|
]
|
|
675
|
-
}), efforts.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
726
|
+
}), efforts.length > 0 && !reasoningSlider ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
676
727
|
ref: itemRef(),
|
|
677
728
|
type: "button",
|
|
678
729
|
role: "menuitem",
|
|
679
730
|
className: "dsh-model-menu-cell",
|
|
731
|
+
disabled,
|
|
680
732
|
onClick: () => drill("effort"),
|
|
681
733
|
children: [
|
|
682
734
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -695,6 +747,7 @@ function ModelMenu(props) {
|
|
|
695
747
|
className: "dsh-model-menu-searchRow",
|
|
696
748
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
697
749
|
ref: searchRef,
|
|
750
|
+
disabled,
|
|
698
751
|
className: `dsh-model-menu-search${query !== "" ? " dsh-model-menu-searchWithQuery" : ""}`,
|
|
699
752
|
type: "text",
|
|
700
753
|
role: "searchbox",
|
|
@@ -711,6 +764,7 @@ function ModelMenu(props) {
|
|
|
711
764
|
type: "button",
|
|
712
765
|
className: "dsh-model-menu-searchClear",
|
|
713
766
|
"aria-label": clearSearchLabel,
|
|
767
|
+
disabled,
|
|
714
768
|
onClick: () => {
|
|
715
769
|
setQuery("");
|
|
716
770
|
setHighlightedIndex(null);
|
|
@@ -733,10 +787,12 @@ function ModelMenu(props) {
|
|
|
733
787
|
type: "button",
|
|
734
788
|
role: "menuitemradio",
|
|
735
789
|
"aria-checked": item.selected === true,
|
|
790
|
+
disabled: disabled || busy,
|
|
736
791
|
className: `dsh-model-menu-option${item.selected === true ? " dsh-model-menu-selected" : ""}`,
|
|
737
792
|
onClick: () => {
|
|
793
|
+
if (disabled || busy) return;
|
|
738
794
|
onPickLeading?.(item.id);
|
|
739
|
-
close(true);
|
|
795
|
+
if (closeOnPick) close(true);
|
|
740
796
|
},
|
|
741
797
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
742
798
|
className: "dsh-model-menu-optionCopy",
|
|
@@ -762,6 +818,7 @@ function ModelMenu(props) {
|
|
|
762
818
|
"aria-checked": checked,
|
|
763
819
|
id: `${id}-model-${index}`,
|
|
764
820
|
tabIndex: showSearch ? -1 : 0,
|
|
821
|
+
disabled: disabled || busy,
|
|
765
822
|
onFocus: () => setHighlightedIndex(index),
|
|
766
823
|
"data-highlighted": index === activeModelIndex ? "" : void 0,
|
|
767
824
|
className: `dsh-model-menu-option dsh-model-menu-modelOption${checked ? " dsh-model-menu-selected" : ""}${index === activeModelIndex ? " dsh-model-menu-optionActive" : ""}`,
|
|
@@ -771,11 +828,12 @@ function ModelMenu(props) {
|
|
|
771
828
|
},
|
|
772
829
|
title: choice.label,
|
|
773
830
|
onClick: () => {
|
|
831
|
+
if (disabled || busy) return;
|
|
774
832
|
onPick({
|
|
775
833
|
provider: choice.provider,
|
|
776
834
|
model: choice.model
|
|
777
835
|
});
|
|
778
|
-
close(true);
|
|
836
|
+
if (closeOnPick) close(true);
|
|
779
837
|
},
|
|
780
838
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
781
839
|
className: "dsh-model-menu-optionCopy",
|
|
@@ -791,6 +849,14 @@ function ModelMenu(props) {
|
|
|
791
849
|
})
|
|
792
850
|
}, group.provider))]
|
|
793
851
|
}),
|
|
852
|
+
reasoningSlider && efforts.length > 0 && filteredGroups.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EffortSlider, {
|
|
853
|
+
label: effortLabel,
|
|
854
|
+
rows: effortRows,
|
|
855
|
+
selected: selectedEffort ?? "",
|
|
856
|
+
disabled,
|
|
857
|
+
busy,
|
|
858
|
+
onPick: (value) => onPickEffort?.(value)
|
|
859
|
+
}) : null,
|
|
794
860
|
filteredGroups.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
795
861
|
className: "dsh-model-menu-empty",
|
|
796
862
|
role: "status",
|
|
@@ -804,10 +870,12 @@ function ModelMenu(props) {
|
|
|
804
870
|
type: "button",
|
|
805
871
|
role: "menuitemradio",
|
|
806
872
|
"aria-checked": checked,
|
|
873
|
+
disabled: disabled || busy,
|
|
807
874
|
className: `dsh-model-menu-option${checked ? " dsh-model-menu-selected" : ""}`,
|
|
808
875
|
onClick: () => {
|
|
876
|
+
if (disabled || busy) return;
|
|
809
877
|
onPickEffort?.(row.id || void 0);
|
|
810
|
-
close(true);
|
|
878
|
+
if (closeOnPick) close(true);
|
|
811
879
|
},
|
|
812
880
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
813
881
|
className: "dsh-model-menu-optionCopy",
|
|
@@ -826,7 +894,8 @@ function ModelMenu(props) {
|
|
|
826
894
|
const trigger = (0, react.isValidElement)(anchor) ? (0, react.cloneElement)(anchor, {
|
|
827
895
|
"aria-haspopup": "menu",
|
|
828
896
|
"aria-expanded": open,
|
|
829
|
-
"aria-controls": open ? `${id}-menu` : void 0
|
|
897
|
+
"aria-controls": open ? `${id}-menu` : void 0,
|
|
898
|
+
"aria-disabled": disabled || void 0
|
|
830
899
|
}) : anchor;
|
|
831
900
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
832
901
|
ref: rootRef,
|
|
@@ -834,7 +903,7 @@ function ModelMenu(props) {
|
|
|
834
903
|
onKeyDown,
|
|
835
904
|
onBlur,
|
|
836
905
|
onClick: (event) => {
|
|
837
|
-
if (event.defaultPrevented) return;
|
|
906
|
+
if (disabled || event.defaultPrevented) return;
|
|
838
907
|
const button = event.target instanceof Element ? event.target.closest("button") : null;
|
|
839
908
|
if (!(button instanceof HTMLButtonElement) || button.disabled || !rootRef.current?.contains(button)) return;
|
|
840
909
|
requestOpen();
|
|
@@ -846,6 +915,102 @@ function ModelMenu(props) {
|
|
|
846
915
|
});
|
|
847
916
|
}
|
|
848
917
|
/**
|
|
918
|
+
* Discrete reasoning-effort slider shown under the model list in enhanced mode.
|
|
919
|
+
*
|
|
920
|
+
* A native range input (`step=1`) over exactly the rows the caller supplies —
|
|
921
|
+
* the default row plus each effort the selected model advertises — so it can
|
|
922
|
+
* never invent a level. It previews locally while dragging or arrowing and
|
|
923
|
+
* commits only when the gesture ends (pointer up, key up, or blur), so a drag
|
|
924
|
+
* does not write on every step. The menu stays open, so adjusting the effort
|
|
925
|
+
* never loses the model selection or the search query.
|
|
926
|
+
*
|
|
927
|
+
* The input itself stays the entire interaction and accessibility surface —
|
|
928
|
+
* keyboard, ARIA, disabled and the native step snapping all come from it —
|
|
929
|
+
* while the track, gradient fill and thumb below are decorative layers painted
|
|
930
|
+
* from the `--_p` percentage the preview position sets on the rail. Value
|
|
931
|
+
* changes only update that custom property, so the CSS transitions animate
|
|
932
|
+
* between levels without remounting nodes or restarting an animation.
|
|
933
|
+
*/
|
|
934
|
+
function EffortSlider(props) {
|
|
935
|
+
const { label, rows, selected, disabled = false, busy = false, onPick } = props;
|
|
936
|
+
const active = Math.max(0, rows.findIndex((row) => row.id === selected));
|
|
937
|
+
const [position, setPosition] = (0, react.useState)(active);
|
|
938
|
+
const positionRef = (0, react.useRef)(position);
|
|
939
|
+
(0, react.useEffect)(() => {
|
|
940
|
+
positionRef.current = active;
|
|
941
|
+
setPosition(active);
|
|
942
|
+
}, [active]);
|
|
943
|
+
const preview = (next) => {
|
|
944
|
+
positionRef.current = next;
|
|
945
|
+
setPosition(next);
|
|
946
|
+
};
|
|
947
|
+
const commit = () => {
|
|
948
|
+
if (disabled || busy) return;
|
|
949
|
+
const row = rows[positionRef.current];
|
|
950
|
+
if (row !== void 0) onPick(row.id === "" ? void 0 : row.id);
|
|
951
|
+
};
|
|
952
|
+
const max = Math.max(0, rows.length - 1);
|
|
953
|
+
const percent = max > 0 ? Math.round(position / max * 100) : 0;
|
|
954
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
955
|
+
className: "dsh-model-menu-sliderGroup",
|
|
956
|
+
role: "group",
|
|
957
|
+
"aria-label": label,
|
|
958
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
959
|
+
className: "dsh-model-menu-sliderHead",
|
|
960
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
961
|
+
className: "dsh-model-menu-sliderTitle",
|
|
962
|
+
children: label
|
|
963
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
964
|
+
className: "dsh-model-menu-sliderValue",
|
|
965
|
+
children: rows[position]?.name
|
|
966
|
+
})]
|
|
967
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
968
|
+
className: "dsh-model-menu-sliderRail",
|
|
969
|
+
style: { "--_p": percent },
|
|
970
|
+
children: [
|
|
971
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
972
|
+
type: "range",
|
|
973
|
+
className: "dsh-model-menu-sliderInput",
|
|
974
|
+
min: 0,
|
|
975
|
+
max,
|
|
976
|
+
step: 1,
|
|
977
|
+
value: position,
|
|
978
|
+
disabled,
|
|
979
|
+
"aria-label": label,
|
|
980
|
+
"aria-disabled": disabled || busy || void 0,
|
|
981
|
+
"aria-valuetext": rows[position]?.name,
|
|
982
|
+
onChange: (event) => {
|
|
983
|
+
const value = Number(event.target.value);
|
|
984
|
+
if (Number.isFinite(value)) preview(value);
|
|
985
|
+
},
|
|
986
|
+
onPointerUp: commit,
|
|
987
|
+
onKeyUp: commit,
|
|
988
|
+
onBlur: commit
|
|
989
|
+
}),
|
|
990
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
991
|
+
className: "dsh-model-menu-sliderTrack",
|
|
992
|
+
"aria-hidden": "true",
|
|
993
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
994
|
+
className: "dsh-model-menu-sliderFill",
|
|
995
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dsh-model-menu-sliderFillBar" })
|
|
996
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
997
|
+
className: "dsh-model-menu-sliderNodes",
|
|
998
|
+
children: rows.map((row, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
999
|
+
"data-covered": index <= position ? "" : void 0,
|
|
1000
|
+
className: "dsh-model-menu-sliderNode",
|
|
1001
|
+
style: { "--_n": max > 0 ? Math.round(index / max * 100) : 0 }
|
|
1002
|
+
}, row.id || "__default"))
|
|
1003
|
+
})]
|
|
1004
|
+
}),
|
|
1005
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1006
|
+
className: "dsh-model-menu-sliderThumb",
|
|
1007
|
+
"aria-hidden": "true"
|
|
1008
|
+
})
|
|
1009
|
+
]
|
|
1010
|
+
})]
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
849
1014
|
* ModelMenu styles, transcribed from the official ModelSelect.module.css (the
|
|
850
1015
|
* published dsh-client-ui-model-selection bundle): card capped at
|
|
851
1016
|
* min(360px, 100vh - 96px), 34px option cells at 13/18 with 5px 7px padding,
|
|
@@ -904,6 +1069,7 @@ const modelMenuCss = `
|
|
|
904
1069
|
overflow: hidden;
|
|
905
1070
|
}
|
|
906
1071
|
.dsh-model-menu-status, .dsh-model-menu-empty { color: var(--dsw-alias-label-tertiary); padding: 8px; font-size: 12px; line-height: 18px; }
|
|
1072
|
+
.dsh-model-menu-header { flex: none; padding: 2px 2px 4px; }
|
|
907
1073
|
.dsh-model-menu-searchRow { flex-shrink: 0; margin: 2px 0 3px; position: relative; }
|
|
908
1074
|
.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; }
|
|
909
1075
|
.dsh-model-menu-searchRow .dsh-model-menu-search:focus-within { border-color: transparent; }
|
|
@@ -982,7 +1148,235 @@ const modelMenuCss = `
|
|
|
982
1148
|
.dsh-model-menu-cellLabel { white-space: nowrap; flex: none; }
|
|
983
1149
|
.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; }
|
|
984
1150
|
.dsh-model-menu-cellChevron { width: 12px; height: 12px; color: var(--dsw-alias-menu-icon); flex: none; }
|
|
1151
|
+
/* Discrete reasoning-effort slider under the model list (enhanced mode).
|
|
1152
|
+
* The painted control is layered over a fully functional native range input:
|
|
1153
|
+
* the input stays on top, transparent, and owns pointer/keyboard/ARIA, while
|
|
1154
|
+
* the decorative track, fill and thumb render beneath it at pointer-events:
|
|
1155
|
+
* none. Position lives in one custom property (--_p, 0–100) on the rail, so
|
|
1156
|
+
* the only animated work a level change triggers is a clip-path/left
|
|
1157
|
+
* transition — no keyframes, no element churn. */
|
|
1158
|
+
.dsh-model-menu-sliderGroup { border-top: 0.5px solid var(--dsw-alias-border-l2); flex: none; margin-top: 4px; padding: 6px 8px 8px; }
|
|
1159
|
+
.dsh-model-menu-sliderHead { align-items: baseline; justify-content: space-between; gap: 8px; margin: 0 2px 4px; display: flex; }
|
|
1160
|
+
.dsh-model-menu-sliderTitle { color: var(--dsw-alias-label-tertiary); font-size: 12px; line-height: 18px; }
|
|
1161
|
+
.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); }
|
|
1162
|
+
.dsh-model-menu-sliderRail { position: relative; direction: ltr; height: 38px; margin-top: 2px; }
|
|
1163
|
+
.dsh-model-menu-sliderRail:has(:disabled) { opacity: 0.45; }
|
|
1164
|
+
.dsh-model-menu-sliderInput {
|
|
1165
|
+
appearance: none;
|
|
1166
|
+
-webkit-appearance: none;
|
|
1167
|
+
box-sizing: border-box;
|
|
1168
|
+
border: 0;
|
|
1169
|
+
cursor: pointer;
|
|
1170
|
+
direction: ltr;
|
|
1171
|
+
opacity: 0;
|
|
1172
|
+
margin: 0;
|
|
1173
|
+
padding: 0;
|
|
1174
|
+
width: 100%;
|
|
1175
|
+
height: 100%;
|
|
1176
|
+
position: absolute;
|
|
1177
|
+
inset: 0;
|
|
1178
|
+
z-index: 1;
|
|
1179
|
+
touch-action: pan-y;
|
|
1180
|
+
}
|
|
1181
|
+
.dsh-model-menu-sliderInput:disabled { cursor: default; }
|
|
1182
|
+
/* The transparent input must keep the painted thumb's geometry: an
|
|
1183
|
+
* appearance:none webkit thumb defaults to 16px, which would shift the
|
|
1184
|
+
* end-point positions 7px off the decorative 30px disc and misplace the
|
|
1185
|
+
* native drag hit target. Track gets the full rail height so the whole band
|
|
1186
|
+
* maps to values identically. */
|
|
1187
|
+
.dsh-model-menu-sliderInput::-webkit-slider-runnable-track { width: 100%; height: 100%; }
|
|
1188
|
+
.dsh-model-menu-sliderInput::-webkit-slider-thumb { appearance: none; -webkit-appearance: none; width: 30px; height: 30px; margin: 0; }
|
|
1189
|
+
.dsh-model-menu-sliderInput::-moz-range-track { width: 100%; height: 100%; }
|
|
1190
|
+
.dsh-model-menu-sliderInput::-moz-range-thumb { width: 30px; height: 30px; margin: 0; border: 0; }
|
|
1191
|
+
.dsh-model-menu-sliderTrack {
|
|
1192
|
+
pointer-events: none;
|
|
1193
|
+
border-radius: 999px;
|
|
1194
|
+
width: 100%;
|
|
1195
|
+
height: 26px;
|
|
1196
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
1197
|
+
box-shadow: inset 0 0 0 0.5px var(--dsw-alias-border-l2);
|
|
1198
|
+
position: absolute;
|
|
1199
|
+
inset-inline: 0;
|
|
1200
|
+
top: 50%;
|
|
1201
|
+
transform: translateY(-50%);
|
|
1202
|
+
/* No overflow:hidden — it would clip the fill's drop-shadow glow; the bar's
|
|
1203
|
+
* own clip-path already supplies the rounded reveal shape. */
|
|
1204
|
+
}
|
|
1205
|
+
/* The accent ramp paints the full track width once and is revealed by an
|
|
1206
|
+
* animated clip-path, so raising the effort unwraps more of the spectrum
|
|
1207
|
+
* instead of squashing the same gradient into a narrower bar. The glow lives
|
|
1208
|
+
* on the un-clipped parent: a filter on the clipped element itself would be
|
|
1209
|
+
* clipped away with it. */
|
|
1210
|
+
.dsh-model-menu-sliderFill {
|
|
1211
|
+
display: block;
|
|
1212
|
+
height: 100%;
|
|
1213
|
+
filter: drop-shadow(0 0 5px var(--dsw-alias-state-business-primary));
|
|
1214
|
+
transition: filter var(--ds-transition-duration) var(--ds-ease-in-out);
|
|
1215
|
+
}
|
|
1216
|
+
.dsh-model-menu-sliderInput:hover:not(:disabled) ~ .dsh-model-menu-sliderTrack .dsh-model-menu-sliderFill,
|
|
1217
|
+
.dsh-model-menu-sliderInput:active:not(:disabled) ~ .dsh-model-menu-sliderTrack .dsh-model-menu-sliderFill {
|
|
1218
|
+
filter: drop-shadow(0 0 9px var(--dsw-alias-state-business-primary));
|
|
1219
|
+
}
|
|
1220
|
+
/* The fill stops at the thumb's center, not at the track edge — at a 30px
|
|
1221
|
+
* thumb the end stops sit 15px in from each side, matching the node dots. */
|
|
1222
|
+
.dsh-model-menu-sliderFillBar {
|
|
1223
|
+
display: block;
|
|
1224
|
+
height: 100%;
|
|
1225
|
+
background: linear-gradient(90deg,
|
|
1226
|
+
var(--dsw-alias-state-business-primary),
|
|
1227
|
+
var(--dsw-alias-state-success-primary),
|
|
1228
|
+
var(--dsw-alias-state-warn-primary),
|
|
1229
|
+
var(--dsw-alias-state-error-primary));
|
|
1230
|
+
clip-path: inset(0 calc(100% - 15px - (100% - 30px) * var(--_p, 0) / 100) 0 0 round 999px);
|
|
1231
|
+
transition: clip-path var(--ds-transition-duration) var(--ds-ease-in-out);
|
|
1232
|
+
}
|
|
1233
|
+
/* Stop dots: one per declared row (default + every advertised effort), evenly
|
|
1234
|
+
* spread across the band between the two thumb-center extremes (inset 15px,
|
|
1235
|
+
* i.e. half the 30px thumb). --_n is 0-100 along that band. A covered dot
|
|
1236
|
+
* reads as a pale pin through the gradient; an uncovered one stays a neutral
|
|
1237
|
+
* notch on the empty track. */
|
|
1238
|
+
.dsh-model-menu-sliderNodes {
|
|
1239
|
+
pointer-events: none;
|
|
1240
|
+
display: block;
|
|
1241
|
+
position: absolute;
|
|
1242
|
+
inset-inline: 15px;
|
|
1243
|
+
inset-block: 0;
|
|
1244
|
+
}
|
|
1245
|
+
.dsh-model-menu-sliderNode {
|
|
1246
|
+
pointer-events: none;
|
|
1247
|
+
box-sizing: border-box;
|
|
1248
|
+
width: 4px;
|
|
1249
|
+
height: 4px;
|
|
1250
|
+
border-radius: 50%;
|
|
1251
|
+
background: color-mix(in srgb, var(--dsw-alias-label-secondary) 35%, transparent);
|
|
1252
|
+
position: absolute;
|
|
1253
|
+
left: calc(var(--_n, 0) * 1%);
|
|
1254
|
+
top: 50%;
|
|
1255
|
+
transform: translate(-50%, -50%);
|
|
1256
|
+
transition: background-color var(--ds-transition-duration) var(--ds-ease-in-out),
|
|
1257
|
+
transform var(--ds-transition-duration) var(--ds-ease-in-out);
|
|
1258
|
+
}
|
|
1259
|
+
.dsh-model-menu-sliderNode[data-covered] { background: color-mix(in srgb, var(--dsw-alias-label-primary) 60%, transparent); }
|
|
1260
|
+
.dsh-model-menu-sliderThumb {
|
|
1261
|
+
pointer-events: none;
|
|
1262
|
+
box-sizing: border-box;
|
|
1263
|
+
border-radius: 50%;
|
|
1264
|
+
width: 30px;
|
|
1265
|
+
height: 30px;
|
|
1266
|
+
background: var(--dsw-alias-button-primary-fill);
|
|
1267
|
+
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
1268
|
+
box-shadow: 0 0 0 3px var(--dsw-alias-interactive-bg-hover), var(--dsw-elevation-soft);
|
|
1269
|
+
position: absolute;
|
|
1270
|
+
left: calc((100% - 30px) * var(--_p, 0) / 100);
|
|
1271
|
+
top: 50%;
|
|
1272
|
+
transform: translateY(-50%) scale(0.92);
|
|
1273
|
+
transition: left var(--ds-transition-duration) var(--ds-ease-in-out),
|
|
1274
|
+
transform var(--ds-transition-duration) var(--ds-ease-in-out),
|
|
1275
|
+
box-shadow var(--ds-transition-duration) var(--ds-ease-in-out);
|
|
1276
|
+
}
|
|
1277
|
+
.dsh-model-menu-sliderInput:hover:not(:disabled) ~ .dsh-model-menu-sliderThumb { transform: translateY(-50%) scale(1); }
|
|
1278
|
+
.dsh-model-menu-sliderInput:active:not(:disabled) ~ .dsh-model-menu-sliderThumb {
|
|
1279
|
+
transform: translateY(-50%) scale(1.1);
|
|
1280
|
+
box-shadow: 0 0 0 4px var(--dsw-alias-interactive-bg-hover), 0 0 12px var(--dsw-alias-state-business-primary);
|
|
1281
|
+
}
|
|
1282
|
+
.dsh-model-menu-sliderInput:focus-visible ~ .dsh-model-menu-sliderThumb {
|
|
1283
|
+
box-shadow: 0 0 0 3px var(--dsw-alias-interactive-bg-hover),
|
|
1284
|
+
0 0 0 calc(3px + var(--dsw-focus-ring-width)) var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
1285
|
+
}
|
|
1286
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1287
|
+
.dsh-model-menu-sliderFill, .dsh-model-menu-sliderFillBar, .dsh-model-menu-sliderThumb,
|
|
1288
|
+
.dsh-model-menu-sliderNode, .dsh-model-menu-sliderValue { transition: none; }
|
|
1289
|
+
}
|
|
985
1290
|
`;
|
|
1291
|
+
//#endregion
|
|
1292
|
+
//#region ../dsh-plugin-kit/src/model-picker.tsx
|
|
1293
|
+
/**
|
|
1294
|
+
* Optional shared model picker (`klarkxy.model-picker` Factory).
|
|
1295
|
+
*
|
|
1296
|
+
* `@klarkxy/dsh-model-picker`, when installed, registers one root-scoped
|
|
1297
|
+
* Component Factory under `MODEL_PICKER_FACTORY`: an enhanced model menu with
|
|
1298
|
+
* a searchable provider-grouped list and a reasoning-effort slider, built on
|
|
1299
|
+
* the same ModelMenu recipe. Consumers keep their own ModelMenu as the
|
|
1300
|
+
* `fallback`; when no definition is registered the renderer resolves that
|
|
1301
|
+
* fallback verbatim, so the shared picker is strictly opt-in and every saved
|
|
1302
|
+
* value keeps its existing write path.
|
|
1303
|
+
*
|
|
1304
|
+
* `renderFactorySlot` is a framework-composed prop, never a context: the
|
|
1305
|
+
* component a plugin registers into any slot receives it automatically, but
|
|
1306
|
+
* the plugin must pass it down to whichever child renders the menu (or to the
|
|
1307
|
+
* render callback handed to registerSessionSettingsItem).
|
|
1308
|
+
*
|
|
1309
|
+
* The Factory manages its own open state; a consumer's `open`/`onOpenChange`
|
|
1310
|
+
* pair belongs to the fallback menu only and must not be fed to both.
|
|
1311
|
+
*/
|
|
1312
|
+
/**
|
|
1313
|
+
* The trigger every consumer used to hand its own ModelMenu, rebuilt as the
|
|
1314
|
+
* factory's `anchor` local component: identical classes and chevron, the menu
|
|
1315
|
+
* state coming from the occurrence's props instead of local open state.
|
|
1316
|
+
*
|
|
1317
|
+
* `ariaLabel` and `title` are the whole accessible name and tooltip (the
|
|
1318
|
+
* caller formats "field: current route" itself because the factory's resolved
|
|
1319
|
+
* caption may intentionally be a short name). The component identity stays
|
|
1320
|
+
* put across label changes — a new function would unmount the trigger, and
|
|
1321
|
+
* its cleanup would close a menu that `closeOnPick={false}` is keeping open.
|
|
1322
|
+
* The latest label, title, and `onClose` are read from refs.
|
|
1323
|
+
*/
|
|
1324
|
+
function useModelMenuAnchor(ariaLabel, title, onClose) {
|
|
1325
|
+
const closeRef = (0, react.useRef)(onClose);
|
|
1326
|
+
const labelRef = (0, react.useRef)(ariaLabel);
|
|
1327
|
+
const titleRef = (0, react.useRef)(title);
|
|
1328
|
+
closeRef.current = onClose;
|
|
1329
|
+
labelRef.current = ariaLabel;
|
|
1330
|
+
titleRef.current = title;
|
|
1331
|
+
return (0, react.useMemo)(() => {
|
|
1332
|
+
const ModelPickerTrigger = (props) => {
|
|
1333
|
+
(0, react.useEffect)(() => () => closeRef.current?.(), []);
|
|
1334
|
+
const record = props;
|
|
1335
|
+
const injected = {};
|
|
1336
|
+
for (const [key, value] of Object.entries(record)) if (key.startsWith("aria-")) injected[key] = value;
|
|
1337
|
+
const extraClass = typeof record.className === "string" ? ` ${record.className}` : "";
|
|
1338
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1339
|
+
type: "button",
|
|
1340
|
+
className: `dsh-model-menu-trigger${extraClass}`,
|
|
1341
|
+
disabled: props.disabled,
|
|
1342
|
+
"aria-haspopup": "menu",
|
|
1343
|
+
"aria-expanded": props.open,
|
|
1344
|
+
...injected,
|
|
1345
|
+
"aria-label": labelRef.current,
|
|
1346
|
+
title: titleRef.current,
|
|
1347
|
+
children: [
|
|
1348
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1349
|
+
className: "dsh-model-menu-triggerLabel",
|
|
1350
|
+
children: props.label
|
|
1351
|
+
}),
|
|
1352
|
+
props.effortLabel ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1353
|
+
className: "dsh-model-menu-triggerEffort",
|
|
1354
|
+
children: props.effortLabel
|
|
1355
|
+
}) : null,
|
|
1356
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${props.open ? " dsh-model-menu-chevronOpen" : ""}` })
|
|
1357
|
+
]
|
|
1358
|
+
});
|
|
1359
|
+
};
|
|
1360
|
+
return ModelPickerTrigger;
|
|
1361
|
+
}, []);
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* Render the shared model picker while it is registered; `options.fallback`
|
|
1365
|
+
* whenever it is not (plugin absent, disabled, or unloaded).
|
|
1366
|
+
*
|
|
1367
|
+
* `options.anchor` is optional: the factory ships a default trigger, so only
|
|
1368
|
+
* consumers that need their own chrome (custom aria-label, tooltip, class)
|
|
1369
|
+
* pass one built with {@link useModelMenuAnchor}. The returned node is never
|
|
1370
|
+
* assumed to be the factory's own output — a defensive `?? fallback` covers a
|
|
1371
|
+
* renderer that yields null for an unregistered definition.
|
|
1372
|
+
*/
|
|
1373
|
+
function renderModelPicker(renderFactorySlot, props, options) {
|
|
1374
|
+
if (typeof renderFactorySlot !== "function") return options.fallback;
|
|
1375
|
+
return renderFactorySlot("klarkxy.model-picker", props, {
|
|
1376
|
+
...options.anchor ? { slots: { ["anchor"]: options.anchor } } : {},
|
|
1377
|
+
fallback: options.fallback
|
|
1378
|
+
}) ?? options.fallback;
|
|
1379
|
+
}
|
|
986
1380
|
Object.values({
|
|
987
1381
|
/** Surfaces, from the page up to the topmost nested card. */
|
|
988
1382
|
surface: [
|
|
@@ -1072,7 +1466,9 @@ Object.values({
|
|
|
1072
1466
|
"--dsh-scrollbar-thumb-hover",
|
|
1073
1467
|
"--dsh-content-font-size-secondary",
|
|
1074
1468
|
"--dsh-content-font-delta",
|
|
1075
|
-
"--dsh-frame-top-clearance"
|
|
1469
|
+
"--dsh-frame-top-clearance",
|
|
1470
|
+
"--dsh-composer-model-icon-display",
|
|
1471
|
+
"--dsh-composer-model-text-display"
|
|
1076
1472
|
]
|
|
1077
1473
|
}).flat();
|
|
1078
1474
|
/**
|
|
@@ -1799,7 +2195,7 @@ async function callAuxiliaryRpc(client, endpoint, payload = {}) {
|
|
|
1799
2195
|
* The shared card. All state comes from the shared host service over the
|
|
1800
2196
|
* plugin-kit RPC channel; the card only renders the stored row.
|
|
1801
2197
|
*/
|
|
1802
|
-
function AuxiliaryModelCard({ client, subscribe }) {
|
|
2198
|
+
function AuxiliaryModelCard({ client, subscribe, renderFactorySlot }) {
|
|
1803
2199
|
const language = useAuxiliaryLocale(client);
|
|
1804
2200
|
const text = auxiliaryCopy[language];
|
|
1805
2201
|
const [settings, setSettings] = (0, react.useState)();
|
|
@@ -1920,6 +2316,17 @@ function AuxiliaryModelCard({ client, subscribe }) {
|
|
|
1920
2316
|
refreshCatalog
|
|
1921
2317
|
]);
|
|
1922
2318
|
const locked = busy;
|
|
2319
|
+
const current = settings?.model ?? {
|
|
2320
|
+
provider: "",
|
|
2321
|
+
model: ""
|
|
2322
|
+
};
|
|
2323
|
+
const follow = !current.provider || !current.model;
|
|
2324
|
+
const selected = follow ? void 0 : choices.find((item) => item.provider === current.provider && item.model === current.model);
|
|
2325
|
+
const efforts = follow ? [] : modelMenuEffortOptions(selected, current.reasoningEffort);
|
|
2326
|
+
const currentLabel = follow ? text.modelDefault : selected?.label ?? `${current.provider} / ${current.model}`;
|
|
2327
|
+
const selectedEffort = efforts.find((item) => item.id === (current.reasoningEffort || ""));
|
|
2328
|
+
const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : void 0;
|
|
2329
|
+
const anchor = useModelMenuAnchor(`${text.model}: ${settings ? currentLabel : ""}`, void 0, () => setMenuOpen(false));
|
|
1923
2330
|
if (!settings) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1924
2331
|
className: "ams-root dsh-ui-card",
|
|
1925
2332
|
"data-testid": "auxiliary-model-settings",
|
|
@@ -1958,16 +2365,6 @@ function AuxiliaryModelCard({ client, subscribe }) {
|
|
|
1958
2365
|
})
|
|
1959
2366
|
]
|
|
1960
2367
|
});
|
|
1961
|
-
const current = settings.model ?? {
|
|
1962
|
-
provider: "",
|
|
1963
|
-
model: ""
|
|
1964
|
-
};
|
|
1965
|
-
const follow = !current.provider || !current.model;
|
|
1966
|
-
const selected = follow ? void 0 : choices.find((item) => item.provider === current.provider && item.model === current.model);
|
|
1967
|
-
const efforts = follow ? [] : modelMenuEffortOptions(selected, current.reasoningEffort);
|
|
1968
|
-
const currentLabel = follow ? text.modelDefault : selected?.label ?? `${current.provider} / ${current.model}`;
|
|
1969
|
-
const selectedEffort = efforts.find((item) => item.id === (current.reasoningEffort || ""));
|
|
1970
|
-
const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : void 0;
|
|
1971
2368
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1972
2369
|
className: "ams-root dsh-ui-card",
|
|
1973
2370
|
"data-testid": "auxiliary-model-settings",
|
|
@@ -1991,28 +2388,12 @@ function AuxiliaryModelCard({ client, subscribe }) {
|
|
|
1991
2388
|
state: catalogState,
|
|
1992
2389
|
locale: language
|
|
1993
2390
|
}),
|
|
1994
|
-
|
|
2391
|
+
renderModelPicker(renderFactorySlot, {
|
|
1995
2392
|
locale: language,
|
|
1996
|
-
|
|
2393
|
+
disabled: locked,
|
|
2394
|
+
busy: locked,
|
|
1997
2395
|
onOpenChange: (next) => setMenuOpen(next && !locked),
|
|
1998
2396
|
listClassName: "ams-root ams-menu-portal",
|
|
1999
|
-
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2000
|
-
type: "button",
|
|
2001
|
-
className: "dsh-model-menu-trigger",
|
|
2002
|
-
disabled: locked,
|
|
2003
|
-
"aria-label": `${text.model}: ${currentLabel}`,
|
|
2004
|
-
children: [
|
|
2005
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2006
|
-
className: "dsh-model-menu-triggerLabel",
|
|
2007
|
-
children: currentLabel
|
|
2008
|
-
}),
|
|
2009
|
-
!follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2010
|
-
className: "dsh-model-menu-triggerEffort",
|
|
2011
|
-
children: effortName ?? text.effortDefault
|
|
2012
|
-
}) : null,
|
|
2013
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen && !locked ? " dsh-model-menu-chevronOpen" : ""}` })
|
|
2014
|
-
]
|
|
2015
|
-
}),
|
|
2016
2397
|
choices,
|
|
2017
2398
|
selected: follow ? void 0 : {
|
|
2018
2399
|
provider: current.provider,
|
|
@@ -2035,9 +2416,61 @@ function AuxiliaryModelCard({ client, subscribe }) {
|
|
|
2035
2416
|
model: current.model,
|
|
2036
2417
|
...effort ? { reasoningEffort: effort } : {}
|
|
2037
2418
|
}),
|
|
2419
|
+
triggerLabel: currentLabel,
|
|
2420
|
+
triggerEffortLabel: !follow && efforts.length ? effortName ?? text.effortDefault : void 0,
|
|
2038
2421
|
modelLabel: text.model,
|
|
2039
2422
|
modelValue: currentLabel,
|
|
2040
2423
|
defaultEffortLabel: text.effortDefault
|
|
2424
|
+
}, {
|
|
2425
|
+
anchor,
|
|
2426
|
+
fallback: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
|
|
2427
|
+
locale: language,
|
|
2428
|
+
open: menuOpen && !locked,
|
|
2429
|
+
onOpenChange: (next) => setMenuOpen(next && !locked),
|
|
2430
|
+
listClassName: "ams-root ams-menu-portal",
|
|
2431
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2432
|
+
type: "button",
|
|
2433
|
+
className: "dsh-model-menu-trigger",
|
|
2434
|
+
disabled: locked,
|
|
2435
|
+
"aria-label": `${text.model}: ${currentLabel}`,
|
|
2436
|
+
children: [
|
|
2437
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2438
|
+
className: "dsh-model-menu-triggerLabel",
|
|
2439
|
+
children: currentLabel
|
|
2440
|
+
}),
|
|
2441
|
+
!follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2442
|
+
className: "dsh-model-menu-triggerEffort",
|
|
2443
|
+
children: effortName ?? text.effortDefault
|
|
2444
|
+
}) : null,
|
|
2445
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen && !locked ? " dsh-model-menu-chevronOpen" : ""}` })
|
|
2446
|
+
]
|
|
2447
|
+
}),
|
|
2448
|
+
choices,
|
|
2449
|
+
selected: follow ? void 0 : {
|
|
2450
|
+
provider: current.provider,
|
|
2451
|
+
model: current.model
|
|
2452
|
+
},
|
|
2453
|
+
onPick: (picked) => save({
|
|
2454
|
+
provider: picked.provider,
|
|
2455
|
+
model: picked.model
|
|
2456
|
+
}),
|
|
2457
|
+
leading: [{
|
|
2458
|
+
id: "default",
|
|
2459
|
+
label: text.modelDefault,
|
|
2460
|
+
selected: follow
|
|
2461
|
+
}],
|
|
2462
|
+
onPickLeading: () => save(null),
|
|
2463
|
+
efforts,
|
|
2464
|
+
selectedEffort: current.reasoningEffort || "",
|
|
2465
|
+
onPickEffort: (effort) => save(follow ? null : {
|
|
2466
|
+
provider: current.provider,
|
|
2467
|
+
model: current.model,
|
|
2468
|
+
...effort ? { reasoningEffort: effort } : {}
|
|
2469
|
+
}),
|
|
2470
|
+
modelLabel: text.model,
|
|
2471
|
+
modelValue: currentLabel,
|
|
2472
|
+
defaultEffortLabel: text.effortDefault
|
|
2473
|
+
})
|
|
2041
2474
|
}),
|
|
2042
2475
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2043
2476
|
className: "dsh-ui-hint",
|
|
@@ -2139,9 +2572,10 @@ function acquireAuxiliaryModelCard(root, ctx) {
|
|
|
2139
2572
|
let unprovide;
|
|
2140
2573
|
const watchLease = acquireWatchCoordinator(root).mount();
|
|
2141
2574
|
const subscriber = createModelSettingsSubscriber(ctx.root, AUXILIARY_MODEL_PACKAGE, AUXILIARY_MODEL_CHANGES_IDENTITY, (ns) => ns === AUXILIARY_MODEL_NS);
|
|
2142
|
-
const render = () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuxiliaryModelCard, {
|
|
2575
|
+
const render = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuxiliaryModelCard, {
|
|
2143
2576
|
client: root,
|
|
2144
|
-
subscribe: subscriber.subscribe
|
|
2577
|
+
subscribe: subscriber.subscribe,
|
|
2578
|
+
renderFactorySlot: props.renderFactorySlot
|
|
2145
2579
|
});
|
|
2146
2580
|
const service = {
|
|
2147
2581
|
retain() {
|
|
@@ -2397,7 +2831,7 @@ function GitBranchIcon() {
|
|
|
2397
2831
|
]
|
|
2398
2832
|
});
|
|
2399
2833
|
}
|
|
2400
|
-
function GitCommitAction({ sessionId, useSession, call, t, client, subscribeSettings }) {
|
|
2834
|
+
function GitCommitAction({ sessionId, useSession, call, t, client, subscribeSettings, renderFactorySlot }) {
|
|
2401
2835
|
const [open, setOpen] = (0, react.useState)(false);
|
|
2402
2836
|
const [status, setStatus] = (0, react.useState)();
|
|
2403
2837
|
const [loading, setLoading] = (0, react.useState)(false);
|
|
@@ -2613,6 +3047,7 @@ function GitCommitAction({ sessionId, useSession, call, t, client, subscribeSett
|
|
|
2613
3047
|
client,
|
|
2614
3048
|
subscribeSettings,
|
|
2615
3049
|
disabled: running,
|
|
3050
|
+
renderFactorySlot,
|
|
2616
3051
|
onSaving: markWrite,
|
|
2617
3052
|
onWriteFailed: markWriteFailed,
|
|
2618
3053
|
onReady: markSeen,
|
|
@@ -2913,6 +3348,17 @@ function GitCommitSettingsPanel(props) {
|
|
|
2913
3348
|
props.disabled
|
|
2914
3349
|
]);
|
|
2915
3350
|
const locked = busy || controlsLocked;
|
|
3351
|
+
const current = settings?.model ?? {
|
|
3352
|
+
provider: "",
|
|
3353
|
+
model: ""
|
|
3354
|
+
};
|
|
3355
|
+
const follow = !current.provider || !current.model;
|
|
3356
|
+
const selected = follow ? void 0 : choices.find((item) => item.provider === current.provider && item.model === current.model);
|
|
3357
|
+
const efforts = follow ? [] : modelMenuEffortOptions(selected, current.reasoningEffort);
|
|
3358
|
+
const currentLabel = follow ? text.modelDefault : selected?.label ?? `${current.provider} / ${current.model}`;
|
|
3359
|
+
const selectedEffort = efforts.find((item) => item.id === (current.reasoningEffort || ""));
|
|
3360
|
+
const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : void 0;
|
|
3361
|
+
const anchor = useModelMenuAnchor(`${text.model}: ${settings ? currentLabel : ""}`, void 0, () => setMenuOpen(false));
|
|
2916
3362
|
if (!settings) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2917
3363
|
className: "gcm-root gcm-settings dsh-ui-stack",
|
|
2918
3364
|
children: [
|
|
@@ -2939,13 +3385,6 @@ function GitCommitSettingsPanel(props) {
|
|
|
2939
3385
|
})
|
|
2940
3386
|
]
|
|
2941
3387
|
});
|
|
2942
|
-
const current = settings.model;
|
|
2943
|
-
const follow = !current.provider || !current.model;
|
|
2944
|
-
const selected = follow ? void 0 : choices.find((item) => item.provider === current.provider && item.model === current.model);
|
|
2945
|
-
const efforts = follow ? [] : modelMenuEffortOptions(selected, current.reasoningEffort);
|
|
2946
|
-
const currentLabel = follow ? text.modelDefault : selected?.label ?? `${current.provider} / ${current.model}`;
|
|
2947
|
-
const selectedEffort = efforts.find((item) => item.id === (current.reasoningEffort || ""));
|
|
2948
|
-
const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : void 0;
|
|
2949
3388
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2950
3389
|
className: "gcm-root gcm-settings dsh-ui-stack",
|
|
2951
3390
|
"data-testid": "git-commit-settings",
|
|
@@ -2961,28 +3400,12 @@ function GitCommitSettingsPanel(props) {
|
|
|
2961
3400
|
state: catalogState,
|
|
2962
3401
|
locale: language
|
|
2963
3402
|
}),
|
|
2964
|
-
|
|
3403
|
+
renderModelPicker(props.renderFactorySlot, {
|
|
2965
3404
|
locale: language,
|
|
2966
|
-
|
|
3405
|
+
disabled: locked,
|
|
3406
|
+
busy: locked,
|
|
2967
3407
|
onOpenChange: (next) => setMenuOpen(next && !locked),
|
|
2968
3408
|
listClassName: "gcm-root gcm-menu-portal",
|
|
2969
|
-
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2970
|
-
type: "button",
|
|
2971
|
-
className: "dsh-model-menu-trigger",
|
|
2972
|
-
disabled: locked,
|
|
2973
|
-
"aria-label": `${text.model}: ${currentLabel}`,
|
|
2974
|
-
children: [
|
|
2975
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2976
|
-
className: "dsh-model-menu-triggerLabel",
|
|
2977
|
-
children: currentLabel
|
|
2978
|
-
}),
|
|
2979
|
-
!follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2980
|
-
className: "dsh-model-menu-triggerEffort",
|
|
2981
|
-
children: effortName ?? text.modelDefault
|
|
2982
|
-
}) : null,
|
|
2983
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen && !locked ? " dsh-model-menu-chevronOpen" : ""}` })
|
|
2984
|
-
]
|
|
2985
|
-
}),
|
|
2986
3409
|
choices,
|
|
2987
3410
|
selected: follow ? void 0 : {
|
|
2988
3411
|
provider: current.provider,
|
|
@@ -3011,9 +3434,67 @@ function GitCommitSettingsPanel(props) {
|
|
|
3011
3434
|
model: current.model,
|
|
3012
3435
|
...effort ? { reasoningEffort: effort } : {}
|
|
3013
3436
|
} }),
|
|
3437
|
+
triggerLabel: currentLabel,
|
|
3438
|
+
triggerEffortLabel: !follow && efforts.length ? effortName ?? text.modelDefault : void 0,
|
|
3014
3439
|
modelLabel: text.model,
|
|
3015
3440
|
modelValue: currentLabel,
|
|
3016
3441
|
defaultEffortLabel: text.modelDefault
|
|
3442
|
+
}, {
|
|
3443
|
+
anchor,
|
|
3444
|
+
fallback: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
|
|
3445
|
+
locale: language,
|
|
3446
|
+
open: menuOpen && !locked,
|
|
3447
|
+
onOpenChange: (next) => setMenuOpen(next && !locked),
|
|
3448
|
+
listClassName: "gcm-root gcm-menu-portal",
|
|
3449
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3450
|
+
type: "button",
|
|
3451
|
+
className: "dsh-model-menu-trigger",
|
|
3452
|
+
disabled: locked,
|
|
3453
|
+
"aria-label": `${text.model}: ${currentLabel}`,
|
|
3454
|
+
children: [
|
|
3455
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3456
|
+
className: "dsh-model-menu-triggerLabel",
|
|
3457
|
+
children: currentLabel
|
|
3458
|
+
}),
|
|
3459
|
+
!follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3460
|
+
className: "dsh-model-menu-triggerEffort",
|
|
3461
|
+
children: effortName ?? text.modelDefault
|
|
3462
|
+
}) : null,
|
|
3463
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen && !locked ? " dsh-model-menu-chevronOpen" : ""}` })
|
|
3464
|
+
]
|
|
3465
|
+
}),
|
|
3466
|
+
choices,
|
|
3467
|
+
selected: follow ? void 0 : {
|
|
3468
|
+
provider: current.provider,
|
|
3469
|
+
model: current.model
|
|
3470
|
+
},
|
|
3471
|
+
onPick: (picked) => save({ model: {
|
|
3472
|
+
provider: picked.provider,
|
|
3473
|
+
model: picked.model
|
|
3474
|
+
} }),
|
|
3475
|
+
leading: [{
|
|
3476
|
+
id: "default",
|
|
3477
|
+
label: text.modelDefault,
|
|
3478
|
+
selected: follow
|
|
3479
|
+
}],
|
|
3480
|
+
onPickLeading: () => save({ model: {
|
|
3481
|
+
provider: "",
|
|
3482
|
+
model: ""
|
|
3483
|
+
} }),
|
|
3484
|
+
efforts,
|
|
3485
|
+
selectedEffort: current.reasoningEffort || "",
|
|
3486
|
+
onPickEffort: (effort) => save({ model: follow ? {
|
|
3487
|
+
provider: "",
|
|
3488
|
+
model: ""
|
|
3489
|
+
} : {
|
|
3490
|
+
provider: current.provider,
|
|
3491
|
+
model: current.model,
|
|
3492
|
+
...effort ? { reasoningEffort: effort } : {}
|
|
3493
|
+
} }),
|
|
3494
|
+
modelLabel: text.model,
|
|
3495
|
+
modelValue: currentLabel,
|
|
3496
|
+
defaultEffortLabel: text.modelDefault
|
|
3497
|
+
})
|
|
3017
3498
|
})
|
|
3018
3499
|
]
|
|
3019
3500
|
}),
|