@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 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
- * 配置卡片基础控件:文本/数字输入、勾选行、下拉、键值对编辑、JSON 文本框。
322
- * 视觉对齐官方卡片字段(label + hint + 控件纵列),样式类前缀 lpc-。
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 lpc-wide",
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) => update(index, { key: event.target.value })
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) => update(index, { value: event.target.value })
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
- onClick: () => remove(index),
464
- children: props.removeLabel
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)("span", {
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/)。配置读写走官方 settingsScope 传输(scope.ts)。
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)("li", {
1505
- className: `lpc-card${open ? " lpc-cardOpen" : ""}`,
1506
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1507
- type: "button",
1508
- className: "lpc-header",
1509
- "aria-expanded": open,
1510
- "aria-label": `${t(open ? "collapse" : "expand")}: ${t("title")}`,
1511
- onClick: () => setOpen(!open),
1512
- children: [
1513
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1514
- className: "lpc-headText",
1515
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1516
- className: "lpc-name",
1517
- children: t("title")
1518
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1519
- className: "lpc-description",
1520
- children: t("description")
1521
- })]
1522
- }),
1523
- dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1524
- className: "lpc-pending",
1525
- children: t("unsaved")
1526
- }) : null,
1527
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1528
- className: `lpc-chevron${open ? " lpc-chevronOpen" : ""}`,
1529
- children: "▾"
1530
- })
1531
- ]
1532
- }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1533
- className: "lpc-body",
1534
- children: [
1535
- disabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1536
- className: "lpc-readOnly",
1537
- role: "status",
1538
- children: t("readOnly")
1539
- }) : null,
1540
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
1541
- id: "lpc-enabled",
1542
- label: t("enabled"),
1543
- checked: draft.enabled,
1544
- disabled,
1545
- onEdit: (value) => {
1546
- setDraft({
1547
- ...draft,
1548
- enabled: value
1549
- });
1550
- setStatus(IDLE_STATUS);
1551
- }
1552
- }),
1553
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
1554
- id: "lpc-catalogUrl",
1555
- label: t("catalogUrl"),
1556
- hint: t("catalogUrlHint"),
1557
- value: draft.catalogUrl,
1558
- disabled,
1559
- onEdit: (value) => {
1560
- setDraft({
1561
- ...draft,
1562
- catalogUrl: value
1563
- });
1564
- setStatus(IDLE_STATUS);
1565
- }
1566
- }),
1567
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
1568
- id: "lpc-catalogRefresh",
1569
- label: t("catalogRefreshHours"),
1570
- hint: t("catalogRefreshHoursHint"),
1571
- value: draft.catalogRefreshHours,
1572
- numeric: true,
1573
- disabled,
1574
- invalid: !numTextOk(draft.catalogRefreshHours),
1575
- invalidLabel: t("invalidNumber"),
1576
- onEdit: (value) => {
1577
- setDraft({
1578
- ...draft,
1579
- catalogRefreshHours: value
1580
- });
1581
- setStatus(IDLE_STATUS);
1582
- }
1583
- }),
1584
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
1585
- id: "lpc-catalogProxy",
1586
- label: t("catalogProxy"),
1587
- hint: t("catalogProxyHint"),
1588
- value: draft.catalogProxy,
1589
- disabled,
1590
- onEdit: (value) => {
1591
- setDraft({
1592
- ...draft,
1593
- catalogProxy: value
1594
- });
1595
- setStatus(IDLE_STATUS);
1596
- }
1597
- }),
1598
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1599
- className: "lpc-statusRow",
1600
- children: [
1601
- t("kitSource"),
1602
- "",
1603
- kitSource ?? ""
1604
- ]
1605
- }),
1606
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1607
- className: "lpc-statusRow",
1608
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
1609
- t("modelsDevStatus"),
1610
- "",
1611
- modelsDevText(modelsDevStatus, t)
1612
- ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1613
- type: "button",
1614
- className: "lpc-btn lpc-btnGhost lpc-btnSmall lpc-refreshBtn",
1615
- disabled: disabled || refreshing,
1616
- onClick: onRefreshCatalog,
1617
- children: t(refreshing ? "refreshingCatalog" : "refreshCatalog")
1618
- })]
1619
- }),
1620
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProvidersSection, {
1621
- providers: draft.providers,
1622
- epoch,
1623
- disabled,
1624
- t,
1625
- onAddRoute,
1626
- onRemoveRoute,
1627
- onPatchProvider: setProvider
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 zh = {
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 en = {
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
- * 配置卡片样式:沿用官方 data-plugin / data-plugin-css 约定(HMR 据此卸载),
1846
- * 视觉对齐官方卡片(--dsw-alias-* 变量),不覆盖上游任何选择器。
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 STYLE_TAG_ID = `${PLUGIN_ID}/card.css`;
1851
- const cardCss = `
1852
- .lpc-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}
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-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
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-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}
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
- if (typeof document === "undefined") return null;
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,