dsh-model-groups 0.0.0-stage → 0.3.3
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/LICENSE +21 -0
- package/README.md +159 -2
- package/cordis.patch.yml +3 -0
- package/lib/client.js +1336 -0
- package/native/api-remotes.patch.json +17 -0
- package/native/llm-typert.host.patch.json +21 -0
- package/native/llm-typert.remote-client.patch.json +17 -0
- package/native/pi-ai.patch.json +85 -0
- package/native/settings-models.patch.json +149 -0
- package/package.json +64 -4
- package/scripts/build.mjs +22 -0
- package/scripts/native-patches.mjs +135 -0
- package/src/client.js +33 -0
- package/src/fallback.js +69 -0
- package/src/group-state.js +61 -0
- package/src/groups.css +280 -0
- package/src/hierarchy.js +113 -0
- package/src/index.js +4 -0
- package/src/model-select.js +1033 -0
|
@@ -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
|
+
}
|