@edge-sky/dsh-oauth-adapter 0.1.2-dev.2 → 0.1.3-beta.3

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
@@ -98,6 +98,7 @@ window.__ModuleLoader__.load({
98
98
  },
99
99
  children: [
100
100
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t("modelId"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
101
+ className: "dsh-oauth-input",
101
102
  required: true,
102
103
  maxLength: 256,
103
104
  value: id,
@@ -105,12 +106,14 @@ window.__ModuleLoader__.load({
105
106
  onChange: (event) => setId(event.target.value)
106
107
  })] }),
107
108
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t("modelName"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
109
+ className: "dsh-oauth-input",
108
110
  maxLength: 256,
109
111
  value: name,
110
112
  onChange: (event) => setName(event.target.value),
111
113
  placeholder: id
112
114
  })] }),
113
115
  choices.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t("modelProtocol"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
116
+ className: "dsh-oauth-select",
114
117
  value: api,
115
118
  onChange: (event) => setApi(event.target.value),
116
119
  children: choices.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
@@ -123,11 +126,13 @@ window.__ModuleLoader__.load({
123
126
  children: t("manualModelHint")
124
127
  }),
125
128
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
129
+ className: "dsh-oauth-button",
126
130
  type: "submit",
127
131
  disabled: busy || !validManual(provider, model),
128
132
  children: t("saveModel")
129
133
  }),
130
134
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
135
+ className: "dsh-oauth-button",
131
136
  type: "button",
132
137
  disabled: busy,
133
138
  onClick: close,
@@ -137,6 +142,7 @@ window.__ModuleLoader__.load({
137
142
  });
138
143
  }
139
144
  function ProviderModels({ page, controller, busy, error, t }) {
145
+ const [expanded, setExpanded] = (0, react.useState)(false);
140
146
  const [editing, setEditing] = (0, react.useState)();
141
147
  const [submitted, setSubmitted] = (0, react.useState)(false);
142
148
  const [confirmMigration, setConfirmMigration] = (0, react.useState)(false);
@@ -155,144 +161,184 @@ window.__ModuleLoader__.load({
155
161
  const disabled = busy || !page.connected || !page.writable;
156
162
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
157
163
  className: "dsh-oauth-model-provider",
158
- children: [
159
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: PROVIDERS.find((p) => p.id === page.provider)?.modelGroupLabel }),
160
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
161
- page.connected ? t("modelConnected") : t("disconnected"),
162
- " · ",
163
- page.source === "account" ? t("accountCatalog") : t("builtinCatalog")
164
- ] }),
165
- page.syncedAt && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
166
- t("lastModelSync"),
167
- ": ",
168
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("time", {
169
- dateTime: page.syncedAt,
170
- children: new Date(page.syncedAt).toLocaleString()
171
- })
172
- ] }),
173
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
174
- t("matchedModels"),
175
- ": ",
176
- page.counts.matched,
177
- " · ",
178
- t("pendingModels"),
179
- ": ",
180
- page.counts.pending,
181
- " · ",
182
- t("manualModels"),
183
- ": ",
184
- page.counts.manual
185
- ] }),
186
- (error || page.error) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
187
- role: "alert",
188
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t(errorLabel) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("technicalDetails") }), error?.message ?? page.error] })]
189
- }),
190
- page.migration ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
191
- disabled,
192
- onClick: () => setConfirmMigration(!confirmMigration),
193
- children: t("reviewMigration")
194
- }), confirmMigration && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
195
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("migrationDescription") }),
196
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
197
- page.migration.fields.join(", "),
198
- " · ",
199
- page.migration.models,
200
- " ",
201
- t("modelsTitle")
202
- ] }),
203
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
204
- disabled,
205
- onClick: () => controller.migrateModels(page.provider),
206
- children: t("confirmMigration")
207
- })
208
- ] })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
209
- disabled: disabled || !page.managed,
210
- onClick: () => controller.syncModels(page.provider),
211
- children: busy ? t("modelWorking") : t("syncModels")
164
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
165
+ className: "dsh-oauth-model-header",
166
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
167
+ className: "dsh-oauth-name",
168
+ children: PROVIDERS.find((p) => p.id === page.provider)?.modelGroupLabel
212
169
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
213
- disabled: disabled || !page.managed,
214
- onClick: () => setEditing(null),
215
- children: t("addModel")
216
- })] }),
217
- editing !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelForm, {
218
- provider: page.provider,
219
- ...editing === null ? {} : { initial: editing },
220
- busy,
221
- t,
222
- save: (model) => {
223
- setSubmitted(true);
224
- controller.saveModel(page.provider, model);
225
- },
226
- close: () => setEditing(void 0)
227
- }, editing?.id ?? "new"),
228
- [
229
- "matched",
230
- "pending",
231
- "manual"
232
- ].map((source) => {
233
- const rows = page.rows.filter((row) => (row.source === "catalog" ? "matched" : row.source) === source);
234
- if (!rows.length) return null;
235
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
236
- "aria-label": t(source === "pending" ? "pendingModels" : source === "manual" ? "manualModels" : "matchedModels"),
237
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t(source === "pending" ? "pendingModels" : source === "manual" ? "manualModels" : "matchedModels") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
238
- className: "dsh-oauth-model-list",
239
- children: rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
240
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
241
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: row.name }),
242
- " ",
243
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: row.id }),
244
- " ",
245
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t(row.source === "pending" ? "pendingModels" : row.source === "manual" ? "manualModels" : "matchedModels") })
246
- ] }),
247
- row.source === "pending" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
248
- disabled: disabled || !page.managed,
249
- onClick: () => setEditing({
250
- id: row.id,
251
- name: row.name
252
- }),
253
- children: t("configureModel")
170
+ className: "dsh-oauth-button",
171
+ type: "button",
172
+ "aria-expanded": expanded,
173
+ "aria-controls": `dsh-oauth-models-${page.provider}`,
174
+ onClick: () => setExpanded(!expanded),
175
+ children: t(expanded ? "collapseModels" : "editModel")
176
+ })]
177
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
178
+ className: "dsh-oauth-model-details",
179
+ id: `dsh-oauth-models-${page.provider}`,
180
+ hidden: !expanded,
181
+ children: [
182
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
183
+ className: "dsh-oauth-muted",
184
+ children: [
185
+ page.connected ? t("modelConnected") : t("disconnected"),
186
+ " · ",
187
+ page.source === "account" ? t("accountCatalog") : t("builtinCatalog")
188
+ ]
189
+ }),
190
+ page.syncedAt && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
191
+ className: "dsh-oauth-muted",
192
+ children: [
193
+ t("lastModelSync"),
194
+ ": ",
195
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("time", {
196
+ dateTime: page.syncedAt,
197
+ children: new Date(page.syncedAt).toLocaleString()
198
+ })
199
+ ]
200
+ }),
201
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
202
+ className: "dsh-oauth-muted",
203
+ children: [
204
+ t("matchedModels"),
205
+ ": ",
206
+ page.counts.matched,
207
+ " · ",
208
+ t("manualModels"),
209
+ ": ",
210
+ page.counts.manual
211
+ ]
212
+ }),
213
+ (error || page.error) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
214
+ className: "dsh-oauth-error",
215
+ role: "alert",
216
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t(errorLabel) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("technicalDetails") }), error?.message ?? page.error] })]
217
+ }),
218
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
219
+ className: "dsh-oauth-model-actions",
220
+ children: page.migration ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
221
+ className: "dsh-oauth-button",
222
+ disabled,
223
+ onClick: () => setConfirmMigration(!confirmMigration),
224
+ children: t("reviewMigration")
225
+ }), confirmMigration && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
226
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("migrationDescription") }),
227
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
228
+ page.migration.fields.join(", "),
229
+ " · ",
230
+ page.migration.models,
231
+ " ",
232
+ t("modelsTitle")
233
+ ] }),
234
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
235
+ className: "dsh-oauth-button",
236
+ disabled,
237
+ onClick: () => controller.migrateModels(page.provider),
238
+ children: t("confirmMigration")
239
+ })
240
+ ] })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
241
+ className: "dsh-oauth-button",
242
+ disabled: disabled || !page.managed,
243
+ onClick: () => controller.syncModels(page.provider),
244
+ children: busy ? t("modelWorking") : t("syncModels")
245
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
246
+ className: "dsh-oauth-button",
247
+ disabled: disabled || !page.managed,
248
+ onClick: () => setEditing(null),
249
+ children: t("addModel")
250
+ })] })
251
+ }),
252
+ editing === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelForm, {
253
+ provider: page.provider,
254
+ busy,
255
+ t,
256
+ save: (model) => {
257
+ setSubmitted(true);
258
+ controller.saveModel(page.provider, model);
259
+ },
260
+ close: () => setEditing(void 0)
261
+ }, "new"),
262
+ ["manual", "matched"].map((source) => {
263
+ const label = t(source === "manual" ? "manualModels" : "matchedModels");
264
+ const rows = page.rows.filter((row) => source === "manual" ? row.source === "manual" : row.source === "catalog" || row.source === "matched");
265
+ const offset = source === "manual" ? page.manualOffset : page.offset;
266
+ const total = page.counts[source];
267
+ const move = (next) => controller.readModels(page.provider, source === "matched" ? next : page.offset, source === "manual" ? next : page.manualOffset);
268
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
269
+ className: "dsh-oauth-model-group",
270
+ "aria-label": label,
271
+ children: [
272
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: label }),
273
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
274
+ className: "dsh-oauth-model-list",
275
+ children: rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: source === "manual" && editing?.id === row.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelForm, {
276
+ provider: page.provider,
277
+ initial: editing,
278
+ busy,
279
+ t,
280
+ save: (model) => {
281
+ setSubmitted(true);
282
+ controller.saveModel(page.provider, model);
283
+ },
284
+ close: () => setEditing(void 0)
285
+ }, row.id) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
286
+ className: "dsh-oauth-model-identity",
287
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: row.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: row.id })]
288
+ }), source === "manual" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
289
+ className: "dsh-oauth-model-actions",
290
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
291
+ className: "dsh-oauth-button",
292
+ disabled,
293
+ onClick: () => setEditing({
294
+ id: row.id,
295
+ name: row.name,
296
+ ...MODEL_PROTOCOLS[page.provider].length > 1 ? { api: row.api } : {}
297
+ }),
298
+ children: t("editModel")
299
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
300
+ className: "dsh-oauth-button",
301
+ "data-variant": "danger",
302
+ disabled,
303
+ onClick: () => controller.deleteModel(page.provider, row.id),
304
+ children: t("deleteModel")
305
+ })]
306
+ })] }) }, row.id))
254
307
  }),
255
- row.source === "manual" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
256
- disabled,
257
- onClick: () => setEditing({
258
- id: row.id,
259
- name: row.name,
260
- ...MODEL_PROTOCOLS[page.provider].length > 1 ? { api: row.api } : {}
261
- }),
262
- children: t("editModel")
263
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
264
- disabled,
265
- onClick: () => controller.deleteModel(page.provider, row.id),
266
- children: t("deleteModel")
267
- })] })
268
- ] }, row.id))
269
- })]
270
- }, source);
271
- }),
272
- page.total === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("noModels") }),
273
- page.total > 10 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("nav", {
274
- "aria-label": t("modelsPagination"),
275
- children: [
276
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
277
- disabled: page.offset === 0 || busy,
278
- onClick: () => controller.readModels(page.provider, Math.max(0, page.offset - 10)),
279
- children: t("previousModels")
280
- }),
281
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
282
- page.offset + 1,
283
- "–",
284
- Math.min(page.offset + 10, page.total),
285
- " / ",
286
- page.total
287
- ] }),
288
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
289
- disabled: page.offset + 10 >= page.total || busy,
290
- onClick: () => controller.readModels(page.provider, page.offset + 10),
291
- children: t("nextModels")
292
- })
293
- ]
294
- })
295
- ]
308
+ total === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
309
+ className: "dsh-oauth-muted",
310
+ children: t("noModels")
311
+ }),
312
+ total > 10 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("nav", {
313
+ className: "dsh-oauth-pagination",
314
+ "aria-label": `${label} · ${t("modelsPagination")}`,
315
+ children: [
316
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
317
+ className: "dsh-oauth-button",
318
+ disabled: offset === 0 || busy,
319
+ onClick: () => move(Math.max(0, offset - 10)),
320
+ children: t("previousModels")
321
+ }),
322
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
323
+ offset + 1,
324
+ "–",
325
+ Math.min(offset + 10, total),
326
+ " / ",
327
+ total
328
+ ] }),
329
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
330
+ className: "dsh-oauth-button",
331
+ disabled: offset + 10 >= total || busy,
332
+ onClick: () => move(offset + 10),
333
+ children: t("nextModels")
334
+ })
335
+ ]
336
+ })
337
+ ]
338
+ }, source);
339
+ })
340
+ ]
341
+ })]
296
342
  });
297
343
  }
298
344
  function OAuthModelsSection(props) {
@@ -311,8 +357,14 @@ window.__ModuleLoader__.load({
311
357
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
312
358
  className: "dsh-oauth-section",
313
359
  children: [
314
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { children: t("modelsTitle") }),
315
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("modelsIntro") }),
360
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
361
+ className: "dsh-oauth-title",
362
+ children: t("modelsTitle")
363
+ }),
364
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
365
+ className: "dsh-oauth-intro",
366
+ children: t("modelsIntro")
367
+ }),
316
368
  snapshot.connection !== "open" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
317
369
  role: "status",
318
370
  children: t("hostDisconnected")
@@ -320,7 +372,7 @@ window.__ModuleLoader__.load({
320
372
  PROVIDERS.map((provider) => {
321
373
  const page = snapshot.modelPages?.[provider.id];
322
374
  if (!page) return null;
323
- if (!page.connected && !page.managed && page.counts.manual === 0) return null;
375
+ if (!page.connected || !snapshot.accounts.some((account) => account.provider === provider.id && account.configured)) return null;
324
376
  const error = snapshot.modelErrors?.[provider.id];
325
377
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderModels, {
326
378
  page,
@@ -588,7 +640,7 @@ window.__ModuleLoader__.load({
588
640
  modelRouteConflict: "This provider is owned by an existing configuration. Review the details before migration.",
589
641
  loginSyncFailed: "Sign-in succeeded, but model synchronization failed. Open Models to retry.",
590
642
  modelsTitle: "OAuth models",
591
- modelsIntro: "Synchronize account-visible models or add a model manually. Manual models survive synchronization.",
643
+ modelsIntro: "Models are automatically matched against the pi-ai catalog built into DSH. If a model you need is not matched, try adding it manually.",
592
644
  modelId: "Model ID",
593
645
  modelName: "Display name",
594
646
  modelProtocol: "Request protocol",
@@ -598,7 +650,7 @@ window.__ModuleLoader__.load({
598
650
  accountCatalog: "Account-synchronized list",
599
651
  builtinCatalog: "Built-in list · account access unverified",
600
652
  lastModelSync: "Last successful synchronization",
601
- matchedModels: "Matched",
653
+ matchedModels: "Automatic matches",
602
654
  pendingModels: "Needs configuration",
603
655
  manualModels: "Manual",
604
656
  modelConflict: "Models changed elsewhere. The list was refreshed; review it and retry.",
@@ -611,8 +663,9 @@ window.__ModuleLoader__.load({
611
663
  addModel: "Add model",
612
664
  configureModel: "Configure",
613
665
  editModel: "Edit",
666
+ collapseModels: "Collapse",
614
667
  deleteModel: "Delete manual entry",
615
- noModels: "No selectable models. Configure an unmatched model or add one manually.",
668
+ noModels: "No models in this group.",
616
669
  modelsPagination: "OAuth model pages",
617
670
  previousModels: "Previous",
618
671
  nextModels: "Next",
@@ -654,7 +707,7 @@ window.__ModuleLoader__.load({
654
707
  modelRouteConflict: "该供应商由现有配置管理,请查看详情后再迁移。",
655
708
  loginSyncFailed: "登录成功,但模型同步失败。请前往模型页面重试。",
656
709
  modelsTitle: "OAuth 模型",
657
- modelsIntro: "同步账号可见模型,或手动添加模型。同步时会保留手动模型。",
710
+ modelsIntro: "模型由 DSH 内置 pi-ai 自动匹配。若未匹配到你需要的模型,请尝试手动添加。",
658
711
  modelId: "模型 ID",
659
712
  modelName: "显示名称",
660
713
  modelProtocol: "调用协议",
@@ -664,9 +717,9 @@ window.__ModuleLoader__.load({
664
717
  accountCatalog: "已同步账号列表",
665
718
  builtinCatalog: "内置列表 · 尚未验证账号权限",
666
719
  lastModelSync: "最近成功同步",
667
- matchedModels: "已匹配",
720
+ matchedModels: "自动匹配",
668
721
  pendingModels: "待配置",
669
- manualModels: "手动模型",
722
+ manualModels: "手动配置",
670
723
  modelConflict: "模型已被其他操作修改。列表已刷新,请确认后重试。",
671
724
  modelOperationFailed: "模型操作失败,请查看详情后重试。",
672
725
  reviewMigration: "预览迁移",
@@ -677,8 +730,9 @@ window.__ModuleLoader__.load({
677
730
  addModel: "手动添加",
678
731
  configureModel: "补充配置",
679
732
  editModel: "编辑",
733
+ collapseModels: "收起",
680
734
  deleteModel: "删除手动项",
681
- noModels: "暂无可选模型,请补充待配置项或手动添加。",
735
+ noModels: "此分组暂无模型。",
682
736
  modelsPagination: "OAuth 模型分页",
683
737
  previousModels: "上一页",
684
738
  nextModels: "下一页",
@@ -811,9 +865,9 @@ window.__ModuleLoader__.load({
811
865
  }
812
866
  loadModels() {
813
867
  this.modelsVisible = true;
814
- for (const provider of this.snapshot.accounts) this.readModels(provider.provider);
868
+ for (const provider of this.snapshot.accounts) if (provider.configured) this.readModels(provider.provider);
815
869
  }
816
- readModels(provider, offset = 0) {
870
+ readModels(provider, offset = 0, manualOffset = 0) {
817
871
  if (this.snapshot.connection !== "open") return;
818
872
  const requestId = crypto.randomUUID();
819
873
  this.latestModelReads.set(provider, requestId);
@@ -825,7 +879,8 @@ window.__ModuleLoader__.load({
825
879
  type: "models-list",
826
880
  requestId,
827
881
  provider,
828
- offset
882
+ offset,
883
+ manualOffset
829
884
  });
830
885
  }
831
886
  syncModels(provider) {
@@ -977,12 +1032,12 @@ window.__ModuleLoader__.load({
977
1032
  receive(message) {
978
1033
  switch (message.type) {
979
1034
  case "models-changed":
980
- if (this.modelsVisible) this.readModels(message.provider, this.snapshot.modelPages?.[message.provider]?.offset ?? 0);
1035
+ if (this.modelsVisible) this.readModels(message.provider, this.snapshot.modelPages?.[message.provider]?.offset ?? 0, this.snapshot.modelPages?.[message.provider]?.manualOffset ?? 0);
981
1036
  return;
982
1037
  case "models-page": {
983
1038
  const request = this.modelRequests.get(message.requestId);
984
1039
  this.modelRequests.delete(message.requestId);
985
- if (!request) return;
1040
+ if (!request || !this.snapshot.accounts.some((account) => account.provider === request.provider && account.configured)) return;
986
1041
  if (!request.write && this.latestModelReads.get(request.provider) !== message.requestId) return;
987
1042
  const current = this.snapshot.modelPages?.[request.provider];
988
1043
  if (current && message.page.revision < current.revision) {
@@ -1011,7 +1066,7 @@ window.__ModuleLoader__.load({
1011
1066
  case "models-error": {
1012
1067
  const request = this.modelRequests.get(message.requestId);
1013
1068
  this.modelRequests.delete(message.requestId);
1014
- if (!request) return;
1069
+ if (!request || !this.snapshot.accounts.some((account) => account.provider === request.provider && account.configured)) return;
1015
1070
  if (!request.write && this.latestModelReads.get(request.provider) !== message.requestId) return;
1016
1071
  this.update({
1017
1072
  ...this.snapshot,
@@ -1033,14 +1088,30 @@ window.__ModuleLoader__.load({
1033
1088
  case "snapshot":
1034
1089
  const accounts = [...message.accounts];
1035
1090
  const reconciled = reconcileAttemptView(this.snapshot.attempt, accounts);
1091
+ const modelPages = { ...this.snapshot.modelPages };
1092
+ const modelBusy = { ...this.snapshot.modelBusy };
1093
+ const modelErrors = { ...this.snapshot.modelErrors };
1094
+ for (const account of accounts) {
1095
+ if (account.configured) continue;
1096
+ delete modelPages[account.provider];
1097
+ delete modelBusy[account.provider];
1098
+ delete modelErrors[account.provider];
1099
+ this.latestModelReads.delete(account.provider);
1100
+ for (const [id, request] of this.modelRequests) if (request.provider === account.provider) this.modelRequests.delete(id);
1101
+ }
1036
1102
  const nextSnapshot = {
1037
1103
  ...this.snapshot,
1038
- accounts
1104
+ accounts,
1105
+ modelPages,
1106
+ modelBusy,
1107
+ modelErrors
1039
1108
  };
1040
1109
  if (reconciled === void 0) delete nextSnapshot.attempt;
1041
1110
  else nextSnapshot.attempt = reconciled;
1042
1111
  this.update(nextSnapshot);
1043
- if (this.modelsVisible) for (const account of accounts) this.readModels(account.provider, this.snapshot.modelPages?.[account.provider]?.offset ?? 0);
1112
+ if (this.modelsVisible) {
1113
+ for (const account of accounts) if (account.configured) this.readModels(account.provider, this.snapshot.modelPages?.[account.provider]?.offset ?? 0, this.snapshot.modelPages?.[account.provider]?.manualOffset ?? 0);
1114
+ }
1044
1115
  return;
1045
1116
  case "started":
1046
1117
  const { error: _error, errorDetail: _errorDetail, ...current } = this.snapshot;
@@ -1152,14 +1223,23 @@ window.__ModuleLoader__.load({
1152
1223
  .dsh-oauth-flow{display:flex;flex-direction:column;gap:8px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l3);font-size:13px;line-height:20px}.dsh-oauth-link{display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;gap:8px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);text-decoration:none}.dsh-oauth-link:hover{background:var(--dsw-alias-interactive-bg-hover-solid)}.dsh-oauth-link-label{font-weight:500}.dsh-oauth-link-host{max-width:240px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:12px}.dsh-oauth-link-icon{color:var(--dsw-alias-brand-text)}.dsh-oauth-code{display:flex;align-items:center;gap:8px}.dsh-oauth-code code{padding:5px 8px;border-radius:6px;background:var(--dsw-alias-bg-module-platform);font-size:14px;letter-spacing:.08em}
1153
1224
  .dsh-oauth-prompt{display:flex;flex-direction:column;gap:7px}.dsh-oauth-input,.dsh-oauth-select{box-sizing:border-box;width:100%;height:34px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary)}
1154
1225
 
1155
- .dsh-oauth-model-provider { border: 1px solid var(--border-color, currentColor); border-radius: 8px; padding: 16px; margin-block: 16px; }
1156
- .dsh-oauth-model-provider button { margin: 4px; }
1157
- .dsh-oauth-model-list { padding: 0; list-style: none; }
1158
- .dsh-oauth-model-list li { padding-block: 8px; border-bottom: 1px solid var(--border-color, currentColor); overflow-wrap: anywhere; }
1159
- .dsh-oauth-model-list code { margin-inline: 8px; }
1160
- .dsh-oauth-model-form { display: grid; gap: 12px; margin-block: 12px; }
1161
- .dsh-oauth-model-form label { display: grid; gap: 4px; }
1162
- .dsh-oauth-model-provider nav { display: flex; align-items: center; gap: 8px; }
1226
+ .dsh-oauth-model-provider{display:flex;flex-direction:column;gap:10px;min-width:0;padding:14px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px}
1227
+ .dsh-oauth-model-header{display:flex;align-items:center;justify-content:space-between;gap:12px}.dsh-oauth-model-header h3{min-width:0;overflow-wrap:anywhere}.dsh-oauth-model-header button{flex-shrink:0}
1228
+ .dsh-oauth-model-details{display:flex;flex-direction:column;gap:10px}.dsh-oauth-model-details[hidden]{display:none}
1229
+ .dsh-oauth-model-provider h3,.dsh-oauth-model-provider h4{margin:0;line-height:22px;font-weight:500}
1230
+ .dsh-oauth-model-provider h4{font-size:13px;color:var(--dsw-alias-label-secondary)}
1231
+ .dsh-oauth-model-actions{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
1232
+ .dsh-oauth-model-group{display:flex;flex-direction:column;gap:8px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2)}
1233
+ .dsh-oauth-model-list{display:flex;flex-direction:column;gap:0;margin:0;padding:0;list-style:none}
1234
+ .dsh-oauth-model-list li{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;padding:10px 0;min-width:0}
1235
+ .dsh-oauth-model-list li>.dsh-oauth-model-form{box-sizing:border-box;width:100%;min-width:0}
1236
+ .dsh-oauth-model-list li+li{border-top:1px solid var(--dsw-alias-border-l2)}
1237
+ .dsh-oauth-model-identity{display:flex;flex-direction:column;gap:3px;min-width:0;overflow-wrap:anywhere}
1238
+ .dsh-oauth-model-identity strong{font-size:13px;line-height:20px;font-weight:500}
1239
+ .dsh-oauth-model-identity code{font:12px/18px ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere}
1240
+ .dsh-oauth-model-form{display:flex;flex-direction:column;gap:10px;padding:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px}
1241
+ .dsh-oauth-model-form label{display:flex;flex-direction:column;gap:5px;font-size:13px;line-height:20px}
1242
+ .dsh-oauth-pagination{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:12px;color:var(--dsw-alias-label-tertiary)}
1163
1243
  `;
1164
1244
  //#endregion
1165
1245
  //#region src/client/index.ts
package/lib/index.d.ts CHANGED
@@ -5,13 +5,16 @@
5
5
  */
6
6
  import { type ModelContext } from './model-service.js';
7
7
  import type { IncomingMessage } from 'node:http';
8
+ import type { Volatile } from '@deepseek-ai/cordis';
8
9
  import type { AuthorizationService } from '@deepseek-ai/dsh-authorization';
9
10
  import type { CredentialProvider } from '@deepseek-ai/dsh-credentials';
10
11
  import type { WebServer } from '@deepseek-ai/dsh-host-webserver';
11
- import { type SettingsProvider } from '@deepseek-ai/dsh-settings';
12
+ import type SettingsForms from '@deepseek-ai/dsh-settings';
12
13
  import z from '@deepseek-ai/schemastery';
14
+ import { type ModelStore } from './model-catalog.js';
13
15
  /** Plugin configuration. */
14
16
  export interface Config {
17
+ modelStore: Volatile<ModelStore>;
15
18
  /** Emit secret-free transport lifecycle diagnostics. */
16
19
  debug?: boolean;
17
20
  modelSyncTimeoutMs?: number;
@@ -28,7 +31,7 @@ export declare const inject: string[];
28
31
  type OAuthContext = ModelContext & {
29
32
  authorization: AuthorizationService;
30
33
  credentials: CredentialProvider;
31
- settings: SettingsProvider;
34
+ settings: SettingsForms;
32
35
  webServer: WebServer;
33
36
  };
34
37
  /** Whether a socket address belongs to the local host. */
package/lib/index.js CHANGED
@@ -10,8 +10,10 @@ import { inspect } from 'node:util';
10
10
  import z from '@deepseek-ai/schemastery';
11
11
  import WebSocket, { WebSocketServer } from 'ws';
12
12
  import { MAX_FRAME_BYTES, OAUTH_SOCKET_PATH, OAUTH_SOCKET_PROTOCOL, PROVIDERS, parseClientCommand, } from './protocol.js';
13
+ import { validateModelStore } from './model-catalog.js';
13
14
  /** Runtime-validated plugin configuration. */
14
15
  export const Config = z.object({
16
+ modelStore: z.object({ version: z.const(1).default(1), providers: z.dict(z.any()).default({}) }).default({ version: 1, providers: {} }).volatile(),
15
17
  debug: z.boolean().default(false),
16
18
  modelSyncTimeoutMs: z.number().min(1).max(2147483647).step(1).default(30000),
17
19
  modelSyncMaxResponseBytes: z.number().min(1024).step(1).default(4194304),
@@ -274,7 +276,7 @@ class OAuthConnection {
274
276
  }
275
277
  if (!this.closed)
276
278
  this.send({ type: 'models-page', requestId: command.requestId,
277
- page: await this.models.page(command.provider, command.type === 'models-list' ? command.offset : 0) });
279
+ page: await this.models.page(command.provider, command.type === 'models-list' ? command.offset : 0, command.type === 'models-list' ? command.manualOffset : 0) });
278
280
  }
279
281
  catch (error) {
280
282
  if (!this.closed)
@@ -464,6 +466,13 @@ class OAuthConnection {
464
466
  }
465
467
  /** Mount the loopback-only OAuth WebSocket over official rc services. */
466
468
  export function apply(ctx, config) {
469
+ validateModelStore(config.modelStore.get());
470
+ ctx.on('internal/config', function (_raw, next) {
471
+ const raw = next();
472
+ if (this === ctx.fiber)
473
+ validateModelStore(Config(raw).modelStore.get());
474
+ return raw;
475
+ });
467
476
  const debug = config.debug === true
468
477
  ? (message) => { ctx.logger.debug('dsh-oauth-adapter: %s', message); }
469
478
  : () => { };
@@ -474,7 +483,7 @@ export function apply(ctx, config) {
474
483
  maxPages: config.modelSyncMaxPages ?? 100,
475
484
  codexClientVersion: config.codexClientVersion ?? '0.149.0',
476
485
  }, provider => { for (const connection of connections)
477
- connection.modelChanged(provider); });
486
+ connection.modelChanged(provider); }, config.modelStore);
478
487
  const server = new WebSocketServer({
479
488
  noServer: true,
480
489
  maxPayload: MAX_FRAME_BYTES,