@dsh-plus/llm-pi 0.1.40 → 0.1.42
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/lib/client.js +351 -269
- package/package.json +2 -2
- package/src/client/card.tsx +103 -125
- package/src/client/client.ts +2 -2
- package/src/client/draft.ts +12 -0
- package/src/client/i18n.ts +6 -0
- package/src/client/root-fields.tsx +84 -0
- package/src/client/views/models.tsx +17 -2
- package/src/client/views/providers.tsx +16 -0
- package/src/ns.ts +2 -2
package/lib/client.js
CHANGED
|
@@ -4,8 +4,8 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
-
let react = require("react");
|
|
8
7
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
|
+
let react = require("react");
|
|
9
9
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
10
10
|
//#region ../shared/src/client/card.tsx
|
|
11
11
|
const IDLE_STATUS = {
|
|
@@ -15,60 +15,68 @@ window.__ModuleLoader__.load({
|
|
|
15
15
|
function CardChrome(props) {
|
|
16
16
|
const p = props.prefix;
|
|
17
17
|
const badge = props.statusBadge;
|
|
18
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
19
|
-
className: `${p}-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
28
|
-
className: `${p}-headText`,
|
|
29
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
30
|
-
className: `${p}-name`,
|
|
18
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
19
|
+
className: `${p}-section`,
|
|
20
|
+
"aria-label": props.title,
|
|
21
|
+
children: [
|
|
22
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
23
|
+
className: `${p}-sectionHead`,
|
|
24
|
+
children: [
|
|
25
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
26
|
+
className: `${p}-sectionTitle`,
|
|
31
27
|
children: props.title
|
|
32
|
-
}),
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
role: "status",
|
|
53
|
-
children: props.readOnlyNotice
|
|
54
|
-
}) : null,
|
|
55
|
-
props.children,
|
|
56
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
57
|
-
className: `${p}-footer`,
|
|
58
|
-
children: [props.status !== void 0 && props.status.kind !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
59
|
-
className: `${p}-status${props.status.kind === "error" ? ` ${p}-statusError` : ""}`,
|
|
28
|
+
}),
|
|
29
|
+
badge !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
30
|
+
className: `${p}-sectionBadge ${badge.on ? `${p}-statusOn` : `${p}-statusOff`}`,
|
|
31
|
+
children: badge.text
|
|
32
|
+
}) : null,
|
|
33
|
+
props.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
34
|
+
className: `${p}-pending`,
|
|
35
|
+
children: props.dirtyLabel
|
|
36
|
+
}) : null
|
|
37
|
+
]
|
|
38
|
+
}),
|
|
39
|
+
props.description === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
40
|
+
className: `${p}-sectionDesc`,
|
|
41
|
+
children: props.description
|
|
42
|
+
}),
|
|
43
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
44
|
+
className: `${p}-sectionBody`,
|
|
45
|
+
children: [
|
|
46
|
+
props.readOnlyNotice !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
47
|
+
className: `${p}-readOnly`,
|
|
60
48
|
role: "status",
|
|
61
|
-
children: props.
|
|
62
|
-
}) : null,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
49
|
+
children: props.readOnlyNotice
|
|
50
|
+
}) : null,
|
|
51
|
+
props.children,
|
|
52
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
53
|
+
className: `${p}-sectionFooter`,
|
|
54
|
+
children: [props.status !== void 0 && props.status.kind !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
55
|
+
className: `${p}-status${props.status.kind === "error" ? ` ${p}-statusError` : ""}`,
|
|
56
|
+
role: "status",
|
|
57
|
+
children: props.status.text
|
|
58
|
+
}) : null, props.actions.map((action) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
59
|
+
type: "button",
|
|
60
|
+
className: `${p}-btn ${action.variant === "primary" ? `${p}-btnPrimary` : `${p}-btnGhost`}`,
|
|
61
|
+
disabled: action.disabled === true,
|
|
62
|
+
onClick: action.onClick,
|
|
63
|
+
children: action.label
|
|
64
|
+
}, action.key))]
|
|
65
|
+
})
|
|
66
|
+
]
|
|
67
|
+
})
|
|
68
|
+
]
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/** 取值未到达时的占位:与 CardChrome 同形态,避免宿主页里闪一次卡片外壳。 */
|
|
72
|
+
function CardLoading(props) {
|
|
73
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
74
|
+
className: `${props.prefix}-section`,
|
|
75
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
76
|
+
className: `${props.prefix}-readOnly`,
|
|
77
|
+
role: "status",
|
|
78
|
+
children: props.text
|
|
79
|
+
})
|
|
72
80
|
});
|
|
73
81
|
}
|
|
74
82
|
//#endregion
|
|
@@ -76,19 +84,14 @@ window.__ModuleLoader__.load({
|
|
|
76
84
|
/** dsh-plus 聚合 bundle 的包名(各插件 row 均经其补丁层插入 profile)。 */
|
|
77
85
|
const DSH_PLUS_BUNDLE = "@dsh-plus/bundle-main";
|
|
78
86
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
87
|
+
* 把一张配置卡片注册进插件页的两个配置槽位。
|
|
88
|
+
* 两个槽位都由 0.2.1-alpha.1 的插件页声明,任一缺席即报错——不做版本探测。
|
|
81
89
|
*/
|
|
82
90
|
function injectPluginConfigCard(slots, reg) {
|
|
83
91
|
const base = {
|
|
84
92
|
locale: reg.ns,
|
|
85
93
|
inject: reg.inject
|
|
86
94
|
};
|
|
87
|
-
slots.inject("settings.plugin.item", () => slots.register({
|
|
88
|
-
name: "settings.plugin.item",
|
|
89
|
-
key: reg.ns,
|
|
90
|
-
...base
|
|
91
|
-
}, reg.component));
|
|
92
95
|
slots.inject("plugins.row.config", () => slots.register({
|
|
93
96
|
name: "plugins.row.config",
|
|
94
97
|
key: `${DSH_PLUS_BUNDLE}#${reg.rowId}`,
|
|
@@ -101,6 +104,17 @@ window.__ModuleLoader__.load({
|
|
|
101
104
|
}, reg.component));
|
|
102
105
|
}
|
|
103
106
|
//#endregion
|
|
107
|
+
//#region ../shared/src/client/draft.ts
|
|
108
|
+
/**
|
|
109
|
+
* 单命名空间 staged 草稿(简单配置卡片共用):读 scope 快照、播种草稿、
|
|
110
|
+
* 脏判定、保存(revision fencing + 重新装载)与状态行。
|
|
111
|
+
*
|
|
112
|
+
* 语义与官方 staged 表单一致:Host 更新不覆盖在途编辑,只有保存才写回;
|
|
113
|
+
* 保存成功后按 Host 解析值重置草稿。`from`/`patch` 参与 effect 与 useMemo
|
|
114
|
+
* 依赖,必须是模块级稳定函数(不要内联箭头函数)。
|
|
115
|
+
* @module @dsh-plus/shared/client/draft
|
|
116
|
+
*/
|
|
117
|
+
//#endregion
|
|
104
118
|
//#region ../shared/src/client/fields.tsx
|
|
105
119
|
function TextField$1(props) {
|
|
106
120
|
const p = props.prefix ?? "dshp";
|
|
@@ -298,33 +312,33 @@ window.__ModuleLoader__.load({
|
|
|
298
312
|
//#endregion
|
|
299
313
|
//#region ../shared/src/client/styles.ts
|
|
300
314
|
/**
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
315
|
+
* 配置分节样式工厂(各插件 client 的公共收编版)。
|
|
316
|
+
*
|
|
317
|
+
* 目标宿主 dsh 0.2.1-alpha.1:插件页把配置视图放进已有页面级内边距的容器里
|
|
318
|
+
* (`div.detailSections[data-plugin-config]` 或 `section.detailSection[data-plugin-config]`),
|
|
319
|
+
* 外层无边框——故这里输出的是**无边框分节**(小节标题 13px/600、字段 12px 0、
|
|
320
|
+
* 横向零内边距、页脚非 sticky),度量对齐官方设置页
|
|
321
|
+
* (`@deepseek-ai/dsh-client-ui-primitives` 的 settings-form)。
|
|
322
|
+
*
|
|
323
|
+
* 移动端与可达性(767px 断点 + pointer:coarse,对齐 ui-mobile-fit 约定):
|
|
304
324
|
* - 窄屏 input/textarea 16px(防 iOS 聚焦缩放)、可点目标 ≥44px;
|
|
305
|
-
* -
|
|
306
|
-
* - 展开态 body 底部 sticky 保存栏(长表单滚动中始终可达)。
|
|
325
|
+
* - 页脚按钮等宽换行、状态文字独占一行。
|
|
307
326
|
* 沿用官方 data-plugin / data-plugin-css 约定(HMR 据此卸载),不覆盖上游选择器。
|
|
308
327
|
* @module @dsh-plus/shared/client/styles
|
|
309
328
|
*/
|
|
310
|
-
/**
|
|
329
|
+
/** 生成全套分节 CSS;prefix 为插件类名前缀(如 'dne'),extra 为插件特有规则。 */
|
|
311
330
|
function cardCss(prefix, extra = "") {
|
|
312
331
|
return `
|
|
313
|
-
.${prefix}-
|
|
314
|
-
.${prefix}-
|
|
315
|
-
.${prefix}-
|
|
316
|
-
.${prefix}-
|
|
317
|
-
.${prefix}-
|
|
318
|
-
.${prefix}-
|
|
319
|
-
.${prefix}-
|
|
320
|
-
.${prefix}-description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}
|
|
321
|
-
.${prefix}-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s;display:inline-flex}
|
|
322
|
-
.${prefix}-chevronOpen{transform:rotate(180deg)}
|
|
332
|
+
.${prefix}-section{box-sizing:border-box;flex-direction:column;width:100%;min-width:0;padding:16px 0;display:flex}
|
|
333
|
+
.${prefix}-sectionHead{align-items:center;gap:8px;min-width:0;display:flex}
|
|
334
|
+
.${prefix}-sectionTitle{min-width:0;color:var(--dsw-alias-label-primary);flex:1;margin:0;font-size:13px;font-weight:600;line-height:1.5}
|
|
335
|
+
.${prefix}-sectionBadge{white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
336
|
+
.${prefix}-sectionDesc{color:var(--dsw-alias-label-tertiary);margin:4px 0 0;font-size:12px;line-height:1.5}
|
|
337
|
+
.${prefix}-sectionBody{flex-direction:column;min-width:0;display:flex}
|
|
338
|
+
.${prefix}-sectionFooter{align-items:center;gap:8px;padding-top:16px;display:flex;flex-wrap:wrap}
|
|
323
339
|
.${prefix}-pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
324
|
-
.${prefix}-statusBadge{white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
325
340
|
.${prefix}-statusOn{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
|
|
326
341
|
.${prefix}-statusOff{color:var(--dsw-alias-label-tertiary)}
|
|
327
|
-
.${prefix}-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}
|
|
328
342
|
.${prefix}-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
|
|
329
343
|
.${prefix}-field+.${prefix}-field{border-top:1px solid var(--dsw-alias-border-l2)}
|
|
330
344
|
.${prefix}-head{align-items:center;gap:8px;display:flex}
|
|
@@ -353,7 +367,6 @@ window.__ModuleLoader__.load({
|
|
|
353
367
|
.${prefix}-groupLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;padding:12px 0 2px;margin:0}
|
|
354
368
|
.${prefix}-readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}
|
|
355
369
|
.${prefix}-warn{color:var(--dsw-alias-label-error);margin:12px 0 0;font-size:12px;line-height:1.5}
|
|
356
|
-
.${prefix}-footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex;flex-wrap:wrap}
|
|
357
370
|
.${prefix}-status{min-width:0;color:var(--dsw-alias-label-secondary);flex:1;margin:0;font-size:12px;line-height:1.5}
|
|
358
371
|
.${prefix}-statusError{color:var(--dsw-alias-label-error)}
|
|
359
372
|
.${prefix}-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}
|
|
@@ -363,14 +376,10 @@ window.__ModuleLoader__.load({
|
|
|
363
376
|
.${prefix}-btn:disabled{opacity:.4;cursor:default}
|
|
364
377
|
.${prefix}-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
|
|
365
378
|
@media (max-width:767px){
|
|
366
|
-
.${prefix}-body{margin:0 10px}
|
|
367
|
-
.${prefix}-header{padding:12px 12px}
|
|
368
379
|
.${prefix}-input,.${prefix}-select{font-size:16px;height:40px}
|
|
369
380
|
.${prefix}-checkRow{min-height:44px;padding:8px 0}
|
|
370
|
-
.${prefix}-footer{position:sticky;bottom:0;background:var(--dsw-alias-bg-layer-2);margin:0 -10px;padding:10px 10px 6px;border-top:1px solid var(--dsw-alias-border-l2);border-bottom-left-radius:12px;border-bottom-right-radius:12px}
|
|
371
381
|
.${prefix}-status{flex-basis:100%;flex:none}
|
|
372
382
|
.${prefix}-btn{min-height:44px;flex:1;min-width:96px}
|
|
373
|
-
.${prefix}-statusBadge{display:none}
|
|
374
383
|
}
|
|
375
384
|
@media (pointer:coarse){
|
|
376
385
|
.${prefix}-btn{min-height:44px}
|
|
@@ -401,8 +410,8 @@ ${extra}
|
|
|
401
410
|
* settings 命名空间字面量(纯常量,零依赖,浏览器半可安全引入)。
|
|
402
411
|
* 服务端直接以字面量注册(0.1.2-alpha.2 起 dsh-settings 对命名空间做编译期
|
|
403
412
|
* 语法校验,见 config.ts);浏览器半把它作为配置卡片的注册键
|
|
404
|
-
*
|
|
405
|
-
*
|
|
413
|
+
* (插件页配置卡片挂在 row/bundle config 槽位上,与命名空间解耦,
|
|
414
|
+
* 见 shared/config-slots),
|
|
406
415
|
* 同一字面量两处共用,防止漂移。
|
|
407
416
|
* @module llm-pi/ns
|
|
408
417
|
*/
|
|
@@ -621,6 +630,17 @@ ${extra}
|
|
|
621
630
|
for (const [key, value] of Object.entries(wire)) if (!known.has(key) && value !== void 0) out[key] = value;
|
|
622
631
|
return out;
|
|
623
632
|
}
|
|
633
|
+
/**
|
|
634
|
+
* JSON 文本框值 → extra 映射。
|
|
635
|
+
* `undefined`(空文本)表示清空为 `{}`;合法但非对象的 JSON(数组/标量/null)
|
|
636
|
+
* 返回 undefined,调用方据此忽略这次编辑——extra 是「其余 wire 字段」的映射,
|
|
637
|
+
* 静默丢弃已配置字段比拒绝一次编辑更糟。
|
|
638
|
+
*/
|
|
639
|
+
function extraOfJson(value) {
|
|
640
|
+
if (value === void 0) return {};
|
|
641
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
642
|
+
return { ...value };
|
|
643
|
+
}
|
|
624
644
|
function modelDraftFromWire(model) {
|
|
625
645
|
return {
|
|
626
646
|
id: model.id,
|
|
@@ -775,6 +795,79 @@ ${extra}
|
|
|
775
795
|
};
|
|
776
796
|
}
|
|
777
797
|
//#endregion
|
|
798
|
+
//#region src/client/root-fields.tsx
|
|
799
|
+
/**
|
|
800
|
+
* 「LLM 路由」卡片的根字段组:总开关、目录端点/刷新间隔/代理,以及运行期诊断行
|
|
801
|
+
* (kitSource 与 models-dev 快照状态,来自模型目录端点而非配置数据)。
|
|
802
|
+
* @module llm-pi/client/root-fields
|
|
803
|
+
*/
|
|
804
|
+
/** models-dev 状态行文案(缺席/错误/未拉取/正常四态)。 */
|
|
805
|
+
function modelsDevText(status, t) {
|
|
806
|
+
if (status === null) return t("modelsDevEmpty");
|
|
807
|
+
if (status.error !== null) return `${t("modelsDevError")}${status.error}`;
|
|
808
|
+
if (status.fetchedAt === null) return t("modelsDevEmpty");
|
|
809
|
+
return `${t("modelsDevStatusLine")}:${status.providers} 个 provider,快照 ${status.fetchedAt}`;
|
|
810
|
+
}
|
|
811
|
+
function RootFields(props) {
|
|
812
|
+
const { t, draft, disabled, onEdit } = props;
|
|
813
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
814
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
|
|
815
|
+
prefix: "lpc",
|
|
816
|
+
id: "lpc-enabled",
|
|
817
|
+
label: t("enabled"),
|
|
818
|
+
checked: draft.enabled,
|
|
819
|
+
disabled,
|
|
820
|
+
onEdit: (value) => onEdit({ enabled: value })
|
|
821
|
+
}),
|
|
822
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField$1, {
|
|
823
|
+
prefix: "lpc",
|
|
824
|
+
id: "lpc-catalogUrl",
|
|
825
|
+
label: t("catalogUrl"),
|
|
826
|
+
hint: t("catalogUrlHint"),
|
|
827
|
+
value: draft.catalogUrl,
|
|
828
|
+
disabled,
|
|
829
|
+
onEdit: (value) => onEdit({ catalogUrl: value })
|
|
830
|
+
}),
|
|
831
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField$1, {
|
|
832
|
+
prefix: "lpc",
|
|
833
|
+
id: "lpc-catalogRefresh",
|
|
834
|
+
label: t("catalogRefreshHours"),
|
|
835
|
+
hint: t("catalogRefreshHoursHint"),
|
|
836
|
+
value: draft.catalogRefreshHours,
|
|
837
|
+
numeric: true,
|
|
838
|
+
disabled,
|
|
839
|
+
invalid: !numTextOk(draft.catalogRefreshHours),
|
|
840
|
+
invalidLabel: t("invalidNumber"),
|
|
841
|
+
onEdit: (value) => onEdit({ catalogRefreshHours: value })
|
|
842
|
+
}),
|
|
843
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField$1, {
|
|
844
|
+
prefix: "lpc",
|
|
845
|
+
id: "lpc-catalogProxy",
|
|
846
|
+
label: t("catalogProxy"),
|
|
847
|
+
hint: t("catalogProxyHint"),
|
|
848
|
+
value: draft.catalogProxy,
|
|
849
|
+
disabled,
|
|
850
|
+
onEdit: (value) => onEdit({ catalogProxy: value })
|
|
851
|
+
}),
|
|
852
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
853
|
+
className: "lpc-statusRow",
|
|
854
|
+
children: [
|
|
855
|
+
t("kitSource"),
|
|
856
|
+
":",
|
|
857
|
+
props.kitSource ?? ""
|
|
858
|
+
]
|
|
859
|
+
}),
|
|
860
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
861
|
+
className: "lpc-statusRow",
|
|
862
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
863
|
+
t("modelsDevStatus"),
|
|
864
|
+
":",
|
|
865
|
+
modelsDevText(props.modelsDevStatus, t)
|
|
866
|
+
] })
|
|
867
|
+
})
|
|
868
|
+
] });
|
|
869
|
+
}
|
|
870
|
+
//#endregion
|
|
778
871
|
//#region src/client/fields.tsx
|
|
779
872
|
/**
|
|
780
873
|
* llm-pi 特有卡片控件:可折叠小节、键值对编辑、JSON 文本框、带 datalist 的
|
|
@@ -1354,6 +1447,20 @@ ${extra}
|
|
|
1354
1447
|
wide: true,
|
|
1355
1448
|
t,
|
|
1356
1449
|
onEdit: (compat) => props.onPatch({ compat })
|
|
1450
|
+
}),
|
|
1451
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(JsonField, {
|
|
1452
|
+
id: `${id}-extra`,
|
|
1453
|
+
label: t("extraFields"),
|
|
1454
|
+
hint: t("extraFieldsHint"),
|
|
1455
|
+
invalidText: t("invalidJson"),
|
|
1456
|
+
value: Object.keys(model.extra).length === 0 ? void 0 : model.extra,
|
|
1457
|
+
epoch: props.epoch,
|
|
1458
|
+
disabled: props.disabled === true,
|
|
1459
|
+
wide: true,
|
|
1460
|
+
onEdit: (extra) => {
|
|
1461
|
+
const next = extraOfJson(extra);
|
|
1462
|
+
if (next !== void 0) props.onPatch({ extra: next });
|
|
1463
|
+
}
|
|
1357
1464
|
})
|
|
1358
1465
|
]
|
|
1359
1466
|
});
|
|
@@ -1716,26 +1823,43 @@ ${extra}
|
|
|
1716
1823
|
id: `${id}-advanced`,
|
|
1717
1824
|
title: t("advancedGroup"),
|
|
1718
1825
|
defaultOpen: false,
|
|
1719
|
-
children: [
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1826
|
+
children: [
|
|
1827
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(JsonField, {
|
|
1828
|
+
id: `${id}-extra`,
|
|
1829
|
+
label: t("extraFields"),
|
|
1830
|
+
hint: t("extraFieldsHint"),
|
|
1831
|
+
invalidText: t("invalidJson"),
|
|
1832
|
+
value: Object.keys(draft.extra).length === 0 ? void 0 : draft.extra,
|
|
1833
|
+
epoch: props.epoch,
|
|
1834
|
+
disabled: props.disabled === true,
|
|
1835
|
+
wide: true,
|
|
1836
|
+
onEdit: (extra) => {
|
|
1837
|
+
const next = extraOfJson(extra);
|
|
1838
|
+
if (next !== void 0) props.onPatch({ extra: next });
|
|
1839
|
+
}
|
|
1840
|
+
}),
|
|
1841
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(JsonField, {
|
|
1842
|
+
id: `${id}-retry`,
|
|
1843
|
+
label: t("retryPolicy"),
|
|
1844
|
+
hint: t("retryPolicyHint"),
|
|
1845
|
+
invalidText: t("invalidJson"),
|
|
1846
|
+
value: draft.retryPolicy,
|
|
1847
|
+
epoch: props.epoch,
|
|
1848
|
+
disabled: props.disabled === true,
|
|
1849
|
+
wide: true,
|
|
1850
|
+
onEdit: (retryPolicy) => props.onPatch({ retryPolicy })
|
|
1851
|
+
}),
|
|
1852
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompatEditor, {
|
|
1853
|
+
idPrefix: `${id}-compat`,
|
|
1854
|
+
api: draft.api,
|
|
1855
|
+
compat: draft.compat,
|
|
1856
|
+
epoch: props.epoch,
|
|
1857
|
+
disabled: props.disabled === true,
|
|
1858
|
+
wide: true,
|
|
1859
|
+
t,
|
|
1860
|
+
onEdit: (compat) => props.onPatch({ compat })
|
|
1861
|
+
})
|
|
1862
|
+
]
|
|
1739
1863
|
}),
|
|
1740
1864
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelsTable, {
|
|
1741
1865
|
route,
|
|
@@ -1754,37 +1878,18 @@ ${extra}
|
|
|
1754
1878
|
//#endregion
|
|
1755
1879
|
//#region src/client/card.tsx
|
|
1756
1880
|
/**
|
|
1757
|
-
* 「LLM 路由」配置卡片:经 injectPluginConfigCard
|
|
1758
|
-
*
|
|
1759
|
-
* summary/page)。
|
|
1881
|
+
* 「LLM 路由」配置卡片:经 injectPluginConfigCard 注册(插件页
|
|
1882
|
+
* plugins.row.config / plugins.bundle.config,view 分发 summary/page)。
|
|
1760
1883
|
* 顶部:enabled / catalogUrl / catalogRefreshHours / 只读状态行(kitSource、
|
|
1761
1884
|
* modelsDevStatus,来自模型目录端点)+ 保存(settings.update 全量深合并)与
|
|
1762
1885
|
* 错误/成功提示;下方为 providers 路由列表(新增/删除/字段编辑/compat/模型
|
|
1763
1886
|
* 目录,见 views/)。外壳与基础控件走 @dsh-plus/shared/client 套件。
|
|
1764
1887
|
* @module llm-pi/client/card
|
|
1765
1888
|
*/
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
if (status.error !== null) return `${t("modelsDevError")}${status.error}`;
|
|
1769
|
-
if (status.fetchedAt === null) return t("modelsDevEmpty");
|
|
1770
|
-
return `${t("modelsDevStatusLine")}:${status.providers} 个 provider,快照 ${status.fetchedAt}`;
|
|
1771
|
-
}
|
|
1772
|
-
function LlmPiCard(props) {
|
|
1773
|
-
const { t, scope, api } = props;
|
|
1774
|
-
const snapshot = (0, react.useSyncExternalStore)((listener) => scope.subscribe(listener), () => scope.getSnapshot());
|
|
1775
|
-
const value = snapshot.value;
|
|
1776
|
-
const [open, setOpen] = (0, react.useState)(props.view === "page");
|
|
1777
|
-
const [draft, setDraft] = (0, react.useState)(null);
|
|
1778
|
-
const [epoch, setEpoch] = (0, react.useState)(0);
|
|
1889
|
+
/** 运行期诊断行数据来源:模型目录端点(compat 字段表同批安装)。 */
|
|
1890
|
+
function useRuntimeDiagnostics() {
|
|
1779
1891
|
const [kitSource, setKitSource] = (0, react.useState)(null);
|
|
1780
1892
|
const [modelsDevStatus, setModelsDevStatus] = (0, react.useState)(null);
|
|
1781
|
-
const [saving, setSaving] = (0, react.useState)(false);
|
|
1782
|
-
const [refreshing, setRefreshing] = (0, react.useState)(false);
|
|
1783
|
-
const [status, setStatus] = (0, react.useState)(IDLE_STATUS);
|
|
1784
|
-
(0, react.useEffect)(() => {
|
|
1785
|
-
if (value === void 0 || draft !== null) return;
|
|
1786
|
-
setDraft(draftFromValue(value));
|
|
1787
|
-
}, [value, draft]);
|
|
1788
1893
|
(0, react.useEffect)(() => {
|
|
1789
1894
|
let alive = true;
|
|
1790
1895
|
fetchCatalog("", "models-dev").then((result) => {
|
|
@@ -1797,50 +1902,102 @@ ${extra}
|
|
|
1797
1902
|
alive = false;
|
|
1798
1903
|
};
|
|
1799
1904
|
}, []);
|
|
1905
|
+
return {
|
|
1906
|
+
kitSource,
|
|
1907
|
+
modelsDevStatus,
|
|
1908
|
+
setModelsDevStatus
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
/** 路由级草稿改写:改字段 / 新增 route / 删除 route(返回新草稿,不改入参)。 */
|
|
1912
|
+
function withProviderPatch(draft, route, patch) {
|
|
1913
|
+
const current = draft.providers[route] ?? emptyProviderDraft();
|
|
1914
|
+
return {
|
|
1915
|
+
...draft,
|
|
1916
|
+
providers: {
|
|
1917
|
+
...draft.providers,
|
|
1918
|
+
[route]: {
|
|
1919
|
+
...current,
|
|
1920
|
+
...patch
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
};
|
|
1924
|
+
}
|
|
1925
|
+
function withNewRoute(draft, key) {
|
|
1926
|
+
return {
|
|
1927
|
+
...draft,
|
|
1928
|
+
providers: {
|
|
1929
|
+
...draft.providers,
|
|
1930
|
+
[key]: emptyProviderDraft()
|
|
1931
|
+
}
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
function withoutRoute(draft, route) {
|
|
1935
|
+
const next = { ...draft.providers };
|
|
1936
|
+
delete next[route];
|
|
1937
|
+
return {
|
|
1938
|
+
...draft,
|
|
1939
|
+
providers: next
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
/** 卡片动作:手动拉取目录、放弃、保存。 */
|
|
1943
|
+
function cardActions(options) {
|
|
1944
|
+
const { t } = options;
|
|
1945
|
+
return [
|
|
1946
|
+
{
|
|
1947
|
+
key: "refresh",
|
|
1948
|
+
label: t(options.refreshing ? "refreshingCatalog" : "refreshCatalog"),
|
|
1949
|
+
disabled: options.disabled || options.refreshing,
|
|
1950
|
+
onClick: options.onRefresh
|
|
1951
|
+
},
|
|
1952
|
+
{
|
|
1953
|
+
key: "discard",
|
|
1954
|
+
label: t("discard"),
|
|
1955
|
+
disabled: !options.dirty || options.saving,
|
|
1956
|
+
onClick: options.onDiscard
|
|
1957
|
+
},
|
|
1958
|
+
{
|
|
1959
|
+
key: "save",
|
|
1960
|
+
label: t(options.saving ? "saving" : "save"),
|
|
1961
|
+
variant: "primary",
|
|
1962
|
+
disabled: !options.dirty || options.invalid || options.saving || options.disabled,
|
|
1963
|
+
onClick: options.onSave
|
|
1964
|
+
}
|
|
1965
|
+
];
|
|
1966
|
+
}
|
|
1967
|
+
function LlmPiCard(props) {
|
|
1968
|
+
const { t, scope, api } = props;
|
|
1969
|
+
const snapshot = (0, react.useSyncExternalStore)((listener) => scope.subscribe(listener), () => scope.getSnapshot());
|
|
1970
|
+
const value = snapshot.value;
|
|
1971
|
+
const [draft, setDraft] = (0, react.useState)(null);
|
|
1972
|
+
const [epoch, setEpoch] = (0, react.useState)(0);
|
|
1973
|
+
const [saving, setSaving] = (0, react.useState)(false);
|
|
1974
|
+
const [refreshing, setRefreshing] = (0, react.useState)(false);
|
|
1975
|
+
const [status, setStatus] = (0, react.useState)(IDLE_STATUS);
|
|
1976
|
+
const { kitSource, modelsDevStatus, setModelsDevStatus } = useRuntimeDiagnostics();
|
|
1977
|
+
(0, react.useEffect)(() => {
|
|
1978
|
+
if (value === void 0 || draft !== null) return;
|
|
1979
|
+
setDraft(draftFromValue(value));
|
|
1980
|
+
}, [value, draft]);
|
|
1800
1981
|
const dirty = (0, react.useMemo)(() => value !== void 0 && draft !== null && JSON.stringify(toPatch(draft)) !== JSON.stringify(toPatch(draftFromValue(value))), [value, draft]);
|
|
1801
1982
|
const invalid = (0, react.useMemo)(() => {
|
|
1802
1983
|
if (draft === null) return false;
|
|
1803
1984
|
return !numTextOk(draft.catalogRefreshHours) || Object.values(draft.providers).some((provider) => provider.models.some((model) => model.id.trim() === ""));
|
|
1804
1985
|
}, [draft]);
|
|
1805
1986
|
if (props.view === "summary") return t("summaryLine");
|
|
1806
|
-
if (value === void 0 || draft === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
className: "lpc-readOnly",
|
|
1810
|
-
children: t("loading")
|
|
1811
|
-
})
|
|
1987
|
+
if (value === void 0 || draft === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardLoading, {
|
|
1988
|
+
prefix: "lpc",
|
|
1989
|
+
text: t("loading")
|
|
1812
1990
|
});
|
|
1813
1991
|
const setProvider = (route, patch) => {
|
|
1814
|
-
|
|
1815
|
-
setDraft({
|
|
1816
|
-
...draft,
|
|
1817
|
-
providers: {
|
|
1818
|
-
...draft.providers,
|
|
1819
|
-
[route]: {
|
|
1820
|
-
...current,
|
|
1821
|
-
...patch
|
|
1822
|
-
}
|
|
1823
|
-
}
|
|
1824
|
-
});
|
|
1992
|
+
setDraft(withProviderPatch(draft, route, patch));
|
|
1825
1993
|
setStatus(IDLE_STATUS);
|
|
1826
1994
|
};
|
|
1827
1995
|
const onAddRoute = (key) => {
|
|
1828
|
-
setDraft(
|
|
1829
|
-
...draft,
|
|
1830
|
-
providers: {
|
|
1831
|
-
...draft.providers,
|
|
1832
|
-
[key]: emptyProviderDraft()
|
|
1833
|
-
}
|
|
1834
|
-
});
|
|
1996
|
+
setDraft(withNewRoute(draft, key));
|
|
1835
1997
|
setStatus(IDLE_STATUS);
|
|
1836
1998
|
};
|
|
1837
1999
|
const onRemoveRoute = (route) => {
|
|
1838
|
-
|
|
1839
|
-
delete next[route];
|
|
1840
|
-
setDraft({
|
|
1841
|
-
...draft,
|
|
1842
|
-
providers: next
|
|
1843
|
-
});
|
|
2000
|
+
setDraft(withoutRoute(draft, route));
|
|
1844
2001
|
setStatus(IDLE_STATUS);
|
|
1845
2002
|
};
|
|
1846
2003
|
const onSave = () => {
|
|
@@ -1889,8 +2046,6 @@ ${extra}
|
|
|
1889
2046
|
prefix: "lpc",
|
|
1890
2047
|
title: t("title"),
|
|
1891
2048
|
description: t("description"),
|
|
1892
|
-
open,
|
|
1893
|
-
onToggle: setOpen,
|
|
1894
2049
|
statusBadge: {
|
|
1895
2050
|
text: t(draft.enabled ? "enabledOn" : "enabledOff"),
|
|
1896
2051
|
on: draft.enabled
|
|
@@ -1899,116 +2054,39 @@ ${extra}
|
|
|
1899
2054
|
dirtyLabel: t("unsaved"),
|
|
1900
2055
|
readOnlyNotice: disabled ? t("readOnly") : void 0,
|
|
1901
2056
|
status,
|
|
1902
|
-
actions:
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
2057
|
+
actions: cardActions({
|
|
2058
|
+
t,
|
|
2059
|
+
dirty,
|
|
2060
|
+
invalid,
|
|
2061
|
+
saving,
|
|
2062
|
+
refreshing,
|
|
2063
|
+
disabled,
|
|
2064
|
+
onRefresh: onRefreshCatalog,
|
|
2065
|
+
onDiscard,
|
|
2066
|
+
onSave
|
|
2067
|
+
}),
|
|
2068
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RootFields, {
|
|
2069
|
+
t,
|
|
2070
|
+
draft,
|
|
2071
|
+
disabled,
|
|
2072
|
+
kitSource,
|
|
2073
|
+
modelsDevStatus,
|
|
2074
|
+
onEdit: (patch) => {
|
|
2075
|
+
setDraft({
|
|
2076
|
+
...draft,
|
|
2077
|
+
...patch
|
|
2078
|
+
});
|
|
2079
|
+
setStatus(IDLE_STATUS);
|
|
1921
2080
|
}
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
setDraft({
|
|
1932
|
-
...draft,
|
|
1933
|
-
enabled: value
|
|
1934
|
-
});
|
|
1935
|
-
setStatus(IDLE_STATUS);
|
|
1936
|
-
}
|
|
1937
|
-
}),
|
|
1938
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField$1, {
|
|
1939
|
-
prefix: "lpc",
|
|
1940
|
-
id: "lpc-catalogUrl",
|
|
1941
|
-
label: t("catalogUrl"),
|
|
1942
|
-
hint: t("catalogUrlHint"),
|
|
1943
|
-
value: draft.catalogUrl,
|
|
1944
|
-
disabled,
|
|
1945
|
-
onEdit: (value) => {
|
|
1946
|
-
setDraft({
|
|
1947
|
-
...draft,
|
|
1948
|
-
catalogUrl: value
|
|
1949
|
-
});
|
|
1950
|
-
setStatus(IDLE_STATUS);
|
|
1951
|
-
}
|
|
1952
|
-
}),
|
|
1953
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField$1, {
|
|
1954
|
-
prefix: "lpc",
|
|
1955
|
-
id: "lpc-catalogRefresh",
|
|
1956
|
-
label: t("catalogRefreshHours"),
|
|
1957
|
-
hint: t("catalogRefreshHoursHint"),
|
|
1958
|
-
value: draft.catalogRefreshHours,
|
|
1959
|
-
numeric: true,
|
|
1960
|
-
disabled,
|
|
1961
|
-
invalid: !numTextOk(draft.catalogRefreshHours),
|
|
1962
|
-
invalidLabel: t("invalidNumber"),
|
|
1963
|
-
onEdit: (value) => {
|
|
1964
|
-
setDraft({
|
|
1965
|
-
...draft,
|
|
1966
|
-
catalogRefreshHours: value
|
|
1967
|
-
});
|
|
1968
|
-
setStatus(IDLE_STATUS);
|
|
1969
|
-
}
|
|
1970
|
-
}),
|
|
1971
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField$1, {
|
|
1972
|
-
prefix: "lpc",
|
|
1973
|
-
id: "lpc-catalogProxy",
|
|
1974
|
-
label: t("catalogProxy"),
|
|
1975
|
-
hint: t("catalogProxyHint"),
|
|
1976
|
-
value: draft.catalogProxy,
|
|
1977
|
-
disabled,
|
|
1978
|
-
onEdit: (value) => {
|
|
1979
|
-
setDraft({
|
|
1980
|
-
...draft,
|
|
1981
|
-
catalogProxy: value
|
|
1982
|
-
});
|
|
1983
|
-
setStatus(IDLE_STATUS);
|
|
1984
|
-
}
|
|
1985
|
-
}),
|
|
1986
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1987
|
-
className: "lpc-statusRow",
|
|
1988
|
-
children: [
|
|
1989
|
-
t("kitSource"),
|
|
1990
|
-
":",
|
|
1991
|
-
kitSource ?? ""
|
|
1992
|
-
]
|
|
1993
|
-
}),
|
|
1994
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1995
|
-
className: "lpc-statusRow",
|
|
1996
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1997
|
-
t("modelsDevStatus"),
|
|
1998
|
-
":",
|
|
1999
|
-
modelsDevText(modelsDevStatus, t)
|
|
2000
|
-
] })
|
|
2001
|
-
}),
|
|
2002
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProvidersSection, {
|
|
2003
|
-
providers: draft.providers,
|
|
2004
|
-
epoch,
|
|
2005
|
-
disabled,
|
|
2006
|
-
t,
|
|
2007
|
-
onAddRoute,
|
|
2008
|
-
onRemoveRoute,
|
|
2009
|
-
onPatchProvider: setProvider
|
|
2010
|
-
})
|
|
2011
|
-
]
|
|
2081
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProvidersSection, {
|
|
2082
|
+
providers: draft.providers,
|
|
2083
|
+
epoch,
|
|
2084
|
+
disabled,
|
|
2085
|
+
t,
|
|
2086
|
+
onAddRoute,
|
|
2087
|
+
onRemoveRoute,
|
|
2088
|
+
onPatchProvider: setProvider
|
|
2089
|
+
})]
|
|
2012
2090
|
});
|
|
2013
2091
|
}
|
|
2014
2092
|
//#endregion
|
|
@@ -2040,6 +2118,8 @@ ${extra}
|
|
|
2040
2118
|
refreshOk: "目录已拉取。",
|
|
2041
2119
|
refreshFailed: "拉取失败:",
|
|
2042
2120
|
advancedGroup: "高级设置(retryPolicy / compat)",
|
|
2121
|
+
extraFields: "其余字段(JSON)",
|
|
2122
|
+
extraFieldsHint: "表单未覆盖的 wire 字段原样往返:如 adapter=deepseek 的 thinking / reasoningEffort、Files API 与图片配额等。对象整体合并进该 route(模型级同 key 覆盖 route 级),非法 JSON 不会提交。",
|
|
2043
2123
|
providersGroup: "Provider 路由",
|
|
2044
2124
|
addRoute: "新增 route",
|
|
2045
2125
|
addRoutePlaceholder: "新 route 键名(如 my-llm)",
|
|
@@ -2122,6 +2202,8 @@ ${extra}
|
|
|
2122
2202
|
refreshOk: "Catalog refreshed.",
|
|
2123
2203
|
refreshFailed: "Refresh failed: ",
|
|
2124
2204
|
advancedGroup: "Advanced (retryPolicy / compat)",
|
|
2205
|
+
extraFields: "Other fields (JSON)",
|
|
2206
|
+
extraFieldsHint: "Wire fields the form does not cover, round-tripped verbatim: e.g. thinking / reasoningEffort and the Files API & image quotas for adapter=deepseek. The object merges into this route (model-level keys win); invalid JSON is not submitted.",
|
|
2125
2207
|
providersGroup: "Provider routes",
|
|
2126
2208
|
addRoute: "Add route",
|
|
2127
2209
|
addRoutePlaceholder: "New route key (e.g. my-llm)",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-plus/llm-pi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.42",
|
|
4
4
|
"description": "dsh-plus service+ui plugin: 基于 PiAiAdapter 的自定义 LLM 路由(全量 compat、模型继承、models.dev 目录兜底)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"https-proxy-agent": "^9.1.0",
|
|
35
|
-
"@dsh-plus/shared": "0.1.
|
|
35
|
+
"@dsh-plus/shared": "0.1.32"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@deepseek-ai/cordis": "^4.0.4",
|
package/src/client/card.tsx
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 「LLM 路由」配置卡片:经 injectPluginConfigCard
|
|
3
|
-
*
|
|
4
|
-
* summary/page)。
|
|
2
|
+
* 「LLM 路由」配置卡片:经 injectPluginConfigCard 注册(插件页
|
|
3
|
+
* plugins.row.config / plugins.bundle.config,view 分发 summary/page)。
|
|
5
4
|
* 顶部:enabled / catalogUrl / catalogRefreshHours / 只读状态行(kitSource、
|
|
6
5
|
* modelsDevStatus,来自模型目录端点)+ 保存(settings.update 全量深合并)与
|
|
7
6
|
* 错误/成功提示;下方为 providers 路由列表(新增/删除/字段编辑/compat/模型
|
|
@@ -10,14 +9,14 @@
|
|
|
10
9
|
*/
|
|
11
10
|
|
|
12
11
|
import {
|
|
12
|
+
type CardAction,
|
|
13
13
|
CardChrome,
|
|
14
|
+
CardLoading,
|
|
14
15
|
type CardStatusState,
|
|
15
|
-
CheckRow,
|
|
16
16
|
IDLE_STATUS,
|
|
17
17
|
type NamespaceSettingsApi,
|
|
18
18
|
type PluginConfigViewProps,
|
|
19
19
|
type Scope,
|
|
20
|
-
TextField,
|
|
21
20
|
} from '@dsh-plus/shared/client'
|
|
22
21
|
import { type ReactElement, useEffect, useMemo, useState, useSyncExternalStore } from 'react'
|
|
23
22
|
import { type ConfigValue, fetchCatalog, refreshCatalog, type WireModelsDevStatus } from './api.ts'
|
|
@@ -31,6 +30,7 @@ import {
|
|
|
31
30
|
toPatch,
|
|
32
31
|
} from './draft.ts'
|
|
33
32
|
import type { Translate } from './i18n.ts'
|
|
33
|
+
import { RootFields } from './root-fields.tsx'
|
|
34
34
|
import { ProvidersSection } from './views/providers.tsx'
|
|
35
35
|
|
|
36
36
|
export interface CardProps extends PluginConfigViewProps {
|
|
@@ -39,11 +39,81 @@ export interface CardProps extends PluginConfigViewProps {
|
|
|
39
39
|
api: NamespaceSettingsApi
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
/** 运行期诊断行数据来源:模型目录端点(compat 字段表同批安装)。 */
|
|
43
|
+
function useRuntimeDiagnostics(): {
|
|
44
|
+
kitSource: string | null
|
|
45
|
+
modelsDevStatus: WireModelsDevStatus | null
|
|
46
|
+
setModelsDevStatus(next: WireModelsDevStatus): void
|
|
47
|
+
} {
|
|
48
|
+
const [kitSource, setKitSource] = useState<string | null>(null)
|
|
49
|
+
const [modelsDevStatus, setModelsDevStatus] = useState<WireModelsDevStatus | null>(null)
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
let alive = true
|
|
52
|
+
fetchCatalog('', 'models-dev')
|
|
53
|
+
.then((result) => {
|
|
54
|
+
if (!alive) return
|
|
55
|
+
setKitSource(result.kitSource ?? null)
|
|
56
|
+
setModelsDevStatus(result.status ?? null)
|
|
57
|
+
if (result.compat !== undefined) installCompatFields(result.compat.fields)
|
|
58
|
+
})
|
|
59
|
+
.catch(() => {})
|
|
60
|
+
return () => {
|
|
61
|
+
alive = false
|
|
62
|
+
}
|
|
63
|
+
}, [])
|
|
64
|
+
return { kitSource, modelsDevStatus, setModelsDevStatus }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** 路由级草稿改写:改字段 / 新增 route / 删除 route(返回新草稿,不改入参)。 */
|
|
68
|
+
function withProviderPatch(draft: Draft, route: string, patch: Partial<ProviderDraft>): Draft {
|
|
69
|
+
const current = draft.providers[route] ?? emptyProviderDraft()
|
|
70
|
+
return { ...draft, providers: { ...draft.providers, [route]: { ...current, ...patch } } }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function withNewRoute(draft: Draft, key: string): Draft {
|
|
74
|
+
return { ...draft, providers: { ...draft.providers, [key]: emptyProviderDraft() } }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function withoutRoute(draft: Draft, route: string): Draft {
|
|
78
|
+
const next = { ...draft.providers }
|
|
79
|
+
delete next[route]
|
|
80
|
+
return { ...draft, providers: next }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** 卡片动作:手动拉取目录、放弃、保存。 */
|
|
84
|
+
function cardActions(options: {
|
|
85
|
+
t: Translate
|
|
86
|
+
dirty: boolean
|
|
87
|
+
invalid: boolean
|
|
88
|
+
saving: boolean
|
|
89
|
+
refreshing: boolean
|
|
90
|
+
disabled: boolean
|
|
91
|
+
onRefresh(): void
|
|
92
|
+
onDiscard(): void
|
|
93
|
+
onSave(): void
|
|
94
|
+
}): CardAction[] {
|
|
95
|
+
const { t } = options
|
|
96
|
+
return [
|
|
97
|
+
{
|
|
98
|
+
key: 'refresh',
|
|
99
|
+
label: t(options.refreshing ? 'refreshingCatalog' : 'refreshCatalog'),
|
|
100
|
+
disabled: options.disabled || options.refreshing,
|
|
101
|
+
onClick: options.onRefresh,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
key: 'discard',
|
|
105
|
+
label: t('discard'),
|
|
106
|
+
disabled: !options.dirty || options.saving,
|
|
107
|
+
onClick: options.onDiscard,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
key: 'save',
|
|
111
|
+
label: t(options.saving ? 'saving' : 'save'),
|
|
112
|
+
variant: 'primary',
|
|
113
|
+
disabled: !options.dirty || options.invalid || options.saving || options.disabled,
|
|
114
|
+
onClick: options.onSave,
|
|
115
|
+
},
|
|
116
|
+
]
|
|
47
117
|
}
|
|
48
118
|
|
|
49
119
|
export function LlmPiCard(props: CardProps): ReactElement | string | null {
|
|
@@ -53,15 +123,12 @@ export function LlmPiCard(props: CardProps): ReactElement | string | null {
|
|
|
53
123
|
() => scope.getSnapshot(),
|
|
54
124
|
)
|
|
55
125
|
const value = snapshot.value as ConfigValue | undefined
|
|
56
|
-
// 0.1.6-alpha.2 插件页 page 视图为表单落地页,默认展开;旧槽位无 view,保持折叠。
|
|
57
|
-
const [open, setOpen] = useState(props.view === 'page')
|
|
58
126
|
const [draft, setDraft] = useState<Draft | null>(null)
|
|
59
127
|
const [epoch, setEpoch] = useState(0)
|
|
60
|
-
const [kitSource, setKitSource] = useState<string | null>(null)
|
|
61
|
-
const [modelsDevStatus, setModelsDevStatus] = useState<WireModelsDevStatus | null>(null)
|
|
62
128
|
const [saving, setSaving] = useState(false)
|
|
63
129
|
const [refreshing, setRefreshing] = useState(false)
|
|
64
130
|
const [status, setStatus] = useState<CardStatusState>(IDLE_STATUS)
|
|
131
|
+
const { kitSource, modelsDevStatus, setModelsDevStatus } = useRuntimeDiagnostics()
|
|
65
132
|
|
|
66
133
|
// 首次拿到解析值后播种草稿;后续 Host 更新不覆盖在途编辑(与官方 staged 表单一致)。
|
|
67
134
|
useEffect(() => {
|
|
@@ -69,23 +136,6 @@ export function LlmPiCard(props: CardProps): ReactElement | string | null {
|
|
|
69
136
|
setDraft(draftFromValue(value))
|
|
70
137
|
}, [value, draft])
|
|
71
138
|
|
|
72
|
-
// 运行期诊断行(kitSource / models-dev 状态)来自模型目录端点,非配置数据。
|
|
73
|
-
// compat 字段表同批下发:安装后 UI 渲染与服务端校验同源(不再手抄镜像表)。
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
let alive = true
|
|
76
|
-
fetchCatalog('', 'models-dev')
|
|
77
|
-
.then((result) => {
|
|
78
|
-
if (!alive) return
|
|
79
|
-
setKitSource(result.kitSource ?? null)
|
|
80
|
-
setModelsDevStatus(result.status ?? null)
|
|
81
|
-
if (result.compat !== undefined) installCompatFields(result.compat.fields)
|
|
82
|
-
})
|
|
83
|
-
.catch(() => {})
|
|
84
|
-
return () => {
|
|
85
|
-
alive = false
|
|
86
|
-
}
|
|
87
|
-
}, [])
|
|
88
|
-
|
|
89
139
|
const dirty = useMemo(
|
|
90
140
|
() =>
|
|
91
141
|
value !== undefined &&
|
|
@@ -107,32 +157,19 @@ export function LlmPiCard(props: CardProps): ReactElement | string | null {
|
|
|
107
157
|
if (props.view === 'summary') return t('summaryLine')
|
|
108
158
|
|
|
109
159
|
if (value === undefined || draft === null) {
|
|
110
|
-
return (
|
|
111
|
-
<li className="lpc-card">
|
|
112
|
-
<p className="lpc-readOnly">{t('loading')}</p>
|
|
113
|
-
</li>
|
|
114
|
-
)
|
|
160
|
+
return <CardLoading prefix="lpc" text={t('loading')} />
|
|
115
161
|
}
|
|
116
162
|
|
|
117
163
|
const setProvider = (route: string, patch: Partial<ProviderDraft>): void => {
|
|
118
|
-
|
|
119
|
-
setDraft({
|
|
120
|
-
...draft,
|
|
121
|
-
providers: { ...draft.providers, [route]: { ...current, ...patch } },
|
|
122
|
-
})
|
|
164
|
+
setDraft(withProviderPatch(draft, route, patch))
|
|
123
165
|
setStatus(IDLE_STATUS)
|
|
124
166
|
}
|
|
125
167
|
const onAddRoute = (key: string): void => {
|
|
126
|
-
setDraft(
|
|
127
|
-
...draft,
|
|
128
|
-
providers: { ...draft.providers, [key]: emptyProviderDraft() },
|
|
129
|
-
})
|
|
168
|
+
setDraft(withNewRoute(draft, key))
|
|
130
169
|
setStatus(IDLE_STATUS)
|
|
131
170
|
}
|
|
132
171
|
const onRemoveRoute = (route: string): void => {
|
|
133
|
-
|
|
134
|
-
delete next[route]
|
|
135
|
-
setDraft({ ...draft, providers: next })
|
|
172
|
+
setDraft(withoutRoute(draft, route))
|
|
136
173
|
setStatus(IDLE_STATUS)
|
|
137
174
|
}
|
|
138
175
|
const onSave = (): void => {
|
|
@@ -178,93 +215,34 @@ export function LlmPiCard(props: CardProps): ReactElement | string | null {
|
|
|
178
215
|
prefix="lpc"
|
|
179
216
|
title={t('title')}
|
|
180
217
|
description={t('description')}
|
|
181
|
-
open={open}
|
|
182
|
-
onToggle={setOpen}
|
|
183
218
|
statusBadge={{ text: t(draft.enabled ? 'enabledOn' : 'enabledOff'), on: draft.enabled }}
|
|
184
219
|
dirty={dirty}
|
|
185
220
|
dirtyLabel={t('unsaved')}
|
|
186
221
|
readOnlyNotice={disabled ? t('readOnly') : undefined}
|
|
187
222
|
status={status}
|
|
188
|
-
actions={
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
onClick: onDiscard,
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
key: 'save',
|
|
203
|
-
label: t(saving ? 'saving' : 'save'),
|
|
204
|
-
variant: 'primary',
|
|
205
|
-
disabled: !dirty || invalid || saving || disabled,
|
|
206
|
-
onClick: onSave,
|
|
207
|
-
},
|
|
208
|
-
]}
|
|
223
|
+
actions={cardActions({
|
|
224
|
+
t,
|
|
225
|
+
dirty,
|
|
226
|
+
invalid,
|
|
227
|
+
saving,
|
|
228
|
+
refreshing,
|
|
229
|
+
disabled,
|
|
230
|
+
onRefresh: onRefreshCatalog,
|
|
231
|
+
onDiscard,
|
|
232
|
+
onSave,
|
|
233
|
+
})}
|
|
209
234
|
>
|
|
210
|
-
<
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
label={t('enabled')}
|
|
214
|
-
checked={draft.enabled}
|
|
215
|
-
disabled={disabled}
|
|
216
|
-
onEdit={(value) => {
|
|
217
|
-
setDraft({ ...draft, enabled: value })
|
|
218
|
-
setStatus(IDLE_STATUS)
|
|
219
|
-
}}
|
|
220
|
-
/>
|
|
221
|
-
<TextField
|
|
222
|
-
prefix="lpc"
|
|
223
|
-
id="lpc-catalogUrl"
|
|
224
|
-
label={t('catalogUrl')}
|
|
225
|
-
hint={t('catalogUrlHint')}
|
|
226
|
-
value={draft.catalogUrl}
|
|
227
|
-
disabled={disabled}
|
|
228
|
-
onEdit={(value) => {
|
|
229
|
-
setDraft({ ...draft, catalogUrl: value })
|
|
230
|
-
setStatus(IDLE_STATUS)
|
|
231
|
-
}}
|
|
232
|
-
/>
|
|
233
|
-
<TextField
|
|
234
|
-
prefix="lpc"
|
|
235
|
-
id="lpc-catalogRefresh"
|
|
236
|
-
label={t('catalogRefreshHours')}
|
|
237
|
-
hint={t('catalogRefreshHoursHint')}
|
|
238
|
-
value={draft.catalogRefreshHours}
|
|
239
|
-
numeric
|
|
240
|
-
disabled={disabled}
|
|
241
|
-
invalid={!numTextOk(draft.catalogRefreshHours)}
|
|
242
|
-
invalidLabel={t('invalidNumber')}
|
|
243
|
-
onEdit={(value) => {
|
|
244
|
-
setDraft({ ...draft, catalogRefreshHours: value })
|
|
245
|
-
setStatus(IDLE_STATUS)
|
|
246
|
-
}}
|
|
247
|
-
/>
|
|
248
|
-
<TextField
|
|
249
|
-
prefix="lpc"
|
|
250
|
-
id="lpc-catalogProxy"
|
|
251
|
-
label={t('catalogProxy')}
|
|
252
|
-
hint={t('catalogProxyHint')}
|
|
253
|
-
value={draft.catalogProxy}
|
|
235
|
+
<RootFields
|
|
236
|
+
t={t}
|
|
237
|
+
draft={draft}
|
|
254
238
|
disabled={disabled}
|
|
255
|
-
|
|
256
|
-
|
|
239
|
+
kitSource={kitSource}
|
|
240
|
+
modelsDevStatus={modelsDevStatus}
|
|
241
|
+
onEdit={(patch) => {
|
|
242
|
+
setDraft({ ...draft, ...patch })
|
|
257
243
|
setStatus(IDLE_STATUS)
|
|
258
244
|
}}
|
|
259
245
|
/>
|
|
260
|
-
<p className="lpc-statusRow">
|
|
261
|
-
{t('kitSource')}:{kitSource ?? ''}
|
|
262
|
-
</p>
|
|
263
|
-
<div className="lpc-statusRow">
|
|
264
|
-
<span>
|
|
265
|
-
{t('modelsDevStatus')}:{modelsDevText(modelsDevStatus, t)}
|
|
266
|
-
</span>
|
|
267
|
-
</div>
|
|
268
246
|
<ProvidersSection
|
|
269
247
|
providers={draft.providers}
|
|
270
248
|
epoch={epoch}
|
package/src/client/client.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 浏览器半入口:注册 locale 字典 + 注册「LLM 路由」卡片
|
|
3
|
-
* (injectPluginConfigCard
|
|
4
|
-
*
|
|
3
|
+
* (injectPluginConfigCard 两槽位:插件页 plugins.row.config /
|
|
4
|
+
* plugins.bundle.config)。
|
|
5
5
|
* 构建产物为 window.__ModuleLoader__.load({id, factory}) 形式的 CJS factory
|
|
6
6
|
* (包装见 tsdown.config.ts);样式沿用官方 data-plugin-css 约定,HMR 据此卸载。
|
|
7
7
|
*
|
package/src/client/draft.ts
CHANGED
|
@@ -215,6 +215,18 @@ function extraOf(wire: object, known: Set<string>): Record<string, unknown> {
|
|
|
215
215
|
return out
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
/**
|
|
219
|
+
* JSON 文本框值 → extra 映射。
|
|
220
|
+
* `undefined`(空文本)表示清空为 `{}`;合法但非对象的 JSON(数组/标量/null)
|
|
221
|
+
* 返回 undefined,调用方据此忽略这次编辑——extra 是「其余 wire 字段」的映射,
|
|
222
|
+
* 静默丢弃已配置字段比拒绝一次编辑更糟。
|
|
223
|
+
*/
|
|
224
|
+
export function extraOfJson(value: unknown): Record<string, unknown> | undefined {
|
|
225
|
+
if (value === undefined) return {}
|
|
226
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined
|
|
227
|
+
return { ...(value as Record<string, unknown>) }
|
|
228
|
+
}
|
|
229
|
+
|
|
218
230
|
function modelDraftFromWire(model: WireModel): ModelDraft {
|
|
219
231
|
return {
|
|
220
232
|
id: model.id,
|
package/src/client/i18n.ts
CHANGED
|
@@ -28,6 +28,9 @@ const ownZh = {
|
|
|
28
28
|
refreshOk: '目录已拉取。',
|
|
29
29
|
refreshFailed: '拉取失败:',
|
|
30
30
|
advancedGroup: '高级设置(retryPolicy / compat)',
|
|
31
|
+
extraFields: '其余字段(JSON)',
|
|
32
|
+
extraFieldsHint:
|
|
33
|
+
'表单未覆盖的 wire 字段原样往返:如 adapter=deepseek 的 thinking / reasoningEffort、Files API 与图片配额等。对象整体合并进该 route(模型级同 key 覆盖 route 级),非法 JSON 不会提交。',
|
|
31
34
|
providersGroup: 'Provider 路由',
|
|
32
35
|
addRoute: '新增 route',
|
|
33
36
|
addRoutePlaceholder: '新 route 键名(如 my-llm)',
|
|
@@ -112,6 +115,9 @@ const ownEn = {
|
|
|
112
115
|
refreshOk: 'Catalog refreshed.',
|
|
113
116
|
refreshFailed: 'Refresh failed: ',
|
|
114
117
|
advancedGroup: 'Advanced (retryPolicy / compat)',
|
|
118
|
+
extraFields: 'Other fields (JSON)',
|
|
119
|
+
extraFieldsHint:
|
|
120
|
+
'Wire fields the form does not cover, round-tripped verbatim: e.g. thinking / reasoningEffort and the Files API & image quotas for adapter=deepseek. The object merges into this route (model-level keys win); invalid JSON is not submitted.',
|
|
115
121
|
providersGroup: 'Provider routes',
|
|
116
122
|
addRoute: 'Add route',
|
|
117
123
|
addRoutePlaceholder: 'New route key (e.g. my-llm)',
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 「LLM 路由」卡片的根字段组:总开关、目录端点/刷新间隔/代理,以及运行期诊断行
|
|
3
|
+
* (kitSource 与 models-dev 快照状态,来自模型目录端点而非配置数据)。
|
|
4
|
+
* @module llm-pi/client/root-fields
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { CheckRow, TextField } from '@dsh-plus/shared/client'
|
|
8
|
+
import type { ReactElement } from 'react'
|
|
9
|
+
|
|
10
|
+
import type { WireModelsDevStatus } from './api.ts'
|
|
11
|
+
import { type Draft, numTextOk } from './draft.ts'
|
|
12
|
+
import type { Translate } from './i18n.ts'
|
|
13
|
+
|
|
14
|
+
/** models-dev 状态行文案(缺席/错误/未拉取/正常四态)。 */
|
|
15
|
+
function modelsDevText(status: WireModelsDevStatus | null, t: Translate): string {
|
|
16
|
+
if (status === null) return t('modelsDevEmpty')
|
|
17
|
+
if (status.error !== null) return `${t('modelsDevError')}${status.error}`
|
|
18
|
+
if (status.fetchedAt === null) return t('modelsDevEmpty')
|
|
19
|
+
return `${t('modelsDevStatusLine')}:${status.providers} 个 provider,快照 ${status.fetchedAt}`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface RootFieldsProps {
|
|
23
|
+
t: Translate
|
|
24
|
+
draft: Draft
|
|
25
|
+
disabled: boolean
|
|
26
|
+
kitSource: string | null
|
|
27
|
+
modelsDevStatus: WireModelsDevStatus | null
|
|
28
|
+
/** 根字段编辑(合并式写入并清空状态行)。 */
|
|
29
|
+
onEdit(patch: Partial<Draft>): void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function RootFields(props: RootFieldsProps): ReactElement {
|
|
33
|
+
const { t, draft, disabled, onEdit } = props
|
|
34
|
+
return (
|
|
35
|
+
<>
|
|
36
|
+
<CheckRow
|
|
37
|
+
prefix="lpc"
|
|
38
|
+
id="lpc-enabled"
|
|
39
|
+
label={t('enabled')}
|
|
40
|
+
checked={draft.enabled}
|
|
41
|
+
disabled={disabled}
|
|
42
|
+
onEdit={(value) => onEdit({ enabled: value })}
|
|
43
|
+
/>
|
|
44
|
+
<TextField
|
|
45
|
+
prefix="lpc"
|
|
46
|
+
id="lpc-catalogUrl"
|
|
47
|
+
label={t('catalogUrl')}
|
|
48
|
+
hint={t('catalogUrlHint')}
|
|
49
|
+
value={draft.catalogUrl}
|
|
50
|
+
disabled={disabled}
|
|
51
|
+
onEdit={(value) => onEdit({ catalogUrl: value })}
|
|
52
|
+
/>
|
|
53
|
+
<TextField
|
|
54
|
+
prefix="lpc"
|
|
55
|
+
id="lpc-catalogRefresh"
|
|
56
|
+
label={t('catalogRefreshHours')}
|
|
57
|
+
hint={t('catalogRefreshHoursHint')}
|
|
58
|
+
value={draft.catalogRefreshHours}
|
|
59
|
+
numeric
|
|
60
|
+
disabled={disabled}
|
|
61
|
+
invalid={!numTextOk(draft.catalogRefreshHours)}
|
|
62
|
+
invalidLabel={t('invalidNumber')}
|
|
63
|
+
onEdit={(value) => onEdit({ catalogRefreshHours: value })}
|
|
64
|
+
/>
|
|
65
|
+
<TextField
|
|
66
|
+
prefix="lpc"
|
|
67
|
+
id="lpc-catalogProxy"
|
|
68
|
+
label={t('catalogProxy')}
|
|
69
|
+
hint={t('catalogProxyHint')}
|
|
70
|
+
value={draft.catalogProxy}
|
|
71
|
+
disabled={disabled}
|
|
72
|
+
onEdit={(value) => onEdit({ catalogProxy: value })}
|
|
73
|
+
/>
|
|
74
|
+
<p className="lpc-statusRow">
|
|
75
|
+
{t('kitSource')}:{props.kitSource ?? ''}
|
|
76
|
+
</p>
|
|
77
|
+
<div className="lpc-statusRow">
|
|
78
|
+
<span>
|
|
79
|
+
{t('modelsDevStatus')}:{modelsDevText(props.modelsDevStatus, t)}
|
|
80
|
+
</span>
|
|
81
|
+
</div>
|
|
82
|
+
</>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
@@ -9,8 +9,8 @@ import { CheckRow } from '@dsh-plus/shared/client'
|
|
|
9
9
|
import { type ReactElement, useEffect, useState } from 'react'
|
|
10
10
|
import { fetchCatalog, type WireModelsDevStatus } from '../api.ts'
|
|
11
11
|
import { MODALITIES, THINKING_LEVELS } from '../constants.ts'
|
|
12
|
-
import { emptyModelDraft, type ModelDraft, type ReasoningDraft } from '../draft.ts'
|
|
13
|
-
import { TextField } from '../fields.tsx'
|
|
12
|
+
import { emptyModelDraft, extraOfJson, type ModelDraft, type ReasoningDraft } from '../draft.ts'
|
|
13
|
+
import { JsonField, TextField } from '../fields.tsx'
|
|
14
14
|
import type { Translate } from '../i18n.ts'
|
|
15
15
|
import { CompatEditor } from './compat.tsx'
|
|
16
16
|
|
|
@@ -261,6 +261,21 @@ export function ModelRow(props: ModelRowProps): ReactElement {
|
|
|
261
261
|
t={t}
|
|
262
262
|
onEdit={(compat) => props.onPatch({ compat })}
|
|
263
263
|
/>
|
|
264
|
+
<JsonField
|
|
265
|
+
id={`${id}-extra`}
|
|
266
|
+
label={t('extraFields')}
|
|
267
|
+
hint={t('extraFieldsHint')}
|
|
268
|
+
invalidText={t('invalidJson')}
|
|
269
|
+
value={Object.keys(model.extra).length === 0 ? undefined : model.extra}
|
|
270
|
+
epoch={props.epoch}
|
|
271
|
+
disabled={props.disabled === true}
|
|
272
|
+
wide
|
|
273
|
+
onEdit={(extra) => {
|
|
274
|
+
const next = extraOfJson(extra)
|
|
275
|
+
// 合法但非对象的 JSON(数组/标量)忽略这次编辑,不静默清空已配置字段。
|
|
276
|
+
if (next !== undefined) props.onPatch({ extra: next })
|
|
277
|
+
}}
|
|
278
|
+
/>
|
|
264
279
|
</div>
|
|
265
280
|
)
|
|
266
281
|
}
|
|
@@ -9,6 +9,7 @@ import { ChevronDownIcon } from '@dsh-plus/shared/client'
|
|
|
9
9
|
import { type ReactElement, useState } from 'react'
|
|
10
10
|
|
|
11
11
|
import type { ProviderDraft } from '../draft.ts'
|
|
12
|
+
import { extraOfJson } from '../draft.ts'
|
|
12
13
|
import { CollapseSection, JsonField, KeyValueEditor } from '../fields.tsx'
|
|
13
14
|
import type { Translate } from '../i18n.ts'
|
|
14
15
|
import { CompatEditor } from './compat.tsx'
|
|
@@ -145,6 +146,21 @@ export function ProviderSection(props: ProviderSectionProps): ReactElement {
|
|
|
145
146
|
onEdit={(headers) => props.onPatch({ headers })}
|
|
146
147
|
/>
|
|
147
148
|
<CollapseSection id={`${id}-advanced`} title={t('advancedGroup')} defaultOpen={false}>
|
|
149
|
+
<JsonField
|
|
150
|
+
id={`${id}-extra`}
|
|
151
|
+
label={t('extraFields')}
|
|
152
|
+
hint={t('extraFieldsHint')}
|
|
153
|
+
invalidText={t('invalidJson')}
|
|
154
|
+
value={Object.keys(draft.extra).length === 0 ? undefined : draft.extra}
|
|
155
|
+
epoch={props.epoch}
|
|
156
|
+
disabled={props.disabled === true}
|
|
157
|
+
wide
|
|
158
|
+
onEdit={(extra) => {
|
|
159
|
+
const next = extraOfJson(extra)
|
|
160
|
+
// 合法但非对象的 JSON(数组/标量)忽略这次编辑,不静默清空已配置字段。
|
|
161
|
+
if (next !== undefined) props.onPatch({ extra: next })
|
|
162
|
+
}}
|
|
163
|
+
/>
|
|
148
164
|
<JsonField
|
|
149
165
|
id={`${id}-retry`}
|
|
150
166
|
label={t('retryPolicy')}
|
package/src/ns.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* settings 命名空间字面量(纯常量,零依赖,浏览器半可安全引入)。
|
|
3
3
|
* 服务端直接以字面量注册(0.1.2-alpha.2 起 dsh-settings 对命名空间做编译期
|
|
4
4
|
* 语法校验,见 config.ts);浏览器半把它作为配置卡片的注册键
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* (插件页配置卡片挂在 row/bundle config 槽位上,与命名空间解耦,
|
|
6
|
+
* 见 shared/config-slots),
|
|
7
7
|
* 同一字面量两处共用,防止漂移。
|
|
8
8
|
* @module llm-pi/ns
|
|
9
9
|
*/
|