@jcy2387/dsh-models-input-modalities 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -9
- package/README.zh.md +51 -13
- package/cordis.patch.yml +4 -3
- package/lib/client.cjs +498 -73
- package/lib/client.cjs.map +1 -1
- package/lib/types/client/ModelCapabilityCard.d.ts +34 -0
- package/lib/types/client/controller.d.ts +22 -7
- package/lib/types/client/index.d.ts +9 -8
- package/lib/types/client/locales.d.ts +13 -3
- package/lib/types/image-input.d.ts +1 -9
- package/lib/types/model-row.d.ts +10 -0
- package/lib/types/reasoning-efforts.d.ts +108 -0
- package/package.json +1 -1
- package/lib/types/client/ImageInputCard.d.ts +0 -27
package/lib/client.cjs
CHANGED
|
@@ -24,8 +24,10 @@ window.__ModuleLoader__.load({
|
|
|
24
24
|
return Array.isArray(value) ? value.map((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry) ? entry : {}) : [];
|
|
25
25
|
}
|
|
26
26
|
/** Joins the settings Remote's document view and fenced writes for one card. */
|
|
27
|
-
var
|
|
27
|
+
var ModelCapabilityController = class {
|
|
28
28
|
ctx;
|
|
29
|
+
/** The cards waiting to hear that this namespace's stored section changed. */
|
|
30
|
+
listeners = /* @__PURE__ */ new Set();
|
|
29
31
|
/**
|
|
30
32
|
* @param ctx - the plugin's client context, which declares `remote.settings`
|
|
31
33
|
* in its own `inject`.
|
|
@@ -34,6 +36,29 @@ window.__ModuleLoader__.load({
|
|
|
34
36
|
this.ctx = ctx;
|
|
35
37
|
}
|
|
36
38
|
/**
|
|
39
|
+
* Start forwarding this namespace's pushed document invalidations to the
|
|
40
|
+
* cards. The Host emits one per committed write — including the Models page's
|
|
41
|
+
* own model-list edits — so a card never has to poll or wait for a remount.
|
|
42
|
+
* @returns the disposer that withdraws the Remote subscription.
|
|
43
|
+
*/
|
|
44
|
+
watch() {
|
|
45
|
+
return this.ctx.remote.$on("settings/document-updated", (ns, revision) => {
|
|
46
|
+
if (String(ns) !== NS$1) return;
|
|
47
|
+
for (const listener of [...this.listeners]) listener(revision);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Subscribe one card to namespace invalidations.
|
|
52
|
+
* @param listener - called with the namespace's new revision on each change.
|
|
53
|
+
* @returns the disposer for this one subscription.
|
|
54
|
+
*/
|
|
55
|
+
subscribe(listener) {
|
|
56
|
+
this.listeners.add(listener);
|
|
57
|
+
return () => {
|
|
58
|
+
this.listeners.delete(listener);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
37
62
|
* Read one provider's model rows and the revision fence for writing them.
|
|
38
63
|
* @param entry - the card's directory row (its settings address names the profile).
|
|
39
64
|
* @returns the view, or undefined when the settings face or namespace is unavailable.
|
|
@@ -123,6 +148,8 @@ window.__ModuleLoader__.load({
|
|
|
123
148
|
function parseImageInputChoice(value) {
|
|
124
149
|
return value === "inherit" || value === "text" || value === "image" ? value : void 0;
|
|
125
150
|
}
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/model-row.ts
|
|
126
153
|
/**
|
|
127
154
|
* The row's model id for labels.
|
|
128
155
|
* @param row - one stored model row.
|
|
@@ -134,8 +161,197 @@ window.__ModuleLoader__.load({
|
|
|
134
161
|
return typeof id === "string" && id.length > 0 ? id : `#${String(index + 1)}`;
|
|
135
162
|
}
|
|
136
163
|
//#endregion
|
|
164
|
+
//#region src/reasoning-efforts.ts
|
|
165
|
+
/** Every level a row may declare, in escalation order — the adapter's own key set. */
|
|
166
|
+
const THINKING_LEVELS = [
|
|
167
|
+
"off",
|
|
168
|
+
"minimal",
|
|
169
|
+
"low",
|
|
170
|
+
"medium",
|
|
171
|
+
"high",
|
|
172
|
+
"xhigh",
|
|
173
|
+
"max"
|
|
174
|
+
];
|
|
175
|
+
/**
|
|
176
|
+
* The spelling a newly offered level starts from: its own name, except for
|
|
177
|
+
* `off`, whose absence is the spelling most providers read as "do not think".
|
|
178
|
+
* @param level - the level being offered.
|
|
179
|
+
* @returns its default wire spelling.
|
|
180
|
+
*/
|
|
181
|
+
function defaultWire(level) {
|
|
182
|
+
return level === "off" ? "" : level;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* A level set offering exactly `offered`, each at its default spelling.
|
|
186
|
+
* @param offered - the levels to declare.
|
|
187
|
+
* @returns the seven level drafts.
|
|
188
|
+
*/
|
|
189
|
+
function declaring(offered) {
|
|
190
|
+
const drafts = {};
|
|
191
|
+
for (const level of THINKING_LEVELS) {
|
|
192
|
+
const on = offered.includes(level);
|
|
193
|
+
drafts[level] = {
|
|
194
|
+
offered: on,
|
|
195
|
+
wire: on ? defaultWire(level) : ""
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return drafts;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* The levels a row with no dict of its own starts from when it is switched to a
|
|
202
|
+
* declared set: the three efforts an OpenAI-compatible gateway is most likely
|
|
203
|
+
* to serve, each spelled as its own name. `off` is deliberately left unticked:
|
|
204
|
+
* on the plain `reasoning_effort` wire an empty `off` is the very same request
|
|
205
|
+
* as naming no effort at all, so pre-offering it would promise a "stop
|
|
206
|
+
* thinking" choice the endpoint may not honour — the author ticks it, and
|
|
207
|
+
* spells it, once their endpoint says how.
|
|
208
|
+
*/
|
|
209
|
+
const DEFAULT_LEVELS = declaring([
|
|
210
|
+
"low",
|
|
211
|
+
"medium",
|
|
212
|
+
"high"
|
|
213
|
+
]);
|
|
214
|
+
/**
|
|
215
|
+
* The choice a row's stored `reasoningEfforts` displays. Anything that is not
|
|
216
|
+
* `false` and not a plain object states no claim this card understands, and
|
|
217
|
+
* reads as the inheritance it leaves in place; an empty object reads as a
|
|
218
|
+
* declared set offering nothing, which the validator then names.
|
|
219
|
+
* @param row - one stored model row.
|
|
220
|
+
* @returns the choice the row's select shows.
|
|
221
|
+
*/
|
|
222
|
+
function reasoningChoice(row) {
|
|
223
|
+
const value = row["reasoningEfforts"];
|
|
224
|
+
if (value === false) return "none";
|
|
225
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) return "custom";
|
|
226
|
+
return "inherit";
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* The row's declared levels as editor state. A level the dict omits is not
|
|
230
|
+
* offered; one it carries is, with its wire spelling — an empty value, which is
|
|
231
|
+
* what a valueless `off:` stores as, and a value of a type the schema refuses
|
|
232
|
+
* both read as no spelling yet. A row declaring nothing shows the defaults a
|
|
233
|
+
* fresh declaration starts from.
|
|
234
|
+
* @param row - one stored model row.
|
|
235
|
+
* @returns the seven level drafts.
|
|
236
|
+
*/
|
|
237
|
+
function reasoningLevels(row) {
|
|
238
|
+
const value = row["reasoningEfforts"];
|
|
239
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return DEFAULT_LEVELS;
|
|
240
|
+
const stored = value;
|
|
241
|
+
const drafts = {};
|
|
242
|
+
for (const level of THINKING_LEVELS) {
|
|
243
|
+
if (!(level in stored)) {
|
|
244
|
+
drafts[level] = {
|
|
245
|
+
offered: false,
|
|
246
|
+
wire: ""
|
|
247
|
+
};
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const wire = stored[level];
|
|
251
|
+
drafts[level] = {
|
|
252
|
+
offered: true,
|
|
253
|
+
wire: typeof wire === "string" ? wire : ""
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
return drafts;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* The row with one reasoning choice applied: `inherit` removes the field,
|
|
260
|
+
* `none` stores `false`, and `custom` stores exactly the levels offered — in
|
|
261
|
+
* escalation order, a valueless `off` as `null`, spellings trimmed so a stray
|
|
262
|
+
* space cannot reach the wire. Every other field, including ones this card
|
|
263
|
+
* never shows, survives.
|
|
264
|
+
* @param row - the row to patch.
|
|
265
|
+
* @param choice - the selected state.
|
|
266
|
+
* @param levels - the editor's level drafts; read only for `custom`.
|
|
267
|
+
* @returns a new row carrying the choice.
|
|
268
|
+
*/
|
|
269
|
+
function withReasoning(row, choice, levels) {
|
|
270
|
+
const next = { ...row };
|
|
271
|
+
delete next["reasoningEfforts"];
|
|
272
|
+
if (choice === "none") {
|
|
273
|
+
next["reasoningEfforts"] = false;
|
|
274
|
+
return next;
|
|
275
|
+
}
|
|
276
|
+
if (choice !== "custom") return next;
|
|
277
|
+
const declared = {};
|
|
278
|
+
for (const level of THINKING_LEVELS) {
|
|
279
|
+
const draft = levels[level];
|
|
280
|
+
if (!draft.offered) continue;
|
|
281
|
+
const wire = draft.wire.trim();
|
|
282
|
+
declared[level] = wire.length === 0 ? null : wire;
|
|
283
|
+
}
|
|
284
|
+
next["reasoningEfforts"] = declared;
|
|
285
|
+
return next;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* The level set with one level offered or withdrawn. Offering a level always
|
|
289
|
+
* starts its spelling from the default, so a tick is never a blank the save
|
|
290
|
+
* then refuses, and what a withdrawn level carried is not what a reticked one
|
|
291
|
+
* silently revives; levels left alone keep theirs.
|
|
292
|
+
* @param levels - the drafts to patch.
|
|
293
|
+
* @param level - the level toggled.
|
|
294
|
+
* @param offered - whether the row should declare it.
|
|
295
|
+
* @returns the patched drafts, or the same object when nothing changes.
|
|
296
|
+
*/
|
|
297
|
+
function toggleLevel(levels, level, offered) {
|
|
298
|
+
if (levels[level].offered === offered) return levels;
|
|
299
|
+
return {
|
|
300
|
+
...levels,
|
|
301
|
+
[level]: {
|
|
302
|
+
offered,
|
|
303
|
+
wire: offered ? defaultWire(level) : ""
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* The level set with one level's wire spelling retyped.
|
|
309
|
+
* @param levels - the drafts to patch.
|
|
310
|
+
* @param level - the level whose spelling changed.
|
|
311
|
+
* @param wire - the text the field now carries.
|
|
312
|
+
* @returns the patched drafts.
|
|
313
|
+
*/
|
|
314
|
+
function setWire(levels, level, wire) {
|
|
315
|
+
return {
|
|
316
|
+
...levels,
|
|
317
|
+
[level]: {
|
|
318
|
+
...levels[level],
|
|
319
|
+
wire
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Why a row's declared levels would be refused, checked before the write so the
|
|
325
|
+
* card can name the row instead of answering a rejected settings mutation. The
|
|
326
|
+
* rules are the adapter's: every declared level but `off` needs a wire spelling,
|
|
327
|
+
* and a set offering no level beyond `off` declares nothing worth declaring.
|
|
328
|
+
* @param row - one stored model row.
|
|
329
|
+
* @returns the failure, or undefined for a row that can be saved.
|
|
330
|
+
*/
|
|
331
|
+
function reasoningFailure(row) {
|
|
332
|
+
if (reasoningChoice(row) !== "custom") return void 0;
|
|
333
|
+
const levels = reasoningLevels(row);
|
|
334
|
+
let thinks = false;
|
|
335
|
+
for (const level of THINKING_LEVELS) {
|
|
336
|
+
const draft = levels[level];
|
|
337
|
+
if (!draft.offered || level === "off") continue;
|
|
338
|
+
if (draft.wire.trim().length === 0) return "needsWire";
|
|
339
|
+
thinks = true;
|
|
340
|
+
}
|
|
341
|
+
return thinks ? void 0 : "needsLevel";
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Read a select's submitted value. The DOM hands over a bare string, so an
|
|
345
|
+
* unrecognized one is refused rather than cast into the union.
|
|
346
|
+
* @param value - the submitted option value.
|
|
347
|
+
* @returns the choice, or undefined for anything else.
|
|
348
|
+
*/
|
|
349
|
+
function parseReasoningChoice(value) {
|
|
350
|
+
return value === "inherit" || value === "none" || value === "custom" ? value : void 0;
|
|
351
|
+
}
|
|
352
|
+
//#endregion
|
|
137
353
|
//#region \0dsh-css:src/client/styles.module.css.mjs
|
|
138
|
-
const css = ".
|
|
354
|
+
const css = ".q49TuG_fold{border:1px solid var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-bg-layer-3,transparent);border-radius:10px;margin:8px 0 0}.q49TuG_summary{cursor:pointer;color:var(--dsw-alias-label-primary,inherit);user-select:none;padding:10px 14px;font-size:13px;font-weight:600}.q49TuG_body{flex-direction:column;gap:10px;padding:2px 14px 14px;display:flex}.q49TuG_model{flex-direction:column;gap:6px;display:flex}.q49TuG_row{flex-wrap:wrap;align-items:flex-end;gap:10px;display:flex}.q49TuG_rowId{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary,inherit);flex:auto;padding-bottom:6px;font-size:13px;overflow:hidden}.q49TuG_field{flex-direction:column;gap:3px;display:flex}.q49TuG_fieldLabel{color:var(--dsw-alias-label-tertiary,inherit);font-size:11px;line-height:1.2}.q49TuG_select{min-width:9em;color:var(--dsw-alias-label-primary,inherit);border:1px solid var(--dsw-alias-border-l2,#0000001f);border-radius:8px;flex:none;padding:6px 8px;font-size:13px}.q49TuG_levels{border:1px dashed var(--dsw-alias-border-l2,#0000001f);border-radius:8px;flex-direction:column;gap:6px;padding:8px 10px;display:flex}.q49TuG_levelGrid{grid-template-columns:repeat(auto-fill,minmax(15em,1fr));gap:6px 12px;display:grid}.q49TuG_levelRow{align-items:center;gap:8px;min-width:0;display:flex}.q49TuG_levelOffered{color:var(--dsw-alias-label-primary,inherit);cursor:pointer;flex:0 0 6.5em;align-items:center;gap:6px;font-size:12px;display:flex}.q49TuG_wire{min-width:0;color:var(--dsw-alias-label-primary,inherit);border:1px solid var(--dsw-alias-border-l2,#0000001f);background:0 0;border-radius:8px;flex:auto;padding:4px 8px;font-size:12px}.q49TuG_wire:disabled{opacity:.55}.q49TuG_hint,.q49TuG_status{color:var(--dsw-alias-label-tertiary,inherit);margin:0;font-size:12px;line-height:1.5}.q49TuG_error{color:var(--dsw-alias-status-danger,#d64545);margin:0;font-size:12px;line-height:1.5}.q49TuG_footer{align-items:center;gap:10px;display:flex}";
|
|
139
355
|
const tagId = "@jcy2387/dsh-models-input-modalities/styles.module.css";
|
|
140
356
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
141
357
|
const tag = document.createElement("style");
|
|
@@ -145,67 +361,156 @@ window.__ModuleLoader__.load({
|
|
|
145
361
|
document.head.appendChild(tag);
|
|
146
362
|
}
|
|
147
363
|
var styles_module_css_default = {
|
|
148
|
-
"
|
|
149
|
-
"
|
|
150
|
-
"
|
|
151
|
-
"
|
|
152
|
-
"
|
|
153
|
-
"
|
|
154
|
-
"
|
|
155
|
-
"
|
|
156
|
-
"
|
|
157
|
-
"
|
|
364
|
+
"field": "q49TuG_field",
|
|
365
|
+
"status": "q49TuG_status",
|
|
366
|
+
"body": "q49TuG_body",
|
|
367
|
+
"fold": "q49TuG_fold",
|
|
368
|
+
"row": "q49TuG_row",
|
|
369
|
+
"fieldLabel": "q49TuG_fieldLabel",
|
|
370
|
+
"levels": "q49TuG_levels",
|
|
371
|
+
"hint": "q49TuG_hint",
|
|
372
|
+
"levelOffered": "q49TuG_levelOffered",
|
|
373
|
+
"summary": "q49TuG_summary",
|
|
374
|
+
"rowId": "q49TuG_rowId",
|
|
375
|
+
"select": "q49TuG_select",
|
|
376
|
+
"levelRow": "q49TuG_levelRow",
|
|
377
|
+
"footer": "q49TuG_footer",
|
|
378
|
+
"levelGrid": "q49TuG_levelGrid",
|
|
379
|
+
"error": "q49TuG_error",
|
|
380
|
+
"wire": "q49TuG_wire",
|
|
381
|
+
"model": "q49TuG_model"
|
|
158
382
|
};
|
|
159
383
|
//#endregion
|
|
160
|
-
//#region src/client/
|
|
384
|
+
//#region src/client/ModelCapabilityCard.tsx
|
|
161
385
|
/**
|
|
162
|
-
* One pi-ai provider card's
|
|
163
|
-
* Models page's own form does not carry
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
386
|
+
* One pi-ai provider card's model-capability fold: the per-model claims the
|
|
387
|
+
* Models page's own form does not carry — which inputs a model accepts, and
|
|
388
|
+
* which reasoning levels it offers. The fold loads the provider's stored rows
|
|
389
|
+
* when first opened, edits them locally, and writes the whole `models` array
|
|
390
|
+
* back under the revision fence the load answered — the same array semantics
|
|
391
|
+
* the page's own cards use, and the reason both claims live in one fold: two
|
|
392
|
+
* folds would write the same array and fence each other into conflicts. A
|
|
393
|
+
* stored change elsewhere on the page (a model added or removed in the catalog
|
|
394
|
+
* above) reaches the fold through the pushed settings invalidation, so the list
|
|
395
|
+
* it shows never waits for the section to remount.
|
|
167
396
|
*/
|
|
168
397
|
/**
|
|
169
|
-
* Render the
|
|
398
|
+
* Render the model-capability fold of one provider card.
|
|
170
399
|
* @param props - the card's directory row plus the bound face and copy.
|
|
171
400
|
* @returns the fold, or nothing while the provider is still a dormant row.
|
|
172
401
|
*/
|
|
173
|
-
function
|
|
174
|
-
const { provider, configured, t, loadModels, saveModels } = props;
|
|
402
|
+
function ModelCapabilityCard(props) {
|
|
403
|
+
const { provider, configured, t, loadModels, saveModels, subscribeChanges } = props;
|
|
175
404
|
const [status, setStatus] = (0, react.useState)("idle");
|
|
176
405
|
const [view, setView] = (0, react.useState)(void 0);
|
|
177
406
|
const [rows, setRows] = (0, react.useState)([]);
|
|
178
407
|
const [failure, setFailure] = (0, react.useState)(void 0);
|
|
179
408
|
const [saved, setSaved] = (0, react.useState)(false);
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
409
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
410
|
+
const [stale, setStale] = (0, react.useState)(false);
|
|
411
|
+
/** Latest read wins: an older response never overwrites a newer one. */
|
|
412
|
+
const generation = (0, react.useRef)(0);
|
|
413
|
+
/**
|
|
414
|
+
* The newest revision this fold holds adopted data for. An announcement at
|
|
415
|
+
* or below it is old news — the card's own committed write's echo included.
|
|
416
|
+
*/
|
|
417
|
+
const seen = (0, react.useRef)(0);
|
|
418
|
+
/**
|
|
419
|
+
* The newest revision the namespace has announced. A completed read or
|
|
420
|
+
* write compares against it to tell whether a commit outran it mid-flight.
|
|
421
|
+
*/
|
|
422
|
+
const noticed = (0, react.useRef)(0);
|
|
423
|
+
const dirtyRef = (0, react.useRef)(false);
|
|
424
|
+
const savingRef = (0, react.useRef)(false);
|
|
425
|
+
const dirty = view !== void 0 && JSON.stringify(rows) !== JSON.stringify(view.models);
|
|
426
|
+
(0, react.useEffect)(() => {
|
|
427
|
+
dirtyRef.current = dirty;
|
|
428
|
+
}, [dirty]);
|
|
429
|
+
(0, react.useEffect)(() => {
|
|
430
|
+
savingRef.current = status === "saving";
|
|
431
|
+
}, [status]);
|
|
432
|
+
/**
|
|
433
|
+
* Re-read the provider's rows.
|
|
434
|
+
* @param silent - keep the rendered list and copy in place while reading, for
|
|
435
|
+
* a background refresh the user never asked for.
|
|
436
|
+
*/
|
|
437
|
+
const reload = (0, react.useCallback)(async (silent) => {
|
|
438
|
+
const ticket = ++generation.current;
|
|
439
|
+
if (!silent) {
|
|
440
|
+
setStatus("loading");
|
|
441
|
+
setFailure(void 0);
|
|
442
|
+
setSaved(false);
|
|
443
|
+
}
|
|
184
444
|
const loaded = await loadModels(provider);
|
|
445
|
+
if (ticket !== generation.current) return;
|
|
446
|
+
if (silent && (dirtyRef.current || savingRef.current)) {
|
|
447
|
+
setStale(true);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
185
450
|
if (loaded === void 0) {
|
|
451
|
+
if (silent) {
|
|
452
|
+
setStale(true);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
186
455
|
setView(void 0);
|
|
187
456
|
setFailure(t("loadFailed"));
|
|
188
457
|
setStatus("ready");
|
|
189
458
|
return;
|
|
190
459
|
}
|
|
460
|
+
seen.current = loaded.revision;
|
|
191
461
|
setView(loaded);
|
|
192
462
|
setRows(loaded.models.map((row) => ({ ...row })));
|
|
463
|
+
setFailure(void 0);
|
|
464
|
+
setStale(loaded.revision < noticed.current);
|
|
193
465
|
setStatus("ready");
|
|
194
466
|
}, [
|
|
195
467
|
loadModels,
|
|
196
468
|
provider,
|
|
197
469
|
t
|
|
198
470
|
]);
|
|
471
|
+
(0, react.useEffect)(() => {
|
|
472
|
+
if (configured !== true) return void 0;
|
|
473
|
+
return subscribeChanges((revision) => {
|
|
474
|
+
if (revision > noticed.current) noticed.current = revision;
|
|
475
|
+
if (revision <= seen.current) return;
|
|
476
|
+
setStale(true);
|
|
477
|
+
});
|
|
478
|
+
}, [configured, subscribeChanges]);
|
|
479
|
+
(0, react.useEffect)(() => {
|
|
480
|
+
if (stale && open && status === "ready" && !dirty) reload(true);
|
|
481
|
+
}, [
|
|
482
|
+
stale,
|
|
483
|
+
open,
|
|
484
|
+
status,
|
|
485
|
+
dirty,
|
|
486
|
+
view,
|
|
487
|
+
reload
|
|
488
|
+
]);
|
|
199
489
|
if (configured !== true) return null;
|
|
200
|
-
const
|
|
201
|
-
const choose = (index, choice) => {
|
|
490
|
+
const chooseInput = (index, choice) => {
|
|
202
491
|
setRows((current) => current.map((row, at) => at === index ? withImageInput(row, choice) : row));
|
|
203
492
|
};
|
|
493
|
+
const chooseReasoning = (index, choice) => {
|
|
494
|
+
setRows((current) => current.map((row, at) => at === index ? withReasoning(row, choice, reasoningLevels(row)) : row));
|
|
495
|
+
};
|
|
496
|
+
/**
|
|
497
|
+
* Patch one row's declared levels. The edit runs inside the state updater and
|
|
498
|
+
* re-reads the row there, so two controls changed in one batch each see what
|
|
499
|
+
* the previous one wrote.
|
|
500
|
+
* @param index - the row to patch.
|
|
501
|
+
* @param edit - the level-set transformation.
|
|
502
|
+
*/
|
|
503
|
+
const patchLevels = (index, edit) => {
|
|
504
|
+
setRows((current) => current.map((row, at) => at === index ? withReasoning(row, "custom", edit(reasoningLevels(row))) : row));
|
|
505
|
+
};
|
|
506
|
+
const unsavable = rows.some((row) => reasoningFailure(row) !== void 0);
|
|
507
|
+
const locked = view === void 0 || !view.writable || status === "saving";
|
|
204
508
|
const submit = async () => {
|
|
205
|
-
if (view === void 0) return;
|
|
509
|
+
if (view === void 0 || unsavable) return;
|
|
206
510
|
setStatus("saving");
|
|
207
511
|
const outcome = await saveModels(provider, rows, view.revision);
|
|
208
512
|
if (outcome.kind === "written") {
|
|
513
|
+
seen.current = outcome.revision;
|
|
209
514
|
setView({
|
|
210
515
|
...view,
|
|
211
516
|
revision: outcome.revision,
|
|
@@ -213,17 +518,21 @@ window.__ModuleLoader__.load({
|
|
|
213
518
|
fromUser: true
|
|
214
519
|
});
|
|
215
520
|
setSaved(true);
|
|
521
|
+
setFailure(void 0);
|
|
522
|
+
setStale(noticed.current > outcome.revision);
|
|
216
523
|
setStatus("ready");
|
|
217
524
|
return;
|
|
218
525
|
}
|
|
219
526
|
setFailure(outcome.kind === "conflict" ? t("conflict") : outcome.message);
|
|
220
|
-
if (outcome.kind === "conflict") await reload();
|
|
527
|
+
if (outcome.kind === "conflict") await reload(false);
|
|
221
528
|
else setStatus("ready");
|
|
222
529
|
};
|
|
223
530
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
224
531
|
className: styles_module_css_default["fold"],
|
|
225
532
|
onToggle: (event) => {
|
|
226
|
-
|
|
533
|
+
const opened = event.currentTarget.open;
|
|
534
|
+
setOpen(opened);
|
|
535
|
+
if (opened && (status === "idle" || stale && !dirty)) reload(false);
|
|
227
536
|
},
|
|
228
537
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", {
|
|
229
538
|
className: styles_module_css_default["summary"],
|
|
@@ -244,7 +553,7 @@ window.__ModuleLoader__.load({
|
|
|
244
553
|
variant: "ghost",
|
|
245
554
|
size: "sm",
|
|
246
555
|
onClick: () => {
|
|
247
|
-
reload();
|
|
556
|
+
reload(false);
|
|
248
557
|
},
|
|
249
558
|
children: t("retry")
|
|
250
559
|
})
|
|
@@ -257,36 +566,130 @@ window.__ModuleLoader__.load({
|
|
|
257
566
|
className: styles_module_css_default["hint"],
|
|
258
567
|
children: t("inheritsHint")
|
|
259
568
|
}),
|
|
260
|
-
rows.map((row, index) =>
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
className: styles_module_css_default["
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
569
|
+
rows.map((row, index) => {
|
|
570
|
+
const id = rowId(row, index);
|
|
571
|
+
const reasoning = reasoningChoice(row);
|
|
572
|
+
const levels = reasoningLevels(row);
|
|
573
|
+
const invalid = reasoningFailure(row);
|
|
574
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
575
|
+
className: styles_module_css_default["model"],
|
|
576
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
577
|
+
className: styles_module_css_default["row"],
|
|
578
|
+
children: [
|
|
579
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
580
|
+
className: styles_module_css_default["rowId"],
|
|
581
|
+
children: id
|
|
582
|
+
}),
|
|
583
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
584
|
+
className: styles_module_css_default["field"],
|
|
585
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
586
|
+
className: styles_module_css_default["fieldLabel"],
|
|
587
|
+
children: t("inputLabel")
|
|
588
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
589
|
+
className: styles_module_css_default["select"],
|
|
590
|
+
value: imageInputChoice(row),
|
|
591
|
+
"aria-label": `${t("inputLabel")} ${id}`,
|
|
592
|
+
disabled: locked,
|
|
593
|
+
onChange: (event) => {
|
|
594
|
+
const next = parseImageInputChoice(event.target.value);
|
|
595
|
+
if (next !== void 0) chooseInput(index, next);
|
|
596
|
+
},
|
|
597
|
+
children: [
|
|
598
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
599
|
+
value: "inherit",
|
|
600
|
+
children: t("choiceDefault")
|
|
601
|
+
}),
|
|
602
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
603
|
+
value: "text",
|
|
604
|
+
children: t("choiceText")
|
|
605
|
+
}),
|
|
606
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
607
|
+
value: "image",
|
|
608
|
+
children: t("choiceImage")
|
|
609
|
+
})
|
|
610
|
+
]
|
|
611
|
+
})]
|
|
612
|
+
}),
|
|
613
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
614
|
+
className: styles_module_css_default["field"],
|
|
615
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
616
|
+
className: styles_module_css_default["fieldLabel"],
|
|
617
|
+
children: t("reasoningLabel")
|
|
618
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
619
|
+
className: styles_module_css_default["select"],
|
|
620
|
+
value: reasoning,
|
|
621
|
+
"aria-label": `${t("reasoningLabel")} ${id}`,
|
|
622
|
+
disabled: locked,
|
|
623
|
+
onChange: (event) => {
|
|
624
|
+
const next = parseReasoningChoice(event.target.value);
|
|
625
|
+
if (next !== void 0) chooseReasoning(index, next);
|
|
626
|
+
},
|
|
627
|
+
children: [
|
|
628
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
629
|
+
value: "inherit",
|
|
630
|
+
children: t("reasoningInherit")
|
|
631
|
+
}),
|
|
632
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
633
|
+
value: "none",
|
|
634
|
+
children: t("reasoningNone")
|
|
635
|
+
}),
|
|
636
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
637
|
+
value: "custom",
|
|
638
|
+
children: t("reasoningCustom")
|
|
639
|
+
})
|
|
640
|
+
]
|
|
641
|
+
})]
|
|
642
|
+
})
|
|
643
|
+
]
|
|
644
|
+
}), reasoning === "custom" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
645
|
+
className: styles_module_css_default["levels"],
|
|
646
|
+
children: [
|
|
647
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
648
|
+
className: styles_module_css_default["levelGrid"],
|
|
649
|
+
children: THINKING_LEVELS.map((level) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
650
|
+
className: styles_module_css_default["levelRow"],
|
|
651
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
652
|
+
className: styles_module_css_default["levelOffered"],
|
|
653
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
654
|
+
type: "checkbox",
|
|
655
|
+
checked: levels[level].offered,
|
|
656
|
+
disabled: locked,
|
|
657
|
+
onChange: (event) => {
|
|
658
|
+
const offered = event.target.checked;
|
|
659
|
+
patchLevels(index, (current) => toggleLevel(current, level, offered));
|
|
660
|
+
}
|
|
661
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: level })]
|
|
662
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
663
|
+
className: styles_module_css_default["wire"],
|
|
664
|
+
type: "text",
|
|
665
|
+
value: levels[level].wire,
|
|
666
|
+
placeholder: level === "off" ? t("wireNothing") : level,
|
|
667
|
+
"aria-label": `${t("wireLabel")} ${level}`,
|
|
668
|
+
spellCheck: false,
|
|
669
|
+
disabled: locked || !levels[level].offered,
|
|
670
|
+
onChange: (event) => {
|
|
671
|
+
const wire = event.target.value;
|
|
672
|
+
patchLevels(index, (current) => setWire(current, level, wire));
|
|
673
|
+
}
|
|
674
|
+
})]
|
|
675
|
+
}, level))
|
|
676
|
+
}),
|
|
677
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
678
|
+
className: styles_module_css_default["hint"],
|
|
679
|
+
children: t("reasoningHint")
|
|
680
|
+
}),
|
|
681
|
+
invalid === "needsWire" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
682
|
+
className: styles_module_css_default["error"],
|
|
683
|
+
children: `${id}: ${t("needsWire")}`
|
|
684
|
+
}) : null,
|
|
685
|
+
invalid === "needsLevel" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
686
|
+
className: styles_module_css_default["error"],
|
|
687
|
+
children: `${id}: ${t("needsLevel")}`
|
|
688
|
+
}) : null
|
|
689
|
+
]
|
|
690
|
+
}) : null]
|
|
691
|
+
}, index);
|
|
692
|
+
}),
|
|
290
693
|
failure !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
291
694
|
className: styles_module_css_default["error"],
|
|
292
695
|
children: failure
|
|
@@ -297,7 +700,7 @@ window.__ModuleLoader__.load({
|
|
|
297
700
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
298
701
|
variant: "outline",
|
|
299
702
|
size: "sm",
|
|
300
|
-
disabled:
|
|
703
|
+
disabled: locked || !dirty || unsavable,
|
|
301
704
|
onClick: () => {
|
|
302
705
|
submit();
|
|
303
706
|
},
|
|
@@ -308,7 +711,7 @@ window.__ModuleLoader__.load({
|
|
|
308
711
|
size: "sm",
|
|
309
712
|
disabled: status === "saving",
|
|
310
713
|
onClick: () => {
|
|
311
|
-
reload();
|
|
714
|
+
reload(false);
|
|
312
715
|
},
|
|
313
716
|
children: t("retry")
|
|
314
717
|
}) : null,
|
|
@@ -329,19 +732,29 @@ window.__ModuleLoader__.load({
|
|
|
329
732
|
}
|
|
330
733
|
//#endregion
|
|
331
734
|
//#region src/client/locales.ts
|
|
332
|
-
/** Copy dictionaries for the
|
|
735
|
+
/** Copy dictionaries for the model-capability card. */
|
|
333
736
|
/** English strings (the key-set source of truth for this pair). */
|
|
334
737
|
const en = {
|
|
335
|
-
title: "
|
|
738
|
+
title: "Model capabilities",
|
|
336
739
|
loading: "Loading the model list…",
|
|
337
740
|
loadFailed: "Loading the model configuration failed.",
|
|
338
741
|
retry: "Retry",
|
|
339
|
-
empty: "No explicit model list yet — add models in the catalog above, then declare
|
|
742
|
+
empty: "No explicit model list yet — add models in the catalog above, then declare what each one accepts and reasons with here.",
|
|
340
743
|
inheritsHint: "Showing the inherited model list; saving copies it into your user settings.",
|
|
341
744
|
readOnly: "The settings document is read-only in this deployment.",
|
|
745
|
+
inputLabel: "Input modalities",
|
|
342
746
|
choiceDefault: "Provider default",
|
|
343
747
|
choiceText: "Text only",
|
|
344
748
|
choiceImage: "Text and image",
|
|
749
|
+
reasoningLabel: "Reasoning levels",
|
|
750
|
+
reasoningInherit: "Catalog default",
|
|
751
|
+
reasoningNone: "Not a reasoning model",
|
|
752
|
+
reasoningCustom: "Declare levels",
|
|
753
|
+
reasoningHint: "A ticked level is one the model picker offers; the value beside it is the spelling sent on the wire, which a gateway may name its own way. Only off may stay empty — offered, and sent as no parameter at all. A level left unticked is not offered.",
|
|
754
|
+
wireLabel: "Wire value for",
|
|
755
|
+
wireNothing: "send nothing",
|
|
756
|
+
needsLevel: "declare at least one level beyond off, or choose “Not a reasoning model”.",
|
|
757
|
+
needsWire: "every level except off needs the wire value to send.",
|
|
345
758
|
save: "Save",
|
|
346
759
|
saving: "Saving…",
|
|
347
760
|
saved: "Saved. The adapter picks it up on its next request.",
|
|
@@ -349,16 +762,26 @@ window.__ModuleLoader__.load({
|
|
|
349
762
|
};
|
|
350
763
|
/** Chinese strings (same keys as {@link en}). */
|
|
351
764
|
const zh = {
|
|
352
|
-
title: "
|
|
765
|
+
title: "模型能力",
|
|
353
766
|
loading: "正在读取模型列表…",
|
|
354
767
|
loadFailed: "读取模型配置失败。",
|
|
355
768
|
retry: "重试",
|
|
356
|
-
empty: "
|
|
769
|
+
empty: "还没有显式模型列表——请先在上方模型目录中添加模型,再回到这里声明每个模型接受的输入与推理等级。",
|
|
357
770
|
inheritsHint: "当前显示的是继承的模型列表;保存会将其复制到你的用户设置层。",
|
|
358
771
|
readOnly: "当前部署的设置文档为只读。",
|
|
772
|
+
inputLabel: "输入模态",
|
|
359
773
|
choiceDefault: "提供方默认",
|
|
360
774
|
choiceText: "仅文本",
|
|
361
775
|
choiceImage: "文本和图片",
|
|
776
|
+
reasoningLabel: "推理等级",
|
|
777
|
+
reasoningInherit: "目录默认",
|
|
778
|
+
reasoningNone: "非推理模型",
|
|
779
|
+
reasoningCustom: "声明等级",
|
|
780
|
+
reasoningHint: "勾选的等级就是模型选择器会提供的选项;旁边的值是实际发到网关的拼写,网关可以有自己的叫法。只有 off 可以留空——表示提供该等级但完全不发送参数。未勾选的等级不会提供。",
|
|
781
|
+
wireLabel: "发送值:",
|
|
782
|
+
wireNothing: "不发送",
|
|
783
|
+
needsLevel: "至少声明一个 off 以外的等级,或选择「非推理模型」。",
|
|
784
|
+
needsWire: "除 off 外,每个勾选的等级都要填写发送值。",
|
|
362
785
|
save: "保存",
|
|
363
786
|
saving: "保存中…",
|
|
364
787
|
saved: "已保存。适配器会在下一次请求时生效。",
|
|
@@ -367,7 +790,7 @@ window.__ModuleLoader__.load({
|
|
|
367
790
|
//#endregion
|
|
368
791
|
//#region src/client/index.ts
|
|
369
792
|
/** Dictionary namespace owned by this plugin. */
|
|
370
|
-
const NS = "settings.models.
|
|
793
|
+
const NS = "settings.models.modelCapabilities";
|
|
371
794
|
/** The effect label prefix. */
|
|
372
795
|
const PKG = "@jcy2387/dsh-models-input-modalities";
|
|
373
796
|
/** Required browser services. */
|
|
@@ -378,7 +801,7 @@ window.__ModuleLoader__.load({
|
|
|
378
801
|
"remote.settings"
|
|
379
802
|
];
|
|
380
803
|
/**
|
|
381
|
-
* Register the
|
|
804
|
+
* Register the model-capability fold on every llm-pi-ai provider card once the
|
|
382
805
|
* Models section has declared the seat.
|
|
383
806
|
* @param ctx - the plugin's client context.
|
|
384
807
|
*/
|
|
@@ -387,17 +810,19 @@ window.__ModuleLoader__.load({
|
|
|
387
810
|
zh,
|
|
388
811
|
en
|
|
389
812
|
}), `${PKG}: dictionaries`);
|
|
390
|
-
const controller = new
|
|
813
|
+
const controller = new ModelCapabilityController(ctx);
|
|
814
|
+
ctx.effect(() => controller.watch(), `${PKG}: settings invalidations`);
|
|
391
815
|
const face = {
|
|
392
816
|
loadModels: (entry) => controller.load(entry),
|
|
393
|
-
saveModels: (entry, models, revision) => controller.save(entry, models, revision)
|
|
817
|
+
saveModels: (entry, models, revision) => controller.save(entry, models, revision),
|
|
818
|
+
subscribeChanges: (listener) => controller.subscribe(listener)
|
|
394
819
|
};
|
|
395
820
|
ctx.slots.inject("settings.models.provider-card", () => ctx.slots.register({
|
|
396
821
|
name: "settings.models.provider-card",
|
|
397
822
|
key: "llm-pi-ai",
|
|
398
823
|
locale: NS,
|
|
399
824
|
inject: () => face
|
|
400
|
-
},
|
|
825
|
+
}, ModelCapabilityCard));
|
|
401
826
|
}
|
|
402
827
|
//#endregion
|
|
403
828
|
exports.apply = apply;
|