dsh-plugin-effort-declare 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/CONTRIBUTING.en.md +5 -1
- package/CONTRIBUTING.md +5 -1
- package/INSTALL.en.md +35 -5
- package/INSTALL.md +35 -5
- package/README.en.md +17 -3
- package/README.md +17 -3
- package/lib/client.js +251 -79
- package/lib/client.js.map +1 -1
- package/lib/types/client/EffortDeclareSection.d.ts +2 -3
- package/lib/types/client/EffortDeclareSection.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/load-drafts.d.ts +3 -0
- package/lib/types/client/load-drafts.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/schema-ops.d.ts +10 -0
- package/lib/types/client/schema-ops.d.ts.map +1 -1
- package/lib/types/core/catalog.d.ts +6 -4
- package/lib/types/core/catalog.d.ts.map +1 -1
- package/lib/types/core/drafts.d.ts +34 -2
- package/lib/types/core/drafts.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/EffortDeclareSection.tsx +85 -41
- package/src/client/README.en.md +4 -4
- package/src/client/README.md +4 -4
- package/src/client/index.ts +10 -3
- package/src/client/load-drafts.ts +7 -5
- package/src/client/locales.ts +5 -2
- package/src/client/schema-ops.ts +33 -0
- package/src/core/README.en.md +2 -2
- package/src/core/README.md +2 -2
- package/src/core/catalog.ts +6 -4
- package/src/core/drafts.ts +151 -4
package/lib/client.js
CHANGED
|
@@ -12,9 +12,11 @@ window.__ModuleLoader__.load({
|
|
|
12
12
|
*
|
|
13
13
|
* Levels match `@deepseek-ai/dsh-llm-pi-ai` catalog.ts `THINKING_LEVELS`.
|
|
14
14
|
* Formats match `SUPPORTED_THINKING_FORMATS` in the same file (rc.8).
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* Tests pin these lists against a checked-in schema fixture
|
|
16
|
+
* (`tests/fixtures/pi-ai-thinking-format-union.ts`) and the local level
|
|
17
|
+
* whitelist. The settings page never offers the handwritten thinkingFormat
|
|
18
|
+
* list as writable choices — only the live schema union, plus a stored
|
|
19
|
+
* value that the union omitted.
|
|
18
20
|
*/
|
|
19
21
|
/** Selectable reasoning levels, in pi-ai escalation order. */
|
|
20
22
|
const THINKING_LEVELS = [
|
|
@@ -28,22 +30,6 @@ window.__ModuleLoader__.load({
|
|
|
28
30
|
];
|
|
29
31
|
/** Thinking levels other than Off; Off has its own tri-state control. */
|
|
30
32
|
const THINKING_LEVELS_WITHOUT_OFF = THINKING_LEVELS.filter((level) => level !== "off");
|
|
31
|
-
/**
|
|
32
|
-
* openai-completions thinkingFormat values from llm-pi-ai catalog.ts rc.8.
|
|
33
|
-
* Tests pin this list; the settings page prefers schema union choices.
|
|
34
|
-
*/
|
|
35
|
-
const FALLBACK_THINKING_FORMATS = [
|
|
36
|
-
"openai",
|
|
37
|
-
"deepseek",
|
|
38
|
-
"openrouter",
|
|
39
|
-
"together",
|
|
40
|
-
"zai",
|
|
41
|
-
"qwen",
|
|
42
|
-
"chat-template",
|
|
43
|
-
"qwen-chat-template",
|
|
44
|
-
"string-thinking",
|
|
45
|
-
"ant-ling"
|
|
46
|
-
];
|
|
47
33
|
/** Wire protocol this plugin's v1 editor supports. */
|
|
48
34
|
const OPENAI_COMPLETIONS = "openai-completions";
|
|
49
35
|
/** Settings namespace this plugin writes. */
|
|
@@ -172,6 +158,14 @@ window.__ModuleLoader__.load({
|
|
|
172
158
|
* Route-card draft merge: user-layer slices, namespace revision, dirty preserve.
|
|
173
159
|
* No React — settings UI and tests share these helpers.
|
|
174
160
|
*/
|
|
161
|
+
/** JSON-stable equality matching pathOps (key order included). */
|
|
162
|
+
function sliceEqual(left, right) {
|
|
163
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
164
|
+
}
|
|
165
|
+
/** Whether two settings slices differ. */
|
|
166
|
+
function sliceChanged(before, after) {
|
|
167
|
+
return !sliceEqual(before, after);
|
|
168
|
+
}
|
|
175
169
|
/** Build a draft from the stored user subtree (never from effective `value`). */
|
|
176
170
|
function routeDraftFromUserProfile(args) {
|
|
177
171
|
const { provider, displayName, settingsPath, revision, userProfile } = args;
|
|
@@ -226,9 +220,103 @@ window.__ModuleLoader__.load({
|
|
|
226
220
|
});
|
|
227
221
|
});
|
|
228
222
|
}
|
|
223
|
+
function modelRowId(row) {
|
|
224
|
+
return String(row.id);
|
|
225
|
+
}
|
|
226
|
+
function indexById(rows) {
|
|
227
|
+
const map = /* @__PURE__ */ new Map();
|
|
228
|
+
for (const row of rows) {
|
|
229
|
+
const id = modelRowId(row);
|
|
230
|
+
if (!map.has(id)) map.set(id, row);
|
|
231
|
+
}
|
|
232
|
+
return map;
|
|
233
|
+
}
|
|
234
|
+
function effortsPresence(row) {
|
|
235
|
+
if (row === void 0 || !Object.hasOwn(row, "reasoningEfforts")) return {
|
|
236
|
+
present: false,
|
|
237
|
+
value: void 0
|
|
238
|
+
};
|
|
239
|
+
return {
|
|
240
|
+
present: true,
|
|
241
|
+
value: row.reasoningEfforts
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function effortsEqual(left, right) {
|
|
245
|
+
if (left.present !== right.present) return false;
|
|
246
|
+
if (!left.present) return true;
|
|
247
|
+
return sliceEqual(left.value, right.value);
|
|
248
|
+
}
|
|
249
|
+
function overlayLocalEfforts(incomingRow, prevRow) {
|
|
250
|
+
const next = structuredClone(incomingRow);
|
|
251
|
+
if (Object.hasOwn(prevRow, "reasoningEfforts")) next.reasoningEfforts = structuredClone(prevRow.reasoningEfforts);
|
|
252
|
+
else delete next.reasoningEfforts;
|
|
253
|
+
return next;
|
|
254
|
+
}
|
|
255
|
+
function objectKeyChanged(left, right, key) {
|
|
256
|
+
const leftHas = Object.hasOwn(left, key);
|
|
257
|
+
if (leftHas !== Object.hasOwn(right, key)) return true;
|
|
258
|
+
if (!leftHas) return false;
|
|
259
|
+
return sliceChanged(left[key], right[key]);
|
|
260
|
+
}
|
|
229
261
|
/**
|
|
230
|
-
*
|
|
231
|
-
*
|
|
262
|
+
* Membership follows the latest user-layer models list (Models page add/delete).
|
|
263
|
+
* Local unsaved `reasoningEfforts` (including a cleared key) overlay by id.
|
|
264
|
+
*/
|
|
265
|
+
function mergeModelsById(args) {
|
|
266
|
+
const prevById = indexById(args.prevModels);
|
|
267
|
+
const prevOrigById = indexById(args.prevOriginal);
|
|
268
|
+
const incomingOrigById = indexById(args.incomingOriginal);
|
|
269
|
+
const incomingIds = new Set(args.incomingModels.map(modelRowId));
|
|
270
|
+
let conflicted = false;
|
|
271
|
+
const models = args.incomingModels.map((incomingRow) => {
|
|
272
|
+
const id = modelRowId(incomingRow);
|
|
273
|
+
const prevRow = prevById.get(id);
|
|
274
|
+
if (prevRow === void 0) return structuredClone(incomingRow);
|
|
275
|
+
const prevOrig = prevOrigById.get(id);
|
|
276
|
+
if (!!effortsEqual(effortsPresence(prevRow), effortsPresence(prevOrig))) return structuredClone(incomingRow);
|
|
277
|
+
const incomingOrig = incomingOrigById.get(id);
|
|
278
|
+
if (!effortsEqual(effortsPresence(prevOrig), effortsPresence(incomingOrig))) conflicted = true;
|
|
279
|
+
return overlayLocalEfforts(incomingRow, prevRow);
|
|
280
|
+
});
|
|
281
|
+
for (const [id, prevRow] of prevById) {
|
|
282
|
+
if (incomingIds.has(id)) continue;
|
|
283
|
+
const prevOrig = prevOrigById.get(id);
|
|
284
|
+
if (effortsEqual(effortsPresence(prevRow), effortsPresence(prevOrig))) continue;
|
|
285
|
+
if (!effortsEqual(effortsPresence(prevOrig), effortsPresence(incomingOrigById.get(id)))) conflicted = true;
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
models,
|
|
289
|
+
conflicted
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Three-way compat merge: locally changed keys stay local; everything else
|
|
294
|
+
* follows incoming. Conflict only when a locally dirty key also moved in originals.
|
|
295
|
+
*/
|
|
296
|
+
function mergeCompat(args) {
|
|
297
|
+
if (!sliceChanged(args.prev, args.prevOriginal)) return {
|
|
298
|
+
compat: cloneObject(args.incoming),
|
|
299
|
+
conflicted: false
|
|
300
|
+
};
|
|
301
|
+
const compat = cloneObject(args.incoming);
|
|
302
|
+
let conflicted = false;
|
|
303
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(args.prev), ...Object.keys(args.prevOriginal)]);
|
|
304
|
+
for (const key of keys) {
|
|
305
|
+
if (!objectKeyChanged(args.prev, args.prevOriginal, key)) continue;
|
|
306
|
+
if (objectKeyChanged(args.prevOriginal, args.incomingOriginal, key)) conflicted = true;
|
|
307
|
+
if (Object.hasOwn(args.prev, key)) compat[key] = structuredClone(args.prev[key]);
|
|
308
|
+
else delete compat[key];
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
compat,
|
|
312
|
+
conflicted
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Apply a freshly loaded table. Membership and metadata follow incoming;
|
|
317
|
+
* unsaved reasoningEfforts / dirty compat keys overlay by id. Conflict only
|
|
318
|
+
* when a locally dirty field also changed in originals (revision-only bumps
|
|
319
|
+
* and sibling-card saves do not warn).
|
|
232
320
|
*/
|
|
233
321
|
function mergeLoadedDrafts(current, incoming, options) {
|
|
234
322
|
const currentByProvider = new Map(current.map((draft) => [draft.provider, draft]));
|
|
@@ -237,13 +325,27 @@ window.__ModuleLoader__.load({
|
|
|
237
325
|
drafts: incoming.map((next) => {
|
|
238
326
|
const prev = currentByProvider.get(next.provider);
|
|
239
327
|
if (prev === void 0 || !options.preserveDirty || !draftDirty(prev)) return next;
|
|
240
|
-
|
|
328
|
+
const modelsMerge = mergeModelsById({
|
|
329
|
+
prevModels: prev.models,
|
|
330
|
+
prevOriginal: prev.originalModels,
|
|
331
|
+
incomingModels: next.models,
|
|
332
|
+
incomingOriginal: next.originalModels
|
|
333
|
+
});
|
|
334
|
+
const compatMerge = mergeCompat({
|
|
335
|
+
prev: prev.compat,
|
|
336
|
+
prevOriginal: prev.originalCompat,
|
|
337
|
+
incoming: next.compat,
|
|
338
|
+
incomingOriginal: next.originalCompat
|
|
339
|
+
});
|
|
340
|
+
if (modelsMerge.conflicted || compatMerge.conflicted) conflicted.push(next.provider);
|
|
241
341
|
return {
|
|
242
|
-
|
|
342
|
+
provider: next.provider,
|
|
243
343
|
displayName: next.displayName,
|
|
244
344
|
settingsPath: next.settingsPath,
|
|
245
345
|
revision: next.revision,
|
|
346
|
+
models: modelsMerge.models,
|
|
246
347
|
originalModels: cloneModels(next.originalModels),
|
|
348
|
+
compat: compatMerge.compat,
|
|
247
349
|
originalCompat: cloneObject(next.originalCompat),
|
|
248
350
|
compatPresent: next.compatPresent
|
|
249
351
|
};
|
|
@@ -434,25 +536,28 @@ window.__ModuleLoader__.load({
|
|
|
434
536
|
/**
|
|
435
537
|
* First paint: `ensure()` (reads only from idle). Never treat ensure as refresh.
|
|
436
538
|
* Callers that must not apply a stale settlement compare generation themselves.
|
|
539
|
+
*
|
|
540
|
+
* `formats` is the live schema union only. Empty means the dropdown has no
|
|
541
|
+
* writable choices (stored values stay visible via `thinkingFormatChoices`).
|
|
437
542
|
*/
|
|
438
543
|
async function loadDrafts(api, describe, schema) {
|
|
439
544
|
await describe.ensure();
|
|
440
545
|
const mirrored = describe.getSnapshot();
|
|
441
546
|
if (mirrored.view === void 0) return {
|
|
442
547
|
writable: false,
|
|
443
|
-
formats: [
|
|
548
|
+
formats: [],
|
|
444
549
|
drafts: [],
|
|
445
550
|
error: mirrored.error ?? void 0
|
|
446
551
|
};
|
|
447
552
|
const providersResponse = await api.llm.providers({});
|
|
448
553
|
if (!providersResponse.result.ok) return {
|
|
449
554
|
writable: mirrored.view.writable,
|
|
450
|
-
formats: [
|
|
555
|
+
formats: [],
|
|
451
556
|
drafts: [],
|
|
452
557
|
error: providersResponse.result.error.message
|
|
453
558
|
};
|
|
454
559
|
const pi = new Map(mirrored.view.namespaces.map((view) => [view.ns, view])).get(LLM_PI_AI_NS);
|
|
455
|
-
let formats = [
|
|
560
|
+
let formats = [];
|
|
456
561
|
let schemaDefaultApi;
|
|
457
562
|
if (pi !== void 0) try {
|
|
458
563
|
const root = schema.rehydrate(pi.schema);
|
|
@@ -488,6 +593,36 @@ window.__ModuleLoader__.load({
|
|
|
488
593
|
};
|
|
489
594
|
}
|
|
490
595
|
//#endregion
|
|
596
|
+
//#region src/client/schema-ops.ts
|
|
597
|
+
/** Wrap a live settingsSchema service as plain callbacks. */
|
|
598
|
+
function bindSchema(service) {
|
|
599
|
+
return {
|
|
600
|
+
rehydrate: (serialized) => service.rehydrate(serialized),
|
|
601
|
+
nodeAtPath: (root, path) => service.nodeAtPath(root, path),
|
|
602
|
+
getPath: (value, path) => service.getPath(value, path),
|
|
603
|
+
hasPath: (value, path) => service.hasPath(value, path),
|
|
604
|
+
validate: (node, draft) => service.validate(node, draft)
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Pre-mutate schema check used by the settings page.
|
|
609
|
+
* A returned string means do not call `settings.mutate`.
|
|
610
|
+
*/
|
|
611
|
+
function validateSaveDraft(schema, root, settingsPath, afterModels, afterCompat, willWriteCompat) {
|
|
612
|
+
const modelsNode = schema.nodeAtPath(root, [...settingsPath, "models"]);
|
|
613
|
+
if (modelsNode !== void 0) {
|
|
614
|
+
const error = schema.validate(modelsNode, afterModels);
|
|
615
|
+
if (error !== void 0) return error;
|
|
616
|
+
}
|
|
617
|
+
if (willWriteCompat) {
|
|
618
|
+
const compatNode = schema.nodeAtPath(root, [...settingsPath, "compat"]);
|
|
619
|
+
if (compatNode !== void 0) {
|
|
620
|
+
const error = schema.validate(compatNode, afterCompat);
|
|
621
|
+
if (error !== void 0) return error;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
//#endregion
|
|
491
626
|
//#region \0dsh-css:C:\Users\zimo\AppData\Roaming\io.github.hairyf.deepseek-harness-desktop\data\dsh\临时目录\dsh-plugin-effort-declare\src\client\effort-declare.module.css.mjs
|
|
492
627
|
const cssText = ".hYEBUa_section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}.hYEBUa_title{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;font-weight:500;line-height:24px}.hYEBUa_intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}.hYEBUa_notice{color:var(--dsw-alias-state-warn-label);margin:0;font-size:12px;line-height:18px}.hYEBUa_savedNotice{color:var(--dsw-alias-state-success-primary);margin:0;font-size:12px;line-height:18px}.hYEBUa_error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:18px}.hYEBUa_rows{flex-direction:column;gap:8px;margin:12px 0 0;padding:0;list-style:none;display:flex}.hYEBUa_rowCard{border:1px solid var(--dsw-alias-border-l2);border-radius:12px;flex-direction:column;gap:12px;padding:12px 14px;display:flex}.hYEBUa_rowHead{align-items:baseline;gap:8px;display:flex}.hYEBUa_rowName{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.hYEBUa_rowTag{border:1px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-secondary);border-radius:4px;flex:none;padding:1px 6px;font-size:11px;line-height:16px}.hYEBUa_compatSummary{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.hYEBUa_presetRow,.hYEBUa_actions{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.hYEBUa_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px}.hYEBUa_primaryButton,.hYEBUa_secondaryButton{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}.hYEBUa_primaryButton{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);border:none}.hYEBUa_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.hYEBUa_secondaryButton{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:0 0}.hYEBUa_secondaryButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.hYEBUa_primaryButton:disabled,.hYEBUa_secondaryButton:disabled,.hYEBUa_linkButton:disabled,.hYEBUa_input:disabled{opacity:.4;cursor:default}.hYEBUa_primaryButton:focus-visible,.hYEBUa_secondaryButton:focus-visible,.hYEBUa_linkButton:focus-visible,.hYEBUa_input:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}.hYEBUa_linkButton{box-sizing:border-box;height:28px;color:var(--dsw-alias-label-tertiary);font:inherit;cursor:pointer;background:0 0;border:none;border-radius:14px;align-items:center;padding:0 10px;font-size:12px;line-height:18px;display:inline-flex}.hYEBUa_linkButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.hYEBUa_modelEntry{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:8px;padding:10px 0;display:flex}.hYEBUa_modelHead{flex-wrap:wrap;align-items:baseline;gap:8px;display:flex}.hYEBUa_modelId{font-size:13px;font-weight:500;line-height:20px}.hYEBUa_modelName{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.hYEBUa_levels{flex-wrap:wrap;gap:10px 14px;display:flex}.hYEBUa_level{align-items:center;gap:6px;font-size:12px;line-height:18px;display:inline-flex}.hYEBUa_wireRow{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.hYEBUa_input,.hYEBUa_selectInput{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);height:32px;color:var(--dsw-alias-label-primary);font:inherit;border-radius:8px;padding:0 10px;font-size:13px}.hYEBUa_wireInput{width:7em}.hYEBUa_offGroup{flex-direction:column;gap:6px;display:flex}.hYEBUa_advanced{border-top:1px solid var(--dsw-alias-border-l2);padding-top:8px}.hYEBUa_advanced summary{cursor:pointer;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.hYEBUa_advancedBody{flex-direction:column;gap:10px;padding-top:10px;display:flex}.hYEBUa_check{align-items:flex-start;gap:8px;font-size:12px;line-height:18px;display:flex}";
|
|
493
628
|
const cssTagId = "dsh-plugin-effort-declare/effort-declare.module.css";
|
|
@@ -653,10 +788,10 @@ window.__ModuleLoader__.load({
|
|
|
653
788
|
});
|
|
654
789
|
}
|
|
655
790
|
function RouteCard(props) {
|
|
656
|
-
const { draft, writable, busy,
|
|
791
|
+
const { draft, writable, busy, saveLocked, t, onChange } = props;
|
|
657
792
|
const noModels = draft.models.length === 0;
|
|
658
793
|
const editDisabled = !writable || busy || noModels;
|
|
659
|
-
const
|
|
794
|
+
const saveDisabled = saveLocked || noModels;
|
|
660
795
|
const formats = thinkingFormatChoices(props.formats, draft.compat.thinkingFormat);
|
|
661
796
|
const summary = compatSummary(draft.compat);
|
|
662
797
|
const sameWire = draft.compat.supportsReasoningEffort === false;
|
|
@@ -840,7 +975,7 @@ window.__ModuleLoader__.load({
|
|
|
840
975
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
841
976
|
type: "button",
|
|
842
977
|
className: effort_declare_module_css_default.primaryButton,
|
|
843
|
-
disabled:
|
|
978
|
+
disabled: saveDisabled || !dirty || clientError !== void 0,
|
|
844
979
|
onClick: () => {
|
|
845
980
|
props.onSave(draft);
|
|
846
981
|
},
|
|
@@ -872,12 +1007,12 @@ window.__ModuleLoader__.load({
|
|
|
872
1007
|
return;
|
|
873
1008
|
}
|
|
874
1009
|
const generation = nextGeneration(generationRef);
|
|
875
|
-
setStatus("loading");
|
|
1010
|
+
if (draftsRef.current.length === 0) setStatus("loading");
|
|
876
1011
|
setError("");
|
|
877
1012
|
loadDrafts(api, describe, schema).then((result) => {
|
|
878
1013
|
if (!generationIsCurrent(generationRef, generation)) return;
|
|
879
1014
|
setWritable(result.writable);
|
|
880
|
-
|
|
1015
|
+
setFormats(result.formats);
|
|
881
1016
|
if (result.error !== void 0) {
|
|
882
1017
|
setStatus("error");
|
|
883
1018
|
setError(result.error);
|
|
@@ -885,10 +1020,14 @@ window.__ModuleLoader__.load({
|
|
|
885
1020
|
}
|
|
886
1021
|
const merged = mergeLoadedDrafts(draftsRef.current, result.drafts, { preserveDirty });
|
|
887
1022
|
setDrafts(merged.drafts);
|
|
888
|
-
if (merged.conflicted.length > 0) setNotices(
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1023
|
+
if (merged.conflicted.length > 0) setNotices((current) => {
|
|
1024
|
+
const next = { ...current };
|
|
1025
|
+
for (const provider of merged.conflicted) next[provider] = {
|
|
1026
|
+
kind: "conflict",
|
|
1027
|
+
text: t("dirtyConflict")
|
|
1028
|
+
};
|
|
1029
|
+
return next;
|
|
1030
|
+
});
|
|
892
1031
|
setStatus("ready");
|
|
893
1032
|
}, (failure) => {
|
|
894
1033
|
if (!generationIsCurrent(generationRef, generation)) return;
|
|
@@ -906,23 +1045,46 @@ window.__ModuleLoader__.load({
|
|
|
906
1045
|
}, [reload]);
|
|
907
1046
|
(0, react.useEffect)(() => {
|
|
908
1047
|
if (props.subscribeInvalidate === void 0) return void 0;
|
|
909
|
-
return props.subscribeInvalidate(() => {
|
|
910
|
-
|
|
1048
|
+
return props.subscribeInvalidate((source) => {
|
|
1049
|
+
if (source === "writable") {
|
|
1050
|
+
const view = describe?.getSnapshot().view;
|
|
1051
|
+
if (view !== void 0) setWritable(view.writable);
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
if (source === "settings" || source === "directory") reload(true);
|
|
1055
|
+
});
|
|
1056
|
+
}, [
|
|
1057
|
+
describe,
|
|
1058
|
+
props.subscribeInvalidate,
|
|
1059
|
+
reload
|
|
1060
|
+
]);
|
|
1061
|
+
const patchNotice = (provider, notice) => {
|
|
1062
|
+
setNotices((current) => {
|
|
1063
|
+
const copy = { ...current };
|
|
1064
|
+
if (notice === void 0) delete copy[provider];
|
|
1065
|
+
else copy[provider] = notice;
|
|
1066
|
+
return copy;
|
|
911
1067
|
});
|
|
912
|
-
}
|
|
1068
|
+
};
|
|
913
1069
|
const save = async (draft) => {
|
|
914
|
-
if (api === void 0 || describe === void 0) return;
|
|
915
|
-
if (status === "loading" || busyRoute !== null)
|
|
1070
|
+
if (api === void 0 || describe === void 0 || schema === void 0) return;
|
|
1071
|
+
if (status === "loading" || busyRoute !== null) {
|
|
1072
|
+
patchNotice(draft.provider, {
|
|
1073
|
+
kind: "error",
|
|
1074
|
+
text: t("saveBusy")
|
|
1075
|
+
});
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
916
1078
|
const blocking = draft.models.map((row) => errorText(modelEffortError(row), t)).find((text) => text !== void 0);
|
|
917
1079
|
if (blocking !== void 0) {
|
|
918
|
-
|
|
1080
|
+
patchNotice(draft.provider, {
|
|
919
1081
|
kind: "error",
|
|
920
1082
|
text: blocking
|
|
921
|
-
}
|
|
1083
|
+
});
|
|
922
1084
|
return;
|
|
923
1085
|
}
|
|
924
1086
|
setBusyRoute(draft.provider);
|
|
925
|
-
|
|
1087
|
+
patchNotice(draft.provider, void 0);
|
|
926
1088
|
try {
|
|
927
1089
|
const ops = buildSaveOps({
|
|
928
1090
|
settingsPath: draft.settingsPath,
|
|
@@ -935,16 +1097,38 @@ window.__ModuleLoader__.load({
|
|
|
935
1097
|
setDrafts((current) => current.map((row) => row.provider === draft.provider ? alignDraft(row) : row));
|
|
936
1098
|
return;
|
|
937
1099
|
}
|
|
1100
|
+
const willWriteCompat = ops.some((op) => op.path.length > draft.settingsPath.length && op.path[draft.settingsPath.length] === "compat");
|
|
1101
|
+
const pi = describe.getSnapshot().view?.namespaces.find((view) => view.ns === LLM_PI_AI_NS);
|
|
1102
|
+
if (pi !== void 0) {
|
|
1103
|
+
let root;
|
|
1104
|
+
try {
|
|
1105
|
+
root = schema.rehydrate(pi.schema);
|
|
1106
|
+
} catch {
|
|
1107
|
+
root = void 0;
|
|
1108
|
+
}
|
|
1109
|
+
if (root !== void 0) {
|
|
1110
|
+
const schemaError = validateSaveDraft(schema, root, draft.settingsPath, draft.models, draft.compat, willWriteCompat);
|
|
1111
|
+
if (schemaError !== void 0) {
|
|
1112
|
+
patchNotice(draft.provider, {
|
|
1113
|
+
kind: "error",
|
|
1114
|
+
text: schemaError
|
|
1115
|
+
});
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
938
1120
|
const response = await api.settings.mutate({
|
|
939
1121
|
ns: LLM_PI_AI_NS,
|
|
940
1122
|
ops,
|
|
941
1123
|
expectedRevision: draft.revision
|
|
942
1124
|
});
|
|
943
1125
|
if (!response.result.ok) {
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1126
|
+
const conflict = response.result.error.code === "settings-conflict";
|
|
1127
|
+
patchNotice(draft.provider, {
|
|
1128
|
+
kind: conflict ? "conflict" : "error",
|
|
1129
|
+
text: conflict ? t("conflict") : response.result.error.message
|
|
1130
|
+
});
|
|
1131
|
+
if (conflict) reload(true);
|
|
948
1132
|
return;
|
|
949
1133
|
}
|
|
950
1134
|
const view = response.result.value;
|
|
@@ -953,15 +1137,15 @@ window.__ModuleLoader__.load({
|
|
|
953
1137
|
user: view.user ?? {},
|
|
954
1138
|
revision: view.revision
|
|
955
1139
|
}));
|
|
956
|
-
|
|
1140
|
+
patchNotice(draft.provider, {
|
|
957
1141
|
kind: "saved",
|
|
958
1142
|
text: t("saved")
|
|
959
|
-
}
|
|
1143
|
+
});
|
|
960
1144
|
} catch (failure) {
|
|
961
|
-
|
|
1145
|
+
patchNotice(draft.provider, {
|
|
962
1146
|
kind: "error",
|
|
963
1147
|
text: failure instanceof Error ? failure.message : t("loadError")
|
|
964
|
-
}
|
|
1148
|
+
});
|
|
965
1149
|
} finally {
|
|
966
1150
|
setBusyRoute(null);
|
|
967
1151
|
}
|
|
@@ -969,6 +1153,8 @@ window.__ModuleLoader__.load({
|
|
|
969
1153
|
const showLoading = status === "loading" && drafts.length === 0;
|
|
970
1154
|
const showEmpty = status === "ready" && drafts.length === 0;
|
|
971
1155
|
const showList = drafts.length > 0;
|
|
1156
|
+
const hasCardFailure = Object.values(notices).some((notice) => notice.kind === "conflict" || notice.kind === "error");
|
|
1157
|
+
const showReload = status === "error" || hasCardFailure || showEmpty || !writable || status === "loading" || showList;
|
|
972
1158
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
973
1159
|
className: effort_declare_module_css_default.section,
|
|
974
1160
|
children: [
|
|
@@ -992,7 +1178,7 @@ window.__ModuleLoader__.load({
|
|
|
992
1178
|
className: effort_declare_module_css_default.error,
|
|
993
1179
|
children: error
|
|
994
1180
|
}) : null,
|
|
995
|
-
|
|
1181
|
+
showReload ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
996
1182
|
type: "button",
|
|
997
1183
|
className: effort_declare_module_css_default.secondaryButton,
|
|
998
1184
|
onClick: () => {
|
|
@@ -1014,26 +1200,18 @@ window.__ModuleLoader__.load({
|
|
|
1014
1200
|
formats: formats.length > 0 ? formats : [],
|
|
1015
1201
|
writable,
|
|
1016
1202
|
busy: busyRoute === draft.provider,
|
|
1017
|
-
|
|
1203
|
+
saveLocked: !writable || busyRoute !== null || status === "loading",
|
|
1018
1204
|
notice: notices[draft.provider],
|
|
1019
1205
|
t,
|
|
1020
1206
|
onChange: (next) => {
|
|
1021
|
-
|
|
1022
|
-
const copy = { ...current };
|
|
1023
|
-
delete copy[next.provider];
|
|
1024
|
-
return copy;
|
|
1025
|
-
});
|
|
1207
|
+
patchNotice(next.provider, void 0);
|
|
1026
1208
|
setDrafts((current) => current.map((row) => row.provider === next.provider ? next : row));
|
|
1027
1209
|
},
|
|
1028
1210
|
onSave: (next) => {
|
|
1029
1211
|
save(next);
|
|
1030
1212
|
},
|
|
1031
1213
|
onCancel: (next) => {
|
|
1032
|
-
|
|
1033
|
-
const copy = { ...current };
|
|
1034
|
-
delete copy[next.provider];
|
|
1035
|
-
return copy;
|
|
1036
|
-
});
|
|
1214
|
+
patchNotice(next.provider, void 0);
|
|
1037
1215
|
setDrafts((current) => current.map((row) => row.provider === next.provider ? {
|
|
1038
1216
|
...row,
|
|
1039
1217
|
models: cloneModels(row.originalModels),
|
|
@@ -1046,17 +1224,6 @@ window.__ModuleLoader__.load({
|
|
|
1046
1224
|
});
|
|
1047
1225
|
}
|
|
1048
1226
|
//#endregion
|
|
1049
|
-
//#region src/client/schema-ops.ts
|
|
1050
|
-
/** Wrap a live settingsSchema service as plain callbacks. */
|
|
1051
|
-
function bindSchema(service) {
|
|
1052
|
-
return {
|
|
1053
|
-
rehydrate: (serialized) => service.rehydrate(serialized),
|
|
1054
|
-
nodeAtPath: (root, path) => service.nodeAtPath(root, path),
|
|
1055
|
-
getPath: (value, path) => service.getPath(value, path),
|
|
1056
|
-
hasPath: (value, path) => service.hasPath(value, path)
|
|
1057
|
-
};
|
|
1058
|
-
}
|
|
1059
|
-
//#endregion
|
|
1060
1227
|
//#region src/client/locales.ts
|
|
1061
1228
|
const NS = "plugin-effort-declare";
|
|
1062
1229
|
const zh = {
|
|
@@ -1070,6 +1237,7 @@ window.__ModuleLoader__.load({
|
|
|
1070
1237
|
readOnly: "当前设置为只读,无法保存。",
|
|
1071
1238
|
save: "保存",
|
|
1072
1239
|
saving: "保存中…",
|
|
1240
|
+
saveBusy: "另有路由正在保存,请稍候。",
|
|
1073
1241
|
cancel: "取消",
|
|
1074
1242
|
saved: "已保存。对话选择器会按新的能力声明显示 Effort 行。",
|
|
1075
1243
|
conflict: "设置已被其他地方改过,请重新加载后再保存。",
|
|
@@ -1090,7 +1258,7 @@ window.__ModuleLoader__.load({
|
|
|
1090
1258
|
clear: "清除本模型声明",
|
|
1091
1259
|
advanced: "高级:协议方言",
|
|
1092
1260
|
thinkingFormat: "thinkingFormat",
|
|
1093
|
-
thinkingFormatDefault: "
|
|
1261
|
+
thinkingFormatDefault: "默认(省略该键)",
|
|
1094
1262
|
supportsDeveloperRole: "系统提示走 system 而不是 developer(supportsDeveloperRole: false)",
|
|
1095
1263
|
supportsReasoningEffort: "不发 reasoning_effort,只发开关(supportsReasoningEffort: false)",
|
|
1096
1264
|
developerTrueHint: "当前文档是 supportsDeveloperRole: true。v1 只能强制 false 或缺席;勾选会写成 false,取消勾选会删除该键。",
|
|
@@ -1112,6 +1280,7 @@ window.__ModuleLoader__.load({
|
|
|
1112
1280
|
readOnly: "Settings are read-only; saving is disabled.",
|
|
1113
1281
|
save: "Save",
|
|
1114
1282
|
saving: "Saving…",
|
|
1283
|
+
saveBusy: "Another route is saving. Wait, then save this card.",
|
|
1115
1284
|
cancel: "Cancel",
|
|
1116
1285
|
saved: "Saved. The composer Effort row follows this capability declaration.",
|
|
1117
1286
|
conflict: "Settings changed elsewhere. Reload, then save again.",
|
|
@@ -1132,7 +1301,7 @@ window.__ModuleLoader__.load({
|
|
|
1132
1301
|
clear: "Clear this model’s declaration",
|
|
1133
1302
|
advanced: "Advanced: protocol dialect",
|
|
1134
1303
|
thinkingFormat: "thinkingFormat",
|
|
1135
|
-
thinkingFormatDefault: "Default (
|
|
1304
|
+
thinkingFormatDefault: "Default (omit the key)",
|
|
1136
1305
|
supportsDeveloperRole: "Send system prompts as system, not developer (supportsDeveloperRole: false)",
|
|
1137
1306
|
supportsReasoningEffort: "Do not send reasoning_effort; switch only (supportsReasoningEffort: false)",
|
|
1138
1307
|
developerTrueHint: "The document has supportsDeveloperRole: true. v1 can only force false or omit the key; checking writes false, unchecking deletes the key.",
|
|
@@ -1181,7 +1350,8 @@ window.__ModuleLoader__.load({
|
|
|
1181
1350
|
rehydrate: (serialized) => settingsSchema.rehydrate(serialized),
|
|
1182
1351
|
nodeAtPath: (root, path) => settingsSchema.nodeAtPath(root, path),
|
|
1183
1352
|
getPath: (value, path) => settingsSchema.getPath(value, path),
|
|
1184
|
-
hasPath: (value, path) => settingsSchema.hasPath(value, path)
|
|
1353
|
+
hasPath: (value, path) => settingsSchema.hasPath(value, path),
|
|
1354
|
+
validate: (node, draft) => settingsSchema.validate(node, draft)
|
|
1185
1355
|
});
|
|
1186
1356
|
const t = ctx.locale.bind(NS);
|
|
1187
1357
|
const describe = ctx.settingsScope.describe();
|
|
@@ -1191,6 +1361,9 @@ window.__ModuleLoader__.load({
|
|
|
1191
1361
|
for (const listener of invalidation) listener(source);
|
|
1192
1362
|
};
|
|
1193
1363
|
const disposers = [
|
|
1364
|
+
describe.subscribe(() => {
|
|
1365
|
+
emit("writable");
|
|
1366
|
+
}),
|
|
1194
1367
|
ctx.remote.$on("settings/document-updated", (ns) => {
|
|
1195
1368
|
if (ns !== "llm-pi-ai") return;
|
|
1196
1369
|
emit("settings");
|
|
@@ -1215,14 +1388,13 @@ window.__ModuleLoader__.load({
|
|
|
1215
1388
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
1216
1389
|
name: "settings.section",
|
|
1217
1390
|
id: "effort-declare",
|
|
1218
|
-
order:
|
|
1391
|
+
order: 12,
|
|
1219
1392
|
label: () => t("nav"),
|
|
1220
1393
|
locale: NS,
|
|
1221
1394
|
inject: () => ({
|
|
1222
1395
|
api: connection.api,
|
|
1223
1396
|
describe,
|
|
1224
1397
|
schema,
|
|
1225
|
-
t,
|
|
1226
1398
|
subscribeInvalidate
|
|
1227
1399
|
})
|
|
1228
1400
|
}, EffortDeclareSection));
|