dsh-model-groups 0.0.0-stage → 0.3.2

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.
@@ -0,0 +1,1033 @@
1
+ // Model picker adapted from DeepSeek Harness 0.2.1-rc.2 (MIT; see LICENSE).
2
+ //#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
3
+ function r(e) {
4
+ var t, f, n = "";
5
+ if ("string" == typeof e || "number" == typeof e) n += e;
6
+ else if ("object" == typeof e) if (Array.isArray(e)) {
7
+ var o = e.length;
8
+ for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
9
+ } else for (f in e) e[f] && (n && (n += " "), n += f);
10
+ return n;
11
+ }
12
+ function clsx() {
13
+ for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
14
+ return n;
15
+ }
16
+ //#endregion
17
+ //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-model-selection/src/client/ModelSelect.module.css.mjs
18
+ const css = "._7KE1Ra_root{min-width:0;position:relative}._7KE1Ra_trigger{border-radius:var(--dsw-radius-sm);min-width:0;max-width:360px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;outline:none;align-items:center;gap:4px;padding:0 4px 0 8px;font-size:13px;font-weight:400;line-height:20px;display:flex}@media (hover:hover){._7KE1Ra_trigger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}}._7KE1Ra_trigger:focus-visible:not([data-selection-focus]){box-shadow:0 0 0 2px var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary))}._7KE1Ra_trigger:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}._7KE1Ra_triggerLabel{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}._7KE1Ra_triggerEffort{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-caption);flex-shrink:1000;overflow:hidden}._7KE1Ra_triggerIcon{display:var(--dsh-composer-model-icon-display,none);flex:none}._7KE1Ra_triggerLabel,._7KE1Ra_triggerEffort{display:var(--dsh-composer-model-text-display,block)}._7KE1Ra_chevron{color:var(--dsw-alias-label-caption);flex:none;transition:transform .12s}._7KE1Ra_chevronOpen{transform:rotate(180deg)}._7KE1Ra_menu{box-sizing:border-box;z-index:1100;width:max-content;min-width:min(240px, calc(100vw - 32px), var(--dsh-anchored-max-width,100vw));max-width:min(420px, calc(100vw - 32px), var(--dsh-anchored-max-width,100vw));max-height:min(360px, calc(100vh - 96px), var(--dsh-anchored-max-height,100vh));--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border:0;flex-direction:column;padding:4px;display:flex;position:fixed;overflow:hidden}._7KE1Ra_status,._7KE1Ra_empty{color:var(--dsw-alias-label-tertiary);padding:8px;font-size:12px;line-height:18px}._7KE1Ra_error,._7KE1Ra_warning{border-radius:var(--dsw-radius-md);background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);justify-content:space-between;align-items:flex-start;gap:6px;margin-bottom:3px;padding:6px 7px;font-size:11px;line-height:16px;display:flex}._7KE1Ra_warning{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-state-warn-label)}._7KE1Ra_retry{color:inherit;font:inherit;cursor:pointer;background:0 0;border:none;flex:none;padding:0;font-weight:600}._7KE1Ra_searchRow{flex-shrink:0;margin:2px 0 3px;position:relative}._7KE1Ra_searchRow ._7KE1Ra_search{border-radius:var(--dsw-radius-md);background:0 0;border:0 solid #0000;height:auto;padding:5px 7px;display:flex}._7KE1Ra_searchRow ._7KE1Ra_searchWithQuery{padding-right:34px}._7KE1Ra_searchClear{corner-shape:round;width:24px;height:24px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:50%;justify-content:center;align-items:center;padding:0;display:inline-flex;position:absolute;top:50%;right:4px;transform:translateY(-50%)}._7KE1Ra_searchClear:focus-visible{background:var(--dsw-alias-interactive-bg-hover);outline:none}@media (hover:hover){._7KE1Ra_searchClear:hover{background:var(--dsw-alias-interactive-bg-hover);outline:none}}._7KE1Ra_searchRow ._7KE1Ra_search:focus-within{border-color:#0000}._7KE1Ra_searchRow ._7KE1Ra_search input{padding:0;font-size:12px;line-height:normal}@media (pointer:coarse){._7KE1Ra_searchRow ._7KE1Ra_search input{font-size:16px}}._7KE1Ra_searchRow ._7KE1Ra_search input::placeholder{color:var(--dsw-alias-label-caption)}._7KE1Ra_groups:not([hidden]){min-height:0;overflow:hidden auto}._7KE1Ra_option{box-sizing:border-box;border-radius:var(--dsw-radius-md);width:auto;min-width:100%;min-height:34px;color:inherit;text-align:left;cursor:pointer;background:0 0;border:none;outline:none;align-items:center;gap:6px;padding:5px 7px;display:flex}._7KE1Ra_option:focus-visible,._7KE1Ra_optionActive:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}._7KE1Ra_modelOption{max-width:100%}@media (hover:hover){._7KE1Ra_option:not(._7KE1Ra_modelOption):hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}}._7KE1Ra_selected{background:0 0}._7KE1Ra_option:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}._7KE1Ra_optionCopy{flex-direction:column;flex:1;min-width:0;display:flex}._7KE1Ra_modelName{color:inherit;text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:400;line-height:18px;overflow:hidden}._7KE1Ra_badgedName{vertical-align:baseline;white-space:nowrap;align-items:center;gap:4px;max-width:100%;display:inline-flex}._7KE1Ra_badgedNameText{text-overflow:ellipsis;min-width:0;overflow:hidden}._7KE1Ra_badge{flex:none}._7KE1Ra_check{color:var(--dsw-alias-label-primary);flex:0 0 14px;place-items:center;display:grid}._7KE1Ra_check svg{width:14px;height:14px}._7KE1Ra_cell{box-sizing:border-box;border-radius:var(--dsw-radius-md);width:auto;min-width:100%;height:34px;color:var(--dsw-alias-label-primary);cursor:pointer;text-align:left;background:0 0;border:none;outline:none;align-items:center;gap:6px;padding:0 8px;font-size:13px;line-height:20px;display:flex}._7KE1Ra_cell:focus-visible{background:var(--dsw-alias-interactive-bg-hover)}@media (hover:hover){._7KE1Ra_cell:hover{background:var(--dsw-alias-interactive-bg-hover)}}._7KE1Ra_cellLabel{white-space:nowrap;flex:none}._7KE1Ra_cellValue{text-overflow:ellipsis;white-space:nowrap;text-align:right;min-width:0;color:var(--dsw-alias-label-tertiary);flex:auto;overflow:hidden}._7KE1Ra_cellChevron{width:12px;height:12px;color:var(--dsw-alias-menu-icon);flex:none}";
19
+ const tagId = "dsh-model-groups/ModelSelect.css";
20
+ function installModelSelectStyles() {
21
+ if (typeof document === "undefined" || document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") !== null) return;
22
+ const tag = document.createElement("style");
23
+ tag.dataset.plugin = "dsh-model-groups";
24
+ tag.dataset.pluginCss = tagId;
25
+ tag.textContent = css;
26
+ document.head.appendChild(tag);
27
+ }
28
+ var ModelSelect_module_css_default = {
29
+ "badge": "_7KE1Ra_badge",
30
+ "badgedName": "_7KE1Ra_badgedName",
31
+ "badgedNameText": "_7KE1Ra_badgedNameText",
32
+ "cell": "_7KE1Ra_cell",
33
+ "cellChevron": "_7KE1Ra_cellChevron",
34
+ "cellLabel": "_7KE1Ra_cellLabel",
35
+ "cellValue": "_7KE1Ra_cellValue",
36
+ "check": "_7KE1Ra_check",
37
+ "chevron": "_7KE1Ra_chevron",
38
+ "chevronOpen": "_7KE1Ra_chevronOpen",
39
+ "empty": "_7KE1Ra_empty",
40
+ "error": "_7KE1Ra_error",
41
+ "groups": "_7KE1Ra_groups",
42
+ "menu": "_7KE1Ra_menu",
43
+ "modelName": "_7KE1Ra_modelName",
44
+ "modelOption": "_7KE1Ra_modelOption",
45
+ "option": "_7KE1Ra_option",
46
+ "optionActive": "_7KE1Ra_optionActive",
47
+ "optionCopy": "_7KE1Ra_optionCopy",
48
+ "retry": "_7KE1Ra_retry",
49
+ "root": "_7KE1Ra_root",
50
+ "search": "_7KE1Ra_search",
51
+ "searchClear": "_7KE1Ra_searchClear",
52
+ "searchRow": "_7KE1Ra_searchRow",
53
+ "searchWithQuery": "_7KE1Ra_searchWithQuery",
54
+ "selected": "_7KE1Ra_selected",
55
+ "status": "_7KE1Ra_status",
56
+ "trigger": "_7KE1Ra_trigger",
57
+ "triggerEffort": "_7KE1Ra_triggerEffort",
58
+ "triggerIcon": "_7KE1Ra_triggerIcon",
59
+ "triggerLabel": "_7KE1Ra_triggerLabel",
60
+ "warning": "_7KE1Ra_warning"
61
+ };
62
+ //#endregion
63
+ //#region lib/types/client/provider-order.js
64
+ /** Shared provider display order for the composer and command model pickers. */
65
+ /**
66
+ * Put the account and official providers first, preserving every other relative order.
67
+ * @param groups - Provider groups in catalog order.
68
+ * @returns a sorted copy; model order within each group is unchanged.
69
+ */
70
+ function orderModelProviders(groups) {
71
+ return groups.toSorted((left, right) => (left.id === "deepseek-account" ? 0 : left.id === "deepseek-official" ? 1 : 2) - (right.id === "deepseek-account" ? 0 : right.id === "deepseek-official" ? 1 : 2));
72
+ }
73
+ //#endregion
74
+ //#region lib/types/client/ModelSelect.js
75
+ /**
76
+ * ModelSelect: the composer's named model seat (`conversation.input.model`).
77
+ * Two-level selection per figma 496:26454's MenuDropdown: the root menu is
78
+ * the Model / Effort row pair (label + current value + a right chevron),
79
+ * each drilling into its own list — the provider-grouped model list over
80
+ * the shared directory, and the effort levels. The trigger (313:14108's
81
+ * ToggleButton) shows both: model name + effort in the caption tone.
82
+ * Model catalogs above four entries show search, which retains focus while
83
+ * ↑/↓ cycle the highlighted result; Enter and Tab accept it. Smaller model
84
+ * catalogs, root panes, and effort panes move focus between rows. Escape and Shift+Tab leave a drilled pane first and otherwise close
85
+ * back to the trigger. A drilled pane focuses the current effort or model
86
+ * search field. Provider headings paint their background only while pinned
87
+ * by scrolling. Clearing a query restores the full list and search focus.
88
+ * Selecting restores trigger focus without a ring until the trigger loses focus
89
+ * or the menu reopens. Model names match a case-insensitive ordered subsequence
90
+ * within each provider group, ranked by
91
+ * prefix, alignment score, then catalog order. Returning to the root pane
92
+ * hands focus back to the cell that opened it. Data and submission ride the
93
+ * same per-session ModelDirectory as the /model popup; exact-model reasoning
94
+ * metadata and the selected effort come from the Host rather than a
95
+ * client-owned vocabulary. A rejected selection announces through the shared
96
+ * transient Toast anchored to the composer card; the in-menu strip with
97
+ * Retry remains the catalog-load surface. While the directory's pending
98
+ * selection is unsettled, the trigger shows a spinner in place of its
99
+ * chevron, and each row whose value that selection carries shows one in place
100
+ * of its check mark.
101
+ */
102
+ /** Unplaced portal card: hidden but laid out at a fixed origin so offsetWidth/offsetHeight are real (Menu primitive's measure pass). */
103
+ const MEASURE_STYLE = {
104
+ visibility: "hidden",
105
+ left: 0,
106
+ top: 0
107
+ };
108
+ /**
109
+ * Render the composer model seat.
110
+ * @param props - owner share (locked) + injected face (shared directory
111
+ * store/verbs) + the standard locale seat.
112
+ * @returns the trigger and, while open, the two-level menu.
113
+ */
114
+ function ModelSelect({ locked, available, directory, load, select, t, renderSlot, providersForm }) {
115
+ const preferences = react.useSyncExternalStore(groupPreferences.subscribe, groupPreferences.getSnapshot);
116
+ const configuration = react.useSyncExternalStore(listener => providersForm?.subscribe(listener) ?? (() => {}), () => providersForm?.getSnapshot() ?? EMPTY_PROVIDERS_SNAPSHOT);
117
+ const providers = configuration.status === 'ready' ? (configuration.value?.providers ?? {}) : undefined;
118
+ const text = (zh, en) => t("menu.model") === "模型" ? zh : en;
119
+ const state = (0, react.useSyncExternalStore)((fn) => directory.subscribe(fn), () => directory.getSnapshot());
120
+ const [open, setOpen] = (0, react.useState)(false);
121
+ const [pane, setPane] = (0, react.useState)("root");
122
+ const [query, setQuery] = (0, react.useState)("");
123
+ const [highlightedIndex, setHighlightedIndex] = (0, react.useState)(null);
124
+ const [selectionFocus, setSelectionFocus] = (0, react.useState)(false);
125
+ const [menuMode, setLocalMenuMode] = (0, react.useState)(() => getMenuMode());
126
+ const [activeFlyout, setActiveFlyout] = (0, react.useState)(null);
127
+ const [activeSubFlyout, setActiveSubFlyout] = (0, react.useState)(null);
128
+ const flyoutTimerRef = (0, react.useRef)(null);
129
+ const subFlyoutTimerRef = (0, react.useRef)(null);
130
+ const flyoutRef = (0, react.useRef)(null);
131
+ const subFlyoutRef = (0, react.useRef)(null);
132
+ const lastActionRef = (0, react.useRef)("load");
133
+
134
+ const switchMode = (mode) => {
135
+ setLocalMenuMode(mode);
136
+ setMenuMode(mode);
137
+ setActiveFlyout(null);
138
+ setActiveSubFlyout(null);
139
+ };
140
+
141
+ const clearFlyoutTimer = () => {
142
+ if (flyoutTimerRef.current !== null) {
143
+ clearTimeout(flyoutTimerRef.current);
144
+ flyoutTimerRef.current = null;
145
+ }
146
+ if (subFlyoutTimerRef.current !== null) {
147
+ clearTimeout(subFlyoutTimerRef.current);
148
+ subFlyoutTimerRef.current = null;
149
+ }
150
+ };
151
+
152
+ // 完全读取并匹配官方原版主卡片尺寸(宽、高严格保持与主卡片完全一致)
153
+ const triggerFlyout = (entry, anchorEl) => {
154
+ clearFlyoutTimer();
155
+ if (activeFlyout?.key === entry.key) return;
156
+ setActiveSubFlyout(null);
157
+ const menuEl = menuRef.current;
158
+ const menuRect = menuEl ? menuEl.getBoundingClientRect() : anchorEl.getBoundingClientRect();
159
+ const margin = 12;
160
+ const winW = window.innerWidth;
161
+ const winH = window.innerHeight;
162
+ // 直接读取官方主卡片的实际渲染宽度与高度
163
+ const panelW = menuRect.width > 0 ? menuRect.width : 280;
164
+ const panelH = menuRect.height > 0 ? menuRect.height : 360;
165
+
166
+ // 紧靠主卡片左侧,0px 缝隙严丝合缝
167
+ let left = menuRect.left - panelW;
168
+ if (left < margin) {
169
+ if (winW - menuRect.right >= panelW + margin) {
170
+ left = menuRect.right;
171
+ } else {
172
+ left = Math.max(margin, Math.min(menuRect.left, winW - panelW - margin));
173
+ }
174
+ }
175
+
176
+ // 顶部与官方主卡片绝对平齐,高度与官方主卡片 1:1 等高
177
+ let top = menuRect.top;
178
+ setActiveFlyout({
179
+ key: entry.key,
180
+ entry,
181
+ pos: {
182
+ left: Math.round(left),
183
+ top: Math.round(top),
184
+ width: Math.round(panelW),
185
+ height: Math.round(panelH)
186
+ }
187
+ });
188
+ };
189
+
190
+ const triggerSubFlyout = (childGroup, anchorEl) => {
191
+ clearFlyoutTimer();
192
+ if (activeSubFlyout?.key === childGroup.key) return;
193
+ const flyoutEl = flyoutRef.current;
194
+ const flyoutRect = flyoutEl ? flyoutEl.getBoundingClientRect() : anchorEl.getBoundingClientRect();
195
+ const margin = 12;
196
+ const winW = window.innerWidth;
197
+ const winH = window.innerHeight;
198
+ const panelW = flyoutRect.width > 0 ? flyoutRect.width : 280;
199
+ const panelH = flyoutRect.height > 0 ? flyoutRect.height : 360;
200
+
201
+ let left = flyoutRect.left - panelW;
202
+ if (left < margin) {
203
+ if (winW - flyoutRect.right >= panelW + margin) {
204
+ left = flyoutRect.right;
205
+ } else {
206
+ left = Math.max(margin, Math.min(flyoutRect.left, winW - panelW - margin));
207
+ }
208
+ }
209
+
210
+ let top = flyoutRect.top;
211
+ setActiveSubFlyout({
212
+ key: childGroup.key,
213
+ childGroup,
214
+ pos: {
215
+ left: Math.round(left),
216
+ top: Math.round(top),
217
+ width: Math.round(panelW),
218
+ height: Math.round(panelH)
219
+ }
220
+ });
221
+ };
222
+
223
+ const scheduleCloseFlyout = (delay = 240) => {
224
+ clearFlyoutTimer();
225
+ flyoutTimerRef.current = setTimeout(() => {
226
+ setActiveFlyout(null);
227
+ setActiveSubFlyout(null);
228
+ }, delay);
229
+ };
230
+
231
+ const scheduleCloseSubFlyout = (delay = 240) => {
232
+ if (subFlyoutTimerRef.current !== null) clearTimeout(subFlyoutTimerRef.current);
233
+ subFlyoutTimerRef.current = setTimeout(() => {
234
+ setActiveSubFlyout(null);
235
+ }, delay);
236
+ };
237
+ const [toast, setToast] = (0, react.useState)(null);
238
+ const toastSeq = (0, react.useRef)(0);
239
+ const rootRef = (0, react.useRef)(null);
240
+ const triggerRef = (0, react.useRef)(null);
241
+ const searchRef = (0, react.useRef)(null);
242
+ const menuRef = (0, react.useRef)(null);
243
+ const groupsRef = (0, react.useRef)(null);
244
+ const [menuPos, setMenuPos] = (0, react.useState)(null);
245
+ const itemRefs = (0, react.useRef)([]);
246
+ const id = (0, react.useId)();
247
+ const accountProviderName = t("provider.account");
248
+ const groups = (0, react.useMemo)(() => orderModelProviders(state.groups).map((group) => ({
249
+ ...group,
250
+ provider: {
251
+ id: group.id,
252
+ name: group.id === "deepseek-account" ? accountProviderName : group.name
253
+ }
254
+ })), [state.groups, accountProviderName]);
255
+ const choices = (0, react.useMemo)(() => groups.flatMap((group) => group.models.map((model) => ({
256
+ group,
257
+ model,
258
+ selection: {
259
+ provider: group.id,
260
+ model: model.id,
261
+ ...model.reasoning?.defaultEffort === void 0 ? {} : { reasoningEffort: model.reasoning.defaultEffort }
262
+ }
263
+ }))), [groups]);
264
+ const showSearch = choices.length > 4;
265
+ const searching = showSearch && query.trim() !== "";
266
+ const fullHierarchy = react.useMemo(() => buildModelHierarchy(groups, providers, preferences, state.current), [groups, providers, preferences, state.current]);
267
+ const displayGroups = react.useMemo(() => filterModelHierarchy(fullHierarchy, showSearch ? query : "", _deepseek_ai_dsh_client_ui_primitives.rankByName), [fullHierarchy, query, showSearch]);
268
+ const expansionKeys = react.useMemo(() => hierarchyExpansionKeys(fullHierarchy), [fullHierarchy]);
269
+ const providerCount = fullHierarchy.length;
270
+ const visibleModels = react.useMemo(() => visibleHierarchyModels(displayGroups), [displayGroups]);
271
+ const currentVisibleIndex = visibleModels.findIndex((model) => model.provider === state.current?.provider && model.model === state.current.model);
272
+ const activeModelIndex = Math.min(highlightedIndex ?? Math.max(0, currentVisibleIndex), visibleModels.length - 1);
273
+ const currentChoice = choices[state.current === null ? -1 : choices.findIndex((c) => c.selection.provider === state.current?.provider && c.selection.model === state.current.model)];
274
+ const reasoning = currentChoice?.model.reasoning;
275
+ const effectiveEffort = state.current?.reasoningEffort ?? reasoning?.defaultEffort;
276
+ const effortLabel = currentChoice === void 0 ? void 0 : reasoning === void 0 ? state.retainedEffort : effectiveEffort === void 0 ? t("effort.providerDefault") : reasoning.efforts.find((level) => level.id === effectiveEffort)?.name ?? effectiveEffort;
277
+ const effortChoices = (0, react.useMemo)(() => reasoning === void 0 ? [] : [...reasoning.defaultEffort === void 0 ? [{
278
+ key: "provider-default",
279
+ effort: void 0,
280
+ label: t("effort.providerDefault")
281
+ }] : [], ...reasoning.efforts.map((effort) => ({
282
+ key: `effort:${effort.id}`,
283
+ effort: effort.id,
284
+ label: effort.name
285
+ }))], [reasoning, t]);
286
+ const { pending } = state;
287
+ const busy = pending !== null;
288
+ /** Group headers and bulk buttons; the only focus targets when every group is collapsed. */
289
+ const groupControls = () => [...(menuRef.current?.querySelectorAll("[data-group-bulk]:not(:disabled), [data-group-toggle]:not(:disabled)") ?? [])];
290
+ const reload = () => {
291
+ lastActionRef.current = "load";
292
+ load();
293
+ };
294
+ (0, react.useEffect)(() => {
295
+ if (!open) return;
296
+ const closeOutside = (event) => {
297
+ if (rootRef.current?.contains(event.target) === true) return;
298
+ if (menuRef.current?.contains(event.target) === true) return;
299
+ if (flyoutRef.current?.contains(event.target) === true) return;
300
+ if (subFlyoutRef.current?.contains(event.target) === true) return;
301
+ setOpen(false);
302
+ setActiveFlyout(null);
303
+ setActiveSubFlyout(null);
304
+ };
305
+ document.addEventListener("pointerdown", closeOutside);
306
+ return () => {
307
+ document.removeEventListener("pointerdown", closeOutside);
308
+ };
309
+ }, [open]);
310
+ (0, react.useLayoutEffect)(() => {
311
+ if (!showSearch) {
312
+ setQuery("");
313
+ setHighlightedIndex(null);
314
+ }
315
+ }, [showSearch]);
316
+ const paneFocus = (0, react.useRef)(null);
317
+ const previousShowSearch = (0, react.useRef)(showSearch);
318
+ (0, react.useEffect)(() => {
319
+ const changedSearchMode = previousShowSearch.current !== showSearch;
320
+ previousShowSearch.current = showSearch;
321
+ const intent = paneFocus.current ?? (changedSearchMode && pane === "model" ? "drill" : null);
322
+ paneFocus.current = null;
323
+ if (!open || intent === null) return;
324
+ if (intent === "drill") {
325
+ // Never focus the search field here: on touch devices that raises the
326
+ // on-screen keyboard the moment the model list opens. Land on the
327
+ // current row so arrow keys and Enter still work without an IME.
328
+ (menuRef.current?.querySelector("[role=\"menuitemradio\"][aria-checked=\"true\"]:not([disabled])") ?? itemRefs.current.find((item) => item !== null && !item.disabled) ?? groupControls()[0] ?? triggerRef.current)?.focus();
329
+ return;
330
+ }
331
+ const cell = itemRefs.current[intent === "effort" ? 1 : 0];
332
+ (cell !== null && cell !== void 0 && !cell.disabled ? cell : triggerRef.current)?.focus();
333
+ }, [
334
+ open,
335
+ pane,
336
+ showSearch
337
+ ]);
338
+ (0, react.useEffect)(() => {
339
+ const viewport = groupsRef.current;
340
+ if (viewport === null) return;
341
+ return (0, _deepseek_ai_dsh_client_ui_primitives.observeStickyMenuGroups)(viewport);
342
+ }, [
343
+ available,
344
+ open,
345
+ pane,
346
+ displayGroups
347
+ ]);
348
+ (0, react.useLayoutEffect)(() => {
349
+ // Folding should not scroll away from the heading the user just pressed.
350
+ if (document.activeElement?.matches?.("[data-group-toggle], [data-group-bulk]")) return;
351
+ if (open && pane === "model" && activeModelIndex >= 0) itemRefs.current[activeModelIndex]?.scrollIntoView({ block: "nearest" });
352
+ }, [
353
+ open,
354
+ pane,
355
+ activeModelIndex,
356
+ visibleModels
357
+ ]);
358
+ (0, react.useLayoutEffect)(() => {
359
+ if (!available || !open) {
360
+ setMenuPos(null);
361
+ return;
362
+ }
363
+ const place = () => {
364
+ /* v8 ignore next 2 -- the trigger ref is attached whenever the menu is open. */
365
+ const rect = triggerRef.current?.getBoundingClientRect();
366
+ if (rect === void 0) return;
367
+ const MARGIN = 12;
368
+ const winW = window.innerWidth;
369
+ const winH = window.innerHeight;
370
+ const lw = menuRef.current?.offsetWidth ?? 0;
371
+ const lh = menuRef.current?.offsetHeight ?? 0;
372
+ // 窄屏(手机竖屏等)优先居中并确保不会超出屏幕左右
373
+ let x;
374
+ if (winW <= 480) {
375
+ x = Math.max(MARGIN, (winW - lw) / 2);
376
+ } else {
377
+ x = rect.right - lw;
378
+ if (lw > 0) x = Math.min(Math.max(x, MARGIN), winW - lw - MARGIN);
379
+ }
380
+ let y = rect.top - 8 - lh;
381
+ // 如果上方空间不够容纳弹窗,向下弹出
382
+ if (y < MARGIN && rect.bottom + 8 + lh <= winH - MARGIN) {
383
+ y = rect.bottom + 8;
384
+ } else {
385
+ if (lh > 0) y = Math.min(Math.max(y, MARGIN), winH - lh - MARGIN);
386
+ }
387
+ setMenuPos({
388
+ left: Math.round(x),
389
+ top: Math.round(y)
390
+ });
391
+ };
392
+ place();
393
+ window.addEventListener("scroll", place, true);
394
+ window.addEventListener("resize", place);
395
+ const menu = menuRef.current;
396
+ const observer = typeof ResizeObserver !== "undefined" && menu !== null ? new ResizeObserver(place) : null;
397
+ if (menu !== null) observer?.observe(menu);
398
+ return () => {
399
+ observer?.disconnect();
400
+ window.removeEventListener("scroll", place, true);
401
+ window.removeEventListener("resize", place);
402
+ };
403
+ }, [
404
+ available,
405
+ open,
406
+ pane,
407
+ state,
408
+ query
409
+ ]);
410
+ if (!available) return null;
411
+ const show = () => {
412
+ setSelectionFocus(false);
413
+ triggerRef.current?.focus();
414
+ setQuery("");
415
+ setHighlightedIndex(null);
416
+ if (currentChoice === void 0) paneFocus.current = "drill";
417
+ setPane(currentChoice === void 0 ? "model" : "root");
418
+ setOpen(true);
419
+ reload();
420
+ };
421
+ const changeQuery = (next) => {
422
+ setQuery(next);
423
+ setHighlightedIndex(0);
424
+ };
425
+ const close = (restoreFocus = false) => {
426
+ setOpen(false);
427
+ setPane("root");
428
+ setActiveFlyout(null);
429
+ setActiveSubFlyout(null);
430
+ if (restoreFocus) queueMicrotask(() => {
431
+ triggerRef.current?.focus();
432
+ });
433
+ };
434
+ const closeAfterSelection = () => {
435
+ setSelectionFocus(true);
436
+ close(true);
437
+ };
438
+ const drill = (next) => {
439
+ setQuery("");
440
+ setHighlightedIndex(null);
441
+ paneFocus.current = "drill";
442
+ setPane(next);
443
+ };
444
+ /** Leave a drilled pane for the root one, handing the keyboard back to its cell. */
445
+ const back = (from) => {
446
+ paneFocus.current = from;
447
+ setPane("root");
448
+ };
449
+ const moveFocus = (offset) => {
450
+ const items = itemRefs.current.filter((item) => item !== null);
451
+ if (items.length === 0) return;
452
+ const active = items.findIndex((item) => item === document.activeElement);
453
+ items[active === -1 ? offset > 0 ? 0 : items.length - 1 : (active + offset + items.length) % items.length]?.focus();
454
+ };
455
+ const onRootKeyDown = (event) => {
456
+ if (event.nativeEvent.isComposing) return;
457
+ if (event.key === "Escape" && open) {
458
+ event.preventDefault();
459
+ if (pane !== "root" && currentChoice !== void 0) back(pane);
460
+ else close(true);
461
+ return;
462
+ }
463
+ if (!open) return;
464
+ if (pane === "model" && (event.key === "ArrowDown" || event.key === "ArrowUp") && event.target !== searchRef.current) {
465
+ event.preventDefault();
466
+ const rows = [...(menuRef.current?.querySelectorAll('[data-group-bulk]:not(:disabled), [data-group-toggle]:not(:disabled), [data-model-choice]:not(:disabled)') ?? [])];
467
+ const at = rows.indexOf(document.activeElement);
468
+ rows[at < 0 ? (event.key === 'ArrowDown' ? 0 : rows.length - 1) : (at + (event.key === 'ArrowDown' ? 1 : -1) + rows.length) % rows.length]?.focus();
469
+ return;
470
+ }
471
+ if (pane === "model" && event.target instanceof HTMLButtonElement && event.target.matches("[data-group-toggle]")) {
472
+ if ((event.key === 'ArrowLeft' && event.target.getAttribute('aria-expanded') === 'true') || (event.key === 'ArrowRight' && event.target.getAttribute('aria-expanded') === 'false')) {
473
+ event.preventDefault();
474
+ event.target.click();
475
+ }
476
+ return;
477
+ }
478
+ if (pane === "model" && showSearch && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
479
+ event.preventDefault();
480
+ if (!busy && visibleModels.length > 0) {
481
+ setHighlightedIndex((activeModelIndex + (event.key === "ArrowDown" ? 1 : -1) + visibleModels.length) % visibleModels.length);
482
+ searchRef.current?.focus();
483
+ }
484
+ return;
485
+ }
486
+ if (pane === "model" && showSearch && event.target instanceof HTMLInputElement && (event.key === "Enter" || event.key === "Tab" && !event.shiftKey)) {
487
+ if (event.key === "Tab" && visibleModels.length === 0) return;
488
+ event.preventDefault();
489
+ const highlighted = visibleModels[activeModelIndex];
490
+ if (!busy && highlighted !== void 0) choose(highlighted);
491
+ return;
492
+ }
493
+ if (event.key === "Tab" && event.shiftKey && pane === "model" && showSearch && document.activeElement === searchRef.current) {
494
+ const controls = groupControls();
495
+ if (controls.length > 0) {
496
+ event.preventDefault();
497
+ controls[0].focus();
498
+ return;
499
+ }
500
+ }
501
+ if (event.key === "Tab") {
502
+ if (event.shiftKey) {
503
+ event.preventDefault();
504
+ if (pane !== "root" && currentChoice !== void 0) back(pane);
505
+ else close(true);
506
+ return;
507
+ }
508
+ const focused = document.activeElement;
509
+ const rows = itemRefs.current.filter((item) => item !== null);
510
+ if (focused instanceof HTMLButtonElement && rows.includes(focused)) {
511
+ event.preventDefault();
512
+ focused.click();
513
+ return;
514
+ }
515
+ if (focused !== triggerRef.current) return;
516
+ event.preventDefault();
517
+ if (pane === "model" && showSearch) {
518
+ setHighlightedIndex(null);
519
+ searchRef.current?.focus();
520
+ return;
521
+ }
522
+ (menuRef.current?.querySelector("[role=\"menuitemradio\"][aria-checked=\"true\"]:not([disabled])") ?? rows.find((item) => !item.disabled) ?? groupControls()[0])?.focus();
523
+ return;
524
+ }
525
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
526
+ event.preventDefault();
527
+ if (itemRefs.current.some((item) => item !== null)) moveFocus(event.key === "ArrowDown" ? 1 : -1);
528
+ else groupControls()[0]?.focus();
529
+ }
530
+ };
531
+ const settleSelection = (result) => {
532
+ if (result === void 0) return;
533
+ if (result.ok) {
534
+ if (rootRef.current !== null) closeAfterSelection();
535
+ return;
536
+ }
537
+ const { error } = result;
538
+ toastSeq.current += 1;
539
+ setToast({
540
+ seq: toastSeq.current,
541
+ text: error.code === "session/writer-held" ? t("error.sessionInUse") : t("error.action", { message: `${error.code}: ${error.message}` })
542
+ });
543
+ };
544
+ const submit = (selection) => {
545
+ lastActionRef.current = "select";
546
+ setSelectionFocus(true);
547
+ triggerRef.current?.focus();
548
+ select(selection).then(settleSelection);
549
+ };
550
+ const choose = (selection) => {
551
+ if (state.current?.provider === selection.provider && state.current.model === selection.model) {
552
+ closeAfterSelection();
553
+ return;
554
+ }
555
+ submit(selection);
556
+ };
557
+ const chooseEffort = (effort) => {
558
+ if (state.current === null) return;
559
+ if (effectiveEffort === effort) {
560
+ closeAfterSelection();
561
+ return;
562
+ }
563
+ submit({
564
+ provider: state.current.provider,
565
+ model: state.current.model,
566
+ ...effort === void 0 ? {} : { reasoningEffort: effort }
567
+ });
568
+ };
569
+ const waiting = currentChoice === void 0 && state.status === "loading";
570
+ const currentKey = decodeKeyModelId(state.current?.model);
571
+ const currentSource = currentChoice === undefined ? '' : currentKey ? `${currentChoice.group.provider.name} / ${keyEntryName(providers, currentChoice.group.id, currentKey.entryId)}` : currentChoice.group.provider.name;
572
+ const modelLabel = waiting ? t("trigger.loading") : currentChoice === undefined ? t("trigger.fallback") : `${currentChoice.model.name} · ${currentSource}`;
573
+ const triggerLabel = effortLabel === void 0 ? modelLabel : `${modelLabel} · ${effortLabel}`;
574
+ const triggerAria = waiting ? t("trigger.loading") : currentChoice === void 0 ? t("trigger.selectAria") : effortLabel === void 0 ? t("trigger.aria", { model: modelLabel }) : t("trigger.ariaEffort", {
575
+ model: modelLabel,
576
+ effort: effortLabel
577
+ });
578
+ const renderModelLabel = (provider, model, placement, selected) => {
579
+ // The label child slot is declared by the shipped picker's own entry; if this
580
+ // bundle ever runs without it, degrade to the plain name instead of throwing.
581
+ let label;
582
+ try {
583
+ label = renderSlot("conversation.input.model.label", {
584
+ provider,
585
+ model,
586
+ placement,
587
+ selected
588
+ }, { fallback: model.name });
589
+ } catch {
590
+ label = model.name;
591
+ }
592
+ return (0, react_jsx_runtime.jsx)("span", {
593
+ "data-provider-id": provider.id,
594
+ "data-model-id": model.id,
595
+ children: label
596
+ });
597
+ };
598
+ let itemIndex = 0;
599
+ let modelIndex = 0;
600
+ const itemRef = () => {
601
+ const at = itemIndex++;
602
+ return (node) => {
603
+ itemRefs.current[at] = node;
604
+ };
605
+ };
606
+ const renderModels = (group, direct = false) => group.models.map(model => {
607
+ const index = modelIndex++;
608
+ const selected = state.current?.provider === group.id && state.current.model === model.id;
609
+ return react_jsx_runtime.jsxs('button', {
610
+ ref: itemRef(), type: 'button', role: 'menuitemradio',
611
+ 'aria-checked': selected, id: `${id}-model-${index}`,
612
+ 'data-model-choice': '', 'data-provider-id': group.id, 'data-model-id': model.id,
613
+ tabIndex: showSearch ? -1 : 0,
614
+ onFocus: () => setHighlightedIndex(index),
615
+ 'data-highlighted': index === activeModelIndex ? '' : undefined,
616
+ className: clsx(ModelSelect_module_css_default.option, ModelSelect_module_css_default.modelOption, selected && ModelSelect_module_css_default.selected, index === activeModelIndex && ModelSelect_module_css_default.optionActive),
617
+ onMouseMove: busy || index === activeModelIndex ? undefined : () => setHighlightedIndex(index),
618
+ title: `${model.name} · ${group.provider.name} (${group.id})`, disabled: busy,
619
+ onClick: () => choose({ provider: group.id, model: model.id }),
620
+ children: [
621
+ react_jsx_runtime.jsxs('span', { className: ModelSelect_module_css_default.optionCopy, children: [
622
+ react_jsx_runtime.jsx('span', { className: ModelSelect_module_css_default.modelName, children: renderModelLabel(group.provider, model, 'option', selected) }),
623
+ direct && react_jsx_runtime.jsx('span', { className: 'dsh-mg-source', children: group.provider.name }),
624
+ ] }),
625
+ react_jsx_runtime.jsx('span', { className: ModelSelect_module_css_default.check, children: pending?.provider === group.id && pending.model === model.id ? react_jsx_runtime.jsx(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: 'ongoing' }) : selected ? react_jsx_runtime.jsx(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, {}) : null }),
626
+ ],
627
+ }, `${group.id}/${model.id}`);
628
+ });
629
+ const renderHeading = (key, name, expanded, count, current, contentId, major = false, route = '') => react_jsx_runtime.jsx('div', {
630
+ 'data-menu-group-heading': major ? '' : undefined,
631
+ className: clsx('dsh-mg-groupHeading', major ? 'dsh-mg-majorHeading' : 'dsh-mg-minorHeading'),
632
+ children: react_jsx_runtime.jsxs('button', {
633
+ type: 'button', id: `${contentId}-heading`, 'data-group-toggle': key,
634
+ 'aria-expanded': expanded, 'aria-controls': contentId, disabled: busy || searching,
635
+ title: searching ? text('搜索时自动展开,清空搜索后恢复', 'Expanded during search; clearing restores your groups') : route || name,
636
+ onClick: () => { setHighlightedIndex(null); groupPreferences.setExpanded([key], !expanded); },
637
+ children: [
638
+ react_jsx_runtime.jsx(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, { className: expanded ? 'dsh-mg-groupArrow dsh-mg-groupArrowOpen' : 'dsh-mg-groupArrow', size: 14 }),
639
+ react_jsx_runtime.jsx('span', { className: 'dsh-mg-groupName', children: name }),
640
+ current && react_jsx_runtime.jsx('span', { className: 'dsh-mg-currentBadge', children: text('当前', 'Current') }),
641
+ react_jsx_runtime.jsx('span', { className: 'dsh-mg-groupCount', children: count }),
642
+ ],
643
+ }),
644
+ });
645
+ const renderHierarchy = () => displayGroups.map((entry, index) => {
646
+ const providerId = `${id}-provider-${index}`;
647
+ const isFlyoutMode = menuMode === 'panel' && !searching;
648
+ const isFlyoutActive = activeFlyout?.key === entry.key;
649
+
650
+ return react_jsx_runtime.jsxs('section', {
651
+ role: 'group', 'aria-labelledby': `${providerId}-heading`, 'data-menu-group': '', 'data-provider-group': entry.id,
652
+ onMouseEnter: (e) => {
653
+ if (isFlyoutMode) {
654
+ clearFlyoutTimer();
655
+ triggerFlyout(entry, e.currentTarget);
656
+ }
657
+ },
658
+ onMouseLeave: () => {
659
+ if (isFlyoutMode) scheduleCloseFlyout();
660
+ },
661
+ children: [
662
+ react_jsx_runtime.jsx('span', { 'aria-hidden': true, 'data-menu-group-start': '', className: 'dsh-mg-groupStart' }),
663
+ react_jsx_runtime.jsx('div', {
664
+ 'data-menu-group-heading': '',
665
+ className: clsx('dsh-mg-groupHeading', 'dsh-mg-majorHeading', isFlyoutActive && 'dsh-mg-active-heading'),
666
+ children: react_jsx_runtime.jsxs('button', {
667
+ type: 'button', id: `${providerId}-heading`, 'data-group-toggle': entry.key,
668
+ 'aria-expanded': isFlyoutMode ? isFlyoutActive : entry.expanded,
669
+ 'aria-controls': providerId, disabled: busy || searching,
670
+ title: searching ? text('搜索时自动展开,清空搜索后恢复', 'Expanded during search; clearing restores your groups') : entry.id || entry.name,
671
+ onClick: (e) => {
672
+ if (isFlyoutMode) {
673
+ if (isFlyoutActive) setActiveFlyout(null);
674
+ else triggerFlyout(entry, e.currentTarget);
675
+ } else {
676
+ setHighlightedIndex(null);
677
+ groupPreferences.setExpanded([entry.key], !entry.expanded);
678
+ }
679
+ },
680
+ children: [
681
+ react_jsx_runtime.jsx(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, {
682
+ className: (isFlyoutMode ? isFlyoutActive : entry.expanded) ? 'dsh-mg-groupArrow dsh-mg-groupArrowOpen' : 'dsh-mg-groupArrow',
683
+ size: 14
684
+ }),
685
+ react_jsx_runtime.jsx('span', { className: 'dsh-mg-groupName', children: entry.name }),
686
+ entry.current && react_jsx_runtime.jsx('span', { className: 'dsh-mg-currentBadge', children: text('当前', 'Current') }),
687
+ react_jsx_runtime.jsx('span', { className: 'dsh-mg-groupCount', children: entry.modelCount }),
688
+ ],
689
+ }),
690
+ }),
691
+ !isFlyoutMode && react_jsx_runtime.jsx('div', {
692
+ id: providerId, hidden: !entry.expanded, className: entry.kind === 'multi' ? 'dsh-mg-majorContent' : undefined,
693
+ children: entry.expanded && (entry.kind !== 'multi' ? renderModels(entry.group) : entry.children.map((group, childIndex) => {
694
+ const keyId = `${providerId}-key-${childIndex}`;
695
+ const name = group.legacy ? text('原单密钥(兼容旧会话)', 'Legacy single key (existing session)') : group.name;
696
+ return react_jsx_runtime.jsxs('section', {
697
+ role: 'group', 'aria-labelledby': `${keyId}-heading`, 'data-key-entry': group.entryId ?? '@legacy',
698
+ children: [
699
+ renderHeading(group.key, name, group.expanded, group.models.length, group.current, keyId, false, `${entry.name} / ${name}`),
700
+ react_jsx_runtime.jsx('div', { id: keyId, hidden: !group.expanded, className: 'dsh-mg-minorContent', children: group.expanded && renderModels(group) }),
701
+ ],
702
+ }, group.key);
703
+ }))
704
+ }),
705
+ ],
706
+ }, entry.key);
707
+ });
708
+ return (0, react_jsx_runtime.jsxs)("div", {
709
+ ref: rootRef,
710
+ className: ModelSelect_module_css_default.root,
711
+ "data-model-groups-plugin": "0.3.0",
712
+ onKeyDown: onRootKeyDown,
713
+ onMouseDown: (event) => {
714
+ if (event.target instanceof Element && event.target.closest("[data-group-toggle], [data-group-bulk]") !== null) return;
715
+ if (event.target instanceof Element && event.target.closest("button") !== null) event.preventDefault();
716
+ },
717
+ children: [
718
+ (0, react_jsx_runtime.jsxs)("button", {
719
+ ref: triggerRef,
720
+ type: "button",
721
+ className: ModelSelect_module_css_default.trigger,
722
+ "aria-label": triggerAria,
723
+ "aria-haspopup": "menu",
724
+ "aria-expanded": open,
725
+ "aria-controls": open ? `${id}-menu` : void 0,
726
+ title: triggerLabel,
727
+ "aria-busy": busy,
728
+ "data-selection-focus": selectionFocus ? "" : void 0,
729
+ onBlur: () => {
730
+ setSelectionFocus(false);
731
+ },
732
+ disabled: locked,
733
+ onClick: () => {
734
+ if (open) close(true);
735
+ else show();
736
+ },
737
+ children: [
738
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDataOutlineRegular, {
739
+ className: ModelSelect_module_css_default.triggerIcon,
740
+ size: 16
741
+ }),
742
+ (0, react_jsx_runtime.jsx)("span", {
743
+ className: ModelSelect_module_css_default.triggerLabel,
744
+ children: currentChoice === void 0 ? modelLabel : react_jsx_runtime.jsxs(react_jsx_runtime.Fragment, { children: [renderModelLabel(currentChoice.group.provider, currentChoice.model, "trigger", true), react_jsx_runtime.jsx('span', { className: 'dsh-mg-triggerSource', children: ` · ${currentSource}` })] })
745
+ }),
746
+ effortLabel !== void 0 && (0, react_jsx_runtime.jsx)("span", {
747
+ className: ModelSelect_module_css_default.triggerEffort,
748
+ children: effortLabel
749
+ }),
750
+ busy ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "ongoing" }) : (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: clsx(ModelSelect_module_css_default.chevron, open && ModelSelect_module_css_default.chevronOpen) })
751
+ ]
752
+ }),
753
+ open && (0, react_dom.createPortal)((0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.MenuSurface, {
754
+ ref: menuRef,
755
+ id: `${id}-menu`,
756
+ className: ModelSelect_module_css_default.menu,
757
+ style: menuPos ?? MEASURE_STYLE,
758
+ role: pane === "model" ? "group" : "menu",
759
+ "aria-label": t("menu.aria"),
760
+ "aria-busy": state.status === "loading" || busy,
761
+ children: [
762
+ pane === "root" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("button", {
763
+ ref: itemRef(),
764
+ type: "button",
765
+ role: "menuitem",
766
+ className: ModelSelect_module_css_default.cell,
767
+ onClick: () => {
768
+ drill("model");
769
+ },
770
+ children: [
771
+ (0, react_jsx_runtime.jsx)("span", {
772
+ className: ModelSelect_module_css_default.cellLabel,
773
+ children: t("menu.model")
774
+ }),
775
+ (0, react_jsx_runtime.jsx)("span", {
776
+ className: ModelSelect_module_css_default.cellValue,
777
+ children: currentChoice === void 0 ? modelLabel : react_jsx_runtime.jsxs(react_jsx_runtime.Fragment, { children: [renderModelLabel(currentChoice.group.provider, currentChoice.model, "menu", true), ` · ${currentSource}`] })
778
+ }),
779
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, { className: ModelSelect_module_css_default.cellChevron })
780
+ ]
781
+ }), reasoning !== void 0 && (0, react_jsx_runtime.jsxs)("button", {
782
+ ref: itemRef(),
783
+ type: "button",
784
+ role: "menuitem",
785
+ className: ModelSelect_module_css_default.cell,
786
+ onClick: () => {
787
+ drill("effort");
788
+ },
789
+ children: [
790
+ (0, react_jsx_runtime.jsx)("span", {
791
+ className: ModelSelect_module_css_default.cellLabel,
792
+ children: t("menu.effort")
793
+ }),
794
+ (0, react_jsx_runtime.jsx)("span", {
795
+ className: ModelSelect_module_css_default.cellValue,
796
+ children: effortLabel
797
+ }),
798
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, { className: ModelSelect_module_css_default.cellChevron })
799
+ ]
800
+ })] }),
801
+ pane === "model" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
802
+ showSearch && (0, react_jsx_runtime.jsxs)("div", {
803
+ className: ModelSelect_module_css_default.searchRow,
804
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
805
+ ref: searchRef,
806
+ className: clsx(ModelSelect_module_css_default.search, query !== "" && ModelSelect_module_css_default.searchWithQuery),
807
+ type: "text",
808
+ role: "searchbox",
809
+ "aria-label": t("search.placeholder"),
810
+ "aria-controls": `${id}-models`,
811
+ "aria-activedescendant": activeModelIndex < 0 ? void 0 : `${id}-model-${activeModelIndex}`,
812
+ placeholder: t("search.placeholder"),
813
+ value: query,
814
+ readOnly: busy,
815
+ onChange: (event) => {
816
+ changeQuery(event.target.value);
817
+ }
818
+ }), query !== "" && (0, react_jsx_runtime.jsx)("button", {
819
+ type: "button",
820
+ className: ModelSelect_module_css_default.searchClear,
821
+ "aria-label": t("search.clear"),
822
+ disabled: busy,
823
+ onClick: () => {
824
+ changeQuery("");
825
+ searchRef.current?.focus();
826
+ },
827
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseFillRegular, {})
828
+ })]
829
+ }),
830
+ choices.length > 0 && react_jsx_runtime.jsxs("div", {
831
+ className: "dsh-mg-toolbar",
832
+ role: "group",
833
+ "aria-label": text("模型分组", "Model groups"),
834
+ children: [
835
+ react_jsx_runtime.jsx("span", { className: "dsh-mg-summary", children: searching ? text("搜索中 · 展开匹配", "Search active") : text(`${providerCount} 供应商 · ${fullHierarchy.reduce((sum, entry) => sum + entry.modelCount, 0)} 模型`, `${providerCount} providers · ${fullHierarchy.reduce((sum, entry) => sum + entry.modelCount, 0)} models`) }),
836
+ react_jsx_runtime.jsxs("div", {
837
+ className: "dsh-mg-toolbar-actions",
838
+ children: [true, false].map(expanded => react_jsx_runtime.jsx("button", {
839
+ type: "button", "data-group-bulk": expanded ? "expand" : "collapse",
840
+ disabled: busy || searching || expansionKeys.length === 0,
841
+ onClick: () => { setHighlightedIndex(null); groupPreferences.setExpanded(expansionKeys, expanded); },
842
+ children: expanded ? text("展开全部", "Expand") : text("收起全部", "Collapse"),
843
+ }, String(expanded)))
844
+ })
845
+ ],
846
+ }),
847
+ state.status === "loading" && (0, react_jsx_runtime.jsx)("div", {
848
+ className: ModelSelect_module_css_default.status,
849
+ children: t("status.loading")
850
+ }),
851
+ state.error !== null && lastActionRef.current === "load" && (0, react_jsx_runtime.jsxs)("div", {
852
+ className: ModelSelect_module_css_default.error,
853
+ children: [(0, react_jsx_runtime.jsx)("span", { children: t("error.action", { message: state.error }) }), (0, react_jsx_runtime.jsx)("button", {
854
+ type: "button",
855
+ className: ModelSelect_module_css_default.retry,
856
+ onClick: reload,
857
+ children: t("retry")
858
+ })]
859
+ }),
860
+ state.failures.map((failure) => (0, react_jsx_runtime.jsxs)("div", {
861
+ className: ModelSelect_module_css_default.warning,
862
+ children: [(0, react_jsx_runtime.jsx)("span", { children: t("warning.groupLoad", {
863
+ name: failure.id === "deepseek-account" ? t("provider.account") : failure.name,
864
+ message: failure.message
865
+ }) }), (0, react_jsx_runtime.jsx)("button", {
866
+ type: "button",
867
+ className: ModelSelect_module_css_default.retry,
868
+ onClick: reload,
869
+ children: t("retry")
870
+ })]
871
+ }, failure.id)),
872
+ (0, react_jsx_runtime.jsx)("div", {
873
+ ref: groupsRef,
874
+ id: `${id}-models`,
875
+ className: clsx(ModelSelect_module_css_default.groups, "scrollable"),
876
+ role: "menu",
877
+ "aria-label": t("menu.model"),
878
+ hidden: displayGroups.length === 0,
879
+ children: renderHierarchy()
880
+ }),
881
+ state.status === "ready" && displayGroups.length === 0 && (0, react_jsx_runtime.jsx)("div", {
882
+ className: ModelSelect_module_css_default.empty,
883
+ role: "status",
884
+ children: t(choices.length === 0 ? "empty.models" : "search.empty")
885
+ }),
886
+ !searching && choices.length > 0 && react_jsx_runtime.jsxs("div", {
887
+ className: "dsh-mg-modebar",
888
+ children: [
889
+ react_jsx_runtime.jsx("span", { children: text("展示模式", "View") }),
890
+ react_jsx_runtime.jsxs("div", {
891
+ style: { display: "flex", gap: "6px" },
892
+ children: [
893
+ react_jsx_runtime.jsx("button", {
894
+ type: "button",
895
+ className: "dsh-mg-mode-btn",
896
+ "data-active": menuMode === "panel",
897
+ onClick: () => switchMode("panel"),
898
+ children: text("侧边面板", "Flyout"),
899
+ }),
900
+ react_jsx_runtime.jsx("button", {
901
+ type: "button",
902
+ className: "dsh-mg-mode-btn",
903
+ "data-active": menuMode === "inline",
904
+ onClick: () => switchMode("inline"),
905
+ children: text("原地折叠", "Inline"),
906
+ }),
907
+ ]
908
+ })
909
+ ]
910
+ })
911
+ ] }),
912
+ pane === "effort" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [state.error !== null && lastActionRef.current === "load" && (0, react_jsx_runtime.jsxs)("div", {
913
+ className: ModelSelect_module_css_default.error,
914
+ children: [(0, react_jsx_runtime.jsx)("span", { children: t("error.action", { message: state.error }) }), (0, react_jsx_runtime.jsx)("button", {
915
+ type: "button",
916
+ className: ModelSelect_module_css_default.retry,
917
+ onClick: reload,
918
+ children: t("action.reload")
919
+ })]
920
+ }), effortChoices.length === 0 ? (0, react_jsx_runtime.jsx)("div", {
921
+ className: ModelSelect_module_css_default.empty,
922
+ children: t("empty.efforts")
923
+ }) : effortChoices.map((level) => (0, react_jsx_runtime.jsxs)("button", {
924
+ ref: itemRef(),
925
+ type: "button",
926
+ role: "menuitemradio",
927
+ "aria-checked": effectiveEffort === level.effort,
928
+ className: clsx(ModelSelect_module_css_default.option, effectiveEffort === level.effort && ModelSelect_module_css_default.selected),
929
+ disabled: busy,
930
+ onClick: () => {
931
+ chooseEffort(level.effort);
932
+ },
933
+ children: [(0, react_jsx_runtime.jsx)("span", {
934
+ className: ModelSelect_module_css_default.optionCopy,
935
+ children: (0, react_jsx_runtime.jsx)("span", {
936
+ className: ModelSelect_module_css_default.modelName,
937
+ children: level.label
938
+ })
939
+ }), (0, react_jsx_runtime.jsx)("span", {
940
+ className: ModelSelect_module_css_default.check,
941
+ children: pending !== null && pending.provider === state.current?.provider && pending.model === state.current.model && pending.reasoningEffort === level.effort ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "ongoing" }) : effectiveEffort === level.effort ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, {}) : null
942
+ })]
943
+ }, level.key))] })
944
+ ]
945
+ }), document.body),
946
+ open && pane === "model" && menuMode === "panel" && activeFlyout !== null && (0, react_dom.createPortal)(
947
+ react_jsx_runtime.jsx(_deepseek_ai_dsh_client_ui_primitives.MenuSurface, {
948
+ ref: flyoutRef,
949
+ className: ModelSelect_module_css_default.menu,
950
+ style: {
951
+ left: activeFlyout.pos.left,
952
+ top: activeFlyout.pos.top,
953
+ width: activeFlyout.pos.width,
954
+ height: activeFlyout.pos.height,
955
+ position: "fixed",
956
+ margin: 0
957
+ },
958
+ onMouseEnter: clearFlyoutTimer,
959
+ onMouseLeave: () => scheduleCloseFlyout(200),
960
+ onMouseDown: (e) => {
961
+ if (e.target instanceof Element && e.target.closest("button") !== null) e.preventDefault();
962
+ },
963
+ children: react_jsx_runtime.jsx("div", {
964
+ className: clsx(ModelSelect_module_css_default.groups, "scrollable"),
965
+ children: react_jsx_runtime.jsx(_deepseek_ai_dsh_client_ui_primitives.MenuGroup, {
966
+ label: `${activeFlyout.entry.name} (${activeFlyout.entry.modelCount})`,
967
+ children: activeFlyout.entry.kind !== "multi" ? renderModels(activeFlyout.entry.group) : activeFlyout.entry.children.map((childGroup) => {
968
+ const name = childGroup.legacy ? text("原单密钥(兼容旧会话)", "Legacy single key (existing session)") : childGroup.name;
969
+ const isSubActive = activeSubFlyout?.key === childGroup.key;
970
+ return react_jsx_runtime.jsxs("button", {
971
+ type: "button",
972
+ className: clsx(ModelSelect_module_css_default.option, isSubActive && ModelSelect_module_css_default.optionActive),
973
+ "data-active": isSubActive ? "true" : "false",
974
+ onMouseEnter: (e) => {
975
+ clearFlyoutTimer();
976
+ triggerSubFlyout(childGroup, e.currentTarget);
977
+ },
978
+ onMouseLeave: () => scheduleCloseSubFlyout(200),
979
+ onClick: (e) => {
980
+ if (isSubActive) setActiveSubFlyout(null);
981
+ else triggerSubFlyout(childGroup, e.currentTarget);
982
+ },
983
+ children: [
984
+ react_jsx_runtime.jsx("span", { className: ModelSelect_module_css_default.modelName, children: name }),
985
+ childGroup.current && react_jsx_runtime.jsx("span", { className: "dsh-mg-currentBadge", children: text("当前", "Current") }),
986
+ react_jsx_runtime.jsx("span", { className: "dsh-mg-groupCount", children: childGroup.models.length }),
987
+ react_jsx_runtime.jsx(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, { className: ModelSelect_module_css_default.cellChevron, size: 12 }),
988
+ ]
989
+ }, childGroup.key);
990
+ })
991
+ })
992
+ })
993
+ }),
994
+ document.body
995
+ ),
996
+ open && pane === "model" && menuMode === "panel" && activeSubFlyout !== null && (0, react_dom.createPortal)(
997
+ react_jsx_runtime.jsx(_deepseek_ai_dsh_client_ui_primitives.MenuSurface, {
998
+ ref: subFlyoutRef,
999
+ className: ModelSelect_module_css_default.menu,
1000
+ style: {
1001
+ left: activeSubFlyout.pos.left,
1002
+ top: activeSubFlyout.pos.top,
1003
+ width: activeSubFlyout.pos.width,
1004
+ height: activeSubFlyout.pos.height,
1005
+ position: "fixed",
1006
+ margin: 0
1007
+ },
1008
+ onMouseEnter: clearFlyoutTimer,
1009
+ onMouseLeave: () => scheduleCloseSubFlyout(200),
1010
+ onMouseDown: (e) => {
1011
+ if (e.target instanceof Element && e.target.closest("button") !== null) e.preventDefault();
1012
+ },
1013
+ children: react_jsx_runtime.jsx("div", {
1014
+ className: clsx(ModelSelect_module_css_default.groups, "scrollable"),
1015
+ children: react_jsx_runtime.jsx(_deepseek_ai_dsh_client_ui_primitives.MenuGroup, {
1016
+ label: `${activeSubFlyout.childGroup.legacy ? text("原单密钥(兼容旧会话)", "Legacy single key (existing session)") : activeSubFlyout.childGroup.name} (${activeSubFlyout.childGroup.models.length})`,
1017
+ children: renderModels(activeSubFlyout.childGroup)
1018
+ })
1019
+ })
1020
+ }),
1021
+ document.body
1022
+ ),
1023
+ toast !== null && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
1024
+ text: toast.text,
1025
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
1026
+ anchor: rootRef.current?.closest("[data-composer-card]") ?? null,
1027
+ onDone: () => {
1028
+ setToast(null);
1029
+ }
1030
+ }, toast.seq)
1031
+ ]
1032
+ });
1033
+ }