dsh-plugin-effort-declare 0.1.1 → 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 +3 -1
- package/CONTRIBUTING.md +3 -1
- package/README.en.md +6 -6
- package/README.md +6 -6
- package/lib/client.js +150 -15
- package/lib/client.js.map +1 -1
- package/lib/types/client/EffortDeclareSection.d.ts +1 -1
- package/lib/types/client/EffortDeclareSection.d.ts.map +1 -1
- package/lib/types/client/index.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/core/catalog.d.ts +5 -3
- 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 +19 -9
- package/src/client/README.en.md +2 -2
- package/src/client/README.md +2 -2
- package/src/client/index.ts +4 -1
- package/src/client/locales.ts +3 -0
- package/src/core/README.en.md +2 -2
- package/src/core/README.md +2 -2
- package/src/core/catalog.ts +5 -3
- package/src/core/drafts.ts +151 -4
package/CONTRIBUTING.en.md
CHANGED
|
@@ -17,6 +17,8 @@ Write `llm-pi-ai` settings only:
|
|
|
17
17
|
- No fork of the official Models page
|
|
18
18
|
- No parallel effort table
|
|
19
19
|
- No per-model `reasoningEfforts` in the bundle layer (settings dict merge has no delete semantics)
|
|
20
|
+
- Do not auto-write efforts onto undeclared models at Host start (dict merge cannot delete; it would mutate models the user never saved)
|
|
21
|
+
- Do not hook `agent/request` to fill a default effort for subagents
|
|
20
22
|
- Do not set route-level `reasoning` to `high`
|
|
21
23
|
|
|
22
24
|
Any new model-visible input must land in a session-log-rebuildable mechanism. This plugin does not inject prompt text.
|
|
@@ -38,7 +40,7 @@ pnpm build
|
|
|
38
40
|
|
|
39
41
|
Before sending a change, say whether it is a bugfix, UI improvement, or an alignment with a newer DSH / pi-ai schema. Update zh and en docs together. The root README and [INSTALL.en.md](./INSTALL.en.md) are for users; this file and the READMEs under `src/` and `tests/` are for people changing code.
|
|
40
42
|
|
|
41
|
-
Canonical level names and `thinkingFormat` enums come from the installed `llm-pi-ai` schema / catalog. The
|
|
43
|
+
Canonical level names and `thinkingFormat` enums come from the installed `llm-pi-ai` schema / catalog. The live `thinkingFormat` schema contract is [`tests/fixtures/pi-ai-thinking-format-union.ts`](./tests/fixtures/pi-ai-thinking-format-union.ts). When upgrading DSH, refresh that fixture from `dsh --dump-config` or the settings describe schema, then update `src/core/catalog.ts` and UI copy. Do not value-import `@deepseek-ai/dsh-llm-pi-ai` into the client bundle. Effort keys stay a locally pinned whitelist; if the schema drifts, change the fixture / tests first. Do not offer that list as writable dropdown choices.
|
|
42
44
|
|
|
43
45
|
## Publishing to npm
|
|
44
46
|
|
package/CONTRIBUTING.md
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
- 不要 fork 官方模型页
|
|
18
18
|
- 不要平行再存一份档位表
|
|
19
19
|
- 不要在 bundle 层预置 per-model `reasoningEfforts`(settings 字典合并没有删除语义)
|
|
20
|
+
- 不要在 Host 启动时给未声明的模型自动写入档位(字典合并删不掉,会改用户没保存过的模型)
|
|
21
|
+
- 不要挂 `agent/request` 给 subagent 填默认档
|
|
20
22
|
- 不要把路由级 `reasoning` 写成 `high`
|
|
21
23
|
|
|
22
24
|
新增任何模型可见输入,必须走会话日志可重建的机制。本插件不向模型注入提示词。
|
|
@@ -38,7 +40,7 @@ pnpm build
|
|
|
38
40
|
|
|
39
41
|
提交前请说明改动是修 bug、增强 UI,还是对齐新的 DSH / pi-ai schema。中英文档一起改。根 README 和 [INSTALL.md](./INSTALL.md) 面向使用者;本文件与 `src/`、`tests/` 的 README 面向改代码的人。
|
|
40
42
|
|
|
41
|
-
上游档位名与 `thinkingFormat` 枚举以 DSH 安装里 `llm-pi-ai` 的 schema / catalog
|
|
43
|
+
上游档位名与 `thinkingFormat` 枚举以 DSH 安装里 `llm-pi-ai` 的 schema / catalog 为准。`thinkingFormat` 的现场 schema 契约在 [`tests/fixtures/pi-ai-thinking-format-union.ts`](./tests/fixtures/pi-ai-thinking-format-union.ts)。升 DSH 时用 `dsh --dump-config` 或设置 describe 的 schema 更新该 fixture,再改 `src/core/catalog.ts` 与 UI。不要把 `@deepseek-ai/dsh-llm-pi-ai` 打进 client bundle。档位键仍由测试钉本地白名单;schema 漂移时应先改 fixture / 测试再改 UI。不要把这份名单当作可写入下拉选项。
|
|
42
44
|
|
|
43
45
|
## 发布到 npm
|
|
44
46
|
|
package/README.en.md
CHANGED
|
@@ -8,16 +8,16 @@
|
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
10
|
<p align="center">
|
|
11
|
-
<a href="https://www.npmjs.com/package/dsh-plugin-effort-declare"><img alt="npm" src="https://img.shields.io/npm/v/dsh-plugin-effort-declare"></a>
|
|
12
|
-
<a href="https://github.com/zimodzh/dsh-plugin-effort-declare/commits/master"><img alt="last commit" src="https://img.shields.io/github/last-commit/zimodzh/dsh-plugin-effort-declare"></a>
|
|
13
|
-
<a href="https://github.com/zimodzh/dsh-plugin-effort-declare"><img alt="repo size" src="https://img.shields.io/github/repo-size/zimodzh/dsh-plugin-effort-declare"></a>
|
|
14
|
-
<a href="./LICENSE"><img alt="license" src="https://img.shields.io/github/license/zimodzh/dsh-plugin-effort-declare"></a>
|
|
15
|
-
<a href="https://github.com/deepseek-ai/deepseek-harness"><img alt="Agent" src="https://img.shields.io/badge/Agent-Deepseek%20Harness-000000"></a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/dsh-plugin-effort-declare" target="_blank" rel="noopener noreferrer"><img alt="npm" src="https://img.shields.io/npm/v/dsh-plugin-effort-declare"></a>
|
|
12
|
+
<a href="https://github.com/zimodzh/dsh-plugin-effort-declare/commits/master" target="_blank" rel="noopener noreferrer"><img alt="last commit" src="https://img.shields.io/github/last-commit/zimodzh/dsh-plugin-effort-declare"></a>
|
|
13
|
+
<a href="https://github.com/zimodzh/dsh-plugin-effort-declare" target="_blank" rel="noopener noreferrer"><img alt="repo size" src="https://img.shields.io/github/repo-size/zimodzh/dsh-plugin-effort-declare"></a>
|
|
14
|
+
<a href="./LICENSE" target="_blank" rel="noopener noreferrer"><img alt="license" src="https://img.shields.io/github/license/zimodzh/dsh-plugin-effort-declare"></a>
|
|
15
|
+
<a href="https://github.com/deepseek-ai/deepseek-harness" target="_blank" rel="noopener noreferrer"><img alt="Agent" src="https://img.shields.io/badge/Agent-Deepseek%20Harness-000000"></a>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
18
|
Add reasoning-effort options to OpenAI-compatible models you added yourself, so the Effort row shows up in the composer.
|
|
19
19
|
|
|
20
|
-
> This is a community plugin. It is not an official DeepSeek project and is not endorsed by DeepSeek. Requires DeepSeek Harness
|
|
20
|
+
> This is a community-maintained third-party plugin. It is not an official DeepSeek project and is not endorsed by DeepSeek. Requires **DeepSeek Harness 0.1.0-rc.8** or later. Versions before **DeepSeek Harness 0.1.0-rc.8** have not been tested and are not guaranteed to work.
|
|
21
21
|
|
|
22
22
|
## Why
|
|
23
23
|
|
package/README.md
CHANGED
|
@@ -8,16 +8,16 @@
|
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
10
|
<p align="center">
|
|
11
|
-
<a href="https://www.npmjs.com/package/dsh-plugin-effort-declare"><img alt="npm" src="https://img.shields.io/npm/v/dsh-plugin-effort-declare"></a>
|
|
12
|
-
<a href="https://github.com/zimodzh/dsh-plugin-effort-declare/commits/master"><img alt="last commit" src="https://img.shields.io/github/last-commit/zimodzh/dsh-plugin-effort-declare"></a>
|
|
13
|
-
<a href="https://github.com/zimodzh/dsh-plugin-effort-declare"><img alt="repo size" src="https://img.shields.io/github/repo-size/zimodzh/dsh-plugin-effort-declare"></a>
|
|
14
|
-
<a href="./LICENSE"><img alt="license" src="https://img.shields.io/github/license/zimodzh/dsh-plugin-effort-declare"></a>
|
|
15
|
-
<a href="https://github.com/deepseek-ai/deepseek-harness"><img alt="Agent" src="https://img.shields.io/badge/Agent-Deepseek%20Harness-000000"></a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/dsh-plugin-effort-declare" target="_blank" rel="noopener noreferrer"><img alt="npm" src="https://img.shields.io/npm/v/dsh-plugin-effort-declare"></a>
|
|
12
|
+
<a href="https://github.com/zimodzh/dsh-plugin-effort-declare/commits/master" target="_blank" rel="noopener noreferrer"><img alt="last commit" src="https://img.shields.io/github/last-commit/zimodzh/dsh-plugin-effort-declare"></a>
|
|
13
|
+
<a href="https://github.com/zimodzh/dsh-plugin-effort-declare" target="_blank" rel="noopener noreferrer"><img alt="repo size" src="https://img.shields.io/github/repo-size/zimodzh/dsh-plugin-effort-declare"></a>
|
|
14
|
+
<a href="./LICENSE" target="_blank" rel="noopener noreferrer"><img alt="license" src="https://img.shields.io/github/license/zimodzh/dsh-plugin-effort-declare"></a>
|
|
15
|
+
<a href="https://github.com/deepseek-ai/deepseek-harness" target="_blank" rel="noopener noreferrer"><img alt="Agent" src="https://img.shields.io/badge/Agent-Deepseek%20Harness-000000"></a>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
18
|
给自己添加的 OpenAI 兼容模型补上「推理强度」选项,对话里的 Effort 行就会出现。
|
|
19
19
|
|
|
20
|
-
>
|
|
20
|
+
> 这是社区维护的第三方插件,不是 DeepSeek 官方项目,也未获官方背书。需要 **DeepSeek Harness 0.1.0-rc.8** 及以上, **DeepSeek Harness 0.1.0-rc.8** 之前的版本没测试过,不保证可用。
|
|
21
21
|
|
|
22
22
|
## 它解决什么
|
|
23
23
|
|
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
|
-
* Tests pin these lists against
|
|
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 = [
|
|
@@ -156,6 +158,14 @@ window.__ModuleLoader__.load({
|
|
|
156
158
|
* Route-card draft merge: user-layer slices, namespace revision, dirty preserve.
|
|
157
159
|
* No React — settings UI and tests share these helpers.
|
|
158
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
|
+
}
|
|
159
169
|
/** Build a draft from the stored user subtree (never from effective `value`). */
|
|
160
170
|
function routeDraftFromUserProfile(args) {
|
|
161
171
|
const { provider, displayName, settingsPath, revision, userProfile } = args;
|
|
@@ -210,9 +220,103 @@ window.__ModuleLoader__.load({
|
|
|
210
220
|
});
|
|
211
221
|
});
|
|
212
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
|
+
}
|
|
261
|
+
/**
|
|
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
|
+
}
|
|
213
292
|
/**
|
|
214
|
-
*
|
|
215
|
-
*
|
|
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).
|
|
216
320
|
*/
|
|
217
321
|
function mergeLoadedDrafts(current, incoming, options) {
|
|
218
322
|
const currentByProvider = new Map(current.map((draft) => [draft.provider, draft]));
|
|
@@ -221,13 +325,27 @@ window.__ModuleLoader__.load({
|
|
|
221
325
|
drafts: incoming.map((next) => {
|
|
222
326
|
const prev = currentByProvider.get(next.provider);
|
|
223
327
|
if (prev === void 0 || !options.preserveDirty || !draftDirty(prev)) return next;
|
|
224
|
-
|
|
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);
|
|
225
341
|
return {
|
|
226
|
-
|
|
342
|
+
provider: next.provider,
|
|
227
343
|
displayName: next.displayName,
|
|
228
344
|
settingsPath: next.settingsPath,
|
|
229
345
|
revision: next.revision,
|
|
346
|
+
models: modelsMerge.models,
|
|
230
347
|
originalModels: cloneModels(next.originalModels),
|
|
348
|
+
compat: compatMerge.compat,
|
|
231
349
|
originalCompat: cloneObject(next.originalCompat),
|
|
232
350
|
compatPresent: next.compatPresent
|
|
233
351
|
};
|
|
@@ -670,10 +788,10 @@ window.__ModuleLoader__.load({
|
|
|
670
788
|
});
|
|
671
789
|
}
|
|
672
790
|
function RouteCard(props) {
|
|
673
|
-
const { draft, writable, busy,
|
|
791
|
+
const { draft, writable, busy, saveLocked, t, onChange } = props;
|
|
674
792
|
const noModels = draft.models.length === 0;
|
|
675
793
|
const editDisabled = !writable || busy || noModels;
|
|
676
|
-
const
|
|
794
|
+
const saveDisabled = saveLocked || noModels;
|
|
677
795
|
const formats = thinkingFormatChoices(props.formats, draft.compat.thinkingFormat);
|
|
678
796
|
const summary = compatSummary(draft.compat);
|
|
679
797
|
const sameWire = draft.compat.supportsReasoningEffort === false;
|
|
@@ -857,7 +975,7 @@ window.__ModuleLoader__.load({
|
|
|
857
975
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
858
976
|
type: "button",
|
|
859
977
|
className: effort_declare_module_css_default.primaryButton,
|
|
860
|
-
disabled:
|
|
978
|
+
disabled: saveDisabled || !dirty || clientError !== void 0,
|
|
861
979
|
onClick: () => {
|
|
862
980
|
props.onSave(draft);
|
|
863
981
|
},
|
|
@@ -889,7 +1007,7 @@ window.__ModuleLoader__.load({
|
|
|
889
1007
|
return;
|
|
890
1008
|
}
|
|
891
1009
|
const generation = nextGeneration(generationRef);
|
|
892
|
-
setStatus("loading");
|
|
1010
|
+
if (draftsRef.current.length === 0) setStatus("loading");
|
|
893
1011
|
setError("");
|
|
894
1012
|
loadDrafts(api, describe, schema).then((result) => {
|
|
895
1013
|
if (!generationIsCurrent(generationRef, generation)) return;
|
|
@@ -928,9 +1046,18 @@ window.__ModuleLoader__.load({
|
|
|
928
1046
|
(0, react.useEffect)(() => {
|
|
929
1047
|
if (props.subscribeInvalidate === void 0) return void 0;
|
|
930
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
|
+
}
|
|
931
1054
|
if (source === "settings" || source === "directory") reload(true);
|
|
932
1055
|
});
|
|
933
|
-
}, [
|
|
1056
|
+
}, [
|
|
1057
|
+
describe,
|
|
1058
|
+
props.subscribeInvalidate,
|
|
1059
|
+
reload
|
|
1060
|
+
]);
|
|
934
1061
|
const patchNotice = (provider, notice) => {
|
|
935
1062
|
setNotices((current) => {
|
|
936
1063
|
const copy = { ...current };
|
|
@@ -941,7 +1068,13 @@ window.__ModuleLoader__.load({
|
|
|
941
1068
|
};
|
|
942
1069
|
const save = async (draft) => {
|
|
943
1070
|
if (api === void 0 || describe === void 0 || schema === void 0) return;
|
|
944
|
-
if (status === "loading" || busyRoute !== null)
|
|
1071
|
+
if (status === "loading" || busyRoute !== null) {
|
|
1072
|
+
patchNotice(draft.provider, {
|
|
1073
|
+
kind: "error",
|
|
1074
|
+
text: t("saveBusy")
|
|
1075
|
+
});
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
945
1078
|
const blocking = draft.models.map((row) => errorText(modelEffortError(row), t)).find((text) => text !== void 0);
|
|
946
1079
|
if (blocking !== void 0) {
|
|
947
1080
|
patchNotice(draft.provider, {
|
|
@@ -1067,7 +1200,7 @@ window.__ModuleLoader__.load({
|
|
|
1067
1200
|
formats: formats.length > 0 ? formats : [],
|
|
1068
1201
|
writable,
|
|
1069
1202
|
busy: busyRoute === draft.provider,
|
|
1070
|
-
|
|
1203
|
+
saveLocked: !writable || busyRoute !== null || status === "loading",
|
|
1071
1204
|
notice: notices[draft.provider],
|
|
1072
1205
|
t,
|
|
1073
1206
|
onChange: (next) => {
|
|
@@ -1104,6 +1237,7 @@ window.__ModuleLoader__.load({
|
|
|
1104
1237
|
readOnly: "当前设置为只读,无法保存。",
|
|
1105
1238
|
save: "保存",
|
|
1106
1239
|
saving: "保存中…",
|
|
1240
|
+
saveBusy: "另有路由正在保存,请稍候。",
|
|
1107
1241
|
cancel: "取消",
|
|
1108
1242
|
saved: "已保存。对话选择器会按新的能力声明显示 Effort 行。",
|
|
1109
1243
|
conflict: "设置已被其他地方改过,请重新加载后再保存。",
|
|
@@ -1146,6 +1280,7 @@ window.__ModuleLoader__.load({
|
|
|
1146
1280
|
readOnly: "Settings are read-only; saving is disabled.",
|
|
1147
1281
|
save: "Save",
|
|
1148
1282
|
saving: "Saving…",
|
|
1283
|
+
saveBusy: "Another route is saving. Wait, then save this card.",
|
|
1149
1284
|
cancel: "Cancel",
|
|
1150
1285
|
saved: "Saved. The composer Effort row follows this capability declaration.",
|
|
1151
1286
|
conflict: "Settings changed elsewhere. Reload, then save again.",
|
|
@@ -1227,7 +1362,7 @@ window.__ModuleLoader__.load({
|
|
|
1227
1362
|
};
|
|
1228
1363
|
const disposers = [
|
|
1229
1364
|
describe.subscribe(() => {
|
|
1230
|
-
emit("
|
|
1365
|
+
emit("writable");
|
|
1231
1366
|
}),
|
|
1232
1367
|
ctx.remote.$on("settings/document-updated", (ns) => {
|
|
1233
1368
|
if (ns !== "llm-pi-ai") return;
|