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 +109 -26
- package/lib/client.js +109 -26
- package/lib/index.js +18 -10
- package/lib/panel.d.ts +1 -0
- package/lib/routes.d.ts +13 -3
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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 [
|
|
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
|
|
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(
|
|
1171
|
-
|
|
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(
|
|
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
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 &&
|
|
1358
|
-
className: "s2s-
|
|
1359
|
-
children:
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 [
|
|
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
|
|
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(
|
|
1177
|
-
|
|
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(
|
|
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
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 &&
|
|
1364
|
-
className: "s2s-
|
|
1365
|
-
children:
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
retryableCodes: [...override.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
|
-
|
|
313
|
-
|
|
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
|
|
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
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
|
-
|
|
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": "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.
|
|
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",
|