@klarkxy/dsh-current-title 0.2.3 → 0.2.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/lib/client.js CHANGED
@@ -7,6 +7,7 @@ window.__ModuleLoader__.load({
7
7
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
8
8
  let react = require("react");
9
9
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
10
+ let react_dom = require("react-dom");
10
11
  let react_jsx_runtime = require("react/jsx-runtime");
11
12
  //#region ../dsh-plugin-kit/src/client-utils.ts
12
13
  /** Fixed pause for failing background reads; manual work is never replayed. */
@@ -483,6 +484,29 @@ ${scope} {
483
484
  & .dsh-ui-select:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
484
485
  & .dsh-ui-select[aria-invalid='true'] { border-color: var(--dsw-alias-state-error-primary); }
485
486
 
487
+ /* --- Native textarea. The primitives ship no textarea either, so a settings
488
+ * prompt keeps the platform element and the same field shell as the select:
489
+ * 0.5px l4, radius-md, layer-3, 13/1.5 type. It grows vertically instead of
490
+ * locking to the select's 34px row. --- */
491
+ & .dsh-ui-textarea {
492
+ box-sizing: border-box;
493
+ width: 100%;
494
+ min-width: 0;
495
+ resize: vertical;
496
+ padding: 8px 12px;
497
+ border: 0.5px solid var(--dsw-alias-border-l4);
498
+ border-radius: var(--dsw-radius-md);
499
+ background: var(--dsw-alias-bg-layer-3);
500
+ font: inherit;
501
+ font-size: 13px;
502
+ line-height: 1.5;
503
+ color: var(--dsw-alias-label-primary);
504
+ }
505
+ & .dsh-ui-textarea:focus-visible { outline: none; border-color: var(--dsw-alias-state-business-primary); }
506
+ & .dsh-ui-textarea:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
507
+ & .dsh-ui-textarea::placeholder { color: var(--dsw-alias-label-dimmed); }
508
+ & .dsh-ui-textarea[aria-invalid='true'] { border-color: var(--dsw-alias-state-error-primary); }
509
+
486
510
  /* Long panels scroll inside themselves instead of growing the host page. */
487
511
  & .dsh-ui-panel { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
488
512
  & .dsh-ui-panel-wide { max-width: 100%; }
@@ -650,6 +674,965 @@ function createModelSettingsSubscriber(ctx, namespace) {
650
674
  } });
651
675
  }
652
676
  //#endregion
677
+ //#region ../dsh-model-route/src/catalog.ts
678
+ /** Both services are required: Cordis checks the nested namespace separately. */
679
+ const MODEL_CATALOG_INJECT = ["remote", "remote.session"];
680
+ function asRecord(value) {
681
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
682
+ }
683
+ function asText(value) {
684
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
685
+ }
686
+ /** A useful diagnostic even when a transport rejects with a plain object. */
687
+ function modelCatalogErrorMessage(value) {
688
+ if (value instanceof Error && value.message) return value.message;
689
+ return asText(asRecord(value)?.message) ?? asText(value) ?? "Unable to load the model catalog.";
690
+ }
691
+ /**
692
+ * Validate and unwrap a successful catalog without discarding provider failures
693
+ * or newer host metadata. Names and metadata remain optional for older hosts.
694
+ * A broken response is never presented as a successfully loaded empty menu.
695
+ */
696
+ function checkedModelCatalog(value) {
697
+ const envelope = asRecord(value);
698
+ if (envelope?.ok === false) throw new Error(modelCatalogErrorMessage(envelope.error ?? envelope));
699
+ const root = envelope?.ok === true ? asRecord(envelope.value) : envelope;
700
+ const malformed = (field) => {
701
+ throw new Error(`Invalid model catalog: ${field}.`);
702
+ };
703
+ if (!root || !Array.isArray(root.groups)) return malformed("groups must be an array");
704
+ for (const value of root.groups) {
705
+ const group = asRecord(value);
706
+ if (!group || !asText(group.id) || !Array.isArray(group.models)) return malformed("each group needs an id and models array");
707
+ for (const value of group.models) {
708
+ const model = asRecord(value);
709
+ if (!model || !asText(model.id)) return malformed("each model needs an id");
710
+ if (model.reasoning !== void 0) {
711
+ const reasoning = asRecord(model.reasoning);
712
+ if (!reasoning || !Array.isArray(reasoning.efforts) || reasoning.efforts.some((value) => !parseEffort(value))) return malformed("reasoning efforts must be an array of identified efforts");
713
+ }
714
+ }
715
+ }
716
+ if (root.failures !== void 0 && (!Array.isArray(root.failures) || root.failures.some((value) => {
717
+ const failure = asRecord(value);
718
+ return !failure || !asText(failure.id) || typeof failure.name !== "string" || typeof failure.message !== "string";
719
+ }))) return malformed("failures must be an array of provider failures");
720
+ return root;
721
+ }
722
+ /** Read through the injected session namespace; absence and rejection are errors. */
723
+ async function readModelCatalog(client) {
724
+ const session = client.remote?.session;
725
+ if (typeof session?.modelCatalog !== "function") throw new Error("Model catalog unavailable: inject both remote and remote.session.");
726
+ return checkedModelCatalog(await session.modelCatalog());
727
+ }
728
+ /** Provider failures are partial results, not a transport failure. */
729
+ function modelCatalogFailures(value) {
730
+ try {
731
+ return checkedModelCatalog(value).failures ?? [];
732
+ } catch {
733
+ return [];
734
+ }
735
+ }
736
+ function parseEffort(value) {
737
+ const row = asRecord(value);
738
+ const id = row ? asText(row.id) : void 0;
739
+ if (!id) return void 0;
740
+ return {
741
+ id,
742
+ name: row ? asText(row.name) ?? id : id
743
+ };
744
+ }
745
+ /** Parse the host session catalog into flat choices, keeping a saved route selectable. */
746
+ function parseModelMenuChoices(value, bound) {
747
+ const envelope = asRecord(value);
748
+ const root = (envelope && envelope.ok === true ? asRecord(envelope.value) : void 0) ?? envelope ?? {};
749
+ const groups = Array.isArray(root.groups) ? root.groups : [];
750
+ const choices = [];
751
+ for (const group of groups) {
752
+ const groupRow = asRecord(group);
753
+ const provider = groupRow ? asText(groupRow.id) : void 0;
754
+ if (!provider || !groupRow || !Array.isArray(groupRow.models)) continue;
755
+ const providerName = asText(groupRow.name) ?? provider;
756
+ for (const entry of groupRow.models) {
757
+ const modelRow = asRecord(entry);
758
+ const model = modelRow ? asText(modelRow.id) : void 0;
759
+ if (!model || !modelRow) continue;
760
+ const name = asText(modelRow.name) ?? model;
761
+ const reasoning = asRecord(modelRow.reasoning);
762
+ const efforts = reasoning && Array.isArray(reasoning.efforts) ? reasoning.efforts.flatMap((effort) => {
763
+ const parsed = parseEffort(effort);
764
+ return parsed ? [parsed] : [];
765
+ }) : [];
766
+ choices.push({
767
+ provider,
768
+ model,
769
+ label: `${providerName} / ${name}`,
770
+ efforts
771
+ });
772
+ }
773
+ }
774
+ if (bound?.provider && bound.model && !choices.some((item) => item.provider === bound.provider && item.model === bound.model)) choices.push({
775
+ provider: bound.provider,
776
+ model: bound.model,
777
+ label: `${bound.provider} / ${bound.model}`,
778
+ efforts: []
779
+ });
780
+ return choices;
781
+ }
782
+ /** Reasoning-effort options for one choice; the saved value stays selectable. */
783
+ function modelMenuEffortOptions(choice, current) {
784
+ const listed = (choice?.efforts ?? []).map((item) => ({ ...item }));
785
+ if (current && !listed.some((item) => item.id === current)) listed.push({
786
+ id: current,
787
+ name: current
788
+ });
789
+ return listed;
790
+ }
791
+ //#endregion
792
+ //#region ../dsh-model-route/src/menu.ts
793
+ /** Localize standard effort ids only when the provider has not supplied a display name. */
794
+ function modelMenuEffortLabel(effort, locale) {
795
+ if (effort.name !== effort.id) return effort.name;
796
+ return {
797
+ off: ["关闭", "Off"],
798
+ none: ["无", "None"],
799
+ minimal: ["最低", "Minimal"],
800
+ low: ["低", "Low"],
801
+ medium: ["中", "Medium"],
802
+ high: ["高", "High"],
803
+ xhigh: ["极高", "Extra high"],
804
+ max: ["最高", "Maximum"],
805
+ ultra: ["超高", "Ultra"]
806
+ }[effort.id]?.[locale?.toLowerCase().startsWith("zh") ? 0 : 1] ?? effort.name;
807
+ }
808
+ /** The short model label shown inside a provider group. */
809
+ function modelMenuShortName(choice) {
810
+ const sep = choice.label.indexOf(" / ");
811
+ return sep > 0 ? choice.label.slice(sep + 3) : choice.model;
812
+ }
813
+ /** Group catalog choices by provider and filter them by a case-insensitive query. */
814
+ function groupModelMenuChoices(choices, query) {
815
+ const needle = query.trim().toLowerCase();
816
+ const matched = needle ? choices.filter((choice) => choice.label.toLowerCase().includes(needle) || choice.model.toLowerCase().includes(needle) || choice.provider.toLowerCase().includes(needle)) : choices;
817
+ const groups = [];
818
+ for (const choice of matched) {
819
+ const sep = choice.label.indexOf(" / ");
820
+ const name = sep > 0 ? choice.label.slice(0, sep) : choice.provider;
821
+ const last = groups.at(-1);
822
+ if (last && last.provider === choice.provider) last.items.push(choice);
823
+ else groups.push({
824
+ provider: choice.provider,
825
+ name,
826
+ items: [choice]
827
+ });
828
+ }
829
+ return groups;
830
+ }
831
+ //#endregion
832
+ //#region ../dsh-model-route/src/catalog-ui.tsx
833
+ /**
834
+ * Latest refresh wins, including retries while a previous request is pending.
835
+ * Keep load stable (for example with useCallback); changing it invalidates the
836
+ * previous source and fetches again, so switching clients cannot publish stale data.
837
+ */
838
+ function useModelCatalog(load, options = {}) {
839
+ const enabled = options.enabled !== false;
840
+ const enabledRef = (0, react.useRef)(enabled);
841
+ enabledRef.current = enabled;
842
+ const live = (0, react.useRef)(false);
843
+ const source = (0, react.useRef)(load);
844
+ const revision = (0, react.useRef)(0);
845
+ const [state, setState] = (0, react.useState)(() => {
846
+ try {
847
+ return {
848
+ catalog: options.initialCatalog === void 0 ? void 0 : checkedModelCatalog(options.initialCatalog),
849
+ loading: enabled,
850
+ error: ""
851
+ };
852
+ } catch (error) {
853
+ return {
854
+ catalog: void 0,
855
+ loading: enabled,
856
+ error: modelCatalogErrorMessage(error)
857
+ };
858
+ }
859
+ });
860
+ const refresh = (0, react.useCallback)(async () => {
861
+ if (!live.current || !enabledRef.current) return;
862
+ const request = ++revision.current;
863
+ setState((previous) => ({
864
+ ...previous,
865
+ loading: true,
866
+ error: ""
867
+ }));
868
+ try {
869
+ const catalog = checkedModelCatalog(await load());
870
+ if (live.current && request === revision.current) setState({
871
+ catalog,
872
+ loading: false,
873
+ error: ""
874
+ });
875
+ } catch (error) {
876
+ if (live.current && request === revision.current) setState((previous) => ({
877
+ ...previous,
878
+ loading: false,
879
+ error: modelCatalogErrorMessage(error)
880
+ }));
881
+ }
882
+ }, [load, enabled]);
883
+ (0, react.useEffect)(() => {
884
+ live.current = true;
885
+ if (source.current !== load) {
886
+ source.current = load;
887
+ setState({
888
+ catalog: void 0,
889
+ loading: enabled,
890
+ error: ""
891
+ });
892
+ }
893
+ if (enabled) refresh();
894
+ else setState((previous) => ({
895
+ ...previous,
896
+ loading: false
897
+ }));
898
+ return () => {
899
+ live.current = false;
900
+ ++revision.current;
901
+ };
902
+ }, [enabled, refresh]);
903
+ (0, react.useEffect)(() => {
904
+ if (!enabled) return;
905
+ const reload = () => {
906
+ refresh();
907
+ };
908
+ if (typeof window !== "undefined") window.addEventListener("focus", reload);
909
+ let unsubscribe;
910
+ try {
911
+ unsubscribe = options.subscribe?.(reload);
912
+ } catch (error) {
913
+ setState((previous) => ({
914
+ ...previous,
915
+ error: modelCatalogErrorMessage(error)
916
+ }));
917
+ }
918
+ return () => {
919
+ if (typeof window !== "undefined") window.removeEventListener("focus", reload);
920
+ unsubscribe?.();
921
+ };
922
+ }, [
923
+ enabled,
924
+ refresh,
925
+ options.subscribe
926
+ ]);
927
+ const choices = (0, react.useMemo)(() => parseModelMenuChoices(state.catalog), [state.catalog]);
928
+ const failures = (0, react.useMemo)(() => state.catalog === void 0 ? [] : modelCatalogFailures(state.catalog), [state.catalog]);
929
+ return {
930
+ ...state,
931
+ choices,
932
+ failures,
933
+ refresh
934
+ };
935
+ }
936
+ /** Also accepts a mapped state from a caller that owns broader async orchestration. */
937
+ function ModelCatalogStatus({ state, locale }) {
938
+ const zh = locale?.toLowerCase().startsWith("zh") === true;
939
+ const copy = zh ? {
940
+ loading: "正在加载模型…",
941
+ error: "模型列表加载失败",
942
+ partial: "部分提供商的模型未能加载",
943
+ retry: "重试"
944
+ } : {
945
+ loading: "Loading models…",
946
+ error: "Unable to load models",
947
+ partial: "Some providers could not load their models",
948
+ retry: "Retry"
949
+ };
950
+ if (!state.loading && !state.error && state.failures.length === 0) return null;
951
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
952
+ className: "dsh-model-catalog-status",
953
+ lang: zh ? "zh" : "en",
954
+ children: [
955
+ state.loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
956
+ role: "status",
957
+ children: copy.loading
958
+ }) : null,
959
+ state.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
960
+ role: "alert",
961
+ children: [
962
+ copy.error,
963
+ ": ",
964
+ state.error
965
+ ]
966
+ }) : null,
967
+ state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
968
+ role: "status",
969
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: copy.partial }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { children: state.failures.map((failure, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
970
+ failure.name || failure.id,
971
+ ": ",
972
+ failure.message
973
+ ] }, `${failure.id}:${index}`)) })]
974
+ }) : null,
975
+ state.error || state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
976
+ type: "button",
977
+ size: "sm",
978
+ disabled: state.loading,
979
+ onClick: () => {
980
+ state.refresh();
981
+ },
982
+ children: copy.retry
983
+ }) : null
984
+ ]
985
+ });
986
+ }
987
+ //#endregion
988
+ //#region ../dsh-model-route/src/ui.tsx
989
+ /**
990
+ * ModelMenu: the standard editor of one model-route field — the visible half
991
+ * of the x-model-route contract. A feature plugin's settings page renders it
992
+ * for its own marked field; the model hub renders one per detected field.
993
+ *
994
+ * It is a plugin-side copy of the host's composer model picker
995
+ * (dsh-client-ui-model-selection's ModelSelect), rebuilt from the official
996
+ * primitives and the published bundle's behavior: one card that opens on the
997
+ * root pane (Model / Effort cells), drills into the provider-grouped,
998
+ * searchable model list or the effort list, and Escapes back before closing.
999
+ * Sticky group headings, subsequence ranking, highlight walking and focus
1000
+ * hand-back all match the host. SYNC POINT: when the host's ModelSelect or
1001
+ * ModelSelect.module.css changes, this copy must be re-transcribed; styles
1002
+ * below may only reference --dsw-* theme tokens, never literal colors.
1003
+ *
1004
+ * It statically imports the primitives, so only browser clients should load
1005
+ * this entry; host code and node tests use the core entry (`./index.ts`).
1006
+ */
1007
+ /** Unplaced portal card: hidden but laid out at a fixed origin so measurements are real. */
1008
+ const MEASURE_STYLE = {
1009
+ visibility: "hidden",
1010
+ left: 0,
1011
+ top: 0
1012
+ };
1013
+ function ModelMenu(props) {
1014
+ const language = props.locale?.toLowerCase().startsWith("zh") ? "zh" : "en";
1015
+ const copy = language === "zh" ? {
1016
+ model: "模型",
1017
+ effort: "思考强度",
1018
+ default: "默认",
1019
+ search: "搜索模型…",
1020
+ empty: "没有匹配的模型",
1021
+ clear: "清除搜索"
1022
+ } : {
1023
+ model: "Model",
1024
+ effort: "Reasoning",
1025
+ default: "Default",
1026
+ search: "Search models…",
1027
+ empty: "No matching models",
1028
+ clear: "Clear search"
1029
+ };
1030
+ 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;
1031
+ const rootRef = (0, react.useRef)(null);
1032
+ const openRef = (0, react.useRef)(open);
1033
+ openRef.current = open;
1034
+ const pendingOpenRef = (0, react.useRef)(false);
1035
+ const pendingRestoreFocus = (0, react.useRef)(false);
1036
+ const [requestedOpen, setRequestedOpen] = (0, react.useState)(false);
1037
+ const menuRef = (0, react.useRef)(null);
1038
+ const groupsRef = (0, react.useRef)(null);
1039
+ const searchRef = (0, react.useRef)(null);
1040
+ const itemRefs = (0, react.useRef)([]);
1041
+ const paneFocus = (0, react.useRef)(null);
1042
+ const [pane, setPane] = (0, react.useState)("root");
1043
+ const [query, setQuery] = (0, react.useState)("");
1044
+ const [highlightedIndex, setHighlightedIndex] = (0, react.useState)(null);
1045
+ const [menuPos, setMenuPos] = (0, react.useState)(null);
1046
+ const id = (0, react.useId)();
1047
+ const showSearch = choices.length > 4;
1048
+ const effortRows = (0, react.useMemo)(() => [{
1049
+ id: "",
1050
+ name: defaultEffortLabel
1051
+ }, ...efforts.map((effort) => ({
1052
+ ...effort,
1053
+ name: modelMenuEffortLabel(effort, language)
1054
+ }))], [
1055
+ defaultEffortLabel,
1056
+ efforts,
1057
+ language
1058
+ ]);
1059
+ const filteredGroups = (0, react.useMemo)(() => groupModelMenuChoices(choices, "").map((group) => ({
1060
+ ...group,
1061
+ items: (0, _deepseek_ai_dsh_client_ui_primitives.rankByName)(group.items.map((choice) => ({
1062
+ choice,
1063
+ name: modelMenuShortName(choice),
1064
+ label: choice.model
1065
+ })), showSearch ? query.trim() : "")
1066
+ })).filter((group) => group.items.length > 0), [
1067
+ choices,
1068
+ query,
1069
+ showSearch
1070
+ ]);
1071
+ const visibleModels = (0, react.useMemo)(() => filteredGroups.flatMap((group) => group.items.map((item) => item.choice)), [filteredGroups]);
1072
+ const currentVisibleIndex = visibleModels.findIndex((choice) => choice.provider === selected?.provider && choice.model === selected.model);
1073
+ const activeModelIndex = Math.min(highlightedIndex ?? Math.max(0, currentVisibleIndex), visibleModels.length - 1);
1074
+ const changeOpen = (next) => {
1075
+ if (openRef.current === next) return;
1076
+ openRef.current = next;
1077
+ onOpenChange(next);
1078
+ };
1079
+ const requestOpen = () => {
1080
+ if (open || pendingOpenRef.current) return;
1081
+ pendingOpenRef.current = true;
1082
+ pendingRestoreFocus.current = false;
1083
+ setRequestedOpen(true);
1084
+ };
1085
+ const close = (restoreFocus = false) => {
1086
+ const active = document.activeElement;
1087
+ pendingRestoreFocus.current = restoreFocus && (active === document.body || rootRef.current?.contains(active) === true || menuRef.current?.contains(active) === true);
1088
+ changeOpen(false);
1089
+ };
1090
+ const drill = (next) => {
1091
+ setQuery("");
1092
+ setHighlightedIndex(null);
1093
+ paneFocus.current = "drill";
1094
+ setPane(next);
1095
+ };
1096
+ /** Leave a drilled pane for the root one, handing the keyboard back to its cell. */
1097
+ const back = (from) => {
1098
+ paneFocus.current = from;
1099
+ setPane("root");
1100
+ };
1101
+ const moveFocus = (offset) => {
1102
+ const items = itemRefs.current.filter((item) => item !== null && !item.disabled);
1103
+ if (!items.length) return;
1104
+ const active = items.findIndex((item) => item === document.activeElement);
1105
+ items[active === -1 ? offset > 0 ? 0 : items.length - 1 : (active + offset + items.length) % items.length]?.focus();
1106
+ };
1107
+ (0, react.useLayoutEffect)(() => {
1108
+ if (open || !pendingRestoreFocus.current) return;
1109
+ const active = document.activeElement;
1110
+ if (active !== document.body && !rootRef.current?.contains(active)) {
1111
+ pendingRestoreFocus.current = false;
1112
+ return;
1113
+ }
1114
+ const trigger = rootRef.current?.querySelector("button");
1115
+ if (!trigger || trigger.disabled) return;
1116
+ trigger.focus();
1117
+ if (document.activeElement === trigger) pendingRestoreFocus.current = false;
1118
+ });
1119
+ (0, react.useEffect)(() => {
1120
+ if (open || !pendingRestoreFocus.current) return;
1121
+ const cancelOutside = (event) => {
1122
+ if (event.target instanceof Node && !rootRef.current?.contains(event.target)) pendingRestoreFocus.current = false;
1123
+ };
1124
+ document.addEventListener("focusin", cancelOutside);
1125
+ document.addEventListener("pointerdown", cancelOutside);
1126
+ return () => {
1127
+ document.removeEventListener("focusin", cancelOutside);
1128
+ document.removeEventListener("pointerdown", cancelOutside);
1129
+ };
1130
+ });
1131
+ (0, react.useEffect)(() => {
1132
+ if (!requestedOpen) return;
1133
+ pendingOpenRef.current = false;
1134
+ setRequestedOpen(false);
1135
+ if (!open) changeOpen(true);
1136
+ }, [requestedOpen, open]);
1137
+ (0, react.useEffect)(() => {
1138
+ if (!open) return;
1139
+ setPane("root");
1140
+ setQuery("");
1141
+ setHighlightedIndex(null);
1142
+ }, [open]);
1143
+ (0, react.useEffect)(() => {
1144
+ if (!open) return;
1145
+ const closeOutside = (event) => {
1146
+ const target = event.target;
1147
+ if (rootRef.current?.contains(target) || menuRef.current?.contains(target)) return;
1148
+ changeOpen(false);
1149
+ };
1150
+ document.addEventListener("mousedown", closeOutside);
1151
+ return () => document.removeEventListener("mousedown", closeOutside);
1152
+ }, [open, onOpenChange]);
1153
+ (0, react.useEffect)(() => {
1154
+ const intent = paneFocus.current;
1155
+ if (!open || menuPos === null || intent === null) return;
1156
+ if (intent !== "drill" && pane !== "root") return;
1157
+ paneFocus.current = null;
1158
+ if (intent === "drill") {
1159
+ if (pane === "model" && showSearch) {
1160
+ searchRef.current?.focus();
1161
+ return;
1162
+ }
1163
+ (menuRef.current?.querySelector("[role=\"menuitemradio\"][aria-checked=\"true\"]:not([disabled])") ?? itemRefs.current.find((item) => item !== null && !item.disabled))?.focus();
1164
+ return;
1165
+ }
1166
+ itemRefs.current[intent === "effort" ? 1 : 0]?.focus();
1167
+ }, [
1168
+ open,
1169
+ pane,
1170
+ showSearch,
1171
+ menuPos
1172
+ ]);
1173
+ (0, react.useEffect)(() => {
1174
+ const viewport = groupsRef.current;
1175
+ if (viewport === null) return;
1176
+ return (0, _deepseek_ai_dsh_client_ui_primitives.observeStickyMenuGroups)(viewport);
1177
+ }, [
1178
+ open,
1179
+ pane,
1180
+ filteredGroups
1181
+ ]);
1182
+ (0, react.useLayoutEffect)(() => {
1183
+ if (open && pane === "model" && activeModelIndex >= 0) itemRefs.current[leading.length + activeModelIndex]?.scrollIntoView({ block: "nearest" });
1184
+ }, [
1185
+ open,
1186
+ pane,
1187
+ activeModelIndex,
1188
+ visibleModels,
1189
+ leading.length
1190
+ ]);
1191
+ (0, react.useLayoutEffect)(() => {
1192
+ if (!open) {
1193
+ setMenuPos(null);
1194
+ return;
1195
+ }
1196
+ const place = () => {
1197
+ const rect = rootRef.current?.getBoundingClientRect();
1198
+ if (!rect) return;
1199
+ const margin = 12, lw = menuRef.current?.offsetWidth ?? 0, lh = menuRef.current?.offsetHeight ?? 0;
1200
+ let x = rect.right - lw, y = rect.top - 8 - lh;
1201
+ if (lw > 0) x = Math.min(Math.max(x, margin), window.innerWidth - lw - margin);
1202
+ if (lh > 0) y = Math.min(Math.max(y, margin), window.innerHeight - lh - margin);
1203
+ setMenuPos({
1204
+ left: x,
1205
+ top: y
1206
+ });
1207
+ };
1208
+ place();
1209
+ window.addEventListener("scroll", place, true);
1210
+ window.addEventListener("resize", place);
1211
+ return () => {
1212
+ window.removeEventListener("scroll", place, true);
1213
+ window.removeEventListener("resize", place);
1214
+ };
1215
+ }, [
1216
+ open,
1217
+ pane,
1218
+ choices,
1219
+ query
1220
+ ]);
1221
+ function onKeyDown(event) {
1222
+ if (event.defaultPrevented || event.nativeEvent.isComposing) return;
1223
+ if (!open && [
1224
+ "Enter",
1225
+ " ",
1226
+ "ArrowDown",
1227
+ "ArrowUp"
1228
+ ].includes(event.key)) {
1229
+ const button = event.target instanceof Element ? event.target.closest("button") : null;
1230
+ if (!(button instanceof HTMLButtonElement) || button.disabled || !rootRef.current?.contains(button)) return;
1231
+ event.preventDefault();
1232
+ paneFocus.current = "model";
1233
+ requestOpen();
1234
+ return;
1235
+ }
1236
+ if (event.key === "Escape" && open) {
1237
+ event.preventDefault();
1238
+ if (pane !== "root") back(pane);
1239
+ else close(true);
1240
+ return;
1241
+ }
1242
+ if (!open) return;
1243
+ if (pane === "model" && showSearch && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
1244
+ event.preventDefault();
1245
+ if (visibleModels.length > 0) {
1246
+ setHighlightedIndex((activeModelIndex + (event.key === "ArrowDown" ? 1 : -1) + visibleModels.length) % visibleModels.length);
1247
+ searchRef.current?.focus();
1248
+ }
1249
+ return;
1250
+ }
1251
+ if (pane === "model" && showSearch && event.target instanceof HTMLInputElement && (event.key === "Enter" || event.key === "Tab" && !event.shiftKey)) {
1252
+ if (event.key === "Tab" && visibleModels.length === 0) return;
1253
+ event.preventDefault();
1254
+ const highlighted = visibleModels[activeModelIndex];
1255
+ if (highlighted) {
1256
+ onPick({
1257
+ provider: highlighted.provider,
1258
+ model: highlighted.model
1259
+ });
1260
+ close(true);
1261
+ }
1262
+ return;
1263
+ }
1264
+ if (event.key === "Tab") {
1265
+ if (event.shiftKey) {
1266
+ event.preventDefault();
1267
+ if (pane !== "root") back(pane);
1268
+ else close(true);
1269
+ return;
1270
+ }
1271
+ const focused = document.activeElement;
1272
+ const rows = itemRefs.current.filter((item) => item !== null);
1273
+ if (focused instanceof HTMLButtonElement && rows.includes(focused)) {
1274
+ event.preventDefault();
1275
+ focused.click();
1276
+ return;
1277
+ }
1278
+ if (pane === "model" && showSearch) {
1279
+ event.preventDefault();
1280
+ setHighlightedIndex(null);
1281
+ searchRef.current?.focus();
1282
+ }
1283
+ return;
1284
+ }
1285
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
1286
+ event.preventDefault();
1287
+ moveFocus(event.key === "ArrowDown" ? 1 : -1);
1288
+ }
1289
+ }
1290
+ function onBlur(event) {
1291
+ if (event.relatedTarget instanceof Node && (rootRef.current?.contains(event.relatedTarget) || menuRef.current?.contains(event.relatedTarget))) return;
1292
+ if (open) changeOpen(false);
1293
+ }
1294
+ itemRefs.current = [];
1295
+ let itemIndex = 0;
1296
+ let modelIndex = 0;
1297
+ const itemRef = () => {
1298
+ const at = itemIndex++;
1299
+ return (node) => {
1300
+ itemRefs.current[at] = node;
1301
+ };
1302
+ };
1303
+ const menu = open && typeof document !== "undefined" ? (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.MenuSurface, {
1304
+ ref: menuRef,
1305
+ id: `${id}-menu`,
1306
+ className: `dsh-model-menu-menu${listClassName ? ` ${listClassName}` : ""}`,
1307
+ style: menuPos ?? MEASURE_STYLE,
1308
+ role: pane === "model" ? "group" : "menu",
1309
+ "aria-label": modelLabel,
1310
+ lang: language,
1311
+ children: [
1312
+ pane === "root" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1313
+ ref: itemRef(),
1314
+ type: "button",
1315
+ role: "menuitem",
1316
+ className: "dsh-model-menu-cell",
1317
+ onClick: () => drill("model"),
1318
+ children: [
1319
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1320
+ className: "dsh-model-menu-cellLabel",
1321
+ children: modelLabel
1322
+ }),
1323
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1324
+ className: "dsh-model-menu-cellValue",
1325
+ children: modelValue
1326
+ }),
1327
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, { className: "dsh-model-menu-cellChevron" })
1328
+ ]
1329
+ }), efforts.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1330
+ ref: itemRef(),
1331
+ type: "button",
1332
+ role: "menuitem",
1333
+ className: "dsh-model-menu-cell",
1334
+ onClick: () => drill("effort"),
1335
+ children: [
1336
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1337
+ className: "dsh-model-menu-cellLabel",
1338
+ children: effortLabel
1339
+ }),
1340
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1341
+ className: "dsh-model-menu-cellValue",
1342
+ children: effortValue ?? effortRows.find((row) => row.id === (selectedEffort ?? ""))?.name ?? defaultEffortLabel
1343
+ }),
1344
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, { className: "dsh-model-menu-cellChevron" })
1345
+ ]
1346
+ }) : null] }) : null,
1347
+ pane === "model" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1348
+ showSearch ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1349
+ className: "dsh-model-menu-searchRow",
1350
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
1351
+ ref: searchRef,
1352
+ className: `dsh-model-menu-search${query !== "" ? " dsh-model-menu-searchWithQuery" : ""}`,
1353
+ type: "text",
1354
+ role: "searchbox",
1355
+ "aria-label": searchPlaceholder,
1356
+ "aria-controls": `${id}-models`,
1357
+ "aria-activedescendant": activeModelIndex < 0 ? void 0 : `${id}-model-${activeModelIndex}`,
1358
+ placeholder: searchPlaceholder,
1359
+ value: query,
1360
+ onChange: (event) => {
1361
+ setQuery(event.target.value);
1362
+ setHighlightedIndex(0);
1363
+ }
1364
+ }), query !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1365
+ type: "button",
1366
+ className: "dsh-model-menu-searchClear",
1367
+ "aria-label": clearSearchLabel,
1368
+ onClick: () => {
1369
+ setQuery("");
1370
+ setHighlightedIndex(null);
1371
+ searchRef.current?.focus();
1372
+ },
1373
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseFillRegular, {})
1374
+ }) : null]
1375
+ }) : null,
1376
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1377
+ ref: groupsRef,
1378
+ id: `${id}-models`,
1379
+ className: "dsh-model-menu-groups scrollable",
1380
+ role: "menu",
1381
+ "aria-label": modelLabel,
1382
+ hidden: filteredGroups.length === 0,
1383
+ children: [leading.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1384
+ className: "dsh-model-menu-leading",
1385
+ children: leading.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1386
+ ref: itemRef(),
1387
+ type: "button",
1388
+ role: "menuitemradio",
1389
+ "aria-checked": item.selected === true,
1390
+ className: `dsh-model-menu-option${item.selected === true ? " dsh-model-menu-selected" : ""}`,
1391
+ onClick: () => {
1392
+ onPickLeading?.(item.id);
1393
+ close(true);
1394
+ },
1395
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1396
+ className: "dsh-model-menu-optionCopy",
1397
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1398
+ className: "dsh-model-menu-modelName",
1399
+ children: item.label
1400
+ })
1401
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1402
+ className: "dsh-model-menu-check",
1403
+ children: item.selected === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, {}) : null
1404
+ })]
1405
+ }, item.id))
1406
+ }) : null, filteredGroups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuGroup, {
1407
+ label: group.name,
1408
+ children: group.items.map((item) => {
1409
+ const index = modelIndex++;
1410
+ const choice = item.choice;
1411
+ const checked = selected?.provider === choice.provider && selected.model === choice.model;
1412
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1413
+ ref: itemRef(),
1414
+ type: "button",
1415
+ role: "menuitemradio",
1416
+ "aria-checked": checked,
1417
+ id: `${id}-model-${index}`,
1418
+ tabIndex: showSearch ? -1 : 0,
1419
+ onFocus: () => setHighlightedIndex(index),
1420
+ "data-highlighted": index === activeModelIndex ? "" : void 0,
1421
+ className: `dsh-model-menu-option dsh-model-menu-modelOption${checked ? " dsh-model-menu-selected" : ""}${index === activeModelIndex ? " dsh-model-menu-optionActive" : ""}`,
1422
+ onMouseMove: index === activeModelIndex ? void 0 : () => {
1423
+ if (showSearch) setHighlightedIndex(index);
1424
+ else itemRefs.current[leading.length + index]?.focus();
1425
+ },
1426
+ title: choice.label,
1427
+ onClick: () => {
1428
+ onPick({
1429
+ provider: choice.provider,
1430
+ model: choice.model
1431
+ });
1432
+ close(true);
1433
+ },
1434
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1435
+ className: "dsh-model-menu-optionCopy",
1436
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1437
+ className: "dsh-model-menu-modelName",
1438
+ children: modelMenuShortName(choice)
1439
+ })
1440
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1441
+ className: "dsh-model-menu-check",
1442
+ children: checked ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, {}) : null
1443
+ })]
1444
+ }, `${choice.provider}/${choice.model}`);
1445
+ })
1446
+ }, group.provider))]
1447
+ }),
1448
+ filteredGroups.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1449
+ className: "dsh-model-menu-empty",
1450
+ role: "status",
1451
+ children: emptyLabel
1452
+ }) : null
1453
+ ] }) : null,
1454
+ pane === "effort" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: effortRows.map((row) => {
1455
+ const checked = (selectedEffort ?? "") === row.id;
1456
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1457
+ ref: itemRef(),
1458
+ type: "button",
1459
+ role: "menuitemradio",
1460
+ "aria-checked": checked,
1461
+ className: `dsh-model-menu-option${checked ? " dsh-model-menu-selected" : ""}`,
1462
+ onClick: () => {
1463
+ onPickEffort?.(row.id || void 0);
1464
+ close(true);
1465
+ },
1466
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1467
+ className: "dsh-model-menu-optionCopy",
1468
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1469
+ className: "dsh-model-menu-modelName",
1470
+ children: row.name
1471
+ })
1472
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1473
+ className: "dsh-model-menu-check",
1474
+ children: checked ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, {}) : null
1475
+ })]
1476
+ }, row.id || "__default");
1477
+ }) }) : null
1478
+ ]
1479
+ }), document.body) : null;
1480
+ const trigger = (0, react.isValidElement)(anchor) ? (0, react.cloneElement)(anchor, {
1481
+ "aria-haspopup": "menu",
1482
+ "aria-expanded": open,
1483
+ "aria-controls": open ? `${id}-menu` : void 0
1484
+ }) : anchor;
1485
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1486
+ ref: rootRef,
1487
+ className: `dsh-model-menu-root${className ? ` ${className}` : ""}`,
1488
+ onKeyDown,
1489
+ onBlur,
1490
+ onClick: (event) => {
1491
+ if (event.defaultPrevented) return;
1492
+ const button = event.target instanceof Element ? event.target.closest("button") : null;
1493
+ if (!(button instanceof HTMLButtonElement) || button.disabled || !rootRef.current?.contains(button)) return;
1494
+ requestOpen();
1495
+ },
1496
+ onMouseDown: (event) => {
1497
+ if (event.target instanceof Element && menuRef.current?.contains(event.target) && event.target.closest("button") !== null) event.preventDefault();
1498
+ },
1499
+ children: [trigger, menu]
1500
+ });
1501
+ }
1502
+ /**
1503
+ * ModelMenu styles, transcribed from the official ModelSelect.module.css (the
1504
+ * published dsh-client-ui-model-selection bundle): card capped at
1505
+ * min(360px, 100vh - 96px), 34px option cells at 13/18 with 5px 7px padding,
1506
+ * 34px root cells at 13/20, a borderless 12px search row with a clear button,
1507
+ * sticky group headings (MenuGroup's own CSS), and the l1-stroke prominent
1508
+ * elevation at portal z-index 1100. The trigger mirrors the composer's 28px
1509
+ * model pill, chevron rotating open. MenuSurface paints fill and backdrop.
1510
+ * Only --dsw-* theme tokens may appear here — a literal color is a bug.
1511
+ */
1512
+ const modelMenuCss = `
1513
+ .dsh-model-menu-root { min-width: 0; position: relative; display: inline-flex; }
1514
+ .dsh-model-menu-trigger {
1515
+ border-radius: var(--dsw-radius-sm);
1516
+ min-width: 0;
1517
+ max-width: min(360px, 45cqw);
1518
+ height: 28px;
1519
+ color: var(--dsw-alias-label-secondary);
1520
+ cursor: pointer;
1521
+ background: none;
1522
+ border: none;
1523
+ outline: none;
1524
+ align-items: center;
1525
+ gap: 4px;
1526
+ padding: 0 4px 0 8px;
1527
+ font-size: 13px;
1528
+ font-weight: 400;
1529
+ line-height: 20px;
1530
+ display: flex;
1531
+ }
1532
+ .dsh-model-menu-trigger:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
1533
+ .dsh-model-menu-trigger:focus-visible:not([data-selection-focus]) { box-shadow: 0 0 0 2px var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); }
1534
+ .dsh-model-menu-trigger:disabled { color: var(--dsw-alias-label-dimmed); cursor: default; }
1535
+ .dsh-model-menu-triggerLabel { text-overflow: ellipsis; white-space: nowrap; min-width: 0; overflow: hidden; }
1536
+ .dsh-model-menu-triggerEffort { text-overflow: ellipsis; white-space: nowrap; min-width: 0; color: var(--dsw-alias-label-caption); flex-shrink: 1000; overflow: hidden; }
1537
+ .dsh-model-menu-chevron { color: var(--dsw-alias-label-caption); flex: none; transition: transform 0.12s; }
1538
+ .dsh-model-menu-chevronOpen { transform: rotate(180deg); }
1539
+ .dsh-model-menu-menu {
1540
+ z-index: 1100;
1541
+ --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
1542
+ width: max-content;
1543
+ min-width: min(240px, 100vw - 32px);
1544
+ max-width: min(420px, 100vw - 32px);
1545
+ max-height: min(360px, 100vh - 96px);
1546
+ box-shadow: var(--dsw-elevation-prominent);
1547
+ color: var(--dsw-alias-label-primary);
1548
+ --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
1549
+ --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
1550
+ border: 0;
1551
+ flex-direction: column;
1552
+ padding: 4px;
1553
+ display: flex;
1554
+ position: fixed;
1555
+ overflow: hidden;
1556
+ }
1557
+ .dsh-model-menu-status, .dsh-model-menu-empty { color: var(--dsw-alias-label-tertiary); padding: 8px; font-size: 12px; line-height: 18px; }
1558
+ .dsh-model-menu-searchRow { flex-shrink: 0; margin: 2px 0 3px; position: relative; }
1559
+ .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; }
1560
+ .dsh-model-menu-searchRow .dsh-model-menu-search:focus-within { border-color: transparent; }
1561
+ .dsh-model-menu-searchWithQuery { padding-right: 34px; }
1562
+ .dsh-model-menu-searchRow .dsh-model-menu-search input { padding: 0; font-size: 12px; line-height: normal; }
1563
+ .dsh-model-menu-searchRow .dsh-model-menu-search input::placeholder { color: var(--dsw-alias-label-caption); }
1564
+ .dsh-model-menu-searchClear {
1565
+ width: 24px;
1566
+ height: 24px;
1567
+ color: var(--dsw-alias-label-secondary);
1568
+ cursor: pointer;
1569
+ background: none;
1570
+ border: none;
1571
+ border-radius: 50%;
1572
+ justify-content: center;
1573
+ align-items: center;
1574
+ padding: 0;
1575
+ display: inline-flex;
1576
+ position: absolute;
1577
+ top: 50%;
1578
+ right: 4px;
1579
+ transform: translateY(-50%);
1580
+ }
1581
+ .dsh-model-menu-searchClear:hover, .dsh-model-menu-searchClear:focus-visible { background: var(--dsw-alias-interactive-bg-hover); outline: none; }
1582
+ .dsh-model-menu-groups { min-height: 0; overflow-y: auto; }
1583
+ .dsh-model-menu-leading { border-bottom: 0.5px solid var(--dsw-alias-border-l2); margin-bottom: 3px; padding-bottom: 3px; }
1584
+ .dsh-model-menu-option {
1585
+ box-sizing: border-box;
1586
+ border-radius: var(--dsw-radius-md);
1587
+ width: auto;
1588
+ min-width: 100%;
1589
+ min-height: 34px;
1590
+ color: inherit;
1591
+ text-align: left;
1592
+ cursor: pointer;
1593
+ background: none;
1594
+ border: none;
1595
+ outline: none;
1596
+ align-items: center;
1597
+ gap: 6px;
1598
+ padding: 5px 7px;
1599
+ display: flex;
1600
+ }
1601
+ .dsh-model-menu-option:not(.dsh-model-menu-modelOption):hover:not(:disabled),
1602
+ .dsh-model-menu-option:focus-visible,
1603
+ .dsh-model-menu-optionActive:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
1604
+ .dsh-model-menu-modelOption:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
1605
+ .dsh-model-menu-selected { background: none; }
1606
+ .dsh-model-menu-option:disabled { color: var(--dsw-alias-label-dimmed); cursor: default; }
1607
+ .dsh-model-menu-optionCopy { flex-direction: column; flex: 1; min-width: 0; display: flex; }
1608
+ .dsh-model-menu-modelName { color: inherit; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; font-weight: 400; line-height: 18px; overflow: hidden; }
1609
+ .dsh-model-menu-check { color: var(--dsw-alias-label-primary); flex: 0 0 14px; place-items: center; display: grid; }
1610
+ .dsh-model-menu-check svg { width: 14px; height: 14px; }
1611
+ .dsh-model-menu-cell {
1612
+ box-sizing: border-box;
1613
+ border-radius: var(--dsw-radius-md);
1614
+ width: auto;
1615
+ min-width: 100%;
1616
+ height: 34px;
1617
+ color: var(--dsw-alias-label-primary);
1618
+ cursor: pointer;
1619
+ text-align: left;
1620
+ background: none;
1621
+ border: none;
1622
+ outline: none;
1623
+ align-items: center;
1624
+ gap: 6px;
1625
+ padding: 0 8px;
1626
+ font-size: 13px;
1627
+ line-height: 20px;
1628
+ display: flex;
1629
+ }
1630
+ .dsh-model-menu-cell:hover, .dsh-model-menu-cell:focus-visible { background: var(--dsw-alias-interactive-bg-hover); }
1631
+ .dsh-model-menu-cellLabel { white-space: nowrap; flex: none; }
1632
+ .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; }
1633
+ .dsh-model-menu-cellChevron { width: 12px; height: 12px; color: var(--dsw-alias-menu-icon); flex: none; }
1634
+ `;
1635
+ //#endregion
653
1636
  //#region src/contracts.ts
654
1637
  const RPC_CHANNEL = "/dsh-current-title";
655
1638
  const TITLE_CLIENT_SERVICE = "dshCurrentTitleClient";
@@ -658,6 +1641,8 @@ const TITLE_CLIENT_SERVICE = "dshCurrentTitleClient";
658
1641
  const titleCopy = {
659
1642
  zh: {
660
1643
  label: "自动标题",
1644
+ session: "当前会话",
1645
+ noSession: "没有打开的会话。打开一个会话后,可以在这里查看并重新生成标题。",
661
1646
  pinned: "已手动命名",
662
1647
  generating: "正在生成标题…",
663
1648
  regenerate: "重新生成",
@@ -677,10 +1662,13 @@ const titleCopy = {
677
1662
  retry: "重试",
678
1663
  externalChange: "设置已在别处修改。提示词草稿已保留,请重新加载后确认。",
679
1664
  syncUnavailable: "自动刷新不可用,请手动重新加载设置。",
680
- reload: "重新加载(丢弃草稿)"
1665
+ reload: "重新加载(丢弃草稿)",
1666
+ defaults: "默认设置"
681
1667
  },
682
1668
  en: {
683
1669
  label: "Auto Title",
1670
+ session: "Current session",
1671
+ noSession: "No session is open. Open one to see its title and regenerate it here.",
684
1672
  pinned: "Renamed manually",
685
1673
  generating: "Generating title…",
686
1674
  regenerate: "Regenerate",
@@ -700,7 +1688,8 @@ const titleCopy = {
700
1688
  retry: "Retry",
701
1689
  externalChange: "Settings changed elsewhere. Prompt draft preserved; reload and review.",
702
1690
  syncUnavailable: "Automatic refresh unavailable; reload settings manually.",
703
- reload: "Reload (discard draft)"
1691
+ reload: "Reload (discard draft)",
1692
+ defaults: "Default settings"
704
1693
  }
705
1694
  };
706
1695
  /** Only plugin-owned defaults are translated; model output and remote diagnostics stay intact. */
@@ -847,44 +1836,12 @@ const name = "dsh-current-title-client";
847
1836
  const inject = [
848
1837
  "slots",
849
1838
  "connection",
850
- "remote",
851
- "remote.session",
1839
+ ...MODEL_CATALOG_INJECT,
852
1840
  "sessions",
853
1841
  "locale",
854
1842
  "uiWorkspace",
855
1843
  "uiSession"
856
1844
  ];
857
- function catalogChoices(value, selected) {
858
- const envelope = value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
859
- const root = (envelope?.ok === true && envelope.value && typeof envelope.value === "object" && !Array.isArray(envelope.value) ? envelope.value : void 0) ?? envelope ?? {};
860
- const groups = Array.isArray(root.groups) ? root.groups : [];
861
- const choices = [];
862
- for (const group of groups) {
863
- if (!group || typeof group !== "object") continue;
864
- const row = group;
865
- const provider = typeof row.id === "string" ? row.id : "";
866
- const providerName = typeof row.name === "string" && row.name ? row.name : provider;
867
- if (!provider || !Array.isArray(row.models)) continue;
868
- for (const model of row.models) {
869
- if (!model || typeof model !== "object") continue;
870
- const item = model;
871
- const id = typeof item.id === "string" ? item.id : "";
872
- if (!id) continue;
873
- const name = typeof item.name === "string" && item.name ? item.name : id;
874
- choices.push({
875
- provider,
876
- model: id,
877
- label: `${providerName} / ${name}`
878
- });
879
- }
880
- }
881
- if (selected.provider && selected.model && !choices.some((item) => item.provider === selected.provider && item.model === selected.model)) choices.push({
882
- provider: selected.provider,
883
- model: selected.model,
884
- label: `${selected.provider} / ${selected.model}`
885
- });
886
- return choices;
887
- }
888
1845
  function TitleSettings(props) {
889
1846
  const seat = useNativeSeat(props.client);
890
1847
  const sessionId = seat.sessionId;
@@ -895,7 +1852,10 @@ function TitleSettings(props) {
895
1852
  const [note, setNote] = (0, react.useState)("");
896
1853
  const [error, setError] = (0, react.useState)("");
897
1854
  const [prompt, setPrompt] = (0, react.useState)(void 0);
898
- const [choices, setChoices] = (0, react.useState)([]);
1855
+ const catalogState = useModelCatalog((0, react.useCallback)(() => readModelCatalog(props.client), [props.client]), { subscribe: (0, react.useCallback)((change) => props.client.connection.generation?.subscribe(change), [props.client]) });
1856
+ const { refresh: refreshCatalog } = catalogState;
1857
+ const choices = parseModelMenuChoices(catalogState.catalog, status?.settings?.model);
1858
+ const [menuOpen, setMenuOpen] = (0, react.useState)(false);
899
1859
  const [externalChange, setExternalChange] = (0, react.useState)(false);
900
1860
  const [syncError, setSyncError] = (0, react.useState)("");
901
1861
  const [loadingSettings, setLoadingSettings] = (0, react.useState)(true);
@@ -938,26 +1898,6 @@ function TitleSettings(props) {
938
1898
  work,
939
1899
  readGate
940
1900
  ]);
941
- (0, react.useEffect)(() => {
942
- const catalog = props.client.remote?.session?.modelCatalog;
943
- if (!catalog) return void 0;
944
- let live = true;
945
- catalog().then((value) => {
946
- if (live) setChoices(catalogChoices(value, status?.settings.model ?? {
947
- provider: "",
948
- model: ""
949
- }));
950
- }).catch(() => {
951
- if (live) setChoices([]);
952
- });
953
- return () => {
954
- live = false;
955
- };
956
- }, [
957
- props.client,
958
- status?.settings.model.provider,
959
- status?.settings.model.model
960
- ]);
961
1901
  (0, react.useEffect)(() => {
962
1902
  const generation = work.beginLoad();
963
1903
  reading.current = true;
@@ -1059,6 +1999,7 @@ function TitleSettings(props) {
1059
1999
  const stop = props.subscribeSettings?.(() => {
1060
2000
  if (!live) return;
1061
2001
  setSyncError("");
2002
+ refreshCatalog();
1062
2003
  if (dirty.current && !busyRef.current) {
1063
2004
  external.current = true;
1064
2005
  setExternalChange(true);
@@ -1071,17 +2012,25 @@ function TitleSettings(props) {
1071
2012
  live = false;
1072
2013
  stop?.();
1073
2014
  };
1074
- }, [props.subscribeSettings, props.client]);
1075
- const savedPrompt = status?.settings.prompt ?? "";
1076
- const savedModel = status?.settings.model ?? {
2015
+ }, [
2016
+ props.subscribeSettings,
2017
+ props.client,
2018
+ refreshCatalog
2019
+ ]);
2020
+ const savedPrompt = status?.settings?.prompt ?? "";
2021
+ const savedModel = status?.settings?.model ?? {
1077
2022
  provider: "",
1078
2023
  model: ""
1079
2024
  };
1080
- const selectableChoices = choices.some((choice) => choice.provider === savedModel.provider && choice.model === savedModel.model) ? choices : [...choices, ...catalogChoices({}, savedModel)];
1081
- const modelKey = savedModel.provider && savedModel.model ? `${savedModel.provider}\u001f${savedModel.model}` : "";
2025
+ const follow = !savedModel.provider || !savedModel.model;
2026
+ const selectedModel = follow ? void 0 : choices.find((choice) => choice.provider === savedModel.provider && choice.model === savedModel.model);
2027
+ const efforts = follow ? [] : modelMenuEffortOptions(selectedModel, savedModel.reasoningEffort);
2028
+ const currentLabel = follow ? text.modelDefault : selectedModel?.label ?? `${savedModel.provider} / ${savedModel.model}`;
2029
+ const selectedEffort = efforts.find((item) => item.id === (savedModel.reasoningEffort || ""));
2030
+ const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, seat.locale) : void 0;
1082
2031
  const title = status?.session?.title;
1083
2032
  const pinned = status?.session?.pinned === true;
1084
- const weOwn = status?.support.weOwn === true;
2033
+ const weOwn = status?.support?.weOwn === true;
1085
2034
  const canRegenerate = Boolean(sessionId) && weOwn && !busy && !loadingSettings;
1086
2035
  function saveSettings(patch) {
1087
2036
  if (!status || busyRef.current || reading.current || external.current) return;
@@ -1115,221 +2064,266 @@ function TitleSettings(props) {
1115
2064
  className: "dsh-ui-hint",
1116
2065
  children: text.loading
1117
2066
  }) : null,
1118
- title ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1119
- translate: "no",
1120
- className: "dsh-ui-title dsh-ui-wrap",
1121
- children: title
1122
- }) : null,
1123
- pinned ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1124
- className: "dsh-ui-meta",
1125
- children: text.pinned
1126
- }) : null,
1127
- status?.session?.generating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1128
- role: "status",
1129
- className: "dsh-ui-hint",
1130
- children: text.generating
1131
- }) : null,
1132
- status && !weOwn ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1133
- role: "status",
1134
- className: "dsh-ui-hint",
1135
- children: [text.inactive, status.support.limitation ? ` ${titleMessage(status.support.limitation, seat.locale)}` : ""]
1136
- }) : null,
1137
- error ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1138
- role: "alert",
1139
- className: "dsh-ui-error dsh-ui-wrap",
1140
- children: titleMessage(error, seat.locale)
1141
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1142
- variant: "outline",
1143
- size: "sm",
1144
- disabled: busy,
1145
- onClick: () => {
1146
- readGate.reset();
1147
- refreshStatus();
1148
- },
1149
- children: text.retry
1150
- })] }) : null,
1151
- note ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1152
- role: "status",
1153
- className: "dsh-ui-notice",
1154
- children: titleMessage(note, seat.locale)
1155
- }) : null,
1156
- externalChange ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1157
- role: "alert",
1158
- className: "dsh-ui-error",
1159
- children: text.externalChange
1160
- }) : null,
1161
- syncError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1162
- role: "status",
1163
- className: "dsh-ui-hint",
1164
- children: text.syncUnavailable
1165
- }) : null,
1166
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1167
- className: "dsh-ui-actions",
1168
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1169
- variant: "ghost",
1170
- size: "sm",
1171
- disabled: busy || loadingSettings,
1172
- onClick: () => {
1173
- setPrompt(void 0);
1174
- dirty.current = false;
1175
- external.current = false;
1176
- setExternalChange(false);
1177
- readGate.reset();
1178
- syncRef.current();
1179
- },
1180
- children: text.reload
1181
- }), sessionId ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1182
- variant: "outline",
1183
- size: "md",
1184
- disabled: !canRegenerate,
1185
- onClick: () => {
1186
- if (!sessionId) return;
1187
- readGate.reset();
1188
- const token = work.beginRequest();
1189
- setError("");
1190
- setNote("");
1191
- runTitleRegenerateFlow({
1192
- call,
1193
- sessionId,
1194
- isCurrent: () => work.isRequest(token),
1195
- failedMessage: messages.failed,
1196
- onBusy: changeBusy,
1197
- onStatus: setStatus,
1198
- onError: setError,
1199
- onMarker: commitMarker
1200
- });
1201
- },
1202
- children: text.regenerate
1203
- }) : null]
1204
- }),
1205
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1206
- className: "dsh-ui-field",
2067
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
2068
+ className: "dsh-ui-card",
2069
+ "data-testid": "current-title-session",
1207
2070
  children: [
1208
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1209
- className: "dsh-ui-label",
1210
- children: text.prompt
2071
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2072
+ className: "dsh-ui-heading",
2073
+ children: text.session
1211
2074
  }),
1212
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1213
- className: "ct-prompt",
1214
- value: status ? promptFieldValue(prompt, savedPrompt) : "",
1215
- maxLength: 4e3,
1216
- rows: 5,
1217
- disabled: busy || loadingSettings || !status,
1218
- onChange: (event) => {
1219
- dirty.current = true;
1220
- setPrompt(event.target.value);
1221
- setNote("");
2075
+ title ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2076
+ translate: "no",
2077
+ className: "dsh-ui-title dsh-ui-wrap",
2078
+ children: title
2079
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2080
+ className: "dsh-ui-hint",
2081
+ children: text.noSession
2082
+ }),
2083
+ pinned ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2084
+ className: "dsh-ui-meta",
2085
+ children: text.pinned
2086
+ }) : null,
2087
+ status?.session?.generating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2088
+ role: "status",
2089
+ className: "dsh-ui-hint",
2090
+ children: text.generating
2091
+ }) : null,
2092
+ status && !weOwn ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
2093
+ role: "status",
2094
+ className: "dsh-ui-hint",
2095
+ children: [text.inactive, status.support?.limitation ? ` ${titleMessage(status.support.limitation, seat.locale)}` : ""]
2096
+ }) : null,
2097
+ error ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2098
+ role: "alert",
2099
+ className: "dsh-ui-error dsh-ui-wrap",
2100
+ children: titleMessage(error, seat.locale)
2101
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2102
+ variant: "outline",
2103
+ size: "sm",
2104
+ disabled: busy,
2105
+ onClick: () => {
2106
+ readGate.reset();
2107
+ refreshStatus();
1222
2108
  },
1223
- onBlur: () => {
1224
- if (!status || busyRef.current || reading.current || external.current) return;
1225
- const next = promptToSave(prompt, savedPrompt);
1226
- if (next === void 0) {
1227
- dirty.current = false;
2109
+ children: text.retry
2110
+ })] }) : null,
2111
+ note ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2112
+ role: "status",
2113
+ className: "dsh-ui-notice",
2114
+ children: titleMessage(note, seat.locale)
2115
+ }) : null,
2116
+ externalChange ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2117
+ role: "alert",
2118
+ className: "dsh-ui-error",
2119
+ children: text.externalChange
2120
+ }) : null,
2121
+ syncError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2122
+ role: "status",
2123
+ className: "dsh-ui-hint",
2124
+ children: text.syncUnavailable
2125
+ }) : null,
2126
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2127
+ className: "dsh-ui-actions",
2128
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2129
+ variant: "ghost",
2130
+ size: "sm",
2131
+ disabled: busy || loadingSettings,
2132
+ onClick: () => {
1228
2133
  setPrompt(void 0);
1229
- return;
1230
- }
1231
- readGate.reset();
1232
- const token = work.beginRequest();
1233
- setError("");
1234
- runTitleSettingsSave({
1235
- call,
1236
- prompt: next,
1237
- expectedRevision: status.settings.revision,
1238
- isCurrent: () => work.isRequest(token),
1239
- savedMessage: messages.saved,
1240
- failedMessage: messages.failed,
1241
- onBusy: changeBusy,
1242
- onSettings: (settings) => {
1243
- setStatus((current) => current ? {
1244
- ...current,
1245
- settings
1246
- } : current);
1247
- dirty.current = false;
1248
- setPrompt(void 0);
2134
+ dirty.current = false;
2135
+ external.current = false;
2136
+ setExternalChange(false);
2137
+ readGate.reset();
2138
+ syncRef.current();
2139
+ },
2140
+ children: text.reload
2141
+ }), sessionId ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2142
+ variant: "outline",
2143
+ size: "md",
2144
+ disabled: !canRegenerate,
2145
+ onClick: () => {
2146
+ if (!sessionId) return;
2147
+ readGate.reset();
2148
+ const token = work.beginRequest();
2149
+ setError("");
2150
+ setNote("");
2151
+ runTitleRegenerateFlow({
2152
+ call,
2153
+ sessionId,
2154
+ isCurrent: () => work.isRequest(token),
2155
+ failedMessage: messages.failed,
2156
+ onBusy: changeBusy,
2157
+ onStatus: setStatus,
2158
+ onError: setError,
2159
+ onMarker: commitMarker
2160
+ });
2161
+ },
2162
+ children: text.regenerate
2163
+ }) : null]
2164
+ })
2165
+ ]
2166
+ }),
2167
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2168
+ className: "dsh-ui-section",
2169
+ "data-testid": "current-title-defaults",
2170
+ children: [
2171
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2172
+ className: "dsh-ui-heading",
2173
+ children: text.defaults
2174
+ }),
2175
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2176
+ className: "dsh-ui-field",
2177
+ children: [
2178
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2179
+ className: "dsh-ui-label",
2180
+ children: text.prompt
2181
+ }),
2182
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
2183
+ className: "dsh-ui-textarea ct-prompt",
2184
+ value: status ? promptFieldValue(prompt, savedPrompt) : "",
2185
+ maxLength: 4e3,
2186
+ rows: 5,
2187
+ disabled: busy || loadingSettings || !status,
2188
+ onChange: (event) => {
2189
+ dirty.current = true;
2190
+ setPrompt(event.target.value);
2191
+ setNote("");
1249
2192
  },
1250
- onNote: setNote,
1251
- onError: setError
1252
- });
1253
- }
2193
+ onBlur: () => {
2194
+ if (!status || busyRef.current || reading.current || external.current) return;
2195
+ const next = promptToSave(prompt, savedPrompt);
2196
+ if (next === void 0) {
2197
+ dirty.current = false;
2198
+ setPrompt(void 0);
2199
+ return;
2200
+ }
2201
+ readGate.reset();
2202
+ const token = work.beginRequest();
2203
+ setError("");
2204
+ runTitleSettingsSave({
2205
+ call,
2206
+ prompt: next,
2207
+ expectedRevision: status.settings.revision,
2208
+ isCurrent: () => work.isRequest(token),
2209
+ savedMessage: messages.saved,
2210
+ failedMessage: messages.failed,
2211
+ onBusy: changeBusy,
2212
+ onSettings: (settings) => {
2213
+ setStatus((current) => current ? {
2214
+ ...current,
2215
+ settings
2216
+ } : current);
2217
+ dirty.current = false;
2218
+ setPrompt(void 0);
2219
+ },
2220
+ onNote: setNote,
2221
+ onError: setError
2222
+ });
2223
+ }
2224
+ }),
2225
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2226
+ className: "dsh-ui-help",
2227
+ children: text.promptHint
2228
+ })
2229
+ ]
1254
2230
  }),
1255
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1256
- className: "dsh-ui-help",
1257
- children: text.promptHint
2231
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2232
+ className: "dsh-ui-field",
2233
+ children: [
2234
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2235
+ className: "dsh-ui-label",
2236
+ children: text.model
2237
+ }),
2238
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalogStatus, {
2239
+ state: catalogState,
2240
+ locale: seat.locale
2241
+ }),
2242
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
2243
+ locale: seat.locale,
2244
+ open: menuOpen && Boolean(status) && !busy && !loadingSettings,
2245
+ onOpenChange: (next) => setMenuOpen(next && Boolean(status) && !busy && !loadingSettings),
2246
+ listClassName: "ct-root",
2247
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2248
+ type: "button",
2249
+ className: "dsh-model-menu-trigger",
2250
+ disabled: busy || loadingSettings || !status,
2251
+ "aria-label": `${text.model}: ${currentLabel}`,
2252
+ children: [
2253
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2254
+ className: "dsh-model-menu-triggerLabel",
2255
+ children: currentLabel
2256
+ }),
2257
+ !follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2258
+ className: "dsh-model-menu-triggerEffort",
2259
+ children: effortName ?? text.modelDefault
2260
+ }) : null,
2261
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen ? " dsh-model-menu-chevronOpen" : ""}` })
2262
+ ]
2263
+ }),
2264
+ choices,
2265
+ selected: follow ? void 0 : {
2266
+ provider: savedModel.provider,
2267
+ model: savedModel.model
2268
+ },
2269
+ onPick: (picked) => saveSettings({ model: {
2270
+ provider: picked.provider,
2271
+ model: picked.model
2272
+ } }),
2273
+ leading: [{
2274
+ id: "default",
2275
+ label: text.modelDefault,
2276
+ selected: follow
2277
+ }],
2278
+ onPickLeading: () => saveSettings({ model: {
2279
+ provider: "",
2280
+ model: ""
2281
+ } }),
2282
+ efforts,
2283
+ selectedEffort: savedModel.reasoningEffort || "",
2284
+ onPickEffort: (effort) => saveSettings({ model: follow ? {
2285
+ provider: "",
2286
+ model: ""
2287
+ } : {
2288
+ provider: savedModel.provider,
2289
+ model: savedModel.model,
2290
+ ...effort ? { reasoningEffort: effort } : {}
2291
+ } }),
2292
+ modelLabel: text.model,
2293
+ modelValue: currentLabel,
2294
+ defaultEffortLabel: text.modelDefault
2295
+ })
2296
+ ]
2297
+ }),
2298
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2299
+ className: "dsh-ui-field",
2300
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2301
+ className: "dsh-ui-label",
2302
+ children: text.cadence
2303
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2304
+ className: "dsh-ui-select",
2305
+ value: status?.settings?.cadence ?? "all-prompts",
2306
+ disabled: busy || loadingSettings || !status,
2307
+ onChange: (event) => {
2308
+ saveSettings({ cadence: event.target.value === "first-prompt" ? "first-prompt" : "all-prompts" });
2309
+ },
2310
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2311
+ value: "all-prompts",
2312
+ children: text.cadenceAll
2313
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2314
+ value: "first-prompt",
2315
+ children: text.cadenceFirst
2316
+ })]
2317
+ })]
1258
2318
  })
1259
2319
  ]
1260
- }),
1261
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1262
- className: "dsh-ui-field",
1263
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1264
- className: "dsh-ui-label",
1265
- children: text.model
1266
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1267
- className: "dsh-ui-select",
1268
- value: modelKey,
1269
- disabled: busy || loadingSettings || !status,
1270
- onChange: (event) => {
1271
- const value = event.target.value;
1272
- const choice = selectableChoices.find((item) => `${item.provider}\u001f${item.model}` === value);
1273
- saveSettings({ model: choice ? {
1274
- provider: choice.provider,
1275
- model: choice.model
1276
- } : {
1277
- provider: "",
1278
- model: ""
1279
- } });
1280
- },
1281
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1282
- value: "",
1283
- children: text.modelDefault
1284
- }), selectableChoices.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1285
- value: `${choice.provider}\u001f${choice.model}`,
1286
- children: choice.label
1287
- }, `${choice.provider}\u001f${choice.model}`))]
1288
- })]
1289
- }),
1290
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1291
- className: "dsh-ui-field",
1292
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1293
- className: "dsh-ui-label",
1294
- children: text.cadence
1295
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1296
- className: "dsh-ui-select",
1297
- value: status?.settings.cadence ?? "all-prompts",
1298
- disabled: busy || loadingSettings || !status,
1299
- onChange: (event) => {
1300
- saveSettings({ cadence: event.target.value === "first-prompt" ? "first-prompt" : "all-prompts" });
1301
- },
1302
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1303
- value: "all-prompts",
1304
- children: text.cadenceAll
1305
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1306
- value: "first-prompt",
1307
- children: text.cadenceFirst
1308
- })]
1309
- })]
1310
2320
  })
1311
2321
  ]
1312
2322
  });
1313
2323
  }
1314
- const styles = `${officialUiCss("ct-root")}
2324
+ const styles = `${officialUiCss("ct-root")}${modelMenuCss}
1315
2325
  .ct-root { max-width: 760px; }
1316
- .ct-prompt {
1317
- box-sizing: border-box;
1318
- width: 100%;
1319
- min-width: 0;
1320
- min-height: 96px;
1321
- resize: vertical;
1322
- padding: 8px 12px;
1323
- border: 0.5px solid var(--dsw-alias-border-l4);
1324
- border-radius: var(--dsw-radius-md);
1325
- background: var(--dsw-alias-bg-layer-3);
1326
- font: inherit;
1327
- font-size: 13px;
1328
- line-height: 1.5;
1329
- color: var(--dsw-alias-label-primary);
1330
- }
1331
- .ct-prompt:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
1332
- .ct-prompt::placeholder { color: var(--dsw-alias-label-dimmed); }
2326
+ .ct-prompt { min-height: 96px; }
1333
2327
  `;
1334
2328
  function apply(ctx) {
1335
2329
  const client = ctx;