@mangoc/dsh-sub2api-sync 0.2.1 → 0.2.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.cjs CHANGED
@@ -138,9 +138,13 @@ const zh = {
138
138
  keyCreated: "已创建并绑定:{name}",
139
139
  keysListEmpty: "还没有 key",
140
140
  keysListLoading: "正在读取 keys…",
141
+ keysSearchPlaceholder: "搜索 key…",
142
+ keysSearchEmpty: "没有匹配的 key",
141
143
  syncSelected: "同步所选({count})",
142
144
  syncSelectedDone: "同步完成:{ok}/{total} 条成功",
143
- keysLoadMore: "加载更多(还有 {count} 个)"
145
+ keysPagePrev: "上一页",
146
+ keysPageNext: "下一页",
147
+ keysPageStatus: "第 {page} / {pages} 页 · 共 {total} 个"
144
148
  };
145
149
  const en = {
146
150
  nav: "Sub2API Sync",
@@ -248,9 +252,13 @@ const en = {
248
252
  keyCreated: "Created and bound: {name}",
249
253
  keysListEmpty: "No keys yet",
250
254
  keysListLoading: "Loading keys…",
255
+ keysSearchPlaceholder: "Search keys…",
256
+ keysSearchEmpty: "No keys match",
251
257
  syncSelected: "Sync selected ({count})",
252
258
  syncSelectedDone: "Synced: {ok}/{total} succeeded",
253
- keysLoadMore: "Load more ({count} left)"
259
+ keysPagePrev: "Previous page",
260
+ keysPageNext: "Next page",
261
+ keysPageStatus: "Page {page} / {pages} · {total} keys"
254
262
  };
255
263
  const dictionaries = {
256
264
  zh,
@@ -404,7 +412,7 @@ function createPluginApi() {
404
412
  login2fa: (code) => callJson(ROUTES.login2fa, post({ code })),
405
413
  logout: () => callJson(ROUTES.logout, post()),
406
414
  groups: () => callJson(ROUTES.groups),
407
- listKeys: (page, pageSize) => callJson(`${ROUTES.keys}?page=${page}&pageSize=${pageSize}`),
415
+ listKeys: (page, pageSize, search) => callJson(`${ROUTES.keys}?page=${page}&pageSize=${pageSize}${search !== undefined && search.length > 0 ? `&search=${encodeURIComponent(search)}` : ""}`),
408
416
  createKey: (input) => callJson(ROUTES.keys, post(input)),
409
417
  site: () => callJson(ROUTES.site),
410
418
  account: () => callJson(ROUTES.account),
@@ -743,6 +751,31 @@ const CSS = `
743
751
  opacity: 0.55;
744
752
  }
745
753
 
754
+ /* The keys-card search input: fixed-width slot between the title and the
755
+ action buttons (see the s2s-actions flex layout). */
756
+ .s2s-searchInput {
757
+ flex: none;
758
+ width: 220px;
759
+ }
760
+
761
+ /* The keys-card pager: centered prev / status / next strip. */
762
+ .s2s-pager {
763
+ display: flex;
764
+ align-items: center;
765
+ justify-content: flex-end;
766
+ gap: 10px;
767
+ flex-wrap: wrap;
768
+ min-height: 32px;
769
+ }
770
+
771
+ .s2s-pageStatus {
772
+ color: var(--dsw-alias-label-secondary);
773
+ font-size: 12px;
774
+ line-height: 18px;
775
+ font-variant-numeric: tabular-nums;
776
+ white-space: nowrap;
777
+ }
778
+
746
779
  /* U2 placements: the provider-card badge, the Models-page footer strip, and
747
780
  the Plugins-page activation steps. */
748
781
  .s2s-badgeRow {
@@ -1148,12 +1181,16 @@ function protocolForPlatform(platform) {
1148
1181
  //#endregion
1149
1182
  //#region src/client/section/KeysCard.tsx
1150
1183
  const PAGE_SIZE = 5;
1184
+ const SEARCH_DEBOUNCE_MS = 300;
1151
1185
  function KeysCard({ api, config, groups, t }) {
1152
1186
  const snapshot$1 = useConfigForm(config);
1153
1187
  const [keys, setKeys] = (0, react.useState)([]);
1154
- const [page, setPage] = (0, react.useState)(0);
1188
+ const [search, setSearch] = (0, react.useState)("");
1189
+ const [query, setQuery] = (0, react.useState)("");
1190
+ const [page, setPage] = (0, react.useState)(1);
1155
1191
  const [pages, setPages] = (0, react.useState)(1);
1156
1192
  const [total, setTotal] = (0, react.useState)(0);
1193
+ const [reloadTick, setReloadTick] = (0, react.useState)(0);
1157
1194
  const [loading, setLoading] = (0, react.useState)(false);
1158
1195
  const [listError, setListError] = (0, react.useState)(undefined);
1159
1196
  const [creating, setCreating] = (0, react.useState)(false);
@@ -1164,23 +1201,41 @@ function KeysCard({ api, config, groups, t }) {
1164
1201
  const [busy, setBusy] = (0, react.useState)(false);
1165
1202
  const [syncing, setSyncing] = (0, react.useState)(false);
1166
1203
  const [feedback, setFeedback] = (0, react.useState)(undefined);
1167
- const loadPage = (next) => {
1204
+ const appliedQueryRef = (0, react.useRef)(query);
1205
+ (0, react.useEffect)(() => {
1206
+ const timer$1 = window.setTimeout(() => {
1207
+ const next = search.trim();
1208
+ if (appliedQueryRef.current !== next) {
1209
+ appliedQueryRef.current = next;
1210
+ setQuery(next);
1211
+ setPage(1);
1212
+ }
1213
+ }, SEARCH_DEBOUNCE_MS);
1214
+ return () => window.clearTimeout(timer$1);
1215
+ }, [search]);
1216
+ const requestSeqRef = (0, react.useRef)(0);
1217
+ (0, react.useEffect)(() => {
1218
+ const requestId = ++requestSeqRef.current;
1168
1219
  setLoading(true);
1169
1220
  setListError(undefined);
1170
- api.listKeys(next, PAGE_SIZE).then((response) => {
1171
- setKeys((previous) => next === 1 ? response.keys : [...previous, ...response.keys]);
1221
+ api.listKeys(page, PAGE_SIZE, query === "" ? undefined : query).then((response) => {
1222
+ if (requestSeqRef.current !== requestId) return;
1223
+ setKeys(response.keys);
1172
1224
  setPages(response.pages);
1173
1225
  setTotal(response.total);
1174
- setPage(next);
1226
+ if (response.pages > 0 && page > response.pages) setPage(response.pages);
1175
1227
  }).catch((cause) => {
1228
+ if (requestSeqRef.current !== requestId) return;
1176
1229
  setListError(cause instanceof Error ? cause.message : String(cause));
1177
1230
  }).finally(() => {
1178
- setLoading(false);
1231
+ if (requestSeqRef.current === requestId) setLoading(false);
1179
1232
  });
1180
- };
1181
- (0, react.useEffect)(() => {
1182
- loadPage(1);
1183
- }, [api]);
1233
+ }, [
1234
+ api,
1235
+ page,
1236
+ query,
1237
+ reloadTick
1238
+ ]);
1184
1239
  const selectedIds = snapshot$1.value?.selectedKeys ?? [];
1185
1240
  const toggle = (keyId, next) => {
1186
1241
  const nextSet = new Set(selectedIds);
@@ -1241,7 +1296,8 @@ else nextSet.delete(keyId);
1241
1296
  text: t("keyCreated", { name: response.key.name })
1242
1297
  });
1243
1298
  closeCreate();
1244
- loadPage(1);
1299
+ setPage(1);
1300
+ setReloadTick((tick) => tick + 1);
1245
1301
  }).catch((cause) => {
1246
1302
  setFeedback({
1247
1303
  tone: "error",
@@ -1276,10 +1332,19 @@ else nextSet.delete(keyId);
1276
1332
  }), (0, react_jsx_runtime.jsxs)("div", {
1277
1333
  className: "s2s-actions",
1278
1334
  children: [
1335
+ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Input, {
1336
+ className: "s2s-searchInput",
1337
+ type: "text",
1338
+ value: search,
1339
+ placeholder: t("keysSearchPlaceholder"),
1340
+ onChange: (event) => {
1341
+ setSearch(event.target.value);
1342
+ }
1343
+ }),
1279
1344
  (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1280
1345
  variant: "outline",
1281
1346
  onClick: () => {
1282
- loadPage(1);
1347
+ setReloadTick((tick) => tick + 1);
1283
1348
  },
1284
1349
  children: t("groupsRefresh")
1285
1350
  }),
@@ -1309,7 +1374,7 @@ else nextSet.delete(keyId);
1309
1374
  }),
1310
1375
  !loading && keys.length === 0 && listError === undefined && (0, react_jsx_runtime.jsx)("p", {
1311
1376
  className: "s2s-feedback s2s-hint",
1312
- children: t("keysListEmpty")
1377
+ children: query !== "" ? t("keysSearchEmpty") : t("keysListEmpty")
1313
1378
  }),
1314
1379
  loading && keys.length === 0 && (0, react_jsx_runtime.jsx)("p", {
1315
1380
  className: "s2s-feedback s2s-hint",
@@ -1354,16 +1419,34 @@ else nextSet.delete(keyId);
1354
1419
  }, key.id);
1355
1420
  })
1356
1421
  }),
1357
- keys.length > 0 && page < pages && (0, react_jsx_runtime.jsx)("div", {
1358
- className: "s2s-actions",
1359
- children: (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1360
- variant: "outline",
1361
- disabled: loading,
1362
- onClick: () => {
1363
- loadPage(page + 1);
1364
- },
1365
- children: t("keysLoadMore", { count: Math.max(0, Math.min(PAGE_SIZE, total - keys.length)) })
1366
- })
1422
+ keys.length > 0 && (pages > 1 || query !== "") && (0, react_jsx_runtime.jsxs)("div", {
1423
+ className: "s2s-pager",
1424
+ children: [
1425
+ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1426
+ variant: "outline",
1427
+ disabled: loading || page <= 1,
1428
+ onClick: () => {
1429
+ setPage(page - 1);
1430
+ },
1431
+ children: t("keysPagePrev")
1432
+ }),
1433
+ (0, react_jsx_runtime.jsx)("span", {
1434
+ className: "s2s-pageStatus",
1435
+ children: t("keysPageStatus", {
1436
+ page,
1437
+ pages,
1438
+ total
1439
+ })
1440
+ }),
1441
+ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1442
+ variant: "outline",
1443
+ disabled: loading || page >= pages,
1444
+ onClick: () => {
1445
+ setPage(page + 1);
1446
+ },
1447
+ children: t("keysPageNext")
1448
+ })
1449
+ ]
1367
1450
  }),
1368
1451
  (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Modal, {
1369
1452
  open: creating,
package/lib/client.js CHANGED
@@ -144,9 +144,13 @@ const zh = {
144
144
  keyCreated: "已创建并绑定:{name}",
145
145
  keysListEmpty: "还没有 key",
146
146
  keysListLoading: "正在读取 keys…",
147
+ keysSearchPlaceholder: "搜索 key…",
148
+ keysSearchEmpty: "没有匹配的 key",
147
149
  syncSelected: "同步所选({count})",
148
150
  syncSelectedDone: "同步完成:{ok}/{total} 条成功",
149
- keysLoadMore: "加载更多(还有 {count} 个)"
151
+ keysPagePrev: "上一页",
152
+ keysPageNext: "下一页",
153
+ keysPageStatus: "第 {page} / {pages} 页 · 共 {total} 个"
150
154
  };
151
155
  const en = {
152
156
  nav: "Sub2API Sync",
@@ -254,9 +258,13 @@ const en = {
254
258
  keyCreated: "Created and bound: {name}",
255
259
  keysListEmpty: "No keys yet",
256
260
  keysListLoading: "Loading keys…",
261
+ keysSearchPlaceholder: "Search keys…",
262
+ keysSearchEmpty: "No keys match",
257
263
  syncSelected: "Sync selected ({count})",
258
264
  syncSelectedDone: "Synced: {ok}/{total} succeeded",
259
- keysLoadMore: "Load more ({count} left)"
265
+ keysPagePrev: "Previous page",
266
+ keysPageNext: "Next page",
267
+ keysPageStatus: "Page {page} / {pages} · {total} keys"
260
268
  };
261
269
  const dictionaries = {
262
270
  zh,
@@ -410,7 +418,7 @@ function createPluginApi() {
410
418
  login2fa: (code) => callJson(ROUTES.login2fa, post({ code })),
411
419
  logout: () => callJson(ROUTES.logout, post()),
412
420
  groups: () => callJson(ROUTES.groups),
413
- listKeys: (page, pageSize) => callJson(`${ROUTES.keys}?page=${page}&pageSize=${pageSize}`),
421
+ listKeys: (page, pageSize, search) => callJson(`${ROUTES.keys}?page=${page}&pageSize=${pageSize}${search !== undefined && search.length > 0 ? `&search=${encodeURIComponent(search)}` : ""}`),
414
422
  createKey: (input) => callJson(ROUTES.keys, post(input)),
415
423
  site: () => callJson(ROUTES.site),
416
424
  account: () => callJson(ROUTES.account),
@@ -749,6 +757,31 @@ const CSS = `
749
757
  opacity: 0.55;
750
758
  }
751
759
 
760
+ /* The keys-card search input: fixed-width slot between the title and the
761
+ action buttons (see the s2s-actions flex layout). */
762
+ .s2s-searchInput {
763
+ flex: none;
764
+ width: 220px;
765
+ }
766
+
767
+ /* The keys-card pager: centered prev / status / next strip. */
768
+ .s2s-pager {
769
+ display: flex;
770
+ align-items: center;
771
+ justify-content: flex-end;
772
+ gap: 10px;
773
+ flex-wrap: wrap;
774
+ min-height: 32px;
775
+ }
776
+
777
+ .s2s-pageStatus {
778
+ color: var(--dsw-alias-label-secondary);
779
+ font-size: 12px;
780
+ line-height: 18px;
781
+ font-variant-numeric: tabular-nums;
782
+ white-space: nowrap;
783
+ }
784
+
752
785
  /* U2 placements: the provider-card badge, the Models-page footer strip, and
753
786
  the Plugins-page activation steps. */
754
787
  .s2s-badgeRow {
@@ -1154,12 +1187,16 @@ function protocolForPlatform(platform) {
1154
1187
  //#endregion
1155
1188
  //#region src/client/section/KeysCard.tsx
1156
1189
  const PAGE_SIZE = 5;
1190
+ const SEARCH_DEBOUNCE_MS = 300;
1157
1191
  function KeysCard({ api, config, groups, t }) {
1158
1192
  const snapshot$1 = useConfigForm(config);
1159
1193
  const [keys, setKeys] = (0, react.useState)([]);
1160
- const [page, setPage] = (0, react.useState)(0);
1194
+ const [search, setSearch] = (0, react.useState)("");
1195
+ const [query, setQuery] = (0, react.useState)("");
1196
+ const [page, setPage] = (0, react.useState)(1);
1161
1197
  const [pages, setPages] = (0, react.useState)(1);
1162
1198
  const [total, setTotal] = (0, react.useState)(0);
1199
+ const [reloadTick, setReloadTick] = (0, react.useState)(0);
1163
1200
  const [loading, setLoading] = (0, react.useState)(false);
1164
1201
  const [listError, setListError] = (0, react.useState)(undefined);
1165
1202
  const [creating, setCreating] = (0, react.useState)(false);
@@ -1170,23 +1207,41 @@ function KeysCard({ api, config, groups, t }) {
1170
1207
  const [busy, setBusy] = (0, react.useState)(false);
1171
1208
  const [syncing, setSyncing] = (0, react.useState)(false);
1172
1209
  const [feedback, setFeedback] = (0, react.useState)(undefined);
1173
- const loadPage = (next) => {
1210
+ const appliedQueryRef = (0, react.useRef)(query);
1211
+ (0, react.useEffect)(() => {
1212
+ const timer$1 = window.setTimeout(() => {
1213
+ const next = search.trim();
1214
+ if (appliedQueryRef.current !== next) {
1215
+ appliedQueryRef.current = next;
1216
+ setQuery(next);
1217
+ setPage(1);
1218
+ }
1219
+ }, SEARCH_DEBOUNCE_MS);
1220
+ return () => window.clearTimeout(timer$1);
1221
+ }, [search]);
1222
+ const requestSeqRef = (0, react.useRef)(0);
1223
+ (0, react.useEffect)(() => {
1224
+ const requestId = ++requestSeqRef.current;
1174
1225
  setLoading(true);
1175
1226
  setListError(undefined);
1176
- api.listKeys(next, PAGE_SIZE).then((response) => {
1177
- setKeys((previous) => next === 1 ? response.keys : [...previous, ...response.keys]);
1227
+ api.listKeys(page, PAGE_SIZE, query === "" ? undefined : query).then((response) => {
1228
+ if (requestSeqRef.current !== requestId) return;
1229
+ setKeys(response.keys);
1178
1230
  setPages(response.pages);
1179
1231
  setTotal(response.total);
1180
- setPage(next);
1232
+ if (response.pages > 0 && page > response.pages) setPage(response.pages);
1181
1233
  }).catch((cause) => {
1234
+ if (requestSeqRef.current !== requestId) return;
1182
1235
  setListError(cause instanceof Error ? cause.message : String(cause));
1183
1236
  }).finally(() => {
1184
- setLoading(false);
1237
+ if (requestSeqRef.current === requestId) setLoading(false);
1185
1238
  });
1186
- };
1187
- (0, react.useEffect)(() => {
1188
- loadPage(1);
1189
- }, [api]);
1239
+ }, [
1240
+ api,
1241
+ page,
1242
+ query,
1243
+ reloadTick
1244
+ ]);
1190
1245
  const selectedIds = snapshot$1.value?.selectedKeys ?? [];
1191
1246
  const toggle = (keyId, next) => {
1192
1247
  const nextSet = new Set(selectedIds);
@@ -1247,7 +1302,8 @@ else nextSet.delete(keyId);
1247
1302
  text: t("keyCreated", { name: response.key.name })
1248
1303
  });
1249
1304
  closeCreate();
1250
- loadPage(1);
1305
+ setPage(1);
1306
+ setReloadTick((tick) => tick + 1);
1251
1307
  }).catch((cause) => {
1252
1308
  setFeedback({
1253
1309
  tone: "error",
@@ -1282,10 +1338,19 @@ else nextSet.delete(keyId);
1282
1338
  }), (0, react_jsx_runtime.jsxs)("div", {
1283
1339
  className: "s2s-actions",
1284
1340
  children: [
1341
+ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Input, {
1342
+ className: "s2s-searchInput",
1343
+ type: "text",
1344
+ value: search,
1345
+ placeholder: t("keysSearchPlaceholder"),
1346
+ onChange: (event) => {
1347
+ setSearch(event.target.value);
1348
+ }
1349
+ }),
1285
1350
  (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1286
1351
  variant: "outline",
1287
1352
  onClick: () => {
1288
- loadPage(1);
1353
+ setReloadTick((tick) => tick + 1);
1289
1354
  },
1290
1355
  children: t("groupsRefresh")
1291
1356
  }),
@@ -1315,7 +1380,7 @@ else nextSet.delete(keyId);
1315
1380
  }),
1316
1381
  !loading && keys.length === 0 && listError === undefined && (0, react_jsx_runtime.jsx)("p", {
1317
1382
  className: "s2s-feedback s2s-hint",
1318
- children: t("keysListEmpty")
1383
+ children: query !== "" ? t("keysSearchEmpty") : t("keysListEmpty")
1319
1384
  }),
1320
1385
  loading && keys.length === 0 && (0, react_jsx_runtime.jsx)("p", {
1321
1386
  className: "s2s-feedback s2s-hint",
@@ -1360,16 +1425,34 @@ else nextSet.delete(keyId);
1360
1425
  }, key.id);
1361
1426
  })
1362
1427
  }),
1363
- keys.length > 0 && page < pages && (0, react_jsx_runtime.jsx)("div", {
1364
- className: "s2s-actions",
1365
- children: (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1366
- variant: "outline",
1367
- disabled: loading,
1368
- onClick: () => {
1369
- loadPage(page + 1);
1370
- },
1371
- children: t("keysLoadMore", { count: Math.max(0, Math.min(PAGE_SIZE, total - keys.length)) })
1372
- })
1428
+ keys.length > 0 && (pages > 1 || query !== "") && (0, react_jsx_runtime.jsxs)("div", {
1429
+ className: "s2s-pager",
1430
+ children: [
1431
+ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1432
+ variant: "outline",
1433
+ disabled: loading || page <= 1,
1434
+ onClick: () => {
1435
+ setPage(page - 1);
1436
+ },
1437
+ children: t("keysPagePrev")
1438
+ }),
1439
+ (0, react_jsx_runtime.jsx)("span", {
1440
+ className: "s2s-pageStatus",
1441
+ children: t("keysPageStatus", {
1442
+ page,
1443
+ pages,
1444
+ total
1445
+ })
1446
+ }),
1447
+ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1448
+ variant: "outline",
1449
+ disabled: loading || page >= pages,
1450
+ onClick: () => {
1451
+ setPage(page + 1);
1452
+ },
1453
+ children: t("keysPageNext")
1454
+ })
1455
+ ]
1373
1456
  }),
1374
1457
  (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Modal, {
1375
1458
  open: creating,
package/lib/index.js CHANGED
@@ -305,12 +305,13 @@ function buildProviderProfile(input) {
305
305
  const reasoning = input.template?.reasoningEfforts;
306
306
  const override = input.retryPolicy ?? {};
307
307
  const retryPolicy = {
308
- ...SUB2API_RETRY_POLICY,
309
- ...override,
310
- retryableCodes: [...override.retryableCodes ?? SUB2API_RETRY_POLICY.retryableCodes],
308
+ mode: override.mode ?? SUB2API_RETRY_POLICY.mode,
309
+ maxRetries: override.maxRetries ?? SUB2API_RETRY_POLICY.maxRetries,
310
+ retryableCodes: override.retryableCodes !== undefined && override.retryableCodes.length > 0 ? [...override.retryableCodes] : [...SUB2API_RETRY_POLICY.retryableCodes],
311
311
  backoff: {
312
- ...SUB2API_RETRY_POLICY.backoff,
313
- ...override.backoff
312
+ initialDelayMs: override.backoff?.initialDelayMs ?? SUB2API_RETRY_POLICY.backoff.initialDelayMs,
313
+ maxDelayMs: override.backoff?.maxDelayMs ?? SUB2API_RETRY_POLICY.backoff.maxDelayMs,
314
+ jitterRatio: override.backoff?.jitterRatio ?? SUB2API_RETRY_POLICY.backoff.jitterRatio
314
315
  }
315
316
  };
316
317
  return {
@@ -1829,6 +1830,8 @@ var PanelClient = class {
1829
1830
  timezone: timeZoneOf()
1830
1831
  });
1831
1832
  if (options?.groupId !== undefined) query.set("group_id", String(options.groupId));
1833
+ const search = options?.search?.trim();
1834
+ if (search !== undefined && search.length > 0) query.set("search", search);
1832
1835
  const data = await this.call(token, "GET", `/keys?${query.toString()}`);
1833
1836
  const record = data;
1834
1837
  const items = record?.items;
@@ -2206,11 +2209,11 @@ function conflictStatus(error) {
2206
2209
  function registerRoutes(ctx, deps) {
2207
2210
  ctx.inject(["webServer"], (webCtx) => {
2208
2211
  const route = (path, handler) => {
2209
- webCtx.webServer.register({
2212
+ webCtx.effect(() => webCtx.webServer.register({
2210
2213
  kind: "exact",
2211
2214
  path,
2212
2215
  handler
2213
- });
2216
+ }));
2214
2217
  };
2215
2218
  const guard = (req, res) => {
2216
2219
  if (!trustedRequest(req, deps.allowRemote())) {
@@ -2342,7 +2345,9 @@ function registerRoutes(ctx, deps) {
2342
2345
  const rawSize = Number(url.searchParams.get("pageSize") ?? "5");
2343
2346
  const page = Number.isSafeInteger(rawPage) && rawPage > 0 ? rawPage : 1;
2344
2347
  const pageSize = Number.isSafeInteger(rawSize) && rawSize > 0 ? Math.min(rawSize, 50) : 5;
2345
- const result = await deps.listKeys(page, pageSize);
2348
+ const rawSearch = (url.searchParams.get("search") ?? "").trim();
2349
+ const search = rawSearch.length > 0 ? rawSearch : undefined;
2350
+ const result = await deps.listKeys(page, pageSize, search);
2346
2351
  json(res, 200, {
2347
2352
  ok: true,
2348
2353
  keys: result.keys,
@@ -2528,6 +2533,8 @@ function registerRoutes(ctx, deps) {
2528
2533
  });
2529
2534
  }
2530
2535
  });
2536
+ }).then(undefined, (error) => {
2537
+ ctx.logger.error(error);
2531
2538
  });
2532
2539
  }
2533
2540
  /** Standalone failure page when the callback handler itself throws. Inline on
@@ -2688,10 +2695,11 @@ function apply(ctx, config) {
2688
2695
  },
2689
2696
  lastReport: () => reports.report(),
2690
2697
  lastReportAt: () => reports.at(),
2691
- listKeys: async (page, pageSize) => {
2698
+ listKeys: async (page, pageSize, search) => {
2692
2699
  const result = await gateway.use((panel, token) => panel.listKeys(token, {
2693
2700
  page,
2694
- pageSize
2701
+ pageSize,
2702
+ ...search !== undefined && search.length > 0 ? { search } : {}
2695
2703
  }));
2696
2704
  return {
2697
2705
  keys: result.items.map((key) => ({
package/lib/panel.d.ts CHANGED
@@ -100,6 +100,7 @@ export declare class PanelClient {
100
100
  groupId?: number;
101
101
  page?: number;
102
102
  pageSize?: number;
103
+ search?: string;
103
104
  sortBy?: string;
104
105
  sortOrder?: string;
105
106
  }): Promise<PanelKeyPage>;
package/lib/routes.d.ts CHANGED
@@ -193,8 +193,9 @@ export interface RouteDeps {
193
193
  sync(keyIds?: number[]): Promise<SyncReport>;
194
194
  lastReport(): SyncReport | undefined;
195
195
  lastReportAt(): number | undefined;
196
- /** One page of the panel's keys (newest first; page is 1-based). */
197
- listKeys(page: number, pageSize: number): Promise<KeysListPage>;
196
+ /** One page of the panel's keys (newest first; page is 1-based), optional
197
+ * server-side `search` over key names. */
198
+ listKeys(page: number, pageSize: number, search?: string): Promise<KeysListPage>;
198
199
  /** Create one relay key (name defaults to the group name upstream of this seam). */
199
200
  createKey(input: {
200
201
  groupId: number;
@@ -247,7 +248,16 @@ export interface RouteDeps {
247
248
  /** Drop a pending attempt (user cancelled the dialog). */
248
249
  oauthCancel(attemptId: string): Promise<void>;
249
250
  }
250
- /** Register all card routes on the web server (no-op without one). */
251
+ /** Register all card routes on the web server (no-op without one).
252
+ *
253
+ * Lifecycle: every registration's disposer rides the registering inject fiber
254
+ * (`webCtx.effect`). A plugin reload must remove its routes BEFORE the next
255
+ * apply re-registers them — the webserver rejects duplicate (kind, path)
256
+ * entries, and a dropped disposer would orphan the old entries: the new apply
257
+ * fails silently (an inject child failure does not fail the plugin fiber) and
258
+ * the table keeps serving the disposed fiber's closures, whose every service
259
+ * access then throws `cannot get required service … in inactive context`
260
+ * (the OAuth callback's 授权失败). */
251
261
  export declare function registerRoutes(ctx: Context, deps: RouteDeps): void;
252
262
  /**
253
263
  * Assemble the `ownedRoutes` view for the status endpoint from the live
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mangoc/dsh-sub2api-sync",
3
3
  "description": "Sync one sub2api instance's groups, keys, and models into dsh-llm-pi-ai provider routes: panel-driven login (password / OAuth), one relay key one provider.",
4
- "version": "0.2.1",
4
+ "version": "0.2.3",
5
5
  "license": "MIT",
6
6
  "author": "Shuai-S",
7
7
  "homepage": "https://github.com/Shuai-S/dsh-sub2api-sync#readme",