@dsh-plus/llm-pi 0.1.11 → 0.1.13
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 +559 -345
- package/package.json +3 -2
- package/src/client/card.tsx +112 -134
- package/src/client/client.ts +5 -82
- package/src/client/fields.tsx +27 -43
- package/src/client/i18n.ts +7 -20
- package/src/client/styles.ts +27 -51
- package/src/client/views/models.tsx +4 -2
- package/src/client/views/provider-fields.tsx +4 -2
- package/src/client/views/providers.tsx +3 -1
- package/src/client/scope.ts +0 -59
package/lib/client.js
CHANGED
|
@@ -4,8 +4,368 @@ 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
|
+
//#region ../shared/src/client/icons.tsx
|
|
10
|
+
/** 官方 IconChevronDownOutline14 的同构实现(path 数据逐字一致)。 */
|
|
11
|
+
function ChevronDownIcon(props) {
|
|
12
|
+
const size = props.size ?? 14;
|
|
13
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
14
|
+
width: size,
|
|
15
|
+
height: size,
|
|
16
|
+
className: props.className,
|
|
17
|
+
viewBox: "0 0 14 14",
|
|
18
|
+
fill: "none",
|
|
19
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
20
|
+
"aria-hidden": "true",
|
|
21
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
22
|
+
d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
|
|
23
|
+
fill: "currentColor"
|
|
24
|
+
})
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region ../shared/src/client/card.tsx
|
|
29
|
+
const IDLE_STATUS = {
|
|
30
|
+
kind: "idle",
|
|
31
|
+
text: ""
|
|
32
|
+
};
|
|
33
|
+
function CardChrome(props) {
|
|
34
|
+
const p = props.prefix;
|
|
35
|
+
const badge = props.statusBadge;
|
|
36
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
37
|
+
className: `${p}-card${props.open ? ` ${p}-cardOpen` : ""}`,
|
|
38
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
39
|
+
type: "button",
|
|
40
|
+
className: `${p}-header`,
|
|
41
|
+
"aria-expanded": props.open,
|
|
42
|
+
"aria-label": props.title,
|
|
43
|
+
onClick: () => props.onToggle(!props.open),
|
|
44
|
+
children: [
|
|
45
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
46
|
+
className: `${p}-headText`,
|
|
47
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48
|
+
className: `${p}-name`,
|
|
49
|
+
children: props.title
|
|
50
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
51
|
+
className: `${p}-description`,
|
|
52
|
+
children: props.description
|
|
53
|
+
})]
|
|
54
|
+
}),
|
|
55
|
+
badge !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
56
|
+
className: `${p}-statusBadge ${badge.on ? `${p}-statusOn` : `${p}-statusOff`}`,
|
|
57
|
+
children: badge.text
|
|
58
|
+
}) : null,
|
|
59
|
+
props.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
60
|
+
className: `${p}-pending`,
|
|
61
|
+
children: props.dirtyLabel
|
|
62
|
+
}) : null,
|
|
63
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDownIcon, { className: `${p}-chevron${props.open ? ` ${p}-chevronOpen` : ""}` })
|
|
64
|
+
]
|
|
65
|
+
}), props.open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
66
|
+
className: `${p}-body`,
|
|
67
|
+
children: [
|
|
68
|
+
props.readOnlyNotice !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
69
|
+
className: `${p}-readOnly`,
|
|
70
|
+
role: "status",
|
|
71
|
+
children: props.readOnlyNotice
|
|
72
|
+
}) : null,
|
|
73
|
+
props.children,
|
|
74
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
75
|
+
className: `${p}-footer`,
|
|
76
|
+
children: [props.status !== void 0 && props.status.kind !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
77
|
+
className: `${p}-status${props.status.kind === "error" ? ` ${p}-statusError` : ""}`,
|
|
78
|
+
role: "status",
|
|
79
|
+
children: props.status.text
|
|
80
|
+
}) : null, props.actions.map((action) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
81
|
+
type: "button",
|
|
82
|
+
className: `${p}-btn ${action.variant === "primary" ? `${p}-btnPrimary` : `${p}-btnGhost`}`,
|
|
83
|
+
disabled: action.disabled === true,
|
|
84
|
+
onClick: action.onClick,
|
|
85
|
+
children: action.label
|
|
86
|
+
}, action.key))]
|
|
87
|
+
})
|
|
88
|
+
]
|
|
89
|
+
}) : null]
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region ../shared/src/client/fields.tsx
|
|
94
|
+
function TextField$1(props) {
|
|
95
|
+
const p = props.prefix ?? "dshp";
|
|
96
|
+
const invalid = props.invalid === true;
|
|
97
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
98
|
+
className: `${p}-field`,
|
|
99
|
+
children: [
|
|
100
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
101
|
+
className: `${p}-head`,
|
|
102
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
103
|
+
className: `${p}-label`,
|
|
104
|
+
htmlFor: props.id,
|
|
105
|
+
children: props.label
|
|
106
|
+
}), props.badge !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
107
|
+
className: `${p}-badge ${props.badge.set ? `${p}-badgeSet` : `${p}-badgeUnset`}`,
|
|
108
|
+
children: props.badge.text
|
|
109
|
+
}) : null]
|
|
110
|
+
}),
|
|
111
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
112
|
+
id: props.id,
|
|
113
|
+
className: `${p}-input${invalid ? ` ${p}-inputInvalid` : ""}`,
|
|
114
|
+
type: props.password === true ? "password" : "text",
|
|
115
|
+
inputMode: props.numeric === true ? "numeric" : void 0,
|
|
116
|
+
autoComplete: props.password === true ? "off" : void 0,
|
|
117
|
+
"aria-invalid": invalid || void 0,
|
|
118
|
+
value: props.value,
|
|
119
|
+
disabled: props.disabled === true,
|
|
120
|
+
onChange: (event) => props.onEdit(event.target.value)
|
|
121
|
+
}),
|
|
122
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
123
|
+
className: invalid ? `${p}-invalid` : `${p}-hint`,
|
|
124
|
+
children: invalid ? props.invalidLabel ?? "" : props.hint ?? ""
|
|
125
|
+
})
|
|
126
|
+
]
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 勾选行:自绘圆角扁平开关(switch),对齐官方 WebUI 无原生 checkbox 的
|
|
131
|
+
* 设计语言(官方选择类控件全走胶囊/菜单,见 cardCss 的 -switch 规则)。
|
|
132
|
+
* 语义保持 checkbox(无障碍树与表单语义不变),视觉自绘。
|
|
133
|
+
*/
|
|
134
|
+
function CheckRow(props) {
|
|
135
|
+
const p = props.prefix ?? "dshp";
|
|
136
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
137
|
+
className: `${p}-checkRow`,
|
|
138
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
139
|
+
id: props.id,
|
|
140
|
+
type: "checkbox",
|
|
141
|
+
role: "switch",
|
|
142
|
+
"aria-checked": props.checked,
|
|
143
|
+
checked: props.checked,
|
|
144
|
+
disabled: props.disabled === true,
|
|
145
|
+
onChange: (event) => props.onEdit(event.target.checked)
|
|
146
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
147
|
+
htmlFor: props.id,
|
|
148
|
+
children: props.label
|
|
149
|
+
})]
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region ../shared/src/client/i18n.ts
|
|
154
|
+
/** 卡片通用文案(save/discard/unsaved/loading 等四插件共有键)。 */
|
|
155
|
+
const commonZh = {
|
|
156
|
+
save: "保存",
|
|
157
|
+
saving: "保存中…",
|
|
158
|
+
discard: "放弃",
|
|
159
|
+
unsaved: "未保存",
|
|
160
|
+
loading: "加载中…",
|
|
161
|
+
unavailable: "配置服务不可用。",
|
|
162
|
+
readOnly: "当前部署无 settings provider,配置为只读;请编辑 settings.yaml。",
|
|
163
|
+
invalidNumber: "请输入正整数。",
|
|
164
|
+
collapse: "收起",
|
|
165
|
+
expand: "展开",
|
|
166
|
+
enabledOn: "已启用",
|
|
167
|
+
enabledOff: "未启用",
|
|
168
|
+
saveFailed: "保存失败,请检查填写内容。",
|
|
169
|
+
confirm: "确认",
|
|
170
|
+
cancel: "取消"
|
|
171
|
+
};
|
|
172
|
+
const commonEn = {
|
|
173
|
+
save: "Save",
|
|
174
|
+
saving: "Saving…",
|
|
175
|
+
discard: "Discard",
|
|
176
|
+
unsaved: "Unsaved",
|
|
177
|
+
loading: "Loading…",
|
|
178
|
+
unavailable: "Settings service unavailable.",
|
|
179
|
+
readOnly: "No settings provider in this deployment; config is read-only. Edit settings.yaml.",
|
|
180
|
+
invalidNumber: "Enter a positive integer.",
|
|
181
|
+
collapse: "Collapse",
|
|
182
|
+
expand: "Expand",
|
|
183
|
+
enabledOn: "On",
|
|
184
|
+
enabledOff: "Off",
|
|
185
|
+
saveFailed: "Save failed; check the fields.",
|
|
186
|
+
confirm: "Confirm",
|
|
187
|
+
cancel: "Cancel"
|
|
188
|
+
};
|
|
189
|
+
/** own 覆盖同键合并(返回新对象,入参不可变)。 */
|
|
190
|
+
function mergeDict(base, own) {
|
|
191
|
+
return {
|
|
192
|
+
...base,
|
|
193
|
+
...own
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
//#endregion
|
|
197
|
+
//#region ../shared/src/client/scope.ts
|
|
198
|
+
/**
|
|
199
|
+
* api 直连实现的命名空间 scope(官方配置页 tab 同款模式)。
|
|
200
|
+
* 读:describe 取命名空间视图(脱敏 value/revision + 顶层 writable);
|
|
201
|
+
* 刷新:remote `settings/document-updated`(按命名空间过滤)与
|
|
202
|
+
* `connection/reset`;generation 防旧读覆盖新发布。任何页面 origin
|
|
203
|
+
* (loopback 或 tailnet 信任域名)下行为一致。
|
|
204
|
+
* effect 归属调用方 fiber(label 需含插件名以便诊断)。
|
|
205
|
+
*/
|
|
206
|
+
function createApiScope(api, ns, c, label) {
|
|
207
|
+
let state = {
|
|
208
|
+
status: "loading",
|
|
209
|
+
value: void 0,
|
|
210
|
+
revision: void 0,
|
|
211
|
+
writable: false
|
|
212
|
+
};
|
|
213
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
214
|
+
let generation = 0;
|
|
215
|
+
const publish = (next) => {
|
|
216
|
+
state = next;
|
|
217
|
+
for (const listener of [...listeners]) listener();
|
|
218
|
+
};
|
|
219
|
+
const load = async () => {
|
|
220
|
+
const gen = ++generation;
|
|
221
|
+
let response;
|
|
222
|
+
try {
|
|
223
|
+
response = await api.describe({});
|
|
224
|
+
} catch {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (gen !== generation || !response.result.ok) return;
|
|
228
|
+
const writable = response.result.value?.writable ?? false;
|
|
229
|
+
const view = response.result.value?.namespaces.find((candidate) => candidate.ns === ns);
|
|
230
|
+
if (view === void 0) {
|
|
231
|
+
publish({
|
|
232
|
+
status: "unavailable",
|
|
233
|
+
value: void 0,
|
|
234
|
+
revision: void 0,
|
|
235
|
+
writable
|
|
236
|
+
});
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
publish({
|
|
240
|
+
status: "ready",
|
|
241
|
+
value: view.value,
|
|
242
|
+
revision: view.revision,
|
|
243
|
+
writable
|
|
244
|
+
});
|
|
245
|
+
};
|
|
246
|
+
const refresh = (namespace) => {
|
|
247
|
+
if (namespace !== void 0 && namespace !== ns) return;
|
|
248
|
+
load();
|
|
249
|
+
};
|
|
250
|
+
const disposers = [c.get("remote").$on("settings/document-updated", refresh), c.on("connection/reset", () => {
|
|
251
|
+
load();
|
|
252
|
+
})];
|
|
253
|
+
c.effect(() => () => {
|
|
254
|
+
for (const dispose of disposers) dispose();
|
|
255
|
+
}, label);
|
|
256
|
+
load();
|
|
257
|
+
return {
|
|
258
|
+
getSnapshot: () => state,
|
|
259
|
+
subscribe: (listener) => {
|
|
260
|
+
listeners.add(listener);
|
|
261
|
+
return () => {
|
|
262
|
+
listeners.delete(listener);
|
|
263
|
+
};
|
|
264
|
+
},
|
|
265
|
+
load
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
//#endregion
|
|
269
|
+
//#region ../shared/src/client/styles.ts
|
|
270
|
+
/**
|
|
271
|
+
* 配置卡片样式工厂(各插件 client 的公共收编版)。
|
|
272
|
+
* 视觉规则与迁移前逐字一致(类名前缀换为各插件自身,DOM 类名不变),
|
|
273
|
+
* 另补移动端与可达性增强(767px 断点 + pointer:coarse,对齐 ui-mobile-fit 约定):
|
|
274
|
+
* - 窄屏 input/textarea 16px(防 iOS 聚焦缩放)、可点目标 ≥44px;
|
|
275
|
+
* - footer 按钮等宽换行、状态文字独占一行;
|
|
276
|
+
* - 展开态 body 底部 sticky 保存栏(长表单滚动中始终可达)。
|
|
277
|
+
* 沿用官方 data-plugin / data-plugin-css 约定(HMR 据此卸载),不覆盖上游选择器。
|
|
278
|
+
* @module @dsh-plus/shared/client/styles
|
|
279
|
+
*/
|
|
280
|
+
/** 生成全套卡片 CSS;prefix 为插件类名前缀(如 'dne'),extra 为插件特有规则。 */
|
|
281
|
+
function cardCss(prefix, extra = "") {
|
|
282
|
+
return `
|
|
283
|
+
.${prefix}-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}
|
|
284
|
+
.${prefix}-card:hover{border-color:var(--dsw-alias-label-dimmed)}
|
|
285
|
+
.${prefix}-cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}
|
|
286
|
+
.${prefix}-header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}
|
|
287
|
+
.${prefix}-header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}
|
|
288
|
+
.${prefix}-headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}
|
|
289
|
+
.${prefix}-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}
|
|
290
|
+
.${prefix}-description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}
|
|
291
|
+
.${prefix}-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s;display:inline-flex}
|
|
292
|
+
.${prefix}-chevronOpen{transform:rotate(180deg)}
|
|
293
|
+
.${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}
|
|
294
|
+
.${prefix}-statusBadge{white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
295
|
+
.${prefix}-statusOn{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
|
|
296
|
+
.${prefix}-statusOff{color:var(--dsw-alias-label-tertiary)}
|
|
297
|
+
.${prefix}-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}
|
|
298
|
+
.${prefix}-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
|
|
299
|
+
.${prefix}-field+.${prefix}-field{border-top:1px solid var(--dsw-alias-border-l2)}
|
|
300
|
+
.${prefix}-head{align-items:center;gap:8px;display:flex}
|
|
301
|
+
.${prefix}-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}
|
|
302
|
+
.${prefix}-badge{white-space:nowrap;border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
303
|
+
.${prefix}-badgeSet{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
|
|
304
|
+
.${prefix}-badgeUnset{color:var(--dsw-alias-label-tertiary)}
|
|
305
|
+
.${prefix}-input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;transition:border-color .18s,background .18s}
|
|
306
|
+
.${prefix}-input:hover:not(:disabled):not(:focus-visible){border-color:var(--dsw-alias-label-dimmed)}
|
|
307
|
+
.${prefix}-input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}
|
|
308
|
+
.${prefix}-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
|
|
309
|
+
.${prefix}-inputInvalid{border-color:var(--dsw-alias-label-error)}
|
|
310
|
+
.${prefix}-select{appearance:none;background:var(--dsw-alias-bg-module-platform);border:none;cursor:pointer;background-image:linear-gradient(45deg,transparent 50%,var(--dsw-alias-label-secondary) 50%),linear-gradient(135deg,var(--dsw-alias-label-secondary) 50%,transparent 50%);background-position:calc(100% - 16px) 50%,calc(100% - 11px) 50%;background-size:5px 5px;background-repeat:no-repeat;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;height:34px;box-sizing:border-box;border-radius:8px;padding:0 30px 0 12px;transition:background-color .18s}
|
|
311
|
+
.${prefix}-select:hover:not(:disabled){background-color:var(--dsw-alias-interactive-bg-hover)}
|
|
312
|
+
.${prefix}-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
|
|
313
|
+
.${prefix}-invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}
|
|
314
|
+
.${prefix}-checkRow{align-items:center;gap:10px;display:flex;padding:6px 0}
|
|
315
|
+
.${prefix}-checkRow label{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5;cursor:pointer;flex:1;min-width:0}
|
|
316
|
+
/* 自绘圆角扁平开关(官方 UI 无可见原生 checkbox,选择类全走胶囊/菜单) */
|
|
317
|
+
.${prefix}-checkRow input{appearance:none;-webkit-appearance:none;position:relative;box-sizing:border-box;background:var(--dsw-alias-bg-module-platform);border:none;border-radius:999px;width:34px;height:20px;flex:none;cursor:pointer;transition:background .18s;margin:0}
|
|
318
|
+
.${prefix}-checkRow input::after{content:"";position:absolute;top:2px;left:2px;background:var(--dsw-alias-label-primary);border-radius:999px;width:16px;height:16px;transition:left .18s cubic-bezier(.4,0,.2,1)}
|
|
319
|
+
.${prefix}-checkRow input:checked{background:var(--dsw-alias-brand-primary)}
|
|
320
|
+
.${prefix}-checkRow input:checked::after{left:16px;background:var(--dsw-alias-bg-base)}
|
|
321
|
+
.${prefix}-checkRow input:disabled{opacity:.4;cursor:default}
|
|
322
|
+
.${prefix}-checkRow input:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
|
|
323
|
+
.${prefix}-groupLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;padding:12px 0 2px;margin:0}
|
|
324
|
+
.${prefix}-readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}
|
|
325
|
+
.${prefix}-warn{color:var(--dsw-alias-label-error);margin:12px 0 0;font-size:12px;line-height:1.5}
|
|
326
|
+
.${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}
|
|
327
|
+
.${prefix}-status{min-width:0;color:var(--dsw-alias-label-secondary);flex:1;margin:0;font-size:12px;line-height:1.5}
|
|
328
|
+
.${prefix}-statusError{color:var(--dsw-alias-label-error)}
|
|
329
|
+
.${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}
|
|
330
|
+
.${prefix}-btnGhost{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}
|
|
331
|
+
.${prefix}-btnGhost:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}
|
|
332
|
+
.${prefix}-btnPrimary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}
|
|
333
|
+
.${prefix}-btn:disabled{opacity:.4;cursor:default}
|
|
334
|
+
.${prefix}-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
|
|
335
|
+
@media (max-width:767px){
|
|
336
|
+
.${prefix}-body{margin:0 10px}
|
|
337
|
+
.${prefix}-header{padding:12px 12px}
|
|
338
|
+
.${prefix}-input,.${prefix}-select{font-size:16px;height:40px}
|
|
339
|
+
.${prefix}-checkRow{min-height:44px;padding:8px 0}
|
|
340
|
+
.${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}
|
|
341
|
+
.${prefix}-status{flex-basis:100%;flex:none}
|
|
342
|
+
.${prefix}-btn{min-height:44px;flex:1;min-width:96px}
|
|
343
|
+
.${prefix}-statusBadge{display:none}
|
|
344
|
+
}
|
|
345
|
+
@media (pointer:coarse){
|
|
346
|
+
.${prefix}-btn{min-height:44px}
|
|
347
|
+
.${prefix}-checkRow{min-height:44px}
|
|
348
|
+
.${prefix}-input,.${prefix}-select{height:40px}
|
|
349
|
+
}
|
|
350
|
+
${extra}
|
|
351
|
+
`;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* 幂等注入样式标签(官方 data-plugin-css 约定);返回新标签,
|
|
355
|
+
* 已存在或环境无 document 时返回 null(调用方 effect 卸载用)。
|
|
356
|
+
*/
|
|
357
|
+
function injectCardStyle(pluginId, css) {
|
|
358
|
+
if (typeof document === "undefined") return null;
|
|
359
|
+
const tagId = `${pluginId}/card.css`;
|
|
360
|
+
if (document.querySelector(`style[data-plugin-css=${JSON.stringify(tagId)}]`) !== null) return null;
|
|
361
|
+
const tag = document.createElement("style");
|
|
362
|
+
tag.dataset.plugin = pluginId;
|
|
363
|
+
tag.dataset.pluginCss = tagId;
|
|
364
|
+
tag.textContent = css;
|
|
365
|
+
document.head.appendChild(tag);
|
|
366
|
+
return tag;
|
|
367
|
+
}
|
|
368
|
+
//#endregion
|
|
9
369
|
//#region src/ns.ts
|
|
10
370
|
/**
|
|
11
371
|
* settings 命名空间字面量(纯常量,零依赖,浏览器半可安全引入)。
|
|
@@ -318,8 +678,9 @@ window.__ModuleLoader__.load({
|
|
|
318
678
|
//#endregion
|
|
319
679
|
//#region src/client/fields.tsx
|
|
320
680
|
/**
|
|
321
|
-
*
|
|
322
|
-
*
|
|
681
|
+
* llm-pi 特有卡片控件:可折叠小节、键值对编辑、JSON 文本框、带 datalist 的
|
|
682
|
+
* 文本字段。通用控件(TextField/CheckRow)走 @dsh-plus/shared/client;
|
|
683
|
+
* 本文件的 SelectField 是 llm-pi 的窄变体(readonly string[] 选项 + 未设置项)。
|
|
323
684
|
* @module llm-pi/client/fields
|
|
324
685
|
*/
|
|
325
686
|
function CollapseSection(props) {
|
|
@@ -332,10 +693,7 @@ window.__ModuleLoader__.load({
|
|
|
332
693
|
id: props.id,
|
|
333
694
|
"aria-expanded": open,
|
|
334
695
|
onClick: () => setOpen(!open),
|
|
335
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
336
|
-
className: `lpc-chevron${open ? " lpc-chevronOpen" : ""}`,
|
|
337
|
-
children: "▾"
|
|
338
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
696
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDownIcon, { className: `lpc-chevron${open ? " lpc-chevronOpen" : ""}` }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
339
697
|
className: "lpc-collapseTitle",
|
|
340
698
|
children: props.title
|
|
341
699
|
})]
|
|
@@ -376,21 +734,6 @@ window.__ModuleLoader__.load({
|
|
|
376
734
|
]
|
|
377
735
|
});
|
|
378
736
|
}
|
|
379
|
-
function CheckRow(props) {
|
|
380
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
381
|
-
className: "lpc-checkRow",
|
|
382
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
383
|
-
id: props.id,
|
|
384
|
-
type: "checkbox",
|
|
385
|
-
checked: props.checked,
|
|
386
|
-
disabled: props.disabled === true,
|
|
387
|
-
onChange: (event) => props.onEdit(event.target.checked)
|
|
388
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
389
|
-
htmlFor: props.id,
|
|
390
|
-
children: props.label
|
|
391
|
-
})]
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
737
|
function SelectField(props) {
|
|
395
738
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
396
739
|
className: `lpc-field${props.wide === true ? " lpc-wide" : ""}`,
|
|
@@ -418,17 +761,8 @@ window.__ModuleLoader__.load({
|
|
|
418
761
|
});
|
|
419
762
|
}
|
|
420
763
|
function KeyValueEditor(props) {
|
|
421
|
-
const update = (index, patch) => {
|
|
422
|
-
props.onEdit(props.pairs.map((pair, i) => i === index ? {
|
|
423
|
-
...pair,
|
|
424
|
-
...patch
|
|
425
|
-
} : pair));
|
|
426
|
-
};
|
|
427
|
-
const remove = (index) => {
|
|
428
|
-
props.onEdit(props.pairs.filter((_, i) => i !== index));
|
|
429
|
-
};
|
|
430
764
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
431
|
-
className: "lpc-field
|
|
765
|
+
className: "lpc-field",
|
|
432
766
|
children: [
|
|
433
767
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
434
768
|
className: "lpc-head",
|
|
@@ -442,29 +776,45 @@ window.__ModuleLoader__.load({
|
|
|
442
776
|
className: "lpc-kvRow",
|
|
443
777
|
children: [
|
|
444
778
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
445
|
-
id: index === 0 ? props.id : void 0,
|
|
446
|
-
className: "lpc-input",
|
|
447
|
-
value: pair.key,
|
|
448
779
|
placeholder: props.keyPlaceholder,
|
|
780
|
+
className: "lpc-input lpc-kvKey",
|
|
781
|
+
type: "text",
|
|
782
|
+
value: pair.key,
|
|
449
783
|
disabled: props.disabled === true,
|
|
450
|
-
onChange: (event) =>
|
|
784
|
+
onChange: (event) => {
|
|
785
|
+
const next = [...props.pairs];
|
|
786
|
+
next[index] = {
|
|
787
|
+
...pair,
|
|
788
|
+
key: event.target.value
|
|
789
|
+
};
|
|
790
|
+
props.onEdit(next);
|
|
791
|
+
}
|
|
451
792
|
}),
|
|
452
793
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
453
|
-
className: "lpc-input",
|
|
454
|
-
value: pair.value,
|
|
455
794
|
placeholder: props.valuePlaceholder,
|
|
795
|
+
className: "lpc-input lpc-kvValue",
|
|
796
|
+
type: "text",
|
|
797
|
+
value: pair.value,
|
|
456
798
|
disabled: props.disabled === true,
|
|
457
|
-
onChange: (event) =>
|
|
799
|
+
onChange: (event) => {
|
|
800
|
+
const next = [...props.pairs];
|
|
801
|
+
next[index] = {
|
|
802
|
+
...pair,
|
|
803
|
+
value: event.target.value
|
|
804
|
+
};
|
|
805
|
+
props.onEdit(next);
|
|
806
|
+
}
|
|
458
807
|
}),
|
|
459
808
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
460
809
|
type: "button",
|
|
461
810
|
className: "lpc-btn lpc-btnGhost lpc-btnSmall",
|
|
462
811
|
disabled: props.disabled === true,
|
|
463
|
-
|
|
464
|
-
|
|
812
|
+
"aria-label": props.removeLabel,
|
|
813
|
+
onClick: () => props.onEdit(props.pairs.filter((_, i) => i !== index)),
|
|
814
|
+
children: "✕"
|
|
465
815
|
})
|
|
466
816
|
]
|
|
467
|
-
}, index)),
|
|
817
|
+
}, `${props.id}-${index}`)),
|
|
468
818
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
469
819
|
className: "lpc-kvAdd",
|
|
470
820
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -940,6 +1290,7 @@ window.__ModuleLoader__.load({
|
|
|
940
1290
|
className: "lpc-label",
|
|
941
1291
|
children: t("input")
|
|
942
1292
|
}), MODALITIES.map((modality) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
|
|
1293
|
+
prefix: "lpc",
|
|
943
1294
|
id: `${id}-input-${modality}`,
|
|
944
1295
|
label: modality,
|
|
945
1296
|
checked: model.input[modality],
|
|
@@ -1021,6 +1372,12 @@ window.__ModuleLoader__.load({
|
|
|
1021
1372
|
}
|
|
1022
1373
|
//#endregion
|
|
1023
1374
|
//#region src/client/views/provider-fields.tsx
|
|
1375
|
+
/**
|
|
1376
|
+
* Provider route 的标量字段组:基础字段网格(继承/显示名/端点/凭据/默认容量/
|
|
1377
|
+
* 超时/默认模态/预算)与协议/档位/缓存/传输下拉组。compat、headers、
|
|
1378
|
+
* retryPolicy 与模型目录在 views/providers.tsx 的 route 小节内另行渲染。
|
|
1379
|
+
* @module llm-pi/client/views/provider-fields
|
|
1380
|
+
*/
|
|
1024
1381
|
function ProviderScalarFields(props) {
|
|
1025
1382
|
const { draft, t } = props;
|
|
1026
1383
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1111,6 +1468,7 @@ window.__ModuleLoader__.load({
|
|
|
1111
1468
|
className: "lpc-label",
|
|
1112
1469
|
children: t("defaultInput")
|
|
1113
1470
|
}), MODALITIES.map((modality) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
|
|
1471
|
+
prefix: "lpc",
|
|
1114
1472
|
id: `${props.id}-input-${modality}`,
|
|
1115
1473
|
label: modality,
|
|
1116
1474
|
checked: draft.input[modality],
|
|
@@ -1281,10 +1639,7 @@ window.__ModuleLoader__.load({
|
|
|
1281
1639
|
"aria-expanded": open,
|
|
1282
1640
|
onClick: () => setOpen(!open),
|
|
1283
1641
|
children: [
|
|
1284
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
1285
|
-
className: `lpc-chevron${open ? " lpc-chevronOpen" : ""}`,
|
|
1286
|
-
children: "▾"
|
|
1287
|
-
}),
|
|
1642
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDownIcon, { className: `lpc-chevron${open ? " lpc-chevronOpen" : ""}` }),
|
|
1288
1643
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1289
1644
|
className: "lpc-routeKey",
|
|
1290
1645
|
children: route
|
|
@@ -1368,14 +1723,9 @@ window.__ModuleLoader__.load({
|
|
|
1368
1723
|
* 顶部:enabled / catalogUrl / catalogRefreshHours / 只读状态行(kitSource、
|
|
1369
1724
|
* modelsDevStatus,来自模型目录端点)+ 保存(settings.replace 全量)与
|
|
1370
1725
|
* 错误/成功提示;下方为 providers 路由列表(新增/删除/字段编辑/compat/模型
|
|
1371
|
-
* 目录,见 views
|
|
1372
|
-
* 交互对齐官方卡片与 notify-email:折叠/展开、staged draft、未保存标记。
|
|
1726
|
+
* 目录,见 views/)。外壳与基础控件走 @dsh-plus/shared/client 套件。
|
|
1373
1727
|
* @module llm-pi/client/card
|
|
1374
1728
|
*/
|
|
1375
|
-
const IDLE_STATUS = {
|
|
1376
|
-
kind: "idle",
|
|
1377
|
-
text: ""
|
|
1378
|
-
};
|
|
1379
1729
|
function modelsDevText(status, t) {
|
|
1380
1730
|
if (status === null) return t("modelsDevEmpty");
|
|
1381
1731
|
if (status.error !== null) return `${t("modelsDevError")}${status.error}`;
|
|
@@ -1501,167 +1851,141 @@ window.__ModuleLoader__.load({
|
|
|
1501
1851
|
}).finally(() => setRefreshing(false));
|
|
1502
1852
|
};
|
|
1503
1853
|
const disabled = !snapshot.writable;
|
|
1504
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
disabled
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
}),
|
|
1629
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1630
|
-
className: "lpc-footer",
|
|
1631
|
-
children: [
|
|
1632
|
-
status.kind !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1633
|
-
className: `lpc-status${status.kind === "error" ? " lpc-statusError" : ""}`,
|
|
1634
|
-
role: "status",
|
|
1635
|
-
children: status.text
|
|
1636
|
-
}) : null,
|
|
1637
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1638
|
-
type: "button",
|
|
1639
|
-
className: "lpc-btn lpc-btnGhost",
|
|
1640
|
-
disabled: !dirty || saving,
|
|
1641
|
-
onClick: onDiscard,
|
|
1642
|
-
children: t("discard")
|
|
1643
|
-
}),
|
|
1644
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1645
|
-
type: "button",
|
|
1646
|
-
className: "lpc-btn lpc-btnPrimary",
|
|
1647
|
-
disabled: !dirty || invalid || saving || disabled,
|
|
1648
|
-
onClick: onSave,
|
|
1649
|
-
children: t(saving ? "saving" : "save")
|
|
1650
|
-
})
|
|
1651
|
-
]
|
|
1652
|
-
})
|
|
1653
|
-
]
|
|
1654
|
-
}) : null]
|
|
1854
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CardChrome, {
|
|
1855
|
+
prefix: "lpc",
|
|
1856
|
+
title: t("title"),
|
|
1857
|
+
description: t("description"),
|
|
1858
|
+
open,
|
|
1859
|
+
onToggle: setOpen,
|
|
1860
|
+
statusBadge: {
|
|
1861
|
+
text: t(draft.enabled ? "enabledOn" : "enabledOff"),
|
|
1862
|
+
on: draft.enabled
|
|
1863
|
+
},
|
|
1864
|
+
dirty,
|
|
1865
|
+
dirtyLabel: t("unsaved"),
|
|
1866
|
+
readOnlyNotice: disabled ? t("readOnly") : void 0,
|
|
1867
|
+
status,
|
|
1868
|
+
actions: [
|
|
1869
|
+
{
|
|
1870
|
+
key: "refresh",
|
|
1871
|
+
label: t(refreshing ? "refreshingCatalog" : "refreshCatalog"),
|
|
1872
|
+
disabled: disabled || refreshing,
|
|
1873
|
+
onClick: onRefreshCatalog
|
|
1874
|
+
},
|
|
1875
|
+
{
|
|
1876
|
+
key: "discard",
|
|
1877
|
+
label: t("discard"),
|
|
1878
|
+
disabled: !dirty || saving,
|
|
1879
|
+
onClick: onDiscard
|
|
1880
|
+
},
|
|
1881
|
+
{
|
|
1882
|
+
key: "save",
|
|
1883
|
+
label: t(saving ? "saving" : "save"),
|
|
1884
|
+
variant: "primary",
|
|
1885
|
+
disabled: !dirty || invalid || saving || disabled,
|
|
1886
|
+
onClick: onSave
|
|
1887
|
+
}
|
|
1888
|
+
],
|
|
1889
|
+
children: [
|
|
1890
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
|
|
1891
|
+
prefix: "lpc",
|
|
1892
|
+
id: "lpc-enabled",
|
|
1893
|
+
label: t("enabled"),
|
|
1894
|
+
checked: draft.enabled,
|
|
1895
|
+
disabled,
|
|
1896
|
+
onEdit: (value) => {
|
|
1897
|
+
setDraft({
|
|
1898
|
+
...draft,
|
|
1899
|
+
enabled: value
|
|
1900
|
+
});
|
|
1901
|
+
setStatus(IDLE_STATUS);
|
|
1902
|
+
}
|
|
1903
|
+
}),
|
|
1904
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField$1, {
|
|
1905
|
+
prefix: "lpc",
|
|
1906
|
+
id: "lpc-catalogUrl",
|
|
1907
|
+
label: t("catalogUrl"),
|
|
1908
|
+
hint: t("catalogUrlHint"),
|
|
1909
|
+
value: draft.catalogUrl,
|
|
1910
|
+
disabled,
|
|
1911
|
+
onEdit: (value) => {
|
|
1912
|
+
setDraft({
|
|
1913
|
+
...draft,
|
|
1914
|
+
catalogUrl: value
|
|
1915
|
+
});
|
|
1916
|
+
setStatus(IDLE_STATUS);
|
|
1917
|
+
}
|
|
1918
|
+
}),
|
|
1919
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField$1, {
|
|
1920
|
+
prefix: "lpc",
|
|
1921
|
+
id: "lpc-catalogRefresh",
|
|
1922
|
+
label: t("catalogRefreshHours"),
|
|
1923
|
+
hint: t("catalogRefreshHoursHint"),
|
|
1924
|
+
value: draft.catalogRefreshHours,
|
|
1925
|
+
numeric: true,
|
|
1926
|
+
disabled,
|
|
1927
|
+
invalid: !numTextOk(draft.catalogRefreshHours),
|
|
1928
|
+
invalidLabel: t("invalidNumber"),
|
|
1929
|
+
onEdit: (value) => {
|
|
1930
|
+
setDraft({
|
|
1931
|
+
...draft,
|
|
1932
|
+
catalogRefreshHours: value
|
|
1933
|
+
});
|
|
1934
|
+
setStatus(IDLE_STATUS);
|
|
1935
|
+
}
|
|
1936
|
+
}),
|
|
1937
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField$1, {
|
|
1938
|
+
prefix: "lpc",
|
|
1939
|
+
id: "lpc-catalogProxy",
|
|
1940
|
+
label: t("catalogProxy"),
|
|
1941
|
+
hint: t("catalogProxyHint"),
|
|
1942
|
+
value: draft.catalogProxy,
|
|
1943
|
+
disabled,
|
|
1944
|
+
onEdit: (value) => {
|
|
1945
|
+
setDraft({
|
|
1946
|
+
...draft,
|
|
1947
|
+
catalogProxy: value
|
|
1948
|
+
});
|
|
1949
|
+
setStatus(IDLE_STATUS);
|
|
1950
|
+
}
|
|
1951
|
+
}),
|
|
1952
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1953
|
+
className: "lpc-statusRow",
|
|
1954
|
+
children: [
|
|
1955
|
+
t("kitSource"),
|
|
1956
|
+
":",
|
|
1957
|
+
kitSource ?? ""
|
|
1958
|
+
]
|
|
1959
|
+
}),
|
|
1960
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1961
|
+
className: "lpc-statusRow",
|
|
1962
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1963
|
+
t("modelsDevStatus"),
|
|
1964
|
+
":",
|
|
1965
|
+
modelsDevText(modelsDevStatus, t)
|
|
1966
|
+
] })
|
|
1967
|
+
}),
|
|
1968
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProvidersSection, {
|
|
1969
|
+
providers: draft.providers,
|
|
1970
|
+
epoch,
|
|
1971
|
+
disabled,
|
|
1972
|
+
t,
|
|
1973
|
+
onAddRoute,
|
|
1974
|
+
onRemoveRoute,
|
|
1975
|
+
onPatchProvider: setProvider
|
|
1976
|
+
})
|
|
1977
|
+
]
|
|
1655
1978
|
});
|
|
1656
1979
|
}
|
|
1657
1980
|
//#endregion
|
|
1658
1981
|
//#region src/client/i18n.ts
|
|
1659
1982
|
/**
|
|
1660
1983
|
* 配置卡片文案(zh/en)。经 ctx.locale.register 注册、bind 取用,与官方卡片同机制。
|
|
1984
|
+
* 公共键(save/discard/unsaved 等)来自 shared 的 common 字典,本文件只维护业务键。
|
|
1661
1985
|
* @module llm-pi/client/i18n
|
|
1662
1986
|
*/
|
|
1663
1987
|
const NS = "dsh-plus-llm-pi";
|
|
1664
|
-
const
|
|
1988
|
+
const ownZh = {
|
|
1665
1989
|
title: "LLM 路由(llm-pi)",
|
|
1666
1990
|
description: "自定义 LLM 路由:协议、compat、模型目录与 models.dev 目录兜底。",
|
|
1667
1991
|
enabled: "启用插件",
|
|
@@ -1738,19 +2062,10 @@ window.__ModuleLoader__.load({
|
|
|
1738
2062
|
catalogProvider: "候选 provider",
|
|
1739
2063
|
catalogLoading: "目录加载中…",
|
|
1740
2064
|
catalogFailed: "目录加载失败。",
|
|
1741
|
-
save: "保存",
|
|
1742
|
-
saving: "保存中…",
|
|
1743
|
-
discard: "放弃",
|
|
1744
|
-
unsaved: "未保存",
|
|
1745
2065
|
saveOk: "已保存。",
|
|
1746
|
-
saveFailed: "保存失败:"
|
|
1747
|
-
loading: "加载中…",
|
|
1748
|
-
readOnly: "当前部署无 settings provider,配置为只读;请编辑 settings.yaml。",
|
|
1749
|
-
expand: "展开",
|
|
1750
|
-
collapse: "收起",
|
|
1751
|
-
invalidNumber: "请输入有效数字"
|
|
2066
|
+
saveFailed: "保存失败:"
|
|
1752
2067
|
};
|
|
1753
|
-
const
|
|
2068
|
+
const ownEn = {
|
|
1754
2069
|
title: "LLM routes (llm-pi)",
|
|
1755
2070
|
description: "Custom LLM routes: protocol, compat, model catalog and models.dev fallback.",
|
|
1756
2071
|
enabled: "Enable plugin",
|
|
@@ -1827,67 +2142,29 @@ window.__ModuleLoader__.load({
|
|
|
1827
2142
|
catalogProvider: "Candidate provider",
|
|
1828
2143
|
catalogLoading: "Loading catalog…",
|
|
1829
2144
|
catalogFailed: "Failed to load catalog.",
|
|
1830
|
-
save: "Save",
|
|
1831
|
-
saving: "Saving…",
|
|
1832
|
-
discard: "Discard",
|
|
1833
|
-
unsaved: "Unsaved",
|
|
1834
2145
|
saveOk: "Saved.",
|
|
1835
|
-
saveFailed: "Save failed: "
|
|
1836
|
-
loading: "Loading…",
|
|
1837
|
-
readOnly: "No settings provider in this deployment; edit settings.yaml instead.",
|
|
1838
|
-
expand: "Expand",
|
|
1839
|
-
collapse: "Collapse",
|
|
1840
|
-
invalidNumber: "Enter a valid number"
|
|
2146
|
+
saveFailed: "Save failed: "
|
|
1841
2147
|
};
|
|
2148
|
+
const zh = mergeDict(commonZh, ownZh);
|
|
2149
|
+
const en = mergeDict(commonEn, ownEn);
|
|
1842
2150
|
//#endregion
|
|
1843
2151
|
//#region src/client/styles.ts
|
|
1844
2152
|
/**
|
|
1845
|
-
*
|
|
1846
|
-
*
|
|
2153
|
+
* 配置卡片样式:基础规则走 @dsh-plus/shared/client 的 cardCss('lpc'),
|
|
2154
|
+
* 本文件补插件特有规则(route/model 折叠块、键值对行、网格、JSON 文本框)
|
|
2155
|
+
* 及窄屏响应式:views 的双列网格在窄屏转单列、route 头部按钮换行、
|
|
2156
|
+
* 键值对行纵向堆叠、文本框 16px 防 iOS 缩放。
|
|
2157
|
+
* 沿用官方 data-plugin / data-plugin-css 约定(HMR 据此卸载)。
|
|
1847
2158
|
* @module llm-pi/client/styles
|
|
1848
2159
|
*/
|
|
1849
2160
|
const PLUGIN_ID = "@dsh-plus/llm-pi";
|
|
1850
|
-
const
|
|
1851
|
-
|
|
1852
|
-
.lpc-
|
|
1853
|
-
.lpc-card:hover{border-color:var(--dsw-alias-label-dimmed)}
|
|
1854
|
-
.lpc-cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}
|
|
1855
|
-
.lpc-header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}
|
|
1856
|
-
.lpc-header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}
|
|
1857
|
-
.lpc-headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}
|
|
1858
|
-
.lpc-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}
|
|
1859
|
-
.lpc-description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}
|
|
1860
|
-
.lpc-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s;font-size:12px}
|
|
1861
|
-
.lpc-chevronOpen{transform:rotate(180deg)}
|
|
1862
|
-
.lpc-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}
|
|
1863
|
-
.lpc-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}
|
|
1864
|
-
.lpc-field{flex-direction:column;gap:6px;padding:12px 0;display:flex;min-width:0}
|
|
1865
|
-
.lpc-field+.lpc-field{border-top:1px solid var(--dsw-alias-border-l2)}
|
|
1866
|
-
.lpc-head{align-items:center;gap:8px;display:flex}
|
|
1867
|
-
.lpc-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}
|
|
1868
|
-
.lpc-input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;width:100%;box-sizing:border-box;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px}
|
|
1869
|
-
.lpc-input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}
|
|
1870
|
-
.lpc-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
|
|
1871
|
-
.lpc-inputInvalid{border-color:var(--dsw-alias-label-error)}
|
|
1872
|
-
.lpc-select{appearance:none}
|
|
2161
|
+
const cardCssAll = cardCss("lpc", `
|
|
2162
|
+
.lpc-input{width:100%;box-sizing:border-box}
|
|
2163
|
+
.lpc-select{appearance:none;cursor:pointer;background-image:linear-gradient(45deg,transparent 50%,var(--dsw-alias-label-secondary) 50%),linear-gradient(135deg,var(--dsw-alias-label-secondary) 50%,transparent 50%);background-position:calc(100% - 16px) 50%,calc(100% - 11px) 50%;background-size:5px 5px;background-repeat:no-repeat;padding-right:30px}
|
|
1873
2164
|
.lpc-textarea{height:auto;min-height:72px;padding:8px 12px;line-height:1.5;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;resize:vertical}
|
|
1874
|
-
.lpc-
|
|
1875
|
-
.lpc-invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}
|
|
1876
|
-
.lpc-checkRow{align-items:center;gap:8px;display:flex;padding:3px 0}
|
|
1877
|
-
.lpc-checkRow input{accent-color:var(--dsw-alias-brand-primary)}
|
|
1878
|
-
.lpc-checkRow label{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5;cursor:pointer}
|
|
1879
|
-
.lpc-groupLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;padding:14px 0 2px;margin:0}
|
|
1880
|
-
.lpc-readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}
|
|
2165
|
+
.lpc-checkRow{padding:3px 0}
|
|
1881
2166
|
.lpc-statusRow{color:var(--dsw-alias-label-tertiary);margin:6px 0 0;font-size:12px;line-height:1.6;word-break:break-all}
|
|
1882
|
-
.lpc-
|
|
1883
|
-
.lpc-status{min-width:0;color:var(--dsw-alias-label-secondary);flex:1;margin:0;font-size:12px;line-height:1.5}
|
|
1884
|
-
.lpc-statusError{color:var(--dsw-alias-label-error)}
|
|
1885
|
-
.lpc-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5;flex:none}
|
|
1886
|
-
.lpc-btnGhost{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}
|
|
1887
|
-
.lpc-btnGhost:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}
|
|
1888
|
-
.lpc-btnPrimary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}
|
|
1889
|
-
.lpc-btn:disabled{opacity:.4;cursor:default}
|
|
1890
|
-
.lpc-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
|
|
2167
|
+
.lpc-btn{flex:none}
|
|
1891
2168
|
.lpc-btnSmall{padding:2px 10px;font-size:12px}
|
|
1892
2169
|
.lpc-grid{display:grid;grid-template-columns:1fr 1fr;gap:0 16px}
|
|
1893
2170
|
.lpc-gridNested{margin-top:2px}
|
|
@@ -1916,17 +2193,23 @@ window.__ModuleLoader__.load({
|
|
|
1916
2193
|
.lpc-collapseTitle{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}
|
|
1917
2194
|
.lpc-collapseBody{border-top:1px dashed var(--dsw-alias-border-l2);padding-bottom:6px}
|
|
1918
2195
|
.lpc-refreshBtn{margin-left:8px;vertical-align:middle}
|
|
1919
|
-
|
|
2196
|
+
@media (max-width:767px){
|
|
2197
|
+
.lpc-grid{grid-template-columns:1fr}
|
|
2198
|
+
.lpc-routeHead{flex-wrap:wrap}
|
|
2199
|
+
.lpc-routeKey{white-space:normal;word-break:break-all}
|
|
2200
|
+
.lpc-routeBody{margin:0 8px}
|
|
2201
|
+
.lpc-modelRow{padding:0 8px}
|
|
2202
|
+
.lpc-modelHead{flex-wrap:wrap}
|
|
2203
|
+
.lpc-kvRow{flex-direction:column;align-items:stretch}
|
|
2204
|
+
.lpc-kvRow .lpc-btn{align-self:flex-end}
|
|
2205
|
+
.lpc-textarea{font-size:16px}
|
|
2206
|
+
.lpc-statusRow{display:flex;flex-direction:column;gap:4px}
|
|
2207
|
+
.lpc-catalogBar .lpc-input{flex:1;min-width:140px}
|
|
2208
|
+
}
|
|
2209
|
+
`);
|
|
1920
2210
|
/** 幂等注入样式标签;返回标签(已存在或环境无 document 时为 null)。 */
|
|
1921
2211
|
function injectStyle() {
|
|
1922
|
-
|
|
1923
|
-
if (document.querySelector(`style[data-plugin-css=${JSON.stringify(STYLE_TAG_ID)}]`) !== null) return null;
|
|
1924
|
-
const tag = document.createElement("style");
|
|
1925
|
-
tag.dataset.plugin = PLUGIN_ID;
|
|
1926
|
-
tag.dataset.pluginCss = STYLE_TAG_ID;
|
|
1927
|
-
tag.textContent = cardCss;
|
|
1928
|
-
document.head.appendChild(tag);
|
|
1929
|
-
return tag;
|
|
2212
|
+
return injectCardStyle(PLUGIN_ID, cardCssAll);
|
|
1930
2213
|
}
|
|
1931
2214
|
//#endregion
|
|
1932
2215
|
//#region src/client/client.ts
|
|
@@ -1938,75 +2221,6 @@ window.__ModuleLoader__.load({
|
|
|
1938
2221
|
"connection",
|
|
1939
2222
|
"remote"
|
|
1940
2223
|
];
|
|
1941
|
-
/**
|
|
1942
|
-
* api 直连实现的命名空间 scope(官方配置页 tab 同款模式)。
|
|
1943
|
-
* 读:describe 取命名空间视图(脱敏 value/revision + 顶层 writable);
|
|
1944
|
-
* 刷新:remote `settings/document-updated`(按命名空间过滤)与
|
|
1945
|
-
* `connection/reset`;generation 防旧读覆盖新发布。任何页面 origin
|
|
1946
|
-
* (loopback 或 tailnet 信任域名)下行为一致。
|
|
1947
|
-
*/
|
|
1948
|
-
function createApiScope(api, ns, c) {
|
|
1949
|
-
let state = {
|
|
1950
|
-
status: "loading",
|
|
1951
|
-
value: void 0,
|
|
1952
|
-
revision: void 0,
|
|
1953
|
-
writable: false
|
|
1954
|
-
};
|
|
1955
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
1956
|
-
let generation = 0;
|
|
1957
|
-
const publish = (next) => {
|
|
1958
|
-
state = next;
|
|
1959
|
-
for (const listener of [...listeners]) listener();
|
|
1960
|
-
};
|
|
1961
|
-
const load = async () => {
|
|
1962
|
-
const gen = ++generation;
|
|
1963
|
-
let response;
|
|
1964
|
-
try {
|
|
1965
|
-
response = await api.describe({});
|
|
1966
|
-
} catch {
|
|
1967
|
-
return;
|
|
1968
|
-
}
|
|
1969
|
-
if (gen !== generation || !response.result.ok) return;
|
|
1970
|
-
const writable = response.result.value?.writable ?? false;
|
|
1971
|
-
const view = response.result.value?.namespaces.find((candidate) => candidate.ns === ns);
|
|
1972
|
-
if (view === void 0) {
|
|
1973
|
-
publish({
|
|
1974
|
-
status: "unavailable",
|
|
1975
|
-
value: void 0,
|
|
1976
|
-
revision: void 0,
|
|
1977
|
-
writable
|
|
1978
|
-
});
|
|
1979
|
-
return;
|
|
1980
|
-
}
|
|
1981
|
-
publish({
|
|
1982
|
-
status: "ready",
|
|
1983
|
-
value: view.value,
|
|
1984
|
-
revision: view.revision,
|
|
1985
|
-
writable
|
|
1986
|
-
});
|
|
1987
|
-
};
|
|
1988
|
-
const refresh = (namespace) => {
|
|
1989
|
-
if (namespace !== void 0 && namespace !== ns) return;
|
|
1990
|
-
load();
|
|
1991
|
-
};
|
|
1992
|
-
const disposers = [c.get("remote").$on("settings/document-updated", refresh), c.on("connection/reset", () => {
|
|
1993
|
-
load();
|
|
1994
|
-
})];
|
|
1995
|
-
c.effect(() => () => {
|
|
1996
|
-
for (const dispose of disposers) dispose();
|
|
1997
|
-
}, "llm-pi: settings scope");
|
|
1998
|
-
load();
|
|
1999
|
-
return {
|
|
2000
|
-
getSnapshot: () => state,
|
|
2001
|
-
subscribe: (listener) => {
|
|
2002
|
-
listeners.add(listener);
|
|
2003
|
-
return () => {
|
|
2004
|
-
listeners.delete(listener);
|
|
2005
|
-
};
|
|
2006
|
-
},
|
|
2007
|
-
load
|
|
2008
|
-
};
|
|
2009
|
-
}
|
|
2010
2224
|
function apply(ctx) {
|
|
2011
2225
|
const c = ctx;
|
|
2012
2226
|
const tag = injectStyle();
|
|
@@ -2018,7 +2232,7 @@ window.__ModuleLoader__.load({
|
|
|
2018
2232
|
en
|
|
2019
2233
|
}), "llm-pi: locale");
|
|
2020
2234
|
const api = c.get("connection").api.settings;
|
|
2021
|
-
const scope = createApiScope(api, SETTINGS_NS, c);
|
|
2235
|
+
const scope = createApiScope(api, SETTINGS_NS, c, "llm-pi: settings scope");
|
|
2022
2236
|
c.slots.inject("settings.plugin.item", () => c.slots.register({
|
|
2023
2237
|
name: "settings.plugin.item",
|
|
2024
2238
|
key: SETTINGS_NS,
|