@dsh-plus/llm-pi 0.1.41 → 0.1.43
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 +1528 -767
- package/lib/index.d.ts +4 -8
- package/lib/index.js +943 -391
- package/package.json +35 -36
- package/src/catalog/browse.ts +297 -0
- package/src/catalog/builtin.ts +183 -35
- package/src/client/api.ts +96 -33
- package/src/client/card.tsx +183 -133
- package/src/client/client.ts +4 -4
- package/src/client/constants.ts +25 -5
- package/src/client/draft.ts +34 -13
- package/src/client/fields.tsx +18 -3
- package/src/client/i18n.ts +153 -49
- package/src/client/root-fields.tsx +64 -52
- package/src/client/route-fit.ts +117 -0
- package/src/client/styles.ts +40 -10
- package/src/client/views/catalog-browser.tsx +376 -0
- package/src/client/views/catalog-row.tsx +147 -0
- package/src/client/views/compat.tsx +31 -3
- package/src/client/views/models.tsx +189 -193
- package/src/client/views/provider-fields.tsx +2 -2
- package/src/client/views/providers.tsx +51 -11
- package/src/compat.ts +43 -19
- package/src/config-api.ts +65 -37
- package/src/config.ts +20 -28
- package/src/inherit.ts +15 -31
- package/src/kit-meta.ts +156 -0
- package/src/ns.ts +2 -2
- package/src/{compat-gates.ts → official-surface.ts} +60 -16
- package/src/profiles.ts +52 -13
- package/src/resolve-dsh.ts +291 -72
- package/src/service.ts +209 -90
- package/src/catalog/models-dev.ts +0 -261
package/lib/client.js
CHANGED
|
@@ -4,50 +4,18 @@ 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 = {
|
|
12
12
|
kind: "idle",
|
|
13
13
|
text: ""
|
|
14
14
|
};
|
|
15
|
-
/** 状态行 + 动作按钮(两种形态共用,类名随形态切换度量)。 */
|
|
16
|
-
function CardFoot(props) {
|
|
17
|
-
const p = props.prefix;
|
|
18
|
-
const section = props.variant === "section";
|
|
19
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
20
|
-
className: section ? `${p}-sectionFooter` : `${p}-footer`,
|
|
21
|
-
children: [props.status !== void 0 && props.status.kind !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
22
|
-
className: `${p}-status${props.status.kind === "error" ? ` ${p}-statusError` : ""}`,
|
|
23
|
-
role: "status",
|
|
24
|
-
children: props.status.text
|
|
25
|
-
}) : null, props.actions.map((action) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
26
|
-
type: "button",
|
|
27
|
-
className: `${p}-btn ${action.variant === "primary" ? `${p}-btnPrimary` : `${p}-btnGhost`}`,
|
|
28
|
-
disabled: action.disabled === true,
|
|
29
|
-
onClick: action.onClick,
|
|
30
|
-
children: action.label
|
|
31
|
-
}, action.key))]
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
/** 只读提示 + 字段区 + 页脚(两种形态共用)。 */
|
|
35
|
-
function CardBody(props) {
|
|
36
|
-
const p = props.prefix;
|
|
37
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
38
|
-
props.readOnlyNotice !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
39
|
-
className: `${p}-readOnly`,
|
|
40
|
-
role: "status",
|
|
41
|
-
children: props.readOnlyNotice
|
|
42
|
-
}) : null,
|
|
43
|
-
props.children,
|
|
44
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardFoot, { ...props })
|
|
45
|
-
] });
|
|
46
|
-
}
|
|
47
15
|
function CardChrome(props) {
|
|
48
16
|
const p = props.prefix;
|
|
49
17
|
const badge = props.statusBadge;
|
|
50
|
-
|
|
18
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
51
19
|
className: `${p}-section`,
|
|
52
20
|
"aria-label": props.title,
|
|
53
21
|
children: [
|
|
@@ -72,50 +40,37 @@ window.__ModuleLoader__.load({
|
|
|
72
40
|
className: `${p}-sectionDesc`,
|
|
73
41
|
children: props.description
|
|
74
42
|
}),
|
|
75
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
43
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
76
44
|
className: `${p}-sectionBody`,
|
|
77
|
-
children:
|
|
45
|
+
children: [
|
|
46
|
+
props.readOnlyNotice !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
47
|
+
className: `${p}-readOnly`,
|
|
48
|
+
role: "status",
|
|
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
|
+
]
|
|
78
67
|
})
|
|
79
68
|
]
|
|
80
69
|
});
|
|
81
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
82
|
-
className: `${p}-card${props.open ? ` ${p}-cardOpen` : ""}`,
|
|
83
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
84
|
-
type: "button",
|
|
85
|
-
className: `${p}-header`,
|
|
86
|
-
"aria-expanded": props.open,
|
|
87
|
-
"aria-label": props.title,
|
|
88
|
-
onClick: () => props.onToggle(!props.open),
|
|
89
|
-
children: [
|
|
90
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
91
|
-
className: `${p}-headText`,
|
|
92
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
93
|
-
className: `${p}-name`,
|
|
94
|
-
children: props.title
|
|
95
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
96
|
-
className: `${p}-description`,
|
|
97
|
-
children: props.description
|
|
98
|
-
})]
|
|
99
|
-
}),
|
|
100
|
-
badge !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
101
|
-
className: `${p}-statusBadge ${badge.on ? `${p}-statusOn` : `${p}-statusOff`}`,
|
|
102
|
-
children: badge.text
|
|
103
|
-
}) : null,
|
|
104
|
-
props.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
105
|
-
className: `${p}-pending`,
|
|
106
|
-
children: props.dirtyLabel
|
|
107
|
-
}) : null,
|
|
108
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `${p}-chevron${props.open ? ` ${p}-chevronOpen` : ""}` })
|
|
109
|
-
]
|
|
110
|
-
}), props.open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
111
|
-
className: `${p}-body`,
|
|
112
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardBody, { ...props })
|
|
113
|
-
}) : null]
|
|
114
|
-
});
|
|
115
70
|
}
|
|
116
71
|
/** 取值未到达时的占位:与 CardChrome 同形态,避免宿主页里闪一次卡片外壳。 */
|
|
117
72
|
function CardLoading(props) {
|
|
118
|
-
|
|
73
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
119
74
|
className: `${props.prefix}-section`,
|
|
120
75
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
121
76
|
className: `${props.prefix}-readOnly`,
|
|
@@ -123,41 +78,44 @@ window.__ModuleLoader__.load({
|
|
|
123
78
|
children: props.text
|
|
124
79
|
})
|
|
125
80
|
});
|
|
126
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
127
|
-
className: `${props.prefix}-card`,
|
|
128
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
129
|
-
className: `${props.prefix}-readOnly`,
|
|
130
|
-
role: "status",
|
|
131
|
-
children: props.text
|
|
132
|
-
})
|
|
133
|
-
});
|
|
134
81
|
}
|
|
135
82
|
//#endregion
|
|
136
|
-
//#region ../shared/src/client/
|
|
137
|
-
/** dsh-plus 聚合 bundle 的包名(各插件 row 均经其补丁层插入 profile)。 */
|
|
138
|
-
const DSH_PLUS_BUNDLE = "@dsh-plus/bundle-main";
|
|
83
|
+
//#region ../shared/src/client/clipboard.ts
|
|
139
84
|
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
85
|
+
* 剪贴板复制助手(浏览器半共享件):`navigator.clipboard` 优先,
|
|
86
|
+
* 非安全上下文(如非 localhost 直连的 HTTP 页面)无该 API 时退 `execCommand`。
|
|
87
|
+
* @module @dsh-plus/shared/client/clipboard
|
|
143
88
|
*/
|
|
144
|
-
|
|
145
|
-
|
|
89
|
+
/** 复制文本到剪贴板;返回是否成功(失败静默,调用方自行决定是否提示)。 */
|
|
90
|
+
async function copyText(text) {
|
|
91
|
+
try {
|
|
92
|
+
await navigator.clipboard.writeText(text);
|
|
93
|
+
return true;
|
|
94
|
+
} catch {
|
|
95
|
+
const area = document.createElement("textarea");
|
|
96
|
+
area.value = text;
|
|
97
|
+
area.style.position = "fixed";
|
|
98
|
+
area.style.opacity = "0";
|
|
99
|
+
document.body.appendChild(area);
|
|
100
|
+
area.select();
|
|
101
|
+
const ok = document.execCommand("copy");
|
|
102
|
+
area.remove();
|
|
103
|
+
return ok;
|
|
104
|
+
}
|
|
146
105
|
}
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region ../shared/src/client/config-slots.ts
|
|
108
|
+
/** dsh-plus 聚合 bundle 的包名(各插件 row 均经其补丁层插入 profile)。 */
|
|
109
|
+
const DSH_PLUS_BUNDLE = "@dsh-plus/bundle-main";
|
|
147
110
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
111
|
+
* 把一张配置卡片注册进插件页的两个配置槽位。
|
|
112
|
+
* 两个槽位都由 0.2.1-alpha.1 的插件页声明,任一缺席即报错——不做版本探测。
|
|
150
113
|
*/
|
|
151
114
|
function injectPluginConfigCard(slots, reg) {
|
|
152
115
|
const base = {
|
|
153
116
|
locale: reg.ns,
|
|
154
117
|
inject: reg.inject
|
|
155
118
|
};
|
|
156
|
-
slots.inject("settings.plugin.item", () => slots.register({
|
|
157
|
-
name: "settings.plugin.item",
|
|
158
|
-
key: reg.ns,
|
|
159
|
-
...base
|
|
160
|
-
}, reg.component));
|
|
161
119
|
slots.inject("plugins.row.config", () => slots.register({
|
|
162
120
|
name: "plugins.row.config",
|
|
163
121
|
key: `${DSH_PLUS_BUNDLE}#${reg.rowId}`,
|
|
@@ -182,41 +140,6 @@ window.__ModuleLoader__.load({
|
|
|
182
140
|
*/
|
|
183
141
|
//#endregion
|
|
184
142
|
//#region ../shared/src/client/fields.tsx
|
|
185
|
-
function TextField$1(props) {
|
|
186
|
-
const p = props.prefix ?? "dshp";
|
|
187
|
-
const invalid = props.invalid === true;
|
|
188
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
189
|
-
className: `${p}-field`,
|
|
190
|
-
children: [
|
|
191
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
192
|
-
className: `${p}-head`,
|
|
193
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
194
|
-
className: `${p}-label`,
|
|
195
|
-
htmlFor: props.id,
|
|
196
|
-
children: props.label
|
|
197
|
-
}), props.badge !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
198
|
-
className: `${p}-badge ${props.badge.set ? `${p}-badgeSet` : `${p}-badgeUnset`}`,
|
|
199
|
-
children: props.badge.text
|
|
200
|
-
}) : null]
|
|
201
|
-
}),
|
|
202
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
203
|
-
id: props.id,
|
|
204
|
-
className: `${p}-input${invalid ? ` ${p}-inputInvalid` : ""}`,
|
|
205
|
-
type: props.password === true ? "password" : "text",
|
|
206
|
-
inputMode: props.numeric === true ? "numeric" : void 0,
|
|
207
|
-
autoComplete: props.password === true ? "off" : void 0,
|
|
208
|
-
"aria-invalid": invalid || void 0,
|
|
209
|
-
value: props.value,
|
|
210
|
-
disabled: props.disabled === true,
|
|
211
|
-
onChange: (event) => props.onEdit(event.target.value)
|
|
212
|
-
}),
|
|
213
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
214
|
-
className: invalid ? `${p}-invalid` : `${p}-hint`,
|
|
215
|
-
children: invalid ? props.invalidLabel ?? "" : props.hint ?? ""
|
|
216
|
-
})
|
|
217
|
-
]
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
143
|
/**
|
|
221
144
|
* 勾选行:自绘圆角扁平开关(switch),对齐官方 WebUI 无原生 checkbox 的
|
|
222
145
|
* 设计语言(官方选择类控件全走胶囊/菜单,见 cardCss 的 -switch 规则)。
|
|
@@ -378,36 +301,33 @@ window.__ModuleLoader__.load({
|
|
|
378
301
|
//#endregion
|
|
379
302
|
//#region ../shared/src/client/styles.ts
|
|
380
303
|
/**
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
304
|
+
* 配置分节样式工厂(各插件 client 的公共收编版)。
|
|
305
|
+
*
|
|
306
|
+
* 目标宿主 dsh 0.2.1-alpha.1:插件页把配置视图放进已有页面级内边距的容器里
|
|
307
|
+
* (`div.detailSections[data-plugin-config]` 或 `section.detailSection[data-plugin-config]`),
|
|
308
|
+
* 外层无边框——故这里输出的是**无边框分节**(小节标题 13px/600、字段 12px 0、
|
|
309
|
+
* 横向零内边距、页脚非 sticky),度量对齐官方设置页
|
|
310
|
+
* (`@deepseek-ai/dsh-client-ui-primitives` 的 settings-form)。
|
|
311
|
+
*
|
|
312
|
+
* 移动端与可达性(767px 断点 + pointer:coarse,对齐 ui-mobile-fit 约定):
|
|
384
313
|
* - 窄屏 input/textarea 16px(防 iOS 聚焦缩放)、可点目标 ≥44px;
|
|
385
|
-
* -
|
|
386
|
-
* - 展开态 body 底部 sticky 保存栏(长表单滚动中始终可达)。
|
|
387
|
-
* - 分节形态(-section,插件页 page 视图内嵌宿主页面):无边框/背景、横向零
|
|
388
|
-
* 内边距,页脚不 sticky——官方插件页已提供页面级内边距,再叠一层卡片会
|
|
389
|
-
* 缩窄移动端可用宽度(卡片套卡片)。
|
|
314
|
+
* - 页脚按钮等宽换行、状态文字独占一行。
|
|
390
315
|
* 沿用官方 data-plugin / data-plugin-css 约定(HMR 据此卸载),不覆盖上游选择器。
|
|
391
316
|
* @module @dsh-plus/shared/client/styles
|
|
392
317
|
*/
|
|
393
|
-
/**
|
|
318
|
+
/** 生成全套分节 CSS;prefix 为插件类名前缀(如 'dne'),extra 为插件特有规则。 */
|
|
394
319
|
function cardCss(prefix, extra = "") {
|
|
395
320
|
return `
|
|
396
|
-
.${prefix}-
|
|
397
|
-
.${prefix}-
|
|
398
|
-
.${prefix}-
|
|
399
|
-
.${prefix}-
|
|
400
|
-
.${prefix}-
|
|
401
|
-
.${prefix}-
|
|
402
|
-
.${prefix}-
|
|
403
|
-
.${prefix}-description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}
|
|
404
|
-
.${prefix}-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s;display:inline-flex}
|
|
405
|
-
.${prefix}-chevronOpen{transform:rotate(180deg)}
|
|
321
|
+
.${prefix}-section{box-sizing:border-box;flex-direction:column;width:100%;min-width:0;padding:16px 0;display:flex}
|
|
322
|
+
.${prefix}-sectionHead{align-items:center;gap:8px;min-width:0;display:flex}
|
|
323
|
+
.${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}
|
|
324
|
+
.${prefix}-sectionBadge{white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
325
|
+
.${prefix}-sectionDesc{color:var(--dsw-alias-label-tertiary);margin:4px 0 0;font-size:12px;line-height:1.5}
|
|
326
|
+
.${prefix}-sectionBody{flex-direction:column;min-width:0;display:flex}
|
|
327
|
+
.${prefix}-sectionFooter{align-items:center;gap:8px;padding-top:16px;display:flex;flex-wrap:wrap}
|
|
406
328
|
.${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}
|
|
407
|
-
.${prefix}-statusBadge{white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
408
329
|
.${prefix}-statusOn{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
|
|
409
330
|
.${prefix}-statusOff{color:var(--dsw-alias-label-tertiary)}
|
|
410
|
-
.${prefix}-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}
|
|
411
331
|
.${prefix}-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
|
|
412
332
|
.${prefix}-field+.${prefix}-field{border-top:1px solid var(--dsw-alias-border-l2)}
|
|
413
333
|
.${prefix}-head{align-items:center;gap:8px;display:flex}
|
|
@@ -433,19 +353,9 @@ window.__ModuleLoader__.load({
|
|
|
433
353
|
.${prefix}-checkRow input:checked::after{left:16px;background:var(--dsw-alias-bg-base)}
|
|
434
354
|
.${prefix}-checkRow input:disabled{opacity:.4;cursor:default}
|
|
435
355
|
.${prefix}-checkRow input:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
|
|
436
|
-
/* 宿主内嵌分节形态(插件页 page 视图):官方设置页同构——无边框/背景,
|
|
437
|
-
横向零内边距(宽度交给宿主页面容器),小节标题 13px/600,页脚非 sticky。 */
|
|
438
|
-
.${prefix}-section{box-sizing:border-box;flex-direction:column;width:100%;min-width:0;padding:16px 0;display:flex}
|
|
439
|
-
.${prefix}-sectionHead{align-items:center;gap:8px;min-width:0;display:flex}
|
|
440
|
-
.${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}
|
|
441
|
-
.${prefix}-sectionBadge{white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
442
|
-
.${prefix}-sectionDesc{color:var(--dsw-alias-label-tertiary);margin:4px 0 0;font-size:12px;line-height:1.5}
|
|
443
|
-
.${prefix}-sectionBody{flex-direction:column;min-width:0;display:flex}
|
|
444
|
-
.${prefix}-sectionFooter{align-items:center;gap:8px;padding-top:16px;display:flex;flex-wrap:wrap}
|
|
445
356
|
.${prefix}-groupLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;padding:12px 0 2px;margin:0}
|
|
446
357
|
.${prefix}-readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}
|
|
447
358
|
.${prefix}-warn{color:var(--dsw-alias-label-error);margin:12px 0 0;font-size:12px;line-height:1.5}
|
|
448
|
-
.${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}
|
|
449
359
|
.${prefix}-status{min-width:0;color:var(--dsw-alias-label-secondary);flex:1;margin:0;font-size:12px;line-height:1.5}
|
|
450
360
|
.${prefix}-statusError{color:var(--dsw-alias-label-error)}
|
|
451
361
|
.${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}
|
|
@@ -455,16 +365,10 @@ window.__ModuleLoader__.load({
|
|
|
455
365
|
.${prefix}-btn:disabled{opacity:.4;cursor:default}
|
|
456
366
|
.${prefix}-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
|
|
457
367
|
@media (max-width:767px){
|
|
458
|
-
.${prefix}-body{margin:0 10px}
|
|
459
|
-
.${prefix}-header{padding:12px 12px}
|
|
460
368
|
.${prefix}-input,.${prefix}-select{font-size:16px;height:40px}
|
|
461
369
|
.${prefix}-checkRow{min-height:44px;padding:8px 0}
|
|
462
|
-
.${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}
|
|
463
370
|
.${prefix}-status{flex-basis:100%;flex:none}
|
|
464
371
|
.${prefix}-btn{min-height:44px;flex:1;min-width:96px}
|
|
465
|
-
.${prefix}-statusBadge{display:none}
|
|
466
|
-
.${prefix}-sectionBadge{display:inline-block}
|
|
467
|
-
.${prefix}-sectionFooter{position:static}
|
|
468
372
|
}
|
|
469
373
|
@media (pointer:coarse){
|
|
470
374
|
.${prefix}-btn{min-height:44px}
|
|
@@ -495,8 +399,8 @@ ${extra}
|
|
|
495
399
|
* settings 命名空间字面量(纯常量,零依赖,浏览器半可安全引入)。
|
|
496
400
|
* 服务端直接以字面量注册(0.1.2-alpha.2 起 dsh-settings 对命名空间做编译期
|
|
497
401
|
* 语法校验,见 config.ts);浏览器半把它作为配置卡片的注册键
|
|
498
|
-
*
|
|
499
|
-
*
|
|
402
|
+
* (插件页配置卡片挂在 row/bundle config 槽位上,与命名空间解耦,
|
|
403
|
+
* 见 shared/config-slots),
|
|
500
404
|
* 同一字面量两处共用,防止漂移。
|
|
501
405
|
* @module llm-pi/ns
|
|
502
406
|
*/
|
|
@@ -509,28 +413,41 @@ ${extra}
|
|
|
509
413
|
if (!res.ok) throw new Error(body.error ?? `HTTP ${res.status}`);
|
|
510
414
|
return body;
|
|
511
415
|
}
|
|
512
|
-
/**
|
|
513
|
-
async function
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
416
|
+
/** 运行期事实 + 目录索引(卡片挂载时一次)。 */
|
|
417
|
+
async function fetchCatalogMeta() {
|
|
418
|
+
return parse(await fetch(ROUTE_CATALOG, { credentials: "same-origin" }));
|
|
419
|
+
}
|
|
420
|
+
/** 目录搜索(模糊 q + provider/api/能力/分页)。 */
|
|
421
|
+
async function searchCatalog(query, signal) {
|
|
422
|
+
const params = new URLSearchParams();
|
|
423
|
+
if (query.q !== void 0 && query.q.length > 0) params.set("q", query.q);
|
|
424
|
+
if (query.provider !== void 0 && query.provider.length > 0) params.set("provider", query.provider);
|
|
425
|
+
if (query.api !== void 0 && query.api.length > 0) params.set("api", query.api);
|
|
426
|
+
if (query.reasoning === true) params.set("reasoning", "1");
|
|
427
|
+
if (query.image === true) params.set("image", "1");
|
|
428
|
+
if (query.servableOnly === false) params.set("servable", "0");
|
|
429
|
+
if (query.offset !== void 0) params.set("offset", String(query.offset));
|
|
430
|
+
if (query.limit !== void 0) params.set("limit", String(query.limit));
|
|
431
|
+
const suffix = params.toString();
|
|
432
|
+
return parse(await fetch(`${ROUTE_CATALOG}/models${suffix === "" ? "" : `?${suffix}`}`, {
|
|
433
|
+
credentials: "same-origin",
|
|
434
|
+
...signal === void 0 ? {} : { signal }
|
|
522
435
|
}));
|
|
523
436
|
}
|
|
524
437
|
//#endregion
|
|
525
438
|
//#region src/client/constants.ts
|
|
526
439
|
/**
|
|
527
|
-
* 浏览器半内联常量:与服务端 packages/llm-pi/src/config.ts
|
|
440
|
+
* 浏览器半内联常量:与服务端 packages/llm-pi/src/config.ts 逐字对齐。
|
|
528
441
|
* 浏览器半不能 import 服务端模块(tsdown 只打包 client 侧入口),
|
|
529
442
|
* 改动服务端这些常量时必须同步本文件。
|
|
443
|
+
*
|
|
444
|
+
* 两个集合改为**服务端下发**(与服务端校验同源,浏览器半不再手抄):
|
|
445
|
+
* - 线协议:取自已装官方适配器的 supportedProtocols()(`installProtocols`);
|
|
446
|
+
* - compat 字段表:取自官方 bundle + Config schema 推导(`installCompatFields`)。
|
|
530
447
|
* @module llm-pi/client/constants
|
|
531
448
|
*/
|
|
532
|
-
/**
|
|
533
|
-
const
|
|
449
|
+
/** 协议兜底集合(来源:config.ts FALLBACK_PROTOCOLS);服务端下发前/失败时使用。 */
|
|
450
|
+
const FALLBACK_PROTOCOLS = [
|
|
534
451
|
"openai-completions",
|
|
535
452
|
"openai-responses",
|
|
536
453
|
"anthropic-messages"
|
|
@@ -568,13 +485,26 @@ ${extra}
|
|
|
568
485
|
"high"
|
|
569
486
|
];
|
|
570
487
|
/**
|
|
488
|
+
* 生效协议集合:由服务端 /catalog 下发(服务端与官方适配器同源推导),
|
|
489
|
+
* 未拿到前用 FALLBACK_PROTOCOLS。
|
|
490
|
+
*/
|
|
491
|
+
let protocols = [...FALLBACK_PROTOCOLS];
|
|
492
|
+
/** 安装服务端下发的协议集合(card 挂载时调用)。 */
|
|
493
|
+
function installProtocols(list) {
|
|
494
|
+
if (list.length > 0) protocols = [...list];
|
|
495
|
+
}
|
|
496
|
+
/** 当前可选协议(顺序即官方表顺序)。 */
|
|
497
|
+
function protocolOptions() {
|
|
498
|
+
return protocols;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
571
501
|
* 逐协议 compat 字段表:**服务端推导结果经 /catalog 下发**,浏览器半不再手抄
|
|
572
|
-
* (手抄遗漏官方新增字段 = UI 不渲染 + 保存被后端拒绝;见
|
|
502
|
+
* (手抄遗漏官方新增字段 = UI 不渲染 + 保存被后端拒绝;见 official-surface.ts)。
|
|
573
503
|
* 未拿到服务端表时用 EMPTY(UI 只渲染提示行),不放内置副本兜底——宁可少画
|
|
574
504
|
* 控件,也不给出可能与官方不符的字段集。
|
|
575
505
|
*/
|
|
576
506
|
let compatTable = {};
|
|
577
|
-
/** 安装服务端下发的字段表(card
|
|
507
|
+
/** 安装服务端下发的字段表(card 挂载时调用)。 */
|
|
578
508
|
function installCompatFields(table) {
|
|
579
509
|
compatTable = table;
|
|
580
510
|
}
|
|
@@ -600,9 +530,6 @@ ${extra}
|
|
|
600
530
|
const value = Number(trimmed);
|
|
601
531
|
return Number.isFinite(value) ? value : void 0;
|
|
602
532
|
}
|
|
603
|
-
function numTextOk(text) {
|
|
604
|
-
return text.trim() !== "" && Number.isFinite(Number(text.trim()));
|
|
605
|
-
}
|
|
606
533
|
function headersToPairs(headers) {
|
|
607
534
|
return Object.entries(headers ?? {}).map(([key, value]) => ({
|
|
608
535
|
key,
|
|
@@ -862,95 +789,49 @@ ${extra}
|
|
|
862
789
|
function draftFromValue(value) {
|
|
863
790
|
return {
|
|
864
791
|
enabled: value.enabled,
|
|
865
|
-
catalogUrl: value.catalogUrl,
|
|
866
|
-
catalogRefreshHours: String(value.catalogRefreshHours),
|
|
867
|
-
catalogProxy: value.catalogProxy,
|
|
868
792
|
providers: Object.fromEntries(Object.entries(value.providers).map(([route, provider]) => [route, providerDraftFromWire(provider)]))
|
|
869
793
|
};
|
|
870
794
|
}
|
|
871
795
|
/** 提交补丁:完整配置对象,providers 全量替换;空值一律剔除。 */
|
|
872
796
|
function toPatch(draft) {
|
|
873
|
-
const refreshHours = toNum(draft.catalogRefreshHours);
|
|
874
797
|
return {
|
|
875
798
|
enabled: draft.enabled,
|
|
876
|
-
catalogUrl: draft.catalogUrl.trim(),
|
|
877
|
-
catalogRefreshHours: refreshHours,
|
|
878
|
-
catalogProxy: draft.catalogProxy.trim(),
|
|
879
799
|
providers: Object.fromEntries(Object.entries(draft.providers).map(([route, provider]) => [route, providerToWire(provider)]))
|
|
880
800
|
};
|
|
881
801
|
}
|
|
882
|
-
//#endregion
|
|
883
|
-
//#region src/client/root-fields.tsx
|
|
884
802
|
/**
|
|
885
|
-
*
|
|
886
|
-
*
|
|
887
|
-
*
|
|
803
|
+
* 把一个内置目录模型加进目标 route:只写 `id` + `extends`(**不预填**
|
|
804
|
+
* name/contextWindow/maxTokens——继承才能跟随 pi-ai 目录升级,预填会把值冻住)。
|
|
805
|
+
* 同 id 已存在时原样返回(added=false),不产生重复条目。
|
|
888
806
|
*/
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
if (
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
prefix: "lpc",
|
|
918
|
-
id: "lpc-catalogRefresh",
|
|
919
|
-
label: t("catalogRefreshHours"),
|
|
920
|
-
hint: t("catalogRefreshHoursHint"),
|
|
921
|
-
value: draft.catalogRefreshHours,
|
|
922
|
-
numeric: true,
|
|
923
|
-
disabled,
|
|
924
|
-
invalid: !numTextOk(draft.catalogRefreshHours),
|
|
925
|
-
invalidLabel: t("invalidNumber"),
|
|
926
|
-
onEdit: (value) => onEdit({ catalogRefreshHours: value })
|
|
927
|
-
}),
|
|
928
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField$1, {
|
|
929
|
-
prefix: "lpc",
|
|
930
|
-
id: "lpc-catalogProxy",
|
|
931
|
-
label: t("catalogProxy"),
|
|
932
|
-
hint: t("catalogProxyHint"),
|
|
933
|
-
value: draft.catalogProxy,
|
|
934
|
-
disabled,
|
|
935
|
-
onEdit: (value) => onEdit({ catalogProxy: value })
|
|
936
|
-
}),
|
|
937
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
938
|
-
className: "lpc-statusRow",
|
|
939
|
-
children: [
|
|
940
|
-
t("kitSource"),
|
|
941
|
-
":",
|
|
942
|
-
props.kitSource ?? ""
|
|
943
|
-
]
|
|
944
|
-
}),
|
|
945
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
946
|
-
className: "lpc-statusRow",
|
|
947
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
948
|
-
t("modelsDevStatus"),
|
|
949
|
-
":",
|
|
950
|
-
modelsDevText(props.modelsDevStatus, t)
|
|
951
|
-
] })
|
|
952
|
-
})
|
|
953
|
-
] });
|
|
807
|
+
function addModelEntry(draft, route, model) {
|
|
808
|
+
const provider = draft.providers[route];
|
|
809
|
+
if (provider === void 0) return {
|
|
810
|
+
draft,
|
|
811
|
+
added: false
|
|
812
|
+
};
|
|
813
|
+
if (provider.models.some((entry) => entry.id.trim() === model.id)) return {
|
|
814
|
+
draft,
|
|
815
|
+
added: false
|
|
816
|
+
};
|
|
817
|
+
const entry = {
|
|
818
|
+
...emptyModelDraft(),
|
|
819
|
+
id: model.id,
|
|
820
|
+
extends: model.path
|
|
821
|
+
};
|
|
822
|
+
return {
|
|
823
|
+
draft: {
|
|
824
|
+
...draft,
|
|
825
|
+
providers: {
|
|
826
|
+
...draft.providers,
|
|
827
|
+
[route]: {
|
|
828
|
+
...provider,
|
|
829
|
+
models: [...provider.models, entry]
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
},
|
|
833
|
+
added: true
|
|
834
|
+
};
|
|
954
835
|
}
|
|
955
836
|
//#endregion
|
|
956
837
|
//#region src/client/fields.tsx
|
|
@@ -961,7 +842,12 @@ ${extra}
|
|
|
961
842
|
* @module llm-pi/client/fields
|
|
962
843
|
*/
|
|
963
844
|
function CollapseSection(props) {
|
|
964
|
-
const [
|
|
845
|
+
const [innerOpen, setInnerOpen] = (0, react.useState)(props.defaultOpen);
|
|
846
|
+
const open = props.open ?? innerOpen;
|
|
847
|
+
const toggle = () => {
|
|
848
|
+
if (props.onToggle !== void 0) props.onToggle();
|
|
849
|
+
else setInnerOpen((value) => !value);
|
|
850
|
+
};
|
|
965
851
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
966
852
|
className: "lpc-collapse",
|
|
967
853
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
@@ -969,11 +855,18 @@ ${extra}
|
|
|
969
855
|
className: "lpc-collapseHead",
|
|
970
856
|
id: props.id,
|
|
971
857
|
"aria-expanded": open,
|
|
972
|
-
onClick:
|
|
973
|
-
children: [
|
|
974
|
-
className: "lpc-
|
|
975
|
-
|
|
976
|
-
|
|
858
|
+
onClick: toggle,
|
|
859
|
+
children: [
|
|
860
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `lpc-chevron${open ? " lpc-chevronOpen" : ""}` }),
|
|
861
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
862
|
+
className: "lpc-collapseTitle",
|
|
863
|
+
children: props.title
|
|
864
|
+
}),
|
|
865
|
+
props.meta !== void 0 && props.meta !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
866
|
+
className: "lpc-collapseMeta",
|
|
867
|
+
children: props.meta
|
|
868
|
+
}) : null
|
|
869
|
+
]
|
|
977
870
|
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
978
871
|
className: "lpc-collapseBody",
|
|
979
872
|
children: props.children
|
|
@@ -1215,211 +1108,850 @@ ${extra}
|
|
|
1215
1108
|
});
|
|
1216
1109
|
}
|
|
1217
1110
|
//#endregion
|
|
1218
|
-
//#region src/client/
|
|
1219
|
-
/**
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1111
|
+
//#region src/client/root-fields.tsx
|
|
1112
|
+
/**
|
|
1113
|
+
* 卡片根字段组:总开关 + 运行期状态行。
|
|
1114
|
+
*
|
|
1115
|
+
* 状态行回答的是"我现在到底在跑哪一份 pi-ai"——插件按文件路径动态加载已装 dsh 树
|
|
1116
|
+
* 里的套件(见 resolve-dsh.ts),manifest 上写的版本**不是**生效版本。这里显示
|
|
1117
|
+
* 生效版本、安装树、目录规模与数据日期、可服务协议、compat 表来源,以及逐项降级
|
|
1118
|
+
* 诊断;版本超出验证区间时给出提示(提示而非阻断)。
|
|
1119
|
+
* @module llm-pi/client/root-fields
|
|
1120
|
+
*/
|
|
1121
|
+
/** 时间戳 → 本地日期(秒级精度足够)。 */
|
|
1122
|
+
function formatTime(ms, fallback) {
|
|
1123
|
+
if (ms === void 0 || !Number.isFinite(ms)) return fallback;
|
|
1124
|
+
return new Date(ms).toLocaleString();
|
|
1125
|
+
}
|
|
1126
|
+
/** 生效版本三连(缺失显示"未知")。 */
|
|
1127
|
+
function versionsText(kit, t) {
|
|
1128
|
+
const unknown = t("runtimeVersionUnknown");
|
|
1129
|
+
return [
|
|
1130
|
+
`pi-ai ${kit.versions.piAi ?? unknown}`,
|
|
1131
|
+
`dsh-llm-pi-ai ${kit.versions.piAiAdapter ?? unknown}`,
|
|
1132
|
+
...kit.versions.dsh === void 0 ? [] : [`dsh ${kit.versions.dsh}`]
|
|
1133
|
+
].join(" · ");
|
|
1226
1134
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
onEdit: (value) => {
|
|
1288
|
-
if (value === "") setField(field, void 0);
|
|
1289
|
-
else setField(field, value);
|
|
1290
|
-
}
|
|
1291
|
-
}, field);
|
|
1292
|
-
})
|
|
1293
|
-
})]
|
|
1294
|
-
})
|
|
1295
|
-
});
|
|
1135
|
+
/** 来源标签:现场推导 / 回退快照。 */
|
|
1136
|
+
function sourceLabel(t, source) {
|
|
1137
|
+
return source === "official" ? t("runtimeDerived") : t("runtimeFallback");
|
|
1138
|
+
}
|
|
1139
|
+
function RootFields(props) {
|
|
1140
|
+
const { t, draft, disabled, kit, onEdit } = props;
|
|
1141
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1142
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
|
|
1143
|
+
prefix: "lpc",
|
|
1144
|
+
id: "lpc-enabled",
|
|
1145
|
+
label: t("enabled"),
|
|
1146
|
+
checked: draft.enabled,
|
|
1147
|
+
disabled,
|
|
1148
|
+
onEdit: (value) => onEdit({ enabled: value })
|
|
1149
|
+
}),
|
|
1150
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1151
|
+
className: "lpc-groupLabel",
|
|
1152
|
+
children: t("runtimeGroup")
|
|
1153
|
+
}),
|
|
1154
|
+
props.kitError !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1155
|
+
className: "lpc-invalid",
|
|
1156
|
+
children: `${t("browserFailed")}${props.kitError}`
|
|
1157
|
+
}) : null,
|
|
1158
|
+
kit === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1159
|
+
className: "lpc-hint",
|
|
1160
|
+
children: t("browserLoading")
|
|
1161
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1162
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1163
|
+
className: "lpc-statusRow",
|
|
1164
|
+
children: `${t("runtimeSource")}:${kit.source}${kit.root === void 0 ? "" : ` · ${t("runtimeRoot")} ${kit.root}`}`
|
|
1165
|
+
}),
|
|
1166
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1167
|
+
className: "lpc-statusRow",
|
|
1168
|
+
children: `${t("runtimeVersions")}:${versionsText(kit, t)}`
|
|
1169
|
+
}),
|
|
1170
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1171
|
+
className: "lpc-statusRow",
|
|
1172
|
+
children: `${t("runtimeCatalog")}:${kit.catalog.providers} ${t("runtimeCatalogUnit")} ${kit.catalog.models} ${t("runtimeCatalogModels")} · ${t("runtimeCatalogGenerated")} ${formatTime(kit.catalog.generatedAt, t("runtimeVersionUnknown"))}`
|
|
1173
|
+
}),
|
|
1174
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1175
|
+
className: "lpc-statusRow",
|
|
1176
|
+
children: `${t("runtimeProtocols")}:${kit.protocols.join(" / ")}(${sourceLabel(t, kit.protocolSource)})`
|
|
1177
|
+
}),
|
|
1178
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1179
|
+
className: "lpc-statusRow",
|
|
1180
|
+
children: `${t("runtimeCompat")}:${sourceLabel(t, kit.compatSource)}`
|
|
1181
|
+
}),
|
|
1182
|
+
kit.versionNotice !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1183
|
+
className: "lpc-invalid",
|
|
1184
|
+
children: `${t("runtimeNotice")}:${kit.versionNotice}`
|
|
1185
|
+
}) : null,
|
|
1186
|
+
kit.diagnostics.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1187
|
+
className: "lpc-catDetailLabel",
|
|
1188
|
+
children: t("runtimeDiagnostics")
|
|
1189
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
1190
|
+
className: "lpc-catJson",
|
|
1191
|
+
children: kit.diagnostics.join("\n")
|
|
1192
|
+
})] }) : null
|
|
1193
|
+
] })
|
|
1194
|
+
] });
|
|
1296
1195
|
}
|
|
1297
1196
|
//#endregion
|
|
1298
|
-
//#region src/client/
|
|
1197
|
+
//#region src/client/route-fit.ts
|
|
1299
1198
|
/**
|
|
1300
|
-
*
|
|
1301
|
-
*
|
|
1302
|
-
*
|
|
1303
|
-
*
|
|
1199
|
+
* 解析条目指向的继承引用(与服务端 inherit.ts 同规则):
|
|
1200
|
+
* `"provider/model"` 显式引用;裸 `"model"` 随 route 级 extends 源;
|
|
1201
|
+
* 条目无 extends 时按同名模型挂在 route 级 extends 源下。
|
|
1202
|
+
* 两段任一为空(无引用可解析)时返回空串。
|
|
1304
1203
|
*/
|
|
1305
|
-
function
|
|
1306
|
-
|
|
1307
|
-
if (
|
|
1308
|
-
|
|
1204
|
+
function entryReference(entry, routeExtends) {
|
|
1205
|
+
const raw = entry.extends.trim();
|
|
1206
|
+
if (raw === "") return {
|
|
1207
|
+
provider: routeExtends.trim(),
|
|
1208
|
+
model: entry.id.trim()
|
|
1209
|
+
};
|
|
1210
|
+
const slash = raw.indexOf("/");
|
|
1211
|
+
if (slash > 0) return {
|
|
1212
|
+
provider: raw.slice(0, slash),
|
|
1213
|
+
model: raw.slice(slash + 1)
|
|
1214
|
+
};
|
|
1215
|
+
return {
|
|
1216
|
+
provider: routeExtends.trim(),
|
|
1217
|
+
model: raw
|
|
1218
|
+
};
|
|
1309
1219
|
}
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
if (alive) setCandidateModels(result.models);
|
|
1332
|
-
} catch {
|
|
1333
|
-
if (alive) setNote(t("catalogFailed"));
|
|
1334
|
-
}
|
|
1335
|
-
})();
|
|
1336
|
-
return () => {
|
|
1337
|
-
alive = false;
|
|
1338
|
-
};
|
|
1339
|
-
}, [source]);
|
|
1340
|
-
const onProviderChange = (value) => {
|
|
1341
|
-
setProvider(value);
|
|
1342
|
-
if (value === "") {
|
|
1343
|
-
setCandidateModels([]);
|
|
1344
|
-
return;
|
|
1220
|
+
/**
|
|
1221
|
+
* 推断 route 当前实际生效的协议:
|
|
1222
|
+
* - route 级 `api` 显式给出即用它(物化时它压过继承值);
|
|
1223
|
+
* - 否则逐模型查**其引用指向的模型**的协议(注意是引用目标,不是条目 id:
|
|
1224
|
+
* 生产配置普遍用别名条目,如 `{id: 'glm-5.3', extends: 'zai/glm-5.3'}`):
|
|
1225
|
+
* 单一 → 该协议;多个 → mixed;引用解析不出来计 unresolved。
|
|
1226
|
+
*/
|
|
1227
|
+
function routeApiFacts(provider, apis) {
|
|
1228
|
+
const explicit = provider.api.trim();
|
|
1229
|
+
if (explicit !== "") return {
|
|
1230
|
+
api: explicit,
|
|
1231
|
+
mixed: false,
|
|
1232
|
+
unresolved: 0
|
|
1233
|
+
};
|
|
1234
|
+
const found = /* @__PURE__ */ new Set();
|
|
1235
|
+
let unresolved = 0;
|
|
1236
|
+
for (const model of provider.models) {
|
|
1237
|
+
const ref = entryReference(model, provider.extends);
|
|
1238
|
+
if (ref.provider === "" || ref.model === "") {
|
|
1239
|
+
unresolved += 1;
|
|
1240
|
+
continue;
|
|
1345
1241
|
}
|
|
1346
|
-
|
|
1242
|
+
const api = apis[ref.provider]?.[ref.model];
|
|
1243
|
+
if (api === void 0) unresolved += 1;
|
|
1244
|
+
else found.add(api);
|
|
1245
|
+
}
|
|
1246
|
+
if (found.size === 1) return {
|
|
1247
|
+
api: [...found][0] ?? "",
|
|
1248
|
+
mixed: false,
|
|
1249
|
+
unresolved
|
|
1250
|
+
};
|
|
1251
|
+
return {
|
|
1252
|
+
mixed: found.size > 1,
|
|
1253
|
+
unresolved
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
/** route 是否由 PiAiAdapter 服务(`adapter` 在卡片的 extra 里原样往返)。 */
|
|
1257
|
+
function isPiRoute(provider) {
|
|
1258
|
+
const adapter = provider.extra["adapter"];
|
|
1259
|
+
return adapter === void 0 || adapter === "pi";
|
|
1260
|
+
}
|
|
1261
|
+
/** 可作为「一键添加」目标的 route(deepseek 路由不适用 pi-ai 继承)。 */
|
|
1262
|
+
function targetRoutes(providers) {
|
|
1263
|
+
return Object.entries(providers).filter(([, provider]) => isPiRoute(provider)).map(([route]) => route).sort((left, right) => left.localeCompare(right));
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* 判定某内置模型能否加进目标 route:
|
|
1267
|
+
* 协议不受支持 → unsupported;route 不存在 → no-route;同 id 已在 → duplicate;
|
|
1268
|
+
* route 已混用多个协议 → route-mixed(保存本来就会失败,先提示修好);
|
|
1269
|
+
* route 已有确定协议且与模型不同 → protocol-conflict(保存一定失败,提前拦)。
|
|
1270
|
+
*/
|
|
1271
|
+
function addEligibility(options) {
|
|
1272
|
+
const { model, provider, apis } = options;
|
|
1273
|
+
if (!model.servable) return {
|
|
1274
|
+
ok: false,
|
|
1275
|
+
reason: "unsupported",
|
|
1276
|
+
detail: model.api
|
|
1277
|
+
};
|
|
1278
|
+
if (provider === void 0) return {
|
|
1279
|
+
ok: false,
|
|
1280
|
+
reason: "no-route"
|
|
1281
|
+
};
|
|
1282
|
+
if (provider.models.some((entry) => entry.id.trim() === model.id)) return {
|
|
1283
|
+
ok: false,
|
|
1284
|
+
reason: "duplicate"
|
|
1285
|
+
};
|
|
1286
|
+
const facts = routeApiFacts(provider, apis);
|
|
1287
|
+
if (facts.mixed) return {
|
|
1288
|
+
ok: false,
|
|
1289
|
+
reason: "route-mixed"
|
|
1347
1290
|
};
|
|
1291
|
+
if (facts.api !== void 0 && facts.api !== model.api) return {
|
|
1292
|
+
ok: false,
|
|
1293
|
+
reason: "protocol-conflict",
|
|
1294
|
+
detail: facts.api
|
|
1295
|
+
};
|
|
1296
|
+
return { ok: true };
|
|
1297
|
+
}
|
|
1298
|
+
//#endregion
|
|
1299
|
+
//#region src/client/views/catalog-row.tsx
|
|
1300
|
+
/**
|
|
1301
|
+
* 目录浏览器的单行:路径 id(可复制)+ 名称 + 协议徽标 + 参数行 + 折叠的参数明细
|
|
1302
|
+
* + 「添加到 route」(不可添加时按钮文案即原因)。与浏览器主体拆开,
|
|
1303
|
+
* 便于各自演进(行内只依赖服务端给的模型事实)。
|
|
1304
|
+
* @module llm-pi/client/views/catalog-row
|
|
1305
|
+
*/
|
|
1306
|
+
/** 计数展示(千分位;不引本地化依赖)。 */
|
|
1307
|
+
function count$1(value) {
|
|
1308
|
+
return value.toLocaleString();
|
|
1309
|
+
}
|
|
1310
|
+
/** 参数行的键值短展示:字符串化,长值截断。 */
|
|
1311
|
+
function shortValue(value) {
|
|
1312
|
+
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
1313
|
+
if (text === void 0) return "undefined";
|
|
1314
|
+
return text.length > 120 ? `${text.slice(0, 117)}…` : text;
|
|
1315
|
+
}
|
|
1316
|
+
/** 禁止添加的原因文案。 */
|
|
1317
|
+
function blockText(t, reason, detail) {
|
|
1318
|
+
if (reason === "unsupported") return `${t("blockUnsupported")}${detail === void 0 ? "" : `(${detail})`}`;
|
|
1319
|
+
if (reason === "duplicate") return t("blockDuplicate");
|
|
1320
|
+
if (reason === "protocol-conflict") return `${t("blockConflict")}${detail === void 0 || detail === "" ? "" : `(${detail})`}`;
|
|
1321
|
+
if (reason === "route-mixed") return t("blockMixed");
|
|
1322
|
+
return t("blockNoRoute");
|
|
1323
|
+
}
|
|
1324
|
+
/** 单行:路径 id(可复制)+ 名称 + 参数行 + 折叠的参数明细 + 添加按钮。 */
|
|
1325
|
+
function CatalogRow(props) {
|
|
1326
|
+
const { t, model } = props;
|
|
1327
|
+
const [details, setDetails] = (0, react.useState)(false);
|
|
1328
|
+
const [copied, setCopied] = (0, react.useState)(false);
|
|
1329
|
+
const timer = (0, react.useRef)(null);
|
|
1330
|
+
(0, react.useEffect)(() => () => {
|
|
1331
|
+
if (timer.current !== null) clearTimeout(timer.current);
|
|
1332
|
+
}, []);
|
|
1333
|
+
const blocked = props.eligibility.ok ? void 0 : props.eligibility;
|
|
1334
|
+
const compatKeys = Object.keys(model.compat ?? {});
|
|
1335
|
+
const restKeys = Object.keys(model.rest);
|
|
1348
1336
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1349
|
-
className: "lpc-
|
|
1337
|
+
className: "lpc-catRow",
|
|
1350
1338
|
children: [
|
|
1351
1339
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1352
|
-
className: "lpc-
|
|
1353
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1354
|
-
className: "lpc-modelTitle",
|
|
1355
|
-
children: t("modelsGroup")
|
|
1356
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1357
|
-
type: "button",
|
|
1358
|
-
className: "lpc-btn lpc-btnGhost lpc-btnSmall",
|
|
1359
|
-
disabled: props.disabled === true,
|
|
1360
|
-
onClick: () => props.onModels([...props.models, emptyModelDraft()]),
|
|
1361
|
-
children: t("addModel")
|
|
1362
|
-
})]
|
|
1363
|
-
}),
|
|
1364
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1365
|
-
className: "lpc-catalogBar",
|
|
1340
|
+
className: "lpc-catHead",
|
|
1366
1341
|
children: [
|
|
1367
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
1368
|
-
className: "lpc-
|
|
1369
|
-
|
|
1370
|
-
children: t("catalogSource")
|
|
1371
|
-
}),
|
|
1372
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1373
|
-
id: `${listId}-source`,
|
|
1374
|
-
className: "lpc-input lpc-select lpc-catalogSelect",
|
|
1375
|
-
value: source,
|
|
1376
|
-
disabled: props.disabled === true,
|
|
1377
|
-
onChange: (event) => setSource(event.target.value),
|
|
1378
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1379
|
-
value: "builtin",
|
|
1380
|
-
children: "builtin"
|
|
1381
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1382
|
-
value: "models-dev",
|
|
1383
|
-
children: "models-dev"
|
|
1384
|
-
})]
|
|
1342
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1343
|
+
className: "lpc-catPath",
|
|
1344
|
+
children: model.path
|
|
1385
1345
|
}),
|
|
1386
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1346
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1347
|
+
type: "button",
|
|
1348
|
+
className: "lpc-iconBtn",
|
|
1349
|
+
title: copied ? t("browserCopied") : t("browserCopy"),
|
|
1350
|
+
"aria-label": t("browserCopy"),
|
|
1351
|
+
onClick: () => {
|
|
1352
|
+
copyText(model.path).then((ok) => {
|
|
1353
|
+
if (!ok) return;
|
|
1354
|
+
setCopied(true);
|
|
1355
|
+
if (timer.current !== null) clearTimeout(timer.current);
|
|
1356
|
+
timer.current = setTimeout(() => setCopied(false), 1500);
|
|
1357
|
+
});
|
|
1358
|
+
},
|
|
1359
|
+
children: copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutlineRegular, { size: 14 })
|
|
1360
|
+
}),
|
|
1361
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1362
|
+
className: "lpc-catName",
|
|
1363
|
+
children: model.name
|
|
1390
1364
|
}),
|
|
1391
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
children: id
|
|
1403
|
-
}, id))]
|
|
1365
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1366
|
+
className: model.servable ? "lpc-badge lpc-badgeSet" : "lpc-badge lpc-badgeUnset",
|
|
1367
|
+
children: model.api
|
|
1368
|
+
}),
|
|
1369
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1370
|
+
type: "button",
|
|
1371
|
+
className: "lpc-btn lpc-btnGhost lpc-btnSmall",
|
|
1372
|
+
disabled: props.disabled || blocked !== void 0,
|
|
1373
|
+
title: blocked === void 0 ? void 0 : blockText(t, blocked.reason, blocked.detail),
|
|
1374
|
+
onClick: props.onAdd,
|
|
1375
|
+
children: blocked === void 0 ? t("browserAdd") : blockText(t, blocked.reason, blocked.detail)
|
|
1404
1376
|
})
|
|
1405
1377
|
]
|
|
1406
1378
|
}),
|
|
1407
|
-
|
|
1379
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1380
|
+
className: "lpc-catParams",
|
|
1381
|
+
children: [
|
|
1382
|
+
`${t("browserContextShort")} ${count$1(model.contextWindow)} · ${t("browserMaxShort")} ${count$1(model.maxTokens)} · `,
|
|
1383
|
+
model.input.map((m) => m === "image" ? t("browserInputImage") : t("browserInputText")).join("/"),
|
|
1384
|
+
model.reasoning ? ` · ${t("browserReasoning")}` : "",
|
|
1385
|
+
compatKeys.length > 0 ? ` · compat ${compatKeys.length}` : ""
|
|
1386
|
+
]
|
|
1387
|
+
}),
|
|
1388
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1389
|
+
type: "button",
|
|
1390
|
+
className: "lpc-catDetailsToggle",
|
|
1391
|
+
"aria-expanded": details,
|
|
1392
|
+
onClick: () => setDetails(!details),
|
|
1393
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `lpc-chevron${details ? " lpc-chevronOpen" : ""}` }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("browserDetails") })]
|
|
1394
|
+
}),
|
|
1395
|
+
details ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1396
|
+
className: "lpc-catDetails",
|
|
1397
|
+
children: [
|
|
1398
|
+
model.thinkingLevelMap !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1399
|
+
className: "lpc-catParams",
|
|
1400
|
+
children: `${t("browserThinkMap")}:${Object.entries(model.thinkingLevelMap).map(([level, wire]) => `${level}=${wire ?? "-"}`).join(" ")}`
|
|
1401
|
+
}) : null,
|
|
1402
|
+
compatKeys.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1403
|
+
className: "lpc-catDetailLabel",
|
|
1404
|
+
children: t("browserCompat")
|
|
1405
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
1406
|
+
className: "lpc-catJson",
|
|
1407
|
+
children: JSON.stringify(model.compat, null, 2)
|
|
1408
|
+
})] }) : null,
|
|
1409
|
+
restKeys.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1410
|
+
className: "lpc-catDetailLabel",
|
|
1411
|
+
children: t("browserExtra")
|
|
1412
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
1413
|
+
className: "lpc-catJson",
|
|
1414
|
+
children: restKeys.map((key) => `${key}: ${shortValue(model.rest[key])}`).join("\n")
|
|
1415
|
+
})] }) : null,
|
|
1416
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1417
|
+
className: "lpc-catParams",
|
|
1418
|
+
children: `baseUrl:${model.baseUrl}`
|
|
1419
|
+
})
|
|
1420
|
+
]
|
|
1421
|
+
}) : null
|
|
1422
|
+
]
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
//#endregion
|
|
1426
|
+
//#region src/client/views/catalog-browser.tsx
|
|
1427
|
+
/**
|
|
1428
|
+
* 内置模型目录浏览器:搜索(服务端模糊匹配)/筛选/分页 + 复制路径 id +
|
|
1429
|
+
* 一键把 `{id, extends: 'provider/model'}` 加到目标 route。
|
|
1430
|
+
*
|
|
1431
|
+
* 数据全部来自本插件的 `/catalog` 端点(浏览器半读不到 pi-ai 包):
|
|
1432
|
+
* - `apis`(provider → modelId → api)用于推断目标 route 的生效协议,
|
|
1433
|
+
* 在添加前拦下"协议不一致/重复 id/协议不受支持"这三类必然失败的添加;
|
|
1434
|
+
* - 模型行的参数直接渲染服务端给的事实(含官方新增字段,见 `rest`)。
|
|
1435
|
+
* 折叠由外层(card.tsx)控制,故本组件只管内容与交互。
|
|
1436
|
+
*
|
|
1437
|
+
* 结构:`useCatalogSearch`(拉取/分页状态)+ `BrowserToolbar`(搜索与筛选条)
|
|
1438
|
+
* + `CatalogRow`(明细行),`CatalogBrowser` 只做编排。
|
|
1439
|
+
* @module llm-pi/client/views/catalog-browser
|
|
1440
|
+
*/
|
|
1441
|
+
/** 计数展示(千分位;不引本地化依赖)。 */
|
|
1442
|
+
function count(value) {
|
|
1443
|
+
return value.toLocaleString();
|
|
1444
|
+
}
|
|
1445
|
+
/** 一页条数(服务端同样有上限;"加载更多"按此翻页)。 */
|
|
1446
|
+
const PAGE_SIZE = 30;
|
|
1447
|
+
/** 搜索去抖毫秒(本地端点,短去抖即可)。 */
|
|
1448
|
+
const DEBOUNCE_MS = 180;
|
|
1449
|
+
const EMPTY_STATE = {
|
|
1450
|
+
items: [],
|
|
1451
|
+
total: 0,
|
|
1452
|
+
servableHidden: 0,
|
|
1453
|
+
loading: false,
|
|
1454
|
+
error: ""
|
|
1455
|
+
};
|
|
1456
|
+
/**
|
|
1457
|
+
* 目录搜索状态机:条件变化重新拉第一页(旧请求按 requestId 丢弃,避免竞态覆盖),
|
|
1458
|
+
* `loadMore` 追加下一页。
|
|
1459
|
+
*/
|
|
1460
|
+
function useCatalogSearch(filters, enabled) {
|
|
1461
|
+
const [state, setState] = (0, react.useState)(EMPTY_STATE);
|
|
1462
|
+
const requestId = (0, react.useRef)(0);
|
|
1463
|
+
(0, react.useEffect)(() => {
|
|
1464
|
+
if (!enabled) return;
|
|
1465
|
+
const id = requestId.current + 1;
|
|
1466
|
+
requestId.current = id;
|
|
1467
|
+
setState((prev) => ({
|
|
1468
|
+
...prev,
|
|
1469
|
+
loading: true,
|
|
1470
|
+
error: ""
|
|
1471
|
+
}));
|
|
1472
|
+
const controller = new AbortController();
|
|
1473
|
+
searchCatalog({
|
|
1474
|
+
...filters,
|
|
1475
|
+
offset: 0,
|
|
1476
|
+
limit: PAGE_SIZE
|
|
1477
|
+
}, controller.signal).then((page) => {
|
|
1478
|
+
if (requestId.current !== id) return;
|
|
1479
|
+
setState({
|
|
1480
|
+
items: page.items,
|
|
1481
|
+
total: page.total,
|
|
1482
|
+
servableHidden: page.servableHidden,
|
|
1483
|
+
loading: false,
|
|
1484
|
+
error: ""
|
|
1485
|
+
});
|
|
1486
|
+
}).catch((error) => {
|
|
1487
|
+
if (requestId.current !== id) return;
|
|
1488
|
+
setState((prev) => ({
|
|
1489
|
+
...prev,
|
|
1490
|
+
loading: false,
|
|
1491
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1492
|
+
}));
|
|
1493
|
+
});
|
|
1494
|
+
return () => controller.abort();
|
|
1495
|
+
}, [filters, enabled]);
|
|
1496
|
+
const loadMore = () => {
|
|
1497
|
+
const id = requestId.current + 1;
|
|
1498
|
+
requestId.current = id;
|
|
1499
|
+
setState((prev) => ({
|
|
1500
|
+
...prev,
|
|
1501
|
+
loading: true
|
|
1502
|
+
}));
|
|
1503
|
+
searchCatalog({
|
|
1504
|
+
...filters,
|
|
1505
|
+
offset: state.items.length,
|
|
1506
|
+
limit: PAGE_SIZE
|
|
1507
|
+
}).then((page) => {
|
|
1508
|
+
if (requestId.current !== id) return;
|
|
1509
|
+
setState((prev) => ({
|
|
1510
|
+
...prev,
|
|
1511
|
+
items: [...prev.items, ...page.items],
|
|
1512
|
+
total: page.total,
|
|
1513
|
+
servableHidden: page.servableHidden,
|
|
1514
|
+
loading: false
|
|
1515
|
+
}));
|
|
1516
|
+
}).catch((error) => {
|
|
1517
|
+
if (requestId.current !== id) return;
|
|
1518
|
+
setState((prev) => ({
|
|
1519
|
+
...prev,
|
|
1520
|
+
loading: false,
|
|
1521
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1522
|
+
}));
|
|
1523
|
+
});
|
|
1524
|
+
};
|
|
1525
|
+
return {
|
|
1526
|
+
...state,
|
|
1527
|
+
loadMore
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
/** 搜索框输入(本地态)→ 去抖后的查询串。 */
|
|
1531
|
+
function useDebouncedQuery(query) {
|
|
1532
|
+
const [debounced, setDebounced] = (0, react.useState)("");
|
|
1533
|
+
(0, react.useEffect)(() => {
|
|
1534
|
+
const timer = setTimeout(() => setDebounced(query), DEBOUNCE_MS);
|
|
1535
|
+
return () => clearTimeout(timer);
|
|
1536
|
+
}, [query]);
|
|
1537
|
+
return debounced;
|
|
1538
|
+
}
|
|
1539
|
+
/** 搜索/筛选/目标 route 三条工具条。 */
|
|
1540
|
+
function BrowserToolbar(props) {
|
|
1541
|
+
const { t } = props;
|
|
1542
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1543
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1544
|
+
className: "lpc-catBar",
|
|
1545
|
+
children: [
|
|
1546
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1547
|
+
className: "lpc-input lpc-catSearch",
|
|
1548
|
+
type: "search",
|
|
1549
|
+
value: props.query,
|
|
1550
|
+
placeholder: t("browserSearchPlaceholder"),
|
|
1551
|
+
"aria-label": t("browserSearch"),
|
|
1552
|
+
disabled: props.disabled,
|
|
1553
|
+
onChange: (event) => props.onQuery(event.target.value)
|
|
1554
|
+
}),
|
|
1555
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1556
|
+
className: "lpc-input lpc-select lpc-catSelect",
|
|
1557
|
+
value: props.provider,
|
|
1558
|
+
"aria-label": t("browserProvider"),
|
|
1559
|
+
disabled: props.disabled,
|
|
1560
|
+
onChange: (event) => props.onProvider(event.target.value),
|
|
1561
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1562
|
+
value: "",
|
|
1563
|
+
children: `${t("browserProvider")}:${t("browserAll")}`
|
|
1564
|
+
}), props.providers.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1565
|
+
value: entry.id,
|
|
1566
|
+
children: `${entry.id}(${entry.modelCount})`
|
|
1567
|
+
}, entry.id))]
|
|
1568
|
+
}),
|
|
1569
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1570
|
+
className: "lpc-input lpc-select lpc-catSelect",
|
|
1571
|
+
value: props.api,
|
|
1572
|
+
"aria-label": t("browserApi"),
|
|
1573
|
+
disabled: props.disabled,
|
|
1574
|
+
onChange: (event) => props.onApi(event.target.value),
|
|
1575
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1576
|
+
value: "",
|
|
1577
|
+
children: `${t("browserApi")}:${t("browserAll")}`
|
|
1578
|
+
}), protocolOptions().map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1579
|
+
value: id,
|
|
1580
|
+
children: id
|
|
1581
|
+
}, id))]
|
|
1582
|
+
})
|
|
1583
|
+
]
|
|
1584
|
+
}),
|
|
1585
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1586
|
+
className: "lpc-catBar",
|
|
1587
|
+
children: [
|
|
1588
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
|
|
1589
|
+
prefix: "lpc",
|
|
1590
|
+
id: "lpc-cat-servable",
|
|
1591
|
+
label: t("browserOnlyServable"),
|
|
1592
|
+
checked: props.servableOnly,
|
|
1593
|
+
disabled: props.disabled,
|
|
1594
|
+
onEdit: props.onServableOnly
|
|
1595
|
+
}),
|
|
1596
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
|
|
1597
|
+
prefix: "lpc",
|
|
1598
|
+
id: "lpc-cat-reasoning",
|
|
1599
|
+
label: t("browserReasoning"),
|
|
1600
|
+
checked: props.reasoning,
|
|
1601
|
+
disabled: props.disabled,
|
|
1602
|
+
onEdit: props.onReasoning
|
|
1603
|
+
}),
|
|
1604
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
|
|
1605
|
+
prefix: "lpc",
|
|
1606
|
+
id: "lpc-cat-image",
|
|
1607
|
+
label: t("browserImage"),
|
|
1608
|
+
checked: props.image,
|
|
1609
|
+
disabled: props.disabled,
|
|
1610
|
+
onEdit: props.onImage
|
|
1611
|
+
})
|
|
1612
|
+
]
|
|
1613
|
+
}),
|
|
1614
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1615
|
+
className: "lpc-catBar",
|
|
1616
|
+
children: [
|
|
1617
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1618
|
+
className: "lpc-catLabel",
|
|
1619
|
+
htmlFor: "lpc-cat-target",
|
|
1620
|
+
children: t("browserTarget")
|
|
1621
|
+
}),
|
|
1622
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1623
|
+
id: "lpc-cat-target",
|
|
1624
|
+
className: "lpc-input lpc-select lpc-catSelect",
|
|
1625
|
+
value: props.target,
|
|
1626
|
+
disabled: props.disabled || props.targets.length === 0,
|
|
1627
|
+
onChange: (event) => props.onTarget(event.target.value),
|
|
1628
|
+
children: [props.targets.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1629
|
+
value: "",
|
|
1630
|
+
children: "-"
|
|
1631
|
+
}) : null, props.targets.map((route) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1632
|
+
value: route,
|
|
1633
|
+
children: route
|
|
1634
|
+
}, route))]
|
|
1635
|
+
}),
|
|
1636
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1637
|
+
className: "lpc-catNote",
|
|
1638
|
+
children: props.targetApi === void 0 ? t("browserNoProtocol") : `${t("browserTargetProtocol")}:${props.targetApi}`
|
|
1639
|
+
})
|
|
1640
|
+
]
|
|
1641
|
+
})
|
|
1642
|
+
] });
|
|
1643
|
+
}
|
|
1644
|
+
function CatalogBrowser(props) {
|
|
1645
|
+
const { t } = props;
|
|
1646
|
+
const [query, setQuery] = (0, react.useState)("");
|
|
1647
|
+
const debounced = useDebouncedQuery(query);
|
|
1648
|
+
const [provider, setProvider] = (0, react.useState)("");
|
|
1649
|
+
const [api, setApi] = (0, react.useState)("");
|
|
1650
|
+
const [reasoning, setReasoning] = (0, react.useState)(false);
|
|
1651
|
+
const [image, setImage] = (0, react.useState)(false);
|
|
1652
|
+
const [servableOnly, setServableOnly] = (0, react.useState)(true);
|
|
1653
|
+
const [note, setNote] = (0, react.useState)("");
|
|
1654
|
+
const { items, total, servableHidden, loading, error, loadMore } = useCatalogSearch((0, react.useMemo)(() => ({
|
|
1655
|
+
q: debounced,
|
|
1656
|
+
provider,
|
|
1657
|
+
api,
|
|
1658
|
+
reasoning,
|
|
1659
|
+
image,
|
|
1660
|
+
servableOnly
|
|
1661
|
+
}), [
|
|
1662
|
+
debounced,
|
|
1663
|
+
provider,
|
|
1664
|
+
api,
|
|
1665
|
+
reasoning,
|
|
1666
|
+
image,
|
|
1667
|
+
servableOnly
|
|
1668
|
+
]), props.metaError === void 0);
|
|
1669
|
+
if (props.metaError !== void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1670
|
+
className: "lpc-invalid",
|
|
1671
|
+
children: `${t("browserFailed")}${props.metaError}`
|
|
1672
|
+
});
|
|
1673
|
+
const onAdd = (model) => {
|
|
1674
|
+
if (props.target === "") return;
|
|
1675
|
+
const added = props.onAdd(props.target, model);
|
|
1676
|
+
setNote(added ? `${t("browserAddedTo")} ${props.target}:${model.path}` : `${t("blockDuplicate")}:${model.path}`);
|
|
1677
|
+
};
|
|
1678
|
+
const disabled = props.disabled === true;
|
|
1679
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1680
|
+
className: "lpc-cat",
|
|
1681
|
+
children: [
|
|
1682
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1408
1683
|
className: "lpc-hint",
|
|
1409
|
-
children:
|
|
1684
|
+
children: t("browserHint")
|
|
1685
|
+
}),
|
|
1686
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BrowserToolbar, {
|
|
1687
|
+
t,
|
|
1688
|
+
query,
|
|
1689
|
+
onQuery: setQuery,
|
|
1690
|
+
provider,
|
|
1691
|
+
onProvider: setProvider,
|
|
1692
|
+
api,
|
|
1693
|
+
onApi: setApi,
|
|
1694
|
+
reasoning,
|
|
1695
|
+
onReasoning: setReasoning,
|
|
1696
|
+
image,
|
|
1697
|
+
onImage: setImage,
|
|
1698
|
+
servableOnly,
|
|
1699
|
+
onServableOnly: setServableOnly,
|
|
1700
|
+
providers: props.providers,
|
|
1701
|
+
targets: props.targets,
|
|
1702
|
+
target: props.target,
|
|
1703
|
+
onTarget: props.onTargetChange,
|
|
1704
|
+
targetApi: props.target === "" ? void 0 : props.routeApiOf(props.target),
|
|
1705
|
+
disabled
|
|
1706
|
+
}),
|
|
1707
|
+
props.targets.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1708
|
+
className: "lpc-hint",
|
|
1709
|
+
children: t("browserNoTarget")
|
|
1710
|
+
}) : null,
|
|
1711
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1712
|
+
className: "lpc-catSummary",
|
|
1713
|
+
children: [
|
|
1714
|
+
`${t("browserMatched")} ${count(total)}`,
|
|
1715
|
+
servableHidden > 0 ? ` · ${t("browserHiddenServable")} ${count(servableHidden)} ${t("browserHiddenUnit")}` : "",
|
|
1716
|
+
loading ? ` · ${t("browserLoading")}` : ""
|
|
1717
|
+
]
|
|
1718
|
+
}),
|
|
1719
|
+
error !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1720
|
+
className: "lpc-invalid",
|
|
1721
|
+
children: `${t("browserFailed")}${error}`
|
|
1410
1722
|
}) : null,
|
|
1411
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
1412
|
-
|
|
1413
|
-
children:
|
|
1723
|
+
!loading && error === "" && items.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1724
|
+
className: "lpc-hint",
|
|
1725
|
+
children: t("browserEmpty")
|
|
1726
|
+
}) : null,
|
|
1727
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1728
|
+
className: "lpc-catList",
|
|
1729
|
+
children: items.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CatalogRow, {
|
|
1730
|
+
t,
|
|
1731
|
+
model,
|
|
1732
|
+
disabled,
|
|
1733
|
+
eligibility: addEligibility({
|
|
1734
|
+
model,
|
|
1735
|
+
provider: props.providerOf(props.target),
|
|
1736
|
+
apis: props.apis
|
|
1737
|
+
}),
|
|
1738
|
+
onAdd: () => onAdd(model)
|
|
1739
|
+
}, model.path))
|
|
1740
|
+
}),
|
|
1741
|
+
items.length > 0 && items.length < total ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1742
|
+
className: "lpc-catMore",
|
|
1743
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1744
|
+
type: "button",
|
|
1745
|
+
className: "lpc-btn lpc-btnGhost lpc-btnSmall",
|
|
1746
|
+
disabled: loading,
|
|
1747
|
+
onClick: loadMore,
|
|
1748
|
+
children: t("browserLoadMore")
|
|
1749
|
+
})
|
|
1750
|
+
}) : null,
|
|
1751
|
+
note !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1752
|
+
className: "lpc-catNote",
|
|
1753
|
+
children: note
|
|
1754
|
+
}) : null
|
|
1755
|
+
]
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
//#endregion
|
|
1759
|
+
//#region src/client/views/compat.tsx
|
|
1760
|
+
/** api 变更后裁剪 compat:只保留新渲染组的字段,避免保存时被后端拒绝。 */
|
|
1761
|
+
function pruneCompatForApi(compat, api) {
|
|
1762
|
+
const group = api !== "" && compatFieldsOf(api).length > 0 ? api : COMPAT_FALLBACK_API;
|
|
1763
|
+
const fields = new Set(compatFieldsOf(group));
|
|
1764
|
+
const next = {};
|
|
1765
|
+
for (const [key, value] of Object.entries(compat)) if (fields.has(key)) next[key] = value;
|
|
1766
|
+
return next;
|
|
1767
|
+
}
|
|
1768
|
+
/** 字段数超过该值时提供过滤框(19 项一屏放不下)。 */
|
|
1769
|
+
const FILTER_THRESHOLD = 10;
|
|
1770
|
+
function CompatEditor(props) {
|
|
1771
|
+
const [filter, setFilter] = (0, react.useState)("");
|
|
1772
|
+
const effective = props.api !== "" && compatFieldsOf(props.api).length > 0 ? props.api : COMPAT_FALLBACK_API;
|
|
1773
|
+
const allFields = compatFieldsOf(effective);
|
|
1774
|
+
const needle = filter.trim().toLowerCase();
|
|
1775
|
+
const fields = needle === "" ? allFields : allFields.filter((f) => f.toLowerCase().includes(needle));
|
|
1776
|
+
const setCount = allFields.filter((field) => props.compat[field] !== void 0).length;
|
|
1777
|
+
const setField = (field, value) => {
|
|
1778
|
+
const next = { ...props.compat };
|
|
1779
|
+
if (value === void 0) delete next[field];
|
|
1780
|
+
else next[field] = value;
|
|
1781
|
+
props.onEdit(next);
|
|
1782
|
+
};
|
|
1783
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1784
|
+
className: "lpc-field lpc-wide",
|
|
1785
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapseSection, {
|
|
1786
|
+
id: `${props.idPrefix}-collapse`,
|
|
1787
|
+
title: props.t("compatGroup"),
|
|
1788
|
+
meta: allFields.length === 0 ? "" : `${props.t("compatSetCount")} ${setCount}/${allFields.length}`,
|
|
1789
|
+
defaultOpen: false,
|
|
1790
|
+
children: [
|
|
1791
|
+
props.api === "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1792
|
+
className: "lpc-hint",
|
|
1793
|
+
children: props.t("compatApiHint")
|
|
1794
|
+
}) : null,
|
|
1795
|
+
allFields.length > FILTER_THRESHOLD ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1796
|
+
className: "lpc-catBar",
|
|
1797
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1798
|
+
className: "lpc-input lpc-catSearch",
|
|
1799
|
+
type: "search",
|
|
1800
|
+
value: filter,
|
|
1801
|
+
placeholder: props.t("compatFilter"),
|
|
1802
|
+
"aria-label": props.t("compatFilter"),
|
|
1803
|
+
disabled: props.disabled === true,
|
|
1804
|
+
onChange: (event) => setFilter(event.target.value)
|
|
1805
|
+
})
|
|
1806
|
+
}) : null,
|
|
1807
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1808
|
+
className: "lpc-grid",
|
|
1809
|
+
children: fields.map((field) => {
|
|
1810
|
+
const spec = compatFieldSpec(effective, field);
|
|
1811
|
+
if (spec === void 0) return null;
|
|
1812
|
+
if (spec === "boolean") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectField, {
|
|
1813
|
+
id: `${props.idPrefix}-${field}`,
|
|
1814
|
+
label: field,
|
|
1815
|
+
value: props.compat[field] === void 0 ? "" : String(props.compat[field]),
|
|
1816
|
+
options: ["true", "false"],
|
|
1817
|
+
unsetLabel: props.t("compatUnset"),
|
|
1818
|
+
disabled: props.disabled === true,
|
|
1819
|
+
onEdit: (value) => {
|
|
1820
|
+
if (value === "") setField(field, void 0);
|
|
1821
|
+
else setField(field, value === "true");
|
|
1822
|
+
}
|
|
1823
|
+
}, field);
|
|
1824
|
+
if (spec === "object") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(JsonField, {
|
|
1825
|
+
id: `${props.idPrefix}-${field}`,
|
|
1826
|
+
label: field,
|
|
1827
|
+
value: props.compat[field],
|
|
1828
|
+
epoch: props.epoch,
|
|
1829
|
+
disabled: props.disabled === true,
|
|
1830
|
+
invalidText: props.t("invalidJson"),
|
|
1831
|
+
onEdit: (value) => setField(field, value)
|
|
1832
|
+
}, field);
|
|
1833
|
+
if (spec === "integer" || spec === "number") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IntegerField, {
|
|
1834
|
+
id: `${props.idPrefix}-${field}`,
|
|
1835
|
+
label: field,
|
|
1836
|
+
value: props.compat[field],
|
|
1837
|
+
epoch: props.epoch,
|
|
1838
|
+
invalidLabel: props.t("invalidInteger"),
|
|
1839
|
+
disabled: props.disabled === true,
|
|
1840
|
+
onEdit: (value) => setField(field, value)
|
|
1841
|
+
}, field);
|
|
1842
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectField, {
|
|
1843
|
+
id: `${props.idPrefix}-${field}`,
|
|
1844
|
+
label: field,
|
|
1845
|
+
value: props.compat[field] === void 0 ? "" : String(props.compat[field]),
|
|
1846
|
+
options: spec,
|
|
1847
|
+
unsetLabel: props.t("compatUnset"),
|
|
1848
|
+
disabled: props.disabled === true,
|
|
1849
|
+
onEdit: (value) => {
|
|
1850
|
+
if (value === "") setField(field, void 0);
|
|
1851
|
+
else setField(field, value);
|
|
1852
|
+
}
|
|
1853
|
+
}, field);
|
|
1854
|
+
})
|
|
1855
|
+
})
|
|
1856
|
+
]
|
|
1857
|
+
})
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
//#endregion
|
|
1861
|
+
//#region src/client/views/models.tsx
|
|
1862
|
+
/**
|
|
1863
|
+
* 模型目录编辑:每行一个模型(id/extends/name/容量/模态/reasoningEfforts/compat/
|
|
1864
|
+
* 其余字段),**行默认折叠**——route 内模型一多,展开态会把配置页拉得极长。
|
|
1865
|
+
* 工具条提供"全部展开/收起"与 id/继承源过滤;继承源候选不再内联 datalist,
|
|
1866
|
+
* 改由卡片顶部「内置模型目录」浏览器承担(搜索 + 一键添加)。
|
|
1867
|
+
* @module llm-pi/client/views/models
|
|
1868
|
+
*/
|
|
1869
|
+
/** 行标题摘要:继承源与显示名(空则回退 id)。 */
|
|
1870
|
+
function rowSummary(model) {
|
|
1871
|
+
const parts = [];
|
|
1872
|
+
if (model.extends.trim() !== "") parts.push(`extends ${model.extends.trim()}`);
|
|
1873
|
+
if (model.name.trim() !== "" && model.name.trim() !== model.id.trim()) parts.push(model.name.trim());
|
|
1874
|
+
return parts.join(" · ");
|
|
1875
|
+
}
|
|
1876
|
+
function ModelsTable(props) {
|
|
1877
|
+
const { t } = props;
|
|
1878
|
+
const [openRows, setOpenRows] = (0, react.useState)({});
|
|
1879
|
+
const [filter, setFilter] = (0, react.useState)("");
|
|
1880
|
+
const needle = filter.trim().toLowerCase();
|
|
1881
|
+
const visible = props.models.map((model, index) => ({
|
|
1882
|
+
model,
|
|
1883
|
+
index
|
|
1884
|
+
})).filter(({ model }) => needle === "" ? true : model.id.toLowerCase().includes(needle) || model.extends.toLowerCase().includes(needle) || model.name.toLowerCase().includes(needle));
|
|
1885
|
+
const hidden = props.models.length - visible.length;
|
|
1886
|
+
const allOpen = props.models.length > 0 && props.models.every((_, index) => openRows[index] === true);
|
|
1887
|
+
const toggleAll = () => {
|
|
1888
|
+
if (allOpen) {
|
|
1889
|
+
setOpenRows({});
|
|
1890
|
+
return;
|
|
1891
|
+
}
|
|
1892
|
+
setOpenRows(Object.fromEntries(props.models.map((_, index) => [index, true])));
|
|
1893
|
+
};
|
|
1894
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1895
|
+
className: "lpc-models",
|
|
1896
|
+
children: [
|
|
1897
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1898
|
+
className: "lpc-modelHead",
|
|
1899
|
+
children: [
|
|
1900
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1901
|
+
className: "lpc-modelTitle",
|
|
1902
|
+
children: `${t("modelsGroup")}(${props.models.length})`
|
|
1903
|
+
}),
|
|
1904
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1905
|
+
type: "button",
|
|
1906
|
+
className: "lpc-btn lpc-btnGhost lpc-btnSmall",
|
|
1907
|
+
disabled: props.disabled === true || props.models.length === 0,
|
|
1908
|
+
onClick: toggleAll,
|
|
1909
|
+
children: allOpen ? t("modelsCollapseAll") : t("modelsExpandAll")
|
|
1910
|
+
}),
|
|
1911
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1912
|
+
type: "button",
|
|
1913
|
+
className: "lpc-btn lpc-btnGhost lpc-btnSmall",
|
|
1914
|
+
disabled: props.disabled === true,
|
|
1915
|
+
onClick: props.onAddFromCatalog,
|
|
1916
|
+
children: t("addFromCatalog")
|
|
1917
|
+
}),
|
|
1918
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1919
|
+
type: "button",
|
|
1920
|
+
className: "lpc-btn lpc-btnGhost lpc-btnSmall",
|
|
1921
|
+
disabled: props.disabled === true,
|
|
1922
|
+
onClick: () => props.onModels([...props.models, emptyModelDraft()]),
|
|
1923
|
+
children: t("addModel")
|
|
1924
|
+
})
|
|
1925
|
+
]
|
|
1414
1926
|
}),
|
|
1415
|
-
props.models.
|
|
1927
|
+
props.models.length > 6 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1928
|
+
className: "lpc-catBar",
|
|
1929
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1930
|
+
className: "lpc-input lpc-catSearch",
|
|
1931
|
+
type: "search",
|
|
1932
|
+
value: filter,
|
|
1933
|
+
placeholder: t("modelsFilterPlaceholder"),
|
|
1934
|
+
"aria-label": t("modelsFilter"),
|
|
1935
|
+
disabled: props.disabled === true,
|
|
1936
|
+
onChange: (event) => setFilter(event.target.value)
|
|
1937
|
+
}), hidden > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1938
|
+
className: "lpc-catNote",
|
|
1939
|
+
children: `${t("modelsHidden")} ${hidden}`
|
|
1940
|
+
}) : null]
|
|
1941
|
+
}) : null,
|
|
1942
|
+
visible.map(({ model, index }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelRow, {
|
|
1416
1943
|
index,
|
|
1417
1944
|
model,
|
|
1418
1945
|
api: props.api,
|
|
1419
|
-
|
|
1946
|
+
uid: props.route.replace(/[^a-zA-Z0-9_-]/g, "_"),
|
|
1947
|
+
open: openRows[index] === true,
|
|
1420
1948
|
epoch: props.epoch,
|
|
1421
1949
|
disabled: props.disabled === true,
|
|
1422
1950
|
t,
|
|
1951
|
+
onToggle: () => setOpenRows((prev) => ({
|
|
1952
|
+
...prev,
|
|
1953
|
+
[index]: prev[index] !== true
|
|
1954
|
+
})),
|
|
1423
1955
|
onPatch: (patch) => props.onModels(props.models.map((m, i) => i === index ? {
|
|
1424
1956
|
...m,
|
|
1425
1957
|
...patch
|
|
@@ -1429,125 +1961,141 @@ ${extra}
|
|
|
1429
1961
|
]
|
|
1430
1962
|
});
|
|
1431
1963
|
}
|
|
1964
|
+
/** 单行:折叠头(序号/id/摘要) + 展开体(全部字段)。 */
|
|
1432
1965
|
function ModelRow(props) {
|
|
1433
1966
|
const { model, t } = props;
|
|
1434
|
-
const id =
|
|
1967
|
+
const id = `lpc-m-${props.uid}-${props.index}`;
|
|
1968
|
+
const summary = rowSummary(model);
|
|
1435
1969
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1436
1970
|
className: "lpc-modelRow",
|
|
1437
|
-
children: [
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
" ",
|
|
1445
|
-
props.index + 1
|
|
1446
|
-
]
|
|
1447
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1448
|
-
type: "button",
|
|
1449
|
-
className: "lpc-btn lpc-btnGhost lpc-btnSmall",
|
|
1450
|
-
disabled: props.disabled === true,
|
|
1451
|
-
onClick: props.onRemove,
|
|
1452
|
-
children: t("deleteModel")
|
|
1453
|
-
})]
|
|
1454
|
-
}),
|
|
1455
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1456
|
-
className: "lpc-grid",
|
|
1971
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1972
|
+
className: "lpc-modelHead",
|
|
1973
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1974
|
+
type: "button",
|
|
1975
|
+
className: "lpc-routeToggle",
|
|
1976
|
+
"aria-expanded": props.open,
|
|
1977
|
+
onClick: props.onToggle,
|
|
1457
1978
|
children: [
|
|
1458
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
value: model.id,
|
|
1463
|
-
disabled: props.disabled === true,
|
|
1464
|
-
invalid: model.id.trim() === "",
|
|
1465
|
-
invalidLabel: t("modelIdRequired"),
|
|
1466
|
-
onEdit: (value) => props.onPatch({ id: value })
|
|
1467
|
-
}),
|
|
1468
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
1469
|
-
id: `${id}-extends`,
|
|
1470
|
-
label: t("modelExtends"),
|
|
1471
|
-
hint: t("modelExtendsHint"),
|
|
1472
|
-
value: model.extends,
|
|
1473
|
-
list: props.listId,
|
|
1474
|
-
disabled: props.disabled === true,
|
|
1475
|
-
onEdit: (value) => props.onPatch({ extends: value })
|
|
1476
|
-
}),
|
|
1477
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
1478
|
-
id: `${id}-name`,
|
|
1479
|
-
label: t("modelName"),
|
|
1480
|
-
value: model.name,
|
|
1481
|
-
disabled: props.disabled === true,
|
|
1482
|
-
onEdit: (value) => props.onPatch({ name: value })
|
|
1483
|
-
}),
|
|
1484
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
1485
|
-
id: `${id}-ctx`,
|
|
1486
|
-
label: t("contextWindow"),
|
|
1487
|
-
numeric: true,
|
|
1488
|
-
value: model.contextWindow,
|
|
1489
|
-
disabled: props.disabled === true,
|
|
1490
|
-
onEdit: (value) => props.onPatch({ contextWindow: value })
|
|
1979
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `lpc-chevron${props.open ? " lpc-chevronOpen" : ""}` }),
|
|
1980
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1981
|
+
className: "lpc-modelTitle",
|
|
1982
|
+
children: `#${props.index + 1}`
|
|
1491
1983
|
}),
|
|
1492
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
numeric: true,
|
|
1496
|
-
value: model.maxTokens,
|
|
1497
|
-
disabled: props.disabled === true,
|
|
1498
|
-
onEdit: (value) => props.onPatch({ maxTokens: value })
|
|
1984
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1985
|
+
className: model.id.trim() === "" ? "lpc-invalid" : "lpc-routeKey",
|
|
1986
|
+
children: model.id.trim() === "" ? t("modelUntitled") : model.id
|
|
1499
1987
|
}),
|
|
1500
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1501
|
-
className: "lpc-
|
|
1502
|
-
children:
|
|
1503
|
-
|
|
1504
|
-
children: t("input")
|
|
1505
|
-
}), MODALITIES.map((modality) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
|
|
1506
|
-
prefix: "lpc",
|
|
1507
|
-
id: `${id}-input-${modality}`,
|
|
1508
|
-
label: modality,
|
|
1509
|
-
checked: model.input[modality],
|
|
1510
|
-
disabled: props.disabled === true,
|
|
1511
|
-
onEdit: (checked) => props.onPatch({ input: {
|
|
1512
|
-
...model.input,
|
|
1513
|
-
[modality]: checked
|
|
1514
|
-
} })
|
|
1515
|
-
}, modality))]
|
|
1516
|
-
})
|
|
1988
|
+
summary !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1989
|
+
className: "lpc-routeApi",
|
|
1990
|
+
children: summary
|
|
1991
|
+
}) : null
|
|
1517
1992
|
]
|
|
1518
|
-
}),
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
value: model.reasoningEfforts,
|
|
1522
|
-
disabled: props.disabled === true,
|
|
1523
|
-
t,
|
|
1524
|
-
onEdit: (reasoningEfforts) => props.onPatch({ reasoningEfforts })
|
|
1525
|
-
}),
|
|
1526
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompatEditor, {
|
|
1527
|
-
idPrefix: `${id}-compat`,
|
|
1528
|
-
api: props.api,
|
|
1529
|
-
compat: model.compat,
|
|
1530
|
-
epoch: props.epoch,
|
|
1531
|
-
disabled: props.disabled === true,
|
|
1532
|
-
wide: true,
|
|
1533
|
-
t,
|
|
1534
|
-
onEdit: (compat) => props.onPatch({ compat })
|
|
1535
|
-
}),
|
|
1536
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(JsonField, {
|
|
1537
|
-
id: `${id}-extra`,
|
|
1538
|
-
label: t("extraFields"),
|
|
1539
|
-
hint: t("extraFieldsHint"),
|
|
1540
|
-
invalidText: t("invalidJson"),
|
|
1541
|
-
value: Object.keys(model.extra).length === 0 ? void 0 : model.extra,
|
|
1542
|
-
epoch: props.epoch,
|
|
1993
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1994
|
+
type: "button",
|
|
1995
|
+
className: "lpc-btn lpc-btnGhost lpc-btnSmall",
|
|
1543
1996
|
disabled: props.disabled === true,
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1997
|
+
onClick: props.onRemove,
|
|
1998
|
+
children: t("deleteModel")
|
|
1999
|
+
})]
|
|
2000
|
+
}), props.open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2001
|
+
className: "lpc-modelBody",
|
|
2002
|
+
children: [
|
|
2003
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2004
|
+
className: "lpc-grid",
|
|
2005
|
+
children: [
|
|
2006
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
2007
|
+
id: `${id}-id`,
|
|
2008
|
+
label: t("modelId"),
|
|
2009
|
+
hint: t("modelIdHint"),
|
|
2010
|
+
value: model.id,
|
|
2011
|
+
disabled: props.disabled === true,
|
|
2012
|
+
invalid: model.id.trim() === "",
|
|
2013
|
+
invalidLabel: t("modelIdRequired"),
|
|
2014
|
+
onEdit: (value) => props.onPatch({ id: value })
|
|
2015
|
+
}),
|
|
2016
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
2017
|
+
id: `${id}-extends`,
|
|
2018
|
+
label: t("modelExtends"),
|
|
2019
|
+
hint: t("modelExtendsHint"),
|
|
2020
|
+
value: model.extends,
|
|
2021
|
+
disabled: props.disabled === true,
|
|
2022
|
+
onEdit: (value) => props.onPatch({ extends: value })
|
|
2023
|
+
}),
|
|
2024
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
2025
|
+
id: `${id}-name`,
|
|
2026
|
+
label: t("modelName"),
|
|
2027
|
+
value: model.name,
|
|
2028
|
+
disabled: props.disabled === true,
|
|
2029
|
+
onEdit: (value) => props.onPatch({ name: value })
|
|
2030
|
+
}),
|
|
2031
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
2032
|
+
id: `${id}-ctx`,
|
|
2033
|
+
label: t("contextWindow"),
|
|
2034
|
+
numeric: true,
|
|
2035
|
+
value: model.contextWindow,
|
|
2036
|
+
disabled: props.disabled === true,
|
|
2037
|
+
onEdit: (value) => props.onPatch({ contextWindow: value })
|
|
2038
|
+
}),
|
|
2039
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
2040
|
+
id: `${id}-max`,
|
|
2041
|
+
label: t("maxTokens"),
|
|
2042
|
+
numeric: true,
|
|
2043
|
+
value: model.maxTokens,
|
|
2044
|
+
disabled: props.disabled === true,
|
|
2045
|
+
onEdit: (value) => props.onPatch({ maxTokens: value })
|
|
2046
|
+
}),
|
|
2047
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2048
|
+
className: "lpc-field",
|
|
2049
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2050
|
+
className: "lpc-label",
|
|
2051
|
+
children: t("input")
|
|
2052
|
+
}), MODALITIES.map((modality) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
|
|
2053
|
+
prefix: "lpc",
|
|
2054
|
+
id: `${id}-input-${modality}`,
|
|
2055
|
+
label: modality,
|
|
2056
|
+
checked: model.input[modality],
|
|
2057
|
+
disabled: props.disabled === true,
|
|
2058
|
+
onEdit: (checked) => props.onPatch({ input: {
|
|
2059
|
+
...model.input,
|
|
2060
|
+
[modality]: checked
|
|
2061
|
+
} })
|
|
2062
|
+
}, modality))]
|
|
2063
|
+
})
|
|
2064
|
+
]
|
|
2065
|
+
}),
|
|
2066
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReasoningEditor, {
|
|
2067
|
+
idPrefix: `${id}-re`,
|
|
2068
|
+
value: model.reasoningEfforts,
|
|
2069
|
+
disabled: props.disabled === true,
|
|
2070
|
+
t,
|
|
2071
|
+
onEdit: (reasoningEfforts) => props.onPatch({ reasoningEfforts })
|
|
2072
|
+
}),
|
|
2073
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompatEditor, {
|
|
2074
|
+
idPrefix: `${id}-compat`,
|
|
2075
|
+
api: props.api,
|
|
2076
|
+
compat: model.compat,
|
|
2077
|
+
epoch: props.epoch,
|
|
2078
|
+
disabled: props.disabled === true,
|
|
2079
|
+
wide: true,
|
|
2080
|
+
t,
|
|
2081
|
+
onEdit: (compat) => props.onPatch({ compat })
|
|
2082
|
+
}),
|
|
2083
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(JsonField, {
|
|
2084
|
+
id: `${id}-extra`,
|
|
2085
|
+
label: t("extraFields"),
|
|
2086
|
+
hint: t("extraFieldsHint"),
|
|
2087
|
+
invalidText: t("invalidJson"),
|
|
2088
|
+
value: Object.keys(model.extra).length === 0 ? void 0 : model.extra,
|
|
2089
|
+
epoch: props.epoch,
|
|
2090
|
+
disabled: props.disabled === true,
|
|
2091
|
+
wide: true,
|
|
2092
|
+
onEdit: (extra) => {
|
|
2093
|
+
const next = extraOfJson(extra);
|
|
2094
|
+
if (next !== void 0) props.onPatch({ extra: next });
|
|
2095
|
+
}
|
|
2096
|
+
})
|
|
2097
|
+
]
|
|
2098
|
+
}) : null]
|
|
1551
2099
|
});
|
|
1552
2100
|
}
|
|
1553
2101
|
function ReasoningEditor(props) {
|
|
@@ -1739,7 +2287,7 @@ ${extra}
|
|
|
1739
2287
|
id: `${props.id}-api`,
|
|
1740
2288
|
label: t("api"),
|
|
1741
2289
|
value: draft.api,
|
|
1742
|
-
options:
|
|
2290
|
+
options: protocolOptions(),
|
|
1743
2291
|
unsetLabel: t("compatUnset"),
|
|
1744
2292
|
disabled: props.disabled === true,
|
|
1745
2293
|
onEdit: (value) => props.onPatch({
|
|
@@ -1780,14 +2328,16 @@ ${extra}
|
|
|
1780
2328
|
//#endregion
|
|
1781
2329
|
//#region src/client/views/providers.tsx
|
|
1782
2330
|
/**
|
|
1783
|
-
* Provider 路由编辑:每个 route
|
|
1784
|
-
*
|
|
1785
|
-
*
|
|
2331
|
+
* Provider 路由编辑:每个 route 一张**受控**折叠小节(展开态由卡片持有,
|
|
2332
|
+
* 以便「从内置目录添加」后自动展开目标 route、以及卡片级"全部收起")。
|
|
2333
|
+
* 小节内容:基础字段 + 请求头 + 高级设置(其余字段 / retryPolicy / compat)
|
|
2334
|
+
* + 模型目录(行默认折叠,见 views/models.tsx)。
|
|
1786
2335
|
* @module llm-pi/client/views/providers
|
|
1787
2336
|
*/
|
|
1788
2337
|
function ProvidersSection(props) {
|
|
1789
2338
|
const [newRoute, setNewRoute] = (0, react.useState)("");
|
|
1790
2339
|
const [routeError, setRouteError] = (0, react.useState)("");
|
|
2340
|
+
const routes = Object.keys(props.providers);
|
|
1791
2341
|
const submitAdd = () => {
|
|
1792
2342
|
const key = newRoute.trim();
|
|
1793
2343
|
if (key === "") {
|
|
@@ -1805,9 +2355,24 @@ ${extra}
|
|
|
1805
2355
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1806
2356
|
className: "lpc-section",
|
|
1807
2357
|
children: [
|
|
1808
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1809
|
-
className: "lpc-
|
|
1810
|
-
children:
|
|
2358
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2359
|
+
className: "lpc-modelHead",
|
|
2360
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2361
|
+
className: "lpc-modelTitle",
|
|
2362
|
+
children: props.t("providersGroup")
|
|
2363
|
+
}), routes.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2364
|
+
type: "button",
|
|
2365
|
+
className: "lpc-btn lpc-btnGhost lpc-btnSmall",
|
|
2366
|
+
disabled: props.disabled === true,
|
|
2367
|
+
onClick: props.onCollapseAll,
|
|
2368
|
+
children: props.t("providersCollapseAll")
|
|
2369
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2370
|
+
type: "button",
|
|
2371
|
+
className: "lpc-btn lpc-btnGhost lpc-btnSmall",
|
|
2372
|
+
disabled: props.disabled === true,
|
|
2373
|
+
onClick: props.onExpandAll,
|
|
2374
|
+
children: props.t("providersExpandAll")
|
|
2375
|
+
})] }) : null]
|
|
1811
2376
|
}),
|
|
1812
2377
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1813
2378
|
className: "lpc-addRoute",
|
|
@@ -1835,20 +2400,25 @@ ${extra}
|
|
|
1835
2400
|
Object.entries(props.providers).map(([route, draft]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderSection, {
|
|
1836
2401
|
route,
|
|
1837
2402
|
draft,
|
|
2403
|
+
open: props.openRoutes[route] === true,
|
|
1838
2404
|
epoch: props.epoch,
|
|
1839
2405
|
disabled: props.disabled === true,
|
|
1840
2406
|
t: props.t,
|
|
2407
|
+
onToggle: () => props.onToggleRoute(route, props.openRoutes[route] !== true),
|
|
1841
2408
|
onRemove: () => props.onRemoveRoute(route),
|
|
1842
|
-
onPatch: (patch) => props.onPatchProvider(route, patch)
|
|
2409
|
+
onPatch: (patch) => props.onPatchProvider(route, patch),
|
|
2410
|
+
onAddFromCatalog: () => props.onAddFromCatalog(route)
|
|
1843
2411
|
}, route))
|
|
1844
2412
|
]
|
|
1845
2413
|
});
|
|
1846
2414
|
}
|
|
1847
2415
|
function ProviderSection(props) {
|
|
1848
|
-
const [open, setOpen] = (0, react.useState)(false);
|
|
1849
2416
|
const { route, draft, t } = props;
|
|
1850
2417
|
const id = route.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1851
|
-
const
|
|
2418
|
+
const adapter = typeof draft.extra["adapter"] === "string" ? draft.extra["adapter"] : "pi";
|
|
2419
|
+
const summary = draft.api !== "" ? draft.api : draft.extends !== "" ? `extends ${draft.extends}` : adapter;
|
|
2420
|
+
const modelCount = draft.models.length;
|
|
2421
|
+
const meta = modelCount > 0 ? `${modelCount}` : "";
|
|
1852
2422
|
const fieldProps = {
|
|
1853
2423
|
id,
|
|
1854
2424
|
draft,
|
|
@@ -1863,10 +2433,10 @@ ${extra}
|
|
|
1863
2433
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1864
2434
|
type: "button",
|
|
1865
2435
|
className: "lpc-routeToggle",
|
|
1866
|
-
"aria-expanded": open,
|
|
1867
|
-
onClick:
|
|
2436
|
+
"aria-expanded": props.open,
|
|
2437
|
+
onClick: props.onToggle,
|
|
1868
2438
|
children: [
|
|
1869
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `lpc-chevron${open ? " lpc-chevronOpen" : ""}` }),
|
|
2439
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `lpc-chevron${props.open ? " lpc-chevronOpen" : ""}` }),
|
|
1870
2440
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1871
2441
|
className: "lpc-routeKey",
|
|
1872
2442
|
children: route
|
|
@@ -1874,6 +2444,10 @@ ${extra}
|
|
|
1874
2444
|
summary !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1875
2445
|
className: "lpc-routeApi",
|
|
1876
2446
|
children: summary
|
|
2447
|
+
}) : null,
|
|
2448
|
+
meta !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2449
|
+
className: "lpc-collapseMeta",
|
|
2450
|
+
children: meta
|
|
1877
2451
|
}) : null
|
|
1878
2452
|
]
|
|
1879
2453
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -1883,7 +2457,7 @@ ${extra}
|
|
|
1883
2457
|
onClick: props.onRemove,
|
|
1884
2458
|
children: t("deleteRoute")
|
|
1885
2459
|
})]
|
|
1886
|
-
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2460
|
+
}), props.open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1887
2461
|
className: "lpc-routeBody",
|
|
1888
2462
|
children: [
|
|
1889
2463
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -1949,12 +2523,12 @@ ${extra}
|
|
|
1949
2523
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelsTable, {
|
|
1950
2524
|
route,
|
|
1951
2525
|
api: draft.api,
|
|
1952
|
-
defaultProvider: draft.extends,
|
|
1953
2526
|
models: draft.models,
|
|
1954
2527
|
epoch: props.epoch,
|
|
1955
2528
|
disabled: props.disabled === true,
|
|
1956
2529
|
t,
|
|
1957
|
-
onModels: (models) => props.onPatch({ models })
|
|
2530
|
+
onModels: (models) => props.onPatch({ models }),
|
|
2531
|
+
onAddFromCatalog: props.onAddFromCatalog
|
|
1958
2532
|
})
|
|
1959
2533
|
]
|
|
1960
2534
|
}) : null]
|
|
@@ -1963,35 +2537,97 @@ ${extra}
|
|
|
1963
2537
|
//#endregion
|
|
1964
2538
|
//#region src/client/card.tsx
|
|
1965
2539
|
/**
|
|
1966
|
-
* 「LLM 路由」配置卡片:经 injectPluginConfigCard
|
|
1967
|
-
*
|
|
1968
|
-
*
|
|
1969
|
-
*
|
|
1970
|
-
*
|
|
1971
|
-
*
|
|
1972
|
-
*
|
|
2540
|
+
* 「LLM 路由」配置卡片:经 injectPluginConfigCard 注册(插件页
|
|
2541
|
+
* plugins.row.config / plugins.bundle.config,view 分发 summary/page)。
|
|
2542
|
+
*
|
|
2543
|
+
* 页面结构(自上而下,除根字段外**默认全部收起**,避免长列表划不到头):
|
|
2544
|
+
* 1. 根字段:启用开关 + 运行期状态行(生效版本/安装树/目录规模/协议/compat 来源/诊断);
|
|
2545
|
+
* 2. 「内置模型目录」浏览器(折叠小节,默认收起):搜索/筛选/复制路径 id/
|
|
2546
|
+
* 一键把 `{id, extends}` 加到目标 route(添加后自动展开该 route 并回显);
|
|
2547
|
+
* 3. Provider 路由列表(每 route 收起;route 内模型行同样默认收起)。
|
|
2548
|
+
*
|
|
2549
|
+
* 数据来源:配置读写走官方 remote.settings(scope/api);运行期事实与目录搜索
|
|
2550
|
+
* 走本插件 `/catalog` 端点(浏览器半读不到 pi-ai 包)。草稿/保存与目录索引各由
|
|
2551
|
+
* 本文件的 hook 承担,全部状态(浏览器开合、目标 route、route 展开态)在卡片持有,
|
|
2552
|
+
* 子组件受控。
|
|
1973
2553
|
* @module llm-pi/client/card
|
|
1974
2554
|
*/
|
|
1975
|
-
/**
|
|
1976
|
-
function
|
|
1977
|
-
const [
|
|
1978
|
-
const [
|
|
2555
|
+
/** 运行期事实 + 目录索引:卡片挂载拉一次(同时安装协议集合与 compat 字段表)。 */
|
|
2556
|
+
function useCatalogMeta() {
|
|
2557
|
+
const [meta, setMeta] = (0, react.useState)(null);
|
|
2558
|
+
const [error, setError] = (0, react.useState)("");
|
|
1979
2559
|
(0, react.useEffect)(() => {
|
|
1980
2560
|
let alive = true;
|
|
1981
|
-
|
|
2561
|
+
fetchCatalogMeta().then((result) => {
|
|
2562
|
+
if (!alive) return;
|
|
2563
|
+
installProtocols(result.kit.protocols);
|
|
2564
|
+
installCompatFields(result.compat.fields);
|
|
2565
|
+
setMeta(result);
|
|
2566
|
+
}).catch((reason) => {
|
|
1982
2567
|
if (!alive) return;
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
if (result.compat !== void 0) installCompatFields(result.compat.fields);
|
|
1986
|
-
}).catch(() => {});
|
|
2568
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
2569
|
+
});
|
|
1987
2570
|
return () => {
|
|
1988
2571
|
alive = false;
|
|
1989
2572
|
};
|
|
1990
2573
|
}, []);
|
|
1991
2574
|
return {
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
2575
|
+
meta,
|
|
2576
|
+
error
|
|
2577
|
+
};
|
|
2578
|
+
}
|
|
2579
|
+
/**
|
|
2580
|
+
* 草稿与保存:首次拿到解析值后播种草稿;后续 Host 更新不覆盖在途编辑
|
|
2581
|
+
* (与官方 staged 表单一致)。dirty 由"提交形状"双向比较得出。
|
|
2582
|
+
*/
|
|
2583
|
+
function useCardDraft(options) {
|
|
2584
|
+
const { value, scope, api, t } = options;
|
|
2585
|
+
const [draft, setDraft] = (0, react.useState)(null);
|
|
2586
|
+
const [epoch, setEpoch] = (0, react.useState)(0);
|
|
2587
|
+
const [saving, setSaving] = (0, react.useState)(false);
|
|
2588
|
+
const [status, setStatus] = (0, react.useState)(IDLE_STATUS);
|
|
2589
|
+
(0, react.useEffect)(() => {
|
|
2590
|
+
if (value === void 0 || draft !== null) return;
|
|
2591
|
+
setDraft(draftFromValue(value));
|
|
2592
|
+
}, [value, draft]);
|
|
2593
|
+
const dirty = (0, react.useMemo)(() => value !== void 0 && draft !== null && JSON.stringify(toPatch(draft)) !== JSON.stringify(toPatch(draftFromValue(value))), [value, draft]);
|
|
2594
|
+
const onSave = () => {
|
|
2595
|
+
if (draft === null) return;
|
|
2596
|
+
setSaving(true);
|
|
2597
|
+
const revision = scope.getSnapshot().revision;
|
|
2598
|
+
api.update(toPatch(draft), revision).then(async () => {
|
|
2599
|
+
await scope.load();
|
|
2600
|
+
const next = scope.getSnapshot().value;
|
|
2601
|
+
if (next !== void 0) setDraft(draftFromValue(next));
|
|
2602
|
+
setEpoch((current) => current + 1);
|
|
2603
|
+
setStatus({
|
|
2604
|
+
kind: "ok",
|
|
2605
|
+
text: t("saveOk")
|
|
2606
|
+
});
|
|
2607
|
+
}).catch((error) => {
|
|
2608
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2609
|
+
setStatus({
|
|
2610
|
+
kind: "error",
|
|
2611
|
+
text: `${t("saveFailed")}${message}`
|
|
2612
|
+
});
|
|
2613
|
+
}).finally(() => setSaving(false));
|
|
2614
|
+
};
|
|
2615
|
+
const onDiscard = () => {
|
|
2616
|
+
if (value === void 0) return;
|
|
2617
|
+
setDraft(draftFromValue(value));
|
|
2618
|
+
setEpoch((current) => current + 1);
|
|
2619
|
+
setStatus(IDLE_STATUS);
|
|
2620
|
+
};
|
|
2621
|
+
return {
|
|
2622
|
+
draft,
|
|
2623
|
+
setDraft,
|
|
2624
|
+
dirty,
|
|
2625
|
+
saving,
|
|
2626
|
+
status,
|
|
2627
|
+
idle: () => setStatus(IDLE_STATUS),
|
|
2628
|
+
epoch,
|
|
2629
|
+
onSave,
|
|
2630
|
+
onDiscard
|
|
1995
2631
|
};
|
|
1996
2632
|
}
|
|
1997
2633
|
/** 路由级草稿改写:改字段 / 新增 route / 删除 route(返回新草稿,不改入参)。 */
|
|
@@ -2025,119 +2661,82 @@ ${extra}
|
|
|
2025
2661
|
providers: next
|
|
2026
2662
|
};
|
|
2027
2663
|
}
|
|
2028
|
-
/**
|
|
2664
|
+
/** 卡片动作:放弃、保存(模型目录已唯一来自 pi-ai,无可拉取项)。 */
|
|
2029
2665
|
function cardActions(options) {
|
|
2030
2666
|
const { t } = options;
|
|
2031
|
-
return [
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
disabled: !options.dirty || options.invalid || options.saving || options.disabled,
|
|
2049
|
-
onClick: options.onSave
|
|
2050
|
-
}
|
|
2051
|
-
];
|
|
2667
|
+
return [{
|
|
2668
|
+
key: "discard",
|
|
2669
|
+
label: t("discard"),
|
|
2670
|
+
disabled: !options.dirty || options.saving,
|
|
2671
|
+
onClick: options.onDiscard
|
|
2672
|
+
}, {
|
|
2673
|
+
key: "save",
|
|
2674
|
+
label: t(options.saving ? "saving" : "save"),
|
|
2675
|
+
variant: "primary",
|
|
2676
|
+
disabled: !options.dirty || options.saving || options.disabled,
|
|
2677
|
+
onClick: options.onSave
|
|
2678
|
+
}];
|
|
2679
|
+
}
|
|
2680
|
+
/** 打开浏览器时的目标 route 选择:优先当前目标,否则第一个候选。 */
|
|
2681
|
+
function pickTarget(targets, preferred) {
|
|
2682
|
+
if (preferred !== "" && targets.includes(preferred)) return preferred;
|
|
2683
|
+
return targets[0] ?? "";
|
|
2052
2684
|
}
|
|
2053
2685
|
function LlmPiCard(props) {
|
|
2054
2686
|
const { t, scope, api } = props;
|
|
2055
2687
|
const snapshot = (0, react.useSyncExternalStore)((listener) => scope.subscribe(listener), () => scope.getSnapshot());
|
|
2056
2688
|
const value = snapshot.value;
|
|
2057
|
-
const
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
const
|
|
2064
|
-
(0, react.
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
}, [value, draft]);
|
|
2068
|
-
const dirty = (0, react.useMemo)(() => value !== void 0 && draft !== null && JSON.stringify(toPatch(draft)) !== JSON.stringify(toPatch(draftFromValue(value))), [value, draft]);
|
|
2069
|
-
const invalid = (0, react.useMemo)(() => {
|
|
2070
|
-
if (draft === null) return false;
|
|
2071
|
-
return !numTextOk(draft.catalogRefreshHours) || Object.values(draft.providers).some((provider) => provider.models.some((model) => model.id.trim() === ""));
|
|
2072
|
-
}, [draft]);
|
|
2689
|
+
const { draft, setDraft, dirty, saving, status, idle, epoch, onSave, onDiscard } = useCardDraft({
|
|
2690
|
+
value,
|
|
2691
|
+
scope,
|
|
2692
|
+
api,
|
|
2693
|
+
t
|
|
2694
|
+
});
|
|
2695
|
+
const [browserOpen, setBrowserOpen] = (0, react.useState)(false);
|
|
2696
|
+
const [browserTarget, setBrowserTarget] = (0, react.useState)("");
|
|
2697
|
+
const [openRoutes, setOpenRoutes] = (0, react.useState)({});
|
|
2698
|
+
const { meta, error: metaError } = useCatalogMeta();
|
|
2073
2699
|
if (props.view === "summary") return t("summaryLine");
|
|
2074
|
-
const variant = cardVariantFor(props.view);
|
|
2075
2700
|
if (value === void 0 || draft === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardLoading, {
|
|
2076
2701
|
prefix: "lpc",
|
|
2077
|
-
variant,
|
|
2078
2702
|
text: t("loading")
|
|
2079
2703
|
});
|
|
2080
|
-
const
|
|
2081
|
-
|
|
2082
|
-
|
|
2704
|
+
const apis = meta?.apis ?? {};
|
|
2705
|
+
const targets = targetRoutes(draft.providers);
|
|
2706
|
+
const target = pickTarget(targets, browserTarget);
|
|
2707
|
+
const openBrowserFor = (route) => {
|
|
2708
|
+
setBrowserTarget(route);
|
|
2709
|
+
setBrowserOpen(true);
|
|
2083
2710
|
};
|
|
2084
2711
|
const onAddRoute = (key) => {
|
|
2085
2712
|
setDraft(withNewRoute(draft, key));
|
|
2086
|
-
|
|
2713
|
+
setOpenRoutes((prev) => ({
|
|
2714
|
+
...prev,
|
|
2715
|
+
[key]: true
|
|
2716
|
+
}));
|
|
2717
|
+
setBrowserTarget((prev) => prev === "" ? key : prev);
|
|
2718
|
+
idle();
|
|
2087
2719
|
};
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
const
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
text: t("saveOk")
|
|
2103
|
-
});
|
|
2104
|
-
}).catch((error) => {
|
|
2105
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2106
|
-
setStatus({
|
|
2107
|
-
kind: "error",
|
|
2108
|
-
text: `${t("saveFailed")}${message}`
|
|
2109
|
-
});
|
|
2110
|
-
}).finally(() => setSaving(false));
|
|
2111
|
-
};
|
|
2112
|
-
const onDiscard = () => {
|
|
2113
|
-
setDraft(draftFromValue(value));
|
|
2114
|
-
setEpoch((value) => value + 1);
|
|
2115
|
-
setStatus(IDLE_STATUS);
|
|
2116
|
-
};
|
|
2117
|
-
const onRefreshCatalog = () => {
|
|
2118
|
-
setRefreshing(true);
|
|
2119
|
-
refreshCatalog().then((result) => {
|
|
2120
|
-
setModelsDevStatus(result.status);
|
|
2121
|
-
setStatus({
|
|
2122
|
-
kind: "ok",
|
|
2123
|
-
text: t("refreshOk")
|
|
2124
|
-
});
|
|
2125
|
-
}).catch((error) => {
|
|
2126
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2127
|
-
setStatus({
|
|
2128
|
-
kind: "error",
|
|
2129
|
-
text: `${t("refreshFailed")}${message}`
|
|
2130
|
-
});
|
|
2131
|
-
}).finally(() => setRefreshing(false));
|
|
2720
|
+
/**
|
|
2721
|
+
* 目录一键添加:把 `{id, extends: 'provider/model'}` 写进目标 route 草稿
|
|
2722
|
+
* (不预填 name/容量——继承才能跟随 pi-ai 目录升级),并展开该 route 给出反馈。
|
|
2723
|
+
*/
|
|
2724
|
+
const onAddModel = (route, model) => {
|
|
2725
|
+
const result = addModelEntry(draft, route, model);
|
|
2726
|
+
if (!result.added) return false;
|
|
2727
|
+
setDraft(result.draft);
|
|
2728
|
+
setOpenRoutes((prev) => ({
|
|
2729
|
+
...prev,
|
|
2730
|
+
[route]: true
|
|
2731
|
+
}));
|
|
2732
|
+
idle();
|
|
2733
|
+
return true;
|
|
2132
2734
|
};
|
|
2133
2735
|
const disabled = !snapshot.writable;
|
|
2134
2736
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CardChrome, {
|
|
2135
2737
|
prefix: "lpc",
|
|
2136
2738
|
title: t("title"),
|
|
2137
2739
|
description: t("description"),
|
|
2138
|
-
open,
|
|
2139
|
-
onToggle: setOpen,
|
|
2140
|
-
variant,
|
|
2141
2740
|
statusBadge: {
|
|
2142
2741
|
text: t(draft.enabled ? "enabledOn" : "enabledOff"),
|
|
2143
2742
|
on: draft.enabled
|
|
@@ -2149,36 +2748,74 @@ ${extra}
|
|
|
2149
2748
|
actions: cardActions({
|
|
2150
2749
|
t,
|
|
2151
2750
|
dirty,
|
|
2152
|
-
invalid,
|
|
2153
2751
|
saving,
|
|
2154
|
-
refreshing,
|
|
2155
2752
|
disabled,
|
|
2156
|
-
onRefresh: onRefreshCatalog,
|
|
2157
2753
|
onDiscard,
|
|
2158
2754
|
onSave
|
|
2159
2755
|
}),
|
|
2160
|
-
children: [
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2756
|
+
children: [
|
|
2757
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RootFields, {
|
|
2758
|
+
t,
|
|
2759
|
+
draft,
|
|
2760
|
+
disabled,
|
|
2761
|
+
kit: meta?.kit ?? null,
|
|
2762
|
+
kitError: metaError,
|
|
2763
|
+
onEdit: (patch) => {
|
|
2764
|
+
setDraft({
|
|
2765
|
+
...draft,
|
|
2766
|
+
...patch
|
|
2767
|
+
});
|
|
2768
|
+
idle();
|
|
2769
|
+
}
|
|
2770
|
+
}),
|
|
2771
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapseSection, {
|
|
2772
|
+
id: "lpc-catalog-browser",
|
|
2773
|
+
title: t("browserGroup"),
|
|
2774
|
+
defaultOpen: false,
|
|
2775
|
+
open: browserOpen,
|
|
2776
|
+
onToggle: () => setBrowserOpen((open) => !open),
|
|
2777
|
+
meta: meta === null ? "" : `${meta.kit.catalog.models}`,
|
|
2778
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CatalogBrowser, {
|
|
2779
|
+
t,
|
|
2780
|
+
targets,
|
|
2781
|
+
apis,
|
|
2782
|
+
providers: meta?.providers ?? [],
|
|
2783
|
+
target,
|
|
2784
|
+
onTargetChange: setBrowserTarget,
|
|
2785
|
+
providerOf: (route) => draft.providers[route],
|
|
2786
|
+
routeApiOf: (route) => {
|
|
2787
|
+
const provider = draft.providers[route];
|
|
2788
|
+
return provider === void 0 ? void 0 : routeApiFacts(provider, apis).api;
|
|
2789
|
+
},
|
|
2790
|
+
onAdd: onAddModel,
|
|
2791
|
+
disabled,
|
|
2792
|
+
...metaError === "" ? {} : { metaError }
|
|
2793
|
+
})
|
|
2794
|
+
}),
|
|
2795
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProvidersSection, {
|
|
2796
|
+
providers: draft.providers,
|
|
2797
|
+
openRoutes,
|
|
2798
|
+
epoch,
|
|
2799
|
+
disabled,
|
|
2800
|
+
t,
|
|
2801
|
+
onAddRoute,
|
|
2802
|
+
onRemoveRoute: (route) => {
|
|
2803
|
+
setDraft(withoutRoute(draft, route));
|
|
2804
|
+
idle();
|
|
2805
|
+
},
|
|
2806
|
+
onPatchProvider: (route, patch) => {
|
|
2807
|
+
setDraft(withProviderPatch(draft, route, patch));
|
|
2808
|
+
idle();
|
|
2809
|
+
},
|
|
2810
|
+
onToggleRoute: (route, open) => setOpenRoutes((prev) => ({
|
|
2811
|
+
...prev,
|
|
2812
|
+
[route]: open
|
|
2813
|
+
})),
|
|
2814
|
+
onCollapseAll: () => setOpenRoutes({}),
|
|
2815
|
+
onExpandAll: () => setOpenRoutes(Object.fromEntries(Object.keys(draft.providers).map((route) => [route, true]))),
|
|
2816
|
+
onAddFromCatalog: openBrowserFor
|
|
2817
|
+
})
|
|
2818
|
+
]
|
|
2182
2819
|
});
|
|
2183
2820
|
}
|
|
2184
2821
|
//#endregion
|
|
@@ -2186,33 +2823,76 @@ ${extra}
|
|
|
2186
2823
|
/**
|
|
2187
2824
|
* 配置卡片文案(zh/en)。经 ctx.locale.register 注册、bind 取用,与官方卡片同机制。
|
|
2188
2825
|
* 公共键(save/discard/unsaved 等)来自 shared 的 common 字典,本文件只维护业务键。
|
|
2826
|
+
* 服务端推导出来的诊断/提示(compat problem、版本区间提示、降级诊断)按原文展示,
|
|
2827
|
+
* 不做二次翻译——它们是事实描述,且与日志同一份文本。
|
|
2189
2828
|
* @module llm-pi/client/i18n
|
|
2190
2829
|
*/
|
|
2191
2830
|
const NS = "dsh-plus-llm-pi";
|
|
2192
2831
|
const ownZh = {
|
|
2193
2832
|
title: "LLM 路由(llm-pi)",
|
|
2194
|
-
description: "自定义 LLM 路由:协议、compat
|
|
2195
|
-
summaryLine: "自定义 LLM 路由:协议、compat
|
|
2833
|
+
description: "自定义 LLM 路由:协议、compat、模型继承;模型目录唯一来自 pi-ai 内置目录(随已装 dsh 自动跟随)。",
|
|
2834
|
+
summaryLine: "自定义 LLM 路由:协议、compat、模型继承;模型目录唯一来自 pi-ai 内置目录(随已装 dsh 自动跟随)。",
|
|
2196
2835
|
enabled: "启用插件",
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2836
|
+
runtimeGroup: "运行期套件",
|
|
2837
|
+
runtimeSource: "来源",
|
|
2838
|
+
runtimeRoot: "安装树",
|
|
2839
|
+
runtimeVersions: "生效版本",
|
|
2840
|
+
runtimeCatalog: "内置模型目录",
|
|
2841
|
+
runtimeProtocols: "可服务协议",
|
|
2842
|
+
runtimeCompat: "compat 门控表",
|
|
2843
|
+
runtimeDiagnostics: "诊断",
|
|
2844
|
+
runtimeVersionUnknown: "未知",
|
|
2845
|
+
runtimeCatalogUnit: "个 provider /",
|
|
2846
|
+
runtimeCatalogModels: "个模型",
|
|
2847
|
+
runtimeCatalogGenerated: "数据生成于",
|
|
2848
|
+
runtimeNotice: "提示",
|
|
2849
|
+
runtimeDerived: "现场推导",
|
|
2850
|
+
runtimeFallback: "回退快照",
|
|
2851
|
+
browserGroup: "内置模型目录",
|
|
2852
|
+
browserHint: "浏览 pi-ai 内置目录(随已装 dsh 的 pi-ai 版本自动更新):模糊搜索、按供应商/协议/能力筛选;复制路径 id,或一键把「模型 id + 继承源」加到某个 route。",
|
|
2853
|
+
browserSearch: "模糊搜索",
|
|
2854
|
+
browserSearchPlaceholder: "模型 id / 显示名 / provider,多词用空格",
|
|
2855
|
+
browserProvider: "供应商",
|
|
2856
|
+
browserApi: "协议",
|
|
2857
|
+
browserAll: "全部",
|
|
2858
|
+
browserOnlyServable: "仅本插件可服务",
|
|
2859
|
+
browserReasoning: "推理",
|
|
2860
|
+
browserImage: "图片输入",
|
|
2861
|
+
browserTarget: "目标 route",
|
|
2862
|
+
browserNoTarget: "还没有可添加的 route:先在上方新增一个 route(adapter: deepseek 的路由不适用 pi-ai 继承)。",
|
|
2863
|
+
browserMatched: "命中",
|
|
2864
|
+
browserHiddenServable: "另有协议不受支持的模型",
|
|
2865
|
+
browserHiddenUnit: "个已隐藏",
|
|
2866
|
+
browserLoadMore: "加载更多",
|
|
2867
|
+
browserLoading: "目录加载中…",
|
|
2868
|
+
browserEmpty: "没有命中的模型。",
|
|
2869
|
+
browserFailed: "目录加载失败:",
|
|
2870
|
+
browserAdd: "添加",
|
|
2871
|
+
browserAdded: "已添加",
|
|
2872
|
+
browserCopy: "复制路径 id",
|
|
2873
|
+
browserCopied: "已复制",
|
|
2874
|
+
browserDetails: "参数明细",
|
|
2875
|
+
browserCompat: "compat",
|
|
2876
|
+
browserExtra: "其余参数",
|
|
2877
|
+
browserThinkMap: "思考档位",
|
|
2878
|
+
browserInputText: "文本",
|
|
2879
|
+
browserInputImage: "图片",
|
|
2880
|
+
browserContextShort: "上下文",
|
|
2881
|
+
browserMaxShort: "输出",
|
|
2882
|
+
browserAddedTo: "已添加到 route",
|
|
2883
|
+
browserTargetProtocol: "当前协议",
|
|
2884
|
+
browserNoProtocol: "协议待定",
|
|
2885
|
+
blockUnsupported: "该协议本插件无法服务(不可添加)",
|
|
2886
|
+
blockDuplicate: "该 route 已有同 id 模型",
|
|
2887
|
+
blockConflict: "与目标 route 的协议不一致",
|
|
2888
|
+
blockMixed: "目标 route 已混用多个协议,请先修正",
|
|
2889
|
+
blockNoRoute: "目标 route 不存在",
|
|
2212
2890
|
advancedGroup: "高级设置(retryPolicy / compat)",
|
|
2213
2891
|
extraFields: "其余字段(JSON)",
|
|
2214
2892
|
extraFieldsHint: "表单未覆盖的 wire 字段原样往返:如 adapter=deepseek 的 thinking / reasoningEffort、Files API 与图片配额等。对象整体合并进该 route(模型级同 key 覆盖 route 级),非法 JSON 不会提交。",
|
|
2215
2893
|
providersGroup: "Provider 路由",
|
|
2894
|
+
providersCollapseAll: "全部收起",
|
|
2895
|
+
providersExpandAll: "全部展开",
|
|
2216
2896
|
addRoute: "新增 route",
|
|
2217
2897
|
addRoutePlaceholder: "新 route 键名(如 my-llm)",
|
|
2218
2898
|
routeEmpty: "route 键名不能为空。",
|
|
@@ -2220,7 +2900,7 @@ ${extra}
|
|
|
2220
2900
|
deleteRoute: "删除 route",
|
|
2221
2901
|
providerFields: "基础字段",
|
|
2222
2902
|
extends: "继承内置 provider",
|
|
2223
|
-
extendsHint: "如 openai、anthropic
|
|
2903
|
+
extendsHint: "如 openai、anthropic(候选见「内置模型目录」);提供 api/baseURL 默认值与模型查找源。",
|
|
2224
2904
|
displayName: "显示名",
|
|
2225
2905
|
api: "线协议",
|
|
2226
2906
|
baseURL: "端点 URL",
|
|
@@ -2251,52 +2931,98 @@ ${extra}
|
|
|
2251
2931
|
compatGroup: "Compat 覆盖",
|
|
2252
2932
|
compatApiHint: "api 未设置时暂按 openai-completions 字段组渲染;保存时后端按实际协议校验。",
|
|
2253
2933
|
compatUnset: "未设置",
|
|
2934
|
+
compatSetCount: "已设",
|
|
2935
|
+
compatFilter: "过滤字段",
|
|
2254
2936
|
modelsGroup: "模型目录",
|
|
2255
2937
|
addModel: "添加模型",
|
|
2256
|
-
|
|
2938
|
+
addFromCatalog: "从内置目录添加",
|
|
2939
|
+
modelsCollapseAll: "全部收起",
|
|
2940
|
+
modelsExpandAll: "全部展开",
|
|
2941
|
+
modelsFilter: "过滤模型",
|
|
2942
|
+
modelsFilterPlaceholder: "按 id / 继承源筛选",
|
|
2943
|
+
modelsHidden: "已按筛选隐藏",
|
|
2257
2944
|
modelRow: "模型",
|
|
2945
|
+
modelUntitled: "(未填 id)",
|
|
2946
|
+
deleteModel: "删除",
|
|
2258
2947
|
modelId: "模型 id",
|
|
2259
2948
|
modelIdHint: "发送给 provider 的标识,必填。",
|
|
2260
2949
|
modelIdRequired: "模型 id 必填。",
|
|
2261
2950
|
modelExtends: "继承源",
|
|
2262
|
-
modelExtendsHint: "\"provider/model\" 或裸 model id
|
|
2951
|
+
modelExtendsHint: "\"provider/model\" 或裸 model id;候选见「内置模型目录」。",
|
|
2263
2952
|
modelName: "显示名",
|
|
2264
2953
|
contextWindow: "上下文容量",
|
|
2265
2954
|
maxTokens: "输出上限",
|
|
2266
2955
|
input: "输入模态",
|
|
2267
2956
|
reasoningEfforts: "reasoningEfforts",
|
|
2268
2957
|
nonReasoning: "非推理模型",
|
|
2269
|
-
catalogSource: "候选来源",
|
|
2270
|
-
catalogProvider: "候选 provider",
|
|
2271
|
-
catalogLoading: "目录加载中…",
|
|
2272
|
-
catalogFailed: "目录加载失败。",
|
|
2273
2958
|
saveOk: "已保存。",
|
|
2274
2959
|
saveFailed: "保存失败:"
|
|
2275
2960
|
};
|
|
2276
2961
|
const ownEn = {
|
|
2277
2962
|
title: "LLM routes (llm-pi)",
|
|
2278
|
-
description: "Custom LLM routes: protocol, compat, model catalog
|
|
2279
|
-
summaryLine: "Custom LLM routes: protocol, compat, model catalog
|
|
2963
|
+
description: "Custom LLM routes: protocol, compat, model inheritance; the model catalog comes solely from the pi-ai built-in catalog (following the installed dsh).",
|
|
2964
|
+
summaryLine: "Custom LLM routes: protocol, compat, model inheritance; the model catalog comes solely from the pi-ai built-in catalog.",
|
|
2280
2965
|
enabled: "Enable plugin",
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2966
|
+
runtimeGroup: "Runtime kit",
|
|
2967
|
+
runtimeSource: "Source",
|
|
2968
|
+
runtimeRoot: "Install tree",
|
|
2969
|
+
runtimeVersions: "Effective versions",
|
|
2970
|
+
runtimeCatalog: "Built-in catalog",
|
|
2971
|
+
runtimeProtocols: "Servable protocols",
|
|
2972
|
+
runtimeCompat: "compat gates",
|
|
2973
|
+
runtimeDiagnostics: "Diagnostics",
|
|
2974
|
+
runtimeVersionUnknown: "unknown",
|
|
2975
|
+
runtimeCatalogUnit: "providers /",
|
|
2976
|
+
runtimeCatalogModels: "models",
|
|
2977
|
+
runtimeCatalogGenerated: "data generated at",
|
|
2978
|
+
runtimeNotice: "Notice",
|
|
2979
|
+
runtimeDerived: "derived live",
|
|
2980
|
+
runtimeFallback: "fallback snapshot",
|
|
2981
|
+
browserGroup: "Built-in model catalog",
|
|
2982
|
+
browserHint: "Browse the pi-ai built-in catalog (follows the installed dsh): fuzzy search, filter by provider/protocol/capability; copy the path id, or add \"model id + inheritance source\" to a route in one click.",
|
|
2983
|
+
browserSearch: "Fuzzy search",
|
|
2984
|
+
browserSearchPlaceholder: "model id / display name / provider; space-separated terms",
|
|
2985
|
+
browserProvider: "Provider",
|
|
2986
|
+
browserApi: "Protocol",
|
|
2987
|
+
browserAll: "All",
|
|
2988
|
+
browserOnlyServable: "Servable only",
|
|
2989
|
+
browserReasoning: "Reasoning",
|
|
2990
|
+
browserImage: "Image input",
|
|
2991
|
+
browserTarget: "Target route",
|
|
2992
|
+
browserNoTarget: "No route to add to yet: create one above (adapter: deepseek routes do not use pi-ai inheritance).",
|
|
2993
|
+
browserMatched: "Matched",
|
|
2994
|
+
browserHiddenServable: "Unsupported-protocol models",
|
|
2995
|
+
browserHiddenUnit: "hidden",
|
|
2996
|
+
browserLoadMore: "Load more",
|
|
2997
|
+
browserLoading: "Loading catalog…",
|
|
2998
|
+
browserEmpty: "No matching model.",
|
|
2999
|
+
browserFailed: "Catalog request failed: ",
|
|
3000
|
+
browserAdd: "Add",
|
|
3001
|
+
browserAdded: "Added",
|
|
3002
|
+
browserCopy: "Copy path id",
|
|
3003
|
+
browserCopied: "Copied",
|
|
3004
|
+
browserDetails: "Parameters",
|
|
3005
|
+
browserCompat: "compat",
|
|
3006
|
+
browserExtra: "Other parameters",
|
|
3007
|
+
browserThinkMap: "Thinking levels",
|
|
3008
|
+
browserInputText: "text",
|
|
3009
|
+
browserInputImage: "image",
|
|
3010
|
+
browserContextShort: "context",
|
|
3011
|
+
browserMaxShort: "output",
|
|
3012
|
+
browserAddedTo: "Added to route",
|
|
3013
|
+
browserTargetProtocol: "protocol",
|
|
3014
|
+
browserNoProtocol: "protocol undecided",
|
|
3015
|
+
blockUnsupported: "This protocol is not servable by this plugin",
|
|
3016
|
+
blockDuplicate: "This route already has a model with the same id",
|
|
3017
|
+
blockConflict: "Protocol differs from the target route",
|
|
3018
|
+
blockMixed: "The target route already mixes protocols — fix it first",
|
|
3019
|
+
blockNoRoute: "Target route does not exist",
|
|
2296
3020
|
advancedGroup: "Advanced (retryPolicy / compat)",
|
|
2297
3021
|
extraFields: "Other fields (JSON)",
|
|
2298
3022
|
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.",
|
|
2299
3023
|
providersGroup: "Provider routes",
|
|
3024
|
+
providersCollapseAll: "Collapse all",
|
|
3025
|
+
providersExpandAll: "Expand all",
|
|
2300
3026
|
addRoute: "Add route",
|
|
2301
3027
|
addRoutePlaceholder: "New route key (e.g. my-llm)",
|
|
2302
3028
|
routeEmpty: "Route key must not be empty.",
|
|
@@ -2304,7 +3030,7 @@ ${extra}
|
|
|
2304
3030
|
deleteRoute: "Delete route",
|
|
2305
3031
|
providerFields: "Basic fields",
|
|
2306
3032
|
extends: "Inherit built-in provider",
|
|
2307
|
-
extendsHint: "e.g. openai, anthropic; provides api/baseURL defaults and the model lookup source.",
|
|
3033
|
+
extendsHint: "e.g. openai, anthropic (see the built-in catalog below); provides api/baseURL defaults and the model lookup source.",
|
|
2308
3034
|
displayName: "Display name",
|
|
2309
3035
|
api: "Wire protocol",
|
|
2310
3036
|
baseURL: "Base URL",
|
|
@@ -2335,25 +3061,30 @@ ${extra}
|
|
|
2335
3061
|
compatGroup: "Compat overrides",
|
|
2336
3062
|
compatApiHint: "When api is unset, fields render per openai-completions; the backend validates per the effective protocol.",
|
|
2337
3063
|
compatUnset: "Unset",
|
|
3064
|
+
compatSetCount: "set",
|
|
3065
|
+
compatFilter: "Filter fields",
|
|
2338
3066
|
modelsGroup: "Model catalog",
|
|
2339
3067
|
addModel: "Add model",
|
|
2340
|
-
|
|
3068
|
+
addFromCatalog: "Add from catalog",
|
|
3069
|
+
modelsCollapseAll: "Collapse all",
|
|
3070
|
+
modelsExpandAll: "Expand all",
|
|
3071
|
+
modelsFilter: "Filter models",
|
|
3072
|
+
modelsFilterPlaceholder: "filter by id / inheritance source",
|
|
3073
|
+
modelsHidden: "hidden by filter",
|
|
2341
3074
|
modelRow: "Model",
|
|
3075
|
+
modelUntitled: "(no id yet)",
|
|
3076
|
+
deleteModel: "Delete",
|
|
2342
3077
|
modelId: "Model id",
|
|
2343
3078
|
modelIdHint: "The identifier sent to the provider; required.",
|
|
2344
3079
|
modelIdRequired: "Model id is required.",
|
|
2345
3080
|
modelExtends: "Inherits from",
|
|
2346
|
-
modelExtendsHint: "\"provider/model\" or a bare model id
|
|
3081
|
+
modelExtendsHint: "\"provider/model\" or a bare model id; candidates in the built-in catalog above.",
|
|
2347
3082
|
modelName: "Display name",
|
|
2348
3083
|
contextWindow: "Context window",
|
|
2349
3084
|
maxTokens: "Max tokens",
|
|
2350
3085
|
input: "Input modalities",
|
|
2351
3086
|
reasoningEfforts: "reasoningEfforts",
|
|
2352
3087
|
nonReasoning: "Non-reasoning model",
|
|
2353
|
-
catalogSource: "Candidate source",
|
|
2354
|
-
catalogProvider: "Candidate provider",
|
|
2355
|
-
catalogLoading: "Loading catalog…",
|
|
2356
|
-
catalogFailed: "Failed to load catalog.",
|
|
2357
3088
|
saveOk: "Saved.",
|
|
2358
3089
|
saveFailed: "Save failed: "
|
|
2359
3090
|
};
|
|
@@ -2363,9 +3094,9 @@ ${extra}
|
|
|
2363
3094
|
//#region src/client/styles.ts
|
|
2364
3095
|
/**
|
|
2365
3096
|
* 配置卡片样式:基础规则走 @dsh-plus/shared/client 的 cardCss('lpc'),
|
|
2366
|
-
* 本文件补插件特有规则(route/model
|
|
2367
|
-
*
|
|
2368
|
-
* 键值对行纵向堆叠、文本框 16px 防 iOS
|
|
3097
|
+
* 本文件补插件特有规则(route/model 折叠块、目录浏览器行、键值对行、网格、
|
|
3098
|
+
* JSON 文本框)及窄屏响应式:views 的双列网格在窄屏转单列、route 头部按钮换行、
|
|
3099
|
+
* 键值对行纵向堆叠、文本框 16px 防 iOS 缩放、目录结果区限高滚动。
|
|
2369
3100
|
* 沿用官方 data-plugin / data-plugin-css 约定(HMR 据此卸载)。
|
|
2370
3101
|
* @module llm-pi/client/styles
|
|
2371
3102
|
*/
|
|
@@ -2383,6 +3114,11 @@ ${extra}
|
|
|
2383
3114
|
.lpc-wide{grid-column:1 / -1}
|
|
2384
3115
|
.lpc-addRoute{display:flex;gap:8px;align-items:center;padding:10px 0}
|
|
2385
3116
|
.lpc-addRoute .lpc-input{flex:1;min-width:0}
|
|
3117
|
+
.lpc-chevron{flex:none;transition:transform .18s}
|
|
3118
|
+
.lpc-chevronOpen{transform:rotate(180deg)}
|
|
3119
|
+
.lpc-iconBtn{appearance:none;border:none;background:0 0;cursor:pointer;color:var(--dsw-alias-label-tertiary);border-radius:6px;padding:4px;display:inline-flex;flex:none}
|
|
3120
|
+
.lpc-iconBtn:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}
|
|
3121
|
+
.lpc-iconBtn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
|
|
2386
3122
|
.lpc-route{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;margin:10px 0;background:var(--dsw-alias-bg-layer-3)}
|
|
2387
3123
|
.lpc-routeHead{display:flex;align-items:center;gap:8px;padding:6px 10px}
|
|
2388
3124
|
.lpc-routeToggle{appearance:none;background:0 0;border:0;font:inherit;color:inherit;cursor:pointer;display:flex;align-items:center;gap:8px;flex:1;min-width:0;text-align:left;padding:4px 0;border-radius:6px}
|
|
@@ -2394,17 +3130,37 @@ ${extra}
|
|
|
2394
3130
|
.lpc-kvRow .lpc-input{flex:1;min-width:0}
|
|
2395
3131
|
.lpc-kvAdd{padding-top:8px}
|
|
2396
3132
|
.lpc-modelRow{border:1px dashed var(--dsw-alias-border-l2);border-radius:10px;margin:10px 0;padding:0 14px;background:var(--dsw-alias-bg-layer-3)}
|
|
2397
|
-
.lpc-modelHead{display:flex;align-items:center;
|
|
2398
|
-
.lpc-modelTitle{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}
|
|
2399
|
-
.lpc-
|
|
2400
|
-
.lpc-
|
|
2401
|
-
.lpc-catalogSelect{width:auto;height:30px}
|
|
3133
|
+
.lpc-modelHead{display:flex;align-items:center;gap:8px;padding:8px 0}
|
|
3134
|
+
.lpc-modelTitle{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;flex:none}
|
|
3135
|
+
.lpc-modelRow .lpc-modelHead .lpc-routeToggle{flex:1}
|
|
3136
|
+
.lpc-modelBody{border-top:1px dashed var(--dsw-alias-border-l2);padding-bottom:8px}
|
|
2402
3137
|
.lpc-collapse{border-top:1px solid var(--dsw-alias-border-l2);margin:2px 0}
|
|
2403
3138
|
.lpc-collapseHead{appearance:none;background:0 0;border:0;font:inherit;color:inherit;cursor:pointer;display:flex;align-items:center;gap:8px;width:100%;text-align:left;padding:10px 0;border-radius:6px}
|
|
2404
3139
|
.lpc-collapseHead:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}
|
|
2405
3140
|
.lpc-collapseTitle{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}
|
|
3141
|
+
.lpc-collapseMeta{color:var(--dsw-alias-label-tertiary);font-size:11px;flex:none;white-space:nowrap}
|
|
2406
3142
|
.lpc-collapseBody{border-top:1px dashed var(--dsw-alias-border-l2);padding-bottom:6px}
|
|
2407
|
-
.lpc-
|
|
3143
|
+
.lpc-cat{display:flex;flex-direction:column;gap:6px}
|
|
3144
|
+
.lpc-catBar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;padding:4px 0}
|
|
3145
|
+
.lpc-catLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;flex:none}
|
|
3146
|
+
.lpc-catSearch{flex:1;min-width:180px}
|
|
3147
|
+
.lpc-catSelect{width:auto;max-width:260px;height:30px}
|
|
3148
|
+
.lpc-catNote{color:var(--dsw-alias-label-tertiary);font-size:12px;min-width:0;word-break:break-all}
|
|
3149
|
+
.lpc-catSummary{color:var(--dsw-alias-label-secondary);margin:2px 0;font-size:12px}
|
|
3150
|
+
.lpc-catList{display:flex;flex-direction:column;max-height:420px;overflow-y:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-3)}
|
|
3151
|
+
.lpc-catRow{border-bottom:1px solid var(--dsw-alias-border-l2);padding:8px 10px}
|
|
3152
|
+
.lpc-catRow:last-child{border-bottom:none}
|
|
3153
|
+
.lpc-catHead{display:flex;align-items:center;gap:8px;min-width:0}
|
|
3154
|
+
.lpc-catPath{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:600;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
3155
|
+
.lpc-catName{color:var(--dsw-alias-label-secondary);font-size:12px;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
3156
|
+
.lpc-catParams{color:var(--dsw-alias-label-tertiary);margin:2px 0;font-size:12px;line-height:1.6;word-break:break-all}
|
|
3157
|
+
.lpc-catDetailsToggle{appearance:none;background:0 0;border:0;font:inherit;color:var(--dsw-alias-label-tertiary);cursor:pointer;display:inline-flex;align-items:center;gap:6px;padding:2px 0;font-size:12px;border-radius:6px}
|
|
3158
|
+
.lpc-catDetailsToggle:hover{color:var(--dsw-alias-label-primary)}
|
|
3159
|
+
.lpc-catDetailsToggle:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}
|
|
3160
|
+
.lpc-catDetails{padding:2px 0 4px}
|
|
3161
|
+
.lpc-catDetailLabel{color:var(--dsw-alias-label-secondary);margin:6px 0 2px;font-size:12px;font-weight:600}
|
|
3162
|
+
.lpc-catJson{background:var(--dsw-alias-bg-module-platform);border-radius:8px;color:var(--dsw-alias-label-secondary);margin:0;padding:8px 10px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;line-height:1.5;max-height:240px;overflow:auto;white-space:pre-wrap;word-break:break-all}
|
|
3163
|
+
.lpc-catMore{padding:6px 0}
|
|
2408
3164
|
@media (max-width:767px){
|
|
2409
3165
|
.lpc-grid{grid-template-columns:1fr}
|
|
2410
3166
|
.lpc-routeHead{flex-wrap:wrap}
|
|
@@ -2416,7 +3172,12 @@ ${extra}
|
|
|
2416
3172
|
.lpc-kvRow .lpc-btn{align-self:flex-end}
|
|
2417
3173
|
.lpc-textarea{font-size:16px}
|
|
2418
3174
|
.lpc-statusRow{display:flex;flex-direction:column;gap:4px}
|
|
2419
|
-
.lpc-
|
|
3175
|
+
.lpc-catBar .lpc-input{flex:1;min-width:140px}
|
|
3176
|
+
.lpc-catSelect{max-width:none}
|
|
3177
|
+
.lpc-catHead{flex-wrap:wrap}
|
|
3178
|
+
.lpc-catPath{white-space:normal;word-break:break-all}
|
|
3179
|
+
.lpc-catName{flex-basis:100%;white-space:normal}
|
|
3180
|
+
.lpc-catList{max-height:60vh}
|
|
2420
3181
|
}
|
|
2421
3182
|
`);
|
|
2422
3183
|
/** 幂等注入样式标签;返回标签(已存在或环境无 document 时为 null)。 */
|