@1outres/dsh-plugin-model-picker 0.0.0-stage → 0.5.0
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 +106 -2
- package/cordis.patch.yml +6 -0
- package/lib/client.js +1379 -0
- package/lib/index.js +22 -0
- package/package.json +56 -3
package/lib/client.js
ADDED
|
@@ -0,0 +1,1379 @@
|
|
|
1
|
+
/* Generated by scripts/build.mjs — do not edit. */
|
|
2
|
+
window.__ModuleLoader__.load({
|
|
3
|
+
id: "@1outres/dsh-plugin-model-picker",
|
|
4
|
+
factory: (require) => {
|
|
5
|
+
var module = { exports: {} };
|
|
6
|
+
var exports = module.exports;
|
|
7
|
+
"use strict";
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
10
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
11
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
16
|
+
var __copyProps = (to, from, except, desc) => {
|
|
17
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
|
+
for (let key of __getOwnPropNames(from))
|
|
19
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
20
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
25
|
+
|
|
26
|
+
// ../dsh-plugin-model-picker/src/client/index.tsx
|
|
27
|
+
var index_exports = {};
|
|
28
|
+
__export(index_exports, {
|
|
29
|
+
SEAT: () => SEAT,
|
|
30
|
+
SEAT_PRIORITY: () => SEAT_PRIORITY,
|
|
31
|
+
apply: () => apply,
|
|
32
|
+
inject: () => inject
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(index_exports);
|
|
35
|
+
|
|
36
|
+
// ../dsh-plugin-model-picker/src/client/config.ts
|
|
37
|
+
var CONFIG_NAMESPACE = "model-picker";
|
|
38
|
+
function refKey(provider, model) {
|
|
39
|
+
return JSON.stringify([provider, model]);
|
|
40
|
+
}
|
|
41
|
+
function normalizeRefs(value) {
|
|
42
|
+
if (!Array.isArray(value)) return [];
|
|
43
|
+
const seen = /* @__PURE__ */ new Set();
|
|
44
|
+
const refs = [];
|
|
45
|
+
for (const item of value) {
|
|
46
|
+
if (typeof item !== "object" || item === null) continue;
|
|
47
|
+
const { provider, model } = item;
|
|
48
|
+
if (typeof provider !== "string" || typeof model !== "string") continue;
|
|
49
|
+
if (provider.length === 0 || model.length === 0) continue;
|
|
50
|
+
const key = refKey(provider, model);
|
|
51
|
+
if (seen.has(key)) continue;
|
|
52
|
+
seen.add(key);
|
|
53
|
+
refs.push({ provider, model });
|
|
54
|
+
}
|
|
55
|
+
return refs;
|
|
56
|
+
}
|
|
57
|
+
function normalizeConfig(value) {
|
|
58
|
+
return { favorites: normalizeRefs(value?.favorites) };
|
|
59
|
+
}
|
|
60
|
+
function sameRefs(left, right) {
|
|
61
|
+
if (left.length !== right.length) return false;
|
|
62
|
+
return left.every(
|
|
63
|
+
(ref, index) => ref.provider === right[index]?.provider && ref.model === right[index]?.model
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ../dsh-plugin-model-picker/src/client/model.ts
|
|
68
|
+
var ACCOUNT_PROVIDER = "deepseek-account";
|
|
69
|
+
var OFFICIAL_PROVIDER = "deepseek-official";
|
|
70
|
+
var RECENT_VISIBLE = 5;
|
|
71
|
+
var RECENT_STORED = 20;
|
|
72
|
+
function orderProviders(groups) {
|
|
73
|
+
const rank = (id) => id === ACCOUNT_PROVIDER ? 0 : id === OFFICIAL_PROVIDER ? 1 : 2;
|
|
74
|
+
return [...groups].sort((left, right) => rank(left.id) - rank(right.id));
|
|
75
|
+
}
|
|
76
|
+
function providerName(group, accountName) {
|
|
77
|
+
return group.id === ACCOUNT_PROVIDER ? accountName : group.name;
|
|
78
|
+
}
|
|
79
|
+
function catalogEntries(groups, accountName) {
|
|
80
|
+
const entries = [];
|
|
81
|
+
for (const group of orderProviders(groups)) {
|
|
82
|
+
const provider = { id: group.id, name: providerName(group, accountName) };
|
|
83
|
+
for (const model of group.models) {
|
|
84
|
+
entries.push({ key: refKey(group.id, model.id), provider, model, order: entries.length });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return entries;
|
|
88
|
+
}
|
|
89
|
+
function tokenize(query) {
|
|
90
|
+
return query.trim().toLowerCase().split(/\s+/u).filter((token) => token.length > 0);
|
|
91
|
+
}
|
|
92
|
+
var WORD_CHAR = /[\p{L}\p{N}]/u;
|
|
93
|
+
function normalizeField(value) {
|
|
94
|
+
return value.trim().toLowerCase().replace(/\s+/gu, " ");
|
|
95
|
+
}
|
|
96
|
+
function termTier(field, term) {
|
|
97
|
+
if (field.startsWith(term)) return 1;
|
|
98
|
+
let index = field.indexOf(term);
|
|
99
|
+
if (index < 0) return void 0;
|
|
100
|
+
while (index > 0) {
|
|
101
|
+
if (!WORD_CHAR.test(field[index - 1] ?? "")) return 2;
|
|
102
|
+
index = field.indexOf(term, index + 1);
|
|
103
|
+
}
|
|
104
|
+
return 3;
|
|
105
|
+
}
|
|
106
|
+
function matchTier(entry, terms) {
|
|
107
|
+
if (terms.length === 0) return void 0;
|
|
108
|
+
const name = normalizeField(entry.model.name);
|
|
109
|
+
const id = normalizeField(entry.model.id);
|
|
110
|
+
const fields = [name, id, normalizeField(entry.provider.name), normalizeField(entry.provider.id)];
|
|
111
|
+
const whole = terms.join(" ");
|
|
112
|
+
if (whole === name || whole === id) return 0;
|
|
113
|
+
let worst = 1;
|
|
114
|
+
for (const term of terms) {
|
|
115
|
+
let best;
|
|
116
|
+
for (const field of fields) {
|
|
117
|
+
const tier = termTier(field, term);
|
|
118
|
+
if (tier !== void 0 && (best === void 0 || tier < best)) best = tier;
|
|
119
|
+
if (best === 1) break;
|
|
120
|
+
}
|
|
121
|
+
if (best === void 0) return void 0;
|
|
122
|
+
if (best > worst) worst = best;
|
|
123
|
+
}
|
|
124
|
+
return worst;
|
|
125
|
+
}
|
|
126
|
+
var LAST = Number.MAX_SAFE_INTEGER;
|
|
127
|
+
function searchEntries(entries, query, favorites, recent) {
|
|
128
|
+
const terms = tokenize(query);
|
|
129
|
+
if (terms.length === 0) return [];
|
|
130
|
+
const favorite = new Set(favorites.map((ref) => refKey(ref.provider, ref.model)));
|
|
131
|
+
const recency = /* @__PURE__ */ new Map();
|
|
132
|
+
recent.forEach((ref, index) => {
|
|
133
|
+
const key = refKey(ref.provider, ref.model);
|
|
134
|
+
if (!recency.has(key)) recency.set(key, index);
|
|
135
|
+
});
|
|
136
|
+
const scored = [];
|
|
137
|
+
for (const entry of entries) {
|
|
138
|
+
const tier = matchTier(entry, terms);
|
|
139
|
+
if (tier !== void 0) scored.push({ entry, tier });
|
|
140
|
+
}
|
|
141
|
+
scored.sort(
|
|
142
|
+
(left, right) => left.tier - right.tier || (favorite.has(left.entry.key) ? 0 : 1) - (favorite.has(right.entry.key) ? 0 : 1) || (recency.get(left.entry.key) ?? LAST) - (recency.get(right.entry.key) ?? LAST) || left.entry.order - right.entry.order
|
|
143
|
+
);
|
|
144
|
+
return scored.map((item) => item.entry);
|
|
145
|
+
}
|
|
146
|
+
function buildSections(input) {
|
|
147
|
+
const { entries, favorites, recent, query, labels } = input;
|
|
148
|
+
if (tokenize(query).length > 0) {
|
|
149
|
+
const results = searchEntries(entries, query, favorites, recent);
|
|
150
|
+
return results.length === 0 ? [] : [{ id: "results", kind: "results", label: void 0, entries: results }];
|
|
151
|
+
}
|
|
152
|
+
const sections = [];
|
|
153
|
+
const favorite = new Set(favorites.map((ref) => refKey(ref.provider, ref.model)));
|
|
154
|
+
const favoriteEntries = entries.filter((entry) => favorite.has(entry.key));
|
|
155
|
+
if (favoriteEntries.length > 0) {
|
|
156
|
+
sections.push({
|
|
157
|
+
id: "favorites",
|
|
158
|
+
kind: "favorites",
|
|
159
|
+
label: labels.favorites,
|
|
160
|
+
entries: favoriteEntries
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
const byKey = new Map(entries.map((entry) => [entry.key, entry]));
|
|
164
|
+
const recentEntries = [];
|
|
165
|
+
const seen = /* @__PURE__ */ new Set();
|
|
166
|
+
for (const ref of recent) {
|
|
167
|
+
if (recentEntries.length >= RECENT_VISIBLE) break;
|
|
168
|
+
const key = refKey(ref.provider, ref.model);
|
|
169
|
+
const entry = byKey.get(key);
|
|
170
|
+
if (entry === void 0 || favorite.has(key) || seen.has(key)) continue;
|
|
171
|
+
seen.add(key);
|
|
172
|
+
recentEntries.push(entry);
|
|
173
|
+
}
|
|
174
|
+
if (recentEntries.length > 0) {
|
|
175
|
+
sections.push({ id: "recent", kind: "recent", label: labels.recent, entries: recentEntries });
|
|
176
|
+
}
|
|
177
|
+
for (const entry of entries) {
|
|
178
|
+
const last = sections[sections.length - 1];
|
|
179
|
+
if (last?.kind === "provider" && last.id === `provider:${entry.provider.id}`) {
|
|
180
|
+
last.entries.push(entry);
|
|
181
|
+
} else {
|
|
182
|
+
sections.push({
|
|
183
|
+
id: `provider:${entry.provider.id}`,
|
|
184
|
+
kind: "provider",
|
|
185
|
+
label: entry.provider.name,
|
|
186
|
+
entries: [entry]
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return sections;
|
|
191
|
+
}
|
|
192
|
+
function isSelected(entry, selection) {
|
|
193
|
+
return selection !== null && selection !== void 0 && entry.provider.id === selection.provider && entry.model.id === selection.model;
|
|
194
|
+
}
|
|
195
|
+
function effectiveEffort(selection, reasoning) {
|
|
196
|
+
return selection?.reasoningEffort ?? reasoning?.defaultEffort;
|
|
197
|
+
}
|
|
198
|
+
function carriedEffort(effort, target) {
|
|
199
|
+
if (effort === void 0 || target === void 0) return void 0;
|
|
200
|
+
return target.efforts.some((level) => level.id === effort) ? effort : void 0;
|
|
201
|
+
}
|
|
202
|
+
function effortChoices(reasoning, defaultLabel) {
|
|
203
|
+
if (reasoning === void 0) return [];
|
|
204
|
+
return [
|
|
205
|
+
...reasoning.defaultEffort === void 0 ? [{ key: "provider-default", effort: void 0, label: defaultLabel }] : [],
|
|
206
|
+
...reasoning.efforts.map((level) => ({
|
|
207
|
+
key: `effort:${level.id}`,
|
|
208
|
+
effort: level.id,
|
|
209
|
+
label: level.name
|
|
210
|
+
}))
|
|
211
|
+
];
|
|
212
|
+
}
|
|
213
|
+
function effortCaption(input) {
|
|
214
|
+
const { entry, selection, retainedEffort, defaultLabel } = input;
|
|
215
|
+
if (entry === void 0) return void 0;
|
|
216
|
+
const reasoning = entry.model.reasoning;
|
|
217
|
+
if (reasoning === void 0) return retainedEffort;
|
|
218
|
+
const effort = effectiveEffort(selection, reasoning);
|
|
219
|
+
if (effort === void 0) return defaultLabel;
|
|
220
|
+
return reasoning.efforts.find((level) => level.id === effort)?.name ?? effort;
|
|
221
|
+
}
|
|
222
|
+
function toggleRef(list, ref) {
|
|
223
|
+
const key = refKey(ref.provider, ref.model);
|
|
224
|
+
const kept = list.filter((item) => refKey(item.provider, item.model) !== key);
|
|
225
|
+
return kept.length === list.length ? [...list, { provider: ref.provider, model: ref.model }] : kept;
|
|
226
|
+
}
|
|
227
|
+
function pushRecent(list, ref, limit = RECENT_STORED) {
|
|
228
|
+
const key = refKey(ref.provider, ref.model);
|
|
229
|
+
return [
|
|
230
|
+
{ provider: ref.provider, model: ref.model },
|
|
231
|
+
...list.filter((item) => refKey(item.provider, item.model) !== key)
|
|
232
|
+
].slice(0, limit);
|
|
233
|
+
}
|
|
234
|
+
function stepIndex(current, delta, count, wrap) {
|
|
235
|
+
if (count <= 0) return -1;
|
|
236
|
+
if (current < 0) return delta >= 0 ? 0 : count - 1;
|
|
237
|
+
const next = current + delta;
|
|
238
|
+
if (wrap) return (next % count + count) % count;
|
|
239
|
+
return Math.min(Math.max(next, 0), count - 1);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ../dsh-plugin-model-picker/src/client/favorites.ts
|
|
243
|
+
function createFavoritesStore(form) {
|
|
244
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
245
|
+
const read = () => normalizeConfig(form.getSnapshot().value).favorites;
|
|
246
|
+
let confirmed = read();
|
|
247
|
+
let optimistic;
|
|
248
|
+
let snapshot = confirmed;
|
|
249
|
+
let generation = 0;
|
|
250
|
+
const publish = () => {
|
|
251
|
+
const next = optimistic ?? confirmed;
|
|
252
|
+
if (next === snapshot) return;
|
|
253
|
+
snapshot = next;
|
|
254
|
+
for (const listener of [...listeners]) listener();
|
|
255
|
+
};
|
|
256
|
+
const refresh = () => {
|
|
257
|
+
const next = read();
|
|
258
|
+
if (!sameRefs(next, confirmed)) confirmed = next;
|
|
259
|
+
publish();
|
|
260
|
+
};
|
|
261
|
+
return {
|
|
262
|
+
getSnapshot: () => snapshot,
|
|
263
|
+
subscribe(listener) {
|
|
264
|
+
listeners.add(listener);
|
|
265
|
+
return () => {
|
|
266
|
+
listeners.delete(listener);
|
|
267
|
+
};
|
|
268
|
+
},
|
|
269
|
+
connect() {
|
|
270
|
+
refresh();
|
|
271
|
+
return form.subscribe(refresh);
|
|
272
|
+
},
|
|
273
|
+
async toggle(ref) {
|
|
274
|
+
const state = form.getSnapshot();
|
|
275
|
+
if (state.status !== "ready" || !state.writable) return false;
|
|
276
|
+
const next = toggleRef(optimistic ?? confirmed, ref);
|
|
277
|
+
const ticket = ++generation;
|
|
278
|
+
optimistic = next;
|
|
279
|
+
publish();
|
|
280
|
+
let accepted = false;
|
|
281
|
+
try {
|
|
282
|
+
accepted = await form.set("favorites", next);
|
|
283
|
+
} catch {
|
|
284
|
+
accepted = false;
|
|
285
|
+
}
|
|
286
|
+
if (ticket === generation) {
|
|
287
|
+
optimistic = void 0;
|
|
288
|
+
if (accepted && !sameRefs(next, confirmed)) confirmed = next;
|
|
289
|
+
publish();
|
|
290
|
+
}
|
|
291
|
+
return accepted;
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ../dsh-plugin-model-picker/src/client/i18n.ts
|
|
297
|
+
var en = {
|
|
298
|
+
"provider.account": "DeepSeek Account",
|
|
299
|
+
"trigger.fallback": "Select model",
|
|
300
|
+
"trigger.loading": "Loading models\u2026",
|
|
301
|
+
"trigger.selectAria": "Select model",
|
|
302
|
+
"trigger.aria": "Select model, current {model}",
|
|
303
|
+
"trigger.ariaEffort": "Select model, current {model}, reasoning effort {effort}",
|
|
304
|
+
"menu.aria": "Model and reasoning effort",
|
|
305
|
+
"list.aria": "Models",
|
|
306
|
+
"section.favorites": "Favorites",
|
|
307
|
+
"section.recent": "Recent",
|
|
308
|
+
"section.results": "Search results",
|
|
309
|
+
"search.placeholder": "Search models\u2026",
|
|
310
|
+
"search.clear": "Clear search",
|
|
311
|
+
"search.empty": "No matching models.",
|
|
312
|
+
"empty.models": "No models available.",
|
|
313
|
+
"status.loading": "Refreshing model list\u2026",
|
|
314
|
+
"effort.label": "Effort",
|
|
315
|
+
"effort.aria": "Reasoning effort",
|
|
316
|
+
"effort.default": "Default",
|
|
317
|
+
"favorite.add": "Add to favorites",
|
|
318
|
+
"favorite.remove": "Remove from favorites",
|
|
319
|
+
"error.action": "Model operation failed: {message}",
|
|
320
|
+
"error.sessionInUse": "This session is already in use, possibly by another running DSH instance (such as dsh web or the desktop app). Quit other running DSH instances and try again.",
|
|
321
|
+
"error.favorites": "Favorites could not be saved.",
|
|
322
|
+
"warning.groupLoad": "{name} failed to load: {message}",
|
|
323
|
+
retry: "Retry"
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// ../dsh-plugin-model-picker/src/client/Picker.tsx
|
|
327
|
+
var import_react = require("react");
|
|
328
|
+
var import_react_dom = require("react-dom");
|
|
329
|
+
|
|
330
|
+
// ../dsh-plugin-model-picker/src/client/icons.tsx
|
|
331
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
332
|
+
function IconModels({ size = 16, className }) {
|
|
333
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
334
|
+
"svg",
|
|
335
|
+
{
|
|
336
|
+
width: size,
|
|
337
|
+
height: size,
|
|
338
|
+
className,
|
|
339
|
+
viewBox: "0 0 16 16",
|
|
340
|
+
fill: "none",
|
|
341
|
+
"aria-hidden": "true",
|
|
342
|
+
strokeWidth: 1,
|
|
343
|
+
children: [
|
|
344
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
345
|
+
"path",
|
|
346
|
+
{
|
|
347
|
+
d: "M7.8667 0.349609C8.96906 0.349634 10.0601 0.481272 11.0317 0.735352C11.9973 0.987845 12.8453 1.362 13.4644 1.84766C14.0744 2.32629 14.507 2.95539 14.5161 3.69336H14.5171V8.53516C14.0843 8.32076 13.6108 8.17679 13.1108 8.11816C13.1831 7.96848 13.2162 7.82856 13.2163 7.70312V5.76758C12.6269 6.16618 11.8739 6.47995 11.0317 6.7002C10.0602 6.95423 8.96896 7.08494 7.8667 7.08496C6.76461 7.08493 5.67411 6.95415 4.70264 6.7002C3.85994 6.48006 3.10694 6.1662 2.51709 5.76758V7.70312L2.521 7.78418C2.56374 8.19554 2.93361 8.74414 3.91357 9.23145C4.9281 9.73585 6.35004 10.0371 7.8667 10.0371C8.26373 10.0371 8.6543 10.0141 9.03271 9.97461C8.75596 10.3799 8.54664 10.8349 8.42041 11.3232C8.23666 11.3313 8.0518 11.3369 7.8667 11.3369C6.20108 11.3369 4.57025 11.01 3.33447 10.3955C3.04163 10.2499 2.76658 10.0836 2.51709 9.90039V11.6738C2.51728 12.1379 2.88589 12.7556 3.92236 13.292C4.93457 13.8157 6.35342 14.1289 7.8667 14.1289C8.12318 14.1289 8.37694 14.1161 8.62646 14.0986C8.82021 14.5535 9.08999 14.9682 9.41943 15.3271C8.91285 15.3934 8.39149 15.4287 7.8667 15.4287C6.19761 15.4287 4.56379 15.0869 3.32568 14.4463C2.11244 13.8185 1.21649 12.8562 1.21631 11.6738V3.76367C1.21595 3.74853 1.21438 3.733 1.21436 3.71777C1.21436 2.96917 1.65103 2.33053 2.26807 1.84668C2.88747 1.36112 3.73675 0.987685 4.70264 0.735352C5.67413 0.481376 6.76457 0.349636 7.8667 0.349609ZM7.8667 1.65039C6.86269 1.65042 5.88326 1.77028 5.03076 1.99316C4.17183 2.2176 3.50421 2.52956 3.06982 2.87012C2.65043 3.19909 2.52622 3.48898 2.51709 3.69336V3.74414C2.52719 3.94845 2.65185 4.23772 3.06982 4.56543C3.50425 4.90601 4.17172 5.21795 5.03076 5.44238C5.88326 5.66527 6.8627 5.78513 7.8667 5.78516C8.8707 5.78513 9.85015 5.66525 10.7026 5.44238C11.5611 5.21787 12.2286 4.9049 12.6626 4.56445C13.0982 4.22252 13.2163 3.9231 13.2163 3.71777L13.2104 3.63574C13.1818 3.43623 13.044 3.16941 12.6626 2.87012C12.2286 2.52957 11.5614 2.21773 10.7026 1.99316C9.85009 1.77025 8.8708 1.65041 7.8667 1.65039Z",
|
|
348
|
+
fill: "currentColor"
|
|
349
|
+
}
|
|
350
|
+
),
|
|
351
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
352
|
+
"path",
|
|
353
|
+
{
|
|
354
|
+
d: "M12.8936 10.0361L13.2061 10.5566C13.2296 10.5959 13.2651 10.6562 13.3027 10.707C13.3469 10.7666 13.4148 10.8431 13.5195 10.9023C13.6244 10.9617 13.725 10.9801 13.7988 10.9873C13.8619 10.9934 13.9318 10.9932 13.9775 10.9932H14.6162L14.8896 11.4502L14.5947 11.9443C14.5698 11.9859 14.5312 12.0483 14.5029 12.1084C14.4781 12.1611 14.4514 12.2312 14.4395 12.3164L14.4326 12.4072L14.4395 12.4971C14.4514 12.5825 14.4781 12.6532 14.5029 12.7061C14.5312 12.7661 14.5689 12.8287 14.5938 12.8701L14.8896 13.3633L14.6162 13.8213H13.9775C13.9318 13.8213 13.8619 13.821 13.7988 13.8271C13.7433 13.8326 13.6728 13.8442 13.5967 13.875L13.5195 13.9121C13.4148 13.9714 13.3469 14.0478 13.3027 14.1074C13.265 14.1583 13.2296 14.2186 13.2061 14.2578L12.8936 14.7783H12.3115L11.999 14.2578C11.9755 14.2186 11.9401 14.1583 11.9023 14.1074C11.8693 14.0628 11.823 14.0083 11.7578 13.959L11.6855 13.9121L11.6074 13.875C11.5316 13.8445 11.4615 13.8325 11.4062 13.8271C11.3432 13.821 11.2733 13.8213 11.2275 13.8213H10.5889L10.3135 13.3633L10.6104 12.8701C10.6352 12.8287 10.6739 12.7661 10.7021 12.7061C10.7352 12.6357 10.7724 12.534 10.7725 12.4072C10.7724 12.2804 10.7352 12.1788 10.7021 12.1084C10.6739 12.0483 10.6353 11.9859 10.6104 11.9443L10.3135 11.4502L10.5889 10.9932H11.2275C11.2733 10.9932 11.3432 10.9934 11.4062 10.9873C11.4801 10.9801 11.5808 10.9616 11.6855 10.9023C11.7903 10.843 11.8582 10.7666 11.9023 10.707C11.94 10.6562 11.9755 10.5959 11.999 10.5566L12.3115 10.0361H12.8936Z",
|
|
355
|
+
stroke: "currentColor",
|
|
356
|
+
strokeMiterlimit: "10"
|
|
357
|
+
}
|
|
358
|
+
)
|
|
359
|
+
]
|
|
360
|
+
}
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
function IconChevronDown({ size = 14, className }) {
|
|
364
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
365
|
+
"svg",
|
|
366
|
+
{
|
|
367
|
+
width: size,
|
|
368
|
+
height: size,
|
|
369
|
+
className,
|
|
370
|
+
viewBox: "0 0 16 16",
|
|
371
|
+
fill: "none",
|
|
372
|
+
"aria-hidden": "true",
|
|
373
|
+
strokeWidth: 1,
|
|
374
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
375
|
+
"path",
|
|
376
|
+
{
|
|
377
|
+
d: "M4 6L7.29289 9.29289C7.68342 9.68342 8.31658 9.68342 8.70711 9.29289L12 6",
|
|
378
|
+
stroke: "currentColor"
|
|
379
|
+
}
|
|
380
|
+
)
|
|
381
|
+
}
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
function IconCheck({ size = 14, className }) {
|
|
385
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
386
|
+
"svg",
|
|
387
|
+
{
|
|
388
|
+
width: size,
|
|
389
|
+
height: size,
|
|
390
|
+
className,
|
|
391
|
+
viewBox: "0 0 16 16",
|
|
392
|
+
fill: "none",
|
|
393
|
+
"aria-hidden": "true",
|
|
394
|
+
strokeWidth: 1,
|
|
395
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
396
|
+
"path",
|
|
397
|
+
{
|
|
398
|
+
d: "M2.25 8.5L5.49732 11.7473C5.90519 12.1552 6.57263 12.1344 6.95426 11.7018L13.75 4",
|
|
399
|
+
stroke: "currentColor"
|
|
400
|
+
}
|
|
401
|
+
)
|
|
402
|
+
}
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
function IconClose({ size = 14, className }) {
|
|
406
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
407
|
+
"svg",
|
|
408
|
+
{
|
|
409
|
+
width: size,
|
|
410
|
+
height: size,
|
|
411
|
+
className,
|
|
412
|
+
viewBox: "0 0 16 16",
|
|
413
|
+
fill: "none",
|
|
414
|
+
"aria-hidden": "true",
|
|
415
|
+
strokeWidth: 1,
|
|
416
|
+
children: [
|
|
417
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3.5 3.5L12.5 12.5", stroke: "currentColor" }),
|
|
418
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12.5 3.5L3.5 12.5", stroke: "currentColor" })
|
|
419
|
+
]
|
|
420
|
+
}
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
function IconWarning({ size = 16, className }) {
|
|
424
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
425
|
+
"svg",
|
|
426
|
+
{
|
|
427
|
+
width: size,
|
|
428
|
+
height: size,
|
|
429
|
+
className,
|
|
430
|
+
viewBox: "0 0 16 16",
|
|
431
|
+
fill: "none",
|
|
432
|
+
"aria-hidden": "true",
|
|
433
|
+
strokeWidth: 1,
|
|
434
|
+
children: [
|
|
435
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
436
|
+
"path",
|
|
437
|
+
{
|
|
438
|
+
d: "M8 14.5C11.5899 14.5 14.5 11.5899 14.5 8C14.5 4.41015 11.5899 1.5 8 1.5C4.41015 1.5 1.5 4.41015 1.5 8C1.5 11.5899 4.41015 14.5 8 14.5Z",
|
|
439
|
+
stroke: "currentColor"
|
|
440
|
+
}
|
|
441
|
+
),
|
|
442
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 4.29199V9.79199", stroke: "currentColor" }),
|
|
443
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 10.708V11.708", stroke: "currentColor" })
|
|
444
|
+
]
|
|
445
|
+
}
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
var STAR = "M8 1.9L9.79 5.53L13.8 6.11L10.9 8.94L11.58 12.93L8 11.05L4.42 12.93L5.1 8.94L2.2 6.11L6.21 5.53Z";
|
|
449
|
+
function IconStar({ size = 14, className, filled }) {
|
|
450
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
451
|
+
"svg",
|
|
452
|
+
{
|
|
453
|
+
width: size,
|
|
454
|
+
height: size,
|
|
455
|
+
className,
|
|
456
|
+
viewBox: "0 0 16 16",
|
|
457
|
+
fill: "none",
|
|
458
|
+
"aria-hidden": "true",
|
|
459
|
+
strokeWidth: 1,
|
|
460
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
461
|
+
"path",
|
|
462
|
+
{
|
|
463
|
+
d: STAR,
|
|
464
|
+
stroke: "currentColor",
|
|
465
|
+
strokeLinejoin: "round",
|
|
466
|
+
fill: filled ? "currentColor" : "none"
|
|
467
|
+
}
|
|
468
|
+
)
|
|
469
|
+
}
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
function Spinner({ size = 14 }) {
|
|
473
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
474
|
+
"svg",
|
|
475
|
+
{
|
|
476
|
+
className: "dsh-mp-spinner",
|
|
477
|
+
"data-state": "ongoing",
|
|
478
|
+
width: size,
|
|
479
|
+
height: size,
|
|
480
|
+
viewBox: "0 0 24 24",
|
|
481
|
+
"aria-hidden": "true",
|
|
482
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { className: "dsh-mp-spinner-motion", children: [
|
|
483
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { className: "dsh-mp-spinner-track", cx: "12", cy: "12", r: "9.5" }),
|
|
484
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { className: "dsh-mp-spinner-arc", cx: "12", cy: "12", r: "9.5" })
|
|
485
|
+
] })
|
|
486
|
+
}
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// ../dsh-plugin-model-picker/src/client/Picker.tsx
|
|
491
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
492
|
+
var MEASURE_STYLE = { visibility: "hidden", left: 0, top: 0 };
|
|
493
|
+
var MARGIN = 12;
|
|
494
|
+
var GAP = 8;
|
|
495
|
+
var ROW_HEIGHT = 34;
|
|
496
|
+
var TOAST_HOLD_MS = 3e3;
|
|
497
|
+
var TOAST_FADE_MS = 1e3;
|
|
498
|
+
function classes(...names) {
|
|
499
|
+
return names.filter((name) => typeof name === "string" && name.length > 0).join(" ");
|
|
500
|
+
}
|
|
501
|
+
function refOf(entry) {
|
|
502
|
+
return { provider: entry.provider.id, model: entry.model.id };
|
|
503
|
+
}
|
|
504
|
+
function sameName(left, right) {
|
|
505
|
+
return left.trim().toLowerCase() === right.trim().toLowerCase();
|
|
506
|
+
}
|
|
507
|
+
function useObservable(observable) {
|
|
508
|
+
const subscribe = (0, import_react.useCallback)(
|
|
509
|
+
(listener) => observable.subscribe(listener),
|
|
510
|
+
[observable]
|
|
511
|
+
);
|
|
512
|
+
const getSnapshot = (0, import_react.useCallback)(() => observable.getSnapshot(), [observable]);
|
|
513
|
+
return (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
514
|
+
}
|
|
515
|
+
function PickerToast(props) {
|
|
516
|
+
const { text, anchor, onDone } = props;
|
|
517
|
+
const done = (0, import_react.useRef)(onDone);
|
|
518
|
+
done.current = onDone;
|
|
519
|
+
const [left, setLeft] = (0, import_react.useState)(null);
|
|
520
|
+
(0, import_react.useEffect)(() => {
|
|
521
|
+
const timer = setTimeout(() => done.current(), TOAST_HOLD_MS + TOAST_FADE_MS);
|
|
522
|
+
return () => {
|
|
523
|
+
clearTimeout(timer);
|
|
524
|
+
};
|
|
525
|
+
}, []);
|
|
526
|
+
(0, import_react.useLayoutEffect)(() => {
|
|
527
|
+
if (anchor === null) return;
|
|
528
|
+
const place = () => {
|
|
529
|
+
const rect = anchor.getBoundingClientRect();
|
|
530
|
+
setLeft(rect.left + rect.width / 2);
|
|
531
|
+
};
|
|
532
|
+
place();
|
|
533
|
+
window.addEventListener("resize", place);
|
|
534
|
+
return () => {
|
|
535
|
+
window.removeEventListener("resize", place);
|
|
536
|
+
};
|
|
537
|
+
}, [anchor]);
|
|
538
|
+
const style = {
|
|
539
|
+
...left === null ? {} : { left },
|
|
540
|
+
"--dsh-mp-toast-hold": `${TOAST_HOLD_MS}ms`
|
|
541
|
+
};
|
|
542
|
+
return (0, import_react_dom.createPortal)(
|
|
543
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-mp-toast", role: "alert", style, children: [
|
|
544
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-mp-toast-icon", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IconWarning, {}) }),
|
|
545
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-mp-toast-text", children: text })
|
|
546
|
+
] }),
|
|
547
|
+
document.body
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
function ModelPicker(props) {
|
|
551
|
+
const { locked, available, directory, load, select, favorites, recent, t } = props;
|
|
552
|
+
const state = useObservable(directory);
|
|
553
|
+
const favoriteRefs = useObservable(favorites);
|
|
554
|
+
const recentRefs = useObservable(recent);
|
|
555
|
+
const [open, setOpen] = (0, import_react.useState)(false);
|
|
556
|
+
const [query, setQuery] = (0, import_react.useState)("");
|
|
557
|
+
const [highlightId, setHighlightId] = (0, import_react.useState)(null);
|
|
558
|
+
const [preferFirst, setPreferFirst] = (0, import_react.useState)(false);
|
|
559
|
+
const [selectionFocus, setSelectionFocus] = (0, import_react.useState)(false);
|
|
560
|
+
const [menuPos, setMenuPos] = (0, import_react.useState)(null);
|
|
561
|
+
const [toast, setToast] = (0, import_react.useState)(null);
|
|
562
|
+
const toastSeq = (0, import_react.useRef)(0);
|
|
563
|
+
const lastAction = (0, import_react.useRef)("load");
|
|
564
|
+
const focusSearchOnPlace = (0, import_react.useRef)(false);
|
|
565
|
+
const rootRef = (0, import_react.useRef)(null);
|
|
566
|
+
const triggerRef = (0, import_react.useRef)(null);
|
|
567
|
+
const menuRef = (0, import_react.useRef)(null);
|
|
568
|
+
const searchRef = (0, import_react.useRef)(null);
|
|
569
|
+
const listRef = (0, import_react.useRef)(null);
|
|
570
|
+
const footerRef = (0, import_react.useRef)(null);
|
|
571
|
+
const rowRefs = (0, import_react.useRef)([]);
|
|
572
|
+
const chipRefs = (0, import_react.useRef)([]);
|
|
573
|
+
const id = (0, import_react.useId)();
|
|
574
|
+
const accountName = t("provider.account");
|
|
575
|
+
const defaultLabel = t("effort.default");
|
|
576
|
+
const favoritesLabel = t("section.favorites");
|
|
577
|
+
const recentLabel = t("section.recent");
|
|
578
|
+
const entries = (0, import_react.useMemo)(
|
|
579
|
+
() => catalogEntries(state.groups, accountName),
|
|
580
|
+
[state.groups, accountName]
|
|
581
|
+
);
|
|
582
|
+
const sections = (0, import_react.useMemo)(
|
|
583
|
+
() => buildSections({
|
|
584
|
+
entries,
|
|
585
|
+
favorites: favoriteRefs,
|
|
586
|
+
recent: recentRefs,
|
|
587
|
+
query,
|
|
588
|
+
labels: { favorites: favoritesLabel, recent: recentLabel }
|
|
589
|
+
}),
|
|
590
|
+
[entries, favoriteRefs, recentRefs, query, favoritesLabel, recentLabel]
|
|
591
|
+
);
|
|
592
|
+
const rows = (0, import_react.useMemo)(
|
|
593
|
+
() => sections.flatMap(
|
|
594
|
+
(section) => section.entries.map((entry) => ({
|
|
595
|
+
id: `${section.id}\0${entry.key}`,
|
|
596
|
+
entry,
|
|
597
|
+
showProvider: section.kind !== "provider"
|
|
598
|
+
}))
|
|
599
|
+
),
|
|
600
|
+
[sections]
|
|
601
|
+
);
|
|
602
|
+
const favoriteKeys = (0, import_react.useMemo)(
|
|
603
|
+
() => new Set(favoriteRefs.map((ref) => refKey(ref.provider, ref.model))),
|
|
604
|
+
[favoriteRefs]
|
|
605
|
+
);
|
|
606
|
+
const current = state.current;
|
|
607
|
+
const currentEntry = entries.find((entry) => isSelected(entry, current));
|
|
608
|
+
const reasoning = currentEntry?.model.reasoning;
|
|
609
|
+
const effective = effectiveEffort(current, reasoning);
|
|
610
|
+
const caption = effortCaption({
|
|
611
|
+
entry: currentEntry,
|
|
612
|
+
selection: current,
|
|
613
|
+
retainedEffort: state.retainedEffort,
|
|
614
|
+
defaultLabel
|
|
615
|
+
});
|
|
616
|
+
const choices = (0, import_react.useMemo)(() => effortChoices(reasoning, defaultLabel), [reasoning, defaultLabel]);
|
|
617
|
+
const pending = state.pending;
|
|
618
|
+
const busy = pending !== null;
|
|
619
|
+
const highlighted = highlightId === null ? -1 : rows.findIndex((row) => row.id === highlightId);
|
|
620
|
+
const currentRow = rows.findIndex((row) => isSelected(row.entry, current));
|
|
621
|
+
const activeIndex = rows.length === 0 ? -1 : highlighted >= 0 ? highlighted : preferFirst ? 0 : Math.max(0, currentRow);
|
|
622
|
+
const showToast = (text) => {
|
|
623
|
+
toastSeq.current += 1;
|
|
624
|
+
setToast({ seq: toastSeq.current, text });
|
|
625
|
+
};
|
|
626
|
+
const reload = () => {
|
|
627
|
+
lastAction.current = "load";
|
|
628
|
+
load();
|
|
629
|
+
};
|
|
630
|
+
const close = (restoreFocus = false) => {
|
|
631
|
+
setOpen(false);
|
|
632
|
+
if (restoreFocus) {
|
|
633
|
+
queueMicrotask(() => {
|
|
634
|
+
triggerRef.current?.focus();
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
const closeAfterSelection = () => {
|
|
639
|
+
setSelectionFocus(true);
|
|
640
|
+
close(true);
|
|
641
|
+
};
|
|
642
|
+
const show = () => {
|
|
643
|
+
setSelectionFocus(false);
|
|
644
|
+
setQuery("");
|
|
645
|
+
setHighlightId(null);
|
|
646
|
+
setPreferFirst(false);
|
|
647
|
+
focusSearchOnPlace.current = true;
|
|
648
|
+
setOpen(true);
|
|
649
|
+
reload();
|
|
650
|
+
};
|
|
651
|
+
const changeQuery = (next) => {
|
|
652
|
+
setQuery(next);
|
|
653
|
+
setHighlightId(null);
|
|
654
|
+
setPreferFirst(true);
|
|
655
|
+
};
|
|
656
|
+
const highlight = (index) => {
|
|
657
|
+
const row = rows[index];
|
|
658
|
+
if (row !== void 0 && row.id !== highlightId) setHighlightId(row.id);
|
|
659
|
+
};
|
|
660
|
+
const submit = (selection, record) => {
|
|
661
|
+
lastAction.current = "select";
|
|
662
|
+
setSelectionFocus(true);
|
|
663
|
+
triggerRef.current?.focus();
|
|
664
|
+
select(selection).then(
|
|
665
|
+
(result) => {
|
|
666
|
+
if (result === void 0) return;
|
|
667
|
+
if (result.ok) {
|
|
668
|
+
if (record !== void 0) recent.push(record);
|
|
669
|
+
if (rootRef.current !== null) closeAfterSelection();
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
const { error } = result;
|
|
673
|
+
showToast(
|
|
674
|
+
error.code === "session/writer-held" ? t("error.sessionInUse") : t("error.action", { message: `${error.code}: ${error.message}` })
|
|
675
|
+
);
|
|
676
|
+
},
|
|
677
|
+
(error) => {
|
|
678
|
+
showToast(t("error.action", { message: String(error) }));
|
|
679
|
+
}
|
|
680
|
+
);
|
|
681
|
+
};
|
|
682
|
+
const choose = (entry) => {
|
|
683
|
+
if (isSelected(entry, current)) {
|
|
684
|
+
closeAfterSelection();
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
const effort = carriedEffort(effective, entry.model.reasoning);
|
|
688
|
+
submit(
|
|
689
|
+
{
|
|
690
|
+
provider: entry.provider.id,
|
|
691
|
+
model: entry.model.id,
|
|
692
|
+
...effort === void 0 ? {} : { reasoningEffort: effort }
|
|
693
|
+
},
|
|
694
|
+
refOf(entry)
|
|
695
|
+
);
|
|
696
|
+
};
|
|
697
|
+
const chooseEffort = (choice) => {
|
|
698
|
+
if (current === null) return;
|
|
699
|
+
if (effective === choice.effort) {
|
|
700
|
+
closeAfterSelection();
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
submit({
|
|
704
|
+
provider: current.provider,
|
|
705
|
+
model: current.model,
|
|
706
|
+
...choice.effort === void 0 ? {} : { reasoningEffort: choice.effort }
|
|
707
|
+
});
|
|
708
|
+
};
|
|
709
|
+
const toggleFavorite = (entry) => {
|
|
710
|
+
favorites.toggle(refOf(entry)).then(
|
|
711
|
+
(accepted) => {
|
|
712
|
+
if (!accepted) showToast(t("error.favorites"));
|
|
713
|
+
},
|
|
714
|
+
() => {
|
|
715
|
+
showToast(t("error.favorites"));
|
|
716
|
+
}
|
|
717
|
+
);
|
|
718
|
+
};
|
|
719
|
+
const focusEffort = () => {
|
|
720
|
+
const chips = chipRefs.current.filter((chip) => chip !== null);
|
|
721
|
+
const checked = chips.find((chip) => chip.getAttribute("aria-pressed") === "true");
|
|
722
|
+
(checked ?? chips[0])?.focus();
|
|
723
|
+
};
|
|
724
|
+
(0, import_react.useEffect)(() => {
|
|
725
|
+
if (!open) return;
|
|
726
|
+
const onPointer = (event) => {
|
|
727
|
+
const target = event.target;
|
|
728
|
+
if (!(target instanceof Node)) return;
|
|
729
|
+
if (rootRef.current?.contains(target) === true) return;
|
|
730
|
+
if (menuRef.current?.contains(target) === true) return;
|
|
731
|
+
setOpen(false);
|
|
732
|
+
};
|
|
733
|
+
document.addEventListener("mousedown", onPointer);
|
|
734
|
+
return () => {
|
|
735
|
+
document.removeEventListener("mousedown", onPointer);
|
|
736
|
+
};
|
|
737
|
+
}, [open]);
|
|
738
|
+
(0, import_react.useEffect)(() => {
|
|
739
|
+
if (locked && open) setOpen(false);
|
|
740
|
+
}, [locked, open]);
|
|
741
|
+
const placed = menuPos !== null;
|
|
742
|
+
(0, import_react.useLayoutEffect)(() => {
|
|
743
|
+
if (!open || !placed || !focusSearchOnPlace.current) return;
|
|
744
|
+
focusSearchOnPlace.current = false;
|
|
745
|
+
const focusSearch = () => {
|
|
746
|
+
const input = searchRef.current;
|
|
747
|
+
if (input !== null && document.activeElement !== input) input.focus({ preventScroll: true });
|
|
748
|
+
};
|
|
749
|
+
focusSearch();
|
|
750
|
+
if (document.activeElement === searchRef.current) return;
|
|
751
|
+
const frame = requestAnimationFrame(focusSearch);
|
|
752
|
+
return () => {
|
|
753
|
+
cancelAnimationFrame(frame);
|
|
754
|
+
};
|
|
755
|
+
}, [open, placed]);
|
|
756
|
+
(0, import_react.useLayoutEffect)(() => {
|
|
757
|
+
if (!available || !open) {
|
|
758
|
+
setMenuPos(null);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
const place = () => {
|
|
762
|
+
const rect = triggerRef.current?.getBoundingClientRect();
|
|
763
|
+
if (rect === void 0) return;
|
|
764
|
+
const width = menuRef.current?.offsetWidth ?? 0;
|
|
765
|
+
const height = menuRef.current?.offsetHeight ?? 0;
|
|
766
|
+
let left = rect.right - width;
|
|
767
|
+
let top = rect.top - GAP - height;
|
|
768
|
+
if (width > 0) left = Math.min(Math.max(left, MARGIN), window.innerWidth - width - MARGIN);
|
|
769
|
+
if (height > 0) top = Math.min(Math.max(top, MARGIN), window.innerHeight - height - MARGIN);
|
|
770
|
+
setMenuPos(
|
|
771
|
+
(previous) => previous !== null && previous.left === left && previous.top === top ? previous : { left, top }
|
|
772
|
+
);
|
|
773
|
+
};
|
|
774
|
+
place();
|
|
775
|
+
window.addEventListener("scroll", place, true);
|
|
776
|
+
window.addEventListener("resize", place);
|
|
777
|
+
const menu = menuRef.current;
|
|
778
|
+
const observer = typeof ResizeObserver !== "undefined" && menu !== null ? new ResizeObserver(place) : null;
|
|
779
|
+
if (menu !== null) observer?.observe(menu);
|
|
780
|
+
return () => {
|
|
781
|
+
observer?.disconnect();
|
|
782
|
+
window.removeEventListener("scroll", place, true);
|
|
783
|
+
window.removeEventListener("resize", place);
|
|
784
|
+
};
|
|
785
|
+
}, [available, open, state, sections, choices]);
|
|
786
|
+
(0, import_react.useLayoutEffect)(() => {
|
|
787
|
+
if (!open || activeIndex < 0) return;
|
|
788
|
+
const list = listRef.current;
|
|
789
|
+
const row = rowRefs.current[activeIndex];
|
|
790
|
+
if (list === null || row === null || row === void 0) return;
|
|
791
|
+
const listRect = list.getBoundingClientRect();
|
|
792
|
+
const rowRect = row.getBoundingClientRect();
|
|
793
|
+
const top = rowRect.top - listRect.top + list.scrollTop;
|
|
794
|
+
const heading = row.closest("section")?.querySelector(".dsh-mp-heading");
|
|
795
|
+
const cover = heading?.offsetHeight ?? 0;
|
|
796
|
+
if (top - cover < list.scrollTop) list.scrollTop = Math.max(0, top - cover);
|
|
797
|
+
else if (top + rowRect.height > list.scrollTop + list.clientHeight) {
|
|
798
|
+
list.scrollTop = top + rowRect.height - list.clientHeight;
|
|
799
|
+
}
|
|
800
|
+
}, [open, activeIndex, rows]);
|
|
801
|
+
(0, import_react.useLayoutEffect)(() => {
|
|
802
|
+
const list = listRef.current;
|
|
803
|
+
if (!open || list === null) return;
|
|
804
|
+
let frame = 0;
|
|
805
|
+
const update = () => {
|
|
806
|
+
frame = 0;
|
|
807
|
+
const top = list.getBoundingClientRect().top;
|
|
808
|
+
for (const heading of list.querySelectorAll(".dsh-mp-heading")) {
|
|
809
|
+
const section = heading.parentElement?.getBoundingClientRect();
|
|
810
|
+
const stuck = section !== void 0 && section.top < top - 0.5 && section.bottom > top;
|
|
811
|
+
if (heading.hasAttribute("data-stuck") !== stuck)
|
|
812
|
+
heading.toggleAttribute("data-stuck", stuck);
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
const onScroll = () => {
|
|
816
|
+
if (frame === 0) frame = requestAnimationFrame(update);
|
|
817
|
+
};
|
|
818
|
+
update();
|
|
819
|
+
list.addEventListener("scroll", onScroll, { passive: true });
|
|
820
|
+
return () => {
|
|
821
|
+
list.removeEventListener("scroll", onScroll);
|
|
822
|
+
if (frame !== 0) cancelAnimationFrame(frame);
|
|
823
|
+
};
|
|
824
|
+
}, [open, sections]);
|
|
825
|
+
if (!available) return null;
|
|
826
|
+
const pageSize = () => Math.max(1, Math.floor((listRef.current?.clientHeight ?? ROW_HEIGHT * 8) / ROW_HEIGHT) - 1);
|
|
827
|
+
const onRootKeyDown = (event) => {
|
|
828
|
+
if (event.nativeEvent.isComposing || !open) return;
|
|
829
|
+
if (event.key === "Escape") {
|
|
830
|
+
event.preventDefault();
|
|
831
|
+
close(true);
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
const target = event.target;
|
|
835
|
+
if (menuRef.current?.contains(target) !== true) return;
|
|
836
|
+
if (footerRef.current?.contains(target) === true) return;
|
|
837
|
+
const move = (delta, wrap) => {
|
|
838
|
+
event.preventDefault();
|
|
839
|
+
if (rows.length > 0) highlight(stepIndex(activeIndex, delta, rows.length, wrap));
|
|
840
|
+
searchRef.current?.focus();
|
|
841
|
+
};
|
|
842
|
+
switch (event.key) {
|
|
843
|
+
case "ArrowDown":
|
|
844
|
+
move(1, true);
|
|
845
|
+
return;
|
|
846
|
+
case "ArrowUp":
|
|
847
|
+
move(-1, true);
|
|
848
|
+
return;
|
|
849
|
+
case "PageDown":
|
|
850
|
+
move(pageSize(), false);
|
|
851
|
+
return;
|
|
852
|
+
case "PageUp":
|
|
853
|
+
move(-pageSize(), false);
|
|
854
|
+
return;
|
|
855
|
+
case "Home":
|
|
856
|
+
case "End":
|
|
857
|
+
if (event.shiftKey) return;
|
|
858
|
+
event.preventDefault();
|
|
859
|
+
if (rows.length > 0) highlight(event.key === "Home" ? 0 : rows.length - 1);
|
|
860
|
+
return;
|
|
861
|
+
case "Enter": {
|
|
862
|
+
event.preventDefault();
|
|
863
|
+
const row = rows[activeIndex];
|
|
864
|
+
if (!busy && row !== void 0) choose(row.entry);
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
case "Tab":
|
|
868
|
+
event.preventDefault();
|
|
869
|
+
if (event.shiftKey) close(true);
|
|
870
|
+
else focusEffort();
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
if (event.code === "KeyD" && (event.ctrlKey || event.metaKey) && !event.altKey && !event.shiftKey) {
|
|
874
|
+
event.preventDefault();
|
|
875
|
+
const row = rows[activeIndex];
|
|
876
|
+
if (row !== void 0) toggleFavorite(row.entry);
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
const onFooterKeyDown = (event) => {
|
|
880
|
+
if (event.nativeEvent.isComposing) return;
|
|
881
|
+
const chips = chipRefs.current.filter((chip) => chip !== null);
|
|
882
|
+
if (chips.length === 0) return;
|
|
883
|
+
const index = chips.indexOf(document.activeElement);
|
|
884
|
+
const focusAt = (next) => {
|
|
885
|
+
event.preventDefault();
|
|
886
|
+
chips[next]?.focus();
|
|
887
|
+
};
|
|
888
|
+
switch (event.key) {
|
|
889
|
+
case "ArrowRight":
|
|
890
|
+
focusAt(stepIndex(index, 1, chips.length, true));
|
|
891
|
+
return;
|
|
892
|
+
case "ArrowLeft":
|
|
893
|
+
focusAt(stepIndex(index, -1, chips.length, true));
|
|
894
|
+
return;
|
|
895
|
+
case "Home":
|
|
896
|
+
focusAt(0);
|
|
897
|
+
return;
|
|
898
|
+
case "End":
|
|
899
|
+
focusAt(chips.length - 1);
|
|
900
|
+
return;
|
|
901
|
+
case "Tab":
|
|
902
|
+
event.preventDefault();
|
|
903
|
+
searchRef.current?.focus();
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
const onBlur = (event) => {
|
|
908
|
+
const next = event.relatedTarget;
|
|
909
|
+
if (next instanceof Node && (rootRef.current?.contains(next) === true || menuRef.current?.contains(next) === true)) {
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
close();
|
|
913
|
+
};
|
|
914
|
+
const keepFocus = (event) => {
|
|
915
|
+
const target = event.target;
|
|
916
|
+
if (target instanceof Element && target.closest("button") !== null) event.preventDefault();
|
|
917
|
+
};
|
|
918
|
+
const loading = state.status === "loading";
|
|
919
|
+
const waiting = currentEntry === void 0 && loading;
|
|
920
|
+
const modelLabel = waiting ? t("trigger.loading") : currentEntry?.model.name ?? t("trigger.fallback");
|
|
921
|
+
const triggerTitle = caption === void 0 ? modelLabel : `${modelLabel} \xB7 ${caption}`;
|
|
922
|
+
const triggerAria = waiting ? t("trigger.loading") : currentEntry === void 0 ? t("trigger.selectAria") : caption === void 0 ? t("trigger.aria", { model: modelLabel }) : t("trigger.ariaEffort", { model: modelLabel, effort: caption });
|
|
923
|
+
const menuId = `${id}-menu`;
|
|
924
|
+
const listId = `${id}-models`;
|
|
925
|
+
const rowDomId = (index) => `${id}-row-${index}`;
|
|
926
|
+
const emptyText = rows.length > 0 || loading ? void 0 : entries.length === 0 ? state.status === "ready" ? t("empty.models") : void 0 : t("search.empty");
|
|
927
|
+
rowRefs.current.length = rows.length;
|
|
928
|
+
chipRefs.current.length = choices.length;
|
|
929
|
+
let rowIndex = 0;
|
|
930
|
+
return (
|
|
931
|
+
// biome-ignore lint/a11y/noStaticElementInteractions: the wrapper routes the trigger's and the portaled menu's keyboard and focus events, as the shipped selector does
|
|
932
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
933
|
+
"div",
|
|
934
|
+
{
|
|
935
|
+
ref: rootRef,
|
|
936
|
+
className: "dsh-mp-root",
|
|
937
|
+
onKeyDown: onRootKeyDown,
|
|
938
|
+
onBlur,
|
|
939
|
+
onMouseDown: keepFocus,
|
|
940
|
+
children: [
|
|
941
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
942
|
+
"button",
|
|
943
|
+
{
|
|
944
|
+
ref: triggerRef,
|
|
945
|
+
type: "button",
|
|
946
|
+
className: "dsh-mp-trigger",
|
|
947
|
+
"aria-label": triggerAria,
|
|
948
|
+
"aria-haspopup": "menu",
|
|
949
|
+
"aria-expanded": open,
|
|
950
|
+
"aria-controls": open ? menuId : void 0,
|
|
951
|
+
"aria-busy": busy,
|
|
952
|
+
title: triggerTitle,
|
|
953
|
+
"data-selection-focus": selectionFocus ? "" : void 0,
|
|
954
|
+
disabled: locked,
|
|
955
|
+
onBlur: () => {
|
|
956
|
+
setSelectionFocus(false);
|
|
957
|
+
},
|
|
958
|
+
onClick: () => {
|
|
959
|
+
if (open) close(true);
|
|
960
|
+
else show();
|
|
961
|
+
},
|
|
962
|
+
children: [
|
|
963
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IconModels, { className: "dsh-mp-trigger-icon" }),
|
|
964
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-mp-trigger-label", children: currentEntry === void 0 ? modelLabel : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { "data-provider-id": currentEntry.provider.id, "data-model-id": currentEntry.model.id, children: currentEntry.model.name }) }),
|
|
965
|
+
caption !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-mp-trigger-effort", children: caption }),
|
|
966
|
+
busy ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Spinner, {}) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IconChevronDown, { className: classes("dsh-mp-chevron", open && "dsh-mp-chevron-open") })
|
|
967
|
+
]
|
|
968
|
+
}
|
|
969
|
+
),
|
|
970
|
+
open && (0, import_react_dom.createPortal)(
|
|
971
|
+
// biome-ignore lint/a11y/useSemanticElements: the shipped menu surface is a group, not a form fieldset
|
|
972
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
973
|
+
"div",
|
|
974
|
+
{
|
|
975
|
+
ref: menuRef,
|
|
976
|
+
id: menuId,
|
|
977
|
+
className: "dsh-mp-menu",
|
|
978
|
+
"data-menu-material": "translucent",
|
|
979
|
+
style: menuPos ?? MEASURE_STYLE,
|
|
980
|
+
role: "group",
|
|
981
|
+
"aria-label": t("menu.aria"),
|
|
982
|
+
"aria-busy": loading || busy,
|
|
983
|
+
tabIndex: -1,
|
|
984
|
+
children: [
|
|
985
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-mp-material", "aria-hidden": "true" }),
|
|
986
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-mp-search-row", children: [
|
|
987
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: classes("dsh-mp-search", query !== "" && "dsh-mp-search-with-query"), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
988
|
+
"input",
|
|
989
|
+
{
|
|
990
|
+
ref: searchRef,
|
|
991
|
+
type: "search",
|
|
992
|
+
"aria-label": t("search.placeholder"),
|
|
993
|
+
"aria-controls": listId,
|
|
994
|
+
"aria-activedescendant": activeIndex < 0 ? void 0 : rowDomId(activeIndex),
|
|
995
|
+
placeholder: t("search.placeholder"),
|
|
996
|
+
autoComplete: "off",
|
|
997
|
+
spellCheck: false,
|
|
998
|
+
value: query,
|
|
999
|
+
readOnly: busy,
|
|
1000
|
+
onChange: (event) => {
|
|
1001
|
+
changeQuery(event.target.value);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
) }),
|
|
1005
|
+
query !== "" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1006
|
+
"button",
|
|
1007
|
+
{
|
|
1008
|
+
type: "button",
|
|
1009
|
+
className: "dsh-mp-search-clear",
|
|
1010
|
+
"aria-label": t("search.clear"),
|
|
1011
|
+
disabled: busy,
|
|
1012
|
+
onClick: () => {
|
|
1013
|
+
changeQuery("");
|
|
1014
|
+
searchRef.current?.focus();
|
|
1015
|
+
},
|
|
1016
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IconClose, {})
|
|
1017
|
+
}
|
|
1018
|
+
)
|
|
1019
|
+
] }),
|
|
1020
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-mp-status", children: t("status.loading") }),
|
|
1021
|
+
state.error !== null && lastAction.current === "load" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-mp-error", children: [
|
|
1022
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("error.action", { message: state.error }) }),
|
|
1023
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "dsh-mp-retry", onClick: reload, children: t("retry") })
|
|
1024
|
+
] }),
|
|
1025
|
+
state.failures.map((failure) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-mp-warning", children: [
|
|
1026
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("warning.groupLoad", {
|
|
1027
|
+
name: providerName(failure, accountName),
|
|
1028
|
+
message: failure.message
|
|
1029
|
+
}) }),
|
|
1030
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "dsh-mp-retry", onClick: reload, children: t("retry") })
|
|
1031
|
+
] }, failure.id)),
|
|
1032
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1033
|
+
"div",
|
|
1034
|
+
{
|
|
1035
|
+
ref: listRef,
|
|
1036
|
+
id: listId,
|
|
1037
|
+
className: "dsh-mp-list scrollable",
|
|
1038
|
+
role: "menu",
|
|
1039
|
+
"aria-label": t("list.aria"),
|
|
1040
|
+
tabIndex: -1,
|
|
1041
|
+
hidden: rows.length === 0,
|
|
1042
|
+
children: sections.map((section) => {
|
|
1043
|
+
const headingId = `${id}-${section.id}`;
|
|
1044
|
+
return (
|
|
1045
|
+
// biome-ignore lint/a11y/useSemanticElements: the shipped MenuGroup markup inside role="menu"
|
|
1046
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1047
|
+
"section",
|
|
1048
|
+
{
|
|
1049
|
+
className: "dsh-mp-group",
|
|
1050
|
+
role: "group",
|
|
1051
|
+
"aria-labelledby": section.label === void 0 ? void 0 : headingId,
|
|
1052
|
+
"aria-label": section.label === void 0 ? t("section.results") : void 0,
|
|
1053
|
+
children: [
|
|
1054
|
+
section.label !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-mp-heading", id: headingId, children: section.label }),
|
|
1055
|
+
section.entries.map((entry) => {
|
|
1056
|
+
const index = rowIndex++;
|
|
1057
|
+
const row = rows[index];
|
|
1058
|
+
const selected = isSelected(entry, current);
|
|
1059
|
+
const favorite = favoriteKeys.has(entry.key);
|
|
1060
|
+
const active = index === activeIndex;
|
|
1061
|
+
const pendingHere = pending !== null && isSelected(entry, pending);
|
|
1062
|
+
const showId = !sameName(entry.model.name, entry.model.id);
|
|
1063
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-mp-row", children: [
|
|
1064
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1065
|
+
"button",
|
|
1066
|
+
{
|
|
1067
|
+
ref: (node) => {
|
|
1068
|
+
rowRefs.current[index] = node;
|
|
1069
|
+
},
|
|
1070
|
+
type: "button",
|
|
1071
|
+
role: "menuitemradio",
|
|
1072
|
+
"aria-checked": selected,
|
|
1073
|
+
id: rowDomId(index),
|
|
1074
|
+
tabIndex: -1,
|
|
1075
|
+
className: classes("dsh-mp-option", active && "dsh-mp-option-active"),
|
|
1076
|
+
"data-highlighted": active ? "" : void 0,
|
|
1077
|
+
title: `${entry.provider.name} \xB7 ${entry.model.name}${showId ? ` (${entry.model.id})` : ""}`,
|
|
1078
|
+
disabled: busy,
|
|
1079
|
+
onMouseMove: busy || active ? void 0 : () => highlight(index),
|
|
1080
|
+
onClick: () => {
|
|
1081
|
+
choose(entry);
|
|
1082
|
+
},
|
|
1083
|
+
children: [
|
|
1084
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-mp-check", children: pendingHere ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Spinner, {}) : selected ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IconCheck, {}) : null }),
|
|
1085
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "dsh-mp-copy", children: [
|
|
1086
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1087
|
+
"span",
|
|
1088
|
+
{
|
|
1089
|
+
className: "dsh-mp-name",
|
|
1090
|
+
"data-provider-id": entry.provider.id,
|
|
1091
|
+
"data-model-id": entry.model.id,
|
|
1092
|
+
children: entry.model.name
|
|
1093
|
+
}
|
|
1094
|
+
),
|
|
1095
|
+
showId && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-mp-id", children: entry.model.id })
|
|
1096
|
+
] }),
|
|
1097
|
+
row?.showProvider === true && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-mp-provider", children: entry.provider.name }),
|
|
1098
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-mp-star-room" })
|
|
1099
|
+
]
|
|
1100
|
+
}
|
|
1101
|
+
),
|
|
1102
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1103
|
+
"button",
|
|
1104
|
+
{
|
|
1105
|
+
type: "button",
|
|
1106
|
+
tabIndex: -1,
|
|
1107
|
+
className: classes("dsh-mp-star", favorite && "dsh-mp-star-on"),
|
|
1108
|
+
"aria-label": favorite ? t("favorite.remove") : t("favorite.add"),
|
|
1109
|
+
"aria-pressed": favorite,
|
|
1110
|
+
title: favorite ? t("favorite.remove") : t("favorite.add"),
|
|
1111
|
+
onMouseMove: active ? void 0 : () => highlight(index),
|
|
1112
|
+
onClick: () => {
|
|
1113
|
+
toggleFavorite(entry);
|
|
1114
|
+
},
|
|
1115
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IconStar, { filled: favorite })
|
|
1116
|
+
}
|
|
1117
|
+
)
|
|
1118
|
+
] }, row?.id ?? entry.key);
|
|
1119
|
+
})
|
|
1120
|
+
]
|
|
1121
|
+
},
|
|
1122
|
+
section.id
|
|
1123
|
+
)
|
|
1124
|
+
);
|
|
1125
|
+
})
|
|
1126
|
+
}
|
|
1127
|
+
),
|
|
1128
|
+
emptyText !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-mp-empty", role: "status", children: emptyText }),
|
|
1129
|
+
choices.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1130
|
+
"fieldset",
|
|
1131
|
+
{
|
|
1132
|
+
ref: footerRef,
|
|
1133
|
+
className: "dsh-mp-effort",
|
|
1134
|
+
"aria-label": t("effort.aria"),
|
|
1135
|
+
onKeyDown: onFooterKeyDown,
|
|
1136
|
+
children: [
|
|
1137
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-mp-effort-label", "aria-hidden": "true", children: t("effort.label") }),
|
|
1138
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-mp-chips", children: choices.map((choice, index) => {
|
|
1139
|
+
const checked = effective === choice.effort;
|
|
1140
|
+
const pendingHere = pending !== null && current !== null && pending.provider === current.provider && pending.model === current.model && pending.reasoningEffort === choice.effort;
|
|
1141
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1142
|
+
"button",
|
|
1143
|
+
{
|
|
1144
|
+
ref: (node) => {
|
|
1145
|
+
chipRefs.current[index] = node;
|
|
1146
|
+
},
|
|
1147
|
+
type: "button",
|
|
1148
|
+
"aria-pressed": checked,
|
|
1149
|
+
tabIndex: -1,
|
|
1150
|
+
className: classes("dsh-mp-chip", checked && "dsh-mp-chip-on"),
|
|
1151
|
+
disabled: busy,
|
|
1152
|
+
onClick: () => {
|
|
1153
|
+
chooseEffort(choice);
|
|
1154
|
+
},
|
|
1155
|
+
children: [
|
|
1156
|
+
pendingHere && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Spinner, { size: 12 }),
|
|
1157
|
+
choice.label
|
|
1158
|
+
]
|
|
1159
|
+
},
|
|
1160
|
+
choice.key
|
|
1161
|
+
);
|
|
1162
|
+
}) })
|
|
1163
|
+
]
|
|
1164
|
+
}
|
|
1165
|
+
)
|
|
1166
|
+
]
|
|
1167
|
+
}
|
|
1168
|
+
),
|
|
1169
|
+
document.body
|
|
1170
|
+
),
|
|
1171
|
+
toast !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1172
|
+
PickerToast,
|
|
1173
|
+
{
|
|
1174
|
+
text: toast.text,
|
|
1175
|
+
anchor: rootRef.current?.closest("[data-composer-card]") ?? null,
|
|
1176
|
+
onDone: () => {
|
|
1177
|
+
setToast(null);
|
|
1178
|
+
}
|
|
1179
|
+
},
|
|
1180
|
+
toast.seq
|
|
1181
|
+
)
|
|
1182
|
+
]
|
|
1183
|
+
}
|
|
1184
|
+
)
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
// ../dsh-plugin-model-picker/src/client/recent.ts
|
|
1189
|
+
var RECENT_STORAGE_KEY = "dsh-plugin-model-picker:recent:v1";
|
|
1190
|
+
function createRecentStore(storage, events) {
|
|
1191
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1192
|
+
const load = () => {
|
|
1193
|
+
if (storage === void 0) return [];
|
|
1194
|
+
try {
|
|
1195
|
+
const raw = storage.getItem(RECENT_STORAGE_KEY);
|
|
1196
|
+
return raw === null ? [] : normalizeRefs(JSON.parse(raw)).slice(0, RECENT_STORED);
|
|
1197
|
+
} catch {
|
|
1198
|
+
return [];
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
let snapshot = load();
|
|
1202
|
+
const replace = (next) => {
|
|
1203
|
+
if (sameRefs(next, snapshot)) return;
|
|
1204
|
+
snapshot = next;
|
|
1205
|
+
for (const listener of [...listeners]) listener();
|
|
1206
|
+
};
|
|
1207
|
+
return {
|
|
1208
|
+
getSnapshot: () => snapshot,
|
|
1209
|
+
subscribe(listener) {
|
|
1210
|
+
listeners.add(listener);
|
|
1211
|
+
return () => {
|
|
1212
|
+
listeners.delete(listener);
|
|
1213
|
+
};
|
|
1214
|
+
},
|
|
1215
|
+
connect() {
|
|
1216
|
+
if (events === void 0) return () => {
|
|
1217
|
+
};
|
|
1218
|
+
const onStorage = (event) => {
|
|
1219
|
+
if (event.key === null || event.key === RECENT_STORAGE_KEY) replace(load());
|
|
1220
|
+
};
|
|
1221
|
+
events.addEventListener("storage", onStorage);
|
|
1222
|
+
return () => {
|
|
1223
|
+
events.removeEventListener("storage", onStorage);
|
|
1224
|
+
};
|
|
1225
|
+
},
|
|
1226
|
+
push(ref) {
|
|
1227
|
+
const next = pushRecent(snapshot, ref);
|
|
1228
|
+
try {
|
|
1229
|
+
storage?.setItem(RECENT_STORAGE_KEY, JSON.stringify(next));
|
|
1230
|
+
} catch {
|
|
1231
|
+
}
|
|
1232
|
+
replace(next);
|
|
1233
|
+
}
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// ../dsh-plugin-model-picker/src/client/styles.ts
|
|
1238
|
+
var PICKER_CSS = [
|
|
1239
|
+
".dsh-mp-root{min-width:0;position:relative}",
|
|
1240
|
+
".dsh-mp-trigger{border-radius:var(--dsw-radius-sm);min-width:0;max-width:min(360px,45cqw);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:inherit;font-size:13px;font-weight:400;line-height:20px;display:flex}",
|
|
1241
|
+
".dsh-mp-trigger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}",
|
|
1242
|
+
".dsh-mp-trigger:focus-visible:not([data-selection-focus]){box-shadow:0 0 0 2px var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary))}",
|
|
1243
|
+
".dsh-mp-trigger:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}",
|
|
1244
|
+
".dsh-mp-trigger-label{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}",
|
|
1245
|
+
".dsh-mp-trigger-effort{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-caption);flex-shrink:1000;overflow:hidden}",
|
|
1246
|
+
".dsh-mp-trigger-icon{display:var(--dsh-composer-model-icon-display,none);flex:none}",
|
|
1247
|
+
".dsh-mp-trigger-label,.dsh-mp-trigger-effort{display:var(--dsh-composer-model-text-display,block)}",
|
|
1248
|
+
".dsh-mp-chevron{color:var(--dsw-alias-label-caption);flex:none;transition:transform .12s}",
|
|
1249
|
+
".dsh-mp-chevron-open{transform:rotate(180deg)}",
|
|
1250
|
+
".dsh-mp-menu{z-index:1100;--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);box-sizing:border-box;width:min(400px,100vw - 32px);max-height:min(560px,100vh - 96px);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;border-radius:var(--dsw-radius-lg);isolation:isolate;flex-direction:column;padding:4px;display:flex;position:fixed;overflow:hidden;outline:none}",
|
|
1251
|
+
".dsh-mp-material{position:absolute;inset:0;z-index:-1;border-radius:inherit;background:var(--dsw-menu-surface-fill);backdrop-filter:var(--dsw-menu-backdrop-filter);pointer-events:none}",
|
|
1252
|
+
".dsh-mp-status,.dsh-mp-empty{color:var(--dsw-alias-label-tertiary);padding:8px;font-size:12px;line-height:18px}",
|
|
1253
|
+
".dsh-mp-error,.dsh-mp-warning{flex:none;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}",
|
|
1254
|
+
".dsh-mp-warning{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-state-warn-label)}",
|
|
1255
|
+
".dsh-mp-retry{color:inherit;font:inherit;cursor:pointer;background:0 0;border:none;flex:none;padding:0;font-weight:600}",
|
|
1256
|
+
".dsh-mp-search-row{flex-shrink:0;margin:2px 0 3px;position:relative}",
|
|
1257
|
+
".dsh-mp-search{box-sizing:border-box;width:100%;border-radius:var(--dsw-radius-md);background:0 0;border:0;padding:5px 7px;display:flex}",
|
|
1258
|
+
".dsh-mp-search-with-query{padding-right:34px}",
|
|
1259
|
+
".dsh-mp-search input{flex:1;min-width:0;border:none;outline:none;background:transparent;padding:0;font:inherit;font-size:12px;line-height:normal;color:var(--dsw-alias-label-primary)}",
|
|
1260
|
+
".dsh-mp-search input::placeholder{color:var(--dsw-alias-label-caption)}",
|
|
1261
|
+
".dsh-mp-search input{appearance:none}.dsh-mp-search input::-webkit-search-cancel-button,.dsh-mp-search input::-webkit-search-decoration{appearance:none;display:none}",
|
|
1262
|
+
".dsh-mp-search-clear{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%)}",
|
|
1263
|
+
".dsh-mp-search-clear:hover,.dsh-mp-search-clear:focus-visible{background:var(--dsw-alias-interactive-bg-hover);outline:none}",
|
|
1264
|
+
".dsh-mp-list{position:relative;flex:1 1 auto;min-height:0;overflow-y:auto;outline:none;overscroll-behavior:contain}",
|
|
1265
|
+
".dsh-mp-group{position:relative}",
|
|
1266
|
+
".dsh-mp-group+.dsh-mp-group{margin-top:3px}",
|
|
1267
|
+
".dsh-mp-heading{position:sticky;top:0;z-index:1;padding:4px 7px 2px;background:transparent;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;font-weight:500;border-radius:var(--dsw-radius-md)}",
|
|
1268
|
+
".dsh-mp-heading[data-stuck]{background:var(--dsw-alias-menu-group-header-fill)}",
|
|
1269
|
+
".dsh-mp-row{position:relative}",
|
|
1270
|
+
".dsh-mp-option{box-sizing:border-box;border-radius:var(--dsw-radius-md);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;font:inherit;display:flex}",
|
|
1271
|
+
".dsh-mp-option-active:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}",
|
|
1272
|
+
".dsh-mp-option:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}",
|
|
1273
|
+
".dsh-mp-check{color:var(--dsw-alias-label-primary);flex:0 0 14px;place-items:center;display:grid}",
|
|
1274
|
+
".dsh-mp-check svg{width:14px;height:14px}",
|
|
1275
|
+
".dsh-mp-copy{flex:1;min-width:0;display:flex;align-items:baseline;gap:6px;overflow:hidden}",
|
|
1276
|
+
".dsh-mp-name{flex:0 1 auto;min-width:0;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;font-size:13px;font-weight:400;line-height:18px}",
|
|
1277
|
+
".dsh-mp-id{flex:0 1000 auto;min-width:0;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;color:var(--dsw-alias-label-caption);font-size:11px;line-height:16px}",
|
|
1278
|
+
".dsh-mp-provider{flex:0 0 auto;max-width:35%;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}",
|
|
1279
|
+
".dsh-mp-star-room{flex:0 0 20px}",
|
|
1280
|
+
".dsh-mp-star{position:absolute;top:50%;right:6px;transform:translateY(-50%);width:22px;height:22px;display:grid;place-items:center;padding:0;border:none;border-radius:var(--dsw-radius-sm);background:transparent;color:var(--dsw-alias-label-caption);cursor:pointer;opacity:0;outline:none}",
|
|
1281
|
+
".dsh-mp-row:hover .dsh-mp-star,.dsh-mp-option-active+.dsh-mp-star,.dsh-mp-star-on{opacity:1}",
|
|
1282
|
+
".dsh-mp-star:hover{color:var(--dsw-alias-label-primary)}",
|
|
1283
|
+
".dsh-mp-star-on,.dsh-mp-star-on:hover{color:var(--dsw-alias-state-warn-primary)}",
|
|
1284
|
+
".dsh-mp-effort{flex:none;display:flex;align-items:center;gap:8px;min-width:0;margin:3px 0 0;padding:6px 7px 3px;border:0;border-top:.5px solid var(--dsw-alias-border-l2)}",
|
|
1285
|
+
".dsh-mp-effort-label{flex:none;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}",
|
|
1286
|
+
".dsh-mp-chips{display:flex;flex-wrap:wrap;gap:4px;min-width:0}",
|
|
1287
|
+
".dsh-mp-chip{display:inline-flex;align-items:center;gap:4px;height:24px;padding:0 8px;border:none;border-radius:var(--dsw-radius-sm);background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;line-height:16px;cursor:pointer;outline:none}",
|
|
1288
|
+
".dsh-mp-chip:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}",
|
|
1289
|
+
".dsh-mp-chip:focus-visible{box-shadow:0 0 0 2px var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary))}",
|
|
1290
|
+
".dsh-mp-chip-on{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover);font-weight:500}",
|
|
1291
|
+
".dsh-mp-chip:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}",
|
|
1292
|
+
".dsh-mp-spinner{flex:none;color:var(--dsw-alias-label-tertiary)}",
|
|
1293
|
+
".dsh-mp-spinner-motion{transform-origin:center;animation:dsh-mp-spin 1.5s linear infinite}",
|
|
1294
|
+
".dsh-mp-spinner-track,.dsh-mp-spinner-arc{fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round}",
|
|
1295
|
+
".dsh-mp-spinner-track{opacity:.25}",
|
|
1296
|
+
".dsh-mp-spinner-arc{stroke-dasharray:12 150;animation:dsh-mp-dash 1.5s ease-in-out infinite}",
|
|
1297
|
+
"@keyframes dsh-mp-spin{to{transform:rotate(360deg)}}",
|
|
1298
|
+
"@keyframes dsh-mp-dash{0%{stroke-dasharray:12 150;stroke-dashoffset:0}50%{stroke-dasharray:24 150;stroke-dashoffset:-6}to{stroke-dasharray:12 150;stroke-dashoffset:0}}",
|
|
1299
|
+
".dsh-mp-toast{position:fixed;top:40px;left:50%;z-index:1100;pointer-events:none;display:flex;align-items:center;gap:10px;width:max-content;max-width:min(640px,calc(100vw - 48px));padding:12px 16px;border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-toast-bg);color:var(--dsw-alias-toast-label);font-size:14px;line-height:22px;box-shadow:var(--dsw-shadow-lv3);transform:translate(-50%);animation:dsh-mp-toast-in .16s ease-out,dsh-mp-toast-fade 1s ease var(--dsh-mp-toast-hold,3s) forwards}",
|
|
1300
|
+
".dsh-mp-toast-icon{display:grid;place-items:center;flex:none;color:var(--dsw-alias-state-warn-label)}",
|
|
1301
|
+
".dsh-mp-toast-text{min-width:0}",
|
|
1302
|
+
"@keyframes dsh-mp-toast-in{0%{opacity:0;transform:translate(-50%,-6px)}to{opacity:1;transform:translate(-50%)}}",
|
|
1303
|
+
"@keyframes dsh-mp-toast-fade{to{opacity:0;visibility:hidden}}",
|
|
1304
|
+
"@media (prefers-reduced-motion:reduce){.dsh-mp-toast{animation:dsh-mp-toast-fade 1s ease var(--dsh-mp-toast-hold,3s) forwards}.dsh-mp-chevron{transition:none}}"
|
|
1305
|
+
].join("\n");
|
|
1306
|
+
|
|
1307
|
+
// ../dsh-plugin-model-picker/src/client/index.tsx
|
|
1308
|
+
var inject = [
|
|
1309
|
+
"slots",
|
|
1310
|
+
"locale",
|
|
1311
|
+
"configForms",
|
|
1312
|
+
"sessions",
|
|
1313
|
+
"remote",
|
|
1314
|
+
"remote.session",
|
|
1315
|
+
"modelDirectories"
|
|
1316
|
+
];
|
|
1317
|
+
var PLUGIN_ID = "@1outres/dsh-plugin-model-picker";
|
|
1318
|
+
var SEAT = "conversation.input.model";
|
|
1319
|
+
var SEAT_PRIORITY = -100;
|
|
1320
|
+
function browserStorage() {
|
|
1321
|
+
try {
|
|
1322
|
+
return typeof localStorage === "undefined" ? void 0 : localStorage;
|
|
1323
|
+
} catch {
|
|
1324
|
+
return void 0;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
function storageEvents() {
|
|
1328
|
+
return typeof window === "undefined" ? void 0 : window;
|
|
1329
|
+
}
|
|
1330
|
+
function apply(ctx) {
|
|
1331
|
+
ctx.effect(() => ctx.locale.register(CONFIG_NAMESPACE, { en }), "model-picker: dictionaries");
|
|
1332
|
+
ctx.effect(() => {
|
|
1333
|
+
const tag = document.createElement("style");
|
|
1334
|
+
tag.dataset.plugin = PLUGIN_ID;
|
|
1335
|
+
tag.dataset.pluginCss = `${PLUGIN_ID}/picker.css`;
|
|
1336
|
+
tag.textContent = PICKER_CSS;
|
|
1337
|
+
document.head.append(tag);
|
|
1338
|
+
return () => {
|
|
1339
|
+
tag.remove();
|
|
1340
|
+
};
|
|
1341
|
+
}, "model-picker: stylesheet");
|
|
1342
|
+
const favorites = createFavoritesStore(ctx.configForms.get(CONFIG_NAMESPACE));
|
|
1343
|
+
const recent = createRecentStore(browserStorage(), storageEvents());
|
|
1344
|
+
ctx.effect(() => favorites.connect(), "model-picker: favorites");
|
|
1345
|
+
ctx.effect(() => recent.connect(), "model-picker: recent models");
|
|
1346
|
+
ctx.effect(
|
|
1347
|
+
() => ctx.slots.inject(
|
|
1348
|
+
SEAT,
|
|
1349
|
+
() => ctx.slots.register(
|
|
1350
|
+
{
|
|
1351
|
+
name: SEAT,
|
|
1352
|
+
priority: SEAT_PRIORITY,
|
|
1353
|
+
locale: CONFIG_NAMESPACE,
|
|
1354
|
+
inject: (sessionId) => {
|
|
1355
|
+
const directory = ctx.modelDirectories.directoryFor(sessionId);
|
|
1356
|
+
const available = ctx.sessions.subagentAddress(sessionId) === void 0;
|
|
1357
|
+
return {
|
|
1358
|
+
available,
|
|
1359
|
+
directory: directory.store,
|
|
1360
|
+
load: () => {
|
|
1361
|
+
if (available) directory.load().catch(() => {
|
|
1362
|
+
});
|
|
1363
|
+
},
|
|
1364
|
+
select: (selection) => available ? directory.select(selection) : Promise.resolve(void 0),
|
|
1365
|
+
favorites,
|
|
1366
|
+
recent
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
},
|
|
1370
|
+
ModelPicker
|
|
1371
|
+
)
|
|
1372
|
+
),
|
|
1373
|
+
"model-picker: composer seat"
|
|
1374
|
+
);
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
return module.exports;
|
|
1378
|
+
}
|
|
1379
|
+
});
|