@fonlan/dsh-model-parameters 0.1.0
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/LICENSE +21 -0
- package/README.en.md +140 -0
- package/README.md +112 -0
- package/cordis.patch.yml +3 -0
- package/lib/client.js +586 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +637 -0
- package/lib/types/index.d.ts +20 -0
- package/lib/types/server/catalog.d.ts +83 -0
- package/lib/types/server/rpc.d.ts +3 -0
- package/lib/types/server/service.d.ts +66 -0
- package/lib/types/shared/config.d.ts +53 -0
- package/package.json +98 -0
- package/src/client/api.ts +59 -0
- package/src/client/index.tsx +33 -0
- package/src/client/locales.ts +95 -0
- package/src/client/settings-section.css +172 -0
- package/src/client/settings-section.tsx +310 -0
- package/src/client/slot-augment.d.ts +14 -0
- package/src/index.ts +47 -0
- package/src/server/catalog.ts +300 -0
- package/src/server/rpc.ts +162 -0
- package/src/server/service.ts +258 -0
- package/src/shared/config.ts +106 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@fonlan/dsh-model-parameters",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react = require("react");
|
|
8
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
+
//#region src/client/locales.ts
|
|
10
|
+
/**
|
|
11
|
+
* Locale dictionaries for @fonlan/dsh-model-parameters settings page.
|
|
12
|
+
* zh is the key source of truth; en must carry the exact same key set.
|
|
13
|
+
*/
|
|
14
|
+
const LOCALE_NS = "model-parameters";
|
|
15
|
+
const zh = {
|
|
16
|
+
settingsTitle: "模型参数补全",
|
|
17
|
+
description: "同步模型时自动从 models.dev 目录补全缺失的显示名称、最大上下文、最大输出、推理强度与输入模态。",
|
|
18
|
+
master: "启用自动补全",
|
|
19
|
+
masterDesc: "关闭后不再自动补全任何字段(已补全的值保留)。",
|
|
20
|
+
fields: "补全字段",
|
|
21
|
+
fillName: "显示名称",
|
|
22
|
+
fillNameDesc: "模型条目缺 name 时写入目录中的显示名称。",
|
|
23
|
+
fillContext: "最大上下文",
|
|
24
|
+
fillContextDesc: "缺 contextWindow 时写入目录的上下文上限。",
|
|
25
|
+
fillMaxTokens: "最大输出",
|
|
26
|
+
fillMaxTokensDesc: "缺 maxTokens 时写入目录的输出上限。",
|
|
27
|
+
fillReasoning: "推理强度",
|
|
28
|
+
fillReasoningDesc: "缺 reasoningEfforts 时按目录 effort 档位写入 identity 字典(如 {high:\"high\", max:\"max\"})。",
|
|
29
|
+
fillInput: "输入模态",
|
|
30
|
+
fillInputDesc: "缺 input 时写入目录中的 text/image 模态。",
|
|
31
|
+
ttl: "目录更新周期(天)",
|
|
32
|
+
ttlDesc: "缓存的 models.dev 目录超过该天数后,在下次补全时自动刷新。",
|
|
33
|
+
providerMap: "Provider 映射",
|
|
34
|
+
providerMapDesc: "dsh provider → models.dev provider 的匹配覆盖(可选)。",
|
|
35
|
+
addMapping: "添加映射",
|
|
36
|
+
removeMapping: "移除",
|
|
37
|
+
catalog: "模型目录",
|
|
38
|
+
catalogEntries: "目录条目",
|
|
39
|
+
catalogProviders: "覆盖 Provider",
|
|
40
|
+
catalogFetched: "上次更新",
|
|
41
|
+
catalogFresh: "新鲜",
|
|
42
|
+
catalogStale: "已过期",
|
|
43
|
+
catalogNever: "尚未获取",
|
|
44
|
+
refresh: "立即更新目录并补全",
|
|
45
|
+
refreshBusy: "更新中…",
|
|
46
|
+
reconcileNow: "立即补全一次",
|
|
47
|
+
report: "最近一次补全",
|
|
48
|
+
reportNever: "尚未执行",
|
|
49
|
+
reportFilled: "补全字段",
|
|
50
|
+
reportTouched: "涉及 Provider",
|
|
51
|
+
reportUnmatched: "未匹配模型",
|
|
52
|
+
reportEmpty: "全部匹配,无未匹配项",
|
|
53
|
+
reportAt: "时间",
|
|
54
|
+
error: "加载失败",
|
|
55
|
+
saving: "保存中…",
|
|
56
|
+
officialProviders: "官方厂商优先级",
|
|
57
|
+
officialProvidersDesc: "同一模型 id 在多处出现时,优先采用这些 models.dev provider 的条目。"
|
|
58
|
+
};
|
|
59
|
+
const en = {
|
|
60
|
+
settingsTitle: "Model Parameters",
|
|
61
|
+
description: "Auto-fill missing display name, context window, max output, reasoning efforts and input modalities from the models.dev catalog when syncing models.",
|
|
62
|
+
master: "Enable auto-fill",
|
|
63
|
+
masterDesc: "When off, no fields are filled (already-filled values are kept).",
|
|
64
|
+
fields: "Fields to fill",
|
|
65
|
+
fillName: "Display name",
|
|
66
|
+
fillNameDesc: "Write the catalog display name when a model entry lacks name.",
|
|
67
|
+
fillContext: "Context window",
|
|
68
|
+
fillContextDesc: "Write the catalog context limit when contextWindow is missing.",
|
|
69
|
+
fillMaxTokens: "Max output",
|
|
70
|
+
fillMaxTokensDesc: "Write the catalog output limit when maxTokens is missing.",
|
|
71
|
+
fillReasoning: "Reasoning efforts",
|
|
72
|
+
fillReasoningDesc: "Write an identity reasoningEfforts dict (e.g. {high:\"high\", max:\"max\"}) from catalog effort levels when missing.",
|
|
73
|
+
fillInput: "Input modalities",
|
|
74
|
+
fillInputDesc: "Write text/image modalities from the catalog when input is missing.",
|
|
75
|
+
ttl: "Catalog refresh interval (days)",
|
|
76
|
+
ttlDesc: "The cached models.dev catalog is refreshed on the next fill once older than this many days.",
|
|
77
|
+
providerMap: "Provider mapping",
|
|
78
|
+
providerMapDesc: "Optional dsh provider → models.dev provider matching overrides.",
|
|
79
|
+
addMapping: "Add mapping",
|
|
80
|
+
removeMapping: "Remove",
|
|
81
|
+
catalog: "Model catalog",
|
|
82
|
+
catalogEntries: "Catalog entries",
|
|
83
|
+
catalogProviders: "Providers covered",
|
|
84
|
+
catalogFetched: "Last updated",
|
|
85
|
+
catalogFresh: "fresh",
|
|
86
|
+
catalogStale: "stale",
|
|
87
|
+
catalogNever: "not fetched yet",
|
|
88
|
+
refresh: "Update catalog & fill now",
|
|
89
|
+
refreshBusy: "Updating…",
|
|
90
|
+
reconcileNow: "Fill now",
|
|
91
|
+
report: "Last fill",
|
|
92
|
+
reportNever: "never run",
|
|
93
|
+
reportFilled: "fields filled",
|
|
94
|
+
reportTouched: "providers touched",
|
|
95
|
+
reportUnmatched: "unmatched models",
|
|
96
|
+
reportEmpty: "all matched, nothing unmatched",
|
|
97
|
+
reportAt: "time",
|
|
98
|
+
error: "Failed to load",
|
|
99
|
+
saving: "Saving…",
|
|
100
|
+
officialProviders: "Official provider priority",
|
|
101
|
+
officialProvidersDesc: "When one model id appears under several providers, prefer entries from these models.dev providers."
|
|
102
|
+
};
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/client/api.ts
|
|
105
|
+
var ModelParametersApiError = class extends Error {
|
|
106
|
+
code;
|
|
107
|
+
constructor(code, message) {
|
|
108
|
+
super(message);
|
|
109
|
+
this.code = code;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
async function call(method, payload = {}) {
|
|
113
|
+
let response;
|
|
114
|
+
try {
|
|
115
|
+
response = await fetch(`/plugins/@fonlan/dsh-model-parameters/api/${method}`, {
|
|
116
|
+
method: "POST",
|
|
117
|
+
headers: { "content-type": "application/json" },
|
|
118
|
+
body: JSON.stringify(payload)
|
|
119
|
+
});
|
|
120
|
+
} catch (error) {
|
|
121
|
+
throw new ModelParametersApiError("network", error instanceof Error ? error.message : String(error));
|
|
122
|
+
}
|
|
123
|
+
const parsed = await response.json().catch(() => null);
|
|
124
|
+
if (!response.ok || parsed === null || parsed.ok !== true || parsed.value === void 0) throw new ModelParametersApiError(parsed?.error?.code ?? "http", parsed?.error?.message ?? `HTTP ${response.status}`);
|
|
125
|
+
return parsed.value;
|
|
126
|
+
}
|
|
127
|
+
const api = {
|
|
128
|
+
state: () => call("state"),
|
|
129
|
+
refresh: () => call("refresh"),
|
|
130
|
+
reconcile: () => call("reconcile"),
|
|
131
|
+
setConfig: (patch) => call("set-config", { patch })
|
|
132
|
+
};
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region \0dsh-css:/Users/fonlan/Repos/dsh-model-parameters/src/client/settings-section.css.mjs
|
|
135
|
+
const css = ".mp-root {\n display: flex;\n flex-direction: column;\n gap: 14px;\n max-width: 720px;\n}\n\n.mp-desc {\n margin: 0;\n opacity: 0.85;\n font-size: 13px;\n line-height: 1.5;\n}\n\n.mp-card {\n border: 1px solid var(--dsh-border-color, rgba(128, 128, 128, 0.25));\n border-radius: 10px;\n padding: 12px 14px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n background: var(--dsh-surface-color, transparent);\n}\n\n.mp-card-title {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n opacity: 0.9;\n}\n\n.mp-toggle {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n cursor: pointer;\n}\n\n.mp-toggle input[type='checkbox'] {\n margin-top: 3px;\n accent-color: var(--dsh-accent-color, #4f8cff);\n}\n\n.mp-toggle-sub {\n padding-left: 22px;\n}\n\n.mp-toggle-body {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.mp-toggle-label {\n font-size: 13px;\n}\n\n.mp-toggle-desc {\n font-size: 12px;\n opacity: 0.65;\n}\n\n.mp-meta {\n display: flex;\n flex-wrap: wrap;\n gap: 6px 16px;\n font-size: 13px;\n}\n\n.mp-ttl {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n}\n\n.mp-ttl input {\n width: 64px;\n padding: 3px 6px;\n border-radius: 6px;\n border: 1px solid var(--dsh-border-color, rgba(128, 128, 128, 0.35));\n background: var(--dsh-input-bg, transparent);\n color: inherit;\n}\n\n.mp-actions {\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.mp-button {\n padding: 6px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsh-border-color, rgba(128, 128, 128, 0.35));\n background: var(--dsh-accent-color, #4f8cff);\n color: #fff;\n font-size: 13px;\n cursor: pointer;\n}\n\n.mp-button:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n.mp-button-ghost {\n background: transparent;\n color: inherit;\n}\n\n.mp-map-row {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.mp-map-row input {\n flex: 1;\n padding: 4px 8px;\n border-radius: 6px;\n border: 1px solid var(--dsh-border-color, rgba(128, 128, 128, 0.35));\n background: var(--dsh-input-bg, transparent);\n color: inherit;\n font-size: 13px;\n}\n\n.mp-map-arrow {\n opacity: 0.6;\n}\n\n.mp-report {\n margin: 0;\n padding-left: 18px;\n font-size: 13px;\n line-height: 1.7;\n}\n\n.mp-code {\n background: var(--dsh-code-bg, rgba(128, 128, 128, 0.15));\n border-radius: 4px;\n padding: 1px 5px;\n font-size: 12px;\n word-break: break-all;\n}\n\n.mp-badge {\n font-size: 11px;\n padding: 1px 7px;\n border-radius: 999px;\n margin-left: 4px;\n}\n\n.mp-badge-ok {\n background: rgba(52, 199, 89, 0.18);\n color: #34c759;\n}\n\n.mp-badge-warn {\n background: rgba(255, 149, 0, 0.18);\n color: #ff9500;\n}\n\n.mp-muted {\n font-size: 13px;\n opacity: 0.6;\n}\n\n.mp-error {\n font-size: 13px;\n color: #ff3b30;\n}\n";
|
|
136
|
+
const tagId = "@fonlan/dsh-model-parameters/settings-section.css";
|
|
137
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
138
|
+
const tag = document.createElement("style");
|
|
139
|
+
tag.dataset.plugin = "@fonlan/dsh-model-parameters";
|
|
140
|
+
tag.dataset.pluginCss = tagId;
|
|
141
|
+
tag.textContent = css;
|
|
142
|
+
document.head.appendChild(tag);
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region src/client/settings-section.tsx
|
|
146
|
+
/**
|
|
147
|
+
* The Model Parameters settings page: master + per-field toggles, catalog TTL,
|
|
148
|
+
* provider→models.dev mapping overrides, a manual refresh/fill action, the
|
|
149
|
+
* catalog freshness card, and the last fill report (filled / touched /
|
|
150
|
+
* unmatched). All reads/mutations go through the plugin's fenced API; config
|
|
151
|
+
* persists into the `model-parameters` settings namespace (settings.yaml).
|
|
152
|
+
*/
|
|
153
|
+
function useLocaleRevision(ctx) {
|
|
154
|
+
const subscribe = (0, react.useCallback)((onChange) => {
|
|
155
|
+
try {
|
|
156
|
+
return ctx.locale.subscribe(onChange);
|
|
157
|
+
} catch {
|
|
158
|
+
return () => {};
|
|
159
|
+
}
|
|
160
|
+
}, [ctx]);
|
|
161
|
+
const getSnapshot = (0, react.useCallback)(() => {
|
|
162
|
+
try {
|
|
163
|
+
return ctx.locale.getLocale().revision;
|
|
164
|
+
} catch {
|
|
165
|
+
return 0;
|
|
166
|
+
}
|
|
167
|
+
}, [ctx]);
|
|
168
|
+
return (0, react.useSyncExternalStore)(subscribe, getSnapshot);
|
|
169
|
+
}
|
|
170
|
+
function formatTime(epochMs) {
|
|
171
|
+
try {
|
|
172
|
+
return new Date(epochMs).toLocaleString();
|
|
173
|
+
} catch {
|
|
174
|
+
return String(epochMs);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function makeSettingsSection(ctx) {
|
|
178
|
+
const t = (() => {
|
|
179
|
+
try {
|
|
180
|
+
return ctx.locale.bind(LOCALE_NS);
|
|
181
|
+
} catch {
|
|
182
|
+
return (key) => key;
|
|
183
|
+
}
|
|
184
|
+
})();
|
|
185
|
+
return function ModelParametersSettingsSection() {
|
|
186
|
+
useLocaleRevision(ctx);
|
|
187
|
+
const [state, setState] = (0, react.useState)(null);
|
|
188
|
+
const [error, setError] = (0, react.useState)(null);
|
|
189
|
+
const [loading, setLoading] = (0, react.useState)(true);
|
|
190
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
191
|
+
const [saving, setSaving] = (0, react.useState)(false);
|
|
192
|
+
const [mappingRows, setMappingRows] = (0, react.useState)([]);
|
|
193
|
+
const [newFrom, setNewFrom] = (0, react.useState)("");
|
|
194
|
+
const [newTo, setNewTo] = (0, react.useState)("");
|
|
195
|
+
const load = (0, react.useCallback)(async () => {
|
|
196
|
+
setLoading(true);
|
|
197
|
+
try {
|
|
198
|
+
const next = await api.state();
|
|
199
|
+
setState(next);
|
|
200
|
+
setMappingRows(Object.entries(next.config.providerMap).map(([from, to]) => ({
|
|
201
|
+
from,
|
|
202
|
+
to
|
|
203
|
+
})));
|
|
204
|
+
setError(null);
|
|
205
|
+
} catch (cause) {
|
|
206
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
207
|
+
} finally {
|
|
208
|
+
setLoading(false);
|
|
209
|
+
}
|
|
210
|
+
}, []);
|
|
211
|
+
(0, react.useEffect)(() => {
|
|
212
|
+
load();
|
|
213
|
+
}, [load]);
|
|
214
|
+
const patchConfig = (0, react.useCallback)(async (patch) => {
|
|
215
|
+
setSaving(true);
|
|
216
|
+
try {
|
|
217
|
+
const next = await api.setConfig(patch);
|
|
218
|
+
setState(next);
|
|
219
|
+
setMappingRows(Object.entries(next.config.providerMap).map(([from, to]) => ({
|
|
220
|
+
from,
|
|
221
|
+
to
|
|
222
|
+
})));
|
|
223
|
+
setError(null);
|
|
224
|
+
} catch (cause) {
|
|
225
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
226
|
+
} finally {
|
|
227
|
+
setSaving(false);
|
|
228
|
+
}
|
|
229
|
+
}, []);
|
|
230
|
+
const refresh = (0, react.useCallback)(async () => {
|
|
231
|
+
setBusy(true);
|
|
232
|
+
try {
|
|
233
|
+
setState(await api.refresh());
|
|
234
|
+
setError(null);
|
|
235
|
+
} catch (cause) {
|
|
236
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
237
|
+
} finally {
|
|
238
|
+
setBusy(false);
|
|
239
|
+
}
|
|
240
|
+
}, []);
|
|
241
|
+
const reconcileNow = (0, react.useCallback)(async () => {
|
|
242
|
+
setBusy(true);
|
|
243
|
+
try {
|
|
244
|
+
setState(await api.reconcile());
|
|
245
|
+
setError(null);
|
|
246
|
+
} catch (cause) {
|
|
247
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
248
|
+
} finally {
|
|
249
|
+
setBusy(false);
|
|
250
|
+
}
|
|
251
|
+
}, []);
|
|
252
|
+
const applyMappings = (0, react.useCallback)(async () => {
|
|
253
|
+
const providerMap = {};
|
|
254
|
+
for (const row of mappingRows) {
|
|
255
|
+
const from = row.from.trim();
|
|
256
|
+
const to = row.to.trim();
|
|
257
|
+
if (from.length > 0 && to.length > 0) providerMap[from] = to;
|
|
258
|
+
}
|
|
259
|
+
await patchConfig({ providerMap });
|
|
260
|
+
}, [mappingRows, patchConfig]);
|
|
261
|
+
if (error !== null && state === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
262
|
+
className: "mp-error",
|
|
263
|
+
children: [
|
|
264
|
+
t("error"),
|
|
265
|
+
": ",
|
|
266
|
+
error
|
|
267
|
+
]
|
|
268
|
+
});
|
|
269
|
+
if (loading || state === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
270
|
+
className: "mp-muted",
|
|
271
|
+
children: t("saving")
|
|
272
|
+
});
|
|
273
|
+
const config = state.config;
|
|
274
|
+
const catalog = state.catalog;
|
|
275
|
+
const report = state.report;
|
|
276
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
277
|
+
className: "mp-root",
|
|
278
|
+
children: [
|
|
279
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
280
|
+
className: "mp-desc",
|
|
281
|
+
children: t("description")
|
|
282
|
+
}),
|
|
283
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
284
|
+
className: "mp-card",
|
|
285
|
+
children: [
|
|
286
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
287
|
+
className: "mp-card-title",
|
|
288
|
+
children: t("fields")
|
|
289
|
+
}),
|
|
290
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
291
|
+
className: "mp-toggle",
|
|
292
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
293
|
+
type: "checkbox",
|
|
294
|
+
checked: config.enabled,
|
|
295
|
+
disabled: saving,
|
|
296
|
+
onChange: (event) => void patchConfig({ enabled: event.target.checked })
|
|
297
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
298
|
+
className: "mp-toggle-body",
|
|
299
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
300
|
+
className: "mp-toggle-label",
|
|
301
|
+
children: t("master")
|
|
302
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
303
|
+
className: "mp-toggle-desc",
|
|
304
|
+
children: t("masterDesc")
|
|
305
|
+
})]
|
|
306
|
+
})]
|
|
307
|
+
}),
|
|
308
|
+
[
|
|
309
|
+
[
|
|
310
|
+
"fillName",
|
|
311
|
+
t("fillName"),
|
|
312
|
+
t("fillNameDesc")
|
|
313
|
+
],
|
|
314
|
+
[
|
|
315
|
+
"fillContext",
|
|
316
|
+
t("fillContext"),
|
|
317
|
+
t("fillContextDesc")
|
|
318
|
+
],
|
|
319
|
+
[
|
|
320
|
+
"fillMaxTokens",
|
|
321
|
+
t("fillMaxTokens"),
|
|
322
|
+
t("fillMaxTokensDesc")
|
|
323
|
+
],
|
|
324
|
+
[
|
|
325
|
+
"fillReasoning",
|
|
326
|
+
t("fillReasoning"),
|
|
327
|
+
t("fillReasoningDesc")
|
|
328
|
+
],
|
|
329
|
+
[
|
|
330
|
+
"fillInput",
|
|
331
|
+
t("fillInput"),
|
|
332
|
+
t("fillInputDesc")
|
|
333
|
+
]
|
|
334
|
+
].map(([key, label, desc]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
335
|
+
className: "mp-toggle mp-toggle-sub",
|
|
336
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
337
|
+
type: "checkbox",
|
|
338
|
+
checked: config[key],
|
|
339
|
+
disabled: saving || !config.enabled,
|
|
340
|
+
onChange: (event) => void patchConfig({ [key]: event.target.checked })
|
|
341
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
342
|
+
className: "mp-toggle-body",
|
|
343
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
344
|
+
className: "mp-toggle-label",
|
|
345
|
+
children: label
|
|
346
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
347
|
+
className: "mp-toggle-desc",
|
|
348
|
+
children: desc
|
|
349
|
+
})]
|
|
350
|
+
})]
|
|
351
|
+
}, key))
|
|
352
|
+
]
|
|
353
|
+
}),
|
|
354
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
355
|
+
className: "mp-card",
|
|
356
|
+
children: [
|
|
357
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
358
|
+
className: "mp-card-title",
|
|
359
|
+
children: t("catalog")
|
|
360
|
+
}),
|
|
361
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
362
|
+
className: "mp-meta",
|
|
363
|
+
children: [
|
|
364
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
365
|
+
t("catalogEntries"),
|
|
366
|
+
": ",
|
|
367
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: catalog.entries.toLocaleString() })
|
|
368
|
+
] }),
|
|
369
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
370
|
+
t("catalogProviders"),
|
|
371
|
+
": ",
|
|
372
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: catalog.providers.toLocaleString() })
|
|
373
|
+
] }),
|
|
374
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
375
|
+
t("catalogFetched"),
|
|
376
|
+
":",
|
|
377
|
+
" ",
|
|
378
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: catalog.fetchedAt > 0 ? formatTime(catalog.fetchedAt) : t("catalogNever") }),
|
|
379
|
+
" ",
|
|
380
|
+
catalog.fetchedAt > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
381
|
+
className: catalog.fresh ? "mp-badge mp-badge-ok" : "mp-badge mp-badge-warn",
|
|
382
|
+
children: catalog.fresh ? t("catalogFresh") : t("catalogStale")
|
|
383
|
+
})
|
|
384
|
+
] }),
|
|
385
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
386
|
+
className: "mp-ttl",
|
|
387
|
+
children: [t("ttl"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
388
|
+
type: "number",
|
|
389
|
+
min: 1,
|
|
390
|
+
max: 90,
|
|
391
|
+
value: config.ttlDays,
|
|
392
|
+
disabled: saving,
|
|
393
|
+
onChange: (event) => {
|
|
394
|
+
const days = Number(event.target.value);
|
|
395
|
+
if (Number.isFinite(days) && days >= 1 && days <= 90) patchConfig({ ttlDays: Math.round(days) });
|
|
396
|
+
}
|
|
397
|
+
})]
|
|
398
|
+
})
|
|
399
|
+
]
|
|
400
|
+
}),
|
|
401
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
402
|
+
className: "mp-actions",
|
|
403
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
404
|
+
className: "mp-button",
|
|
405
|
+
disabled: busy,
|
|
406
|
+
onClick: () => void refresh(),
|
|
407
|
+
children: busy ? t("refreshBusy") : t("refresh")
|
|
408
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
409
|
+
className: "mp-button mp-button-ghost",
|
|
410
|
+
disabled: busy,
|
|
411
|
+
onClick: () => void reconcileNow(),
|
|
412
|
+
children: t("reconcileNow")
|
|
413
|
+
})]
|
|
414
|
+
})
|
|
415
|
+
]
|
|
416
|
+
}),
|
|
417
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
418
|
+
className: "mp-card",
|
|
419
|
+
children: [
|
|
420
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
421
|
+
className: "mp-card-title",
|
|
422
|
+
children: t("providerMap")
|
|
423
|
+
}),
|
|
424
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
425
|
+
className: "mp-toggle-desc",
|
|
426
|
+
children: t("providerMapDesc")
|
|
427
|
+
}),
|
|
428
|
+
mappingRows.map((row, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
429
|
+
className: "mp-map-row",
|
|
430
|
+
children: [
|
|
431
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
432
|
+
value: row.from,
|
|
433
|
+
disabled: saving,
|
|
434
|
+
onChange: (event) => {
|
|
435
|
+
const next = [...mappingRows];
|
|
436
|
+
next[index] = {
|
|
437
|
+
...row,
|
|
438
|
+
from: event.target.value
|
|
439
|
+
};
|
|
440
|
+
setMappingRows(next);
|
|
441
|
+
}
|
|
442
|
+
}),
|
|
443
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
444
|
+
className: "mp-map-arrow",
|
|
445
|
+
children: "→"
|
|
446
|
+
}),
|
|
447
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
448
|
+
value: row.to,
|
|
449
|
+
disabled: saving,
|
|
450
|
+
onChange: (event) => {
|
|
451
|
+
const next = [...mappingRows];
|
|
452
|
+
next[index] = {
|
|
453
|
+
...row,
|
|
454
|
+
to: event.target.value
|
|
455
|
+
};
|
|
456
|
+
setMappingRows(next);
|
|
457
|
+
}
|
|
458
|
+
}),
|
|
459
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
460
|
+
className: "mp-button mp-button-ghost",
|
|
461
|
+
disabled: saving,
|
|
462
|
+
onClick: () => setMappingRows(mappingRows.filter((_, i) => i !== index)),
|
|
463
|
+
children: t("removeMapping")
|
|
464
|
+
})
|
|
465
|
+
]
|
|
466
|
+
}, index)),
|
|
467
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
468
|
+
className: "mp-map-row",
|
|
469
|
+
children: [
|
|
470
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
471
|
+
placeholder: "dsh provider",
|
|
472
|
+
value: newFrom,
|
|
473
|
+
onChange: (event) => setNewFrom(event.target.value)
|
|
474
|
+
}),
|
|
475
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
476
|
+
className: "mp-map-arrow",
|
|
477
|
+
children: "→"
|
|
478
|
+
}),
|
|
479
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
480
|
+
placeholder: "models.dev provider",
|
|
481
|
+
value: newTo,
|
|
482
|
+
onChange: (event) => setNewTo(event.target.value)
|
|
483
|
+
}),
|
|
484
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
485
|
+
className: "mp-button",
|
|
486
|
+
disabled: newFrom.trim().length === 0 || newTo.trim().length === 0,
|
|
487
|
+
onClick: () => {
|
|
488
|
+
setMappingRows([...mappingRows, {
|
|
489
|
+
from: newFrom.trim(),
|
|
490
|
+
to: newTo.trim()
|
|
491
|
+
}]);
|
|
492
|
+
setNewFrom("");
|
|
493
|
+
setNewTo("");
|
|
494
|
+
},
|
|
495
|
+
children: t("addMapping")
|
|
496
|
+
})
|
|
497
|
+
]
|
|
498
|
+
}),
|
|
499
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
500
|
+
className: "mp-actions",
|
|
501
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
502
|
+
className: "mp-button mp-button-ghost",
|
|
503
|
+
disabled: saving,
|
|
504
|
+
onClick: () => void applyMappings(),
|
|
505
|
+
children: saving ? t("saving") : "✓ " + t("providerMap")
|
|
506
|
+
})
|
|
507
|
+
})
|
|
508
|
+
]
|
|
509
|
+
}),
|
|
510
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
511
|
+
className: "mp-card",
|
|
512
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
513
|
+
className: "mp-card-title",
|
|
514
|
+
children: t("report")
|
|
515
|
+
}), report === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
516
|
+
className: "mp-muted",
|
|
517
|
+
children: t("reportNever")
|
|
518
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
|
|
519
|
+
className: "mp-report",
|
|
520
|
+
children: [
|
|
521
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
522
|
+
t("reportAt"),
|
|
523
|
+
": ",
|
|
524
|
+
formatTime(report.at)
|
|
525
|
+
] }),
|
|
526
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
527
|
+
t("reportFilled"),
|
|
528
|
+
": ",
|
|
529
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: report.filled })
|
|
530
|
+
] }),
|
|
531
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
532
|
+
t("reportTouched"),
|
|
533
|
+
": ",
|
|
534
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: report.touched })
|
|
535
|
+
] }),
|
|
536
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
537
|
+
t("reportUnmatched"),
|
|
538
|
+
":",
|
|
539
|
+
" ",
|
|
540
|
+
report.unmatched.length === 0 ? t("reportEmpty") : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
541
|
+
className: "mp-code",
|
|
542
|
+
children: report.unmatched.join(", ")
|
|
543
|
+
})
|
|
544
|
+
] })
|
|
545
|
+
]
|
|
546
|
+
})]
|
|
547
|
+
}),
|
|
548
|
+
error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
549
|
+
className: "mp-error",
|
|
550
|
+
children: error
|
|
551
|
+
})
|
|
552
|
+
]
|
|
553
|
+
});
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
//#endregion
|
|
557
|
+
//#region src/client/index.tsx
|
|
558
|
+
/** Services required before mounting (provided by the client runtime). */
|
|
559
|
+
const inject = ["slots", "locale"];
|
|
560
|
+
/** Client plugin body. */
|
|
561
|
+
function apply(ctx) {
|
|
562
|
+
const t = ctx.locale.bind(LOCALE_NS);
|
|
563
|
+
ctx.effect(() => {
|
|
564
|
+
const off = ctx.locale.register(LOCALE_NS, {
|
|
565
|
+
zh,
|
|
566
|
+
en
|
|
567
|
+
});
|
|
568
|
+
return () => off();
|
|
569
|
+
}, "model-parameters: dictionaries");
|
|
570
|
+
const SettingsSection = makeSettingsSection(ctx);
|
|
571
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
572
|
+
name: "settings.section",
|
|
573
|
+
id: "model-parameters",
|
|
574
|
+
order: 320,
|
|
575
|
+
label: () => t("settingsTitle"),
|
|
576
|
+
locale: LOCALE_NS
|
|
577
|
+
}, SettingsSection));
|
|
578
|
+
}
|
|
579
|
+
//#endregion
|
|
580
|
+
exports.apply = apply;
|
|
581
|
+
exports.inject = inject;
|
|
582
|
+
return module.exports;
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","names":["useCallback","useSyncExternalStore","useState"],"sources":["../../../src/client/locales.ts","../../../src/client/api.ts","../../../src/client/settings-section.tsx","../../../src/client/index.tsx"],"sourcesContent":["/**\n * Locale dictionaries for @fonlan/dsh-model-parameters settings page.\n * zh is the key source of truth; en must carry the exact same key set.\n */\nexport const LOCALE_NS = 'model-parameters'\n\nexport const zh = {\n settingsTitle: '模型参数补全',\n description: '同步模型时自动从 models.dev 目录补全缺失的显示名称、最大上下文、最大输出、推理强度与输入模态。',\n master: '启用自动补全',\n masterDesc: '关闭后不再自动补全任何字段(已补全的值保留)。',\n fields: '补全字段',\n fillName: '显示名称',\n fillNameDesc: '模型条目缺 name 时写入目录中的显示名称。',\n fillContext: '最大上下文',\n fillContextDesc: '缺 contextWindow 时写入目录的上下文上限。',\n fillMaxTokens: '最大输出',\n fillMaxTokensDesc: '缺 maxTokens 时写入目录的输出上限。',\n fillReasoning: '推理强度',\n fillReasoningDesc: '缺 reasoningEfforts 时按目录 effort 档位写入 identity 字典(如 {high:\"high\", max:\"max\"})。',\n fillInput: '输入模态',\n fillInputDesc: '缺 input 时写入目录中的 text/image 模态。',\n ttl: '目录更新周期(天)',\n ttlDesc: '缓存的 models.dev 目录超过该天数后,在下次补全时自动刷新。',\n providerMap: 'Provider 映射',\n providerMapDesc: 'dsh provider → models.dev provider 的匹配覆盖(可选)。',\n addMapping: '添加映射',\n removeMapping: '移除',\n catalog: '模型目录',\n catalogEntries: '目录条目',\n catalogProviders: '覆盖 Provider',\n catalogFetched: '上次更新',\n catalogFresh: '新鲜',\n catalogStale: '已过期',\n catalogNever: '尚未获取',\n refresh: '立即更新目录并补全',\n refreshBusy: '更新中…',\n reconcileNow: '立即补全一次',\n report: '最近一次补全',\n reportNever: '尚未执行',\n reportFilled: '补全字段',\n reportTouched: '涉及 Provider',\n reportUnmatched: '未匹配模型',\n reportEmpty: '全部匹配,无未匹配项',\n reportAt: '时间',\n error: '加载失败',\n saving: '保存中…',\n officialProviders: '官方厂商优先级',\n officialProvidersDesc: '同一模型 id 在多处出现时,优先采用这些 models.dev provider 的条目。',\n} as const\n\nexport const en: Record<keyof typeof zh, string> = {\n settingsTitle: 'Model Parameters',\n description: 'Auto-fill missing display name, context window, max output, reasoning efforts and input modalities from the models.dev catalog when syncing models.',\n master: 'Enable auto-fill',\n masterDesc: 'When off, no fields are filled (already-filled values are kept).',\n fields: 'Fields to fill',\n fillName: 'Display name',\n fillNameDesc: 'Write the catalog display name when a model entry lacks name.',\n fillContext: 'Context window',\n fillContextDesc: 'Write the catalog context limit when contextWindow is missing.',\n fillMaxTokens: 'Max output',\n fillMaxTokensDesc: 'Write the catalog output limit when maxTokens is missing.',\n fillReasoning: 'Reasoning efforts',\n fillReasoningDesc: 'Write an identity reasoningEfforts dict (e.g. {high:\"high\", max:\"max\"}) from catalog effort levels when missing.',\n fillInput: 'Input modalities',\n fillInputDesc: 'Write text/image modalities from the catalog when input is missing.',\n ttl: 'Catalog refresh interval (days)',\n ttlDesc: 'The cached models.dev catalog is refreshed on the next fill once older than this many days.',\n providerMap: 'Provider mapping',\n providerMapDesc: 'Optional dsh provider → models.dev provider matching overrides.',\n addMapping: 'Add mapping',\n removeMapping: 'Remove',\n catalog: 'Model catalog',\n catalogEntries: 'Catalog entries',\n catalogProviders: 'Providers covered',\n catalogFetched: 'Last updated',\n catalogFresh: 'fresh',\n catalogStale: 'stale',\n catalogNever: 'not fetched yet',\n refresh: 'Update catalog & fill now',\n refreshBusy: 'Updating…',\n reconcileNow: 'Fill now',\n report: 'Last fill',\n reportNever: 'never run',\n reportFilled: 'fields filled',\n reportTouched: 'providers touched',\n reportUnmatched: 'unmatched models',\n reportEmpty: 'all matched, nothing unmatched',\n reportAt: 'time',\n error: 'Failed to load',\n saving: 'Saving…',\n officialProviders: 'Official provider priority',\n officialProvidersDesc: 'When one model id appears under several providers, prefer entries from these models.dev providers.',\n}\n","/** Client transport for the model-parameters settings API. */\nimport type { ModelParametersConfig } from '../shared/config.js'\n\nexport interface CatalogView {\n entries: number\n providers: number\n fetchedAt: number\n fresh: boolean\n ttlDays: number\n}\n\nexport interface FillReportView {\n at: number\n filled: number\n touched: number\n unmatched: string[]\n}\n\nexport interface PluginState {\n config: ModelParametersConfig\n catalog: CatalogView\n report: FillReportView | null\n}\n\nexport class ModelParametersApiError extends Error {\n constructor(readonly code: string, message: string) {\n super(message)\n }\n}\n\nasync function call<T>(method: string, payload: Record<string, unknown> = {}): Promise<T> {\n let response: Response\n try {\n response = await fetch(`/plugins/@fonlan/dsh-model-parameters/api/${method}`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n })\n } catch (error) {\n throw new ModelParametersApiError('network', error instanceof Error ? error.message : String(error))\n }\n const parsed: { ok?: boolean; value?: unknown; error?: { code?: string; message?: string } } | null\n = await response.json().catch(() => null)\n if (!response.ok || parsed === null || parsed.ok !== true || parsed.value === undefined) {\n throw new ModelParametersApiError(\n parsed?.error?.code ?? 'http',\n parsed?.error?.message ?? `HTTP ${response.status}`,\n )\n }\n return parsed.value as T\n}\n\nexport const api = {\n state: () => call<PluginState>('state'),\n refresh: () => call<PluginState>('refresh'),\n reconcile: () => call<PluginState>('reconcile'),\n setConfig: (patch: Partial<ModelParametersConfig>) =>\n call<PluginState>('set-config', { patch }),\n}\n","/**\n * The Model Parameters settings page: master + per-field toggles, catalog TTL,\n * provider→models.dev mapping overrides, a manual refresh/fill action, the\n * catalog freshness card, and the last fill report (filled / touched /\n * unmatched). All reads/mutations go through the plugin's fenced API; config\n * persists into the `model-parameters` settings namespace (settings.yaml).\n */\nimport { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from 'react'\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { Translate } from '@deepseek-ai/dsh-client-ui-slots'\nimport { LOCALE_NS } from './locales'\nimport { api, type PluginState } from './api'\nimport type { ModelParametersConfig } from '../shared/config.js'\nimport './settings-section.css'\n\nfunction useLocaleRevision(ctx: ClientContext): number {\n const subscribe = useCallback(\n (onChange: () => void) => {\n try {\n return ctx.locale.subscribe(onChange)\n } catch {\n return () => {}\n }\n },\n [ctx],\n )\n const getSnapshot = useCallback(() => {\n try {\n return ctx.locale.getLocale().revision\n } catch {\n return 0\n }\n }, [ctx])\n return useSyncExternalStore(subscribe, getSnapshot)\n}\n\nfunction formatTime(epochMs: number): string {\n try {\n return new Date(epochMs).toLocaleString()\n } catch {\n return String(epochMs)\n }\n}\n\nexport function makeSettingsSection(ctx: ClientContext): () => JSX.Element {\n const t: Translate = (() => {\n try {\n return ctx.locale.bind(LOCALE_NS) as unknown as Translate\n } catch {\n return (key: string) => key\n }\n })()\n\n return function ModelParametersSettingsSection(): JSX.Element {\n useLocaleRevision(ctx)\n const [state, setState] = useState<PluginState | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [loading, setLoading] = useState(true)\n const [busy, setBusy] = useState(false)\n const [saving, setSaving] = useState(false)\n const [mappingRows, setMappingRows] = useState<Array<{ from: string; to: string }>>([])\n const [newFrom, setNewFrom] = useState('')\n const [newTo, setNewTo] = useState('')\n\n const load = useCallback(async () => {\n setLoading(true)\n try {\n const next = await api.state()\n setState(next)\n setMappingRows(Object.entries(next.config.providerMap).map(([from, to]) => ({ from, to })))\n setError(null)\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n } finally {\n setLoading(false)\n }\n }, [])\n\n useEffect(() => {\n void load()\n }, [load])\n\n const patchConfig = useCallback(async (patch: Partial<ModelParametersConfig>) => {\n setSaving(true)\n try {\n const next = await api.setConfig(patch)\n setState(next)\n setMappingRows(Object.entries(next.config.providerMap).map(([from, to]) => ({ from, to })))\n setError(null)\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n } finally {\n setSaving(false)\n }\n }, [])\n\n const refresh = useCallback(async () => {\n setBusy(true)\n try {\n setState(await api.refresh())\n setError(null)\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n } finally {\n setBusy(false)\n }\n }, [])\n\n const reconcileNow = useCallback(async () => {\n setBusy(true)\n try {\n setState(await api.reconcile())\n setError(null)\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n } finally {\n setBusy(false)\n }\n }, [])\n\n const applyMappings = useCallback(async () => {\n const providerMap: Record<string, string> = {}\n for (const row of mappingRows) {\n const from = row.from.trim()\n const to = row.to.trim()\n if (from.length > 0 && to.length > 0) providerMap[from] = to\n }\n await patchConfig({ providerMap })\n }, [mappingRows, patchConfig])\n\n if (error !== null && state === null) {\n return (\n <p className=\"mp-error\">{t('error')}: {error}</p>\n )\n }\n if (loading || state === null) {\n return <p className=\"mp-muted\">{t('saving')}</p>\n }\n\n const config = state.config\n const catalog = state.catalog\n const report = state.report\n\n return (\n <div className=\"mp-root\">\n <p className=\"mp-desc\">{t('description')}</p>\n\n <section className=\"mp-card\">\n <h4 className=\"mp-card-title\">{t('fields')}</h4>\n <label className=\"mp-toggle\">\n <input\n type=\"checkbox\"\n checked={config.enabled}\n disabled={saving}\n onChange={(event) => void patchConfig({ enabled: event.target.checked })}\n />\n <span className=\"mp-toggle-body\">\n <span className=\"mp-toggle-label\">{t('master')}</span>\n <span className=\"mp-toggle-desc\">{t('masterDesc')}</span>\n </span>\n </label>\n {([\n ['fillName', t('fillName'), t('fillNameDesc')],\n ['fillContext', t('fillContext'), t('fillContextDesc')],\n ['fillMaxTokens', t('fillMaxTokens'), t('fillMaxTokensDesc')],\n ['fillReasoning', t('fillReasoning'), t('fillReasoningDesc')],\n ['fillInput', t('fillInput'), t('fillInputDesc')],\n ] as const).map(([key, label, desc]) => (\n <label className=\"mp-toggle mp-toggle-sub\" key={key}>\n <input\n type=\"checkbox\"\n checked={config[key]}\n disabled={saving || !config.enabled}\n onChange={(event) => void patchConfig({ [key]: event.target.checked })}\n />\n <span className=\"mp-toggle-body\">\n <span className=\"mp-toggle-label\">{label}</span>\n <span className=\"mp-toggle-desc\">{desc}</span>\n </span>\n </label>\n ))}\n </section>\n\n <section className=\"mp-card\">\n <h4 className=\"mp-card-title\">{t('catalog')}</h4>\n <div className=\"mp-meta\">\n <span>{t('catalogEntries')}: <strong>{catalog.entries.toLocaleString()}</strong></span>\n <span>{t('catalogProviders')}: <strong>{catalog.providers.toLocaleString()}</strong></span>\n <span>\n {t('catalogFetched')}:{' '}\n <strong>{catalog.fetchedAt > 0 ? formatTime(catalog.fetchedAt) : t('catalogNever')}</strong>{' '}\n {catalog.fetchedAt > 0 && (\n <span className={catalog.fresh ? 'mp-badge mp-badge-ok' : 'mp-badge mp-badge-warn'}>\n {catalog.fresh ? t('catalogFresh') : t('catalogStale')}\n </span>\n )}\n </span>\n <label className=\"mp-ttl\">\n {t('ttl')}\n <input\n type=\"number\"\n min={1}\n max={90}\n value={config.ttlDays}\n disabled={saving}\n onChange={(event) => {\n const days = Number(event.target.value)\n if (Number.isFinite(days) && days >= 1 && days <= 90) void patchConfig({ ttlDays: Math.round(days) })\n }}\n />\n </label>\n </div>\n <div className=\"mp-actions\">\n <button className=\"mp-button\" disabled={busy} onClick={() => void refresh()}>\n {busy ? t('refreshBusy') : t('refresh')}\n </button>\n <button className=\"mp-button mp-button-ghost\" disabled={busy} onClick={() => void reconcileNow()}>\n {t('reconcileNow')}\n </button>\n </div>\n </section>\n\n <section className=\"mp-card\">\n <h4 className=\"mp-card-title\">{t('providerMap')}</h4>\n <p className=\"mp-toggle-desc\">{t('providerMapDesc')}</p>\n {mappingRows.map((row, index) => (\n <div className=\"mp-map-row\" key={index}>\n <input\n value={row.from}\n disabled={saving}\n onChange={(event) => {\n const next = [...mappingRows]\n next[index] = { ...row, from: event.target.value }\n setMappingRows(next)\n }}\n />\n <span className=\"mp-map-arrow\">→</span>\n <input\n value={row.to}\n disabled={saving}\n onChange={(event) => {\n const next = [...mappingRows]\n next[index] = { ...row, to: event.target.value }\n setMappingRows(next)\n }}\n />\n <button\n className=\"mp-button mp-button-ghost\"\n disabled={saving}\n onClick={() => setMappingRows(mappingRows.filter((_, i) => i !== index))}\n >\n {t('removeMapping')}\n </button>\n </div>\n ))}\n <div className=\"mp-map-row\">\n <input\n placeholder=\"dsh provider\"\n value={newFrom}\n onChange={(event) => setNewFrom(event.target.value)}\n />\n <span className=\"mp-map-arrow\">→</span>\n <input\n placeholder=\"models.dev provider\"\n value={newTo}\n onChange={(event) => setNewTo(event.target.value)}\n />\n <button\n className=\"mp-button\"\n disabled={newFrom.trim().length === 0 || newTo.trim().length === 0}\n onClick={() => {\n setMappingRows([...mappingRows, { from: newFrom.trim(), to: newTo.trim() }])\n setNewFrom('')\n setNewTo('')\n }}\n >\n {t('addMapping')}\n </button>\n </div>\n <div className=\"mp-actions\">\n <button className=\"mp-button mp-button-ghost\" disabled={saving} onClick={() => void applyMappings()}>\n {saving ? t('saving') : '✓ ' + t('providerMap')}\n </button>\n </div>\n </section>\n\n <section className=\"mp-card\">\n <h4 className=\"mp-card-title\">{t('report')}</h4>\n {report === null ? (\n <p className=\"mp-muted\">{t('reportNever')}</p>\n ) : (\n <ul className=\"mp-report\">\n <li>{t('reportAt')}: {formatTime(report.at)}</li>\n <li>{t('reportFilled')}: <strong>{report.filled}</strong></li>\n <li>{t('reportTouched')}: <strong>{report.touched}</strong></li>\n <li>\n {t('reportUnmatched')}:{' '}\n {report.unmatched.length === 0\n ? t('reportEmpty')\n : <code className=\"mp-code\">{report.unmatched.join(', ')}</code>}\n </li>\n </ul>\n )}\n </section>\n\n {error !== null && <p className=\"mp-error\">{error}</p>}\n </div>\n )\n }\n}\n","/**\n * @fonlan/dsh-model-parameters client half: the Model Parameters settings\n * page, registered into the settings section slot.\n */\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport { LOCALE_NS, zh, en } from './locales'\nimport { makeSettingsSection } from './settings-section'\n\n/** Services required before mounting (provided by the client runtime). */\nexport const inject = ['slots', 'locale']\n\n/** Client plugin body. */\nexport function apply(ctx: ClientContext): void {\n const t = ctx.locale.bind(LOCALE_NS)\n ctx.effect(() => {\n const off = ctx.locale.register(LOCALE_NS, { zh, en })\n return () => off()\n }, 'model-parameters: dictionaries')\n\n const SettingsSection = makeSettingsSection(ctx)\n ctx.slots.inject('settings.section', () =>\n ctx.slots.register({\n name: 'settings.section',\n id: 'model-parameters',\n order: 320,\n label: () => t('settingsTitle'),\n locale: LOCALE_NS,\n }, SettingsSection as never),\n )\n}\n"],"mappings":"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"}
|