dsh-plugin-effort-declare 0.1.3 → 0.1.4
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 +18 -9
- package/CONTRIBUTING.md +22 -13
- package/INSTALL.en.md +9 -9
- package/INSTALL.md +16 -16
- package/README.en.md +25 -21
- package/README.md +28 -24
- package/lib/client.js +113 -30
- package/lib/client.js.map +1 -1
- package/lib/types/client/EffortDeclareSection.d.ts +2 -2
- package/lib/types/client/EffortDeclareSection.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 +8 -0
- package/lib/types/core/catalog.d.ts.map +1 -1
- package/lib/types/core/drafts.d.ts +8 -4
- package/lib/types/core/drafts.d.ts.map +1 -1
- package/lib/types/core/input.d.ts +15 -0
- package/lib/types/core/input.d.ts.map +1 -0
- package/lib/types/core/validate.d.ts +3 -0
- package/lib/types/core/validate.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/README.en.md +2 -2
- package/src/README.md +2 -2
- package/src/client/EffortDeclareSection.tsx +18 -6
- package/src/client/README.en.md +3 -3
- package/src/client/README.md +3 -3
- package/src/client/locales.ts +13 -4
- package/src/core/README.en.md +8 -7
- package/src/core/README.md +8 -7
- package/src/core/catalog.ts +11 -0
- package/src/core/drafts.ts +40 -26
- package/src/core/input.ts +49 -0
- package/src/core/validate.ts +8 -0
|
@@ -30,4 +30,12 @@ export declare const LLM_DEEPSEEK_NS = "llm-deepseek";
|
|
|
30
30
|
export declare const DEEPSEEK_OFFICIAL = "deepseek-official";
|
|
31
31
|
/** Any route key walks a dict schema to the same profile node. */
|
|
32
32
|
export declare const SCHEMA_PROBE_ROUTE = "\0probe";
|
|
33
|
+
/** Request modalities llm-pi-ai accepts on `models[].input` / `defaultInput`. */
|
|
34
|
+
export declare const INPUT_MODALITIES: readonly ["text", "image"];
|
|
35
|
+
export type InputModality = (typeof INPUT_MODALITIES)[number];
|
|
36
|
+
/**
|
|
37
|
+
* Canonical write for a hand-declared vision model.
|
|
38
|
+
* Must include `text`; image-only is not a serviceable OpenAI-completions route.
|
|
39
|
+
*/
|
|
40
|
+
export declare const IMAGE_CAPABLE_INPUT: readonly InputModality[];
|
|
33
41
|
//# sourceMappingURL=catalog.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../../src/core/catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,8DAA8D;AAC9D,eAAO,MAAM,eAAe,sEAQlB,CAAA;AAEV,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAA;AAE5D,yEAAyE;AACzE,eAAO,MAAM,2BAA2B,6DAEvC,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,yBAAyB,gJAW5B,CAAA;AAEV,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvE,sDAAsD;AACtD,eAAO,MAAM,kBAAkB,uBAAuB,CAAA;AAEtD,6CAA6C;AAC7C,eAAO,MAAM,YAAY,cAAc,CAAA;AAEvC,kDAAkD;AAClD,eAAO,MAAM,eAAe,iBAAiB,CAAA;AAE7C,+CAA+C;AAC/C,eAAO,MAAM,iBAAiB,sBAAsB,CAAA;AAEpD,kEAAkE;AAClE,eAAO,MAAM,kBAAkB,YAAgB,CAAA"}
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../../src/core/catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,8DAA8D;AAC9D,eAAO,MAAM,eAAe,sEAQlB,CAAA;AAEV,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAA;AAE5D,yEAAyE;AACzE,eAAO,MAAM,2BAA2B,6DAEvC,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,yBAAyB,gJAW5B,CAAA;AAEV,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvE,sDAAsD;AACtD,eAAO,MAAM,kBAAkB,uBAAuB,CAAA;AAEtD,6CAA6C;AAC7C,eAAO,MAAM,YAAY,cAAc,CAAA;AAEvC,kDAAkD;AAClD,eAAO,MAAM,eAAe,iBAAiB,CAAA;AAE7C,+CAA+C;AAC/C,eAAO,MAAM,iBAAiB,sBAAsB,CAAA;AAEpD,kEAAkE;AAClE,eAAO,MAAM,kBAAkB,YAAgB,CAAA;AAE/C,iFAAiF;AACjF,eAAO,MAAM,gBAAgB,4BAA6B,CAAA;AAE1D,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE7D;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,aAAa,EAAsB,CAAA"}
|
|
@@ -34,9 +34,13 @@ export declare function applySaveSuccess(drafts: readonly RouteDraft[], savedPro
|
|
|
34
34
|
user: unknown;
|
|
35
35
|
revision: number;
|
|
36
36
|
}): RouteDraft[];
|
|
37
|
+
/** Model-row fields this page edits; other keys follow the Models page. */
|
|
38
|
+
export declare const MODEL_OVERLAY_KEYS: readonly ["reasoningEfforts", "input"];
|
|
39
|
+
export type ModelOverlayKey = (typeof MODEL_OVERLAY_KEYS)[number];
|
|
37
40
|
/**
|
|
38
41
|
* Membership follows the latest user-layer models list (Models page add/delete).
|
|
39
|
-
* Local unsaved
|
|
42
|
+
* Local unsaved overlay keys (including a cleared key) overlay by id; other
|
|
43
|
+
* fields on the row follow incoming.
|
|
40
44
|
*/
|
|
41
45
|
export declare function mergeModelsById(args: {
|
|
42
46
|
prevModels: readonly Record<string, unknown>[];
|
|
@@ -62,9 +66,9 @@ export declare function mergeCompat(args: {
|
|
|
62
66
|
};
|
|
63
67
|
/**
|
|
64
68
|
* Apply a freshly loaded table. Membership and metadata follow incoming;
|
|
65
|
-
* unsaved reasoningEfforts / dirty compat keys
|
|
66
|
-
* when a locally dirty field also changed in
|
|
67
|
-
* and sibling-card saves do not warn).
|
|
69
|
+
* unsaved overlay keys (`reasoningEfforts`, `input`) / dirty compat keys
|
|
70
|
+
* overlay by id. Conflict only when a locally dirty field also changed in
|
|
71
|
+
* originals (revision-only bumps and sibling-card saves do not warn).
|
|
68
72
|
*/
|
|
69
73
|
export declare function mergeLoadedDrafts(current: readonly RouteDraft[], incoming: readonly RouteDraft[], options: {
|
|
70
74
|
preserveDirty: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drafts.d.ts","sourceRoot":"","sources":["../../../src/core/drafts.ts"],"names":[],"mappings":"AAQA,iEAAiE;AACjE,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,0EAA0E;IAC1E,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IAC/B,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;IACjC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;IACzC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACvC,gFAAgF;IAChF,aAAa,EAAE,OAAO,CAAA;CACvB;AAED,kEAAkE;AAClE,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAEjE;AAED,0CAA0C;AAC1C,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAErE;AAED,iFAAiF;AACjF,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,OAAO,CAAA;CACrB,GAAG,UAAU,CAgBb;AAED,wEAAwE;AACxE,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAQrD;AAED,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAOxD;AAED,4FAA4F;AAC5F,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,UAAU,EAAE,EAC7B,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACzC,UAAU,EAAE,CAcd;
|
|
1
|
+
{"version":3,"file":"drafts.d.ts","sourceRoot":"","sources":["../../../src/core/drafts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Whether the stored `input` list includes image. Absence is false. */
|
|
2
|
+
export declare function readImageCapable(row: Record<string, unknown>): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Declare or drop image input on a spread row.
|
|
5
|
+
* Enabled writes `[text, image]`; disabled unsets the key (not `[]`, not `[text]`).
|
|
6
|
+
*/
|
|
7
|
+
export declare function writeImageCapable(row: Record<string, unknown>, enabled: boolean): Record<string, unknown>;
|
|
8
|
+
/**
|
|
9
|
+
* Validate a stored `input` field. Returns an error code; never throws.
|
|
10
|
+
* Absence is valid. Empty list is rejected so a bad stamp cannot be re-saved.
|
|
11
|
+
*/
|
|
12
|
+
export declare function validateInput(input: unknown): 'bad-input' | undefined;
|
|
13
|
+
/** Per-model client-side check used to show errors without throwing. */
|
|
14
|
+
export declare function modelInputError(row: Record<string, unknown>): 'bad-input' | undefined;
|
|
15
|
+
//# sourceMappingURL=input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../src/core/input.ts"],"names":[],"mappings":"AAOA,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAGtE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,OAAO,EAAE,OAAO,GACf,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKzB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,SAAS,CAWrE;AAED,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,GAAG,SAAS,CAGrF"}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export type ModelRowError = 'empty' | 'off-only' | 'bad-wire' | 'bad-input';
|
|
1
2
|
/** Per-model client-side check used to show errors without throwing. */
|
|
2
3
|
export declare function modelEffortError(row: Record<string, unknown>): 'empty' | 'off-only' | 'bad-wire' | undefined;
|
|
4
|
+
/** First blocking error on a model row (efforts, then input). */
|
|
5
|
+
export declare function modelRowError(row: Record<string, unknown>): ModelRowError | undefined;
|
|
3
6
|
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/core/validate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/core/validate.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,CAAA;AAE3E,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAG5G;AAED,iEAAiE;AACjE,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,aAAa,GAAG,SAAS,CAErF"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-effort-declare",
|
|
3
|
-
"description": "
|
|
4
|
-
"version": "0.1.
|
|
3
|
+
"description": "Declare per-model reasoning efforts, image input, and openai-completions dialect on hand-declared llm-pi-ai routes. Writes the official namespace; does not intercept llm/stream or replace the Models page.",
|
|
4
|
+
"version": "0.1.4",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
package/src/README.en.md
CHANGED
|
@@ -16,8 +16,8 @@ Install: [INSTALL.en.md](../INSTALL.en.md). Usage: root [README.en.md](../README
|
|
|
16
16
|
| Path | Role |
|
|
17
17
|
| --- | --- |
|
|
18
18
|
| [`index.ts`](./index.ts) | Host entry. Exports `name` and `apply`. v1 registers no host services or adapters; the bundle still needs this entry so `dsh plugin add` can load the package. |
|
|
19
|
-
| [`core/`](./core/) | UI-free pure functions: efforts, presets, save ops, draft merge, route filtering, footer attribution. Shared by the client and tests; the v1 host entry does not import them (`tsconfig.host` still compiles `core` for types). |
|
|
20
|
-
| [`client/`](./client/) | Browser half: locale, settings page, `settings.mutate` against `llm-pi-ai`. |
|
|
19
|
+
| [`core/`](./core/) | UI-free pure functions: reasoning efforts, image input, presets, save ops, draft merge, route filtering, footer attribution. Shared by the client and tests; the v1 host entry does not import them (`tsconfig.host` still compiles `core` for types). |
|
|
20
|
+
| [`client/`](./client/) | Browser half: locale, settings page, and official `settings.mutate` against `llm-pi-ai` for `reasoningEfforts` and `input`. |
|
|
21
21
|
| [`css-modules.d.ts`](./css-modules.d.ts) | Types for `*.module.css` (class map plus `cssText` / `cssTagId`). |
|
|
22
22
|
|
|
23
23
|
`tsconfig.host.json` compiles host + `core`; `tsconfig.client.json` compiles `client` + `core`. Build output lives in [`lib/`](../lib/).
|
package/src/README.md
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
| 路径 | 说明 |
|
|
17
17
|
| --- | --- |
|
|
18
18
|
| [`index.ts`](./index.ts) | Host 入口。导出 `name` 与 `apply`。v1 不注册 host 服务、不挂适配器;组合包仍需要这一入口才能被 `dsh plugin add` 加载。 |
|
|
19
|
-
| [`core/`](./core/) | 与 UI
|
|
20
|
-
| [`client/`](./client/) | 浏览器半区:locale
|
|
19
|
+
| [`core/`](./core/) | 与 UI 无关的纯函数:推理档位、图片输入、预设、保存 ops、草稿合并、路由过滤、页脚归因格式。client 与单测共用;Host 入口 v1 不引用这些函数(`tsconfig.host` 仍编译 `core` 以产出类型)。 |
|
|
20
|
+
| [`client/`](./client/) | 浏览器半区:locale、设置页,经官方 `settings.mutate` 写入 `llm-pi-ai` 的 `reasoningEfforts` 与 `input`。 |
|
|
21
21
|
| [`css-modules.d.ts`](./css-modules.d.ts) | `*.module.css` 模块类型(class map + `cssText` / `cssTagId`)。 |
|
|
22
22
|
|
|
23
23
|
`tsconfig.host.json` 编译 host + `core`;`tsconfig.client.json` 编译 `client` + `core`。构建产物在 [`lib/`](../lib/)。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Settings section: per-model reasoningEfforts
|
|
3
|
-
* for hand-declared llm-pi-ai routes.
|
|
2
|
+
* Settings section: per-model reasoningEfforts, image input, and
|
|
3
|
+
* openai-completions compat for hand-declared llm-pi-ai routes.
|
|
4
4
|
*/
|
|
5
5
|
import { useCallback, useEffect, useRef, useState, type ReactNode } from 'react'
|
|
6
6
|
import type { IApiClient } from '@deepseek-ai/dsh-api-remotes/client'
|
|
@@ -29,7 +29,8 @@ import {
|
|
|
29
29
|
import { applyPresetCompat, applyPresetEfforts, PRESETS, type PresetId } from '../core/presets.ts'
|
|
30
30
|
import { buildSaveOps } from '../core/path-ops.ts'
|
|
31
31
|
import { cloneModels, cloneObject } from '../core/paths.ts'
|
|
32
|
-
import {
|
|
32
|
+
import { readImageCapable, writeImageCapable } from '../core/input.ts'
|
|
33
|
+
import { modelRowError } from '../core/validate.ts'
|
|
33
34
|
import { PLUGIN_FOOTER_TEXT } from './build-info.ts'
|
|
34
35
|
import {
|
|
35
36
|
foldReloadNotices,
|
|
@@ -75,10 +76,11 @@ function compatSummary(compat: Record<string, unknown>): string {
|
|
|
75
76
|
return parts.join(' · ')
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
function errorText(code: ReturnType<typeof
|
|
79
|
+
function errorText(code: ReturnType<typeof modelRowError>, t: (key: EffortDeclareKey) => string): string | undefined {
|
|
79
80
|
if (code === 'empty') return t('errorEmpty')
|
|
80
81
|
if (code === 'off-only') return t('errorOffOnly')
|
|
81
82
|
if (code === 'bad-wire') return t('errorBadWire')
|
|
83
|
+
if (code === 'bad-input') return t('errorBadInput')
|
|
82
84
|
return undefined
|
|
83
85
|
}
|
|
84
86
|
|
|
@@ -114,6 +116,16 @@ function ModelRowEditor(props: {
|
|
|
114
116
|
{t('clear')}
|
|
115
117
|
</button>
|
|
116
118
|
</div>
|
|
119
|
+
<label className={css.check}>
|
|
120
|
+
<input
|
|
121
|
+
type="checkbox"
|
|
122
|
+
checked={readImageCapable(row)}
|
|
123
|
+
disabled={disabled}
|
|
124
|
+
onChange={(event) => { onChange(writeImageCapable(row, event.target.checked)) }}
|
|
125
|
+
/>
|
|
126
|
+
{t('imageInput')}
|
|
127
|
+
</label>
|
|
128
|
+
{readImageCapable(row) ? <p className={css.notice}>{t('imageInputHint')}</p> : null}
|
|
117
129
|
<span className={css.fieldLabel}>{t('levels')}</span>
|
|
118
130
|
<div className={css.levels}>
|
|
119
131
|
{THINKING_LEVELS_WITHOUT_OFF.map((level) => (
|
|
@@ -197,7 +209,7 @@ function RouteCard(props: {
|
|
|
197
209
|
const summary = compatSummary(draft.compat)
|
|
198
210
|
const sameWire = draft.compat.supportsReasoningEffort === false
|
|
199
211
|
const clientError = draft.models
|
|
200
|
-
.map(row => errorText(
|
|
212
|
+
.map(row => errorText(modelRowError(row), t))
|
|
201
213
|
.find(text => text !== undefined)
|
|
202
214
|
const dirty = draftDirty(draft)
|
|
203
215
|
|
|
@@ -486,7 +498,7 @@ export function EffortDeclareSection(props: EffortDeclareSectionProps): ReactNod
|
|
|
486
498
|
return
|
|
487
499
|
}
|
|
488
500
|
const blocking = draft.models
|
|
489
|
-
.map(row => errorText(
|
|
501
|
+
.map(row => errorText(modelRowError(row), t))
|
|
490
502
|
.find(text => text !== undefined)
|
|
491
503
|
if (blocking !== undefined) {
|
|
492
504
|
patchNotice(draft.provider, { kind: 'error', text: blocking })
|
package/src/client/README.en.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
</samp>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
|
-
Browser half: registers **Reasoning efforts** in the Web UI settings panel and writes `llm-pi-ai` through the official settings RPC.
|
|
10
|
+
Browser half: registers **Reasoning efforts** in the Web UI settings panel and writes per-model `reasoningEfforts` and `input` on `llm-pi-ai` through the official settings RPC.
|
|
11
11
|
|
|
12
12
|
Wiring failures in `apply` throw, same as the official Models page. Do not swallow `apply` with a blanket `try/catch`. Locale, CSS, and event subscriptions are cleaned up through `ctx.effect` on unload.
|
|
13
13
|
|
|
@@ -18,11 +18,11 @@ Cross-plugin work uses Cordis services only (`connection`, `settingsScope`, `set
|
|
|
18
18
|
| File | Role |
|
|
19
19
|
| --- | --- |
|
|
20
20
|
| [`index.ts`](./index.ts) | Registers zh/en copy, CSS (`ctx.effect` insert/remove), and `settings.section` (id `effort-declare`, order 12). `describe.subscribe` only syncs `writable` (it fires for every namespace, including this page’s own `acceptView`). `settings/document-updated` (`llm-pi-ai` only) carries the Host revision; the section skips its own mutate echo, waits until the mirror revision has **caught up**, then `getSnapshot()` — it does not use `ensure()` as refresh. Provider directory changes come from `llm/adapters-updated`; `connection/reset` uses `ensure()` so an in-flight official `load()` is awaited. `locale: NS` lets the framework inject `t`; `inject` only returns `api` / `describe` / `schema` / `subscribeInvalidate`. |
|
|
21
|
-
| [`EffortDeclareSection.tsx`](./EffortDeclareSection.tsx) | Route cards, presets, Off tri-state, advanced protocol switches, save and cancel. Save is exclusive for the whole `llm-pi-ai` namespace (ref-guarded against double-click); `busy` is “Saving…” on the in-flight card, `saveLocked` disables the others. A successful write syncs `draftsRef` immediately and folds `acceptView` + `applySaveSuccess`. External `document-updated` events that arrive during save are replayed afterwards. A reload with no conflict clears that card’s conflict/error notice. Footer shows the version and copyright years frozen at pack time. |
|
|
21
|
+
| [`EffortDeclareSection.tsx`](./EffortDeclareSection.tsx) | Route cards, presets, Off tri-state, per-model **Accepts image input**, advanced protocol switches, save and cancel. Save is exclusive for the whole `llm-pi-ai` namespace (ref-guarded against double-click); `busy` is “Saving…” on the in-flight card, `saveLocked` disables the others. Save is blocked by `modelRowError` for empty efforts, Off-only, and illegal `input`. A successful write syncs `draftsRef` immediately and folds `acceptView` + `applySaveSuccess`. External `document-updated` events that arrive during save are replayed afterwards. A reload with no conflict clears that card’s conflict/error notice. Footer shows the version and copyright years frozen at pack time. |
|
|
22
22
|
| [`build-info.ts`](./build-info.ts) | Reads `package.json` version and UTC pack year injected by tsdown `define`, and formats the footer line. |
|
|
23
23
|
| [`globals.d.ts`](./globals.d.ts) | Types for `__PLUGIN_VERSION__` and `__COPYRIGHT_TO__`. |
|
|
24
24
|
| [`load-drafts.ts`](./load-drafts.ts) | Builds drafts from `llm.providers` + the settings mirror; drafts come from `user`, protocol classification may use `value`. `thinkingFormat` choices come only from the live schema union. First paint uses `ensure()`; refresh reads `getSnapshot()` only, with helpers that wait for the namespace revision. |
|
|
25
|
-
| [`locales.ts`](./locales.ts) | Copy namespace `plugin-effort-declare
|
|
25
|
+
| [`locales.ts`](./locales.ts) | Copy namespace `plugin-effort-declare` (efforts, image input, and validation errors). |
|
|
26
26
|
| [`effort-declare.module.css`](./effort-declare.module.css) | `--dsw-alias-*` tokens only, so dark theme stays correct. |
|
|
27
27
|
| [`schema-ops.ts`](./schema-ops.ts) | Binds `settingsSchema` as plain callbacks (including `validate`) so the service identity is not passed into React. |
|
|
28
28
|
|
package/src/client/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
</samp>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
|
-
浏览器半区:在 Web UI 的设置面板注册「推理档位」,并经官方 settings RPC
|
|
10
|
+
浏览器半区:在 Web UI 的设置面板注册「推理档位」,并经官方 settings RPC 向 `llm-pi-ai` 写入每模型的 `reasoningEfforts` 与 `input`。
|
|
11
11
|
|
|
12
12
|
`apply` 接线失败会抛出,与官方模型页相同;不要整段 `try/catch` 吞掉。卸载时 locale、CSS 与事件订阅随 `ctx.effect` 清理。
|
|
13
13
|
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
| 文件 | 说明 |
|
|
19
19
|
| --- | --- |
|
|
20
20
|
| [`index.ts`](./index.ts) | 注册 zh/en 文案、CSS(`ctx.effect` 插入/移除)与 `settings.section`(id `effort-declare`,order 12)。`describe.subscribe` 只同步 `writable`(任意 namespace 都会触发,含自己的 `acceptView`)。`settings/document-updated`(仅 `llm-pi-ai`)带上 Host 的 revision;设置页跳过自己这次 mutate 的回声,等镜像 revision **追上**后再 `getSnapshot()`,不把 `ensure()` 当刷新。提供方目录来自 `llm/adapters-updated`;`connection/reset` 走 `ensure()` 以便等待官方镜像的 in-flight `load()`。`locale: NS` 由框架注入 `t`,inject 只传 `api` / `describe` / `schema` / `subscribeInvalidate`。 |
|
|
21
|
-
| [`EffortDeclareSection.tsx`](./EffortDeclareSection.tsx) | 路由卡片、预设、Off
|
|
21
|
+
| [`EffortDeclareSection.tsx`](./EffortDeclareSection.tsx) | 路由卡片、预设、Off 三态、每模型「支持图片输入」、高级协议开关、保存与取消。保存互斥到整个 `llm-pi-ai` namespace(以 ref 防连点);`busy` 只给正在 mutate 的卡显示「保存中」,其它卡 `saveLocked`。保存前用 `modelRowError` 拦截空档位、只开 Off 与非法 `input`。自己写入成功立刻同步 `draftsRef` 并走 `acceptView` + `applySaveSuccess`。保存过程中到达的外部 `document-updated` 会在结束后补刷。无冲突的 reload 清掉该卡 conflict/error notice。页脚显示打包时冻结的版本与版权年。 |
|
|
22
22
|
| [`build-info.ts`](./build-info.ts) | 读取 tsdown `define` 注入的 `package.json` version 与 UTC 打包年,拼出页脚文案。 |
|
|
23
23
|
| [`globals.d.ts`](./globals.d.ts) | `__PLUGIN_VERSION__` / `__COPYRIGHT_TO__` 的类型声明。 |
|
|
24
24
|
| [`load-drafts.ts`](./load-drafts.ts) | 从 `llm.providers` + 镜像组装草稿;草稿取自 `user`,协议分类可用 `value`。`thinkingFormat` 可选项只来自现场 schema union。首屏 `ensure()`;刷新只读 `getSnapshot()`,并提供等 namespace revision 的 helper。 |
|
|
25
|
-
| [`locales.ts`](./locales.ts) | 文案 namespace `plugin-effort-declare
|
|
25
|
+
| [`locales.ts`](./locales.ts) | 文案 namespace `plugin-effort-declare`(含档位、图片输入与校验错误)。 |
|
|
26
26
|
| [`effort-declare.module.css`](./effort-declare.module.css) | 仅使用 `--dsw-alias-*` 设计令牌,保证暗色主题正确。 |
|
|
27
27
|
| [`schema-ops.ts`](./schema-ops.ts) | 将 `settingsSchema` 收成普通回调(含 `validate`),避免把服务身份带进 React。 |
|
|
28
28
|
|
package/src/client/locales.ts
CHANGED
|
@@ -44,13 +44,16 @@ export type EffortDeclareKey =
|
|
|
44
44
|
| 'errorBadWire'
|
|
45
45
|
| 'noModels'
|
|
46
46
|
| 'reload'
|
|
47
|
+
| 'imageInput'
|
|
48
|
+
| 'imageInputHint'
|
|
49
|
+
| 'errorBadInput'
|
|
47
50
|
|
|
48
51
|
export const NS = 'plugin-effort-declare'
|
|
49
52
|
|
|
50
53
|
export const zh: Record<EffortDeclareKey, string> = {
|
|
51
54
|
nav: '推理档位',
|
|
52
55
|
title: '推理档位声明',
|
|
53
|
-
intro: '
|
|
56
|
+
intro: '声明手工模型可提供的推理档位,以及是否接受图片输入。未声明则不显示 Effort 行,图片附件也会被拒绝。档位仍在输入框菜单中选择;密钥、目录与当前选中项不在本页修改。',
|
|
54
57
|
empty: '没有可编辑的手工 openai-completions 路由。',
|
|
55
58
|
emptyHint: '请先到「模型」页添加提供方(llm-pi-ai 手工路由)。官方 DeepSeek 与 catalog 路由不在本页编辑。需要 DSH ≥ 0.1.0-rc.8(providers.declared)。',
|
|
56
59
|
loadError: '无法加载提供方或设置。',
|
|
@@ -60,7 +63,7 @@ export const zh: Record<EffortDeclareKey, string> = {
|
|
|
60
63
|
saving: '保存中…',
|
|
61
64
|
saveBusy: '另有路由正在保存,请稍候。',
|
|
62
65
|
cancel: '取消',
|
|
63
|
-
saved: '
|
|
66
|
+
saved: '已保存。对话会按新的能力声明更新 Effort 行和是否允许贴图。',
|
|
64
67
|
conflict: '设置已被其他地方改过,请重新加载后再保存。',
|
|
65
68
|
dirtyConflict: '此路由有未保存修改,设置已在其他地方更新。保存会写到最新文档上,或先取消再改。',
|
|
66
69
|
presets: '预设',
|
|
@@ -89,12 +92,15 @@ export const zh: Record<EffortDeclareKey, string> = {
|
|
|
89
92
|
errorBadWire: '思考档必须填写线上拼写;只有 Off 可以留空。不能使用未知档位键。',
|
|
90
93
|
noModels: '这条路由还没有 models 列表。请先到「模型」页添加模型。',
|
|
91
94
|
reload: '重新加载',
|
|
95
|
+
imageInput: '支持图片输入',
|
|
96
|
+
imageInputHint: '只在这个模型确实能看图时勾选。勾错了图会进聊天记录,然后被接口打回来。',
|
|
97
|
+
errorBadInput: 'input 只能是 text / image,必须包含 text,不能重复或留空。不支持图片请清除该键,不要写成空列表。',
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
export const en: Record<EffortDeclareKey, string> = {
|
|
95
101
|
nav: 'Reasoning efforts',
|
|
96
102
|
title: 'Reasoning effort declarations',
|
|
97
|
-
intro: '
|
|
103
|
+
intro: 'Declare reasoning levels and image input for hand-added models. Undeclared models omit the Effort row and refuse image attachments. Effort is still chosen in the composer menu; keys, catalogs, and the current selection are not modified here.',
|
|
98
104
|
empty: 'No editable hand-declared openai-completions routes.',
|
|
99
105
|
emptyHint: 'Add a provider on the Models page first (an llm-pi-ai hand-declared route). Official DeepSeek and catalog routes are not edited here. Requires DSH ≥ 0.1.0-rc.8 (providers.declared).',
|
|
100
106
|
loadError: 'Could not load providers or settings.',
|
|
@@ -104,7 +110,7 @@ export const en: Record<EffortDeclareKey, string> = {
|
|
|
104
110
|
saving: 'Saving…',
|
|
105
111
|
saveBusy: 'Another route is saving. Wait, then save this card.',
|
|
106
112
|
cancel: 'Cancel',
|
|
107
|
-
saved: 'Saved. The composer Effort row
|
|
113
|
+
saved: 'Saved. The composer Effort row and image admission follow this capability declaration.',
|
|
108
114
|
conflict: 'Settings changed elsewhere. Reload, then save again.',
|
|
109
115
|
dirtyConflict: 'This route has unsaved edits and settings changed elsewhere. Save writes onto the latest document, or cancel first.',
|
|
110
116
|
presets: 'Presets',
|
|
@@ -133,4 +139,7 @@ export const en: Record<EffortDeclareKey, string> = {
|
|
|
133
139
|
errorBadWire: 'Thinking levels need a wire spelling; only Off may be empty. Unknown effort keys are rejected.',
|
|
134
140
|
noModels: 'This route has no models list yet. Add models on the Models page first.',
|
|
135
141
|
reload: 'Reload',
|
|
142
|
+
imageInput: 'Accepts image input',
|
|
143
|
+
imageInputHint: 'Check only if this model actually sees images. Over-claiming admits a picture into history, then the gateway rejects the turn.',
|
|
144
|
+
errorBadInput: 'input may only list text and image, must include text, and must not be empty or duplicated. To drop vision, unset the key; do not write [].',
|
|
136
145
|
}
|
package/src/core/README.en.md
CHANGED
|
@@ -7,22 +7,23 @@
|
|
|
7
7
|
</samp>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
|
-
Domain logic for the plugin: no React, no Cordis service instances. The settings page and Vitest both depend on these pure functions, so effort semantics can regress without booting DSH.
|
|
10
|
+
Domain logic for the plugin: no React, no Cordis service instances. The settings page and Vitest both depend on these pure functions, so reasoning-effort and image-input semantics can regress without booting DSH.
|
|
11
11
|
|
|
12
|
-
Canonical level names and `thinkingFormat` values follow `@deepseek-ai/dsh-llm-pi-ai` (0.1.0-rc.8; 0.1.1-rc.2 uses the same level set). `thinkingFormat` is pinned to the live-schema snapshot in [`tests/fixtures/pi-ai-thinking-format-union.ts`](../../tests/fixtures/pi-ai-thinking-format-union.ts); effort keys stay a locally pinned whitelist. UI choices come only from the live schema union.
|
|
12
|
+
Canonical level names and `thinkingFormat` values follow `@deepseek-ai/dsh-llm-pi-ai` (0.1.0-rc.8; 0.1.1-rc.2 uses the same level set). `thinkingFormat` is pinned to the live-schema snapshot in [`tests/fixtures/pi-ai-thinking-format-union.ts`](../../tests/fixtures/pi-ai-thinking-format-union.ts); effort keys stay a locally pinned whitelist. UI choices come only from the live schema union. Writable `models[].input` modalities are `text` and `image`, matching the official schema.
|
|
13
13
|
|
|
14
14
|
## What's in this directory
|
|
15
15
|
|
|
16
16
|
| File | Role |
|
|
17
17
|
| --- | --- |
|
|
18
|
-
| [`catalog.ts`](./catalog.ts) | Canonical level order, `thinkingFormat` fallback list, `llm-pi-ai` / DeepSeek constants. |
|
|
18
|
+
| [`catalog.ts`](./catalog.ts) | Canonical level order, `thinkingFormat` fallback list, request modalities (`INPUT_MODALITIES` / `IMAGE_CAPABLE_INPUT`), `llm-pi-ai` / DeepSeek constants. |
|
|
19
19
|
| [`efforts.ts`](./efforts.ts) | `reasoningEfforts` read/write, Off tri-state (value mode stores `trim()`), validation (empty object / Off-only / unknown keys return an error code, never throw). |
|
|
20
|
-
| [`
|
|
20
|
+
| [`input.ts`](./input.ts) | Per-model `input`: checking writes `['text', 'image']`, unchecking deletes the key; empty list / image-only / duplicates / unknown modalities return an error code. |
|
|
21
|
+
| [`presets.ts`](./presets.ts) | DeepSeek, OpenAI, and on/off presets; each states all three dialect keys; spread onto existing model rows and route `compat`, leaving existing `input` intact. |
|
|
21
22
|
| [`path-ops.ts`](./path-ops.ts) | Same one-level key diff as the official Models page; `buildSaveOps` uses `settingsPath` and only submits that route’s full `models` table and dirty `compat` keys. |
|
|
22
|
-
| [`drafts.ts`](./drafts.ts) | User-layer drafts, dirty aligned with pathOps, post-save revision. On refresh, latest user-layer model membership wins; unsaved `reasoningEfforts`
|
|
23
|
+
| [`drafts.ts`](./drafts.ts) | User-layer drafts, dirty aligned with pathOps, post-save revision. On refresh, latest user-layer model membership wins; unsaved overlay keys (`reasoningEfforts` and `input` independently, including a cleared key) overlay by id; `compat` is a per-key 3-way merge. Conflict only when a locally dirty field also moved in originals (revision-only bumps and sibling-card saves do not warn). |
|
|
23
24
|
| [`paths.ts`](./paths.ts) | Nested reads; clones of objects and model tables (non-object rows skipped). |
|
|
24
25
|
| [`filter.ts`](./filter.ts) | Which routes are editable: hand-declared `llm-pi-ai` + openai-completions; skip catalog and official DeepSeek. |
|
|
25
|
-
| [`validate.ts`](./validate.ts) | Per-row `reasoningEfforts`
|
|
26
|
+
| [`validate.ts`](./validate.ts) | Per-row validation: `reasoningEfforts` first, then `input`. |
|
|
26
27
|
| [`attribution.ts`](./attribution.ts) | Settings footer line: `version © year Stardust`. Start year is 2026; end year is the UTC year at pack time, not the user’s clock when DSH starts. |
|
|
27
28
|
|
|
28
|
-
An **absent** field means default-off. Do not encode
|
|
29
|
+
An **absent** field means default-off. Do not encode an undeclared effort as `reasoningEfforts: false` — in official semantics `false` strips reasoning from a catalog model. Do not encode “no vision” as `input: []` — the official resolver treats an empty list as absent, but a bad stamp must not be re-saved. Disabling image input must delete the key; do not write `[text]`.
|
package/src/core/README.md
CHANGED
|
@@ -7,22 +7,23 @@
|
|
|
7
7
|
</samp>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
|
-
本插件的领域逻辑:无 React、无 Cordis 服务实例。设置页与 Vitest
|
|
10
|
+
本插件的领域逻辑:无 React、无 Cordis 服务实例。设置页与 Vitest 都只依赖这些纯函数,因此推理档位与图片输入语义可以在不启动 DSH 的情况下回归。
|
|
11
11
|
|
|
12
|
-
规范键名与 `thinkingFormat` 取值对齐 `@deepseek-ai/dsh-llm-pi-ai`(0.1.0-rc.8;0.1.1-rc.2 档位集合相同)。`thinkingFormat` 由 [`tests/fixtures/pi-ai-thinking-format-union.ts`](../../tests/fixtures/pi-ai-thinking-format-union.ts) 钉现场 schema;档位键由测试钉本地白名单。UI 可选项只来自现场 schema union
|
|
12
|
+
规范键名与 `thinkingFormat` 取值对齐 `@deepseek-ai/dsh-llm-pi-ai`(0.1.0-rc.8;0.1.1-rc.2 档位集合相同)。`thinkingFormat` 由 [`tests/fixtures/pi-ai-thinking-format-union.ts`](../../tests/fixtures/pi-ai-thinking-format-union.ts) 钉现场 schema;档位键由测试钉本地白名单。UI 可选项只来自现场 schema union。`models[].input` 的可写模态为 `text` 与 `image`,与官方 schema 一致。
|
|
13
13
|
|
|
14
14
|
## 这个目录里有什么
|
|
15
15
|
|
|
16
16
|
| 文件 | 说明 |
|
|
17
17
|
| --- | --- |
|
|
18
|
-
| [`catalog.ts`](./catalog.ts) | 规范档位顺序、`thinkingFormat`
|
|
18
|
+
| [`catalog.ts`](./catalog.ts) | 规范档位顺序、`thinkingFormat` 回退列表、请求模态(`INPUT_MODALITIES` / `IMAGE_CAPABLE_INPUT`)、`llm-pi-ai` / DeepSeek 相关常量。 |
|
|
19
19
|
| [`efforts.ts`](./efforts.ts) | `reasoningEfforts` 读写、Off 三态(value 模式写入 `trim()` 后的字符串)、校验(空对象 / 只开 Off / 未知键返回错误码,不抛异常)。 |
|
|
20
|
-
| [`
|
|
20
|
+
| [`input.ts`](./input.ts) | 每模型 `input`:勾选写入 `['text', 'image']`,取消删除该键;空列表 / 仅 image / 重复 / 未知模态返回错误码。 |
|
|
21
|
+
| [`presets.ts`](./presets.ts) | DeepSeek、OpenAI、仅开/关三套预设;每套对三个方言键都表态;spread 到已有模型行与路由 `compat`,保留已有 `input`。 |
|
|
21
22
|
| [`path-ops.ts`](./path-ops.ts) | 与官方模型页相同的一层键 diff;`buildSaveOps` 使用 `settingsPath`,只提交该路由的 `models` 整表和有差异的 `compat` 键。 |
|
|
22
|
-
| [`drafts.ts`](./drafts.ts) | 用户层草稿、dirty 与 pathOps 一致、保存后 revision。刷新时以最新用户层模型名单为成员真理,按 id 贴回未保存的 `
|
|
23
|
+
| [`drafts.ts`](./drafts.ts) | 用户层草稿、dirty 与 pathOps 一致、保存后 revision。刷新时以最新用户层模型名单为成员真理,按 id 贴回未保存的 overlay 键(`reasoningEfforts` 与 `input` 独立,含已清除 = 删键);`compat` 按键三路合并。冲突仅当本地脏字段的 originals 也变了(只 bump revision 或别的卡保存不报)。 |
|
|
23
24
|
| [`paths.ts`](./paths.ts) | 嵌套读取、对象与模型表的 clone(非对象行跳过)。 |
|
|
24
25
|
| [`filter.ts`](./filter.ts) | 哪些路由可编辑:手工 `llm-pi-ai` + openai-completions;排除 catalog 与官方 DeepSeek。 |
|
|
25
|
-
| [`validate.ts`](./validate.ts) |
|
|
26
|
+
| [`validate.ts`](./validate.ts) | 对单行先校验 `reasoningEfforts`,再校验 `input`。 |
|
|
26
27
|
| [`attribution.ts`](./attribution.ts) | 设置页页脚:`version © 年 Stardust`。首年写死 2026,结束年由打包时的 UTC 年注入,不是用户打开 DSH 的日期。 |
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
**缺席字段**表示默认关闭。不要把「未声明档位」写成 `reasoningEfforts: false`——`false` 在官方语义里是从 catalog 模型上剥掉推理。不要把「未声明视觉」写成 `input: []`——官方把空列表当成缺席,但坏戳记不应被重新保存。取消图片输入必须删除该键,而不是写成 `[text]`。
|
package/src/core/catalog.ts
CHANGED
|
@@ -61,3 +61,14 @@ export const DEEPSEEK_OFFICIAL = 'deepseek-official'
|
|
|
61
61
|
|
|
62
62
|
/** Any route key walks a dict schema to the same profile node. */
|
|
63
63
|
export const SCHEMA_PROBE_ROUTE = '\u0000probe'
|
|
64
|
+
|
|
65
|
+
/** Request modalities llm-pi-ai accepts on `models[].input` / `defaultInput`. */
|
|
66
|
+
export const INPUT_MODALITIES = ['text', 'image'] as const
|
|
67
|
+
|
|
68
|
+
export type InputModality = (typeof INPUT_MODALITIES)[number]
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Canonical write for a hand-declared vision model.
|
|
72
|
+
* Must include `text`; image-only is not a serviceable OpenAI-completions route.
|
|
73
|
+
*/
|
|
74
|
+
export const IMAGE_CAPABLE_INPUT: readonly InputModality[] = ['text', 'image']
|
package/src/core/drafts.ts
CHANGED
|
@@ -112,31 +112,42 @@ function indexById(rows: readonly Record<string, unknown>[]): Map<string, Record
|
|
|
112
112
|
return map
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
/** Model-row fields this page edits; other keys follow the Models page. */
|
|
116
|
+
export const MODEL_OVERLAY_KEYS = ['reasoningEfforts', 'input'] as const
|
|
117
|
+
|
|
118
|
+
export type ModelOverlayKey = (typeof MODEL_OVERLAY_KEYS)[number]
|
|
119
|
+
|
|
120
|
+
function fieldPresence(
|
|
121
|
+
row: Record<string, unknown> | undefined,
|
|
122
|
+
key: ModelOverlayKey,
|
|
123
|
+
): { present: boolean; value: unknown } {
|
|
124
|
+
if (row === undefined || !Object.hasOwn(row, key)) {
|
|
117
125
|
return { present: false, value: undefined }
|
|
118
126
|
}
|
|
119
|
-
return { present: true, value: row
|
|
127
|
+
return { present: true, value: row[key] }
|
|
120
128
|
}
|
|
121
129
|
|
|
122
|
-
function
|
|
123
|
-
left:
|
|
124
|
-
right:
|
|
130
|
+
function fieldEqual(
|
|
131
|
+
left: Record<string, unknown> | undefined,
|
|
132
|
+
right: Record<string, unknown> | undefined,
|
|
133
|
+
key: ModelOverlayKey,
|
|
125
134
|
): boolean {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
135
|
+
const a = fieldPresence(left, key)
|
|
136
|
+
const b = fieldPresence(right, key)
|
|
137
|
+
if (a.present !== b.present) return false
|
|
138
|
+
if (!a.present) return true
|
|
139
|
+
return sliceEqual(a.value, b.value)
|
|
129
140
|
}
|
|
130
141
|
|
|
131
|
-
function
|
|
142
|
+
function overlayLocalFields(
|
|
132
143
|
incomingRow: Record<string, unknown>,
|
|
133
144
|
prevRow: Record<string, unknown>,
|
|
145
|
+
keys: readonly ModelOverlayKey[],
|
|
134
146
|
): Record<string, unknown> {
|
|
135
147
|
const next = structuredClone(incomingRow)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
delete next.reasoningEfforts
|
|
148
|
+
for (const key of keys) {
|
|
149
|
+
if (Object.hasOwn(prevRow, key)) next[key] = structuredClone(prevRow[key])
|
|
150
|
+
else delete next[key]
|
|
140
151
|
}
|
|
141
152
|
return next
|
|
142
153
|
}
|
|
@@ -155,7 +166,8 @@ function objectKeyChanged(
|
|
|
155
166
|
|
|
156
167
|
/**
|
|
157
168
|
* Membership follows the latest user-layer models list (Models page add/delete).
|
|
158
|
-
* Local unsaved
|
|
169
|
+
* Local unsaved overlay keys (including a cleared key) overlay by id; other
|
|
170
|
+
* fields on the row follow incoming.
|
|
159
171
|
*/
|
|
160
172
|
export function mergeModelsById(args: {
|
|
161
173
|
prevModels: readonly Record<string, unknown>[]
|
|
@@ -174,21 +186,23 @@ export function mergeModelsById(args: {
|
|
|
174
186
|
const prevRow = prevById.get(id)
|
|
175
187
|
if (prevRow === undefined) return structuredClone(incomingRow)
|
|
176
188
|
const prevOrig = prevOrigById.get(id)
|
|
177
|
-
const
|
|
178
|
-
if (
|
|
189
|
+
const dirtyKeys = MODEL_OVERLAY_KEYS.filter(key => !fieldEqual(prevRow, prevOrig, key))
|
|
190
|
+
if (dirtyKeys.length === 0) return structuredClone(incomingRow)
|
|
179
191
|
const incomingOrig = incomingOrigById.get(id)
|
|
180
|
-
|
|
181
|
-
conflicted = true
|
|
192
|
+
for (const key of dirtyKeys) {
|
|
193
|
+
if (!fieldEqual(prevOrig, incomingOrig, key)) conflicted = true
|
|
182
194
|
}
|
|
183
|
-
return
|
|
195
|
+
return overlayLocalFields(incomingRow, prevRow, dirtyKeys)
|
|
184
196
|
})
|
|
185
197
|
|
|
186
198
|
for (const [id, prevRow] of prevById) {
|
|
187
199
|
if (incomingIds.has(id)) continue
|
|
188
200
|
const prevOrig = prevOrigById.get(id)
|
|
189
|
-
|
|
190
|
-
if (
|
|
191
|
-
|
|
201
|
+
const dirtyKeys = MODEL_OVERLAY_KEYS.filter(key => !fieldEqual(prevRow, prevOrig, key))
|
|
202
|
+
if (dirtyKeys.length === 0) continue
|
|
203
|
+
const incomingOrig = incomingOrigById.get(id)
|
|
204
|
+
for (const key of dirtyKeys) {
|
|
205
|
+
if (!fieldEqual(prevOrig, incomingOrig, key)) conflicted = true
|
|
192
206
|
}
|
|
193
207
|
}
|
|
194
208
|
|
|
@@ -222,9 +236,9 @@ export function mergeCompat(args: {
|
|
|
222
236
|
|
|
223
237
|
/**
|
|
224
238
|
* Apply a freshly loaded table. Membership and metadata follow incoming;
|
|
225
|
-
* unsaved reasoningEfforts / dirty compat keys
|
|
226
|
-
* when a locally dirty field also changed in
|
|
227
|
-
* and sibling-card saves do not warn).
|
|
239
|
+
* unsaved overlay keys (`reasoningEfforts`, `input`) / dirty compat keys
|
|
240
|
+
* overlay by id. Conflict only when a locally dirty field also changed in
|
|
241
|
+
* originals (revision-only bumps and sibling-card saves do not warn).
|
|
228
242
|
*/
|
|
229
243
|
export function mergeLoadedDrafts(
|
|
230
244
|
current: readonly RouteDraft[],
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-model `input` modalities as stored in llm-pi-ai settings.
|
|
3
|
+
* Absence falls through to route `defaultInput` (`[text]`). Empty list is
|
|
4
|
+
* treated as absent by the official resolver — do not write `[]`.
|
|
5
|
+
*/
|
|
6
|
+
import { IMAGE_CAPABLE_INPUT, INPUT_MODALITIES } from './catalog.ts'
|
|
7
|
+
|
|
8
|
+
/** Whether the stored `input` list includes image. Absence is false. */
|
|
9
|
+
export function readImageCapable(row: Record<string, unknown>): boolean {
|
|
10
|
+
const value = row.input
|
|
11
|
+
return Array.isArray(value) && value.includes('image')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Declare or drop image input on a spread row.
|
|
16
|
+
* Enabled writes `[text, image]`; disabled unsets the key (not `[]`, not `[text]`).
|
|
17
|
+
*/
|
|
18
|
+
export function writeImageCapable(
|
|
19
|
+
row: Record<string, unknown>,
|
|
20
|
+
enabled: boolean,
|
|
21
|
+
): Record<string, unknown> {
|
|
22
|
+
const next = { ...row }
|
|
23
|
+
if (enabled) next.input = [...IMAGE_CAPABLE_INPUT]
|
|
24
|
+
else delete next.input
|
|
25
|
+
return next
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Validate a stored `input` field. Returns an error code; never throws.
|
|
30
|
+
* Absence is valid. Empty list is rejected so a bad stamp cannot be re-saved.
|
|
31
|
+
*/
|
|
32
|
+
export function validateInput(input: unknown): 'bad-input' | undefined {
|
|
33
|
+
if (input === undefined) return undefined
|
|
34
|
+
if (!Array.isArray(input) || input.length === 0) return 'bad-input'
|
|
35
|
+
const known = new Set<string>(INPUT_MODALITIES)
|
|
36
|
+
const seen = new Set<string>()
|
|
37
|
+
for (const item of input) {
|
|
38
|
+
if (typeof item !== 'string' || !known.has(item) || seen.has(item)) return 'bad-input'
|
|
39
|
+
seen.add(item)
|
|
40
|
+
}
|
|
41
|
+
if (!seen.has('text')) return 'bad-input'
|
|
42
|
+
return undefined
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Per-model client-side check used to show errors without throwing. */
|
|
46
|
+
export function modelInputError(row: Record<string, unknown>): 'bad-input' | undefined {
|
|
47
|
+
if (!('input' in row) || row.input === undefined) return undefined
|
|
48
|
+
return validateInput(row.input)
|
|
49
|
+
}
|
package/src/core/validate.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { validateReasoningEfforts } from './efforts.ts'
|
|
2
|
+
import { modelInputError } from './input.ts'
|
|
3
|
+
|
|
4
|
+
export type ModelRowError = 'empty' | 'off-only' | 'bad-wire' | 'bad-input'
|
|
2
5
|
|
|
3
6
|
/** Per-model client-side check used to show errors without throwing. */
|
|
4
7
|
export function modelEffortError(row: Record<string, unknown>): 'empty' | 'off-only' | 'bad-wire' | undefined {
|
|
5
8
|
if (!('reasoningEfforts' in row) || row.reasoningEfforts === undefined) return undefined
|
|
6
9
|
return validateReasoningEfforts(row.reasoningEfforts)
|
|
7
10
|
}
|
|
11
|
+
|
|
12
|
+
/** First blocking error on a model row (efforts, then input). */
|
|
13
|
+
export function modelRowError(row: Record<string, unknown>): ModelRowError | undefined {
|
|
14
|
+
return modelEffortError(row) ?? modelInputError(row)
|
|
15
|
+
}
|