dsh-model-switch 0.0.0-stage → 0.4.17
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/CHANGELOG.md +173 -0
- package/IMPLEMENTATION_PLAN.md +40 -0
- package/LICENSE +21 -0
- package/PRODUCT.md +33 -0
- package/README.md +196 -3
- package/README.zh.md +194 -0
- package/cordis.patch.yml +18 -0
- package/docs/screenshots/composer-picker.png +0 -0
- package/docs/screenshots/plan-review.png +0 -0
- package/docs/screenshots/settings-subagent.png +0 -0
- package/lib/adapter-registry.js +59 -0
- package/lib/capabilities.js +73 -0
- package/lib/client.js +3956 -0
- package/lib/index.js +1030 -0
- package/lib/main-settings.js +30 -0
- package/lib/subagent-route-policy.js +55 -0
- package/lib/subagent-runtime.js +186 -0
- package/lib/types/adapter-registry.d.ts +60 -0
- package/lib/types/capabilities-rpc.d.ts +12 -0
- package/lib/types/capabilities.d.ts +22 -0
- package/lib/types/client/ModelSwitchSettings.d.ts +32 -0
- package/lib/types/client/antigravity-catalog.d.ts +30 -0
- package/lib/types/client/derived-config-form.d.ts +10 -0
- package/lib/types/client/derived-settings-scope.d.ts +4 -0
- package/lib/types/client/index.d.ts +10 -0
- package/lib/types/client/locales.d.ts +45 -0
- package/lib/types/client/main-row-controller.d.ts +49 -0
- package/lib/types/client/nav-icon.d.ts +3 -0
- package/lib/types/client/picker/ComposerPicker.d.ts +47 -0
- package/lib/types/client/picker/PickerDirectory.d.ts +31 -0
- package/lib/types/client/picker/PickerSeatBoundary.d.ts +7 -0
- package/lib/types/client/picker/PlanReviewCard.d.ts +37 -0
- package/lib/types/client/picker/RetryBoundary.d.ts +20 -0
- package/lib/types/client/picker/external-catalog.d.ts +14 -0
- package/lib/types/client/picker/install.d.ts +23 -0
- package/lib/types/client/picker/locales.d.ts +104 -0
- package/lib/types/client/picker/main-default-restore.d.ts +56 -0
- package/lib/types/client/picker/popup-dismissal.d.ts +22 -0
- package/lib/types/client/picker/useComposerPickerSurface.d.ts +25 -0
- package/lib/types/client/plugin-rpc.d.ts +10 -0
- package/lib/types/client/provider-directory.d.ts +23 -0
- package/lib/types/client/remote-settings-scope.d.ts +45 -0
- package/lib/types/client/runtime-lock.d.ts +68 -0
- package/lib/types/client/search-capabilities.d.ts +21 -0
- package/lib/types/client-contract.d.ts +51 -0
- package/lib/types/compatibility.d.ts +38 -0
- package/lib/types/deepseek-search-adapter.d.ts +14 -0
- package/lib/types/host-runtime.d.ts +78 -0
- package/lib/types/host-settings.d.ts +49 -0
- package/lib/types/image-tool.d.ts +17 -0
- package/lib/types/index.d.ts +7 -0
- package/lib/types/main-settings.d.ts +9 -0
- package/lib/types/picker/family.d.ts +108 -0
- package/lib/types/picker/pending-dismissal.d.ts +34 -0
- package/lib/types/picker/plan-review.d.ts +57 -0
- package/lib/types/picker/public.d.ts +5 -0
- package/lib/types/picker/selection-feedback.d.ts +2 -0
- package/lib/types/runtime-capabilities.d.ts +50 -0
- package/lib/types/search-provider.d.ts +23 -0
- package/lib/types/subagent-route-policy.d.ts +26 -0
- package/lib/types/subagent-runtime.d.ts +51 -0
- package/lib/types/switch-compact.d.ts +72 -0
- package/package.json +342 -6
package/lib/client.js
ADDED
|
@@ -0,0 +1,3956 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "dsh-model-switch",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react = require("react");
|
|
8
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
+
let react_dom = require("react-dom");
|
|
10
|
+
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
11
|
+
//#region src/client-contract.ts
|
|
12
|
+
const MODEL_SWITCH_CONFIG_ID = "model-switch";
|
|
13
|
+
const MAIN_DEFAULT_CONFIG_ID = "agent-default-model";
|
|
14
|
+
const PROVIDERS_CONFIG_ID = "llm-providers-ui";
|
|
15
|
+
var MainSettingsConflictError = class extends Error {
|
|
16
|
+
name = "MainSettingsConflictError";
|
|
17
|
+
};
|
|
18
|
+
const SUBAGENT_SETTINGS_FIELDS = Object.freeze({
|
|
19
|
+
mode: "subagentMode",
|
|
20
|
+
provider: "subagentProvider",
|
|
21
|
+
model: "subagentModel",
|
|
22
|
+
reasoningEffort: "subagentReasoningEffort"
|
|
23
|
+
});
|
|
24
|
+
/** Instant Subagent header toggle. Off keeps the stored `follow-main` unset token. */
|
|
25
|
+
function subagentModeForEnabled(enabled) {
|
|
26
|
+
return enabled ? "fixed" : "follow-main";
|
|
27
|
+
}
|
|
28
|
+
const SEARCH_SETTINGS_FIELDS = Object.freeze({
|
|
29
|
+
provider: "searchProvider",
|
|
30
|
+
model: "searchModel"
|
|
31
|
+
});
|
|
32
|
+
const IMAGE_SETTINGS_FIELDS = Object.freeze({
|
|
33
|
+
provider: "imageProvider",
|
|
34
|
+
model: "imageModel"
|
|
35
|
+
});
|
|
36
|
+
function deriveSubagentSettings(settings) {
|
|
37
|
+
return {
|
|
38
|
+
mode: settings.subagentMode,
|
|
39
|
+
...settings.subagentProvider === void 0 ? {} : { provider: settings.subagentProvider },
|
|
40
|
+
...settings.subagentModel === void 0 ? {} : { model: settings.subagentModel },
|
|
41
|
+
...settings.subagentReasoningEffort === void 0 ? {} : { reasoningEffort: settings.subagentReasoningEffort }
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function deriveSearchSettings(settings) {
|
|
45
|
+
return {
|
|
46
|
+
...settings.searchProvider === void 0 ? {} : { provider: settings.searchProvider },
|
|
47
|
+
...settings.searchModel === void 0 ? {} : { model: settings.searchModel }
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function deriveImageSettings(settings) {
|
|
51
|
+
return {
|
|
52
|
+
...settings.imageProvider === void 0 ? {} : { provider: settings.imageProvider },
|
|
53
|
+
...settings.imageModel === void 0 ? {} : { model: settings.imageModel }
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/client/derived-config-form.ts
|
|
58
|
+
function deriveConfigForm(source, project, fields) {
|
|
59
|
+
let sourceSnapshot;
|
|
60
|
+
let viewSnapshot;
|
|
61
|
+
const getSnapshot = () => {
|
|
62
|
+
const current = source.getSnapshot();
|
|
63
|
+
if (current === sourceSnapshot && viewSnapshot !== void 0) return viewSnapshot;
|
|
64
|
+
sourceSnapshot = current;
|
|
65
|
+
const next = {
|
|
66
|
+
...current,
|
|
67
|
+
value: current.value === void 0 ? void 0 : project(current.value)
|
|
68
|
+
};
|
|
69
|
+
viewSnapshot = next;
|
|
70
|
+
return next;
|
|
71
|
+
};
|
|
72
|
+
const write = async (operation, field, value) => {
|
|
73
|
+
const sourceField = fields[field];
|
|
74
|
+
if (!(operation === "set" ? await source.set(sourceField, value) : await source.unset(sourceField))) throw new Error("settings-rejected");
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
getSnapshot,
|
|
78
|
+
subscribe: (listener) => source.subscribe(listener),
|
|
79
|
+
set: (field, value) => write("set", field, value),
|
|
80
|
+
unset: (field) => write("unset", field)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/runtime-capabilities.ts
|
|
85
|
+
const RUNTIME_CAPABILITIES = Object.freeze({
|
|
86
|
+
mainDefaults: Object.freeze({ available: true }),
|
|
87
|
+
settings: Object.freeze({ available: true }),
|
|
88
|
+
centralSubagentRouting: Object.freeze({ available: true }),
|
|
89
|
+
packagedPresetRoots: Object.freeze({
|
|
90
|
+
available: false,
|
|
91
|
+
reason: "packaged-preset-roots"
|
|
92
|
+
}),
|
|
93
|
+
toolOwnerSuppression: Object.freeze({
|
|
94
|
+
available: false,
|
|
95
|
+
reason: "tool-owner-suppression"
|
|
96
|
+
}),
|
|
97
|
+
searchProviderAdapters: Object.freeze({
|
|
98
|
+
available: false,
|
|
99
|
+
reason: "search-provider-adapters",
|
|
100
|
+
providers: Object.freeze([])
|
|
101
|
+
}),
|
|
102
|
+
visionProviderAdapters: Object.freeze({
|
|
103
|
+
available: false,
|
|
104
|
+
reason: "vision-provider-adapters"
|
|
105
|
+
}),
|
|
106
|
+
imageProviderAdapters: Object.freeze({
|
|
107
|
+
available: true,
|
|
108
|
+
providers: Object.freeze(["codex", "grok"])
|
|
109
|
+
})
|
|
110
|
+
});
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/client/search-capabilities.ts
|
|
113
|
+
function record$1(value) {
|
|
114
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
115
|
+
}
|
|
116
|
+
function cleanId(value) {
|
|
117
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
118
|
+
}
|
|
119
|
+
function decodeModel(value) {
|
|
120
|
+
const item = record$1(value);
|
|
121
|
+
const id = item === void 0 ? void 0 : cleanId(item.id);
|
|
122
|
+
const name = item === void 0 ? void 0 : cleanId(item.name);
|
|
123
|
+
return id === void 0 || name === void 0 ? void 0 : {
|
|
124
|
+
id,
|
|
125
|
+
name
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Strictly decode the Host search catalog into fresh plain metadata. Any malformed
|
|
130
|
+
* entry fails the whole catalog (undefined): when the Host claims search is
|
|
131
|
+
* available, invalid config must surface as an error, never hide as dropped rows.
|
|
132
|
+
* Extra fields (functions, credentials) never survive: only id/name copy over.
|
|
133
|
+
*/
|
|
134
|
+
function decodeSearchCatalog(value) {
|
|
135
|
+
if (!Array.isArray(value)) return void 0;
|
|
136
|
+
const providers = [];
|
|
137
|
+
for (const item of value) {
|
|
138
|
+
const entry = record$1(item);
|
|
139
|
+
const id = entry === void 0 ? void 0 : cleanId(entry.id);
|
|
140
|
+
const name = entry === void 0 ? void 0 : cleanId(entry.name);
|
|
141
|
+
if (entry === void 0 || id === void 0 || name === void 0 || !Array.isArray(entry.models)) return void 0;
|
|
142
|
+
const models = [];
|
|
143
|
+
for (const model of entry.models) {
|
|
144
|
+
const decoded = decodeModel(model);
|
|
145
|
+
if (decoded === void 0) return void 0;
|
|
146
|
+
models.push(decoded);
|
|
147
|
+
}
|
|
148
|
+
providers.push({
|
|
149
|
+
id,
|
|
150
|
+
name,
|
|
151
|
+
models
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return providers;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Strictly decode one capabilities long-poll value; undefined when untrusted.
|
|
158
|
+
* Only the decoded search block is taken from the network and overlaid onto the
|
|
159
|
+
* frozen local defaults: no arbitrary Host fields pass through.
|
|
160
|
+
*/
|
|
161
|
+
function decodeCapabilitiesSnapshot(value) {
|
|
162
|
+
const payload = record$1(value);
|
|
163
|
+
const revision = payload?.revision;
|
|
164
|
+
if (typeof revision !== "number" || !Number.isSafeInteger(revision) || revision < 0) return void 0;
|
|
165
|
+
const capabilities = payload?.capabilities === void 0 ? void 0 : record$1(payload.capabilities);
|
|
166
|
+
if (capabilities === void 0) return void 0;
|
|
167
|
+
const search = record$1(capabilities.searchProviderAdapters);
|
|
168
|
+
if (search === void 0 || typeof search.available !== "boolean") return void 0;
|
|
169
|
+
if (search.providers !== void 0 && (!Array.isArray(search.providers) || !search.providers.every((id) => typeof id === "string"))) return void 0;
|
|
170
|
+
const catalog = search.catalog === void 0 ? [] : decodeSearchCatalog(search.catalog);
|
|
171
|
+
if (catalog === void 0) return void 0;
|
|
172
|
+
return {
|
|
173
|
+
revision,
|
|
174
|
+
capabilities: {
|
|
175
|
+
...RUNTIME_CAPABILITIES,
|
|
176
|
+
searchProviderAdapters: {
|
|
177
|
+
available: search.available,
|
|
178
|
+
providers: search.providers ?? [],
|
|
179
|
+
catalog
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/** Project already-validated Host search metadata onto group shape (no re-decode). */
|
|
185
|
+
function searchGroupsFromCapabilities(capabilities) {
|
|
186
|
+
const catalog = capabilities?.searchProviderAdapters.catalog;
|
|
187
|
+
if (catalog === void 0) return [];
|
|
188
|
+
return catalog.map((provider) => ({
|
|
189
|
+
id: provider.id,
|
|
190
|
+
name: provider.name,
|
|
191
|
+
models: provider.models.map((model) => ({
|
|
192
|
+
id: model.id,
|
|
193
|
+
name: model.name
|
|
194
|
+
}))
|
|
195
|
+
}));
|
|
196
|
+
}
|
|
197
|
+
//#endregion
|
|
198
|
+
//#region src/client/plugin-rpc.ts
|
|
199
|
+
function callPluginRpc(rpc, method, endpoint, payload, signal) {
|
|
200
|
+
return rpc.call("/api", method, {
|
|
201
|
+
endpoint,
|
|
202
|
+
payload
|
|
203
|
+
}, signal);
|
|
204
|
+
}
|
|
205
|
+
//#endregion
|
|
206
|
+
//#region src/client/picker/external-catalog.ts
|
|
207
|
+
/** Overlay groups from External Agent catalog onto the LLM session.models snapshot. */
|
|
208
|
+
function mergePickerGroups(base, extra) {
|
|
209
|
+
if (extra.length === 0) return base;
|
|
210
|
+
const ids = new Set(extra.map((group) => group.id));
|
|
211
|
+
return [...base.filter((group) => !ids.has(group.id)), ...extra];
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/client/antigravity-catalog.ts
|
|
215
|
+
/** Agent role owned by ProviderDirectory (dsh-llm-providers-ui), never hardcoded by Model Switch. */
|
|
216
|
+
const AGENT_ROLE = "agent";
|
|
217
|
+
/**
|
|
218
|
+
* Antigravity's authenticated plugin route; this plugin has no build dependency on its owner.
|
|
219
|
+
*/
|
|
220
|
+
const ANTIGRAVITY_CATALOG_METHOD = "plugin-rpc/antigravity";
|
|
221
|
+
const ANTIGRAVITY_CATALOG_ENDPOINT = "catalog";
|
|
222
|
+
function record(value) {
|
|
223
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
224
|
+
}
|
|
225
|
+
/** Retain supported effort choices from the remote catalog. */
|
|
226
|
+
function decodeReasoning(value) {
|
|
227
|
+
const item = record(value);
|
|
228
|
+
if (item === void 0 || !Array.isArray(item.efforts)) return void 0;
|
|
229
|
+
const efforts = [];
|
|
230
|
+
for (const entry of item.efforts) {
|
|
231
|
+
const effort = record(entry);
|
|
232
|
+
if (effort === void 0 || typeof effort.id !== "string" || typeof effort.name !== "string") continue;
|
|
233
|
+
efforts.push({
|
|
234
|
+
id: effort.id,
|
|
235
|
+
name: effort.name
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
if (efforts.length === 0) return void 0;
|
|
239
|
+
return {
|
|
240
|
+
efforts,
|
|
241
|
+
defaultEffort: typeof item.defaultEffort === "string" && efforts.some((effort) => effort.id === item.defaultEffort) ? item.defaultEffort : efforts[0].id
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const NATIVE_EFFORTS = [
|
|
245
|
+
"high",
|
|
246
|
+
"medium",
|
|
247
|
+
"low"
|
|
248
|
+
];
|
|
249
|
+
function collapseNativeEfforts(models) {
|
|
250
|
+
if (models.some((model) => model.reasoning !== void 0)) return models;
|
|
251
|
+
const groups = /* @__PURE__ */ new Map();
|
|
252
|
+
const order = [];
|
|
253
|
+
for (const model of models) {
|
|
254
|
+
let logical = model.id;
|
|
255
|
+
let effort;
|
|
256
|
+
for (const item of NATIVE_EFFORTS) {
|
|
257
|
+
const suffix = "-" + item;
|
|
258
|
+
if (model.id.endsWith(suffix) && model.id.length > suffix.length) {
|
|
259
|
+
logical = model.id.slice(0, -suffix.length);
|
|
260
|
+
effort = item;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
let group = groups.get(logical);
|
|
265
|
+
if (group === void 0) {
|
|
266
|
+
let name = model.name;
|
|
267
|
+
for (const label of [
|
|
268
|
+
" (High)",
|
|
269
|
+
" (Medium)",
|
|
270
|
+
" (Low)"
|
|
271
|
+
]) if (name.endsWith(label)) name = name.slice(0, -label.length);
|
|
272
|
+
group = {
|
|
273
|
+
name,
|
|
274
|
+
efforts: []
|
|
275
|
+
};
|
|
276
|
+
groups.set(logical, group);
|
|
277
|
+
order.push(logical);
|
|
278
|
+
}
|
|
279
|
+
if (effort !== void 0 && !group.efforts.includes(effort)) group.efforts.push(effort);
|
|
280
|
+
if (effort === void 0) group.name = model.name;
|
|
281
|
+
}
|
|
282
|
+
return order.map((id) => {
|
|
283
|
+
const group = groups.get(id);
|
|
284
|
+
const efforts = NATIVE_EFFORTS.filter((item) => group.efforts.includes(item)).map((item) => ({
|
|
285
|
+
id: item,
|
|
286
|
+
name: item[0].toUpperCase() + item.slice(1)
|
|
287
|
+
}));
|
|
288
|
+
return {
|
|
289
|
+
id,
|
|
290
|
+
name: group.name,
|
|
291
|
+
...efforts.length === 0 ? {} : { reasoning: {
|
|
292
|
+
efforts,
|
|
293
|
+
defaultEffort: group.efforts.includes("high") ? "high" : efforts[0].id
|
|
294
|
+
} }
|
|
295
|
+
};
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
/** Strictly decode one Enabled-catalog group; malformed groups are dropped, never thrown. */
|
|
299
|
+
function decodeGroup(value) {
|
|
300
|
+
const group = record(value);
|
|
301
|
+
if (group === void 0 || typeof group.id !== "string" || typeof group.name !== "string" || !Array.isArray(group.models)) return void 0;
|
|
302
|
+
const models = [];
|
|
303
|
+
for (const item of group.models) {
|
|
304
|
+
const model = record(item);
|
|
305
|
+
if (model === void 0 || typeof model.id !== "string" || typeof model.name !== "string") continue;
|
|
306
|
+
const reasoning = decodeReasoning(model.reasoning);
|
|
307
|
+
models.push({
|
|
308
|
+
id: model.id,
|
|
309
|
+
name: model.name,
|
|
310
|
+
...reasoning === void 0 ? {} : { reasoning }
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
if (models.length === 0) return void 0;
|
|
314
|
+
return {
|
|
315
|
+
id: group.id,
|
|
316
|
+
name: group.name,
|
|
317
|
+
models: collapseNativeEfforts(models)
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
/** Decode the Enabled-catalog payload; anything malformed decodes to no groups. */
|
|
321
|
+
function decodeAntigravityCatalogGroups(value) {
|
|
322
|
+
const payload = record(value);
|
|
323
|
+
if (payload === void 0 || !Array.isArray(payload.groups)) return [];
|
|
324
|
+
const groups = [];
|
|
325
|
+
for (const item of payload.groups) {
|
|
326
|
+
const group = decodeGroup(item);
|
|
327
|
+
if (group !== void 0) groups.push(group);
|
|
328
|
+
}
|
|
329
|
+
return groups;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Read the Enabled catalog; resolves to no groups when Antigravity is absent,
|
|
333
|
+
* unreachable, or malformed. Never throws: the Host catalog stays authoritative.
|
|
334
|
+
*/
|
|
335
|
+
async function fetchAntigravityCatalogGroups(rpc) {
|
|
336
|
+
if (rpc === void 0) return [];
|
|
337
|
+
try {
|
|
338
|
+
const result = await callPluginRpc(rpc, ANTIGRAVITY_CATALOG_METHOD, ANTIGRAVITY_CATALOG_ENDPOINT, {});
|
|
339
|
+
return result.ok ? decodeAntigravityCatalogGroups(result.value) : [];
|
|
340
|
+
} catch {
|
|
341
|
+
return [];
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
/** Overlay Enabled-catalog groups onto the Host catalog without duplicating ids. */
|
|
345
|
+
function withAntigravityCatalog(base, extra) {
|
|
346
|
+
return mergePickerGroups(base, extra);
|
|
347
|
+
}
|
|
348
|
+
/** Read one Provider role from the owner directory; undefined when the seam is absent. */
|
|
349
|
+
function readProviderRole(directory, key) {
|
|
350
|
+
if (directory === null || typeof directory !== "object" && typeof directory !== "function") return void 0;
|
|
351
|
+
const roleOf = directory.roleOf;
|
|
352
|
+
if (typeof roleOf !== "function") return void 0;
|
|
353
|
+
const catalogRoutes = directory.catalogRoutes;
|
|
354
|
+
const routes = typeof catalogRoutes === "function" ? catalogRoutes.call(directory) : void 0;
|
|
355
|
+
const role = roleOf.call(directory, routes?.[key] ?? key);
|
|
356
|
+
return typeof role === "string" ? role : void 0;
|
|
357
|
+
}
|
|
358
|
+
/** Whether a ProviderDirectory-owned role marks an Agent provider. */
|
|
359
|
+
function isAgentRole(role) {
|
|
360
|
+
return role === AGENT_ROLE;
|
|
361
|
+
}
|
|
362
|
+
/** Resolve a stored native `…-high|medium|low` id to the collapsed catalog row. */
|
|
363
|
+
function matchCatalogModel(models, modelId) {
|
|
364
|
+
if (modelId === void 0) return void 0;
|
|
365
|
+
const exact = models.find((model) => model.id === modelId);
|
|
366
|
+
if (exact !== void 0) return { model: exact };
|
|
367
|
+
for (const effort of NATIVE_EFFORTS) {
|
|
368
|
+
const suffix = "-" + effort;
|
|
369
|
+
if (!modelId.endsWith(suffix) || modelId.length <= suffix.length) continue;
|
|
370
|
+
const logical = models.find((model) => model.id === modelId.slice(0, -suffix.length));
|
|
371
|
+
if (logical?.reasoning?.efforts.some((option) => option.id === effort)) return {
|
|
372
|
+
model: logical,
|
|
373
|
+
effort
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
//#endregion
|
|
378
|
+
//#region src/client/main-row-controller.ts
|
|
379
|
+
/** Build a settings route for a newly selected model using only that model's default effort. */
|
|
380
|
+
function selectRouteModel(groups, provider, model) {
|
|
381
|
+
const defaultEffort = groups.find((group) => group.id === provider)?.models.find((item) => item.id === model)?.reasoning?.defaultEffort;
|
|
382
|
+
return {
|
|
383
|
+
provider,
|
|
384
|
+
model,
|
|
385
|
+
...defaultEffort === void 0 ? {} : { reasoningEffort: defaultEffort }
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
function deriveRouteChoices(groups, route, allowedProviders) {
|
|
389
|
+
const allowed = allowedProviders === void 0 ? void 0 : new Set(allowedProviders);
|
|
390
|
+
const providers = groups.filter((group) => allowed === void 0 || allowed.has(group.id)).map((group) => ({
|
|
391
|
+
id: group.id,
|
|
392
|
+
name: group.name
|
|
393
|
+
}));
|
|
394
|
+
if (route?.provider !== void 0 && !providers.some((option) => option.id === route.provider)) providers.push({
|
|
395
|
+
id: route.provider,
|
|
396
|
+
name: route.provider,
|
|
397
|
+
unavailable: true
|
|
398
|
+
});
|
|
399
|
+
const models = (groups.find((item) => item.id === route?.provider)?.models ?? []).map((model) => ({
|
|
400
|
+
id: model.id,
|
|
401
|
+
name: model.name
|
|
402
|
+
}));
|
|
403
|
+
if (route?.model !== void 0 && !models.some((option) => option.id === route.model)) models.push({
|
|
404
|
+
id: route.model,
|
|
405
|
+
name: route.model,
|
|
406
|
+
unavailable: true
|
|
407
|
+
});
|
|
408
|
+
return {
|
|
409
|
+
providers,
|
|
410
|
+
models
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
function expectedMainRevision(mirror, accepted) {
|
|
414
|
+
return Math.max(mirror, accepted ?? mirror);
|
|
415
|
+
}
|
|
416
|
+
function acceptedRevisionAfterFailure(accepted, error) {
|
|
417
|
+
return error instanceof MainSettingsConflictError ? void 0 : accepted;
|
|
418
|
+
}
|
|
419
|
+
function deriveMainChoices(groups, draft) {
|
|
420
|
+
const providers = groups.map((group) => ({
|
|
421
|
+
id: group.id,
|
|
422
|
+
name: group.name
|
|
423
|
+
}));
|
|
424
|
+
if (draft !== void 0 && !providers.some((option) => option.id === draft.provider)) providers.push({
|
|
425
|
+
id: draft.provider,
|
|
426
|
+
name: draft.provider
|
|
427
|
+
});
|
|
428
|
+
const group = groups.find((item) => item.id === draft?.provider);
|
|
429
|
+
const models = (group?.models ?? []).map((model) => ({
|
|
430
|
+
id: model.id,
|
|
431
|
+
name: model.name
|
|
432
|
+
}));
|
|
433
|
+
if (draft !== void 0 && !models.some((option) => option.id === draft.model)) models.push({
|
|
434
|
+
id: draft.model,
|
|
435
|
+
name: draft.model
|
|
436
|
+
});
|
|
437
|
+
const efforts = ((group?.models.find((item) => item.id === draft?.model))?.reasoning?.efforts ?? []).map((effort) => ({
|
|
438
|
+
id: effort.id,
|
|
439
|
+
name: effort.name
|
|
440
|
+
}));
|
|
441
|
+
if (draft?.reasoningEffort !== void 0 && !efforts.some((option) => option.id === draft.reasoningEffort)) efforts.push({
|
|
442
|
+
id: draft.reasoningEffort,
|
|
443
|
+
name: draft.reasoningEffort
|
|
444
|
+
});
|
|
445
|
+
return {
|
|
446
|
+
providers,
|
|
447
|
+
models,
|
|
448
|
+
efforts
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
function useModelSwitchSettingsController(input) {
|
|
452
|
+
const main = input.useMainSettings((value) => value);
|
|
453
|
+
const subagent = input.useSubagentSettings((value) => value);
|
|
454
|
+
const [draft, setDraft] = (0, react.useState)(main.value);
|
|
455
|
+
const [groups, setGroups] = (0, react.useState)([]);
|
|
456
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
457
|
+
const [message, setMessage] = (0, react.useState)();
|
|
458
|
+
const locked = (0, react.useRef)(false);
|
|
459
|
+
const acceptedRevision = (0, react.useRef)();
|
|
460
|
+
(0, react.useEffect)(() => {
|
|
461
|
+
setDraft(main.value);
|
|
462
|
+
setMessage(void 0);
|
|
463
|
+
if (main.revision !== void 0 && (acceptedRevision.current === void 0 || main.revision > acceptedRevision.current)) acceptedRevision.current = main.revision;
|
|
464
|
+
}, [main.revision, main.value]);
|
|
465
|
+
(0, react.useEffect)(() => {
|
|
466
|
+
let live = true;
|
|
467
|
+
const load = () => {
|
|
468
|
+
input.loadCatalog().then((value) => {
|
|
469
|
+
if (live) setGroups(value);
|
|
470
|
+
}).catch(() => {
|
|
471
|
+
if (live) setMessage(input.t("catalogFailed"));
|
|
472
|
+
});
|
|
473
|
+
};
|
|
474
|
+
load();
|
|
475
|
+
const stop = input.subscribeProviderOrder?.(() => {
|
|
476
|
+
if (live) load();
|
|
477
|
+
});
|
|
478
|
+
return () => {
|
|
479
|
+
live = false;
|
|
480
|
+
stop?.();
|
|
481
|
+
};
|
|
482
|
+
}, [
|
|
483
|
+
input.loadCatalog,
|
|
484
|
+
input.subscribeProviderOrder,
|
|
485
|
+
input.t
|
|
486
|
+
]);
|
|
487
|
+
const { providers, models, efforts } = deriveMainChoices(groups, draft);
|
|
488
|
+
const disabled = main.status !== "ready" || !main.writable || draft === void 0 || busy || draft.provider.trim() === "" || draft.model.trim() === "";
|
|
489
|
+
const setProvider = (provider) => setDraft((current) => {
|
|
490
|
+
if (current === void 0) return current;
|
|
491
|
+
const first = groups.find((item) => item.id === provider)?.models[0];
|
|
492
|
+
return selectRouteModel(groups, provider, first?.id ?? current.model);
|
|
493
|
+
});
|
|
494
|
+
const setModel = (id) => setDraft((current) => current === void 0 ? current : selectRouteModel(groups, current.provider, id));
|
|
495
|
+
const setReasoningEffort = (value) => setDraft((current) => {
|
|
496
|
+
if (current === void 0) return current;
|
|
497
|
+
const next = { ...current };
|
|
498
|
+
if (value === "") delete next.reasoningEffort;
|
|
499
|
+
else next.reasoningEffort = value;
|
|
500
|
+
return next;
|
|
501
|
+
});
|
|
502
|
+
const reset = () => {
|
|
503
|
+
setDraft(main.value);
|
|
504
|
+
setMessage(void 0);
|
|
505
|
+
};
|
|
506
|
+
const save = async () => {
|
|
507
|
+
if (disabled || locked.current || draft === void 0) return;
|
|
508
|
+
const mirrorRevision = main.revision;
|
|
509
|
+
if (mirrorRevision === void 0) {
|
|
510
|
+
setMessage(input.t("requestFailed"));
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
const expectedRevision = expectedMainRevision(mirrorRevision, acceptedRevision.current);
|
|
514
|
+
locked.current = true;
|
|
515
|
+
setBusy(true);
|
|
516
|
+
setMessage(void 0);
|
|
517
|
+
try {
|
|
518
|
+
acceptedRevision.current = await input.saveMain(draft, expectedRevision);
|
|
519
|
+
setMessage(input.t("saved"));
|
|
520
|
+
} catch (error) {
|
|
521
|
+
acceptedRevision.current = acceptedRevisionAfterFailure(acceptedRevision.current, error);
|
|
522
|
+
setMessage(error instanceof Error ? error.message : input.t("requestFailed"));
|
|
523
|
+
} finally {
|
|
524
|
+
locked.current = false;
|
|
525
|
+
setBusy(false);
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
return {
|
|
529
|
+
main,
|
|
530
|
+
subagent,
|
|
531
|
+
draft,
|
|
532
|
+
groups,
|
|
533
|
+
providers,
|
|
534
|
+
models,
|
|
535
|
+
efforts,
|
|
536
|
+
busy,
|
|
537
|
+
message,
|
|
538
|
+
disabled,
|
|
539
|
+
setProvider,
|
|
540
|
+
setModel,
|
|
541
|
+
setReasoningEffort,
|
|
542
|
+
reset,
|
|
543
|
+
save
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
//#endregion
|
|
547
|
+
//#region \0dsh-css:src/client/ModelSwitchSettings.module.css.mjs
|
|
548
|
+
const css$2 = ".djvrPG_section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex;container-type:inline-size}.djvrPG_title{margin:0;font-size:16px;font-weight:500;line-height:24px}.djvrPG_intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}.djvrPG_saved{color:var(--dsw-alias-state-success-primary);align-items:center;gap:6px;margin:0;font-size:12px;line-height:18px;display:flex}.djvrPG_savedDot{background:currentColor;border-radius:50%;width:7px;height:7px}.djvrPG_group{flex-direction:column;gap:8px;margin-top:8px;display:flex}.djvrPG_groupLabel{color:var(--dsw-alias-label-tertiary);margin:0 2px;font-size:12px;font-weight:500;line-height:18px}.djvrPG_routeCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);border-radius:10px;list-style:none;overflow:hidden}.djvrPG_routeCardUnavailable{opacity:.72}.djvrPG_routeHeader{width:100%;min-height:68px;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;justify-content:space-between;align-items:center;gap:16px;padding:12px 14px;display:flex}.djvrPG_routeHeader:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.djvrPG_routeHeader:disabled{cursor:default}.djvrPG_routeIcon{width:18px;height:18px;color:var(--dsw-alias-label-primary);flex:none;display:block}.djvrPG_routeCopy{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.djvrPG_routeName{color:var(--dsw-alias-label-primary);align-items:center;gap:8px;font-size:14px;font-weight:600;line-height:20px;display:flex}.djvrPG_routeSummary{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:13px;line-height:18px;overflow:hidden}.djvrPG_badge{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.djvrPG_badgeWarn{color:var(--dsw-alias-state-warn-label)}.djvrPG_chevron{width:18px;height:18px;color:var(--dsw-alias-label-primary);flex:none;transition:transform .16s}.djvrPG_routeCardOpen .djvrPG_chevron{transform:rotate(180deg)}.djvrPG_cardBody{border-top:1px solid var(--dsw-alias-border-l2);padding:16px 14px 18px}.djvrPG_formGrid{grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;display:grid}.djvrPG_field{flex-direction:column;gap:6px;min-width:0;display:flex}.djvrPG_fieldFull{grid-column:1/-1}.djvrPG_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px}.djvrPG_input{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);appearance:none;background-color:var(--dsw-alias-bg-layer-1);width:100%;height:32px;color:var(--dsw-alias-label-primary);font:inherit;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%2381858C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");background-position:right 10px center;background-repeat:no-repeat;border-radius:8px;padding:0 32px 0 10px;font-size:14px;line-height:22px}.djvrPG_textInput{background-image:none;padding-right:10px}.djvrPG_input:focus{border-color:var(--dsw-alias-brand-primary);outline:none}.djvrPG_input:disabled{opacity:.6;cursor:default}.djvrPG_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.djvrPG_warning{color:var(--dsw-alias-state-warn-label)}.djvrPG_message{flex:1;min-width:0}.djvrPG_cardFooter{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;margin-top:14px;padding:12px 0 0;display:flex}.djvrPG_button{box-sizing:border-box;height:36px;font:inherit;cursor:pointer;border-radius:18px;justify-content:center;align-items:center;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.djvrPG_secondaryButton{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:0 0}.djvrPG_secondaryButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.djvrPG_primaryButton{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);border:0}.djvrPG_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.djvrPG_button:disabled{opacity:.4;cursor:default}.djvrPG_button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}@container (width<=420px){.djvrPG_formGrid{grid-template-columns:1fr}.djvrPG_cardFooter .djvrPG_button{flex:1}}.djvrPG_routeHeaderRow{align-items:center;gap:8px;padding-right:14px;display:flex}.djvrPG_routeHeaderRow .djvrPG_routeHeader{flex:1;min-width:0}.djvrPG_rowCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);border-radius:10px;align-items:flex-start;gap:16px;padding:14px 16px;display:flex}.djvrPG_rowCopy{flex:1;gap:4px;min-width:0;display:grid}.djvrPG_rowTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600;line-height:20px}.djvrPG_switch{background:var(--dsw-alias-bg-layer-1);width:40px;height:24px;box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l2);cursor:pointer;border:0;border-radius:999px;flex:none;padding:0;position:relative}.djvrPG_switchOn{background:var(--dsw-alias-button-primary-fill);box-shadow:none}.djvrPG_switch i{background:var(--dsw-alias-label-primary-foreground);pointer-events:none;border-radius:50%;width:18px;height:18px;transition:left .16s;position:absolute;top:3px;left:3px}.djvrPG_switchOn i{left:19px}.djvrPG_switch:disabled{opacity:.4;cursor:default}.djvrPG_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}@media (prefers-reduced-motion:reduce){.djvrPG_chevron,.djvrPG_switch i{transition:none}}";
|
|
549
|
+
const tagId$2 = "dsh-model-switch/ModelSwitchSettings.module.css";
|
|
550
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
551
|
+
const tag = document.createElement("style");
|
|
552
|
+
tag.dataset.plugin = "dsh-model-switch";
|
|
553
|
+
tag.dataset.pluginCss = tagId$2;
|
|
554
|
+
tag.textContent = css$2;
|
|
555
|
+
document.head.appendChild(tag);
|
|
556
|
+
}
|
|
557
|
+
var ModelSwitchSettings_module_css_default = {
|
|
558
|
+
"badge": "djvrPG_badge",
|
|
559
|
+
"badgeWarn": "djvrPG_badgeWarn",
|
|
560
|
+
"button": "djvrPG_button",
|
|
561
|
+
"cardBody": "djvrPG_cardBody",
|
|
562
|
+
"cardFooter": "djvrPG_cardFooter",
|
|
563
|
+
"chevron": "djvrPG_chevron",
|
|
564
|
+
"field": "djvrPG_field",
|
|
565
|
+
"fieldFull": "djvrPG_fieldFull",
|
|
566
|
+
"fieldLabel": "djvrPG_fieldLabel",
|
|
567
|
+
"formGrid": "djvrPG_formGrid",
|
|
568
|
+
"group": "djvrPG_group",
|
|
569
|
+
"groupLabel": "djvrPG_groupLabel",
|
|
570
|
+
"hint": "djvrPG_hint",
|
|
571
|
+
"input": "djvrPG_input",
|
|
572
|
+
"intro": "djvrPG_intro",
|
|
573
|
+
"message": "djvrPG_message",
|
|
574
|
+
"primaryButton": "djvrPG_primaryButton",
|
|
575
|
+
"routeCard": "djvrPG_routeCard",
|
|
576
|
+
"routeCardOpen": "djvrPG_routeCardOpen",
|
|
577
|
+
"routeCardUnavailable": "djvrPG_routeCardUnavailable",
|
|
578
|
+
"routeCopy": "djvrPG_routeCopy",
|
|
579
|
+
"routeHeader": "djvrPG_routeHeader",
|
|
580
|
+
"routeHeaderRow": "djvrPG_routeHeaderRow",
|
|
581
|
+
"routeIcon": "djvrPG_routeIcon",
|
|
582
|
+
"routeName": "djvrPG_routeName",
|
|
583
|
+
"routeSummary": "djvrPG_routeSummary",
|
|
584
|
+
"rowCard": "djvrPG_rowCard",
|
|
585
|
+
"rowCopy": "djvrPG_rowCopy",
|
|
586
|
+
"rowTitle": "djvrPG_rowTitle",
|
|
587
|
+
"saved": "djvrPG_saved",
|
|
588
|
+
"savedDot": "djvrPG_savedDot",
|
|
589
|
+
"secondaryButton": "djvrPG_secondaryButton",
|
|
590
|
+
"section": "djvrPG_section",
|
|
591
|
+
"switch": "djvrPG_switch",
|
|
592
|
+
"switchOn": "djvrPG_switchOn",
|
|
593
|
+
"textInput": "djvrPG_textInput",
|
|
594
|
+
"title": "djvrPG_title",
|
|
595
|
+
"warning": "djvrPG_warning"
|
|
596
|
+
};
|
|
597
|
+
//#endregion
|
|
598
|
+
//#region src/client/ModelSwitchSettings.tsx
|
|
599
|
+
function cx(...values) {
|
|
600
|
+
return values.filter(Boolean).join(" ");
|
|
601
|
+
}
|
|
602
|
+
function compact(...values) {
|
|
603
|
+
return values.filter((value) => value !== void 0 && value !== "").join(" · ");
|
|
604
|
+
}
|
|
605
|
+
function RouteIcon({ kind }) {
|
|
606
|
+
if (kind === "main") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
607
|
+
width: "18",
|
|
608
|
+
height: "18",
|
|
609
|
+
viewBox: "0 0 18 18",
|
|
610
|
+
fill: "none",
|
|
611
|
+
"aria-hidden": "true",
|
|
612
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
613
|
+
d: "M3 4.5h12v8H8l-3.5 2v-2H3v-8Z",
|
|
614
|
+
stroke: "currentColor",
|
|
615
|
+
strokeLinejoin: "round"
|
|
616
|
+
})
|
|
617
|
+
});
|
|
618
|
+
if (kind === "subagent") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
619
|
+
width: "18",
|
|
620
|
+
height: "18",
|
|
621
|
+
viewBox: "0 0 18 18",
|
|
622
|
+
fill: "none",
|
|
623
|
+
"aria-hidden": "true",
|
|
624
|
+
children: [
|
|
625
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
626
|
+
cx: "6",
|
|
627
|
+
cy: "6",
|
|
628
|
+
r: "2.5",
|
|
629
|
+
stroke: "currentColor"
|
|
630
|
+
}),
|
|
631
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
632
|
+
cx: "12.5",
|
|
633
|
+
cy: "11.5",
|
|
634
|
+
r: "2",
|
|
635
|
+
stroke: "currentColor"
|
|
636
|
+
}),
|
|
637
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
638
|
+
d: "M3 14c.4-2.5 1.6-4 3-4s2.6 1.5 3 4M10 7.5c.5-.8 1.3-1.2 2.2-1.2 1.5 0 2.6 1 2.8 2.7",
|
|
639
|
+
stroke: "currentColor",
|
|
640
|
+
strokeLinecap: "round"
|
|
641
|
+
})
|
|
642
|
+
]
|
|
643
|
+
});
|
|
644
|
+
if (kind === "search") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
645
|
+
width: "18",
|
|
646
|
+
height: "18",
|
|
647
|
+
viewBox: "0 0 18 18",
|
|
648
|
+
fill: "none",
|
|
649
|
+
"aria-hidden": "true",
|
|
650
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
651
|
+
cx: "8",
|
|
652
|
+
cy: "8",
|
|
653
|
+
r: "4.5",
|
|
654
|
+
stroke: "currentColor"
|
|
655
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
656
|
+
d: "m11.5 11.5 3.5 3.5",
|
|
657
|
+
stroke: "currentColor",
|
|
658
|
+
strokeLinecap: "round"
|
|
659
|
+
})]
|
|
660
|
+
});
|
|
661
|
+
if (kind === "image") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
662
|
+
width: "18",
|
|
663
|
+
height: "18",
|
|
664
|
+
viewBox: "0 0 18 18",
|
|
665
|
+
fill: "none",
|
|
666
|
+
"aria-hidden": "true",
|
|
667
|
+
children: [
|
|
668
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
669
|
+
x: "2.5",
|
|
670
|
+
y: "3",
|
|
671
|
+
width: "13",
|
|
672
|
+
height: "12",
|
|
673
|
+
rx: "2",
|
|
674
|
+
stroke: "currentColor"
|
|
675
|
+
}),
|
|
676
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
677
|
+
cx: "6.5",
|
|
678
|
+
cy: "7",
|
|
679
|
+
r: "1.3",
|
|
680
|
+
stroke: "currentColor"
|
|
681
|
+
}),
|
|
682
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
683
|
+
d: "m4 13 3.5-3 2.2 2 1.8-1.6 2.5 2.6",
|
|
684
|
+
stroke: "currentColor",
|
|
685
|
+
strokeLinejoin: "round"
|
|
686
|
+
})
|
|
687
|
+
]
|
|
688
|
+
});
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
function RouteCard({ title, summary, hint, icon, open, onToggle, disabled = false, badge, badgeWarn = false, trailing, expandable = true, children }) {
|
|
692
|
+
const header = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
693
|
+
type: "button",
|
|
694
|
+
className: ModelSwitchSettings_module_css_default.routeHeader,
|
|
695
|
+
disabled,
|
|
696
|
+
"aria-expanded": disabled || !expandable ? void 0 : open,
|
|
697
|
+
onClick: onToggle,
|
|
698
|
+
children: [
|
|
699
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
700
|
+
className: ModelSwitchSettings_module_css_default.routeIcon,
|
|
701
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteIcon, { kind: icon })
|
|
702
|
+
}),
|
|
703
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
704
|
+
className: ModelSwitchSettings_module_css_default.routeCopy,
|
|
705
|
+
children: [
|
|
706
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
707
|
+
className: ModelSwitchSettings_module_css_default.routeName,
|
|
708
|
+
children: [title, badge === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
|
|
709
|
+
className: cx(ModelSwitchSettings_module_css_default.badge, badgeWarn && ModelSwitchSettings_module_css_default.badgeWarn),
|
|
710
|
+
children: badge
|
|
711
|
+
})]
|
|
712
|
+
}),
|
|
713
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
714
|
+
className: ModelSwitchSettings_module_css_default.routeSummary,
|
|
715
|
+
children: summary
|
|
716
|
+
}),
|
|
717
|
+
hint === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
718
|
+
className: ModelSwitchSettings_module_css_default.hint,
|
|
719
|
+
children: hint
|
|
720
|
+
})
|
|
721
|
+
]
|
|
722
|
+
}),
|
|
723
|
+
disabled || !expandable ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
724
|
+
className: ModelSwitchSettings_module_css_default.chevron,
|
|
725
|
+
width: "14",
|
|
726
|
+
height: "14",
|
|
727
|
+
viewBox: "0 0 14 14",
|
|
728
|
+
fill: "none",
|
|
729
|
+
"aria-hidden": "true",
|
|
730
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
731
|
+
d: "m4 5 3 3 3-3",
|
|
732
|
+
stroke: "currentColor",
|
|
733
|
+
strokeLinecap: "round",
|
|
734
|
+
strokeLinejoin: "round"
|
|
735
|
+
})
|
|
736
|
+
})
|
|
737
|
+
]
|
|
738
|
+
});
|
|
739
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
740
|
+
className: cx(ModelSwitchSettings_module_css_default.routeCard, open && ModelSwitchSettings_module_css_default.routeCardOpen, disabled && ModelSwitchSettings_module_css_default.routeCardUnavailable),
|
|
741
|
+
children: [trailing === void 0 ? header : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
742
|
+
className: ModelSwitchSettings_module_css_default.routeHeaderRow,
|
|
743
|
+
children: [header, trailing]
|
|
744
|
+
}), open && !disabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
745
|
+
className: ModelSwitchSettings_module_css_default.cardBody,
|
|
746
|
+
children
|
|
747
|
+
}) : null]
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
function InstantSwitch({ checked, disabled, label, onChange }) {
|
|
751
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
752
|
+
type: "button",
|
|
753
|
+
className: cx(ModelSwitchSettings_module_css_default.switch, checked && ModelSwitchSettings_module_css_default.switchOn),
|
|
754
|
+
role: "switch",
|
|
755
|
+
"aria-checked": checked,
|
|
756
|
+
"aria-label": label,
|
|
757
|
+
disabled,
|
|
758
|
+
onClick: () => {
|
|
759
|
+
if (!disabled) onChange(!checked);
|
|
760
|
+
},
|
|
761
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {})
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
function Field({ label, value, disabled, choices, onChange, full = false }) {
|
|
765
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
766
|
+
className: cx(ModelSwitchSettings_module_css_default.field, full && ModelSwitchSettings_module_css_default.fieldFull),
|
|
767
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
768
|
+
className: ModelSwitchSettings_module_css_default.fieldLabel,
|
|
769
|
+
children: label
|
|
770
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
771
|
+
className: ModelSwitchSettings_module_css_default.input,
|
|
772
|
+
disabled,
|
|
773
|
+
value: value ?? "",
|
|
774
|
+
onChange: (event) => {
|
|
775
|
+
onChange(event.target.value);
|
|
776
|
+
},
|
|
777
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
778
|
+
value: "",
|
|
779
|
+
children: "—"
|
|
780
|
+
}), choices.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
781
|
+
value: option.id,
|
|
782
|
+
children: [option.name, option.unavailable === true ? " ⚠" : ""]
|
|
783
|
+
}, option.id))]
|
|
784
|
+
})]
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
function Actions({ t, busy, disabled, message, onCancel, onSave }) {
|
|
788
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
789
|
+
className: ModelSwitchSettings_module_css_default.cardFooter,
|
|
790
|
+
children: [
|
|
791
|
+
message === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
792
|
+
className: cx(ModelSwitchSettings_module_css_default.hint, ModelSwitchSettings_module_css_default.message),
|
|
793
|
+
children: message
|
|
794
|
+
}),
|
|
795
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
796
|
+
type: "button",
|
|
797
|
+
className: cx(ModelSwitchSettings_module_css_default.button, ModelSwitchSettings_module_css_default.secondaryButton),
|
|
798
|
+
disabled: busy,
|
|
799
|
+
onClick: onCancel,
|
|
800
|
+
children: t("cancel")
|
|
801
|
+
}),
|
|
802
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
803
|
+
type: "button",
|
|
804
|
+
className: cx(ModelSwitchSettings_module_css_default.button, ModelSwitchSettings_module_css_default.primaryButton),
|
|
805
|
+
disabled,
|
|
806
|
+
onClick: onSave,
|
|
807
|
+
children: busy ? t("saving") : t("save")
|
|
808
|
+
})
|
|
809
|
+
]
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
function useDraft(snapshot) {
|
|
813
|
+
const [draft, setDraft] = (0, react.useState)(snapshot.value);
|
|
814
|
+
(0, react.useEffect)(() => {
|
|
815
|
+
setDraft(snapshot.value);
|
|
816
|
+
}, [snapshot.revision, snapshot.value]);
|
|
817
|
+
return [
|
|
818
|
+
draft,
|
|
819
|
+
setDraft,
|
|
820
|
+
() => {
|
|
821
|
+
setDraft(snapshot.value);
|
|
822
|
+
}
|
|
823
|
+
];
|
|
824
|
+
}
|
|
825
|
+
function routeName(groups, route) {
|
|
826
|
+
if (route === void 0) return "";
|
|
827
|
+
const provider = groups.find((group) => group.id === route.provider);
|
|
828
|
+
const model = matchCatalogModel(provider?.models ?? [], route.model)?.model;
|
|
829
|
+
return compact(provider?.name ?? route.provider, model?.name ?? route.model);
|
|
830
|
+
}
|
|
831
|
+
function routeDefaultEffort(groups, route) {
|
|
832
|
+
if (route === void 0) return void 0;
|
|
833
|
+
const matched = matchCatalogModel(groups.find((group) => group.id === route.provider)?.models ?? [], route.model);
|
|
834
|
+
return matched?.effort ?? matched?.model.reasoning?.defaultEffort;
|
|
835
|
+
}
|
|
836
|
+
function capabilityChoices(groups, route, providers, kind) {
|
|
837
|
+
const choices = deriveRouteChoices(groups, route, providers);
|
|
838
|
+
if (kind === "image" && route?.provider === "grok") {
|
|
839
|
+
const models = [{
|
|
840
|
+
id: "grok-imagine-image-quality",
|
|
841
|
+
name: "Grok Imagine 1.0"
|
|
842
|
+
}];
|
|
843
|
+
if (route.model !== void 0 && !models.some((model) => model.id === route.model)) models.push({
|
|
844
|
+
id: route.model,
|
|
845
|
+
name: route.model,
|
|
846
|
+
unavailable: true
|
|
847
|
+
});
|
|
848
|
+
return {
|
|
849
|
+
providers: choices.providers,
|
|
850
|
+
models
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
return choices;
|
|
854
|
+
}
|
|
855
|
+
function ModelSwitchSettings(props) {
|
|
856
|
+
const controller = useModelSwitchSettingsController(props);
|
|
857
|
+
const { main, subagent, draft, groups } = controller;
|
|
858
|
+
const search = props.useSearchSettings((value) => value);
|
|
859
|
+
const image = props.useImageSettings((value) => value);
|
|
860
|
+
const switchSettings = props.useSwitchSettings((value) => value);
|
|
861
|
+
const [open, setOpen] = (0, react.useState)();
|
|
862
|
+
const [subagentDraft, setSubagentDraft, resetSubagent] = useDraft(subagent);
|
|
863
|
+
const [searchDraft, setSearchDraft, resetSearch] = useDraft(search);
|
|
864
|
+
const [imageDraft, setImageDraft, resetImage] = useDraft(image);
|
|
865
|
+
const [busy, setBusy] = (0, react.useState)();
|
|
866
|
+
const [message, setMessage] = (0, react.useState)();
|
|
867
|
+
const [compactBusy, setCompactBusy] = (0, react.useState)(false);
|
|
868
|
+
const [compactError, setCompactError] = (0, react.useState)();
|
|
869
|
+
const loadSearchCapabilities = props.loadCapabilities;
|
|
870
|
+
const [searchSnapshot, setSearchSnapshot] = (0, react.useState)(void 0);
|
|
871
|
+
const [searchError, setSearchError] = (0, react.useState)(void 0);
|
|
872
|
+
(0, react.useEffect)(() => {
|
|
873
|
+
if (loadSearchCapabilities === void 0) return;
|
|
874
|
+
let live = true;
|
|
875
|
+
const scope = new AbortController();
|
|
876
|
+
let timer;
|
|
877
|
+
let failures = 0;
|
|
878
|
+
let signature;
|
|
879
|
+
const poll = async (revision) => {
|
|
880
|
+
try {
|
|
881
|
+
const snapshot = await loadSearchCapabilities(revision, scope.signal);
|
|
882
|
+
if (!live) return;
|
|
883
|
+
const nextSignature = JSON.stringify(snapshot);
|
|
884
|
+
if (signature !== nextSignature || failures > 0) {
|
|
885
|
+
signature = nextSignature;
|
|
886
|
+
setSearchSnapshot(snapshot);
|
|
887
|
+
setSearchError(void 0);
|
|
888
|
+
}
|
|
889
|
+
failures = 0;
|
|
890
|
+
poll(snapshot.revision);
|
|
891
|
+
} catch (error) {
|
|
892
|
+
if (!live || scope.signal.aborted) return;
|
|
893
|
+
failures = Math.min(failures + 1, 8);
|
|
894
|
+
if (failures >= 3) setSearchError(error instanceof Error ? error.message : props.t("catalogFailed"));
|
|
895
|
+
timer = setTimeout(() => {
|
|
896
|
+
if (live) poll(void 0);
|
|
897
|
+
}, Math.min(250 * 2 ** (failures - 1), 2e4));
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
poll(void 0);
|
|
901
|
+
return () => {
|
|
902
|
+
live = false;
|
|
903
|
+
scope.abort();
|
|
904
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
905
|
+
};
|
|
906
|
+
}, [loadSearchCapabilities]);
|
|
907
|
+
if ([
|
|
908
|
+
controller.main,
|
|
909
|
+
controller.subagent,
|
|
910
|
+
search,
|
|
911
|
+
image,
|
|
912
|
+
switchSettings
|
|
913
|
+
].some((form) => form.mode === "memory")) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
|
|
914
|
+
className: ModelSwitchSettings_module_css_default.section,
|
|
915
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
|
|
916
|
+
className: ModelSwitchSettings_module_css_default.title,
|
|
917
|
+
children: props.t("title")
|
|
918
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
919
|
+
role: "status",
|
|
920
|
+
className: ModelSwitchSettings_module_css_default.hint,
|
|
921
|
+
children: props.t("remoteSettingsUnavailable")
|
|
922
|
+
})]
|
|
923
|
+
});
|
|
924
|
+
const unavailable = (key) => {
|
|
925
|
+
const reason = props.capabilities[key].reason;
|
|
926
|
+
return reason === void 0 ? props.t("unavailable") : props.t("reason." + reason);
|
|
927
|
+
};
|
|
928
|
+
const toggle = (route) => {
|
|
929
|
+
setOpen((current) => current === route ? void 0 : route);
|
|
930
|
+
setMessage(void 0);
|
|
931
|
+
};
|
|
932
|
+
const subagentRoute = subagentDraft === void 0 ? void 0 : {
|
|
933
|
+
...subagentDraft.provider === void 0 ? {} : { provider: subagentDraft.provider },
|
|
934
|
+
...subagentDraft.model === void 0 ? {} : { model: subagentDraft.model }
|
|
935
|
+
};
|
|
936
|
+
const subagentCatalogModel = matchCatalogModel(groups.find((group) => group.id === subagentDraft?.provider)?.models ?? [], subagentDraft?.model);
|
|
937
|
+
const subagentChoices = deriveRouteChoices(groups, subagentCatalogModel === void 0 ? subagentRoute : {
|
|
938
|
+
...subagentRoute,
|
|
939
|
+
model: subagentCatalogModel.model.id
|
|
940
|
+
});
|
|
941
|
+
const subagentEfforts = (() => {
|
|
942
|
+
const efforts = (subagentCatalogModel?.model.reasoning?.efforts ?? []).map((effort) => ({
|
|
943
|
+
id: effort.id,
|
|
944
|
+
name: effort.name
|
|
945
|
+
}));
|
|
946
|
+
if (subagentDraft?.reasoningEffort !== void 0 && !efforts.some((option) => option.id === subagentDraft.reasoningEffort)) efforts.push({
|
|
947
|
+
id: subagentDraft.reasoningEffort,
|
|
948
|
+
name: subagentDraft.reasoningEffort
|
|
949
|
+
});
|
|
950
|
+
return efforts;
|
|
951
|
+
})();
|
|
952
|
+
const defaultEffort = subagentDraft?.reasoningEffort ?? routeDefaultEffort(groups, subagentRoute);
|
|
953
|
+
const mainEffectiveEffort = draft?.reasoningEffort ?? routeDefaultEffort(groups, draft);
|
|
954
|
+
const searchLive = searchSnapshot?.capabilities;
|
|
955
|
+
const searchGroups = searchGroupsFromCapabilities(searchLive);
|
|
956
|
+
const searchChoices = capabilityChoices(searchGroups, searchDraft, void 0, "search");
|
|
957
|
+
const searchAvailable = searchLive?.searchProviderAdapters.available ?? false;
|
|
958
|
+
const imageChoices = capabilityChoices(groups, imageDraft, props.capabilities.imageProviderAdapters.providers ?? [], "image");
|
|
959
|
+
const synced = [
|
|
960
|
+
main,
|
|
961
|
+
subagent,
|
|
962
|
+
search,
|
|
963
|
+
image
|
|
964
|
+
].every((snapshot) => snapshot.status === "ready");
|
|
965
|
+
const run = async (route, operation) => {
|
|
966
|
+
if (busy !== void 0) return;
|
|
967
|
+
setBusy(route);
|
|
968
|
+
setMessage(void 0);
|
|
969
|
+
try {
|
|
970
|
+
await operation();
|
|
971
|
+
setMessage({
|
|
972
|
+
route,
|
|
973
|
+
text: props.t("saved")
|
|
974
|
+
});
|
|
975
|
+
} catch (error) {
|
|
976
|
+
setMessage({
|
|
977
|
+
route,
|
|
978
|
+
text: error instanceof Error ? error.message : props.t("requestFailed")
|
|
979
|
+
});
|
|
980
|
+
} finally {
|
|
981
|
+
setBusy(void 0);
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
const saveSubagent = () => {
|
|
985
|
+
if (subagentDraft === void 0 || subagentDraft.mode !== "fixed") return;
|
|
986
|
+
run("subagent", async () => {
|
|
987
|
+
if (subagent.value?.provider !== subagentDraft.provider) await props.setSubagent("provider", subagentDraft.provider);
|
|
988
|
+
const matched = subagentCatalogModel;
|
|
989
|
+
const nextModel = matched?.model.id ?? subagentDraft.model;
|
|
990
|
+
const nextEffort = subagentDraft.reasoningEffort === "" ? void 0 : subagentDraft.reasoningEffort || matched?.effort;
|
|
991
|
+
if (subagent.value?.model !== nextModel) await props.setSubagent("model", nextModel);
|
|
992
|
+
if (subagent.value?.reasoningEffort !== nextEffort) await props.setSubagent("reasoningEffort", nextEffort);
|
|
993
|
+
});
|
|
994
|
+
};
|
|
995
|
+
const persistSubagentEnabled = (enabled) => {
|
|
996
|
+
if (subagentDraft === void 0 || busy === "subagent" || !subagent.writable) return;
|
|
997
|
+
setSubagentDraft({
|
|
998
|
+
...subagentDraft,
|
|
999
|
+
mode: subagentModeForEnabled(enabled)
|
|
1000
|
+
});
|
|
1001
|
+
setBusy("subagent");
|
|
1002
|
+
setMessage(void 0);
|
|
1003
|
+
props.setSubagent("mode", subagentModeForEnabled(enabled)).then(() => {
|
|
1004
|
+
setBusy(void 0);
|
|
1005
|
+
setOpen((current) => enabled ? "subagent" : current === "subagent" ? void 0 : current);
|
|
1006
|
+
}, (error) => {
|
|
1007
|
+
setBusy(void 0);
|
|
1008
|
+
resetSubagent();
|
|
1009
|
+
setMessage({
|
|
1010
|
+
route: "subagent",
|
|
1011
|
+
text: error instanceof Error ? error.message : props.t("requestFailed")
|
|
1012
|
+
});
|
|
1013
|
+
});
|
|
1014
|
+
};
|
|
1015
|
+
const saveCapability = (route, current, next) => {
|
|
1016
|
+
if (next === void 0) return;
|
|
1017
|
+
run(route, async () => {
|
|
1018
|
+
if (current.value?.provider !== next.provider) await props.setCapability(route, "provider", next.provider);
|
|
1019
|
+
if (current.value?.model !== next.model) await props.setCapability(route, "model", next.model);
|
|
1020
|
+
});
|
|
1021
|
+
};
|
|
1022
|
+
const mainSummary = draft === void 0 ? props.t("loading") : compact(routeName(groups, draft), mainEffectiveEffort);
|
|
1023
|
+
const subagentRole = subagentDraft?.mode === "fixed" && subagentDraft.provider !== void 0 && subagentDraft.provider !== "" ? props.providerRoleOf?.(subagentDraft.provider) : void 0;
|
|
1024
|
+
const subagentOn = subagentDraft?.mode === "fixed";
|
|
1025
|
+
const subagentSummary = subagentDraft === void 0 ? props.t("loading") : subagentOn ? compact(routeName(groups, subagentRoute), isAgentRole(subagentRole) ? props.t("agentBadge") : void 0, defaultEffort === void 0 ? props.t("providerDefaultShort") : compact(props.t("providerDefaultShort"), defaultEffort)) : props.t("subagentOff");
|
|
1026
|
+
const subagentDisabled = subagent.status !== "ready" || !subagent.writable || subagentDraft === void 0 || busy === "subagent" || !subagentOn || (subagentDraft.provider ?? "").trim() === "" || (subagentDraft.model ?? "").trim() === "";
|
|
1027
|
+
const capabilityDisabled = (route, snapshot, next) => snapshot.status !== "ready" || !snapshot.writable || next === void 0 || busy === route || (next.provider ?? "").trim() === "" || (next.model ?? "").trim() === "";
|
|
1028
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
|
|
1029
|
+
className: ModelSwitchSettings_module_css_default.section,
|
|
1030
|
+
children: [
|
|
1031
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
|
|
1032
|
+
className: ModelSwitchSettings_module_css_default.title,
|
|
1033
|
+
children: props.t("title")
|
|
1034
|
+
}),
|
|
1035
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1036
|
+
className: ModelSwitchSettings_module_css_default.intro,
|
|
1037
|
+
children: props.t("subtitle")
|
|
1038
|
+
}),
|
|
1039
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1040
|
+
className: ModelSwitchSettings_module_css_default.saved,
|
|
1041
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { className: ModelSwitchSettings_module_css_default.savedDot }), synced ? props.t("settingsSynced") : props.t("loading")]
|
|
1042
|
+
}),
|
|
1043
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1044
|
+
className: ModelSwitchSettings_module_css_default.group,
|
|
1045
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
1046
|
+
className: ModelSwitchSettings_module_css_default.groupLabel,
|
|
1047
|
+
children: props.t("sendProtection")
|
|
1048
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1049
|
+
className: ModelSwitchSettings_module_css_default.rowCard,
|
|
1050
|
+
children: [
|
|
1051
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1052
|
+
className: ModelSwitchSettings_module_css_default.routeIcon,
|
|
1053
|
+
"aria-hidden": "true",
|
|
1054
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
1055
|
+
width: "18",
|
|
1056
|
+
height: "18",
|
|
1057
|
+
viewBox: "0 0 18 18",
|
|
1058
|
+
fill: "none",
|
|
1059
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1060
|
+
d: "M4 5.5h10M4 9h7M4 12.5h5",
|
|
1061
|
+
stroke: "currentColor",
|
|
1062
|
+
strokeLinecap: "round"
|
|
1063
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1064
|
+
d: "M13 11.5v3.5l2-1.6",
|
|
1065
|
+
stroke: "currentColor",
|
|
1066
|
+
strokeLinecap: "round",
|
|
1067
|
+
strokeLinejoin: "round"
|
|
1068
|
+
})]
|
|
1069
|
+
})
|
|
1070
|
+
}),
|
|
1071
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1072
|
+
className: ModelSwitchSettings_module_css_default.rowCopy,
|
|
1073
|
+
children: [
|
|
1074
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1075
|
+
className: ModelSwitchSettings_module_css_default.rowTitle,
|
|
1076
|
+
children: props.t("compactOnSwitch")
|
|
1077
|
+
}),
|
|
1078
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1079
|
+
className: ModelSwitchSettings_module_css_default.hint,
|
|
1080
|
+
children: props.t("compactOnSwitchHelp")
|
|
1081
|
+
}),
|
|
1082
|
+
compactError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1083
|
+
className: cx(ModelSwitchSettings_module_css_default.hint, ModelSwitchSettings_module_css_default.message),
|
|
1084
|
+
children: compactError
|
|
1085
|
+
})
|
|
1086
|
+
]
|
|
1087
|
+
}),
|
|
1088
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
|
|
1089
|
+
checked: switchSettings.value?.compactOnSwitch !== false,
|
|
1090
|
+
disabled: !switchSettings.writable || compactBusy,
|
|
1091
|
+
label: props.t("compactOnSwitch"),
|
|
1092
|
+
onChange: (value) => {
|
|
1093
|
+
setCompactBusy(true);
|
|
1094
|
+
setCompactError(void 0);
|
|
1095
|
+
props.setCompactOnSwitch(value).then(() => {
|
|
1096
|
+
setCompactBusy(false);
|
|
1097
|
+
}, (error) => {
|
|
1098
|
+
setCompactBusy(false);
|
|
1099
|
+
setCompactError(error instanceof Error ? error.message : props.t("requestFailed"));
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
})
|
|
1103
|
+
]
|
|
1104
|
+
})]
|
|
1105
|
+
}),
|
|
1106
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1107
|
+
className: ModelSwitchSettings_module_css_default.group,
|
|
1108
|
+
children: [
|
|
1109
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
1110
|
+
className: ModelSwitchSettings_module_css_default.groupLabel,
|
|
1111
|
+
children: props.t("conversationRoutes")
|
|
1112
|
+
}),
|
|
1113
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteCard, {
|
|
1114
|
+
title: props.t("main"),
|
|
1115
|
+
summary: mainSummary,
|
|
1116
|
+
icon: "main",
|
|
1117
|
+
open: open === "main",
|
|
1118
|
+
onToggle: () => {
|
|
1119
|
+
toggle("main");
|
|
1120
|
+
},
|
|
1121
|
+
badge: props.t("defaultBadge"),
|
|
1122
|
+
children: draft === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1123
|
+
className: ModelSwitchSettings_module_css_default.hint,
|
|
1124
|
+
children: props.t("loading")
|
|
1125
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1126
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1127
|
+
className: ModelSwitchSettings_module_css_default.formGrid,
|
|
1128
|
+
children: [
|
|
1129
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
1130
|
+
label: props.t("provider"),
|
|
1131
|
+
value: draft.provider,
|
|
1132
|
+
disabled: controller.busy || !main.writable,
|
|
1133
|
+
choices: controller.providers,
|
|
1134
|
+
onChange: controller.setProvider
|
|
1135
|
+
}),
|
|
1136
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
1137
|
+
label: props.t("model"),
|
|
1138
|
+
value: draft.model,
|
|
1139
|
+
disabled: controller.busy || !main.writable,
|
|
1140
|
+
choices: controller.models,
|
|
1141
|
+
onChange: controller.setModel
|
|
1142
|
+
}),
|
|
1143
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
1144
|
+
label: props.t("effort"),
|
|
1145
|
+
value: draft.reasoningEffort ?? "",
|
|
1146
|
+
disabled: controller.busy || !main.writable,
|
|
1147
|
+
choices: controller.efforts,
|
|
1148
|
+
onChange: controller.setReasoningEffort
|
|
1149
|
+
})
|
|
1150
|
+
]
|
|
1151
|
+
}),
|
|
1152
|
+
!main.writable && main.status === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1153
|
+
className: ModelSwitchSettings_module_css_default.hint,
|
|
1154
|
+
children: props.t("readonly")
|
|
1155
|
+
}) : null,
|
|
1156
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Actions, {
|
|
1157
|
+
t: props.t,
|
|
1158
|
+
busy: controller.busy,
|
|
1159
|
+
disabled: controller.disabled,
|
|
1160
|
+
...controller.message === void 0 ? {} : { message: controller.message },
|
|
1161
|
+
onCancel: controller.reset,
|
|
1162
|
+
onSave: () => {
|
|
1163
|
+
controller.save();
|
|
1164
|
+
}
|
|
1165
|
+
})
|
|
1166
|
+
] })
|
|
1167
|
+
}),
|
|
1168
|
+
props.capabilities.centralSubagentRouting.available ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteCard, {
|
|
1169
|
+
title: props.t("subagent"),
|
|
1170
|
+
summary: subagentSummary,
|
|
1171
|
+
hint: props.t("subagentHelp"),
|
|
1172
|
+
icon: "subagent",
|
|
1173
|
+
open: open === "subagent" && subagentOn,
|
|
1174
|
+
onToggle: () => {
|
|
1175
|
+
if (subagentOn) toggle("subagent");
|
|
1176
|
+
},
|
|
1177
|
+
expandable: subagentOn,
|
|
1178
|
+
trailing: subagentDraft === void 0 ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
|
|
1179
|
+
checked: subagentOn,
|
|
1180
|
+
disabled: busy === "subagent" || !subagent.writable,
|
|
1181
|
+
label: props.t("subagent"),
|
|
1182
|
+
onChange: persistSubagentEnabled
|
|
1183
|
+
}),
|
|
1184
|
+
children: subagentDraft === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1185
|
+
className: ModelSwitchSettings_module_css_default.hint,
|
|
1186
|
+
children: props.t("loading")
|
|
1187
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1188
|
+
className: ModelSwitchSettings_module_css_default.formGrid,
|
|
1189
|
+
children: [
|
|
1190
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
1191
|
+
label: props.t("provider"),
|
|
1192
|
+
value: subagentDraft.provider ?? "",
|
|
1193
|
+
disabled: busy === "subagent" || !subagent.writable,
|
|
1194
|
+
choices: subagentChoices.providers,
|
|
1195
|
+
onChange: (provider) => {
|
|
1196
|
+
const first = groups.find((group) => group.id === provider)?.models[0];
|
|
1197
|
+
setSubagentDraft({
|
|
1198
|
+
mode: "fixed",
|
|
1199
|
+
...selectRouteModel(groups, provider, first?.id ?? subagentDraft.model ?? "")
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
}),
|
|
1203
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
1204
|
+
label: props.t("model"),
|
|
1205
|
+
value: subagentCatalogModel?.model.id ?? subagentDraft.model ?? "",
|
|
1206
|
+
disabled: busy === "subagent" || !subagent.writable,
|
|
1207
|
+
choices: subagentChoices.models,
|
|
1208
|
+
onChange: (model) => {
|
|
1209
|
+
setSubagentDraft({
|
|
1210
|
+
mode: "fixed",
|
|
1211
|
+
...selectRouteModel(groups, subagentDraft.provider ?? "", model)
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
}),
|
|
1215
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
1216
|
+
label: props.t("effort"),
|
|
1217
|
+
value: subagentDraft.reasoningEffort || subagentCatalogModel?.effort || "",
|
|
1218
|
+
disabled: busy === "subagent" || !subagent.writable,
|
|
1219
|
+
choices: subagentEfforts,
|
|
1220
|
+
onChange: (effort) => {
|
|
1221
|
+
const matched = subagentCatalogModel;
|
|
1222
|
+
setSubagentDraft({
|
|
1223
|
+
...subagentDraft,
|
|
1224
|
+
...matched?.model.id === void 0 ? {} : { model: matched.model.id },
|
|
1225
|
+
reasoningEffort: effort
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
})
|
|
1229
|
+
]
|
|
1230
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Actions, {
|
|
1231
|
+
t: props.t,
|
|
1232
|
+
busy: busy === "subagent",
|
|
1233
|
+
disabled: subagentDisabled,
|
|
1234
|
+
...message?.route === "subagent" ? { message: message.text } : {},
|
|
1235
|
+
onCancel: () => {
|
|
1236
|
+
resetSubagent();
|
|
1237
|
+
setMessage(void 0);
|
|
1238
|
+
},
|
|
1239
|
+
onSave: saveSubagent
|
|
1240
|
+
})] })
|
|
1241
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteCard, {
|
|
1242
|
+
title: props.t("subagent"),
|
|
1243
|
+
summary: unavailable("centralSubagentRouting"),
|
|
1244
|
+
icon: "subagent",
|
|
1245
|
+
open: false,
|
|
1246
|
+
onToggle: () => {},
|
|
1247
|
+
disabled: true,
|
|
1248
|
+
badge: props.t("unavailable"),
|
|
1249
|
+
badgeWarn: true
|
|
1250
|
+
})
|
|
1251
|
+
]
|
|
1252
|
+
}),
|
|
1253
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1254
|
+
className: ModelSwitchSettings_module_css_default.group,
|
|
1255
|
+
children: [
|
|
1256
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
1257
|
+
className: ModelSwitchSettings_module_css_default.groupLabel,
|
|
1258
|
+
children: props.t("capabilityRoutes")
|
|
1259
|
+
}),
|
|
1260
|
+
searchAvailable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteCard, {
|
|
1261
|
+
title: props.t("search"),
|
|
1262
|
+
summary: searchDraft === void 0 ? props.t("loading") : routeName(searchGroups, searchDraft),
|
|
1263
|
+
icon: "search",
|
|
1264
|
+
open: open === "search",
|
|
1265
|
+
onToggle: () => {
|
|
1266
|
+
toggle("search");
|
|
1267
|
+
},
|
|
1268
|
+
children: searchDraft === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1269
|
+
className: ModelSwitchSettings_module_css_default.hint,
|
|
1270
|
+
children: props.t("loading")
|
|
1271
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1272
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1273
|
+
className: ModelSwitchSettings_module_css_default.hint,
|
|
1274
|
+
children: props.t("searchHelp")
|
|
1275
|
+
}),
|
|
1276
|
+
searchError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1277
|
+
className: ModelSwitchSettings_module_css_default.hint,
|
|
1278
|
+
children: searchError
|
|
1279
|
+
}),
|
|
1280
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1281
|
+
className: ModelSwitchSettings_module_css_default.formGrid,
|
|
1282
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
1283
|
+
label: props.t("provider"),
|
|
1284
|
+
value: searchDraft.provider,
|
|
1285
|
+
disabled: busy === "search" || !search.writable || searchError !== void 0,
|
|
1286
|
+
choices: searchChoices.providers,
|
|
1287
|
+
onChange: (provider) => {
|
|
1288
|
+
const first = searchGroups.find((group) => group.id === provider)?.models[0];
|
|
1289
|
+
setSearchDraft({
|
|
1290
|
+
provider,
|
|
1291
|
+
...first === void 0 ? {} : { model: first.id }
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
1295
|
+
label: props.t("model"),
|
|
1296
|
+
value: searchDraft.model,
|
|
1297
|
+
disabled: busy === "search" || !search.writable || searchError !== void 0,
|
|
1298
|
+
choices: searchChoices.models,
|
|
1299
|
+
onChange: (model) => {
|
|
1300
|
+
setSearchDraft({
|
|
1301
|
+
...searchDraft,
|
|
1302
|
+
model
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
})]
|
|
1306
|
+
}),
|
|
1307
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Actions, {
|
|
1308
|
+
t: props.t,
|
|
1309
|
+
busy: busy === "search",
|
|
1310
|
+
disabled: capabilityDisabled("search", search, searchDraft) || searchError !== void 0 || !searchGroups.some((group) => group.id === searchDraft.provider && group.models.some((model) => model.id === searchDraft.model)),
|
|
1311
|
+
...message?.route === "search" ? { message: message.text } : {},
|
|
1312
|
+
onCancel: () => {
|
|
1313
|
+
resetSearch();
|
|
1314
|
+
setMessage(void 0);
|
|
1315
|
+
},
|
|
1316
|
+
onSave: () => {
|
|
1317
|
+
saveCapability("search", search, searchDraft);
|
|
1318
|
+
}
|
|
1319
|
+
})
|
|
1320
|
+
] })
|
|
1321
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteCard, {
|
|
1322
|
+
title: props.t("search"),
|
|
1323
|
+
summary: searchError ?? unavailable("searchProviderAdapters"),
|
|
1324
|
+
icon: "search",
|
|
1325
|
+
open: false,
|
|
1326
|
+
onToggle: () => {},
|
|
1327
|
+
disabled: true,
|
|
1328
|
+
badge: props.t("unavailable"),
|
|
1329
|
+
badgeWarn: true
|
|
1330
|
+
}),
|
|
1331
|
+
props.capabilities.imageProviderAdapters.available ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteCard, {
|
|
1332
|
+
title: props.t("image"),
|
|
1333
|
+
summary: imageDraft === void 0 ? props.t("loading") : routeName(groups, imageDraft),
|
|
1334
|
+
icon: "image",
|
|
1335
|
+
open: open === "image",
|
|
1336
|
+
onToggle: () => {
|
|
1337
|
+
toggle("image");
|
|
1338
|
+
},
|
|
1339
|
+
children: imageDraft === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1340
|
+
className: ModelSwitchSettings_module_css_default.hint,
|
|
1341
|
+
children: props.t("loading")
|
|
1342
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1343
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1344
|
+
className: ModelSwitchSettings_module_css_default.hint,
|
|
1345
|
+
children: props.t("imageHelp")
|
|
1346
|
+
}),
|
|
1347
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1348
|
+
className: ModelSwitchSettings_module_css_default.formGrid,
|
|
1349
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
1350
|
+
label: props.t("provider"),
|
|
1351
|
+
value: imageDraft.provider,
|
|
1352
|
+
disabled: busy === "image" || !image.writable,
|
|
1353
|
+
choices: imageChoices.providers,
|
|
1354
|
+
onChange: (provider) => {
|
|
1355
|
+
const model = provider === "grok" ? "grok-imagine-image-quality" : groups.find((group) => group.id === provider)?.models[0]?.id;
|
|
1356
|
+
setImageDraft({
|
|
1357
|
+
provider,
|
|
1358
|
+
...model === void 0 ? {} : { model }
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
1362
|
+
label: props.t("model"),
|
|
1363
|
+
value: imageDraft.model,
|
|
1364
|
+
disabled: busy === "image" || !image.writable,
|
|
1365
|
+
choices: imageChoices.models,
|
|
1366
|
+
onChange: (model) => {
|
|
1367
|
+
setImageDraft({
|
|
1368
|
+
...imageDraft,
|
|
1369
|
+
model
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
})]
|
|
1373
|
+
}),
|
|
1374
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Actions, {
|
|
1375
|
+
t: props.t,
|
|
1376
|
+
busy: busy === "image",
|
|
1377
|
+
disabled: capabilityDisabled("image", image, imageDraft),
|
|
1378
|
+
...message?.route === "image" ? { message: message.text } : {},
|
|
1379
|
+
onCancel: () => {
|
|
1380
|
+
resetImage();
|
|
1381
|
+
setMessage(void 0);
|
|
1382
|
+
},
|
|
1383
|
+
onSave: () => {
|
|
1384
|
+
saveCapability("image", image, imageDraft);
|
|
1385
|
+
}
|
|
1386
|
+
})
|
|
1387
|
+
] })
|
|
1388
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteCard, {
|
|
1389
|
+
title: props.t("image"),
|
|
1390
|
+
summary: unavailable("imageProviderAdapters"),
|
|
1391
|
+
icon: "image",
|
|
1392
|
+
open: false,
|
|
1393
|
+
onToggle: () => {},
|
|
1394
|
+
disabled: true,
|
|
1395
|
+
badge: props.t("unavailable"),
|
|
1396
|
+
badgeWarn: true
|
|
1397
|
+
})
|
|
1398
|
+
]
|
|
1399
|
+
})
|
|
1400
|
+
]
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
//#endregion
|
|
1404
|
+
//#region src/client/locales.ts
|
|
1405
|
+
const zh$1 = {
|
|
1406
|
+
nav: "模型切换",
|
|
1407
|
+
title: "模型切换",
|
|
1408
|
+
subtitle: "为主会话、子代理和能力工具设置默认模型。修改只影响新请求。",
|
|
1409
|
+
conversationRoutes: "对话路由",
|
|
1410
|
+
capabilityRoutes: "能力路由",
|
|
1411
|
+
sendProtection: "发送保护",
|
|
1412
|
+
settingsSynced: "设置已同步",
|
|
1413
|
+
remoteSettingsUnavailable: "官方 DSH 在非本机浏览器禁用配置读取与写入;请在 Host 的 127.0.0.1 页面打开设置。",
|
|
1414
|
+
defaultBadge: "默认",
|
|
1415
|
+
cancel: "取消",
|
|
1416
|
+
compactOnSwitch: "切换时压缩",
|
|
1417
|
+
compactOnSwitchHelp: "发送且换了模型时,先按目标窗口检查上下文;超了就用切换前的模型压一次历史,再继续请求。只选模型、不发送,不会压缩。官方自动压缩和 /compact 不受影响。压缩会产生额外模型用量,摘要可能省略细节。",
|
|
1418
|
+
main: "主模型",
|
|
1419
|
+
provider: "提供商",
|
|
1420
|
+
model: "模型",
|
|
1421
|
+
providerDefault: "使用提供商默认值",
|
|
1422
|
+
providerDefaultShort: "Provider 默认",
|
|
1423
|
+
effort: "推理强度",
|
|
1424
|
+
save: "保存",
|
|
1425
|
+
saving: "保存中…",
|
|
1426
|
+
saved: "已保存",
|
|
1427
|
+
subagent: "子代理默认路由",
|
|
1428
|
+
subagentHelp: "DSH 已有官方子代理模型白名单:模型自己点名 provider/model 时以它为准。这里只在未点名时使用。",
|
|
1429
|
+
subagentOff: "未指定时使用官方继承",
|
|
1430
|
+
agentBadge: "Agent",
|
|
1431
|
+
search: "Web 搜索",
|
|
1432
|
+
searchHelp: "下拉选择的是期望使用的搜索 Adapter/模型;仅当全局 Web searchProvider=model-switch 时官方 web_search 才会使用该选择,否则保持原有路由不变。",
|
|
1433
|
+
image: "图像生成",
|
|
1434
|
+
imageHelp: "统一 generate_image 会调用所选 Codex 或 Grok Adapter;Provider 原有图片工具仍保留。",
|
|
1435
|
+
unavailable: "未接入",
|
|
1436
|
+
loading: "正在加载设置…",
|
|
1437
|
+
readonly: "设置为只读",
|
|
1438
|
+
requestFailed: "保存失败",
|
|
1439
|
+
catalogFailed: "无法加载模型目录。",
|
|
1440
|
+
conflict: "设置已在其他位置更改,请检查最新值后重试。",
|
|
1441
|
+
"reason.central-subagent-routing": "Alpha.4 没有全局子代理启动路由接口。",
|
|
1442
|
+
"reason.packaged-preset-roots": "Alpha.4 不支持插件提供额外 preset root。",
|
|
1443
|
+
"reason.tool-owner-suppression": "Alpha.4 没有工具所有者或来源抑制接口。",
|
|
1444
|
+
"reason.search-provider-adapters": "当前公开版本没有可按模型切换的搜索 Provider Adapter。",
|
|
1445
|
+
"reason.vision-provider-adapters": "尚无 Provider 注册可独立路由的 Vision Adapter。",
|
|
1446
|
+
"reason.image-provider-adapters": "当前公开版本没有图像生成 Provider Adapter。"
|
|
1447
|
+
};
|
|
1448
|
+
const en$1 = {
|
|
1449
|
+
nav: "Model Switch",
|
|
1450
|
+
title: "Model Switch",
|
|
1451
|
+
subtitle: "Set default models for Main, Subagents, and capability tools. Changes affect new requests only.",
|
|
1452
|
+
conversationRoutes: "Conversation routes",
|
|
1453
|
+
capabilityRoutes: "Capability routes",
|
|
1454
|
+
sendProtection: "Send protection",
|
|
1455
|
+
settingsSynced: "Settings synced",
|
|
1456
|
+
remoteSettingsUnavailable: "Official DSH disables settings reads and writes in non-loopback browsers. Open Settings on the Host loopback URL to edit model routes.",
|
|
1457
|
+
defaultBadge: "Default",
|
|
1458
|
+
cancel: "Cancel",
|
|
1459
|
+
compactOnSwitch: "Compact on switch",
|
|
1460
|
+
compactOnSwitchHelp: "When a sent message uses a new model, check the target window first. If needed, compact history with the previous model, then continue. Choosing a model does not compact. Official automatic compaction and /compact are unchanged. Compaction uses extra model quota and may omit details.",
|
|
1461
|
+
main: "Main model",
|
|
1462
|
+
provider: "Provider",
|
|
1463
|
+
model: "Model",
|
|
1464
|
+
providerDefault: "Provider default",
|
|
1465
|
+
providerDefaultShort: "Provider default",
|
|
1466
|
+
effort: "Reasoning effort",
|
|
1467
|
+
save: "Save",
|
|
1468
|
+
saving: "Saving…",
|
|
1469
|
+
saved: "Saved",
|
|
1470
|
+
subagent: "Default Subagent route",
|
|
1471
|
+
subagentHelp: "DSH already has an official Allowlist for child models the agent names. This default applies only when the spawn names none.",
|
|
1472
|
+
subagentOff: "Official inherit when unnamed",
|
|
1473
|
+
agentBadge: "Agent",
|
|
1474
|
+
search: "Web search",
|
|
1475
|
+
searchHelp: "The dropdown selects the requested search adapter/model. The official web_search uses it only when the global Web searchProvider is model-switch; otherwise the original route is unchanged.",
|
|
1476
|
+
image: "Image generation",
|
|
1477
|
+
imageHelp: "The stable generate_image tool calls the selected Codex or Grok Adapter; existing provider image tools remain available.",
|
|
1478
|
+
unavailable: "Unavailable",
|
|
1479
|
+
loading: "Loading settings…",
|
|
1480
|
+
readonly: "Settings are read-only",
|
|
1481
|
+
requestFailed: "Save failed",
|
|
1482
|
+
catalogFailed: "Could not load the model catalog.",
|
|
1483
|
+
conflict: "Settings changed elsewhere. Review the latest values and retry.",
|
|
1484
|
+
"reason.central-subagent-routing": "Alpha.4 exposes no global Subagent start-routing seam.",
|
|
1485
|
+
"reason.packaged-preset-roots": "Alpha.4 exposes no plugin-owned preset root.",
|
|
1486
|
+
"reason.tool-owner-suppression": "Alpha.4 exposes no tool owner or provenance suppression seam.",
|
|
1487
|
+
"reason.search-provider-adapters": "This release exposes no model-selectable Search provider adapter.",
|
|
1488
|
+
"reason.vision-provider-adapters": "No Provider has registered an independently routable Vision Adapter.",
|
|
1489
|
+
"reason.image-provider-adapters": "This release exposes no image-generation provider adapter."
|
|
1490
|
+
};
|
|
1491
|
+
//#endregion
|
|
1492
|
+
//#region src/picker/plan-review.ts
|
|
1493
|
+
function planReviewOf(questions) {
|
|
1494
|
+
if (questions.length !== 1) return void 0;
|
|
1495
|
+
const question = questions[0];
|
|
1496
|
+
if (question === void 0) return void 0;
|
|
1497
|
+
const intent = question.intent;
|
|
1498
|
+
if (intent?.kind !== "plan-review" || question.detail === void 0 || question.multiSelect === true) return void 0;
|
|
1499
|
+
const options = question.options ?? [];
|
|
1500
|
+
if (options.length > 2) return void 0;
|
|
1501
|
+
const approve = options.find((option) => option.label === intent.approve);
|
|
1502
|
+
if (approve === void 0) return void 0;
|
|
1503
|
+
const decline = options.find((option) => option.label !== intent.approve);
|
|
1504
|
+
return {
|
|
1505
|
+
id: question.id,
|
|
1506
|
+
question: question.question,
|
|
1507
|
+
plan: question.detail,
|
|
1508
|
+
approve,
|
|
1509
|
+
...decline === void 0 ? {} : { decline }
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
function selectPlanReview(owner) {
|
|
1513
|
+
const interaction = owner.pendingInteraction;
|
|
1514
|
+
if (interaction === void 0 || interaction.kind !== "question" && interaction.kind !== "plan-review") return null;
|
|
1515
|
+
return planReviewOf(interaction.questions) === void 0 ? null : interaction;
|
|
1516
|
+
}
|
|
1517
|
+
var PlanApprovalResponseError = class extends Error {
|
|
1518
|
+
constructor(message, options) {
|
|
1519
|
+
super(message, options);
|
|
1520
|
+
this.name = "PlanApprovalResponseError";
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
function codeOf(value) {
|
|
1524
|
+
if (value === null || typeof value !== "object") return void 0;
|
|
1525
|
+
const code = value.code;
|
|
1526
|
+
return typeof code === "string" && code !== "" ? code : void 0;
|
|
1527
|
+
}
|
|
1528
|
+
/**
|
|
1529
|
+
* Diagnostic text for one failed action, keeping the wire code visible.
|
|
1530
|
+
* Official Remote failures carry a code that names the owning domain
|
|
1531
|
+
* (`session/writer-held`, `session/model-unavailable`, …); dropping it leaves
|
|
1532
|
+
* the human with copy they cannot act on.
|
|
1533
|
+
* @param cause - the thrown value.
|
|
1534
|
+
* @returns The message, prefixed with the nearest available code.
|
|
1535
|
+
*/
|
|
1536
|
+
function planErrorText(cause) {
|
|
1537
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
1538
|
+
const code = codeOf(cause) ?? (cause instanceof Error ? codeOf(cause.cause) : void 0);
|
|
1539
|
+
return code === void 0 ? message : `${code}: ${message}`;
|
|
1540
|
+
}
|
|
1541
|
+
async function approvePlanReview(args) {
|
|
1542
|
+
const current = args.current;
|
|
1543
|
+
const commit = current !== void 0 && current !== null && current.provider === args.selection.provider && current.model === args.selection.model && current.reasoningEffort === args.selection.reasoningEffort ? {
|
|
1544
|
+
committed: true,
|
|
1545
|
+
mainDefaultRestored: true
|
|
1546
|
+
} : await args.select(args.selection);
|
|
1547
|
+
if (!commit.committed) return false;
|
|
1548
|
+
await args.answer();
|
|
1549
|
+
if (!commit.mainDefaultRestored) args.onNotRestored?.(args.selection.model);
|
|
1550
|
+
return true;
|
|
1551
|
+
}
|
|
1552
|
+
function planActionView(state, available, hasExecution) {
|
|
1553
|
+
return {
|
|
1554
|
+
approveDisabled: state.busy || state.blocked || !available || !hasExecution,
|
|
1555
|
+
error: state.error
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
async function settlePlanAction(send, update) {
|
|
1559
|
+
update({
|
|
1560
|
+
busy: true,
|
|
1561
|
+
blocked: false,
|
|
1562
|
+
error: null
|
|
1563
|
+
});
|
|
1564
|
+
try {
|
|
1565
|
+
await send();
|
|
1566
|
+
return true;
|
|
1567
|
+
} catch (cause) {
|
|
1568
|
+
update({
|
|
1569
|
+
busy: false,
|
|
1570
|
+
blocked: cause instanceof PlanApprovalResponseError,
|
|
1571
|
+
error: planErrorText(cause)
|
|
1572
|
+
});
|
|
1573
|
+
return false;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
const CONTEXT_SUFFIX = /-(\d+)(k|m)$/iu;
|
|
1577
|
+
/** Peel Fast and `-<n>k` / `-<n>m` in either order. Product names like `-max` stay. */
|
|
1578
|
+
function parsePickerId(id) {
|
|
1579
|
+
let rest = id;
|
|
1580
|
+
let fast = false;
|
|
1581
|
+
let contextTier = null;
|
|
1582
|
+
let contextTokens;
|
|
1583
|
+
for (;;) {
|
|
1584
|
+
if (rest.endsWith("-fast") && rest.length > 5) {
|
|
1585
|
+
rest = rest.slice(0, -5);
|
|
1586
|
+
fast = true;
|
|
1587
|
+
continue;
|
|
1588
|
+
}
|
|
1589
|
+
const match = CONTEXT_SUFFIX.exec(rest);
|
|
1590
|
+
if (match !== null && match.index > 0) {
|
|
1591
|
+
const n = Number(match[1]);
|
|
1592
|
+
const unit = match[2].toLowerCase();
|
|
1593
|
+
rest = rest.slice(0, match.index);
|
|
1594
|
+
contextTier = `${n}${unit}`;
|
|
1595
|
+
contextTokens = unit === "m" ? n * 1e6 : n * 1e3;
|
|
1596
|
+
continue;
|
|
1597
|
+
}
|
|
1598
|
+
break;
|
|
1599
|
+
}
|
|
1600
|
+
return {
|
|
1601
|
+
base: rest,
|
|
1602
|
+
fast,
|
|
1603
|
+
contextTier,
|
|
1604
|
+
...contextTokens === void 0 ? {} : { contextTokens }
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
/** Catalog id for a standard-row window that the Host did not publish. UI localizes this. */
|
|
1608
|
+
const STANDARD_CONTEXT_LABEL = "standard";
|
|
1609
|
+
/** Human label for a context tier: 1M, 272K, or STANDARD_CONTEXT_LABEL when its window is unknown. */
|
|
1610
|
+
function contextTierLabel(tier, tokens) {
|
|
1611
|
+
if (tier === null) return tokens === void 0 ? STANDARD_CONTEXT_LABEL : formatWindow(tokens);
|
|
1612
|
+
const match = /^(\d+)(k|m)$/iu.exec(tier);
|
|
1613
|
+
if (match !== null) return `${match[1]}${match[2].toUpperCase()}`;
|
|
1614
|
+
return tokens === void 0 ? tier : formatWindow(tokens);
|
|
1615
|
+
}
|
|
1616
|
+
/** Compact token window for trigger / context-cell copy. */
|
|
1617
|
+
function formatWindow(tokens) {
|
|
1618
|
+
if (tokens >= 1e6 && tokens % 1e6 === 0) return `${tokens / 1e6}M`;
|
|
1619
|
+
if (tokens >= 1e3 && tokens % 1e3 === 0) return `${tokens / 1e3}K`;
|
|
1620
|
+
if (tokens >= 1e3) return `${Math.round(tokens / 1e3)}K`;
|
|
1621
|
+
return String(tokens);
|
|
1622
|
+
}
|
|
1623
|
+
/** Standard-row window when the Host directory omits contextWindow. */
|
|
1624
|
+
function impliedStandardTokens(base) {
|
|
1625
|
+
if (/^gpt-5\.6(?:-|$)/u.test(base)) return 272e3;
|
|
1626
|
+
}
|
|
1627
|
+
/** Label a selected variant from catalog identity, never from stale session pressure. */
|
|
1628
|
+
function contextLabelForMember(family, member) {
|
|
1629
|
+
const tokens = member.contextTokens ?? (member.contextTier === null ? impliedStandardTokens(family.base) : void 0);
|
|
1630
|
+
return contextTierLabel(member.contextTier, tokens);
|
|
1631
|
+
}
|
|
1632
|
+
function memberOf(model) {
|
|
1633
|
+
const parsed = parsePickerId(model.id);
|
|
1634
|
+
return {
|
|
1635
|
+
model,
|
|
1636
|
+
fast: parsed.fast,
|
|
1637
|
+
contextTier: parsed.contextTier,
|
|
1638
|
+
...parsed.contextTokens === void 0 ? {} : { contextTokens: parsed.contextTokens },
|
|
1639
|
+
thinking: model.reasoning !== void 0
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
/** Group directory rows by provider + peeled base. */
|
|
1643
|
+
function groupFamilies(groups) {
|
|
1644
|
+
const families = [];
|
|
1645
|
+
const index = /* @__PURE__ */ new Map();
|
|
1646
|
+
for (const group of groups) for (const model of group.models) {
|
|
1647
|
+
const parsed = parsePickerId(model.id);
|
|
1648
|
+
const key = `${group.id}\0${parsed.base}`;
|
|
1649
|
+
let family = index.get(key);
|
|
1650
|
+
if (family === void 0) {
|
|
1651
|
+
family = {
|
|
1652
|
+
provider: group.id,
|
|
1653
|
+
providerName: group.name,
|
|
1654
|
+
base: parsed.base,
|
|
1655
|
+
name: displayNameOf(model.name, parsed),
|
|
1656
|
+
members: []
|
|
1657
|
+
};
|
|
1658
|
+
index.set(key, family);
|
|
1659
|
+
families.push(family);
|
|
1660
|
+
}
|
|
1661
|
+
family.members.push(memberOf(model));
|
|
1662
|
+
if (!parsed.fast && parsed.contextTier === null) family.name = displayNameOf(model.name, parsed);
|
|
1663
|
+
}
|
|
1664
|
+
return families;
|
|
1665
|
+
}
|
|
1666
|
+
/** Locate the family that owns a provider/model pair. */
|
|
1667
|
+
function findFamily(families, provider, modelId) {
|
|
1668
|
+
return families.find((family) => family.provider === provider && family.members.some((member) => member.model.id === modelId));
|
|
1669
|
+
}
|
|
1670
|
+
/** Locate one family member by catalog id. */
|
|
1671
|
+
function findMember(family, modelId) {
|
|
1672
|
+
return family.members.find((member) => member.model.id === modelId);
|
|
1673
|
+
}
|
|
1674
|
+
/** Pick a sibling after toggling Fast / context / thinking, keeping the other axes. */
|
|
1675
|
+
function pickVariant(family, current, patch) {
|
|
1676
|
+
const fast = patch.fast ?? current.fast;
|
|
1677
|
+
const contextTier = patch.contextTier !== void 0 ? patch.contextTier : current.contextTier;
|
|
1678
|
+
const thinking = patch.thinking ?? current.thinking;
|
|
1679
|
+
const exact = family.members.find((member) => member.fast === fast && member.contextTier === contextTier && member.thinking === thinking);
|
|
1680
|
+
if (exact !== void 0) return exact;
|
|
1681
|
+
const sameTier = family.members.find((member) => member.fast === fast && member.contextTier === contextTier);
|
|
1682
|
+
if (sameTier !== void 0) return sameTier;
|
|
1683
|
+
return family.members.find((member) => member.fast === fast) ?? family.members[0] ?? current;
|
|
1684
|
+
}
|
|
1685
|
+
/** Fast row appears only when both a Fast and a non-Fast sibling exist. */
|
|
1686
|
+
function familyHasFast(family) {
|
|
1687
|
+
return family.members.some((member) => member.fast) && family.members.some((member) => !member.fast);
|
|
1688
|
+
}
|
|
1689
|
+
function displayNameOf(name, parsed) {
|
|
1690
|
+
let next = name;
|
|
1691
|
+
if (parsed.fast) next = next.replace(/\s+Fast$/iu, "");
|
|
1692
|
+
if (parsed.contextTier !== null) next = next.replace(/\s+(?:Max|1M)$/iu, "");
|
|
1693
|
+
return next.replace(/\s+/gu, " ").trim() || name;
|
|
1694
|
+
}
|
|
1695
|
+
/** Provider sections in catalog order, for the model pane. */
|
|
1696
|
+
function sectionFamilies(families) {
|
|
1697
|
+
const sections = [];
|
|
1698
|
+
const index = /* @__PURE__ */ new Map();
|
|
1699
|
+
for (const family of families) {
|
|
1700
|
+
let section = index.get(family.provider);
|
|
1701
|
+
if (section === void 0) {
|
|
1702
|
+
section = {
|
|
1703
|
+
provider: family.provider,
|
|
1704
|
+
providerName: family.providerName,
|
|
1705
|
+
families: []
|
|
1706
|
+
};
|
|
1707
|
+
index.set(family.provider, section);
|
|
1708
|
+
sections.push(section);
|
|
1709
|
+
}
|
|
1710
|
+
section.families.push(family);
|
|
1711
|
+
}
|
|
1712
|
+
return sections;
|
|
1713
|
+
}
|
|
1714
|
+
/** Unique context tiers in catalog order. */
|
|
1715
|
+
function contextTiers(family, standardTokens) {
|
|
1716
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1717
|
+
const rows = [];
|
|
1718
|
+
for (const member of family.members) {
|
|
1719
|
+
const tokens = member.contextTier === null ? member.contextTokens ?? standardTokens ?? impliedStandardTokens(family.base) : member.contextTokens;
|
|
1720
|
+
const label = contextTierLabel(member.contextTier, tokens);
|
|
1721
|
+
if (seen.has(label)) continue;
|
|
1722
|
+
seen.add(label);
|
|
1723
|
+
rows.push({
|
|
1724
|
+
tier: member.contextTier,
|
|
1725
|
+
label,
|
|
1726
|
+
...tokens === void 0 ? {} : { tokens }
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
return rows;
|
|
1730
|
+
}
|
|
1731
|
+
/** Context row appears only when the family has more than one tier. */
|
|
1732
|
+
function familyHasContextChoices(family) {
|
|
1733
|
+
return contextTiers(family).length > 1;
|
|
1734
|
+
}
|
|
1735
|
+
/** Thinking on/off siblings at the current Fast + context axes, or null. */
|
|
1736
|
+
function thinkingSiblings(family, current) {
|
|
1737
|
+
const on = family.members.find((member) => member.fast === current.fast && member.contextTier === current.contextTier && member.thinking);
|
|
1738
|
+
const off = family.members.find((member) => member.fast === current.fast && member.contextTier === current.contextTier && !member.thinking);
|
|
1739
|
+
if (on === void 0 || off === void 0) return null;
|
|
1740
|
+
return {
|
|
1741
|
+
on,
|
|
1742
|
+
off
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
/** Case-insensitive local search over family name, base, and provider. */
|
|
1746
|
+
function filterFamilies(families, query) {
|
|
1747
|
+
const needle = query.trim().toLowerCase();
|
|
1748
|
+
if (needle.length === 0) return [...families];
|
|
1749
|
+
return families.filter((family) => family.name.toLowerCase().includes(needle) || family.base.toLowerCase().includes(needle) || family.providerName.toLowerCase().includes(needle) || family.members.some((member) => member.model.id.toLowerCase().includes(needle)));
|
|
1750
|
+
}
|
|
1751
|
+
/** Build a Host selection from a member, preserving or defaulting effort. */
|
|
1752
|
+
function selectionOf(family, member, reasoningEffort) {
|
|
1753
|
+
const reasoning = member.model.reasoning;
|
|
1754
|
+
const effort = reasoningEffort !== void 0 && reasoning?.efforts.some((level) => level.id === reasoningEffort) ? reasoningEffort : reasoning?.defaultEffort;
|
|
1755
|
+
return {
|
|
1756
|
+
provider: family.provider,
|
|
1757
|
+
model: member.model.id,
|
|
1758
|
+
...effort === void 0 ? {} : { reasoningEffort: effort }
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
//#endregion
|
|
1762
|
+
//#region src/picker/selection-feedback.ts
|
|
1763
|
+
/** Immediate picker feedback around an asynchronous Host model selection. */
|
|
1764
|
+
async function beginSelection(select, showFeedback, settle) {
|
|
1765
|
+
showFeedback();
|
|
1766
|
+
settle(await select());
|
|
1767
|
+
}
|
|
1768
|
+
//#endregion
|
|
1769
|
+
//#region src/client/picker/popup-dismissal.ts
|
|
1770
|
+
/** Install capture-phase outside-pointer dismissal and optional mobile Back registration. */
|
|
1771
|
+
function installPickerDismissal({ documentTarget, surfaceId, interaction, trigger, popup, dismiss }) {
|
|
1772
|
+
let unregister = () => {};
|
|
1773
|
+
try {
|
|
1774
|
+
unregister = interaction?.registerSurface({
|
|
1775
|
+
id: surfaceId,
|
|
1776
|
+
kind: "popup",
|
|
1777
|
+
dismiss: () => {
|
|
1778
|
+
dismiss();
|
|
1779
|
+
}
|
|
1780
|
+
}) ?? unregister;
|
|
1781
|
+
} catch (error) {
|
|
1782
|
+
console.warn("dsh-model-switch: optional interaction surface registration failed", error);
|
|
1783
|
+
}
|
|
1784
|
+
const onPointerDown = (event) => {
|
|
1785
|
+
const target = event.target;
|
|
1786
|
+
if (!(target instanceof Node)) return;
|
|
1787
|
+
if (trigger()?.contains(target) || popup()?.contains(target)) return;
|
|
1788
|
+
dismiss();
|
|
1789
|
+
};
|
|
1790
|
+
documentTarget.addEventListener("pointerdown", onPointerDown, true);
|
|
1791
|
+
return () => {
|
|
1792
|
+
documentTarget.removeEventListener("pointerdown", onPointerDown, true);
|
|
1793
|
+
unregister();
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
//#endregion
|
|
1797
|
+
//#region src/client/picker/useComposerPickerSurface.ts
|
|
1798
|
+
/** Own popup activation, positioning, and dismissal behind one internal seam. */
|
|
1799
|
+
function useComposerPickerSurface(options) {
|
|
1800
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
1801
|
+
const [menuStyle, setMenuStyle] = (0, react.useState)({
|
|
1802
|
+
position: "fixed",
|
|
1803
|
+
zIndex: 4e3
|
|
1804
|
+
});
|
|
1805
|
+
const triggerRef = (0, react.useRef)(null);
|
|
1806
|
+
const menuRef = (0, react.useRef)(null);
|
|
1807
|
+
const pointerOpenIntent = (0, react.useRef)(null);
|
|
1808
|
+
const callbacks = (0, react.useRef)({
|
|
1809
|
+
onOpen: options.onOpen,
|
|
1810
|
+
onClose: options.onClose
|
|
1811
|
+
});
|
|
1812
|
+
callbacks.current = {
|
|
1813
|
+
onOpen: options.onOpen,
|
|
1814
|
+
onClose: options.onClose
|
|
1815
|
+
};
|
|
1816
|
+
const lockedRef = (0, react.useRef)(options.locked);
|
|
1817
|
+
lockedRef.current = options.locked;
|
|
1818
|
+
const id = (0, react.useId)();
|
|
1819
|
+
const close = (0, react.useCallback)((restoreFocus = false) => {
|
|
1820
|
+
setOpen(false);
|
|
1821
|
+
callbacks.current.onClose();
|
|
1822
|
+
if (restoreFocus) queueMicrotask(() => {
|
|
1823
|
+
triggerRef.current?.focus();
|
|
1824
|
+
});
|
|
1825
|
+
}, []);
|
|
1826
|
+
const show = () => {
|
|
1827
|
+
if (lockedRef.current) return;
|
|
1828
|
+
callbacks.current.onOpen();
|
|
1829
|
+
setOpen(true);
|
|
1830
|
+
if (options.tone !== "capsule") options.reload();
|
|
1831
|
+
};
|
|
1832
|
+
(0, react.useEffect)(() => {
|
|
1833
|
+
if (options.locked) close();
|
|
1834
|
+
}, [close, options.locked]);
|
|
1835
|
+
(0, react.useLayoutEffect)(() => {
|
|
1836
|
+
if (!open) return;
|
|
1837
|
+
const trigger = triggerRef.current;
|
|
1838
|
+
if (trigger === null) return;
|
|
1839
|
+
const rect = trigger.getBoundingClientRect();
|
|
1840
|
+
const gutter = 8;
|
|
1841
|
+
const maxWidth = Math.min(420, window.innerWidth - 16);
|
|
1842
|
+
const preferredWidth = Math.min(320, maxWidth);
|
|
1843
|
+
const safeRight = "max(" + Math.min(Math.max(gutter, window.innerWidth - rect.right), Math.max(gutter, window.innerWidth - gutter - preferredWidth)) + "px, calc(env(safe-area-inset-right) + 8px))";
|
|
1844
|
+
setMenuStyle({
|
|
1845
|
+
position: "fixed",
|
|
1846
|
+
right: safeRight,
|
|
1847
|
+
bottom: "max(" + Math.max(gutter, window.innerHeight - rect.top + gutter) + "px, calc(env(safe-area-inset-bottom) + 8px))",
|
|
1848
|
+
maxWidth: "max(0px, calc(100vw - " + safeRight + " - env(safe-area-inset-left) - 8px))",
|
|
1849
|
+
zIndex: 4e3
|
|
1850
|
+
});
|
|
1851
|
+
}, [
|
|
1852
|
+
open,
|
|
1853
|
+
options.embedded,
|
|
1854
|
+
options.pane
|
|
1855
|
+
]);
|
|
1856
|
+
(0, react.useEffect)(() => {
|
|
1857
|
+
if (!open || options.tone === "capsule") return;
|
|
1858
|
+
const interaction = options.resolveInteractionOperations?.();
|
|
1859
|
+
return installPickerDismissal({
|
|
1860
|
+
documentTarget: document,
|
|
1861
|
+
surfaceId: "composer-model-picker-" + id,
|
|
1862
|
+
...interaction === void 0 ? {} : { interaction },
|
|
1863
|
+
trigger: () => triggerRef.current,
|
|
1864
|
+
popup: () => menuRef.current,
|
|
1865
|
+
dismiss: close
|
|
1866
|
+
});
|
|
1867
|
+
}, [
|
|
1868
|
+
close,
|
|
1869
|
+
id,
|
|
1870
|
+
open,
|
|
1871
|
+
options.resolveInteractionOperations,
|
|
1872
|
+
options.tone
|
|
1873
|
+
]);
|
|
1874
|
+
return {
|
|
1875
|
+
id,
|
|
1876
|
+
open,
|
|
1877
|
+
menuStyle,
|
|
1878
|
+
triggerRef,
|
|
1879
|
+
menuRef,
|
|
1880
|
+
show,
|
|
1881
|
+
close,
|
|
1882
|
+
onTriggerPointerDown: (event) => {
|
|
1883
|
+
event.stopPropagation();
|
|
1884
|
+
pointerOpenIntent.current = {
|
|
1885
|
+
open: !open,
|
|
1886
|
+
until: Date.now() + 750
|
|
1887
|
+
};
|
|
1888
|
+
},
|
|
1889
|
+
onTriggerClick: (event) => {
|
|
1890
|
+
event?.stopPropagation();
|
|
1891
|
+
const intent = pointerOpenIntent.current;
|
|
1892
|
+
const desiredOpen = intent !== null && Date.now() <= intent.until ? intent.open : !open;
|
|
1893
|
+
if (intent !== null && Date.now() > intent.until) pointerOpenIntent.current = null;
|
|
1894
|
+
if (desiredOpen) show();
|
|
1895
|
+
else close();
|
|
1896
|
+
}
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1899
|
+
[
|
|
1900
|
+
"[data-provider-card]{box-sizing:border-box;width:100%;min-width:0;list-style:none;margin:0!important;border:0!important;border-radius:0!important;background:none!important;box-shadow:none!important;overflow:visible}",
|
|
1901
|
+
"[data-provider-card-header]{box-sizing:border-box;width:100%;min-height:76px!important;display:flex;align-items:center;justify-content:space-between;gap:16px;border:0;padding:12px 14px!important;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;cursor:pointer}",
|
|
1902
|
+
"[data-provider-body][hidden]{display:none!important}",
|
|
1903
|
+
"[data-provider-role-badge] svg{width:12px;height:12px}",
|
|
1904
|
+
"[data-provider-card-header]:hover{background:color-mix(in srgb, var(--dsw-alias-label-primary) 4%, transparent)}",
|
|
1905
|
+
"[data-provider-body]{display:flex;flex-direction:column;gap:18px;border-top:1px solid var(--dsw-alias-border-l2);padding:16px 14px 18px}",
|
|
1906
|
+
"[data-provider-model]{display:flex;align-items:center;gap:9px;min-height:40px}",
|
|
1907
|
+
"[data-provider-quota-mini]{display:block}",
|
|
1908
|
+
"[data-providers-list]{display:flex;flex-direction:column}",
|
|
1909
|
+
"[data-providers-list] [data-sortable-row]+[data-sortable-row]{border-top:1px solid var(--dsw-alias-border-l2)}",
|
|
1910
|
+
"[data-providers-section]{container-type:inline-size}",
|
|
1911
|
+
"@media (max-width:680px){[data-provider-card-header]{min-height:106px!important;padding:17px 4px!important}[data-provider-header-main]{display:grid!important;grid-template-columns:minmax(0,1fr) auto;gap:7px 9px!important;align-items:center}[data-provider-header-identity]{grid-column:1;grid-row:1;gap:9px!important}[data-provider-header-mark]{width:25px!important;height:25px!important}[data-provider-role-badge]{margin-left:4px;font-size:9px!important}[data-provider-role-badge] svg{width:11px!important;height:11px!important}[data-provider-header-side]{grid-column:2;grid-row:1;justify-self:end}[data-provider-header-side] [data-provider-header-chevron]{width:18px}[data-provider-quota-mini]{grid-column:1;grid-row:2;width:auto!important;max-width:none!important;text-align:left;padding-left:34px!important}[data-provider-header-status]{grid-column:2;grid-row:2;width:auto!important;max-width:100px}[data-provider-model]{min-height:48px}[data-provider-model] input[type=checkbox]{width:17px;height:17px}[data-providers-section] button,[data-provider-card] button{min-height:44px}}",
|
|
1912
|
+
"@container (max-width:540px){[data-provider-card-header]{min-height:106px!important;padding:17px 4px!important}[data-provider-header-main]{display:grid!important;grid-template-columns:minmax(0,1fr) auto;gap:7px 9px!important;align-items:center}[data-provider-header-identity]{grid-column:1;grid-row:1;gap:9px!important}[data-provider-header-mark]{width:25px!important;height:25px!important}[data-provider-role-badge]{margin-left:4px;font-size:9px!important}[data-provider-role-badge] svg{width:11px!important;height:11px!important}[data-provider-header-side]{grid-column:2;grid-row:1;justify-self:end}[data-provider-header-side] [data-provider-header-chevron]{width:18px}[data-provider-quota-mini]{grid-column:1;grid-row:2;width:auto!important;max-width:none!important;text-align:left;padding-left:34px!important}[data-provider-header-status]{grid-column:2;grid-row:2;width:auto!important;max-width:100px}[data-provider-model]{min-height:48px}[data-provider-model] input[type=checkbox]{width:17px;height:17px}[data-providers-section] button,[data-provider-card] button{min-height:44px}}",
|
|
1913
|
+
"@media (pointer:coarse){[data-sortable-handle],[data-sortable-move]{min-width:44px;min-height:44px}}"
|
|
1914
|
+
].join("\n");
|
|
1915
|
+
function Svg(props) {
|
|
1916
|
+
return (0, react_jsx_runtime.jsxs)("svg", {
|
|
1917
|
+
className: "pu-logo",
|
|
1918
|
+
viewBox: props.viewBox,
|
|
1919
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
1920
|
+
"aria-hidden": true,
|
|
1921
|
+
children: [
|
|
1922
|
+
" ",
|
|
1923
|
+
props.children,
|
|
1924
|
+
" "
|
|
1925
|
+
]
|
|
1926
|
+
});
|
|
1927
|
+
}
|
|
1928
|
+
const DEEPSEEK_FISH_PATH = "M22.9168 1.43018C22.6713 1.31018 22.5658 1.53918 22.4223 1.65519C22.3733 1.69269 22.3318 1.74169 22.2903 1.78669C21.9317 2.1697 21.5127 2.42121 20.9657 2.39121C20.1657 2.34621 19.4827 2.59771 18.8787 3.20973C18.7502 2.45521 18.3236 2.0047 17.6746 1.71569C17.3351 1.56568 16.9916 1.41518 16.7536 1.08867C16.5876 0.856163 16.5421 0.597155 16.4591 0.341647C16.4061 0.187643 16.3536 0.0301382 16.1761 0.00363739C15.9836 -0.0263635 15.9081 0.135141 15.8326 0.270145C15.5306 0.822162 15.4136 1.43018 15.4251 2.0462C15.4516 3.43174 16.0366 4.53527 17.1991 5.3203C17.3311 5.4103 17.3651 5.5003 17.3236 5.63181C17.2441 5.90231 17.1501 6.16482 17.0671 6.43533C17.0141 6.60784 16.9351 6.64584 16.7501 6.57033C16.1121 6.30383 15.5611 5.90931 15.074 5.4328C14.2475 4.63328 13.5 3.75075 12.568 3.05973C12.349 2.89822 12.13 2.74822 11.9034 2.60522C10.9524 1.68169 12.028 0.923165 12.277 0.833162C12.5375 0.739159 12.3675 0.41615 11.5259 0.42015C10.6844 0.42365 9.91439 0.705658 8.93286 1.08117C8.78935 1.13767 8.63835 1.17867 8.48384 1.21267C7.59332 1.04367 6.66829 1.00617 5.70226 1.11517C3.88321 1.31768 2.43016 2.1777 1.36213 3.64575C0.0790928 5.4103 -0.222916 7.41536 0.146595 9.50642C0.535106 11.7105 1.66014 13.535 3.38869 14.9616C5.18125 16.4406 7.24581 17.1657 9.60138 17.0266C11.0319 16.9441 12.6245 16.7526 14.421 15.2321C14.874 15.4576 15.3496 15.5476 16.1381 15.6151C16.7456 15.6716 17.3306 15.5851 17.7836 15.4911C18.4931 15.3411 18.4441 14.6841 18.1876 14.5636C16.1081 13.595 16.5646 13.9891 16.1496 13.67C17.2061 12.42 18.8202 10.1979 19.3182 7.17235C19.3672 6.83834 19.4297 6.36783 19.4222 6.09732C19.4182 5.93231 19.4562 5.86831 19.6447 5.84931C20.1657 5.78931 20.6712 5.64681 21.1357 5.3913C22.4833 4.65528 23.0268 3.44624 23.1548 1.9972C23.1738 1.77569 23.1508 1.54668 22.9168 1.43018ZM11.1749 14.4736C9.15936 12.889 8.18184 12.3675 7.77832 12.39C7.40081 12.4125 7.46881 12.8445 7.55182 13.126C7.63882 13.404 7.75182 13.5955 7.91033 13.8396C8.01983 14.0011 8.09533 14.2411 7.80083 14.4216C7.15181 14.8231 6.02327 14.2866 5.97027 14.2601C4.65673 13.4865 3.5587 12.4655 2.78467 11.069C2.03715 9.72493 1.60314 8.28289 1.53164 6.74384C1.51264 6.37233 1.62214 6.24082 1.99215 6.17332C2.47916 6.08332 2.98118 6.06432 3.46769 6.13582C5.52476 6.43633 7.27581 7.35586 8.74385 8.8129C9.58188 9.64243 10.2159 10.634 10.8689 11.6025C11.5634 12.631 12.3105 13.611 13.262 14.4146C13.598 14.6961 13.866 14.9101 14.1225 15.0681C13.349 15.1546 12.058 15.1731 11.1749 14.4746L11.1749 14.4736ZM12.141 8.25988C12.141 8.09488 12.273 7.96338 12.439 7.96338C12.4765 7.96338 12.5105 7.97088 12.541 7.98188C12.5825 7.99688 12.6205 8.01938 12.6505 8.05338C12.7035 8.10588 12.7335 8.18088 12.7335 8.25988C12.7335 8.42489 12.6015 8.55639 12.4355 8.55639C12.2695 8.55639 12.141 8.42489 12.141 8.25988ZM15.1415 9.79893C14.949 9.87793 14.7565 9.94544 14.5715 9.95294C14.2845 9.96794 13.9715 9.85143 13.8015 9.70893C13.5375 9.48742 13.3485 9.36342 13.2695 8.97691C13.2355 8.8119 13.2545 8.55639 13.2845 8.40989C13.3525 8.09438 13.277 7.89187 13.0545 7.70787C12.8735 7.55786 12.643 7.51636 12.39 7.51636C12.2955 7.51636 12.209 7.47486 12.1445 7.44136C12.039 7.38886 11.9519 7.25735 12.035 7.09585C12.0615 7.04335 12.19 6.91584 12.22 6.89334C12.5635 6.69784 12.9595 6.76184 13.326 6.90834C13.6655 7.04735 13.9225 7.30236 14.292 7.66287C14.6695 8.09838 14.7375 8.21838 14.9525 8.54539C15.1225 8.8009 15.277 9.06341 15.3831 9.36392C15.4471 9.55142 15.3641 9.70493 15.1415 9.79893Z";
|
|
1929
|
+
const GENERIC_GLOBE_PATH = "M7.00018 0.353516C10.6708 0.353535 13.6468 3.32958 13.6469 7.00018C13.6468 10.6708 10.6708 13.6468 7.00018 13.6469C3.32957 13.6468 0.353535 10.6708 0.353516 7.00018C0.353535 3.32957 3.32957 0.353531 7.00018 0.353516ZM5.44643 7.59661C5.49463 8.97506 5.70762 10.191 6.02136 11.0793C6.20141 11.5891 6.40328 11.9585 6.59898 12.1889C6.79501 12.4196 6.93213 12.454 7.00018 12.454C7.06822 12.454 7.20533 12.4197 7.40138 12.1889C7.59708 11.9585 7.79895 11.589 7.979 11.0793C8.29274 10.191 8.50574 8.97506 8.55394 7.59661H5.44643ZM1.57861 7.59661C1.80785 9.70467 3.2386 11.4509 5.1715 12.1388C5.07135 11.9317 4.97972 11.7098 4.89746 11.477C4.53084 10.4391 4.30224 9.0828 4.25357 7.59661H1.57861ZM9.74679 7.59661C9.69813 9.0828 9.46952 10.4391 9.1029 11.477C9.0206 11.7099 8.92818 11.9316 8.82797 12.1388C10.7613 11.4511 12.1925 9.70496 12.4218 7.59661H9.74679ZM5.1706 1.8616C3.23814 2.54963 1.80876 4.29604 1.5795 6.40376H4.25357C4.30224 4.91756 4.53083 3.56129 4.89746 2.5234C4.97968 2.29066 5.07051 2.0686 5.1706 1.8616ZM7.00018 1.54637C6.93213 1.54638 6.79503 1.5807 6.59898 1.81145C6.40332 2.04177 6.20139 2.41058 6.02136 2.92012C5.70754 3.80851 5.49461 5.02499 5.44643 6.40376H8.55394C8.50575 5.025 8.29282 3.80851 7.979 2.92012C7.79898 2.41059 7.59705 2.04177 7.40138 1.81145C7.20531 1.58067 7.06823 1.54637 7.00018 1.54637ZM8.82887 1.8616C8.92902 2.0687 9.02064 2.29053 9.1029 2.5234C9.46953 3.56129 9.69812 4.91756 9.74679 6.40376H12.4209C12.1916 4.29575 10.7618 2.54943 8.82887 1.8616Z";
|
|
1930
|
+
function ProviderMark(props) {
|
|
1931
|
+
const raw = props.providerKey;
|
|
1932
|
+
switch (raw === "cursor-agent" || raw === "cursor" || raw === "acp-cursor" ? "llm-cursor" : raw === "claude-agent" || raw === "claude" || raw === "claude-code" || raw === "acp-claude" ? "llm-claude" : raw.startsWith("llm-") ? raw : raw === "opencode" ? "llm-opencode-go" : "llm-" + raw) {
|
|
1933
|
+
case "llm-claude": return (0, react_jsx_runtime.jsx)(Svg, {
|
|
1934
|
+
viewBox: "0 0 24 24",
|
|
1935
|
+
children: (0, react_jsx_runtime.jsx)("path", {
|
|
1936
|
+
fill: "currentColor",
|
|
1937
|
+
d: "m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z"
|
|
1938
|
+
})
|
|
1939
|
+
});
|
|
1940
|
+
case "llm-cursor": return (0, react_jsx_runtime.jsx)(Svg, {
|
|
1941
|
+
viewBox: "0.24 -1.44 23.6 26.88",
|
|
1942
|
+
children: (0, react_jsx_runtime.jsx)("path", {
|
|
1943
|
+
fill: "currentColor",
|
|
1944
|
+
d: "M11.503.131 1.891 5.678a.84.84 0 0 0-.42.726v11.188c0 .3.162.575.42.724l9.609 5.55a1 1 0 0 0 .998 0l9.61-5.55a.84.84 0 0 0 .42-.724V6.404a.84.84 0 0 0-.42-.726L12.497.131a1.01 1.01 0 0 0-.996 0M2.657 6.338h18.55c.263 0 .43.287.297.515L12.23 22.918c-.062.107-.229.064-.229-.06V12.335a.59.59 0 0 0-.295-.51l-9.11-5.257c-.109-.063-.064-.23.061-.23"
|
|
1945
|
+
})
|
|
1946
|
+
});
|
|
1947
|
+
case "llm-antigravity": return (0, react_jsx_runtime.jsx)(Svg, {
|
|
1948
|
+
viewBox: "13.4 8.4 142.1 129.9",
|
|
1949
|
+
children: (0, react_jsx_runtime.jsx)("path", {
|
|
1950
|
+
fill: "currentColor",
|
|
1951
|
+
d: "M84.5 16C64 16 57 39 49 67C42 93 36 111 24 122C18 128 22 132 28 132C42 132 50 116 59 99C66 85 72 78 84.5 78C97 78 103 85 110 99C119 116 127 132 141 132C147 132 151 128 145 122C133 111 127 93 120 67C112 39 105 16 84.5 16Z"
|
|
1952
|
+
})
|
|
1953
|
+
});
|
|
1954
|
+
case "llm-codex": return (0, react_jsx_runtime.jsx)(Svg, {
|
|
1955
|
+
viewBox: "-1.2 -1.44 26.6 26.88",
|
|
1956
|
+
children: (0, react_jsx_runtime.jsx)("path", {
|
|
1957
|
+
fill: "currentColor",
|
|
1958
|
+
d: "M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"
|
|
1959
|
+
})
|
|
1960
|
+
});
|
|
1961
|
+
case "llm-ollama": return (0, react_jsx_runtime.jsx)(Svg, {
|
|
1962
|
+
viewBox: "1.8 -1.44 20.4 26.88",
|
|
1963
|
+
children: (0, react_jsx_runtime.jsx)("path", {
|
|
1964
|
+
fill: "currentColor",
|
|
1965
|
+
d: "M16.361 10.26a.894.894 0 0 0-.558.47l-.072.148.001.207c0 .193.004.217.059.353.076.193.152.312.291.448.24.238.51.3.872.205a.86.86 0 0 0 .517-.436.752.752 0 0 0 .08-.498c-.064-.453-.33-.782-.724-.897a1.06 1.06 0 0 0-.466 0zm-9.203.005c-.305.096-.533.32-.65.639a1.187 1.187 0 0 0-.06.52c.057.309.31.59.598.667.362.095.632.033.872-.205.14-.136.215-.255.291-.448.055-.136.059-.16.059-.353l.001-.207-.072-.148a.894.894 0 0 0-.565-.472 1.02 1.02 0 0 0-.474.007Zm4.184 2c-.131.071-.223.25-.195.383.031.143.157.288.353.407.105.063.112.072.117.136.004.038-.01.146-.029.243-.02.094-.036.194-.036.222.002.074.07.195.143.253.064.052.076.054.255.059.164.005.198.001.264-.03.169-.082.212-.234.15-.525-.052-.243-.042-.28.087-.355.137-.08.281-.219.324-.314a.365.365 0 0 0-.175-.48.394.394 0 0 0-.181-.033c-.126 0-.207.03-.355.124l-.085.053-.053-.032c-.219-.13-.259-.145-.391-.143a.396.396 0 0 0-.193.032zm.39-2.195c-.373.036-.475.05-.654.086-.291.06-.68.195-.951.328-.94.46-1.589 1.226-1.787 2.114-.04.176-.045.234-.045.53 0 .294.005.357.043.524.264 1.16 1.332 2.017 2.714 2.173.3.033 1.596.033 1.896 0 1.11-.125 2.064-.727 2.493-1.571.114-.226.169-.372.22-.602.039-.167.044-.23.044-.523 0-.297-.005-.355-.045-.531-.288-1.29-1.539-2.304-3.072-2.497a6.873 6.873 0 0 0-.855-.031zm.645.937a3.283 3.283 0 0 1 1.44.514c.223.148.537.458.671.662.166.251.26.508.303.82.02.143.01.251-.043.482-.08.345-.332.705-.672.957a3.115 3.115 0 0 1-.689.348c-.382.122-.632.144-1.525.138-.582-.006-.686-.01-.853-.042-.57-.107-1.022-.334-1.35-.68-.264-.28-.385-.535-.45-.946-.03-.192.025-.509.137-.776.136-.326.488-.73.836-.963.403-.269.934-.46 1.422-.512.187-.02.586-.02.773-.002zm-5.503-11a1.653 1.653 0 0 0-.683.298C5.617.74 5.173 1.666 4.985 2.819c-.07.436-.119 1.04-.119 1.503 0 .544.064 1.24.155 1.721.02.107.031.202.023.208a8.12 8.12 0 0 1-.187.152 5.324 5.324 0 0 0-.949 1.02 5.49 5.49 0 0 0-.94 2.339 6.625 6.625 0 0 0-.023 1.357c.091.78.325 1.438.727 2.04l.13.195-.037.064c-.269.452-.498 1.105-.605 1.732-.084.496-.095.629-.095 1.294 0 .67.009.803.088 1.266.095.555.288 1.143.503 1.534.071.128.243.393.264.407.007.003-.014.067-.046.141a7.405 7.405 0 0 0-.548 1.873c-.062.417-.071.552-.071.991 0 .56.031.832.148 1.279L3.42 24h1.478l-.05-.091c-.297-.552-.325-1.575-.068-2.597.117-.472.25-.819.498-1.296l.148-.29v-.177c0-.165-.003-.184-.057-.293a.915.915 0 0 0-.194-.25 1.74 1.74 0 0 1-.385-.543c-.424-.92-.506-2.286-.208-3.451.124-.486.329-.918.544-1.154a.787.787 0 0 0 .223-.531c0-.195-.07-.355-.224-.522a3.136 3.136 0 0 1-.817-1.729c-.14-.96.114-2.005.69-2.834.563-.814 1.353-1.336 2.237-1.475.199-.033.57-.028.776.01.226.04.367.028.512-.041.179-.085.268-.19.374-.431.093-.215.165-.333.36-.576.234-.29.46-.489.822-.729.413-.27.884-.467 1.352-.561.17-.035.25-.04.569-.04.319 0 .398.005.569.04a4.07 4.07 0 0 1 1.914.997c.117.109.398.457.488.602.034.057.095.177.132.267.105.241.195.346.374.43.14.068.286.082.503.045.343-.058.607-.053.943.016 1.144.23 2.14 1.173 2.581 2.437.385 1.108.276 2.267-.296 3.153-.097.15-.193.27-.333.419-.301.322-.301.722-.001 1.053.493.539.801 1.866.708 3.036-.062.772-.26 1.463-.533 1.854a2.096 2.096 0 0 1-.224.258.916.916 0 0 0-.194.25c-.054.109-.057.128-.057.293v.178l.148.29c.248.476.38.823.498 1.295.253 1.008.231 2.01-.059 2.581a.845.845 0 0 0-.044.098c0 .006.329.009.732.009h.73l.02-.074.036-.134c.019-.076.057-.3.088-.516.029-.217.029-1.016 0-1.258-.11-.875-.295-1.57-.597-2.226-.032-.074-.053-.138-.046-.141.008-.005.057-.074.108-.152.376-.569.607-1.284.724-2.228.031-.26.031-1.378 0-1.628-.083-.645-.182-1.082-.348-1.525a6.083 6.083 0 0 0-.329-.7l-.038-.064.131-.194c.402-.604.636-1.262.727-2.04a6.625 6.625 0 0 0-.024-1.358 5.512 5.512 0 0 0-.939-2.339 5.325 5.325 0 0 0-.95-1.02 8.097 8.097 0 0 1-.186-.152.692.692 0 0 1 .023-.208c.208-1.087.201-2.443-.017-3.503-.19-.924-.535-1.658-.98-2.082-.354-.338-.716-.482-1.15-.455-.996.059-1.8 1.205-2.116 3.01a6.805 6.805 0 0 0-.097.726c0 .036-.007.066-.015.066a.96.96 0 0 1-.149-.078A4.857 4.857 0 0 0 12 3.03c-.832 0-1.687.243-2.456.698a.958.958 0 0 1-.148.078c-.008 0-.015-.03-.015-.066a6.71 6.71 0 0 0-.097-.725C8.997 1.392 8.337.319 7.46.048a2.096 2.096 0 0 0-.585-.041Zm.293 1.402c.248.197.523.759.682 1.388.03.113.06.244.069.292.007.047.026.152.041.233.067.365.098.76.102 1.24l.002.475-.12.175-.118.178h-.278c-.324 0-.646.041-.954.124l-.238.06c-.033.007-.038-.003-.057-.144a8.438 8.438 0 0 1 .016-2.323c.124-.788.413-1.501.696-1.711.067-.05.079-.049.157.013zm9.825-.012c.17.126.358.46.498.888.28.854.36 2.028.212 3.145-.019.14-.024.151-.057.144l-.238-.06a3.693 3.693 0 0 0-.954-.124h-.278l-.119-.178-.119-.175.002-.474c.004-.669.066-1.19.214-1.772.157-.623.434-1.185.68-1.382.078-.062.09-.063.159-.012z"
|
|
1966
|
+
})
|
|
1967
|
+
});
|
|
1968
|
+
case "llm-grok": return (0, react_jsx_runtime.jsxs)(Svg, {
|
|
1969
|
+
viewBox: "-33.7 -30.4 629.4 604.9",
|
|
1970
|
+
children: [(0, react_jsx_runtime.jsx)("path", {
|
|
1971
|
+
fill: "currentColor",
|
|
1972
|
+
d: "M411 105C376 80 334 66 289 66C173 66 79 160 79 276C79 306 85 329 95 353C117 407 87 451 0 542L178 383C150 355 134 318 134 277C134 192 203 123 289 123C310 123 330 127 348 134Z"
|
|
1973
|
+
}), (0, react_jsx_runtime.jsx)("path", {
|
|
1974
|
+
fill: "currentColor",
|
|
1975
|
+
d: "M167 448L230 418C248 426 268 430 289 430C374 430 443 361 443 277C443 256 439 234 431 214C427 206 416 204 407 210L217 349L562 2C480 103 475 144 494 229C518 333 468 422 391 459C319 494 235 498 167 448Z"
|
|
1976
|
+
})]
|
|
1977
|
+
});
|
|
1978
|
+
case "llm-commandcode": return (0, react_jsx_runtime.jsxs)(Svg, {
|
|
1979
|
+
viewBox: "-8.2 -8.2 152.5 152.5",
|
|
1980
|
+
children: [
|
|
1981
|
+
(0, react_jsx_runtime.jsx)("path", {
|
|
1982
|
+
fill: "currentColor",
|
|
1983
|
+
d: "m0 66.7959c0-31.4879 0-47.2318 9.78204-57.01386 9.78206-9.78204 25.52596-9.78204 57.01396-9.78204h2.5357c31.4883 0 47.2323 0 57.0143 9.78204 9.782 9.78206 9.782 25.52596 9.782 57.01396v2.5357c0 31.4883 0 47.2323-9.782 57.0143s-25.526 9.782-57.0144 9.782h-2.5357c-31.4879 0-47.2318 0-57.01386-9.782-9.78204-9.782-9.78204-25.526-9.78204-57.0144z"
|
|
1984
|
+
}),
|
|
1985
|
+
(0, react_jsx_runtime.jsx)("path", {
|
|
1986
|
+
clipRule: "evenodd",
|
|
1987
|
+
fill: "currentColor",
|
|
1988
|
+
fillRule: "evenodd",
|
|
1989
|
+
d: "m69.3317 5.56633h-2.5357c-15.9014 0-27.2674.01182-35.905 1.17312-8.4775 1.13977-13.4886 3.29415-17.173 6.97855s-5.83878 8.6955-6.97855 17.173c-1.1613 8.6376-1.17312 20.0036-1.17312 35.9049v2.5357c0 15.9014.01182 27.2674 1.17312 35.9054 1.13977 8.477 3.29415 13.488 6.97855 17.173 3.6844 3.684 8.6955 5.838 17.173 6.978 8.6376 1.161 20.0036 1.173 35.9049 1.173h2.5357c15.9014 0 27.2674-.012 35.9054-1.173 8.477-1.14 13.488-3.294 17.173-6.978 3.684-3.685 5.838-8.696 6.978-17.173 1.161-8.638 1.173-20.004 1.173-35.9053v-2.5357c0-15.9014-.012-27.2674-1.173-35.905-1.14-8.4775-3.294-13.4886-6.978-17.173-3.685-3.6844-8.696-5.83878-17.173-6.97855-8.638-1.1613-20.004-1.17312-35.9053-1.17312zm-59.54966 4.21571c-9.78204 9.78206-9.78204 25.52596-9.78204 57.01386v2.5357c0 31.4884 0 47.2324 9.78204 57.0144 9.78206 9.782 25.52596 9.782 57.01386 9.782h2.5357c31.4884 0 47.2324 0 57.0144-9.782s9.782-25.526 9.782-57.0143v-2.5357c0-31.488 0-47.2319-9.782-57.01396-9.782-9.78204-25.526-9.78204-57.0143-9.78204h-2.5357c-31.488 0-47.2319 0-57.01396 9.78204z"
|
|
1990
|
+
}),
|
|
1991
|
+
(0, react_jsx_runtime.jsx)("path", {
|
|
1992
|
+
fill: "var(--dsw-alias-bg-layer-1)",
|
|
1993
|
+
d: "m93.6604 26.1784c-8.982 0-16.2887 7.3067-16.2887 16.2888v6.9809h-18.6158v-6.9809c0-8.9821-7.3067-16.2888-16.2887-16.2888-8.9821 0-16.2888 7.3067-16.2888 16.2888s7.3067 16.2887 16.2888 16.2887h6.9809v18.6158h-6.9809c-8.9821 0-16.2888 7.3067-16.2888 16.2888 0 8.9825 7.3067 16.2885 16.2888 16.2885 8.982 0 16.2887-7.306 16.2887-16.2885v-6.981h18.6158v6.981c0 8.9825 7.3067 16.2885 16.2887 16.2885 8.9826 0 16.2886-7.306 16.2886-16.2885 0-8.9821-7.306-16.2888-16.2886-16.2888h-6.9809v-18.6158h6.9809c8.9826 0 16.2886-7.3066 16.2886-16.2887s-7.306-16.2888-16.2886-16.2888zm-6.9809 23.2697v-6.9809c0-3.8628 3.1182-6.9809 6.9809-6.9809 3.8628 0 6.9806 3.1181 6.9806 6.9809 0 3.8627-3.1178 6.9809-6.9806 6.9809zm-44.2123 0c-3.8628 0-6.9809-3.1182-6.9809-6.9809 0-3.8628 3.1181-6.9809 6.9809-6.9809 3.8627 0 6.9809 3.1181 6.9809 6.9809v6.9809zm16.2887 27.9236v-18.6158h18.6158v18.6158zm34.9045 23.2693c-3.8627 0-6.9809-3.1178-6.9809-6.9805v-6.981h6.9809c3.8628 0 6.9806 3.1182 6.9806 6.981 0 3.8627-3.1178 6.9805-6.9806 6.9805zm-51.1932 0c-3.8628 0-6.9809-3.1178-6.9809-6.9805 0-3.8628 3.1181-6.981 6.9809-6.981h6.9809v6.981c0 3.8627-3.1182 6.9805-6.9809 6.9805z"
|
|
1994
|
+
})
|
|
1995
|
+
]
|
|
1996
|
+
});
|
|
1997
|
+
case "llm-deepseek":
|
|
1998
|
+
case "llm-deepseek-official": return (0, react_jsx_runtime.jsx)(Svg, {
|
|
1999
|
+
viewBox: "0 0 23.16 17.04",
|
|
2000
|
+
children: (0, react_jsx_runtime.jsx)("path", {
|
|
2001
|
+
fill: "currentColor",
|
|
2002
|
+
d: DEEPSEEK_FISH_PATH
|
|
2003
|
+
})
|
|
2004
|
+
});
|
|
2005
|
+
case "llm-opencode-go": return (0, react_jsx_runtime.jsxs)(Svg, {
|
|
2006
|
+
viewBox: "120.6 86.4 286.1 359.4",
|
|
2007
|
+
children: [(0, react_jsx_runtime.jsx)("path", {
|
|
2008
|
+
fill: "currentColor",
|
|
2009
|
+
opacity: ".35",
|
|
2010
|
+
d: "M320 224V352H192V224H320Z"
|
|
2011
|
+
}), (0, react_jsx_runtime.jsx)("path", {
|
|
2012
|
+
fill: "currentColor",
|
|
2013
|
+
fillRule: "evenodd",
|
|
2014
|
+
d: "M384 416H128V96H384V416ZM320 160H192V352H320V160Z"
|
|
2015
|
+
})]
|
|
2016
|
+
});
|
|
2017
|
+
default: return (0, react_jsx_runtime.jsx)(Svg, {
|
|
2018
|
+
viewBox: "0 0 14 14",
|
|
2019
|
+
children: (0, react_jsx_runtime.jsx)("path", {
|
|
2020
|
+
fill: "currentColor",
|
|
2021
|
+
fillRule: "evenodd",
|
|
2022
|
+
clipRule: "evenodd",
|
|
2023
|
+
d: GENERIC_GLOBE_PATH
|
|
2024
|
+
})
|
|
2025
|
+
});
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
//#endregion
|
|
2029
|
+
//#region src/client/runtime-lock.ts
|
|
2030
|
+
/** Decode a wire reply only for the provider that owns the query. */
|
|
2031
|
+
function decodeBindingProvider(value, provider) {
|
|
2032
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
2033
|
+
const reply = value;
|
|
2034
|
+
return reply.provider === null || reply.provider === provider ? reply.provider : void 0;
|
|
2035
|
+
}
|
|
2036
|
+
/** Query installed declarations; one failed query must never become a successful unbound read. */
|
|
2037
|
+
async function fetchSessionBinding(rpc, sessionId, sources) {
|
|
2038
|
+
if (sources.length === 0) return {
|
|
2039
|
+
provider: null,
|
|
2040
|
+
failed: false
|
|
2041
|
+
};
|
|
2042
|
+
if (rpc === void 0) return {
|
|
2043
|
+
provider: null,
|
|
2044
|
+
failed: true
|
|
2045
|
+
};
|
|
2046
|
+
const replies = await Promise.all(sources.map(async (source) => {
|
|
2047
|
+
try {
|
|
2048
|
+
const result = await callPluginRpc(rpc, source.channel, source.endpoint, { sessionId });
|
|
2049
|
+
return result.ok ? decodeBindingProvider(result.value, source.provider) : void 0;
|
|
2050
|
+
} catch {
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
}));
|
|
2054
|
+
const bindings = new Set(replies.filter((value) => typeof value === "string"));
|
|
2055
|
+
return {
|
|
2056
|
+
provider: bindings.size === 1 ? [...bindings][0] : null,
|
|
2057
|
+
failed: replies.includes(void 0) || bindings.size > 1
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
/** Whether one provider remains selectable under a known lock read. */
|
|
2061
|
+
function providerSelectable(lock, provider) {
|
|
2062
|
+
return lock === null || provider === lock;
|
|
2063
|
+
}
|
|
2064
|
+
/**
|
|
2065
|
+
* Existing DSH history cannot convert to an External Agent. Blank sessions
|
|
2066
|
+
* and already-bound native sessions keep their current Agent choice.
|
|
2067
|
+
*/
|
|
2068
|
+
function agentProviderLocked(blank, bound, active = false) {
|
|
2069
|
+
return (blank === false || active) && bound === null;
|
|
2070
|
+
}
|
|
2071
|
+
/** Picker allow-check over the already-effective lock and the agentLocked bit. */
|
|
2072
|
+
function runtimeChoiceAllowed(lock, agentLocked, provider, currentProvider, agent) {
|
|
2073
|
+
if (!providerSelectable(lock, provider)) return false;
|
|
2074
|
+
if (agentLocked && agent) return provider === currentProvider;
|
|
2075
|
+
return true;
|
|
2076
|
+
}
|
|
2077
|
+
/**
|
|
2078
|
+
* Whether one provider remains selectable under a lock read that may have failed.
|
|
2079
|
+
* Failed reads keep only the known binding or current selection selectable;
|
|
2080
|
+
* active Agent selections reserve their runtime before the first binding arrives.
|
|
2081
|
+
* Unbound sessions with DSH history cannot select a new Agent provider.
|
|
2082
|
+
* Log reading is never gated by this.
|
|
2083
|
+
* @param state - Latest lock read for the session.
|
|
2084
|
+
* @param provider - Candidate provider for the pending selection.
|
|
2085
|
+
* @param currentProvider - Session current provider, if any.
|
|
2086
|
+
* @param context - Session history, request activity, and candidate Agent role.
|
|
2087
|
+
*/
|
|
2088
|
+
function isProviderAllowed(state, provider, currentProvider, context = {}) {
|
|
2089
|
+
if (state.failed && provider !== (state.provider ?? currentProvider)) return false;
|
|
2090
|
+
const lock = effectiveProviderLock(state, currentProvider, context.active, context.currentAgent);
|
|
2091
|
+
if (!providerSelectable(lock, provider)) return false;
|
|
2092
|
+
if (context.agent === true && (context.blank === false || context.active === true) && lock === null) return provider === currentProvider;
|
|
2093
|
+
return true;
|
|
2094
|
+
}
|
|
2095
|
+
/**
|
|
2096
|
+
* Effective single-provider lock for pickers that only understand
|
|
2097
|
+
* RuntimeProviderLock. Errors fail closed exactly as isProviderAllowed does.
|
|
2098
|
+
*/
|
|
2099
|
+
function effectiveProviderLock(state, currentProvider, active = false, currentAgent = false) {
|
|
2100
|
+
if (state.provider !== null) return state.provider;
|
|
2101
|
+
if (state.failed || active && currentAgent) return currentProvider ?? null;
|
|
2102
|
+
return null;
|
|
2103
|
+
}
|
|
2104
|
+
/**
|
|
2105
|
+
* Create one session lock store over an injecting query.
|
|
2106
|
+
* The query sees the previous snapshot for sticky failure mapping and never
|
|
2107
|
+
* throws (failures resolve to failed reads); refresh never rejects.
|
|
2108
|
+
*/
|
|
2109
|
+
function createProviderLockStore(query) {
|
|
2110
|
+
let current = {
|
|
2111
|
+
provider: null,
|
|
2112
|
+
failed: false
|
|
2113
|
+
};
|
|
2114
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2115
|
+
let generation = 0;
|
|
2116
|
+
const emit = () => {
|
|
2117
|
+
for (const listener of [...listeners]) listener();
|
|
2118
|
+
};
|
|
2119
|
+
return {
|
|
2120
|
+
subscribe: (listener) => {
|
|
2121
|
+
listeners.add(listener);
|
|
2122
|
+
return () => {
|
|
2123
|
+
listeners.delete(listener);
|
|
2124
|
+
};
|
|
2125
|
+
},
|
|
2126
|
+
getSnapshot: () => current,
|
|
2127
|
+
refresh: async () => {
|
|
2128
|
+
const seen = ++generation;
|
|
2129
|
+
const previous = current;
|
|
2130
|
+
let next;
|
|
2131
|
+
try {
|
|
2132
|
+
next = await query(previous);
|
|
2133
|
+
} catch {
|
|
2134
|
+
next = {
|
|
2135
|
+
provider: previous.provider,
|
|
2136
|
+
failed: true
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
if (seen !== generation) return current;
|
|
2140
|
+
if (next.provider !== current.provider || next.failed !== current.failed) {
|
|
2141
|
+
current = next;
|
|
2142
|
+
emit();
|
|
2143
|
+
}
|
|
2144
|
+
return current;
|
|
2145
|
+
}
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
//#endregion
|
|
2149
|
+
//#region \0dsh-css:src/client/picker/ComposerPicker.module.css.mjs
|
|
2150
|
+
const css$1 = ".GdQohq_root{min-width:0;position:relative}.GdQohq_trigger{width:fit-content;min-width:0;max-width:256px;min-height:28px;color:var(--dsw-alias-label-secondary);font:var(--dsw-font-xs-strong-13);cursor:pointer;background:0 0;border:none;border-radius:24px;outline:none;align-items:center;gap:4px;padding:4px 4px 4px 8px;display:flex}.GdQohq_trigger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.GdQohq_trigger:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}.GdQohq_trigger:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.GdQohq_triggerLabel{overflow-wrap:normal;text-overflow:ellipsis;white-space:nowrap;align-items:center;gap:4px;min-width:0;display:inline-flex;overflow:hidden}.GdQohq_triggerEffort{color:var(--dsw-alias-label-caption);flex:none}.GdQohq_chevron{color:var(--dsw-alias-label-caption);transition:transform .12s var(--ds-ease-in-out);flex:none}.GdQohq_chevronOpen{transform:rotate(180deg)}.GdQohq_embedded{width:100%}.GdQohq_embedded .GdQohq_trigger{background:var(--dsw-alias-interactive-bg-hover);width:100%;max-width:none;min-height:36px;color:var(--dsw-alias-label-primary);border-radius:10px;justify-content:space-between;padding:4px 10px}.GdQohq_embedded .GdQohq_trigger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.GdQohq_capsule{width:100%}.GdQohq_capsule .GdQohq_trigger{border:1px solid var(--dsw-alias-border-inverted);background:var(--dsw-specific-input-major);width:100%;max-width:none;min-height:32px;color:var(--dsw-alias-label-primary);border-radius:24px;justify-content:space-between;padding:4px 8px 4px 10px}.GdQohq_capsule .GdQohq_trigger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.GdQohq_menu{box-sizing:border-box;width:min(280px, max(0px, calc(100vw - 32px - env(safe-area-inset-left) - env(safe-area-inset-right))));max-height:min(360px, calc(56dvh - env(safe-area-inset-bottom)));border:1px solid var(--dsw-alias-border-inverted);background:var(--dsw-specific-menu);backdrop-filter:var(--dsw-menu-backdrop-filter);box-shadow:var(--dsw-shadow-lv3);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-radius:12px;flex-direction:column;padding:4px;display:flex;overflow:hidden}.GdQohq_paneHeader{flex:none;grid-template-columns:32px minmax(0,1fr) 32px;align-items:center;gap:8px;min-height:40px;padding:2px 4px;display:grid}.GdQohq_paneHeader .GdQohq_headerButton{width:32px;min-width:32px;height:32px;color:var(--dsw-alias-label-secondary);padding:0}.GdQohq_paneHeader .GdQohq_headerButton:last-child{justify-self:end}.GdQohq_paneTitle{min-width:0;color:var(--dsw-alias-label-primary);font:var(--dsw-font-s-strong-14);overflow-wrap:anywhere;text-align:center;padding:4px 8px}.GdQohq_searchSlot{min-width:0;overflow:hidden}.GdQohq_headerSearch{box-sizing:border-box;width:100%;min-width:0;max-width:100%;display:flex}.GdQohq_headerSearch input::-webkit-search-cancel-button{display:none}.GdQohq_list{min-height:0;overflow:hidden auto}.GdQohq_status,.GdQohq_empty{color:var(--dsw-alias-label-tertiary);font:var(--dsw-font-xs-13);overflow-wrap:anywhere;padding:10px}.GdQohq_error,.GdQohq_warning{background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);font:var(--dsw-font-xxs-12);overflow-wrap:anywhere;border-radius:8px;justify-content:space-between;align-items:flex-start;gap:8px;margin-bottom:4px;padding:7px 8px;display:flex}.GdQohq_warning{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-state-warn-label)}.GdQohq_retry{color:inherit;font:var(--dsw-font-xxs-strong-12);cursor:pointer;background:0 0;border:none;flex:none;padding:0}.GdQohq_groups{min-height:0}.GdQohq_group+.GdQohq_group{margin-top:4px}.GdQohq_groupTitle{z-index:1;background:var(--dsw-specific-menu);color:var(--dsw-alias-label-tertiary);font:var(--dsw-font-xxs-strong-12);overflow-wrap:anywhere;align-items:center;gap:6px;padding:5px 8px 3px;display:flex;position:sticky;top:0}.GdQohq_runtimeMark{min-width:14px;color:var(--dsw-alias-label-primary);font:var(--dsw-font-xxs-strong-12);justify-content:center;align-items:center;display:inline-flex}.GdQohq_runtimeMark svg{width:14px;height:14px;display:block}.GdQohq_option{box-sizing:border-box;width:auto;min-width:100%;min-height:38px;color:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:10px;outline:none;align-items:center;gap:8px;padding:6px 8px;display:flex}.GdQohq_option:hover:not(:disabled),.GdQohq_option:focus-visible{background:var(--dsw-alias-interactive-bg-hover)}.GdQohq_selected{background:0 0}.GdQohq_option:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.GdQohq_optionCopy{flex-direction:column;flex:1;min-width:0;display:flex}.GdQohq_modelName{color:inherit;font:var(--dsw-font-s-strong-14);overflow-wrap:anywhere}.GdQohq_description{color:var(--dsw-alias-label-tertiary);font:var(--dsw-font-xxs-12);overflow-wrap:anywhere}.GdQohq_check{color:var(--dsw-alias-label-primary);flex:0 0 18px;place-items:center;display:grid}.GdQohq_cell{box-sizing:border-box;width:auto;min-width:100%;height:40px;min-height:40px;color:var(--dsw-alias-label-primary);font:var(--dsw-font-s-14);cursor:pointer;text-align:left;background:0 0;border:none;border-radius:10px;align-items:center;gap:8px;padding:0 10px;display:flex}.GdQohq_cell:hover{background:var(--dsw-alias-interactive-bg-hover)}.GdQohq_cellLabel{white-space:nowrap;flex:none}.GdQohq_cellValue{min-width:0;color:var(--dsw-alias-label-tertiary);text-align:right;text-overflow:ellipsis;white-space:nowrap;flex:auto;overflow:hidden}.GdQohq_cellChevron{color:var(--dsw-alias-label-tertiary);flex:none}";
|
|
2151
|
+
const tagId$1 = "dsh-model-switch/ComposerPicker.module.css";
|
|
2152
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
2153
|
+
const tag = document.createElement("style");
|
|
2154
|
+
tag.dataset.plugin = "dsh-model-switch";
|
|
2155
|
+
tag.dataset.pluginCss = tagId$1;
|
|
2156
|
+
tag.textContent = css$1;
|
|
2157
|
+
document.head.appendChild(tag);
|
|
2158
|
+
}
|
|
2159
|
+
var ComposerPicker_module_css_default = {
|
|
2160
|
+
"capsule": "GdQohq_capsule",
|
|
2161
|
+
"cell": "GdQohq_cell",
|
|
2162
|
+
"cellChevron": "GdQohq_cellChevron",
|
|
2163
|
+
"cellLabel": "GdQohq_cellLabel",
|
|
2164
|
+
"cellValue": "GdQohq_cellValue",
|
|
2165
|
+
"check": "GdQohq_check",
|
|
2166
|
+
"chevron": "GdQohq_chevron",
|
|
2167
|
+
"chevronOpen": "GdQohq_chevronOpen",
|
|
2168
|
+
"description": "GdQohq_description",
|
|
2169
|
+
"embedded": "GdQohq_embedded",
|
|
2170
|
+
"empty": "GdQohq_empty",
|
|
2171
|
+
"error": "GdQohq_error",
|
|
2172
|
+
"group": "GdQohq_group",
|
|
2173
|
+
"groups": "GdQohq_groups",
|
|
2174
|
+
"groupTitle": "GdQohq_groupTitle",
|
|
2175
|
+
"headerButton": "GdQohq_headerButton",
|
|
2176
|
+
"headerSearch": "GdQohq_headerSearch",
|
|
2177
|
+
"list": "GdQohq_list",
|
|
2178
|
+
"menu": "GdQohq_menu",
|
|
2179
|
+
"modelName": "GdQohq_modelName",
|
|
2180
|
+
"option": "GdQohq_option",
|
|
2181
|
+
"optionCopy": "GdQohq_optionCopy",
|
|
2182
|
+
"paneHeader": "GdQohq_paneHeader",
|
|
2183
|
+
"paneTitle": "GdQohq_paneTitle",
|
|
2184
|
+
"retry": "GdQohq_retry",
|
|
2185
|
+
"root": "GdQohq_root",
|
|
2186
|
+
"runtimeMark": "GdQohq_runtimeMark",
|
|
2187
|
+
"searchSlot": "GdQohq_searchSlot",
|
|
2188
|
+
"selected": "GdQohq_selected",
|
|
2189
|
+
"status": "GdQohq_status",
|
|
2190
|
+
"trigger": "GdQohq_trigger",
|
|
2191
|
+
"triggerEffort": "GdQohq_triggerEffort",
|
|
2192
|
+
"triggerLabel": "GdQohq_triggerLabel",
|
|
2193
|
+
"warning": "GdQohq_warning"
|
|
2194
|
+
};
|
|
2195
|
+
//#endregion
|
|
2196
|
+
//#region src/client/picker/ComposerPicker.tsx
|
|
2197
|
+
/**
|
|
2198
|
+
* Composer model seat: suffix-grouped Model / Effort / Context / Fast / Thinking.
|
|
2199
|
+
*/
|
|
2200
|
+
function sameSelection(left, right) {
|
|
2201
|
+
return left?.provider === right?.provider && left?.model === right?.model && left?.reasoningEffort === right?.reasoningEffort;
|
|
2202
|
+
}
|
|
2203
|
+
function classNames(...parts) {
|
|
2204
|
+
return parts.filter((part) => typeof part === "string" && part.length > 0).join(" ");
|
|
2205
|
+
}
|
|
2206
|
+
function ModelPaneHeader({ title, backLabel, searchLabel, closeSearchLabel, searchable, searching, query, onBack, onStartSearch, onCloseSearch, onQueryChange }) {
|
|
2207
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2208
|
+
className: ComposerPicker_module_css_default.paneHeader,
|
|
2209
|
+
children: [
|
|
2210
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2211
|
+
variant: "ghost",
|
|
2212
|
+
size: "sm",
|
|
2213
|
+
className: ComposerPicker_module_css_default.headerButton,
|
|
2214
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronLeftOutlineRegular, { size: 14 }),
|
|
2215
|
+
"aria-label": backLabel,
|
|
2216
|
+
onClick: onBack
|
|
2217
|
+
}),
|
|
2218
|
+
searching ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2219
|
+
className: ComposerPicker_module_css_default.searchSlot,
|
|
2220
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
2221
|
+
className: ComposerPicker_module_css_default.headerSearch ?? "",
|
|
2222
|
+
type: "search",
|
|
2223
|
+
autoFocus: true,
|
|
2224
|
+
value: query,
|
|
2225
|
+
placeholder: searchLabel,
|
|
2226
|
+
"aria-label": searchLabel,
|
|
2227
|
+
onChange: (event) => {
|
|
2228
|
+
onQueryChange(event.currentTarget.value);
|
|
2229
|
+
}
|
|
2230
|
+
})
|
|
2231
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2232
|
+
className: ComposerPicker_module_css_default.paneTitle,
|
|
2233
|
+
children: title
|
|
2234
|
+
}),
|
|
2235
|
+
searchable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2236
|
+
variant: "ghost",
|
|
2237
|
+
size: "sm",
|
|
2238
|
+
className: ComposerPicker_module_css_default.headerButton,
|
|
2239
|
+
icon: searching ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular, { size: 16 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutlineRegular, { size: 16 }),
|
|
2240
|
+
"aria-label": searching ? closeSearchLabel : searchLabel,
|
|
2241
|
+
onClick: searching ? onCloseSearch : onStartSearch
|
|
2242
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { "aria-hidden": true })
|
|
2243
|
+
]
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
function RuntimeIcon({ provider, roleOf }) {
|
|
2247
|
+
const mark = isAgentRole(roleOf?.(provider)) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderMark, { providerKey: provider }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderMark, { providerKey: "deepseek-official" });
|
|
2248
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2249
|
+
className: ComposerPicker_module_css_default.runtimeMark,
|
|
2250
|
+
children: mark
|
|
2251
|
+
});
|
|
2252
|
+
}
|
|
2253
|
+
function ComposerPicker({ locked, providerLock = null, agentLocked = false, available, unavailableReason, directory, t, draft, onDraftChange, embedded, tone, resolveInteractionOperations, roleOf }) {
|
|
2254
|
+
const { snapshot: state, getDirectorySnapshot, load, select } = directory;
|
|
2255
|
+
const [pane, setPane] = (0, react.useState)("root");
|
|
2256
|
+
const [searching, setSearching] = (0, react.useState)(false);
|
|
2257
|
+
const [query, setQuery] = (0, react.useState)("");
|
|
2258
|
+
const [toast, setToast] = (0, react.useState)(null);
|
|
2259
|
+
const [acceptedSelection, setAcceptedSelection] = (0, react.useState)();
|
|
2260
|
+
const toastSeq = (0, react.useRef)(0);
|
|
2261
|
+
const selectionGeneration = (0, react.useRef)(0);
|
|
2262
|
+
const lastActionRef = (0, react.useRef)("load");
|
|
2263
|
+
const lockedRef = (0, react.useRef)(locked);
|
|
2264
|
+
lockedRef.current = locked;
|
|
2265
|
+
const families = (0, react.useMemo)(() => groupFamilies(state.groups), [state.groups]);
|
|
2266
|
+
const currentSelection = draft ?? acceptedSelection?.selection ?? state.current;
|
|
2267
|
+
const currentCanBeUsed = draft !== void 0 || acceptedSelection !== void 0 || state.routable !== false;
|
|
2268
|
+
const family = currentSelection === null ? void 0 : findFamily(families, currentSelection.provider, currentSelection.model);
|
|
2269
|
+
const member = family === void 0 || currentSelection === null ? void 0 : findMember(family, currentSelection.model);
|
|
2270
|
+
const reasoning = member?.model.reasoning;
|
|
2271
|
+
const effectiveEffort = currentSelection?.reasoningEffort ?? reasoning?.defaultEffort;
|
|
2272
|
+
const effortLabel = reasoning === void 0 ? void 0 : effectiveEffort === void 0 ? t("effort.providerDefault") : reasoning.efforts.find((level) => level.id === effectiveEffort)?.name ?? effectiveEffort;
|
|
2273
|
+
const contextLabel = family === void 0 || member === void 0 ? void 0 : contextLabelForMember(family, member);
|
|
2274
|
+
const thinkingPair = family !== void 0 && member !== void 0 ? thinkingSiblings(family, member) : null;
|
|
2275
|
+
const visibleFamilies = (0, react.useMemo)(() => filterFamilies(families, query), [families, query]);
|
|
2276
|
+
const sections = (0, react.useMemo)(() => sectionFamilies(visibleFamilies), [visibleFamilies]);
|
|
2277
|
+
const busy = state.status === "selecting";
|
|
2278
|
+
const choiceAllowed = (provider) => runtimeChoiceAllowed(providerLock, agentLocked, provider, currentSelection?.provider, isAgentRole(roleOf?.(provider)));
|
|
2279
|
+
const reload = () => {
|
|
2280
|
+
if (lockedRef.current) return;
|
|
2281
|
+
lastActionRef.current = "load";
|
|
2282
|
+
load();
|
|
2283
|
+
};
|
|
2284
|
+
const { id, open, menuStyle, triggerRef, menuRef, close, onTriggerPointerDown, onTriggerClick } = useComposerPickerSurface({
|
|
2285
|
+
locked,
|
|
2286
|
+
embedded: embedded ?? false,
|
|
2287
|
+
pane,
|
|
2288
|
+
reload,
|
|
2289
|
+
onOpen: () => {
|
|
2290
|
+
setPane(embedded && tone !== "capsule" ? "model" : "root");
|
|
2291
|
+
setSearching(false);
|
|
2292
|
+
setQuery("");
|
|
2293
|
+
},
|
|
2294
|
+
onClose: () => {
|
|
2295
|
+
setPane("root");
|
|
2296
|
+
setSearching(false);
|
|
2297
|
+
setQuery("");
|
|
2298
|
+
},
|
|
2299
|
+
...tone === void 0 ? {} : { tone },
|
|
2300
|
+
...resolveInteractionOperations === void 0 ? {} : { resolveInteractionOperations }
|
|
2301
|
+
});
|
|
2302
|
+
(0, react.useEffect)(() => {
|
|
2303
|
+
if (available) {
|
|
2304
|
+
lastActionRef.current = "load";
|
|
2305
|
+
load();
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
2308
|
+
selectionGeneration.current += 1;
|
|
2309
|
+
setAcceptedSelection(void 0);
|
|
2310
|
+
}, [available, load]);
|
|
2311
|
+
(0, react.useEffect)(() => {
|
|
2312
|
+
if (acceptedSelection === void 0) return;
|
|
2313
|
+
if (!sameSelection(state.current, acceptedSelection.projectedBeforeRequest)) setAcceptedSelection(void 0);
|
|
2314
|
+
}, [acceptedSelection, state.current]);
|
|
2315
|
+
(0, react.useEffect)(() => {
|
|
2316
|
+
if (!locked) return;
|
|
2317
|
+
selectionGeneration.current += 1;
|
|
2318
|
+
setAcceptedSelection(void 0);
|
|
2319
|
+
}, [locked]);
|
|
2320
|
+
(0, react.useLayoutEffect)(() => {
|
|
2321
|
+
if (!open) return;
|
|
2322
|
+
const menu = menuRef.current;
|
|
2323
|
+
if (menu !== null && !menu.contains(document.activeElement)) menu.focus();
|
|
2324
|
+
}, [
|
|
2325
|
+
open,
|
|
2326
|
+
pane,
|
|
2327
|
+
searching
|
|
2328
|
+
]);
|
|
2329
|
+
if (!available) return null;
|
|
2330
|
+
const returnToRoot = () => {
|
|
2331
|
+
setPane("root");
|
|
2332
|
+
setSearching(false);
|
|
2333
|
+
setQuery("");
|
|
2334
|
+
};
|
|
2335
|
+
const settleSelection = (outcome) => {
|
|
2336
|
+
if (outcome.committed) {
|
|
2337
|
+
if (!outcome.mainDefaultRestored) {
|
|
2338
|
+
toastSeq.current += 1;
|
|
2339
|
+
setToast({
|
|
2340
|
+
seq: toastSeq.current,
|
|
2341
|
+
text: t("plan.notRestored")
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2346
|
+
const message = getDirectorySnapshot().error;
|
|
2347
|
+
if (message !== null) {
|
|
2348
|
+
toastSeq.current += 1;
|
|
2349
|
+
setToast({
|
|
2350
|
+
seq: toastSeq.current,
|
|
2351
|
+
text: t("error.action", { message })
|
|
2352
|
+
});
|
|
2353
|
+
}
|
|
2354
|
+
};
|
|
2355
|
+
const applySelection = (next) => {
|
|
2356
|
+
if (lockedRef.current || !choiceAllowed(next.provider)) return;
|
|
2357
|
+
if (onDraftChange !== void 0) {
|
|
2358
|
+
onDraftChange(next);
|
|
2359
|
+
returnToRoot();
|
|
2360
|
+
return;
|
|
2361
|
+
}
|
|
2362
|
+
if (currentCanBeUsed && sameSelection(currentSelection, next)) {
|
|
2363
|
+
returnToRoot();
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2366
|
+
lastActionRef.current = "select";
|
|
2367
|
+
if (select !== void 0) {
|
|
2368
|
+
const generation = ++selectionGeneration.current;
|
|
2369
|
+
const projectedBeforeRequest = state.current;
|
|
2370
|
+
beginSelection(() => select(next), returnToRoot, (outcome) => {
|
|
2371
|
+
if (generation !== selectionGeneration.current || lockedRef.current) return;
|
|
2372
|
+
if (outcome.committed) setAcceptedSelection({
|
|
2373
|
+
selection: next,
|
|
2374
|
+
projectedBeforeRequest
|
|
2375
|
+
});
|
|
2376
|
+
settleSelection(outcome);
|
|
2377
|
+
});
|
|
2378
|
+
}
|
|
2379
|
+
};
|
|
2380
|
+
const chooseMember = (nextFamily, next, effort) => {
|
|
2381
|
+
applySelection(selectionOf(nextFamily, next, effort));
|
|
2382
|
+
};
|
|
2383
|
+
const chooseEffort = (effort) => {
|
|
2384
|
+
if (family === void 0 || member === void 0) return;
|
|
2385
|
+
applySelection(selectionOf(family, member, effort));
|
|
2386
|
+
};
|
|
2387
|
+
const modelLabel = family?.name ?? member?.model.name ?? currentSelection?.model ?? t("trigger.fallback");
|
|
2388
|
+
const contextBit = contextLabel === void 0 || contextLabel === "standard" ? void 0 : member?.contextTier === null ? void 0 : contextLabel;
|
|
2389
|
+
const triggerBits = [
|
|
2390
|
+
modelLabel,
|
|
2391
|
+
...effortLabel === void 0 ? [] : [effortLabel],
|
|
2392
|
+
...member?.fast === true ? [t("menu.fast")] : [],
|
|
2393
|
+
...contextBit === void 0 ? [] : [contextBit],
|
|
2394
|
+
...thinkingPair !== null && member?.thinking === true ? [t("menu.thinking")] : []
|
|
2395
|
+
];
|
|
2396
|
+
const contextDisplay = (label) => label === "standard" ? t("context.standard") : label;
|
|
2397
|
+
const triggerLabel = triggerBits.join(" · ");
|
|
2398
|
+
const triggerAria = currentSelection === null ? t("trigger.selectAria") : t("trigger.aria", { model: triggerLabel });
|
|
2399
|
+
const onRootKeyDown = (event) => {
|
|
2400
|
+
if (lockedRef.current) return;
|
|
2401
|
+
if (event.key === "Escape" && open) {
|
|
2402
|
+
event.preventDefault();
|
|
2403
|
+
if (pane === "model" && searching) {
|
|
2404
|
+
setSearching(false);
|
|
2405
|
+
setQuery("");
|
|
2406
|
+
} else if (pane !== "root") returnToRoot();
|
|
2407
|
+
else close(true);
|
|
2408
|
+
}
|
|
2409
|
+
};
|
|
2410
|
+
const paneHeader = pane === "root" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelPaneHeader, {
|
|
2411
|
+
title: t({
|
|
2412
|
+
model: "menu.model",
|
|
2413
|
+
effort: "menu.effort",
|
|
2414
|
+
context: "menu.context",
|
|
2415
|
+
fast: "menu.fast",
|
|
2416
|
+
thinking: "menu.thinking"
|
|
2417
|
+
}[pane]),
|
|
2418
|
+
backLabel: t("menu.back"),
|
|
2419
|
+
searchLabel: t("menu.search"),
|
|
2420
|
+
closeSearchLabel: t("menu.closeSearch"),
|
|
2421
|
+
searchable: pane === "model",
|
|
2422
|
+
searching: pane === "model" && searching,
|
|
2423
|
+
query,
|
|
2424
|
+
onBack: returnToRoot,
|
|
2425
|
+
onStartSearch: () => {
|
|
2426
|
+
setSearching(true);
|
|
2427
|
+
},
|
|
2428
|
+
onCloseSearch: () => {
|
|
2429
|
+
setSearching(false);
|
|
2430
|
+
setQuery("");
|
|
2431
|
+
},
|
|
2432
|
+
onQueryChange: setQuery
|
|
2433
|
+
});
|
|
2434
|
+
const menu = open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2435
|
+
ref: menuRef,
|
|
2436
|
+
id: `${id}-menu`,
|
|
2437
|
+
className: ComposerPicker_module_css_default.menu,
|
|
2438
|
+
style: menuStyle,
|
|
2439
|
+
role: "menu",
|
|
2440
|
+
tabIndex: -1,
|
|
2441
|
+
"aria-label": t("menu.aria"),
|
|
2442
|
+
"aria-busy": state.status === "loading" || busy,
|
|
2443
|
+
onPointerDown: (event) => {
|
|
2444
|
+
event.stopPropagation();
|
|
2445
|
+
},
|
|
2446
|
+
children: [paneHeader, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2447
|
+
className: ComposerPicker_module_css_default.list,
|
|
2448
|
+
children: [
|
|
2449
|
+
pane === "root" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2450
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2451
|
+
type: "button",
|
|
2452
|
+
role: "menuitem",
|
|
2453
|
+
className: ComposerPicker_module_css_default.cell,
|
|
2454
|
+
onClick: () => {
|
|
2455
|
+
setPane("model");
|
|
2456
|
+
setSearching(false);
|
|
2457
|
+
setQuery("");
|
|
2458
|
+
},
|
|
2459
|
+
children: [
|
|
2460
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2461
|
+
className: ComposerPicker_module_css_default.cellLabel,
|
|
2462
|
+
children: t("menu.model")
|
|
2463
|
+
}),
|
|
2464
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2465
|
+
className: ComposerPicker_module_css_default.cellValue,
|
|
2466
|
+
children: family?.name ?? modelLabel
|
|
2467
|
+
}),
|
|
2468
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, {
|
|
2469
|
+
size: 14,
|
|
2470
|
+
className: ComposerPicker_module_css_default.cellChevron
|
|
2471
|
+
})
|
|
2472
|
+
]
|
|
2473
|
+
}),
|
|
2474
|
+
reasoning !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2475
|
+
type: "button",
|
|
2476
|
+
role: "menuitem",
|
|
2477
|
+
className: ComposerPicker_module_css_default.cell,
|
|
2478
|
+
onClick: () => {
|
|
2479
|
+
setPane("effort");
|
|
2480
|
+
},
|
|
2481
|
+
children: [
|
|
2482
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2483
|
+
className: ComposerPicker_module_css_default.cellLabel,
|
|
2484
|
+
children: t("menu.effort")
|
|
2485
|
+
}),
|
|
2486
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2487
|
+
className: ComposerPicker_module_css_default.cellValue,
|
|
2488
|
+
children: effortLabel
|
|
2489
|
+
}),
|
|
2490
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, {
|
|
2491
|
+
size: 14,
|
|
2492
|
+
className: ComposerPicker_module_css_default.cellChevron
|
|
2493
|
+
})
|
|
2494
|
+
]
|
|
2495
|
+
}),
|
|
2496
|
+
family !== void 0 && familyHasContextChoices(family) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2497
|
+
type: "button",
|
|
2498
|
+
role: "menuitem",
|
|
2499
|
+
className: ComposerPicker_module_css_default.cell,
|
|
2500
|
+
onClick: () => {
|
|
2501
|
+
setPane("context");
|
|
2502
|
+
},
|
|
2503
|
+
children: [
|
|
2504
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2505
|
+
className: ComposerPicker_module_css_default.cellLabel,
|
|
2506
|
+
children: t("menu.context")
|
|
2507
|
+
}),
|
|
2508
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2509
|
+
className: ComposerPicker_module_css_default.cellValue,
|
|
2510
|
+
children: contextDisplay(contextLabel ?? "")
|
|
2511
|
+
}),
|
|
2512
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, {
|
|
2513
|
+
size: 14,
|
|
2514
|
+
className: ComposerPicker_module_css_default.cellChevron
|
|
2515
|
+
})
|
|
2516
|
+
]
|
|
2517
|
+
}),
|
|
2518
|
+
family !== void 0 && familyHasFast(family) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2519
|
+
type: "button",
|
|
2520
|
+
role: "menuitem",
|
|
2521
|
+
className: ComposerPicker_module_css_default.cell,
|
|
2522
|
+
onClick: () => {
|
|
2523
|
+
setPane("fast");
|
|
2524
|
+
},
|
|
2525
|
+
children: [
|
|
2526
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2527
|
+
className: ComposerPicker_module_css_default.cellLabel,
|
|
2528
|
+
children: t("menu.fast")
|
|
2529
|
+
}),
|
|
2530
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2531
|
+
className: ComposerPicker_module_css_default.cellValue,
|
|
2532
|
+
children: member?.fast === true ? t("fast.on") : t("fast.off")
|
|
2533
|
+
}),
|
|
2534
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, {
|
|
2535
|
+
size: 14,
|
|
2536
|
+
className: ComposerPicker_module_css_default.cellChevron
|
|
2537
|
+
})
|
|
2538
|
+
]
|
|
2539
|
+
}),
|
|
2540
|
+
thinkingPair !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2541
|
+
type: "button",
|
|
2542
|
+
role: "menuitem",
|
|
2543
|
+
className: ComposerPicker_module_css_default.cell,
|
|
2544
|
+
onClick: () => {
|
|
2545
|
+
setPane("thinking");
|
|
2546
|
+
},
|
|
2547
|
+
children: [
|
|
2548
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2549
|
+
className: ComposerPicker_module_css_default.cellLabel,
|
|
2550
|
+
children: t("menu.thinking")
|
|
2551
|
+
}),
|
|
2552
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2553
|
+
className: ComposerPicker_module_css_default.cellValue,
|
|
2554
|
+
children: member?.thinking === true ? t("thinking.on") : t("thinking.off")
|
|
2555
|
+
}),
|
|
2556
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, {
|
|
2557
|
+
size: 14,
|
|
2558
|
+
className: ComposerPicker_module_css_default.cellChevron
|
|
2559
|
+
})
|
|
2560
|
+
]
|
|
2561
|
+
})
|
|
2562
|
+
] }),
|
|
2563
|
+
pane === "model" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2564
|
+
state.status === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2565
|
+
className: ComposerPicker_module_css_default.status,
|
|
2566
|
+
children: t("status.loading")
|
|
2567
|
+
}),
|
|
2568
|
+
state.error !== null && lastActionRef.current === "load" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2569
|
+
className: ComposerPicker_module_css_default.error,
|
|
2570
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("error.action", { message: state.error }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2571
|
+
type: "button",
|
|
2572
|
+
className: ComposerPicker_module_css_default.retry,
|
|
2573
|
+
disabled: locked,
|
|
2574
|
+
onClick: reload,
|
|
2575
|
+
children: t("retry")
|
|
2576
|
+
})]
|
|
2577
|
+
}),
|
|
2578
|
+
state.failures.map((failure) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2579
|
+
className: ComposerPicker_module_css_default.warning,
|
|
2580
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("warning.groupLoad", {
|
|
2581
|
+
name: failure.name,
|
|
2582
|
+
message: failure.message
|
|
2583
|
+
}) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2584
|
+
type: "button",
|
|
2585
|
+
className: ComposerPicker_module_css_default.retry,
|
|
2586
|
+
disabled: locked,
|
|
2587
|
+
onClick: reload,
|
|
2588
|
+
children: t("retry")
|
|
2589
|
+
})]
|
|
2590
|
+
}, failure.id)),
|
|
2591
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2592
|
+
className: classNames(ComposerPicker_module_css_default.groups, "scrollable"),
|
|
2593
|
+
children: sections.map((section) => {
|
|
2594
|
+
const headingId = `${id}-${section.provider}`;
|
|
2595
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2596
|
+
role: "group",
|
|
2597
|
+
"aria-labelledby": headingId,
|
|
2598
|
+
className: ComposerPicker_module_css_default.group,
|
|
2599
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2600
|
+
className: ComposerPicker_module_css_default.groupTitle,
|
|
2601
|
+
id: headingId,
|
|
2602
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RuntimeIcon, {
|
|
2603
|
+
provider: section.provider,
|
|
2604
|
+
...roleOf === void 0 ? {} : { roleOf }
|
|
2605
|
+
}), section.providerName]
|
|
2606
|
+
}), section.families.map((item) => {
|
|
2607
|
+
const selected = currentSelection?.provider === item.provider && item.members.some((entry) => entry.model.id === currentSelection.model);
|
|
2608
|
+
const representative = member !== void 0 && family?.provider === item.provider && family.base === item.base ? member : item.members.find((entry) => !entry.fast && entry.contextTier === null) ?? item.members[0];
|
|
2609
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2610
|
+
type: "button",
|
|
2611
|
+
role: "menuitemradio",
|
|
2612
|
+
"aria-checked": selected,
|
|
2613
|
+
className: classNames(ComposerPicker_module_css_default.option, selected && ComposerPicker_module_css_default.selected),
|
|
2614
|
+
disabled: locked || busy || !choiceAllowed(item.provider),
|
|
2615
|
+
onClick: () => {
|
|
2616
|
+
if (representative === void 0) return;
|
|
2617
|
+
chooseMember(item, representative);
|
|
2618
|
+
},
|
|
2619
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2620
|
+
className: ComposerPicker_module_css_default.optionCopy,
|
|
2621
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2622
|
+
className: ComposerPicker_module_css_default.modelName,
|
|
2623
|
+
children: item.name
|
|
2624
|
+
})
|
|
2625
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2626
|
+
className: ComposerPicker_module_css_default.check,
|
|
2627
|
+
children: selected ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, { size: 16 }) : null
|
|
2628
|
+
})]
|
|
2629
|
+
}, `${item.provider}:${item.base}`);
|
|
2630
|
+
})]
|
|
2631
|
+
}, section.provider);
|
|
2632
|
+
})
|
|
2633
|
+
}),
|
|
2634
|
+
state.status === "ready" && visibleFamilies.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2635
|
+
className: ComposerPicker_module_css_default.empty,
|
|
2636
|
+
children: t("empty.models")
|
|
2637
|
+
})
|
|
2638
|
+
] }),
|
|
2639
|
+
pane === "effort" && (reasoning === void 0 || reasoning.efforts.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2640
|
+
className: ComposerPicker_module_css_default.empty,
|
|
2641
|
+
children: t("empty.efforts")
|
|
2642
|
+
}) : reasoning.efforts.map((level) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2643
|
+
type: "button",
|
|
2644
|
+
role: "menuitemradio",
|
|
2645
|
+
"aria-checked": effectiveEffort === level.id,
|
|
2646
|
+
className: classNames(ComposerPicker_module_css_default.option, effectiveEffort === level.id && ComposerPicker_module_css_default.selected),
|
|
2647
|
+
disabled: locked || busy || family !== void 0 && !choiceAllowed(family.provider),
|
|
2648
|
+
onClick: () => {
|
|
2649
|
+
chooseEffort(level.id);
|
|
2650
|
+
},
|
|
2651
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2652
|
+
className: ComposerPicker_module_css_default.optionCopy,
|
|
2653
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2654
|
+
className: ComposerPicker_module_css_default.modelName,
|
|
2655
|
+
children: level.name
|
|
2656
|
+
})
|
|
2657
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2658
|
+
className: ComposerPicker_module_css_default.check,
|
|
2659
|
+
children: effectiveEffort === level.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, { size: 16 }) : null
|
|
2660
|
+
})]
|
|
2661
|
+
}, level.id))),
|
|
2662
|
+
pane === "context" && family !== void 0 && member !== void 0 && contextTiers(family).map((row) => {
|
|
2663
|
+
const next = pickVariant(family, member, { contextTier: row.tier });
|
|
2664
|
+
const selected = member.contextTier === row.tier;
|
|
2665
|
+
const honored = next.contextTier === row.tier && next.fast === member.fast && next.thinking === member.thinking;
|
|
2666
|
+
const unreachable = !selected && (!honored || next.model.id === member.model.id);
|
|
2667
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2668
|
+
type: "button",
|
|
2669
|
+
role: "menuitemradio",
|
|
2670
|
+
"aria-checked": selected,
|
|
2671
|
+
className: classNames(ComposerPicker_module_css_default.option, selected && ComposerPicker_module_css_default.selected),
|
|
2672
|
+
disabled: locked || busy || !choiceAllowed(family.provider) || unreachable,
|
|
2673
|
+
title: unreachable ? t("choice.unavailable") : void 0,
|
|
2674
|
+
onClick: () => {
|
|
2675
|
+
if (!unreachable) chooseMember(family, next, effectiveEffort);
|
|
2676
|
+
},
|
|
2677
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2678
|
+
className: ComposerPicker_module_css_default.optionCopy,
|
|
2679
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2680
|
+
className: ComposerPicker_module_css_default.modelName,
|
|
2681
|
+
children: contextDisplay(row.label)
|
|
2682
|
+
})
|
|
2683
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2684
|
+
className: ComposerPicker_module_css_default.check,
|
|
2685
|
+
children: selected ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, { size: 16 }) : null
|
|
2686
|
+
})]
|
|
2687
|
+
}, row.tier ?? "standard");
|
|
2688
|
+
}),
|
|
2689
|
+
pane === "fast" && family !== void 0 && member !== void 0 && [false, true].map((fast) => {
|
|
2690
|
+
const next = pickVariant(family, member, { fast });
|
|
2691
|
+
const selected = member.fast === fast;
|
|
2692
|
+
const honored = next.fast === fast && next.contextTier === member.contextTier && next.thinking === member.thinking;
|
|
2693
|
+
const unreachable = !selected && (!honored || next.model.id === member.model.id);
|
|
2694
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2695
|
+
type: "button",
|
|
2696
|
+
role: "menuitemradio",
|
|
2697
|
+
"aria-checked": selected,
|
|
2698
|
+
className: classNames(ComposerPicker_module_css_default.option, selected && ComposerPicker_module_css_default.selected),
|
|
2699
|
+
disabled: locked || busy || !choiceAllowed(family.provider) || unreachable,
|
|
2700
|
+
title: unreachable ? t("choice.unavailable") : void 0,
|
|
2701
|
+
onClick: () => {
|
|
2702
|
+
if (!unreachable) chooseMember(family, next, effectiveEffort);
|
|
2703
|
+
},
|
|
2704
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2705
|
+
className: ComposerPicker_module_css_default.optionCopy,
|
|
2706
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2707
|
+
className: ComposerPicker_module_css_default.modelName,
|
|
2708
|
+
children: fast ? t("fast.on") : t("fast.off")
|
|
2709
|
+
})
|
|
2710
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2711
|
+
className: ComposerPicker_module_css_default.check,
|
|
2712
|
+
children: selected ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, { size: 16 }) : null
|
|
2713
|
+
})]
|
|
2714
|
+
}, fast ? "on" : "off");
|
|
2715
|
+
}),
|
|
2716
|
+
pane === "thinking" && family !== void 0 && member !== void 0 && thinkingPair !== null && [{
|
|
2717
|
+
on: true,
|
|
2718
|
+
row: thinkingPair.on
|
|
2719
|
+
}, {
|
|
2720
|
+
on: false,
|
|
2721
|
+
row: thinkingPair.off
|
|
2722
|
+
}].map((choice) => {
|
|
2723
|
+
const selected = member.thinking === choice.on;
|
|
2724
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2725
|
+
type: "button",
|
|
2726
|
+
role: "menuitemradio",
|
|
2727
|
+
"aria-checked": selected,
|
|
2728
|
+
className: classNames(ComposerPicker_module_css_default.option, selected && ComposerPicker_module_css_default.selected),
|
|
2729
|
+
disabled: locked || busy || !choiceAllowed(family.provider),
|
|
2730
|
+
onClick: () => {
|
|
2731
|
+
chooseMember(family, choice.row, effectiveEffort);
|
|
2732
|
+
},
|
|
2733
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2734
|
+
className: ComposerPicker_module_css_default.optionCopy,
|
|
2735
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2736
|
+
className: ComposerPicker_module_css_default.modelName,
|
|
2737
|
+
children: choice.on ? t("thinking.on") : t("thinking.off")
|
|
2738
|
+
})
|
|
2739
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2740
|
+
className: ComposerPicker_module_css_default.check,
|
|
2741
|
+
children: selected ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, { size: 16 }) : null
|
|
2742
|
+
})]
|
|
2743
|
+
}, choice.on ? "on" : "off");
|
|
2744
|
+
})
|
|
2745
|
+
]
|
|
2746
|
+
})]
|
|
2747
|
+
}) : null;
|
|
2748
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2749
|
+
className: classNames(ComposerPicker_module_css_default.root, tone === "capsule" ? ComposerPicker_module_css_default.capsule : embedded && ComposerPicker_module_css_default.embedded),
|
|
2750
|
+
onKeyDown: onRootKeyDown,
|
|
2751
|
+
children: [
|
|
2752
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2753
|
+
ref: triggerRef,
|
|
2754
|
+
type: "button",
|
|
2755
|
+
className: ComposerPicker_module_css_default.trigger,
|
|
2756
|
+
"aria-label": unavailableReason === void 0 ? triggerAria : `${triggerAria}: ${unavailableReason}`,
|
|
2757
|
+
"aria-haspopup": "menu",
|
|
2758
|
+
"aria-expanded": open,
|
|
2759
|
+
"aria-controls": open ? `${id}-menu` : void 0,
|
|
2760
|
+
title: unavailableReason ?? triggerLabel,
|
|
2761
|
+
disabled: locked,
|
|
2762
|
+
onPointerDown: onTriggerPointerDown,
|
|
2763
|
+
onClick: onTriggerClick,
|
|
2764
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2765
|
+
className: ComposerPicker_module_css_default.triggerLabel,
|
|
2766
|
+
children: [currentSelection !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RuntimeIcon, {
|
|
2767
|
+
provider: currentSelection.provider,
|
|
2768
|
+
...roleOf === void 0 ? {} : { roleOf }
|
|
2769
|
+
}) : null, triggerLabel]
|
|
2770
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {
|
|
2771
|
+
size: 14,
|
|
2772
|
+
className: classNames(ComposerPicker_module_css_default.chevron, open && ComposerPicker_module_css_default.chevronOpen)
|
|
2773
|
+
})]
|
|
2774
|
+
}),
|
|
2775
|
+
menu !== null && (tone === "capsule" ? menu : (0, react_dom.createPortal)(menu, document.body)),
|
|
2776
|
+
toast !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
|
|
2777
|
+
text: toast.text,
|
|
2778
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, { size: 16 }),
|
|
2779
|
+
anchor: triggerRef.current?.closest("[data-composer-card]") ?? null,
|
|
2780
|
+
onDone: () => {
|
|
2781
|
+
setToast(null);
|
|
2782
|
+
}
|
|
2783
|
+
}, toast.seq)
|
|
2784
|
+
]
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2787
|
+
//#endregion
|
|
2788
|
+
//#region node_modules/.pnpm/dsh-llm-providers-ui@https+_f1dadb1b56406564fc1bb8f2d953b82d/node_modules/dsh-llm-providers-ui/lib/order.js
|
|
2789
|
+
/** Display order for installed provider cards when the user has not saved one. */
|
|
2790
|
+
const PROVIDER_ITEM_ORDER = [
|
|
2791
|
+
"llm-cursor",
|
|
2792
|
+
"llm-grok",
|
|
2793
|
+
"llm-codex",
|
|
2794
|
+
"llm-ollama",
|
|
2795
|
+
"llm-commandcode",
|
|
2796
|
+
"llm-opencode-go"
|
|
2797
|
+
];
|
|
2798
|
+
const KNOWN_KEYS = new Set(PROVIDER_ITEM_ORDER);
|
|
2799
|
+
new Map(Object.entries({
|
|
2800
|
+
"llm-cursor": "cursor",
|
|
2801
|
+
"llm-grok": "grok",
|
|
2802
|
+
"llm-codex": "codex",
|
|
2803
|
+
"llm-ollama": "ollama-cloud",
|
|
2804
|
+
"llm-commandcode": "commandcode",
|
|
2805
|
+
"llm-opencode-go": "opencode-go"
|
|
2806
|
+
}).map(([key, route]) => [route, key]));
|
|
2807
|
+
/**
|
|
2808
|
+
* Merge a saved key list with the keys that are actually installed.
|
|
2809
|
+
* Saved keys that are not installed are dropped; installed keys missing from
|
|
2810
|
+
* the save append in PROVIDER_ITEM_ORDER, then leftover unknown keys.
|
|
2811
|
+
* Nothing registered yields an empty list (the settings empty state).
|
|
2812
|
+
*/
|
|
2813
|
+
function applySavedOrder(registered, saved = []) {
|
|
2814
|
+
const have = [...new Set(registered.filter((key) => key.length > 0))];
|
|
2815
|
+
if (have.length === 0) return [];
|
|
2816
|
+
const installed = new Set(have);
|
|
2817
|
+
const preferredSaved = [...new Set(saved)].filter((key) => installed.has(key));
|
|
2818
|
+
const preferred = new Set(preferredSaved);
|
|
2819
|
+
const rest = have.filter((key) => !preferred.has(key));
|
|
2820
|
+
const known = PROVIDER_ITEM_ORDER.filter((key) => rest.includes(key));
|
|
2821
|
+
const extra = rest.filter((key) => !KNOWN_KEYS.has(key));
|
|
2822
|
+
return [
|
|
2823
|
+
...preferredSaved,
|
|
2824
|
+
...known,
|
|
2825
|
+
...extra
|
|
2826
|
+
];
|
|
2827
|
+
}
|
|
2828
|
+
function ownCatalogKey$1(catalogKeys, id) {
|
|
2829
|
+
return Object.hasOwn(catalogKeys, id) ? catalogKeys[id] : void 0;
|
|
2830
|
+
}
|
|
2831
|
+
/**
|
|
2832
|
+
* Sort picker/catalog groups from declared directory routes.
|
|
2833
|
+
* With a saved card order, mapped providers follow that list; with none, groups keep catalog order.
|
|
2834
|
+
* Groups the live map does not know keep catalog order and append after ranked routes.
|
|
2835
|
+
* @param catalogKeys - live catalog-group-id → card-key map from ProviderDirectory.catalogRoutes().
|
|
2836
|
+
*/
|
|
2837
|
+
function sortCatalogGroups(groups, saved = [], catalogKeys = {}) {
|
|
2838
|
+
if (saved.length === 0) return [...groups];
|
|
2839
|
+
const idByKey = /* @__PURE__ */ new Map();
|
|
2840
|
+
for (const [id, key] of Object.entries(catalogKeys)) idByKey.set(key, id);
|
|
2841
|
+
const ranked = applySavedOrder(groups.map((group) => ownCatalogKey$1(catalogKeys, group.id)).filter((key) => key !== void 0), saved);
|
|
2842
|
+
const rank = new Map(ranked.flatMap((key, index) => {
|
|
2843
|
+
const route = idByKey.get(key);
|
|
2844
|
+
return route === void 0 ? [] : [[route, index]];
|
|
2845
|
+
}));
|
|
2846
|
+
const known = [];
|
|
2847
|
+
const unknown = [];
|
|
2848
|
+
for (const group of groups) if (rank.has(group.id)) known.push(group);
|
|
2849
|
+
else unknown.push(group);
|
|
2850
|
+
known.sort((left, right) => (rank.get(left.id) ?? 0) - (rank.get(right.id) ?? 0));
|
|
2851
|
+
return [...known, ...unknown];
|
|
2852
|
+
}
|
|
2853
|
+
//#endregion
|
|
2854
|
+
//#region src/client/provider-directory.ts
|
|
2855
|
+
/** Optional ProviderDirectory operations consumed by Model Switch. */
|
|
2856
|
+
/** Read the optional catalog route map without assuming a particular Providers UI release. */
|
|
2857
|
+
function readCatalogRoutes(directory) {
|
|
2858
|
+
const catalogRoutes = directory?.catalogRoutes;
|
|
2859
|
+
return typeof catalogRoutes === "function" ? catalogRoutes.call(directory) : {};
|
|
2860
|
+
}
|
|
2861
|
+
/** Read the optional native-binding declarations without assuming a particular Providers UI release. */
|
|
2862
|
+
function readNativeBindings(directory) {
|
|
2863
|
+
const nativeBindings = directory?.nativeBindings;
|
|
2864
|
+
return typeof nativeBindings === "function" ? nativeBindings.call(directory) : [];
|
|
2865
|
+
}
|
|
2866
|
+
function ownCatalogKey(catalogKeys, id) {
|
|
2867
|
+
return Object.hasOwn(catalogKeys, id) ? catalogKeys[id] : void 0;
|
|
2868
|
+
}
|
|
2869
|
+
/**
|
|
2870
|
+
* Rank picker/catalog groups from live ProviderDirectory routes.
|
|
2871
|
+
* Published 0.2.8 `sortCatalogGroups` only understands hardcoded LLM routes and
|
|
2872
|
+
* ignores a third argument, so Agent catalog ids would keep source order.
|
|
2873
|
+
* When the Owner publishes `catalogRoutes`, this ranks those keys locally.
|
|
2874
|
+
* With no live map, fall back to the compile-pinned 0.2.8 LLM-only sort.
|
|
2875
|
+
*/
|
|
2876
|
+
function sortCatalogGroupsWithRoutes(groups, saved = [], catalogKeys = {}) {
|
|
2877
|
+
const live = Object.entries(catalogKeys).filter(([id, key]) => Object.hasOwn(catalogKeys, id) && key.length > 0);
|
|
2878
|
+
if (live.length === 0) return sortCatalogGroups(groups, saved);
|
|
2879
|
+
if (saved.length === 0) return [...groups];
|
|
2880
|
+
const idByKey = /* @__PURE__ */ new Map();
|
|
2881
|
+
for (const [id, key] of live) idByKey.set(key, id);
|
|
2882
|
+
const ranked = applySavedOrder(groups.map((group) => ownCatalogKey(catalogKeys, group.id)).filter((key) => key !== void 0), saved);
|
|
2883
|
+
const rank = new Map(ranked.flatMap((key, index) => {
|
|
2884
|
+
const route = idByKey.get(key);
|
|
2885
|
+
return route === void 0 ? [] : [[route, index]];
|
|
2886
|
+
}));
|
|
2887
|
+
const known = [];
|
|
2888
|
+
const unknown = [];
|
|
2889
|
+
for (const group of groups) if (rank.has(group.id)) known.push(group);
|
|
2890
|
+
else unknown.push(group);
|
|
2891
|
+
known.sort((left, right) => (rank.get(left.id) ?? 0) - (rank.get(right.id) ?? 0));
|
|
2892
|
+
return [...known, ...unknown];
|
|
2893
|
+
}
|
|
2894
|
+
//#endregion
|
|
2895
|
+
//#region src/client/picker/PickerDirectory.ts
|
|
2896
|
+
function pickerDirectoryView(snapshot, operations) {
|
|
2897
|
+
return {
|
|
2898
|
+
snapshot,
|
|
2899
|
+
getDirectorySnapshot: operations.getDirectorySnapshot,
|
|
2900
|
+
load: operations.load,
|
|
2901
|
+
select: operations.select
|
|
2902
|
+
};
|
|
2903
|
+
}
|
|
2904
|
+
/** Directory view whose groups follow the shared LLM Providers card order. */
|
|
2905
|
+
function pickerDirectoryViewOrdered(snapshot, operations, order, catalogKeys = {}) {
|
|
2906
|
+
return pickerDirectoryView({
|
|
2907
|
+
...snapshot,
|
|
2908
|
+
groups: sortCatalogGroupsWithRoutes(snapshot.groups, order, catalogKeys)
|
|
2909
|
+
}, operations);
|
|
2910
|
+
}
|
|
2911
|
+
//#endregion
|
|
2912
|
+
//#region src/client/picker/main-default-restore.ts
|
|
2913
|
+
/** Bound on how long the unawaited Host default write is awaited. */
|
|
2914
|
+
const DEFAULT_WRITE_WAIT_MS = 750;
|
|
2915
|
+
function defaultWait(ms) {
|
|
2916
|
+
return new Promise((resolve) => {
|
|
2917
|
+
setTimeout(resolve, ms);
|
|
2918
|
+
});
|
|
2919
|
+
}
|
|
2920
|
+
/** A fenced snapshot: ready, Host-synced, writable, with a concrete revision. */
|
|
2921
|
+
function fenced(snapshot) {
|
|
2922
|
+
if (snapshot.status !== "ready" || snapshot.mode !== "host" || !snapshot.writable) return void 0;
|
|
2923
|
+
if (snapshot.value === void 0 || snapshot.revision === void 0) return void 0;
|
|
2924
|
+
return {
|
|
2925
|
+
revision: snapshot.revision,
|
|
2926
|
+
value: snapshot.value
|
|
2927
|
+
};
|
|
2928
|
+
}
|
|
2929
|
+
/** Field writes restoring one complete Main selection; the Host replaces the section. */
|
|
2930
|
+
function mainDefaultOps(selection) {
|
|
2931
|
+
return [
|
|
2932
|
+
{
|
|
2933
|
+
op: "set",
|
|
2934
|
+
path: ["provider"],
|
|
2935
|
+
value: selection.provider
|
|
2936
|
+
},
|
|
2937
|
+
{
|
|
2938
|
+
op: "set",
|
|
2939
|
+
path: ["model"],
|
|
2940
|
+
value: selection.model
|
|
2941
|
+
},
|
|
2942
|
+
selection.reasoningEffort === void 0 || selection.reasoningEffort === "" ? {
|
|
2943
|
+
op: "unset",
|
|
2944
|
+
path: ["reasoningEffort"]
|
|
2945
|
+
} : {
|
|
2946
|
+
op: "set",
|
|
2947
|
+
path: ["reasoningEffort"],
|
|
2948
|
+
value: selection.reasoningEffort
|
|
2949
|
+
}
|
|
2950
|
+
];
|
|
2951
|
+
}
|
|
2952
|
+
/** An effort never leaks across a comparison: absent and cleared are the same selection. */
|
|
2953
|
+
function sameMainSelection(left, right) {
|
|
2954
|
+
if (left === void 0) return false;
|
|
2955
|
+
const leftEffort = left.reasoningEffort === void 0 || left.reasoningEffort === "" ? void 0 : left.reasoningEffort;
|
|
2956
|
+
const rightEffort = right.reasoningEffort === void 0 || right.reasoningEffort === "" ? void 0 : right.reasoningEffort;
|
|
2957
|
+
return left.provider === right.provider && left.model === right.model && leftEffort === rightEffort;
|
|
2958
|
+
}
|
|
2959
|
+
/** Resolve as soon as the revision leaves `revision`, or after the bounded wait. */
|
|
2960
|
+
function waitForAdvance(form, revision, wait, timeoutMs) {
|
|
2961
|
+
const current = form.getSnapshot();
|
|
2962
|
+
if (current.revision !== revision) return Promise.resolve(current);
|
|
2963
|
+
return new Promise((resolve) => {
|
|
2964
|
+
let settled = false;
|
|
2965
|
+
const finish = (snapshot) => {
|
|
2966
|
+
if (settled) return;
|
|
2967
|
+
settled = true;
|
|
2968
|
+
unsubscribe();
|
|
2969
|
+
resolve(snapshot);
|
|
2970
|
+
};
|
|
2971
|
+
const unsubscribe = form.subscribe(() => {
|
|
2972
|
+
const next = form.getSnapshot();
|
|
2973
|
+
if (next.revision !== revision) finish(next);
|
|
2974
|
+
});
|
|
2975
|
+
wait(timeoutMs).then(() => {
|
|
2976
|
+
finish(form.getSnapshot());
|
|
2977
|
+
});
|
|
2978
|
+
});
|
|
2979
|
+
}
|
|
2980
|
+
/**
|
|
2981
|
+
* Restore the Main default captured before a Session-only model switch.
|
|
2982
|
+
*
|
|
2983
|
+
* Never throws and never blocks a Plan answer: a failed compensation is
|
|
2984
|
+
* reported, not enforced, because the Session selection the human asked for is
|
|
2985
|
+
* already installed by the time this runs.
|
|
2986
|
+
*
|
|
2987
|
+
* @param form - the official Main Settings namespace form.
|
|
2988
|
+
* @param before - the snapshot captured before the switch.
|
|
2989
|
+
* @param options - wait/timeout/attempt overrides for tests.
|
|
2990
|
+
* @returns Whether the captured default stands again, and whether a newer edit won.
|
|
2991
|
+
*/
|
|
2992
|
+
async function restoreMainDefault(form, before, options = {}) {
|
|
2993
|
+
const captured = fenced(before);
|
|
2994
|
+
if (captured === void 0) return {
|
|
2995
|
+
restored: true,
|
|
2996
|
+
conflict: false
|
|
2997
|
+
};
|
|
2998
|
+
const wait = options.wait ?? defaultWait;
|
|
2999
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_WRITE_WAIT_MS;
|
|
3000
|
+
const attempts = Math.max(1, options.attempts ?? 2);
|
|
3001
|
+
await waitForAdvance(form, captured.revision, wait, timeoutMs);
|
|
3002
|
+
const observed = fenced(form.getSnapshot());
|
|
3003
|
+
if (observed === void 0) return {
|
|
3004
|
+
restored: true,
|
|
3005
|
+
conflict: false
|
|
3006
|
+
};
|
|
3007
|
+
if (observed.revision === captured.revision) return {
|
|
3008
|
+
restored: true,
|
|
3009
|
+
conflict: false
|
|
3010
|
+
};
|
|
3011
|
+
if (sameMainSelection(observed.value, captured.value)) return {
|
|
3012
|
+
restored: true,
|
|
3013
|
+
conflict: false
|
|
3014
|
+
};
|
|
3015
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
3016
|
+
const snapshot = fenced(form.getSnapshot());
|
|
3017
|
+
if (snapshot === void 0) return {
|
|
3018
|
+
restored: false,
|
|
3019
|
+
conflict: false
|
|
3020
|
+
};
|
|
3021
|
+
if (sameMainSelection(snapshot.value, captured.value)) return {
|
|
3022
|
+
restored: true,
|
|
3023
|
+
conflict: false
|
|
3024
|
+
};
|
|
3025
|
+
if (await form.mutate(mainDefaultOps(captured.value), snapshot.revision)) return {
|
|
3026
|
+
restored: true,
|
|
3027
|
+
conflict: false
|
|
3028
|
+
};
|
|
3029
|
+
if (form.getSnapshot().revision === snapshot.revision) return {
|
|
3030
|
+
restored: false,
|
|
3031
|
+
conflict: false
|
|
3032
|
+
};
|
|
3033
|
+
}
|
|
3034
|
+
return {
|
|
3035
|
+
restored: false,
|
|
3036
|
+
conflict: true
|
|
3037
|
+
};
|
|
3038
|
+
}
|
|
3039
|
+
//#endregion
|
|
3040
|
+
//#region src/picker/pending-dismissal.ts
|
|
3041
|
+
/** Raised when the running Host exposes neither withdrawal verb. */
|
|
3042
|
+
var PlanDismissalUnsupportedError = class extends Error {
|
|
3043
|
+
constructor(message) {
|
|
3044
|
+
super(message);
|
|
3045
|
+
this.name = "PlanDismissalUnsupportedError";
|
|
3046
|
+
}
|
|
3047
|
+
};
|
|
3048
|
+
/**
|
|
3049
|
+
* Withdraw a pending card so the human can talk instead, on either generation.
|
|
3050
|
+
* @param wait - the matched pending question carrier.
|
|
3051
|
+
* @returns Which verb withdrew the card.
|
|
3052
|
+
* @throws {PlanDismissalUnsupportedError} when the carrier exposes neither verb.
|
|
3053
|
+
*/
|
|
3054
|
+
async function dismissPendingQuestion(wait) {
|
|
3055
|
+
if (typeof wait.dismiss === "function") {
|
|
3056
|
+
await wait.dismiss();
|
|
3057
|
+
return "dismiss";
|
|
3058
|
+
}
|
|
3059
|
+
if (typeof wait.cancel === "function") {
|
|
3060
|
+
await wait.cancel();
|
|
3061
|
+
return "cancel";
|
|
3062
|
+
}
|
|
3063
|
+
throw new PlanDismissalUnsupportedError("this DSH Host exposes neither PendingQuestion.dismiss() (0.2.x) nor PendingQuestion.cancel() (0.1.x)");
|
|
3064
|
+
}
|
|
3065
|
+
//#endregion
|
|
3066
|
+
//#region src/client/picker/RetryBoundary.tsx
|
|
3067
|
+
/** Own retryable React error-boundary state while callers own presentation and copy. */
|
|
3068
|
+
var RetryBoundary = class extends react.Component {
|
|
3069
|
+
state = { message: null };
|
|
3070
|
+
static getDerivedStateFromError(error) {
|
|
3071
|
+
return { message: error instanceof Error ? error.message : String(error) };
|
|
3072
|
+
}
|
|
3073
|
+
componentDidCatch(error, info) {
|
|
3074
|
+
console.error(this.props.logLabel, error, info);
|
|
3075
|
+
}
|
|
3076
|
+
render() {
|
|
3077
|
+
if (this.state.message === null) return this.props.children;
|
|
3078
|
+
return this.props.renderFallback(this.state.message, () => {
|
|
3079
|
+
this.setState({ message: null });
|
|
3080
|
+
});
|
|
3081
|
+
}
|
|
3082
|
+
};
|
|
3083
|
+
//#endregion
|
|
3084
|
+
//#region \0dsh-css:src/client/picker/PlanReviewCard.module.css.mjs
|
|
3085
|
+
const css = ".BylfoW_frame{padding:6px calc(var(--dsh-composer-side-clearance) + 16px) 10px;justify-content:center;display:flex}.BylfoW_card{width:100%;max-width:var(--dsh-chat-content-width);border:1px solid var(--dsw-alias-state-warn-secondary);background:var(--dsw-specific-input-major);max-height:min(60vh,520px);box-shadow:var(--dsw-shadow-lv2);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-radius:20px;flex-direction:column;display:flex;overflow:visible}.BylfoW_card,.BylfoW_card *{box-sizing:border-box}.BylfoW_strip{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-primary);border-radius:20px 20px 0 0;flex-shrink:0;align-items:center;gap:8px;padding:10px 16px;font-size:13px;line-height:18px;display:flex}.BylfoW_dot{background:var(--dsw-alias-state-warn-primary);border-radius:50%;width:8px;height:8px}.BylfoW_stripTitle{flex:none;font-weight:600}.BylfoW_headerPicker{flex:0 auto;align-items:center;min-width:0;max-width:55%;margin-left:auto;display:flex}.BylfoW_headerPicker>*{min-width:0;max-width:100%}.BylfoW_body{overscroll-behavior:contain;flex:auto;min-height:0;padding:12px 16px 4px;font-size:14px;line-height:22px;overflow-y:auto}.BylfoW_footer{z-index:2;flex-direction:column;flex-shrink:0;gap:6px;padding:8px 16px 12px;display:flex;position:relative;overflow:visible}.BylfoW_feedback{min-height:0;color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:16px}.BylfoW_feedback:empty{display:none}.BylfoW_feedbackRow{align-items:center;gap:8px;min-width:0;display:flex}.BylfoW_feedbackInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-specific-input-minor);min-width:0;height:32px;color:var(--dsw-alias-label-primary);font:var(--dsw-font-xs-13);border-radius:8px;flex:auto;padding:0 10px}.BylfoW_feedbackInput::placeholder{color:var(--dsw-alias-label-tertiary)}.BylfoW_feedbackInput:disabled{opacity:.6}.BylfoW_bar{flex-wrap:nowrap;justify-content:space-between;align-items:center;gap:12px;min-width:0;min-height:32px;display:flex}.BylfoW_picker{flex:16rem;min-width:0;max-width:16rem}.BylfoW_pickerError{color:var(--dsw-alias-state-error-primary);overflow-wrap:anywhere;flex-wrap:wrap;align-items:center;gap:6px;font-size:12px;display:flex}.BylfoW_actions{flex-wrap:nowrap;flex:none;justify-content:flex-end;align-items:center;gap:8px;margin-left:auto;display:flex}.BylfoW_discuss,.BylfoW_keep,.BylfoW_approve,.BylfoW_recover{white-space:nowrap;height:32px;min-height:32px;padding:0 10px}.BylfoW_discuss,.BylfoW_recover{color:var(--dsw-alias-label-secondary)}.BylfoW_discuss:hover:not(:disabled),.BylfoW_recover:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.BylfoW_approve{padding:0 12px}@media (width<=720px){.BylfoW_frame{padding-right:max(10px, env(safe-area-inset-right));padding-bottom:max(10px, env(safe-area-inset-bottom));padding-left:max(10px, env(safe-area-inset-left))}.BylfoW_card{border-radius:16px}.BylfoW_strip,.BylfoW_body,.BylfoW_footer{padding-left:12px;padding-right:12px}.BylfoW_strip{flex-wrap:wrap;gap:6px}.BylfoW_headerPicker{flex:100%;max-width:100%;margin-left:0}.BylfoW_bar{justify-content:flex-end;gap:6px}.BylfoW_actions{flex:100%;justify-content:flex-end;gap:6px;min-width:0}.BylfoW_discuss,.BylfoW_keep,.BylfoW_approve,.BylfoW_recover{overflow-wrap:anywhere;white-space:normal;flex:1 1 0;min-width:0;height:auto;padding:4px;line-height:14px}.BylfoW_feedbackInput{height:auto;min-height:32px}}";
|
|
3086
|
+
const tagId = "dsh-model-switch/PlanReviewCard.module.css";
|
|
3087
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
3088
|
+
const tag = document.createElement("style");
|
|
3089
|
+
tag.dataset.plugin = "dsh-model-switch";
|
|
3090
|
+
tag.dataset.pluginCss = tagId;
|
|
3091
|
+
tag.textContent = css;
|
|
3092
|
+
document.head.appendChild(tag);
|
|
3093
|
+
}
|
|
3094
|
+
var PlanReviewCard_module_css_default = {
|
|
3095
|
+
"actions": "BylfoW_actions",
|
|
3096
|
+
"approve": "BylfoW_approve",
|
|
3097
|
+
"bar": "BylfoW_bar",
|
|
3098
|
+
"body": "BylfoW_body",
|
|
3099
|
+
"card": "BylfoW_card",
|
|
3100
|
+
"discuss": "BylfoW_discuss",
|
|
3101
|
+
"dot": "BylfoW_dot",
|
|
3102
|
+
"feedback": "BylfoW_feedback",
|
|
3103
|
+
"feedbackInput": "BylfoW_feedbackInput",
|
|
3104
|
+
"feedbackRow": "BylfoW_feedbackRow",
|
|
3105
|
+
"footer": "BylfoW_footer",
|
|
3106
|
+
"frame": "BylfoW_frame",
|
|
3107
|
+
"headerPicker": "BylfoW_headerPicker",
|
|
3108
|
+
"keep": "BylfoW_keep",
|
|
3109
|
+
"picker": "BylfoW_picker",
|
|
3110
|
+
"pickerError": "BylfoW_pickerError",
|
|
3111
|
+
"recover": "BylfoW_recover",
|
|
3112
|
+
"strip": "BylfoW_strip",
|
|
3113
|
+
"stripTitle": "BylfoW_stripTitle"
|
|
3114
|
+
};
|
|
3115
|
+
//#endregion
|
|
3116
|
+
//#region src/client/picker/PlanReviewCard.tsx
|
|
3117
|
+
function PickerGuard({ children, errorLabel, retryLabel }) {
|
|
3118
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RetryBoundary, {
|
|
3119
|
+
logLabel: "dsh-model-switch: Plan Review picker crashed",
|
|
3120
|
+
renderFallback: (message, retry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3121
|
+
"data-dsh-ms-plan-picker-error": true,
|
|
3122
|
+
role: "alert",
|
|
3123
|
+
className: PlanReviewCard_module_css_default.pickerError,
|
|
3124
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: errorLabel(message) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3125
|
+
type: "button",
|
|
3126
|
+
variant: "outline",
|
|
3127
|
+
onClick: retry,
|
|
3128
|
+
children: retryLabel
|
|
3129
|
+
})]
|
|
3130
|
+
}),
|
|
3131
|
+
children
|
|
3132
|
+
});
|
|
3133
|
+
}
|
|
3134
|
+
/**
|
|
3135
|
+
* Answer one review question.
|
|
3136
|
+
*
|
|
3137
|
+
* A typed feedback answer follows the official single-select convention: the
|
|
3138
|
+
* custom text replaces the option list, because plan mode reads `custom` as
|
|
3139
|
+
* "keep planning, and here is why" and falls back to the bare option when it
|
|
3140
|
+
* is absent.
|
|
3141
|
+
*/
|
|
3142
|
+
async function respondAnswer(wait, id, label, rejectedMessage, terminalRejection = false, feedback) {
|
|
3143
|
+
const custom = feedback === void 0 || feedback === "" ? void 0 : feedback;
|
|
3144
|
+
const answer = custom === void 0 ? { answers: [{
|
|
3145
|
+
id,
|
|
3146
|
+
selected: [label]
|
|
3147
|
+
}] } : { answers: [{
|
|
3148
|
+
id,
|
|
3149
|
+
selected: [],
|
|
3150
|
+
custom
|
|
3151
|
+
}] };
|
|
3152
|
+
try {
|
|
3153
|
+
await wait.answer(answer);
|
|
3154
|
+
} catch (cause) {
|
|
3155
|
+
throw terminalRejection ? new PlanApprovalResponseError(rejectedMessage, { cause }) : new Error(rejectedMessage, { cause });
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
/**
|
|
3159
|
+
* Withdraw the takeover so the human can speak instead, on either Host
|
|
3160
|
+
* generation; a Host exposing no withdrawal verb is named instead of guessed.
|
|
3161
|
+
*/
|
|
3162
|
+
async function respondDismiss(wait, rejectedMessage, unsupportedMessage) {
|
|
3163
|
+
try {
|
|
3164
|
+
await dismissPendingQuestion(wait);
|
|
3165
|
+
} catch (cause) {
|
|
3166
|
+
throw cause instanceof PlanDismissalUnsupportedError ? new Error(unsupportedMessage, { cause }) : new Error(rejectedMessage, { cause });
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
/** Match the model seat's admission guard before letting either picker offer a change. */
|
|
3170
|
+
function mainDefaultsUnavailableReason(snapshot, t) {
|
|
3171
|
+
if (snapshot.mode === "memory") return t("settings.remoteUnavailable");
|
|
3172
|
+
if (snapshot.status === "loading") return t("settings.loading");
|
|
3173
|
+
if (snapshot.status !== "ready" || snapshot.mode !== "host" || !snapshot.writable || snapshot.value === void 0 || snapshot.revision === void 0) return t("settings.unavailable");
|
|
3174
|
+
}
|
|
3175
|
+
/** Inline failed lock-read status; history and log reading stay unaffected. */
|
|
3176
|
+
function ProviderLockHint(props) {
|
|
3177
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3178
|
+
role: "alert",
|
|
3179
|
+
className: PlanReviewCard_module_css_default.strip,
|
|
3180
|
+
"data-provider-lock-failed": true,
|
|
3181
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: PlanReviewCard_module_css_default.dot }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3182
|
+
className: PlanReviewCard_module_css_default.stripTitle,
|
|
3183
|
+
children: props.t("lock.readFailed")
|
|
3184
|
+
})]
|
|
3185
|
+
});
|
|
3186
|
+
}
|
|
3187
|
+
function PlanReviewCard(props) {
|
|
3188
|
+
const snapshot = props.useDirectory((value) => value);
|
|
3189
|
+
const order = props.useProviderOrder((value) => value);
|
|
3190
|
+
const settingsUnavailableReason = mainDefaultsUnavailableReason((0, react.useSyncExternalStore)(props.subscribeMainDefaults, props.getMainDefaultsSnapshot), props.t);
|
|
3191
|
+
const lock = (0, react.useSyncExternalStore)(props.providerLockStore.subscribe, props.providerLockStore.getSnapshot);
|
|
3192
|
+
const phase = props.useInput((input) => input.phase);
|
|
3193
|
+
const blank = props.useSession((session) => session.blank);
|
|
3194
|
+
const active = props.useSession((session) => session.running || session.awaitingFirstTurn) || phase === "submitting";
|
|
3195
|
+
(0, react.useEffect)(() => {
|
|
3196
|
+
props.refreshProviderLock();
|
|
3197
|
+
}, [
|
|
3198
|
+
props.refreshProviderLock,
|
|
3199
|
+
phase,
|
|
3200
|
+
active,
|
|
3201
|
+
snapshot,
|
|
3202
|
+
order
|
|
3203
|
+
]);
|
|
3204
|
+
const providerLock = effectiveProviderLock(lock, snapshot.current?.provider, active, isAgentRole(props.roleOf?.(snapshot.current?.provider ?? "")));
|
|
3205
|
+
const agentLocked = agentProviderLocked(blank, providerLock, active);
|
|
3206
|
+
const review = planReviewOf(props.matched.questions);
|
|
3207
|
+
if (review === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3208
|
+
className: PlanReviewCard_module_css_default.frame,
|
|
3209
|
+
"data-plan-review-key": props.matched.key,
|
|
3210
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
3211
|
+
className: PlanReviewCard_module_css_default.card,
|
|
3212
|
+
"aria-label": props.t("plan.header"),
|
|
3213
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3214
|
+
className: PlanReviewCard_module_css_default.strip,
|
|
3215
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: PlanReviewCard_module_css_default.dot }), props.t("plan.header")]
|
|
3216
|
+
}), lock.failed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderLockHint, { t: props.t })]
|
|
3217
|
+
})
|
|
3218
|
+
});
|
|
3219
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlanReviewState, {
|
|
3220
|
+
matched: props.matched,
|
|
3221
|
+
review,
|
|
3222
|
+
available: props.available && (!lock.failed || snapshot.current !== null),
|
|
3223
|
+
providerLock,
|
|
3224
|
+
agentLocked,
|
|
3225
|
+
lockFailed: lock.failed,
|
|
3226
|
+
...settingsUnavailableReason === void 0 ? {} : { settingsUnavailableReason },
|
|
3227
|
+
directory: pickerDirectoryViewOrdered(snapshot, props, order, props.catalogRoutes?.() ?? {}),
|
|
3228
|
+
t: props.t,
|
|
3229
|
+
...props.resolveInteractionOperations === void 0 ? {} : { resolveInteractionOperations: props.resolveInteractionOperations },
|
|
3230
|
+
...props.roleOf === void 0 ? {} : { roleOf: props.roleOf }
|
|
3231
|
+
}, props.matched.key);
|
|
3232
|
+
}
|
|
3233
|
+
function PlanReviewState({ matched, review, available, providerLock, agentLocked, lockFailed, settingsUnavailableReason, directory, t, resolveInteractionOperations, roleOf }) {
|
|
3234
|
+
const { snapshot, getDirectorySnapshot, load, select } = directory;
|
|
3235
|
+
const [execution, setExecution] = (0, react.useState)(snapshot.current ?? void 0);
|
|
3236
|
+
const [feedback, setFeedback] = (0, react.useState)("");
|
|
3237
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
3238
|
+
const [blocked, setBlocked] = (0, react.useState)(false);
|
|
3239
|
+
const [error, setError] = (0, react.useState)(null);
|
|
3240
|
+
const [notice, setNotice] = (0, react.useState)(null);
|
|
3241
|
+
const operationLocked = (0, react.useRef)(false);
|
|
3242
|
+
(0, react.useEffect)(() => {
|
|
3243
|
+
load();
|
|
3244
|
+
}, [load]);
|
|
3245
|
+
(0, react.useEffect)(() => {
|
|
3246
|
+
if (execution === void 0 && snapshot.current !== null) setExecution(snapshot.current);
|
|
3247
|
+
}, [execution, snapshot.current]);
|
|
3248
|
+
/**
|
|
3249
|
+
* Run one takeover action. `force` reaches past a terminal answer state: the
|
|
3250
|
+
* withdrawal action must stay reachable, or a rejected answer would leave the
|
|
3251
|
+
* card with no way back to the composer.
|
|
3252
|
+
*/
|
|
3253
|
+
const settle = (send, force = false) => {
|
|
3254
|
+
if (busy) return;
|
|
3255
|
+
if (!force && (operationLocked.current || blocked)) return;
|
|
3256
|
+
operationLocked.current = true;
|
|
3257
|
+
let terminal = false;
|
|
3258
|
+
settlePlanAction(send, (state) => {
|
|
3259
|
+
terminal = state.blocked;
|
|
3260
|
+
setBusy(state.busy);
|
|
3261
|
+
setBlocked(state.blocked);
|
|
3262
|
+
setError(state.error);
|
|
3263
|
+
}).then((completed) => {
|
|
3264
|
+
if (!completed && !terminal) operationLocked.current = false;
|
|
3265
|
+
});
|
|
3266
|
+
};
|
|
3267
|
+
/** Re-read the Host and re-enable every action after a failed settlement. */
|
|
3268
|
+
const recover = () => {
|
|
3269
|
+
operationLocked.current = false;
|
|
3270
|
+
setBusy(false);
|
|
3271
|
+
setBlocked(false);
|
|
3272
|
+
setError(null);
|
|
3273
|
+
setNotice(null);
|
|
3274
|
+
load();
|
|
3275
|
+
};
|
|
3276
|
+
const settingsBlock = settingsUnavailableReason !== void 0 && execution !== void 0 && (snapshot.current === null || execution.provider !== snapshot.current.provider || execution.model !== snapshot.current.model || execution.reasoningEffort !== snapshot.current.reasoningEffort);
|
|
3277
|
+
const executionAllowed = execution !== void 0 && !settingsBlock && runtimeChoiceAllowed(providerLock, agentLocked, execution.provider, snapshot.current?.provider, isAgentRole(roleOf?.(execution.provider)));
|
|
3278
|
+
const action = planActionView({
|
|
3279
|
+
busy,
|
|
3280
|
+
blocked,
|
|
3281
|
+
error
|
|
3282
|
+
}, available, executionAllowed);
|
|
3283
|
+
const onApprove = () => {
|
|
3284
|
+
if (execution === void 0 || !executionAllowed || !available || busy || blocked) return;
|
|
3285
|
+
settle(async () => {
|
|
3286
|
+
if (!await approvePlanReview({
|
|
3287
|
+
select,
|
|
3288
|
+
selection: execution,
|
|
3289
|
+
current: snapshot.current,
|
|
3290
|
+
answer: () => respondAnswer(matched, review.id, review.approve.label, t("plan.responseRejected"), true),
|
|
3291
|
+
onNotRestored: (model) => {
|
|
3292
|
+
setNotice(t("plan.notRestored", { model }));
|
|
3293
|
+
console.warn("dsh-model-switch: the Session model changed but the Main default was not restored", model);
|
|
3294
|
+
}
|
|
3295
|
+
})) {
|
|
3296
|
+
const message = getDirectorySnapshot().error;
|
|
3297
|
+
throw new Error(message === null ? t("plan.modelFailed") : t("error.action", { message }));
|
|
3298
|
+
}
|
|
3299
|
+
});
|
|
3300
|
+
};
|
|
3301
|
+
const decline = review.decline;
|
|
3302
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3303
|
+
className: PlanReviewCard_module_css_default.frame,
|
|
3304
|
+
"data-plan-review-key": matched.key,
|
|
3305
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
3306
|
+
className: PlanReviewCard_module_css_default.card,
|
|
3307
|
+
"aria-label": review.question,
|
|
3308
|
+
children: [
|
|
3309
|
+
lockFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderLockHint, { t }),
|
|
3310
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3311
|
+
className: PlanReviewCard_module_css_default.strip,
|
|
3312
|
+
children: [
|
|
3313
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: PlanReviewCard_module_css_default.dot }),
|
|
3314
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3315
|
+
className: PlanReviewCard_module_css_default.stripTitle,
|
|
3316
|
+
children: t("plan.header")
|
|
3317
|
+
}),
|
|
3318
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3319
|
+
className: PlanReviewCard_module_css_default.headerPicker,
|
|
3320
|
+
"aria-label": t("plan.execution"),
|
|
3321
|
+
onPointerDown: (event) => {
|
|
3322
|
+
event.stopPropagation();
|
|
3323
|
+
},
|
|
3324
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PickerGuard, {
|
|
3325
|
+
errorLabel: (message) => t("plan.pickerCrash", { message }),
|
|
3326
|
+
retryLabel: t("retry"),
|
|
3327
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComposerPicker, {
|
|
3328
|
+
locked: busy || blocked || settingsUnavailableReason !== void 0,
|
|
3329
|
+
providerLock,
|
|
3330
|
+
agentLocked,
|
|
3331
|
+
...roleOf === void 0 ? {} : { roleOf },
|
|
3332
|
+
available,
|
|
3333
|
+
...settingsUnavailableReason === void 0 ? {} : { unavailableReason: settingsUnavailableReason },
|
|
3334
|
+
directory,
|
|
3335
|
+
t,
|
|
3336
|
+
...resolveInteractionOperations === void 0 ? {} : { resolveInteractionOperations },
|
|
3337
|
+
...execution === void 0 ? {} : { draft: execution },
|
|
3338
|
+
onDraftChange: setExecution,
|
|
3339
|
+
embedded: true,
|
|
3340
|
+
tone: "capsule"
|
|
3341
|
+
})
|
|
3342
|
+
})
|
|
3343
|
+
})
|
|
3344
|
+
]
|
|
3345
|
+
}),
|
|
3346
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3347
|
+
className: PlanReviewCard_module_css_default.body,
|
|
3348
|
+
"data-plan-review-scroll": true,
|
|
3349
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
|
|
3350
|
+
text: review.plan,
|
|
3351
|
+
labels: {
|
|
3352
|
+
code: {
|
|
3353
|
+
copyLabel: t("markdown.copy"),
|
|
3354
|
+
copiedLabel: t("markdown.copied")
|
|
3355
|
+
},
|
|
3356
|
+
footnotes: t("markdown.footnotes")
|
|
3357
|
+
}
|
|
3358
|
+
})
|
|
3359
|
+
}),
|
|
3360
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3361
|
+
className: PlanReviewCard_module_css_default.footer,
|
|
3362
|
+
children: [
|
|
3363
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3364
|
+
className: PlanReviewCard_module_css_default.feedback,
|
|
3365
|
+
role: "status",
|
|
3366
|
+
children: action.error ?? notice ?? (settingsBlock ? settingsUnavailableReason : null)
|
|
3367
|
+
}),
|
|
3368
|
+
decline !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3369
|
+
className: PlanReviewCard_module_css_default.feedbackRow,
|
|
3370
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3371
|
+
className: PlanReviewCard_module_css_default.feedbackInput,
|
|
3372
|
+
type: "text",
|
|
3373
|
+
value: feedback,
|
|
3374
|
+
disabled: busy || blocked,
|
|
3375
|
+
placeholder: t("plan.feedbackPlaceholder"),
|
|
3376
|
+
"aria-label": t("plan.feedback"),
|
|
3377
|
+
onChange: (event) => {
|
|
3378
|
+
setFeedback(event.target.value);
|
|
3379
|
+
},
|
|
3380
|
+
onPointerDown: (event) => {
|
|
3381
|
+
event.stopPropagation();
|
|
3382
|
+
}
|
|
3383
|
+
})
|
|
3384
|
+
}),
|
|
3385
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3386
|
+
className: PlanReviewCard_module_css_default.bar,
|
|
3387
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3388
|
+
className: PlanReviewCard_module_css_default.actions,
|
|
3389
|
+
children: [
|
|
3390
|
+
error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3391
|
+
variant: "ghost",
|
|
3392
|
+
className: PlanReviewCard_module_css_default.recover,
|
|
3393
|
+
onClick: recover,
|
|
3394
|
+
children: t("action.reload")
|
|
3395
|
+
}),
|
|
3396
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3397
|
+
variant: "ghost",
|
|
3398
|
+
className: PlanReviewCard_module_css_default.discuss,
|
|
3399
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutlineRegular, { size: 14 }),
|
|
3400
|
+
disabled: busy,
|
|
3401
|
+
onClick: () => {
|
|
3402
|
+
settle(() => respondDismiss(matched, t("plan.cancelRejected"), t("plan.dismissUnsupported")), true);
|
|
3403
|
+
},
|
|
3404
|
+
children: t("plan.discuss")
|
|
3405
|
+
}),
|
|
3406
|
+
decline !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3407
|
+
variant: "outline",
|
|
3408
|
+
className: PlanReviewCard_module_css_default.keep,
|
|
3409
|
+
disabled: busy || blocked,
|
|
3410
|
+
title: decline.description ?? t("plan.keep"),
|
|
3411
|
+
onClick: () => {
|
|
3412
|
+
settle(() => respondAnswer(matched, review.id, decline.label, t("plan.responseRejected"), false, feedback.trim()));
|
|
3413
|
+
},
|
|
3414
|
+
children: t("plan.keep")
|
|
3415
|
+
}),
|
|
3416
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3417
|
+
variant: "primary",
|
|
3418
|
+
className: PlanReviewCard_module_css_default.approve,
|
|
3419
|
+
disabled: action.approveDisabled,
|
|
3420
|
+
onClick: onApprove,
|
|
3421
|
+
children: t("plan.approve")
|
|
3422
|
+
})
|
|
3423
|
+
]
|
|
3424
|
+
})
|
|
3425
|
+
})
|
|
3426
|
+
]
|
|
3427
|
+
})
|
|
3428
|
+
]
|
|
3429
|
+
})
|
|
3430
|
+
});
|
|
3431
|
+
}
|
|
3432
|
+
//#endregion
|
|
3433
|
+
//#region src/client/picker/PickerSeatBoundary.tsx
|
|
3434
|
+
function PickerSeatBoundary({ children, errorLabel }) {
|
|
3435
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RetryBoundary, {
|
|
3436
|
+
logLabel: "dsh-model-switch: composer picker seat crashed",
|
|
3437
|
+
renderFallback: (message, retry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3438
|
+
type: "button",
|
|
3439
|
+
"data-dsh-ms-seat-error": true,
|
|
3440
|
+
title: message,
|
|
3441
|
+
onClick: retry,
|
|
3442
|
+
style: {
|
|
3443
|
+
maxWidth: 280,
|
|
3444
|
+
border: 0,
|
|
3445
|
+
background: "transparent",
|
|
3446
|
+
color: "var(--dsw-alias-state-error-primary)",
|
|
3447
|
+
font: "var(--dsw-font-xs-13)",
|
|
3448
|
+
overflowWrap: "anywhere",
|
|
3449
|
+
cursor: "pointer"
|
|
3450
|
+
},
|
|
3451
|
+
children: errorLabel(message)
|
|
3452
|
+
}),
|
|
3453
|
+
children
|
|
3454
|
+
});
|
|
3455
|
+
}
|
|
3456
|
+
//#endregion
|
|
3457
|
+
//#region src/client/picker/locales.ts
|
|
3458
|
+
/** `composer-picker` namespace dictionaries. */
|
|
3459
|
+
const zh = {
|
|
3460
|
+
"trigger.fallback": "选择模型",
|
|
3461
|
+
"trigger.selectAria": "选择模型",
|
|
3462
|
+
"trigger.aria": "选择模型,当前 {model}",
|
|
3463
|
+
"trigger.ariaEffort": "选择模型,当前 {model},推理等级 {effort}",
|
|
3464
|
+
"menu.aria": "模型档位",
|
|
3465
|
+
"menu.back": "返回模型设置",
|
|
3466
|
+
"menu.closeSearch": "关闭搜索",
|
|
3467
|
+
"menu.model": "模型",
|
|
3468
|
+
"menu.effort": "推理等级",
|
|
3469
|
+
"menu.context": "上下文",
|
|
3470
|
+
"menu.fast": "Fast",
|
|
3471
|
+
"menu.thinking": "思考",
|
|
3472
|
+
"menu.search": "搜索模型",
|
|
3473
|
+
"context.standard": "标准",
|
|
3474
|
+
"fast.on": "开",
|
|
3475
|
+
"fast.off": "关",
|
|
3476
|
+
"thinking.on": "开",
|
|
3477
|
+
"thinking.off": "关",
|
|
3478
|
+
"effort.providerDefault": "Default",
|
|
3479
|
+
"status.loading": "正在刷新模型列表…",
|
|
3480
|
+
"settings.remoteUnavailable": "官方 DSH 在非本机浏览器禁用配置读取与写入;请在 Host 的 127.0.0.1 页面选择模型。",
|
|
3481
|
+
"settings.loading": "正在加载主模型设置;加载完成后才能切换模型。",
|
|
3482
|
+
"settings.unavailable": "主模型设置不可写;请在 Host 的本机页面检查设置状态。",
|
|
3483
|
+
"error.action": "模型操作失败:{message}",
|
|
3484
|
+
"error.picker": "模型选择器出错:{message}(点击重试)",
|
|
3485
|
+
"action.reload": "重新加载",
|
|
3486
|
+
"retry": "重试",
|
|
3487
|
+
"warning.groupLoad": "{name} 加载失败:{message}",
|
|
3488
|
+
"empty.models": "没有可用的模型。",
|
|
3489
|
+
"empty.efforts": "当前模型未提供推理等级。",
|
|
3490
|
+
"lock.readFailed": "提供方锁定状态读取失败,模型选项可能不是最新。",
|
|
3491
|
+
"choice.unavailable": "目录没有这个组合",
|
|
3492
|
+
"plan.kicker": "Plan",
|
|
3493
|
+
"plan.header": "计划待审",
|
|
3494
|
+
"plan.execution": "执行模型",
|
|
3495
|
+
"plan.approve": "确认执行",
|
|
3496
|
+
"plan.keep": "拒绝",
|
|
3497
|
+
"plan.discuss": "去聊天里说",
|
|
3498
|
+
"plan.modelFailed": "切换执行模型失败;计划尚未批准,可以重试。",
|
|
3499
|
+
"plan.pickerCrash": "执行模型选择器出错:{message}",
|
|
3500
|
+
"plan.feedback": "拒绝时的反馈",
|
|
3501
|
+
"plan.feedbackPlaceholder": "说明要改什么(可留空)",
|
|
3502
|
+
"plan.notRestored": "执行模型已切换,但主模型默认值未能还原({model});请在「设置 → 模型」中确认默认模型。",
|
|
3503
|
+
"plan.dismissUnsupported": "当前 DSH 版本既未提供 PendingQuestion.dismiss() 也未提供 cancel(),无法回到聊天;请直接在输入框里发送你的意见。",
|
|
3504
|
+
"plan.responseRejected": "计划答复已被另一客户端处理;已提交的模型切换无法由插件回滚。",
|
|
3505
|
+
"plan.cancelRejected": "计划审查已被另一客户端处理,无法返回讨论。",
|
|
3506
|
+
"markdown.copy": "复制代码",
|
|
3507
|
+
"markdown.copied": "已复制",
|
|
3508
|
+
"markdown.footnotes": "脚注"
|
|
3509
|
+
};
|
|
3510
|
+
const en = {
|
|
3511
|
+
"trigger.fallback": "Select model",
|
|
3512
|
+
"trigger.selectAria": "Select model",
|
|
3513
|
+
"trigger.aria": "Select model, current {model}",
|
|
3514
|
+
"trigger.ariaEffort": "Select model, current {model}, reasoning effort {effort}",
|
|
3515
|
+
"menu.aria": "Model options",
|
|
3516
|
+
"menu.back": "Back to model settings",
|
|
3517
|
+
"menu.closeSearch": "Close search",
|
|
3518
|
+
"menu.model": "Model",
|
|
3519
|
+
"menu.effort": "Effort",
|
|
3520
|
+
"menu.context": "Context",
|
|
3521
|
+
"menu.fast": "Fast",
|
|
3522
|
+
"menu.thinking": "Thinking",
|
|
3523
|
+
"menu.search": "Search models",
|
|
3524
|
+
"context.standard": "Standard",
|
|
3525
|
+
"fast.on": "On",
|
|
3526
|
+
"fast.off": "Off",
|
|
3527
|
+
"thinking.on": "On",
|
|
3528
|
+
"thinking.off": "Off",
|
|
3529
|
+
"effort.providerDefault": "Default",
|
|
3530
|
+
"status.loading": "Refreshing model list…",
|
|
3531
|
+
"settings.remoteUnavailable": "Official DSH disables settings reads and writes in non-loopback browsers. Select a model on the Host loopback URL.",
|
|
3532
|
+
"settings.loading": "Main model settings are loading. Wait until they are ready before switching models.",
|
|
3533
|
+
"settings.unavailable": "Main model settings are not writable. Check their status on the Host loopback URL.",
|
|
3534
|
+
"error.action": "Model operation failed: {message}",
|
|
3535
|
+
"error.picker": "Model picker error: {message} (click to retry)",
|
|
3536
|
+
"action.reload": "Reload",
|
|
3537
|
+
"retry": "Retry",
|
|
3538
|
+
"warning.groupLoad": "{name} failed to load: {message}",
|
|
3539
|
+
"empty.models": "No models available.",
|
|
3540
|
+
"empty.efforts": "This model provides no reasoning effort levels.",
|
|
3541
|
+
"lock.readFailed": "Provider lock read failed; model choices may be stale.",
|
|
3542
|
+
"choice.unavailable": "This combination is not in the catalog",
|
|
3543
|
+
"plan.kicker": "Plan",
|
|
3544
|
+
"plan.header": "Plan review",
|
|
3545
|
+
"plan.execution": "Execution model",
|
|
3546
|
+
"plan.approve": "Approve",
|
|
3547
|
+
"plan.keep": "Keep planning",
|
|
3548
|
+
"plan.discuss": "Discuss",
|
|
3549
|
+
"plan.modelFailed": "Could not switch the execution model; the Plan is still pending and can be retried.",
|
|
3550
|
+
"plan.pickerCrash": "Execution model picker error: {message}",
|
|
3551
|
+
"plan.feedback": "Feedback when declining",
|
|
3552
|
+
"plan.feedbackPlaceholder": "Say what to change (optional)",
|
|
3553
|
+
"plan.notRestored": "The execution model switched, but the Main default could not be restored ({model}); confirm the default in Settings → Models.",
|
|
3554
|
+
"plan.dismissUnsupported": "This DSH Host exposes neither PendingQuestion.dismiss() nor cancel(), so the review cannot return to chat; send your feedback as a message instead.",
|
|
3555
|
+
"plan.responseRejected": "Another client already handled this Plan response. A committed model change cannot be rolled back by this plugin.",
|
|
3556
|
+
"plan.cancelRejected": "Another client already handled this Plan review, so it cannot return to discussion.",
|
|
3557
|
+
"markdown.copy": "Copy code",
|
|
3558
|
+
"markdown.copied": "Copied",
|
|
3559
|
+
"markdown.footnotes": "Footnotes"
|
|
3560
|
+
};
|
|
3561
|
+
//#endregion
|
|
3562
|
+
//#region src/client/picker/install.tsx
|
|
3563
|
+
const NS = "composer-picker";
|
|
3564
|
+
const MODEL_PRIORITY = -10;
|
|
3565
|
+
const PLAN_REVIEW_PRIORITY = -5;
|
|
3566
|
+
function interactionOperationsFrom(ctx) {
|
|
3567
|
+
let value;
|
|
3568
|
+
try {
|
|
3569
|
+
value = ctx.get("interactionOperations", false);
|
|
3570
|
+
} catch {
|
|
3571
|
+
return;
|
|
3572
|
+
}
|
|
3573
|
+
if (value === null || typeof value !== "object") return void 0;
|
|
3574
|
+
const candidate = value;
|
|
3575
|
+
return typeof candidate.registerSurface === "function" ? candidate : void 0;
|
|
3576
|
+
}
|
|
3577
|
+
const EMPTY_ORDER = [];
|
|
3578
|
+
/**
|
|
3579
|
+
* Bind the optional Providers entry order as a React external store.
|
|
3580
|
+
* @returns A subscribable order snapshot with an invalidation hook for directory changes.
|
|
3581
|
+
*/
|
|
3582
|
+
function providerOrderStore(form) {
|
|
3583
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
3584
|
+
let last = EMPTY_ORDER;
|
|
3585
|
+
return {
|
|
3586
|
+
subscribe: (listener) => {
|
|
3587
|
+
listeners.add(listener);
|
|
3588
|
+
const stop = form.subscribe(listener);
|
|
3589
|
+
return () => {
|
|
3590
|
+
listeners.delete(listener);
|
|
3591
|
+
stop();
|
|
3592
|
+
};
|
|
3593
|
+
},
|
|
3594
|
+
getSnapshot: () => {
|
|
3595
|
+
const next = form.getSnapshot().value?.order ?? EMPTY_ORDER;
|
|
3596
|
+
if (next.length !== last.length || next.some((key, index) => key !== last[index])) last = [...next];
|
|
3597
|
+
return last;
|
|
3598
|
+
},
|
|
3599
|
+
invalidate: () => {
|
|
3600
|
+
last = [...last];
|
|
3601
|
+
for (const listener of listeners) listener();
|
|
3602
|
+
}
|
|
3603
|
+
};
|
|
3604
|
+
}
|
|
3605
|
+
function readSessionState(sessions, sessionId) {
|
|
3606
|
+
if (sessions === null || typeof sessions !== "object") return void 0;
|
|
3607
|
+
const get = sessions.get;
|
|
3608
|
+
if (typeof get !== "function") return void 0;
|
|
3609
|
+
try {
|
|
3610
|
+
const snapshot = get.call(sessions, sessionId)?.getSnapshot?.();
|
|
3611
|
+
if (snapshot === void 0) return void 0;
|
|
3612
|
+
return {
|
|
3613
|
+
...typeof snapshot.blank === "boolean" ? { blank: snapshot.blank } : {},
|
|
3614
|
+
active: snapshot.running === true || snapshot.awaitingFirstTurn === true || (snapshot.pendingSubmissions?.length ?? 0) > 0
|
|
3615
|
+
};
|
|
3616
|
+
} catch {
|
|
3617
|
+
return {
|
|
3618
|
+
blank: false,
|
|
3619
|
+
active: true
|
|
3620
|
+
};
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
function ModelSeat(props) {
|
|
3624
|
+
const directory = props.useDirectory((snapshot) => snapshot);
|
|
3625
|
+
const order = props.useProviderOrder((value) => value);
|
|
3626
|
+
const settingsUnavailableReason = mainDefaultsUnavailableReason((0, react.useSyncExternalStore)(props.subscribeMainDefaults, props.getMainDefaultsSnapshot), props.t);
|
|
3627
|
+
const lock = (0, react.useSyncExternalStore)(props.providerLockStore.subscribe, props.providerLockStore.getSnapshot);
|
|
3628
|
+
const phase = props.useInput((input) => input.phase);
|
|
3629
|
+
const blank = props.useSession((session) => session.blank);
|
|
3630
|
+
const active = props.useSession((session) => session.running || session.awaitingFirstTurn) || phase === "submitting";
|
|
3631
|
+
(0, react.useEffect)(() => {
|
|
3632
|
+
props.refreshProviderLock();
|
|
3633
|
+
}, [
|
|
3634
|
+
props.refreshProviderLock,
|
|
3635
|
+
phase,
|
|
3636
|
+
active,
|
|
3637
|
+
directory,
|
|
3638
|
+
order
|
|
3639
|
+
]);
|
|
3640
|
+
const providerLock = effectiveProviderLock(lock, directory.current?.provider, active, isAgentRole(props.roleOf?.(directory.current?.provider ?? "")));
|
|
3641
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [lock.failed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderLockHint, { t: props.t }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComposerPicker, {
|
|
3642
|
+
locked: props.locked || settingsUnavailableReason !== void 0 || lock.failed && directory.current === null,
|
|
3643
|
+
providerLock,
|
|
3644
|
+
agentLocked: agentProviderLocked(blank, providerLock, active),
|
|
3645
|
+
...props.roleOf === void 0 ? {} : { roleOf: props.roleOf },
|
|
3646
|
+
available: props.available,
|
|
3647
|
+
...settingsUnavailableReason === void 0 ? {} : { unavailableReason: settingsUnavailableReason },
|
|
3648
|
+
directory: pickerDirectoryViewOrdered(directory, props, order, props.catalogRoutes?.() ?? {}),
|
|
3649
|
+
t: props.t,
|
|
3650
|
+
...props.resolveInteractionOperations === void 0 ? {} : { resolveInteractionOperations: props.resolveInteractionOperations }
|
|
3651
|
+
})] });
|
|
3652
|
+
}
|
|
3653
|
+
function ModelSeatEntry(props) {
|
|
3654
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PickerSeatBoundary, {
|
|
3655
|
+
errorLabel: (message) => props.t("error.picker", { message }),
|
|
3656
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelSeat, { ...props })
|
|
3657
|
+
});
|
|
3658
|
+
}
|
|
3659
|
+
/** Register composer model picker and Plan Review execution picker. */
|
|
3660
|
+
function installComposerPicker(ctx) {
|
|
3661
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
3662
|
+
zh,
|
|
3663
|
+
en
|
|
3664
|
+
}), "dsh-model-switch: composer picker dictionaries");
|
|
3665
|
+
ctx.inject([
|
|
3666
|
+
"slots",
|
|
3667
|
+
"modelDirectories",
|
|
3668
|
+
"configForms"
|
|
3669
|
+
], (scope) => {
|
|
3670
|
+
const models = scope.modelDirectories;
|
|
3671
|
+
const sessions = scope.sessions;
|
|
3672
|
+
const mainDefaults = scope.configForms.get(MAIN_DEFAULT_CONFIG_ID);
|
|
3673
|
+
let directoryService;
|
|
3674
|
+
const orderStore = providerOrderStore(scope.configForms.get(PROVIDERS_CONFIG_ID));
|
|
3675
|
+
scope.inject(["providerDirectory"], (directoryScope) => {
|
|
3676
|
+
const directory = directoryScope.get("providerDirectory", false);
|
|
3677
|
+
if (directory === void 0 || typeof directory.subscribe !== "function") return;
|
|
3678
|
+
directoryService = directory;
|
|
3679
|
+
orderStore.invalidate();
|
|
3680
|
+
directoryScope.effect(() => directory.subscribe(orderStore.invalidate));
|
|
3681
|
+
directoryScope.effect(() => () => {
|
|
3682
|
+
if (directoryService === directory) directoryService = void 0;
|
|
3683
|
+
orderStore.invalidate();
|
|
3684
|
+
});
|
|
3685
|
+
});
|
|
3686
|
+
const resolveInteractionOperations = () => interactionOperationsFrom(scope);
|
|
3687
|
+
const rpcOf = () => {
|
|
3688
|
+
return scope.get("connection", false)?.rpc;
|
|
3689
|
+
};
|
|
3690
|
+
const directoryFace = (sessionId) => {
|
|
3691
|
+
const directory = models.directoryFor(sessionId);
|
|
3692
|
+
const available = sessions?.subagentAddress?.(sessionId) === void 0;
|
|
3693
|
+
const readLock = async (previous) => {
|
|
3694
|
+
const result = await fetchSessionBinding(rpcOf(), sessionId, readNativeBindings(directoryService));
|
|
3695
|
+
return result.failed ? {
|
|
3696
|
+
provider: result.provider ?? previous.provider,
|
|
3697
|
+
failed: true
|
|
3698
|
+
} : result;
|
|
3699
|
+
};
|
|
3700
|
+
const roleOf = (key) => readProviderRole(directoryService, key);
|
|
3701
|
+
const providerLockStore = createProviderLockStore(readLock);
|
|
3702
|
+
return {
|
|
3703
|
+
available,
|
|
3704
|
+
roleOf,
|
|
3705
|
+
catalogRoutes: () => readCatalogRoutes(directoryService),
|
|
3706
|
+
providerLockStore,
|
|
3707
|
+
refreshProviderLock: () => {
|
|
3708
|
+
providerLockStore.refresh();
|
|
3709
|
+
},
|
|
3710
|
+
subscribeMainDefaults: (listener) => mainDefaults.subscribe(listener),
|
|
3711
|
+
getMainDefaultsSnapshot: () => mainDefaults.getSnapshot(),
|
|
3712
|
+
hooks: {
|
|
3713
|
+
directory: directory.store,
|
|
3714
|
+
providerOrder: orderStore
|
|
3715
|
+
},
|
|
3716
|
+
getDirectorySnapshot: directory.store.getSnapshot,
|
|
3717
|
+
resolveInteractionOperations,
|
|
3718
|
+
load: () => {
|
|
3719
|
+
if (available) directory.load().catch(() => {});
|
|
3720
|
+
},
|
|
3721
|
+
select: async (selection) => {
|
|
3722
|
+
const uncommitted = {
|
|
3723
|
+
committed: false,
|
|
3724
|
+
mainDefaultRestored: true
|
|
3725
|
+
};
|
|
3726
|
+
if (!available) return uncommitted;
|
|
3727
|
+
const state = await providerLockStore.refresh();
|
|
3728
|
+
const currentProvider = directory.store.getSnapshot().current?.provider;
|
|
3729
|
+
if (!isProviderAllowed(state, selection.provider, currentProvider, {
|
|
3730
|
+
...readSessionState(sessions, sessionId),
|
|
3731
|
+
agent: isAgentRole(roleOf(selection.provider)),
|
|
3732
|
+
currentAgent: currentProvider !== void 0 && isAgentRole(roleOf(currentProvider))
|
|
3733
|
+
})) return uncommitted;
|
|
3734
|
+
const defaultBeforeSwitch = mainDefaults.getSnapshot();
|
|
3735
|
+
if (defaultBeforeSwitch.status !== "ready" || defaultBeforeSwitch.mode !== "host" || !defaultBeforeSwitch.writable || defaultBeforeSwitch.value === void 0 || defaultBeforeSwitch.revision === void 0) return uncommitted;
|
|
3736
|
+
let result;
|
|
3737
|
+
try {
|
|
3738
|
+
result = await directory.select(selection);
|
|
3739
|
+
} catch {
|
|
3740
|
+
return uncommitted;
|
|
3741
|
+
}
|
|
3742
|
+
if (!result.ok) return uncommitted;
|
|
3743
|
+
return {
|
|
3744
|
+
committed: true,
|
|
3745
|
+
mainDefaultRestored: (await restoreMainDefault(mainDefaults, defaultBeforeSwitch)).restored
|
|
3746
|
+
};
|
|
3747
|
+
}
|
|
3748
|
+
};
|
|
3749
|
+
};
|
|
3750
|
+
scope.slots.inject("conversation.input.model", () => scope.slots.register({
|
|
3751
|
+
name: "conversation.input.model",
|
|
3752
|
+
locale: NS,
|
|
3753
|
+
priority: MODEL_PRIORITY,
|
|
3754
|
+
inject: directoryFace
|
|
3755
|
+
}, ModelSeatEntry));
|
|
3756
|
+
scope.slots.inject("conversation.composer", () => scope.slots.register({
|
|
3757
|
+
name: "conversation.composer",
|
|
3758
|
+
locale: NS,
|
|
3759
|
+
priority: PLAN_REVIEW_PRIORITY,
|
|
3760
|
+
select: (owner) => selectPlanReview(owner),
|
|
3761
|
+
inject: directoryFace
|
|
3762
|
+
}, PlanReviewCard));
|
|
3763
|
+
});
|
|
3764
|
+
}
|
|
3765
|
+
//#endregion
|
|
3766
|
+
//#region src/client/nav-icon.ts
|
|
3767
|
+
/** Official settings.section has no icon field. Swap the default gear for a model-switch glyph. */
|
|
3768
|
+
const LABELS = /* @__PURE__ */ new Set(["Model Switch", "模型切换"]);
|
|
3769
|
+
const MARK = "data-dsh-ms-icon";
|
|
3770
|
+
const GLYPH = [
|
|
3771
|
+
"<g data-dsh-ms-glyph=\"switch\">",
|
|
3772
|
+
"<path d=\"M3 5.5h7.2\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\"/>",
|
|
3773
|
+
"<path d=\"M10.2 5.5 8.4 3.7M10.2 5.5 8.4 7.3\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>",
|
|
3774
|
+
"<path d=\"M13 10.5H5.8\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\"/>",
|
|
3775
|
+
"<path d=\"M5.8 10.5 7.6 8.7M5.8 10.5 7.6 12.3\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>",
|
|
3776
|
+
"</g>"
|
|
3777
|
+
].join("");
|
|
3778
|
+
function patch() {
|
|
3779
|
+
for (const button of Array.from(document.querySelectorAll("nav button"))) {
|
|
3780
|
+
if (Array.from(button.querySelectorAll("span")).find((span) => LABELS.has(span.textContent?.trim() ?? "")) === void 0) continue;
|
|
3781
|
+
const svg = button.querySelector("svg");
|
|
3782
|
+
if (svg === null) continue;
|
|
3783
|
+
if (svg.getAttribute(MARK) === "switch" && svg.innerHTML.includes("data-dsh-ms-glyph=\"switch\"")) continue;
|
|
3784
|
+
svg.setAttribute(MARK, "switch");
|
|
3785
|
+
svg.setAttribute("viewBox", "0 0 16 16");
|
|
3786
|
+
svg.setAttribute("fill", "none");
|
|
3787
|
+
svg.innerHTML = GLYPH;
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
function touchesSettingsNav(node) {
|
|
3791
|
+
if (!(node instanceof Element)) return false;
|
|
3792
|
+
if (node.closest("nav") !== null || node.querySelector("nav") !== null) return true;
|
|
3793
|
+
const buttons = node.matches("button") ? [node, ...Array.from(node.querySelectorAll("button"))] : Array.from(node.querySelectorAll("button"));
|
|
3794
|
+
for (const button of buttons) for (const span of Array.from(button.querySelectorAll("span"))) if (LABELS.has(span.textContent?.trim() ?? "")) return true;
|
|
3795
|
+
return false;
|
|
3796
|
+
}
|
|
3797
|
+
/** Keep the Model Switch nav glyph in place across Settings re-renders. */
|
|
3798
|
+
function installModelSwitchNavIcon() {
|
|
3799
|
+
if (typeof document === "undefined" || document.body === null) return () => {};
|
|
3800
|
+
let frame = 0;
|
|
3801
|
+
const observer = new MutationObserver((records) => {
|
|
3802
|
+
if (!records.some((record) => touchesSettingsNav(record.target) || Array.from(record.addedNodes).some(touchesSettingsNav))) return;
|
|
3803
|
+
if (frame !== 0) return;
|
|
3804
|
+
frame = requestAnimationFrame(() => {
|
|
3805
|
+
frame = 0;
|
|
3806
|
+
patch();
|
|
3807
|
+
observer.takeRecords();
|
|
3808
|
+
});
|
|
3809
|
+
});
|
|
3810
|
+
observer.observe(document.body, {
|
|
3811
|
+
childList: true,
|
|
3812
|
+
subtree: true
|
|
3813
|
+
});
|
|
3814
|
+
patch();
|
|
3815
|
+
observer.takeRecords();
|
|
3816
|
+
return () => {
|
|
3817
|
+
observer.disconnect();
|
|
3818
|
+
if (frame !== 0) cancelAnimationFrame(frame);
|
|
3819
|
+
};
|
|
3820
|
+
}
|
|
3821
|
+
//#endregion
|
|
3822
|
+
//#region src/client/index.tsx
|
|
3823
|
+
const name = "dsh-model-switch-client";
|
|
3824
|
+
const inject = [
|
|
3825
|
+
"slots",
|
|
3826
|
+
"locale",
|
|
3827
|
+
"sessions",
|
|
3828
|
+
"modelDirectories",
|
|
3829
|
+
"configForms",
|
|
3830
|
+
"remote",
|
|
3831
|
+
"remote.session"
|
|
3832
|
+
];
|
|
3833
|
+
function apply(ctx) {
|
|
3834
|
+
installComposerPicker(ctx);
|
|
3835
|
+
ctx.effect(installModelSwitchNavIcon, "dsh-model-switch: settings nav icon");
|
|
3836
|
+
const localeNamespace = "settings.model-switch";
|
|
3837
|
+
ctx.effect(() => ctx.locale.register(localeNamespace, {
|
|
3838
|
+
zh: zh$1,
|
|
3839
|
+
en: en$1
|
|
3840
|
+
}), "dsh-model-switch: localized Settings section");
|
|
3841
|
+
const t = ctx.locale.bind(localeNamespace);
|
|
3842
|
+
const remote = ctx.remote;
|
|
3843
|
+
const mainForm = ctx.configForms.get(MAIN_DEFAULT_CONFIG_ID);
|
|
3844
|
+
const owned = ctx.configForms.get(MODEL_SWITCH_CONFIG_ID);
|
|
3845
|
+
const main = deriveConfigForm(mainForm, (value) => value, {
|
|
3846
|
+
provider: "provider",
|
|
3847
|
+
model: "model",
|
|
3848
|
+
reasoningEffort: "reasoningEffort"
|
|
3849
|
+
});
|
|
3850
|
+
const subagent = deriveConfigForm(owned, deriveSubagentSettings, SUBAGENT_SETTINGS_FIELDS);
|
|
3851
|
+
const search = deriveConfigForm(owned, deriveSearchSettings, SEARCH_SETTINGS_FIELDS);
|
|
3852
|
+
const image = deriveConfigForm(owned, deriveImageSettings, IMAGE_SETTINGS_FIELDS);
|
|
3853
|
+
const saveMain = async (next, expectedRevision) => {
|
|
3854
|
+
if (!await mainForm.mutate([
|
|
3855
|
+
{
|
|
3856
|
+
op: "set",
|
|
3857
|
+
path: ["provider"],
|
|
3858
|
+
value: next.provider
|
|
3859
|
+
},
|
|
3860
|
+
{
|
|
3861
|
+
op: "set",
|
|
3862
|
+
path: ["model"],
|
|
3863
|
+
value: next.model
|
|
3864
|
+
},
|
|
3865
|
+
next.reasoningEffort === void 0 || next.reasoningEffort === "" ? {
|
|
3866
|
+
op: "unset",
|
|
3867
|
+
path: ["reasoningEffort"]
|
|
3868
|
+
} : {
|
|
3869
|
+
op: "set",
|
|
3870
|
+
path: ["reasoningEffort"],
|
|
3871
|
+
value: next.reasoningEffort
|
|
3872
|
+
}
|
|
3873
|
+
], expectedRevision)) {
|
|
3874
|
+
if (mainForm.getSnapshot().revision !== expectedRevision) throw new MainSettingsConflictError(t("conflict"));
|
|
3875
|
+
throw new Error("settings-rejected");
|
|
3876
|
+
}
|
|
3877
|
+
const revision = mainForm.getSnapshot().revision;
|
|
3878
|
+
if (revision === void 0) throw new Error("settings unavailable after save");
|
|
3879
|
+
return revision;
|
|
3880
|
+
};
|
|
3881
|
+
const connectionRpc = (() => {
|
|
3882
|
+
try {
|
|
3883
|
+
const rpc = ctx.get("connection", false)?.rpc;
|
|
3884
|
+
return rpc !== void 0 && typeof rpc.call === "function" ? rpc : void 0;
|
|
3885
|
+
} catch {
|
|
3886
|
+
return;
|
|
3887
|
+
}
|
|
3888
|
+
})();
|
|
3889
|
+
const orderStore = providerOrderStore(ctx.configForms.get(PROVIDERS_CONFIG_ID));
|
|
3890
|
+
let directory;
|
|
3891
|
+
ctx.inject(["providerDirectory"], (scope) => {
|
|
3892
|
+
const current = scope.get("providerDirectory", false);
|
|
3893
|
+
if (current === void 0 || typeof current.subscribe !== "function") return;
|
|
3894
|
+
directory = current;
|
|
3895
|
+
orderStore.invalidate();
|
|
3896
|
+
scope.effect(() => current.subscribe(orderStore.invalidate));
|
|
3897
|
+
scope.effect(() => () => {
|
|
3898
|
+
if (directory === current) directory = void 0;
|
|
3899
|
+
orderStore.invalidate();
|
|
3900
|
+
});
|
|
3901
|
+
});
|
|
3902
|
+
const providerRoleOf = (key) => readProviderRole(directory, key) ?? "llm";
|
|
3903
|
+
const subscribeProviderOrder = orderStore.subscribe;
|
|
3904
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
3905
|
+
name: "settings.section",
|
|
3906
|
+
id: "model-switch",
|
|
3907
|
+
order: 9,
|
|
3908
|
+
label: () => t("nav"),
|
|
3909
|
+
locale: localeNamespace,
|
|
3910
|
+
inject: () => ({
|
|
3911
|
+
t,
|
|
3912
|
+
hooks: {
|
|
3913
|
+
mainSettings: main,
|
|
3914
|
+
subagentSettings: subagent,
|
|
3915
|
+
searchSettings: search,
|
|
3916
|
+
imageSettings: image,
|
|
3917
|
+
switchSettings: owned
|
|
3918
|
+
},
|
|
3919
|
+
capabilities: RUNTIME_CAPABILITIES,
|
|
3920
|
+
saveMain,
|
|
3921
|
+
setSubagent: (field, value) => value === void 0 ? subagent.unset(field) : subagent.set(field, value),
|
|
3922
|
+
setCapability: (route, field, value) => {
|
|
3923
|
+
const scope = route === "search" ? search : image;
|
|
3924
|
+
return value === void 0 ? scope.unset(field) : scope.set(field, value);
|
|
3925
|
+
},
|
|
3926
|
+
setCompactOnSwitch: async (value) => {
|
|
3927
|
+
if (!await owned.set("compactOnSwitch", value)) throw new Error("settings-rejected");
|
|
3928
|
+
},
|
|
3929
|
+
providerRoleOf,
|
|
3930
|
+
loadCatalog: async () => {
|
|
3931
|
+
const response = await remote.session.modelCatalog();
|
|
3932
|
+
if (!response.ok || response.value === void 0) throw new Error(t("catalogFailed"));
|
|
3933
|
+
const enabled = await fetchAntigravityCatalogGroups(connectionRpc);
|
|
3934
|
+
const order = orderStore.getSnapshot();
|
|
3935
|
+
return sortCatalogGroupsWithRoutes(withAntigravityCatalog(response.value.groups, enabled), order, readCatalogRoutes(directory));
|
|
3936
|
+
},
|
|
3937
|
+
...connectionRpc === void 0 ? {} : { loadCapabilities: async (revision, signal) => {
|
|
3938
|
+
const rpc = connectionRpc;
|
|
3939
|
+
if (rpc === void 0) throw new Error(t("catalogFailed"));
|
|
3940
|
+
const response = await callPluginRpc(rpc, "plugin-rpc/model-switch", "capabilities", revision === void 0 ? {} : { revision }, signal);
|
|
3941
|
+
if (!response.ok || response.value === void 0) throw new Error(t("catalogFailed"));
|
|
3942
|
+
const snapshot = decodeCapabilitiesSnapshot(response.value);
|
|
3943
|
+
if (snapshot === void 0) throw new Error(t("catalogFailed"));
|
|
3944
|
+
return snapshot;
|
|
3945
|
+
} },
|
|
3946
|
+
subscribeProviderOrder
|
|
3947
|
+
})
|
|
3948
|
+
}, ModelSwitchSettings));
|
|
3949
|
+
}
|
|
3950
|
+
//#endregion
|
|
3951
|
+
exports.apply = apply;
|
|
3952
|
+
exports.inject = inject;
|
|
3953
|
+
exports.name = name;
|
|
3954
|
+
return module.exports;
|
|
3955
|
+
}
|
|
3956
|
+
});
|