dsh-sub2api-sync 0.0.0-stage → 0.2.0

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 ADDED
@@ -0,0 +1,2425 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-sub2api-sync",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ "use strict";
8
+ //#region rolldown:runtime
9
+ var __create = Object.create;
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __getProtoOf = Object.getPrototypeOf;
14
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
17
+ key = keys[i];
18
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
19
+ get: ((k) => from[k]).bind(null, key),
20
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
21
+ });
22
+ }
23
+ return to;
24
+ };
25
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
26
+ value: mod,
27
+ enumerable: true
28
+ }) : target, mod));
29
+
30
+ //#endregion
31
+ const react = __toESM(require("react"));
32
+ const __deepseek_ai_dsh_client_ui_primitives = __toESM(require("@deepseek-ai/dsh-client-ui-primitives"));
33
+ const react_jsx_runtime = __toESM(require("react/jsx-runtime"));
34
+
35
+ //#region src/client/constants.ts
36
+ const PKG_NAME = "dsh-sub2api-sync";
37
+
38
+ //#endregion
39
+ //#region src/client/locales.ts
40
+ const NS = "sub2api-sync";
41
+ const zh = {
42
+ nav: "Sub2API 同步",
43
+ localOnlyWarning: "当前页面进程本地,配置仅本页生效",
44
+ configUnavailable: "配置暂不可用:宿主未连接或未暴露该设置条目",
45
+ nameLine: "sub2api · {instance}",
46
+ nameLineSite: "{name} · {instance}",
47
+ balanceLabel: "余额 {amount}",
48
+ balanceTitle: "面板账号余额(同步结束自动刷新)",
49
+ signedOut: "未登录",
50
+ signedInAs: "已登录:{email}",
51
+ signIn: "登录",
52
+ signOut: "登出",
53
+ signOutDone: "已登出",
54
+ baseURLLabel: "实例地址",
55
+ baseURLPlaceholder: "https://api.example.com(可带 /v1)",
56
+ save: "保存",
57
+ savedNotice: "已保存",
58
+ writeRefused: "写入未被接受,已恢复为最新状态",
59
+ accountTitle: "面板账号",
60
+ accountSignedOutHint: "登录后可发现分组、勾选同步,自动为每个分组准备 key",
61
+ signInTitle: "登录 sub2api 面板",
62
+ channelLabel: "登录方式",
63
+ channelPassword: "密码登录",
64
+ channelOAuth: "网页授权",
65
+ oauthStart: "在浏览器中授权",
66
+ oauthReadyHint: "该站点支持网页授权登录——点击「在浏览器中授权」跳转完成登录,完成后这里会自动继续。",
67
+ oauthPendingTitle: "等待浏览器授权…",
68
+ oauthPendingHint: "已在新标签页打开 sub2api 授权页——完成授权后这里会自动继续;若浏览器未打开,请检查弹窗拦截后重试。",
69
+ oauthOpenAgain: "重新打开授权页",
70
+ oauthFailed: "网页授权失败:{message}",
71
+ signedInOAuth: "已通过网页授权登录",
72
+ signedInOAuthAccount: "{account} · 已通过网页授权登录",
73
+ emailPlaceholder: "you@example.com",
74
+ passwordLabel: "密码",
75
+ passwordHint: "留空则使用宿主已保存的密码重新登录",
76
+ twoFactorPlaceholder: "6 位数字验证码",
77
+ twoFactorSubmit: "验证",
78
+ back: "返回",
79
+ cancel: "取消",
80
+ close: "关闭",
81
+ signedInNotice: "登录成功",
82
+ groupsRefresh: "刷新",
83
+ groupTypeTitle: "分组类型:{type} · 同步协议 {protocol}",
84
+ syncTitle: "同步",
85
+ syncNow: "立即同步",
86
+ syncing: "同步中…",
87
+ syncDone: "同步完成",
88
+ reportEmpty: "还没有同步过",
89
+ reportMeta: "写入 {writes} 项 · {attempts} 次尝试",
90
+ entryModels: "{name} · {count} 个模型",
91
+ entryError: "{name} · {error}",
92
+ entryRemove: "移除",
93
+ entryRemoveDone: "已移除 {name} 的同步",
94
+ reportRemoved: "已移除:{routes}",
95
+ reportWarningLine: "警告:{message}",
96
+ routesSummary: "已注册 {count} 条路由",
97
+ actionCreated: "新建",
98
+ actionUpdated: "更新",
99
+ actionUnchanged: "无变化",
100
+ actionFailed: "失败",
101
+ actionFailedLabel: "操作失败:{message}",
102
+ bundleTemplateLabel: "供应商名模板",
103
+ bundleTemplateHint: "displayName 模板,必须包含 {keyName} 占位符",
104
+ bundleTemplateInvalid: "模板必须包含 {keyName} 占位符",
105
+ bundleKeyQuotaLabel: "新建 key 配额(USD)",
106
+ bundleKeyQuotaHint: "自动建 key 时应用;留空不设置",
107
+ bundleKeyExpiresLabel: "新建 key 有效期(天)",
108
+ bundleKeyExpiresHint: "自动建 key 时应用;留空不过期",
109
+ bundleIntervalLabel: "自动同步间隔(毫秒)",
110
+ bundleIntervalHint: "至少 60000;留空关闭自动同步",
111
+ bundleIntervalInvalid: "间隔不能小于 60000",
112
+ bundleNumberInvalid: "请输入合法数字",
113
+ bundleDeleteOrphanLabel: "移除分组时吊销远端 key",
114
+ bundleDeleteOrphanHint: "仅对记录过 keyId 的分组生效",
115
+ bundleEnrichLabel: "补全模型元数据",
116
+ bundleEnrichHint: "同步时按模型 id 从 models.dev 补全容量元数据;关闭则仅用内置快照(离线)",
117
+ bundleAllowRemoteLabel: "允许私网设备调用动作接口",
118
+ bundleAllowRemoteWarning: "开启后同源或私网页面可改写模型路由与凭据——仅在可信内网开启",
119
+ bundleAdvancedHint: "protocolOverrides / compatTemplate 高级字段请编辑 cordis.patch.yml",
120
+ badgeManagedTag: "Sub2API 同步",
121
+ badgeManageHint: "管理入口:设置 → Sub2API 同步",
122
+ badgeModels: "{count} 个模型",
123
+ footerSyncedCount: "已同步 {count} 个分组",
124
+ footerSignedOut: "未连接面板",
125
+ footerNeverSynced: "尚未同步",
126
+ footerJustNow: "刚刚",
127
+ footerMinutesAgo: "{count} 分钟前",
128
+ detailBadgeSynced: "已同步 {count} 组",
129
+ detailBadgeSignedOut: "未连接面板",
130
+ detailSectionTitle: "最近同步",
131
+ detailSectionEmpty: "还没有同步过——在设置 → Sub2API 同步 完成首次同步",
132
+ activationTitle: "开始使用 Sub2API 同步",
133
+ activationStep1: "1. 在设置中连接面板(账号密码或网页授权)",
134
+ activationStep2: "2. 勾选要同步的分组(key 自动准备)",
135
+ activationStep3: "3. 点击「立即同步」,模型即可使用",
136
+ activationOpenConfig: "打开配置页",
137
+ activationDismiss: "知道了",
138
+ keysTitle: "Keys",
139
+ createKey: "创建 key",
140
+ keyGroupLabel: "所属分组",
141
+ keyNameLabel: "Key 名称",
142
+ keyNameHint: "默认与分组同名",
143
+ keyGroupPlaceholder: "选择分组",
144
+ keyCreated: "已创建并绑定:{name}",
145
+ keysListEmpty: "还没有 key",
146
+ keysListLoading: "正在读取 keys…",
147
+ syncSelected: "同步所选({count})",
148
+ syncSelectedDone: "同步完成:{ok}/{total} 条成功",
149
+ keysLoadMore: "加载更多(还有 {count} 个)"
150
+ };
151
+ const en = {
152
+ nav: "Sub2API Sync",
153
+ localOnlyWarning: "This page runs process-local; configuration applies to this page only",
154
+ configUnavailable: "Configuration is unavailable: the host is not connected or does not expose this entry",
155
+ nameLine: "sub2api · {instance}",
156
+ nameLineSite: "{name} · {instance}",
157
+ balanceLabel: "Balance {amount}",
158
+ balanceTitle: "Panel account balance (refreshed after each sync)",
159
+ signedOut: "Signed out",
160
+ signedInAs: "Signed in as {email}",
161
+ signIn: "Sign in",
162
+ signOut: "Sign out",
163
+ signOutDone: "Signed out",
164
+ baseURLLabel: "Instance address",
165
+ baseURLPlaceholder: "https://api.example.com (optionally with /v1)",
166
+ save: "Save",
167
+ savedNotice: "Saved",
168
+ writeRefused: "The write was not accepted; the latest state was restored",
169
+ accountTitle: "Panel account",
170
+ accountSignedOutHint: "Sign in to discover groups, pick the ones to sync, and let each group get its key prepared",
171
+ signInTitle: "Sign in to the sub2api panel",
172
+ channelLabel: "Sign-in channel",
173
+ channelPassword: "Password",
174
+ channelOAuth: "Web authorization",
175
+ oauthStart: "Authorize in browser",
176
+ oauthReadyHint: "This site supports web authorization — click \"Authorize in browser\" to sign in; this dialog continues automatically once done.",
177
+ oauthPendingTitle: "Waiting for browser authorization…",
178
+ oauthPendingHint: "The sub2api authorization page opened in a new tab — this dialog continues automatically. If no tab opened, check the pop-up blocker and retry.",
179
+ oauthOpenAgain: "Reopen the authorization page",
180
+ oauthFailed: "Web authorization failed: {message}",
181
+ signedInOAuth: "Signed in via web authorization",
182
+ signedInOAuthAccount: "{account} · Signed in via web authorization",
183
+ emailPlaceholder: "you@example.com",
184
+ passwordLabel: "Password",
185
+ passwordHint: "Leave empty to re-login with the password the host already stored",
186
+ twoFactorPlaceholder: "6-digit code",
187
+ twoFactorSubmit: "Verify",
188
+ back: "Back",
189
+ cancel: "Cancel",
190
+ close: "Close",
191
+ signedInNotice: "Signed in",
192
+ groupsRefresh: "Refresh",
193
+ groupTypeTitle: "Group type: {type} · syncs as {protocol}",
194
+ syncTitle: "Sync",
195
+ syncNow: "Sync now",
196
+ syncing: "Syncing…",
197
+ syncDone: "Sync finished",
198
+ reportEmpty: "Never synced",
199
+ reportMeta: "{writes} write(s) · {attempts} attempt(s)",
200
+ entryModels: "{name} · {count} model(s)",
201
+ entryError: "{name} · {error}",
202
+ entryRemove: "Remove",
203
+ entryRemoveDone: "Removed {name}",
204
+ reportRemoved: "Removed: {routes}",
205
+ reportWarningLine: "Warning: {message}",
206
+ routesSummary: "{count} route(s) registered",
207
+ actionCreated: "created",
208
+ actionUpdated: "updated",
209
+ actionUnchanged: "unchanged",
210
+ actionFailed: "failed",
211
+ actionFailedLabel: "Operation failed: {message}",
212
+ bundleTemplateLabel: "Provider name template",
213
+ bundleTemplateHint: "displayName template; must contain the {keyName} placeholder",
214
+ bundleTemplateInvalid: "The template must contain the {keyName} placeholder",
215
+ bundleKeyQuotaLabel: "Created key quota (USD)",
216
+ bundleKeyQuotaHint: "Applied to auto-created keys; empty leaves it unset",
217
+ bundleKeyExpiresLabel: "Created key expiry (days)",
218
+ bundleKeyExpiresHint: "Applied to auto-created keys; empty means no expiry",
219
+ bundleIntervalLabel: "Auto-sync interval (ms)",
220
+ bundleIntervalHint: "At least 60000; empty disables auto-sync",
221
+ bundleIntervalInvalid: "The interval must be at least 60000",
222
+ bundleNumberInvalid: "Enter a valid number",
223
+ bundleDeleteOrphanLabel: "Revoke the remote key when a group is removed",
224
+ bundleDeleteOrphanHint: "Only affects groups whose keyId was recorded",
225
+ bundleEnrichLabel: "Enrich model metadata",
226
+ bundleEnrichHint: "Fill per-model capacity facts from models.dev at sync; off = bundled snapshot only (offline)",
227
+ bundleAllowRemoteLabel: "Allow private-network devices to call action routes",
228
+ bundleAllowRemoteWarning: "When enabled, same-origin or private-network pages can rewrite model routes and credentials — enable only on a trusted network",
229
+ bundleAdvancedHint: "Edit cordis.patch.yml for the advanced protocolOverrides / compatTemplate fields",
230
+ badgeManagedTag: "Sub2API Sync",
231
+ badgeManageHint: "Manage in Settings → Sub2API Sync",
232
+ badgeModels: "{count} model(s)",
233
+ footerSyncedCount: "{count} group(s) synced",
234
+ footerSignedOut: "Panel not connected",
235
+ footerNeverSynced: "Never synced",
236
+ footerJustNow: "just now",
237
+ footerMinutesAgo: "{count} min ago",
238
+ detailBadgeSynced: "{count} group(s) synced",
239
+ detailBadgeSignedOut: "Panel not connected",
240
+ detailSectionTitle: "Last sync",
241
+ detailSectionEmpty: "Never synced — finish the first sync in Settings → Sub2API Sync",
242
+ activationTitle: "Get started with Sub2API Sync",
243
+ activationStep1: "1. Connect the panel in Settings (account password or web authorization)",
244
+ activationStep2: "2. Check the groups to sync (keys are prepared automatically)",
245
+ activationStep3: "3. Click \"Sync now\" and the models are ready",
246
+ activationOpenConfig: "Open configuration",
247
+ activationDismiss: "Got it",
248
+ keysTitle: "Keys",
249
+ createKey: "Create key",
250
+ keyGroupLabel: "Group",
251
+ keyNameLabel: "Key name",
252
+ keyNameHint: "Defaults to the group name",
253
+ keyGroupPlaceholder: "Pick a group",
254
+ keyCreated: "Created and bound: {name}",
255
+ keysListEmpty: "No keys yet",
256
+ keysListLoading: "Loading keys…",
257
+ syncSelected: "Sync selected ({count})",
258
+ syncSelectedDone: "Synced: {ok}/{total} succeeded",
259
+ keysLoadMore: "Load more ({count} left)"
260
+ };
261
+ const dictionaries = {
262
+ zh,
263
+ en
264
+ };
265
+
266
+ //#endregion
267
+ //#region src/types.ts
268
+ const PLATFORM_LABELS = {
269
+ anthropic: "Anthropic",
270
+ openai: "OpenAI",
271
+ gemini: "Gemini",
272
+ antigravity: "Antigravity",
273
+ grok: "Grok",
274
+ kimi: "Kimi",
275
+ zhipu: "Zhipu GLM",
276
+ deepseek: "DeepSeek",
277
+ composite: "Composite"
278
+ };
279
+ function platformLabel(platform) {
280
+ return PLATFORM_LABELS[platform] ?? platform;
281
+ }
282
+ const OWNED_ROUTE = /^(?:sub2api-[0-9a-f]{10}|sub2api-\d+)$/;
283
+ function isOwnedRoute(route) {
284
+ return OWNED_ROUTE.test(route);
285
+ }
286
+ /** Round constants for SHA-256 (FIPS 180-4 §4.2.2). */
287
+ const SHA256_K = new Uint32Array([
288
+ 1116352408,
289
+ 1899447441,
290
+ 3049323471,
291
+ 3921009573,
292
+ 961987163,
293
+ 1508970993,
294
+ 2453635748,
295
+ 2870763221,
296
+ 3624381080,
297
+ 310598401,
298
+ 607225278,
299
+ 1426881987,
300
+ 1925078388,
301
+ 2162078206,
302
+ 2614888103,
303
+ 3248222580,
304
+ 3835390401,
305
+ 4022224774,
306
+ 264347078,
307
+ 604807628,
308
+ 770255983,
309
+ 1249150122,
310
+ 1555081692,
311
+ 1996064986,
312
+ 2554220882,
313
+ 2821834349,
314
+ 2952996808,
315
+ 3210313671,
316
+ 3336571891,
317
+ 3584528711,
318
+ 113926993,
319
+ 338241895,
320
+ 666307205,
321
+ 773529912,
322
+ 1294757372,
323
+ 1396182291,
324
+ 1695183700,
325
+ 1986661051,
326
+ 2177026350,
327
+ 2456956037,
328
+ 2730485921,
329
+ 2820302411,
330
+ 3259730800,
331
+ 3345764771,
332
+ 3516065817,
333
+ 3600352804,
334
+ 4094571909,
335
+ 275423344,
336
+ 430227734,
337
+ 506948616,
338
+ 659060556,
339
+ 883997877,
340
+ 958139571,
341
+ 1322822218,
342
+ 1537002063,
343
+ 1747873779,
344
+ 1955562222,
345
+ 2024104815,
346
+ 2227730452,
347
+ 2361852424,
348
+ 2428436474,
349
+ 2756734187,
350
+ 3204031479,
351
+ 3329325298
352
+ ]);
353
+
354
+ //#endregion
355
+ //#region src/routes.ts
356
+ const ROUTES = {
357
+ sync: "/plugins/dsh-sub2api-sync/sync",
358
+ status: "/plugins/dsh-sub2api-sync/status",
359
+ login: "/plugins/dsh-sub2api-sync/login",
360
+ login2fa: "/plugins/dsh-sub2api-sync/login/2fa",
361
+ logout: "/plugins/dsh-sub2api-sync/logout",
362
+ groups: "/plugins/dsh-sub2api-sync/groups",
363
+ keys: "/plugins/dsh-sub2api-sync/keys",
364
+ icon: "/plugins/dsh-sub2api-sync/icon",
365
+ site: "/plugins/dsh-sub2api-sync/site",
366
+ account: "/plugins/dsh-sub2api-sync/account",
367
+ resetSync: "/plugins/dsh-sub2api-sync/reset-sync",
368
+ removeKey: "/plugins/dsh-sub2api-sync/remove-key",
369
+ oauthStart: "/plugins/dsh-sub2api-sync/oauth/start",
370
+ oauthProbe: "/plugins/dsh-sub2api-sync/oauth/probe",
371
+ oauthCallback: "/plugins/dsh-sub2api-sync/oauth/callback",
372
+ oauthStatus: "/plugins/dsh-sub2api-sync/oauth/status",
373
+ oauthCancel: "/plugins/dsh-sub2api-sync/oauth/cancel"
374
+ };
375
+
376
+ //#endregion
377
+ //#region src/client/usePluginApi.ts
378
+ async function callJson(path, init) {
379
+ const response = await fetch(path, {
380
+ ...init,
381
+ headers: {
382
+ accept: "application/json",
383
+ ...init?.body !== undefined ? { "content-type": "application/json" } : {},
384
+ ...init?.headers
385
+ }
386
+ });
387
+ let payload;
388
+ try {
389
+ payload = await response.json();
390
+ } catch {
391
+ throw new Error(`HTTP ${response.status}`);
392
+ }
393
+ if (!response.ok) {
394
+ const message = payload?.error;
395
+ throw new Error(typeof message === "string" && message.length > 0 ? message : `HTTP ${response.status}`);
396
+ }
397
+ return payload;
398
+ }
399
+ const post = (body) => ({
400
+ method: "POST",
401
+ ...body !== undefined ? { body: JSON.stringify(body) } : {}
402
+ });
403
+ function createPluginApi() {
404
+ return {
405
+ status: () => callJson(ROUTES.status),
406
+ login: (email, password) => callJson(ROUTES.login, post({
407
+ email,
408
+ ...password !== undefined && password.length > 0 ? { password } : {}
409
+ })),
410
+ login2fa: (code) => callJson(ROUTES.login2fa, post({ code })),
411
+ logout: () => callJson(ROUTES.logout, post()),
412
+ groups: () => callJson(ROUTES.groups),
413
+ listKeys: (page, pageSize) => callJson(`${ROUTES.keys}?page=${page}&pageSize=${pageSize}`),
414
+ createKey: (input) => callJson(ROUTES.keys, post(input)),
415
+ site: () => callJson(ROUTES.site),
416
+ account: () => callJson(ROUTES.account),
417
+ resetSyncState: (previousBaseURL) => callJson(ROUTES.resetSync, post(previousBaseURL !== undefined ? { previousBaseURL } : undefined)),
418
+ removeKey: (keyId) => callJson(ROUTES.removeKey, post({ keyId })),
419
+ sync: (keyIds) => callJson(ROUTES.sync, post(keyIds !== undefined ? { keyIds } : undefined)),
420
+ oauthStart: () => callJson(ROUTES.oauthStart, post()),
421
+ oauthProbe: (address) => callJson(`${ROUTES.oauthProbe}?address=${encodeURIComponent(address)}`),
422
+ oauthStatus: (attemptId) => callJson(attemptId !== undefined ? `${ROUTES.oauthStatus}?attempt=${encodeURIComponent(attemptId)}` : ROUTES.oauthStatus),
423
+ oauthCancel: (attemptId) => callJson(ROUTES.oauthCancel, post({ attemptId }))
424
+ };
425
+ }
426
+
427
+ //#endregion
428
+ //#region src/client/status.ts
429
+ const POLL_MS = 5e3;
430
+ let snapshot;
431
+ let timer;
432
+ const listeners = new Set();
433
+ function publish(next) {
434
+ if (next === snapshot) return;
435
+ snapshot = next;
436
+ for (const listener of listeners) listener();
437
+ }
438
+ /** One poll round; a failed round keeps the last good snapshot (transient). */
439
+ async function poll() {
440
+ try {
441
+ publish(await callJson(ROUTES.status));
442
+ } catch {}
443
+ }
444
+ function subscribe(listener) {
445
+ listeners.add(listener);
446
+ if (listeners.size === 1) {
447
+ void poll();
448
+ timer = setInterval(() => {
449
+ void poll();
450
+ }, POLL_MS);
451
+ }
452
+ return () => {
453
+ listeners.delete(listener);
454
+ if (listeners.size === 0 && timer !== undefined) {
455
+ clearInterval(timer);
456
+ timer = undefined;
457
+ }
458
+ };
459
+ }
460
+ const statusSource = {
461
+ getSnapshot: () => snapshot,
462
+ subscribe
463
+ };
464
+ function useStatus() {
465
+ return (0, react.useSyncExternalStore)(subscribe, getSnapshot);
466
+ }
467
+ function getSnapshot() {
468
+ return snapshot;
469
+ }
470
+ async function refreshStatus() {
471
+ await poll();
472
+ return snapshot;
473
+ }
474
+
475
+ //#endregion
476
+ //#region src/client/styles.ts
477
+ /**
478
+ * The plugin's one stylesheet, injected once as a `<style data-plugin>`
479
+ * element. Values follow the ui-settings-account `AccountSection.module.css`
480
+ * visual spec exactly (design note §4.4) — page root, cards, rows, identity
481
+ * slots, dividers, and actions — so the card sits next to the official
482
+ * Account section without visual drift. Class names carry the `s2s-` prefix
483
+ * because the sheet is global, not module-scoped.
484
+ *
485
+ * @module sub2api-sync/client/styles
486
+ */
487
+ const CSS = `
488
+ .s2s-section {
489
+ display: flex;
490
+ flex-direction: column;
491
+ gap: 16px;
492
+ padding: 8px 0;
493
+ font-size: 13px;
494
+ line-height: 22px;
495
+ color: var(--dsw-alias-label-primary);
496
+ }
497
+
498
+ .s2s-card {
499
+ display: flex;
500
+ flex-direction: column;
501
+ padding: 12px 16px;
502
+ border: 0.5px solid var(--dsw-alias-settings-card-stroke);
503
+ border-radius: var(--dsw-radius-xl);
504
+ background: var(--dsw-alias-settings-card-fill);
505
+ }
506
+
507
+ .s2s-row {
508
+ display: flex;
509
+ align-items: center;
510
+ justify-content: space-between;
511
+ gap: 16px;
512
+ min-height: 40px;
513
+ padding: 6px 0;
514
+ box-sizing: border-box;
515
+ }
516
+
517
+ .s2s-identity {
518
+ display: flex;
519
+ align-items: center;
520
+ gap: 10px;
521
+ min-height: 44px;
522
+ min-width: 0;
523
+ }
524
+
525
+ .s2s-avatar {
526
+ flex: none;
527
+ display: flex;
528
+ align-items: center;
529
+ justify-content: center;
530
+ width: 32px;
531
+ height: 32px;
532
+ border-radius: 50%;
533
+ color: var(--dsw-alias-label-tertiary);
534
+ background: var(--dsw-alias-bg-skeleton);
535
+ }
536
+
537
+ .s2s-avatar .s2s-favicon {
538
+ width: 18px;
539
+ height: 18px;
540
+ border-radius: 4px;
541
+ object-fit: contain;
542
+ }
543
+
544
+ .s2s-actions .s2s-feedback {
545
+ margin-right: 2px;
546
+ white-space: nowrap;
547
+ }
548
+
549
+ .s2s-identityCopy {
550
+ display: flex;
551
+ flex-direction: column;
552
+ min-width: 0;
553
+ }
554
+
555
+ .s2s-name {
556
+ font-size: 14px;
557
+ font-weight: 500;
558
+ overflow: hidden;
559
+ text-overflow: ellipsis;
560
+ white-space: nowrap;
561
+ }
562
+
563
+ .s2s-secondary {
564
+ color: var(--dsw-alias-label-tertiary);
565
+ overflow: hidden;
566
+ text-overflow: ellipsis;
567
+ white-space: nowrap;
568
+ }
569
+
570
+ .s2s-divider {
571
+ border-top: 0.5px solid var(--dsw-alias-border-l2);
572
+ }
573
+
574
+ .s2s-actions {
575
+ display: flex;
576
+ justify-content: flex-end;
577
+ align-items: center;
578
+ gap: 10px;
579
+ flex-wrap: wrap;
580
+ flex: none;
581
+ }
582
+
583
+ /* The account-balance pill in the identity header's action row. */
584
+ .s2s-balance {
585
+ flex: none;
586
+ padding: 3px 10px;
587
+ border: 0.5px solid var(--dsw-alias-border-l2);
588
+ border-radius: 999px;
589
+ background: var(--dsw-alias-bg-secondary);
590
+ color: var(--dsw-alias-label-primary);
591
+ font-size: 12px;
592
+ font-variant-numeric: tabular-nums;
593
+ white-space: nowrap;
594
+ }
595
+
596
+ .s2s-warn {
597
+ margin: 0;
598
+ padding: 8px 12px;
599
+ border: 0.5px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 35%, transparent);
600
+ border-radius: var(--dsw-radius-md);
601
+ color: var(--dsw-alias-state-warn-primary);
602
+ background: var(--dsw-alias-state-warn-tertiary);
603
+ }
604
+
605
+ .s2s-feedback {
606
+ margin: 0;
607
+ padding: 2px 0 6px;
608
+ overflow-wrap: anywhere;
609
+ }
610
+
611
+ .s2s-feedbackOk {
612
+ color: var(--dsw-alias-state-success-primary);
613
+ }
614
+
615
+ .s2s-feedbackError {
616
+ color: var(--dsw-alias-state-error-primary);
617
+ }
618
+
619
+ .s2s-column {
620
+ display: flex;
621
+ flex-direction: column;
622
+ gap: 8px;
623
+ min-width: 0;
624
+ }
625
+
626
+ .s2s-hint {
627
+ color: var(--dsw-alias-label-tertiary);
628
+ }
629
+
630
+ .s2s-inline {
631
+ display: flex;
632
+ align-items: center;
633
+ justify-content: space-between;
634
+ gap: 16px;
635
+ min-height: 40px;
636
+ padding: 6px 0;
637
+ box-sizing: border-box;
638
+ }
639
+
640
+ .s2s-keyRow {
641
+ display: grid;
642
+ /* Columns align across rows: name+checkbox / group / status / type. */
643
+ grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr) minmax(0, 0.8fr) minmax(0, 1fr);
644
+ align-items: center;
645
+ gap: 10px;
646
+ width: 100%;
647
+ min-height: 36px;
648
+ padding: 4px 10px;
649
+ box-sizing: border-box;
650
+ border: 0.5px solid var(--dsw-alias-border-l2);
651
+ border-radius: var(--dsw-radius-md);
652
+ background: transparent;
653
+ color: inherit;
654
+ font: inherit;
655
+ text-align: left;
656
+ cursor: pointer;
657
+ user-select: none;
658
+ }
659
+
660
+ .s2s-keyRow > :last-child {
661
+ justify-self: end;
662
+ text-align: right;
663
+ }
664
+
665
+ .s2s-keyRow:hover {
666
+ background: var(--dsw-alias-interactive-bg-hover);
667
+ }
668
+
669
+ .s2s-keyRow:focus-visible {
670
+ outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
671
+ outline-offset: 2px;
672
+ }
673
+
674
+ .s2s-keyRow[aria-pressed='true'] {
675
+ border-color: var(--dsw-alias-state-business-primary);
676
+ }
677
+
678
+ .s2s-reportLine {
679
+ display: flex;
680
+ align-items: baseline;
681
+ gap: 8px;
682
+ padding: 2px 0;
683
+ min-width: 0;
684
+ }
685
+
686
+ .s2s-reportRemove {
687
+ margin-left: auto;
688
+ flex: none;
689
+ align-self: center;
690
+ }
691
+
692
+ .s2s-reportMark {
693
+ flex: none;
694
+ font-weight: 500;
695
+ }
696
+
697
+ .s2s-reportMark[data-mark='ok']::before {
698
+ content: '\\2713';
699
+ color: var(--dsw-alias-state-success-primary);
700
+ }
701
+
702
+ .s2s-reportMark[data-mark='fail']::before {
703
+ content: '\\2717';
704
+ color: var(--dsw-alias-state-error-primary);
705
+ }
706
+
707
+ .s2s-reportText {
708
+ min-width: 0;
709
+ overflow-wrap: anywhere;
710
+ }
711
+
712
+ .s2s-reportError {
713
+ color: var(--dsw-alias-state-error-primary);
714
+ }
715
+
716
+ .s2s-fieldLabel {
717
+ color: var(--dsw-alias-label-primary);
718
+ flex: none;
719
+ }
720
+
721
+ .s2s-input {
722
+ min-width: 0;
723
+ flex: 1 1 220px;
724
+ }
725
+
726
+ /* Menu anchor wrapper: give the picker trigger the same field-column
727
+ footprint as the sibling Input rows. */
728
+ .s2s-menuAnchor {
729
+ display: flex;
730
+ flex: 1 1 220px;
731
+ min-width: 0;
732
+ }
733
+
734
+ .s2s-menuAnchor > * {
735
+ width: 100%;
736
+ }
737
+
738
+ /* Select-style trigger (group picker): label left, chevron right. */
739
+ .s2s-selectTrigger {
740
+ display: flex;
741
+ align-items: center;
742
+ justify-content: space-between;
743
+ gap: 8px;
744
+ width: 100%;
745
+ }
746
+
747
+ .s2s-selectChevron {
748
+ flex: none;
749
+ opacity: 0.55;
750
+ }
751
+
752
+ /* U2 placements: the provider-card badge, the Models-page footer strip, and
753
+ the Plugins-page activation steps. */
754
+ .s2s-badgeRow {
755
+ display: flex;
756
+ align-items: center;
757
+ flex-wrap: wrap;
758
+ gap: 8px;
759
+ padding-top: 6px;
760
+ }
761
+
762
+ .s2s-badgeMeta {
763
+ color: var(--dsw-alias-label-secondary);
764
+ font-size: 12px;
765
+ line-height: 18px;
766
+ }
767
+
768
+ .s2s-badgeHint {
769
+ color: var(--dsw-alias-label-tertiary);
770
+ font-size: 12px;
771
+ line-height: 18px;
772
+ }
773
+
774
+ .s2s-footerStrip {
775
+ display: flex;
776
+ align-items: center;
777
+ justify-content: space-between;
778
+ gap: 16px;
779
+ min-height: 32px;
780
+ color: var(--dsw-alias-label-secondary);
781
+ font-size: 12px;
782
+ line-height: 18px;
783
+ }
784
+
785
+ .s2s-steps {
786
+ display: flex;
787
+ flex-direction: column;
788
+ gap: 6px;
789
+ margin: 0;
790
+ padding: 0;
791
+ list-style: none;
792
+ color: var(--dsw-alias-label-secondary);
793
+ font-size: 13px;
794
+ line-height: 22px;
795
+ }
796
+ `;
797
+ function ensureCss() {
798
+ if (document.querySelector("style[data-plugin=\"dsh-sub2api-sync\"]") !== null) return;
799
+ const style = document.createElement("style");
800
+ style.dataset.plugin = "dsh-sub2api-sync";
801
+ style.textContent = CSS;
802
+ document.head.appendChild(style);
803
+ }
804
+
805
+ //#endregion
806
+ //#region src/client/useConfigForm.ts
807
+ function useConfigForm(form) {
808
+ const subscribe$1 = (0, react.useMemo)(() => form.subscribe.bind(form), [form]);
809
+ const getSnapshot$1 = (0, react.useMemo)(() => form.getSnapshot.bind(form), [form]);
810
+ return (0, react.useSyncExternalStore)(subscribe$1, getSnapshot$1);
811
+ }
812
+ function setField(form, field, value) {
813
+ return form.set(field, value);
814
+ }
815
+ function unsetField(form, field) {
816
+ return form.unset(field);
817
+ }
818
+ async function pruneInvalidSelection(config, report) {
819
+ const invalid = new Set(report.entries.filter((entry) => entry.invalid === true).map((entry) => entry.groupId));
820
+ if (invalid.size === 0) return false;
821
+ const current = config.getSnapshot().value?.selectedKeys ?? [];
822
+ const next = current.filter((id) => !invalid.has(id));
823
+ if (next.length === current.length) return false;
824
+ await setField(config, "selectedKeys", [...next].sort((a, b) => a - b));
825
+ return true;
826
+ }
827
+ async function dropSelectedKey(config, keyId) {
828
+ const current = config.getSnapshot().value?.selectedKeys ?? [];
829
+ if (!current.includes(keyId)) return false;
830
+ await setField(config, "selectedKeys", current.filter((id) => id !== keyId));
831
+ return true;
832
+ }
833
+
834
+ //#endregion
835
+ //#region src/client/section/SignInDialog.tsx
836
+ const OAUTH_POLL_MS = 2e3;
837
+ function SignInDialog({ open, onClose, api, config, baseURL, writable, onSignedIn, t }) {
838
+ const [phase, setPhase] = (0, react.useState)("password");
839
+ const [channel, setChannel] = (0, react.useState)("password");
840
+ const [address, setAddress] = (0, react.useState)("");
841
+ const [email, setEmail] = (0, react.useState)("");
842
+ const [password, setPassword] = (0, react.useState)("");
843
+ const [code, setCode] = (0, react.useState)("");
844
+ const [busy, setBusy] = (0, react.useState)(false);
845
+ const [error, setError] = (0, react.useState)(undefined);
846
+ const [authorizeUrl, setAuthorizeUrl] = (0, react.useState)(undefined);
847
+ const attemptRef = (0, react.useRef)(undefined);
848
+ const channelTouchedRef = (0, react.useRef)(false);
849
+ (0, react.useEffect)(() => {
850
+ if (open) {
851
+ setAddress(baseURL ?? "");
852
+ channelTouchedRef.current = false;
853
+ return;
854
+ }
855
+ setPhase("password");
856
+ setChannel("password");
857
+ setEmail("");
858
+ setPassword("");
859
+ setCode("");
860
+ setBusy(false);
861
+ setError(undefined);
862
+ setAuthorizeUrl(undefined);
863
+ attemptRef.current = undefined;
864
+ }, [open, baseURL]);
865
+ (0, react.useEffect)(() => {
866
+ if (!open) return;
867
+ const value = address.trim();
868
+ if (!/^https?:\/\//i.test(value)) return;
869
+ if (phase !== "password" && phase !== "oauth") return;
870
+ const timer$1 = setTimeout(() => {
871
+ void api.oauthProbe(value).then((answer) => {
872
+ if (channelTouchedRef.current) return;
873
+ const next = answer.supported ? "oauth" : "password";
874
+ setChannel(next);
875
+ setPhase((prev) => prev === "password" || prev === "oauth" ? next : prev);
876
+ }).catch(() => undefined);
877
+ }, 600);
878
+ return () => {
879
+ clearTimeout(timer$1);
880
+ };
881
+ }, [
882
+ address,
883
+ open,
884
+ phase
885
+ ]);
886
+ (0, react.useEffect)(() => {
887
+ if (phase !== "oauth-pending") return;
888
+ let stopped = false;
889
+ const timer$1 = setInterval(() => {
890
+ const attemptId = attemptRef.current;
891
+ if (attemptId === undefined) return;
892
+ void api.oauthStatus(attemptId).then((status) => {
893
+ if (stopped) return;
894
+ if (status.auth.signedIn && status.auth.kind === "oauth") {
895
+ finish();
896
+ return;
897
+ }
898
+ const attempt = status.attempt;
899
+ if (attempt === undefined) {
900
+ stop();
901
+ setError(t("oauthFailed", { message: "expired" }));
902
+ setPhase("oauth");
903
+ return;
904
+ }
905
+ if (attempt.phase === "completed") {
906
+ finish();
907
+ return;
908
+ }
909
+ if (attempt.phase === "failed") {
910
+ stop();
911
+ setError(t("oauthFailed", { message: attempt.error ?? "unknown" }));
912
+ setPhase("oauth");
913
+ }
914
+ }).catch(() => undefined);
915
+ }, OAUTH_POLL_MS);
916
+ const stop = () => {
917
+ stopped = true;
918
+ clearInterval(timer$1);
919
+ };
920
+ return stop;
921
+ }, [phase]);
922
+ const pickChannel = (next) => {
923
+ channelTouchedRef.current = true;
924
+ setChannel(next);
925
+ setPhase(next);
926
+ setError(undefined);
927
+ };
928
+ const finish = () => {
929
+ onSignedIn();
930
+ onClose();
931
+ };
932
+ /** Commit the address draft before authenticating; aborts the flow on refusal. */
933
+ const commitAddress = async () => {
934
+ const value = address.trim();
935
+ if (value === (baseURL ?? "")) return;
936
+ const accepted = await (value.length === 0 ? unsetField(config, "baseURL") : setField(config, "baseURL", value));
937
+ if (!accepted) throw new Error(t("writeRefused"));
938
+ };
939
+ const run = async (action) => {
940
+ setBusy(true);
941
+ setError(undefined);
942
+ try {
943
+ await action();
944
+ } catch (cause) {
945
+ setError(cause instanceof Error ? cause.message : String(cause));
946
+ } finally {
947
+ setBusy(false);
948
+ }
949
+ };
950
+ const openAuthorizeTab = (url) => {
951
+ if (url === undefined) return;
952
+ window.open(url, "_blank", "noopener");
953
+ };
954
+ const submitPassword = () => {
955
+ void run(async () => {
956
+ await commitAddress();
957
+ const response = await api.login(email.trim(), password.length > 0 ? password : undefined);
958
+ if (response.kind === "2fa") {
959
+ setPhase("2fa");
960
+ return;
961
+ }
962
+ finish();
963
+ });
964
+ };
965
+ const submit2fa = () => {
966
+ void run(async () => {
967
+ await api.login2fa(code.trim());
968
+ finish();
969
+ });
970
+ };
971
+ const startOauth = () => {
972
+ void run(async () => {
973
+ await commitAddress();
974
+ const started = await api.oauthStart();
975
+ attemptRef.current = started.attemptId;
976
+ setAuthorizeUrl(started.authorizeUrl);
977
+ openAuthorizeTab(started.authorizeUrl);
978
+ setPhase("oauth-pending");
979
+ });
980
+ };
981
+ const cancelOauth = () => {
982
+ const attemptId = attemptRef.current;
983
+ attemptRef.current = undefined;
984
+ if (attemptId !== undefined) void api.oauthCancel(attemptId).catch(() => undefined);
985
+ setPhase("oauth");
986
+ };
987
+ const footer = (0, react_jsx_runtime.jsxs)("div", {
988
+ className: "s2s-actions",
989
+ children: [
990
+ phase === "2fa" && (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
991
+ variant: "outline",
992
+ disabled: busy,
993
+ onClick: () => pickChannel("password"),
994
+ children: t("back")
995
+ }),
996
+ phase === "oauth-pending" && (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
997
+ variant: "outline",
998
+ disabled: busy,
999
+ onClick: cancelOauth,
1000
+ children: t("back")
1001
+ }),
1002
+ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1003
+ variant: "ghost",
1004
+ disabled: busy,
1005
+ onClick: onClose,
1006
+ children: t("cancel")
1007
+ }),
1008
+ phase === "password" && (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1009
+ variant: "primary",
1010
+ disabled: busy || email.trim().length === 0 || address.trim().length === 0,
1011
+ onClick: submitPassword,
1012
+ children: t("signIn")
1013
+ }),
1014
+ phase === "oauth" && (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1015
+ variant: "primary",
1016
+ disabled: busy || address.trim().length === 0,
1017
+ onClick: startOauth,
1018
+ children: t("oauthStart")
1019
+ })
1020
+ ]
1021
+ });
1022
+ return (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Modal, {
1023
+ open,
1024
+ onClose,
1025
+ title: t("signInTitle"),
1026
+ closeLabel: t("close"),
1027
+ footer,
1028
+ children: (0, react_jsx_runtime.jsxs)("div", {
1029
+ className: "s2s-column",
1030
+ children: [
1031
+ (0, react_jsx_runtime.jsxs)("div", {
1032
+ className: "s2s-inline",
1033
+ children: [(0, react_jsx_runtime.jsx)("span", {
1034
+ className: "s2s-fieldLabel",
1035
+ children: t("baseURLLabel")
1036
+ }), (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Input, {
1037
+ className: "s2s-input",
1038
+ type: "text",
1039
+ value: address,
1040
+ placeholder: t("baseURLPlaceholder"),
1041
+ disabled: busy || !writable || phase === "2fa" || phase === "oauth-pending",
1042
+ onChange: (event) => {
1043
+ setAddress(event.target.value);
1044
+ }
1045
+ })]
1046
+ }),
1047
+ phase !== "2fa" && phase !== "oauth-pending" && (0, react_jsx_runtime.jsxs)("div", {
1048
+ className: "s2s-inline",
1049
+ children: [(0, react_jsx_runtime.jsx)("span", {
1050
+ className: "s2s-fieldLabel",
1051
+ children: t("channelLabel")
1052
+ }), (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.SegmentedControl, {
1053
+ id: "sub2api-sync-signin-channel",
1054
+ value: channel,
1055
+ options: [{
1056
+ value: "password",
1057
+ label: t("channelPassword")
1058
+ }, {
1059
+ value: "oauth",
1060
+ label: t("channelOAuth")
1061
+ }],
1062
+ onChange: pickChannel,
1063
+ label: t("channelLabel"),
1064
+ disabled: busy
1065
+ })]
1066
+ }),
1067
+ phase === "password" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1068
+ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Input, {
1069
+ type: "email",
1070
+ value: email,
1071
+ placeholder: t("emailPlaceholder"),
1072
+ disabled: busy,
1073
+ autoFocus: true,
1074
+ onChange: (event) => {
1075
+ setEmail(event.target.value);
1076
+ }
1077
+ }),
1078
+ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Input, {
1079
+ type: "password",
1080
+ value: password,
1081
+ disabled: busy,
1082
+ placeholder: t("passwordLabel"),
1083
+ onChange: (event) => {
1084
+ setPassword(event.target.value);
1085
+ }
1086
+ }),
1087
+ (0, react_jsx_runtime.jsx)("span", {
1088
+ className: "s2s-hint",
1089
+ children: t("passwordHint")
1090
+ })
1091
+ ] }),
1092
+ phase === "oauth" && (0, react_jsx_runtime.jsx)("span", {
1093
+ className: "s2s-hint",
1094
+ children: t("oauthReadyHint")
1095
+ }),
1096
+ phase === "oauth-pending" && (0, react_jsx_runtime.jsxs)("div", {
1097
+ className: "s2s-column",
1098
+ children: [
1099
+ (0, react_jsx_runtime.jsx)("span", {
1100
+ className: "s2s-hint",
1101
+ children: t("oauthPendingTitle")
1102
+ }),
1103
+ (0, react_jsx_runtime.jsx)("span", {
1104
+ className: "s2s-hint",
1105
+ children: t("oauthPendingHint")
1106
+ }),
1107
+ authorizeUrl !== undefined && (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1108
+ variant: "outline",
1109
+ onClick: () => {
1110
+ openAuthorizeTab(authorizeUrl);
1111
+ },
1112
+ children: t("oauthOpenAgain")
1113
+ })
1114
+ ]
1115
+ }),
1116
+ phase === "2fa" && (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Input, {
1117
+ type: "text",
1118
+ inputMode: "numeric",
1119
+ maxLength: 6,
1120
+ value: code,
1121
+ placeholder: t("twoFactorPlaceholder"),
1122
+ disabled: busy,
1123
+ autoFocus: true,
1124
+ onChange: (event) => {
1125
+ setCode(event.target.value.replace(/\D/g, ""));
1126
+ }
1127
+ }),
1128
+ error !== undefined && (0, react_jsx_runtime.jsx)("p", {
1129
+ className: "s2s-feedback s2s-feedbackError",
1130
+ children: t("actionFailedLabel", { message: error })
1131
+ })
1132
+ ]
1133
+ })
1134
+ });
1135
+ }
1136
+
1137
+ //#endregion
1138
+ //#region src/protocols.ts
1139
+ function protocolForPlatform(platform) {
1140
+ switch (platform) {
1141
+ case "anthropic": return "anthropic-messages";
1142
+ case "openai":
1143
+ case "grok":
1144
+ case "kimi":
1145
+ case "zhipu":
1146
+ case "deepseek": return "openai-responses";
1147
+ case "gemini":
1148
+ case "antigravity":
1149
+ case "composite": return "openai-completions";
1150
+ default: return undefined;
1151
+ }
1152
+ }
1153
+
1154
+ //#endregion
1155
+ //#region src/client/section/KeysCard.tsx
1156
+ const PAGE_SIZE = 5;
1157
+ function KeysCard({ api, config, groups, t }) {
1158
+ const snapshot$1 = useConfigForm(config);
1159
+ const [keys, setKeys] = (0, react.useState)([]);
1160
+ const [page, setPage] = (0, react.useState)(0);
1161
+ const [pages, setPages] = (0, react.useState)(1);
1162
+ const [total, setTotal] = (0, react.useState)(0);
1163
+ const [loading, setLoading] = (0, react.useState)(false);
1164
+ const [listError, setListError] = (0, react.useState)(undefined);
1165
+ const [creating, setCreating] = (0, react.useState)(false);
1166
+ const [groupMenuOpen, setGroupMenuOpen] = (0, react.useState)(false);
1167
+ const [groupId, setGroupId] = (0, react.useState)("");
1168
+ const [name$1, setName] = (0, react.useState)("");
1169
+ const [nameTouched, setNameTouched] = (0, react.useState)(false);
1170
+ const [busy, setBusy] = (0, react.useState)(false);
1171
+ const [syncing, setSyncing] = (0, react.useState)(false);
1172
+ const [feedback, setFeedback] = (0, react.useState)(undefined);
1173
+ const loadPage = (next) => {
1174
+ setLoading(true);
1175
+ setListError(undefined);
1176
+ api.listKeys(next, PAGE_SIZE).then((response) => {
1177
+ setKeys((previous) => next === 1 ? response.keys : [...previous, ...response.keys]);
1178
+ setPages(response.pages);
1179
+ setTotal(response.total);
1180
+ setPage(next);
1181
+ }).catch((cause) => {
1182
+ setListError(cause instanceof Error ? cause.message : String(cause));
1183
+ }).finally(() => {
1184
+ setLoading(false);
1185
+ });
1186
+ };
1187
+ (0, react.useEffect)(() => {
1188
+ loadPage(1);
1189
+ }, [api]);
1190
+ const selectedIds = snapshot$1.value?.selectedKeys ?? [];
1191
+ const toggle = (keyId, next) => {
1192
+ const nextSet = new Set(selectedIds);
1193
+ if (next) nextSet.add(keyId);
1194
+ else nextSet.delete(keyId);
1195
+ void setField(config, "selectedKeys", [...nextSet].sort((a, b) => a - b));
1196
+ };
1197
+ const syncSelected = () => {
1198
+ if (selectedIds.length === 0) return;
1199
+ setSyncing(true);
1200
+ setFeedback(undefined);
1201
+ api.sync([...selectedIds]).then(async (report) => {
1202
+ const okCount = report.report.entries.filter((entry) => entry.action !== "failed").length;
1203
+ setFeedback({
1204
+ tone: okCount === report.report.entries.length ? "ok" : "error",
1205
+ text: t("syncSelectedDone", {
1206
+ ok: okCount,
1207
+ total: report.report.entries.length
1208
+ })
1209
+ });
1210
+ await pruneInvalidSelection(config, report.report);
1211
+ await refreshStatus();
1212
+ }).catch((cause) => {
1213
+ setFeedback({
1214
+ tone: "error",
1215
+ text: cause instanceof Error ? cause.message : String(cause)
1216
+ });
1217
+ }).finally(() => {
1218
+ setSyncing(false);
1219
+ });
1220
+ };
1221
+ const groupNameOf = (gid) => groups.find((group) => group.id === gid)?.name ?? `#${gid}`;
1222
+ /** Pick a group: the name field auto-fills with the group name unless typed. */
1223
+ const pickGroup = (gid) => {
1224
+ setGroupId(gid);
1225
+ if (!nameTouched) setName(groupNameOf(gid));
1226
+ };
1227
+ const closeCreate = () => {
1228
+ setCreating(false);
1229
+ setGroupId("");
1230
+ setName("");
1231
+ setNameTouched(false);
1232
+ };
1233
+ const create = () => {
1234
+ if (groupId === "") return;
1235
+ const finalName = (nameTouched ? name$1.trim() : "") || groupNameOf(groupId);
1236
+ setBusy(true);
1237
+ setFeedback(undefined);
1238
+ api.createKey({
1239
+ groupId,
1240
+ name: finalName
1241
+ }).then(async (response) => {
1242
+ const nextSet = new Set(snapshot$1.value?.selectedKeys ?? []);
1243
+ nextSet.add(response.key.id);
1244
+ await setField(config, "selectedKeys", [...nextSet].sort((a, b) => a - b));
1245
+ setFeedback({
1246
+ tone: "ok",
1247
+ text: t("keyCreated", { name: response.key.name })
1248
+ });
1249
+ closeCreate();
1250
+ loadPage(1);
1251
+ }).catch((cause) => {
1252
+ setFeedback({
1253
+ tone: "error",
1254
+ text: cause instanceof Error ? cause.message : String(cause)
1255
+ });
1256
+ }).finally(() => {
1257
+ setBusy(false);
1258
+ });
1259
+ };
1260
+ const createFooter = (0, react_jsx_runtime.jsxs)("div", {
1261
+ className: "s2s-actions",
1262
+ children: [(0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1263
+ variant: "ghost",
1264
+ disabled: busy,
1265
+ onClick: closeCreate,
1266
+ children: t("cancel")
1267
+ }), (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1268
+ variant: "primary",
1269
+ disabled: busy || groupId === "",
1270
+ onClick: create,
1271
+ children: t("createKey")
1272
+ })]
1273
+ });
1274
+ return (0, react_jsx_runtime.jsxs)("section", {
1275
+ className: "s2s-card",
1276
+ children: [
1277
+ (0, react_jsx_runtime.jsxs)("div", {
1278
+ className: "s2s-row",
1279
+ children: [(0, react_jsx_runtime.jsx)("span", {
1280
+ className: "s2s-name",
1281
+ children: t("keysTitle")
1282
+ }), (0, react_jsx_runtime.jsxs)("div", {
1283
+ className: "s2s-actions",
1284
+ children: [
1285
+ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1286
+ variant: "outline",
1287
+ onClick: () => {
1288
+ loadPage(1);
1289
+ },
1290
+ children: t("groupsRefresh")
1291
+ }),
1292
+ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1293
+ variant: "primary",
1294
+ disabled: syncing || selectedIds.length === 0,
1295
+ onClick: syncSelected,
1296
+ children: syncing ? t("syncing") : t("syncSelected", { count: selectedIds.length })
1297
+ }),
1298
+ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1299
+ variant: "primary",
1300
+ onClick: () => {
1301
+ setCreating(true);
1302
+ },
1303
+ children: t("createKey")
1304
+ })
1305
+ ]
1306
+ })]
1307
+ }),
1308
+ feedback !== undefined && (0, react_jsx_runtime.jsx)("p", {
1309
+ className: `s2s-feedback ${feedback.tone === "ok" ? "s2s-feedbackOk" : "s2s-feedbackError"}`,
1310
+ children: feedback.text
1311
+ }),
1312
+ listError !== undefined && (0, react_jsx_runtime.jsx)("p", {
1313
+ className: "s2s-feedback s2s-feedbackError",
1314
+ children: listError
1315
+ }),
1316
+ !loading && keys.length === 0 && listError === undefined && (0, react_jsx_runtime.jsx)("p", {
1317
+ className: "s2s-feedback s2s-hint",
1318
+ children: t("keysListEmpty")
1319
+ }),
1320
+ loading && keys.length === 0 && (0, react_jsx_runtime.jsx)("p", {
1321
+ className: "s2s-feedback s2s-hint",
1322
+ children: t("keysListLoading")
1323
+ }),
1324
+ keys.length > 0 && (0, react_jsx_runtime.jsx)("div", {
1325
+ className: "s2s-column",
1326
+ children: keys.map((key) => {
1327
+ const platform = groups.find((group) => group.id === key.groupId)?.platform;
1328
+ const protocol = platform === undefined ? undefined : protocolForPlatform(platform);
1329
+ return (0, react_jsx_runtime.jsxs)("div", {
1330
+ className: "s2s-keyRow",
1331
+ onClick: (event) => {
1332
+ if (event.target.closest("label") !== null) return;
1333
+ toggle(key.id, !selectedIds.includes(key.id));
1334
+ },
1335
+ children: [
1336
+ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Checkbox, {
1337
+ checked: selectedIds.includes(key.id),
1338
+ onChange: (next) => {
1339
+ toggle(key.id, next);
1340
+ },
1341
+ label: key.name
1342
+ }),
1343
+ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Tag, {
1344
+ tone: "neutral",
1345
+ children: groupNameOf(key.groupId)
1346
+ }),
1347
+ key.status !== undefined ? (0, react_jsx_runtime.jsx)("span", {
1348
+ className: "s2s-badgeHint",
1349
+ children: key.status
1350
+ }) : null,
1351
+ (0, react_jsx_runtime.jsx)("span", {
1352
+ className: "s2s-badgeHint",
1353
+ title: platform === undefined ? undefined : t("groupTypeTitle", {
1354
+ type: platformLabel(platform),
1355
+ protocol: protocol ?? "—"
1356
+ }),
1357
+ children: platform === undefined ? "—" : platformLabel(platform)
1358
+ })
1359
+ ]
1360
+ }, key.id);
1361
+ })
1362
+ }),
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
+ })
1373
+ }),
1374
+ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Modal, {
1375
+ open: creating,
1376
+ onClose: closeCreate,
1377
+ title: t("createKey"),
1378
+ closeLabel: t("close"),
1379
+ footer: createFooter,
1380
+ children: (0, react_jsx_runtime.jsxs)("div", {
1381
+ className: "s2s-column",
1382
+ children: [(0, react_jsx_runtime.jsxs)("div", {
1383
+ className: "s2s-inline",
1384
+ children: [(0, react_jsx_runtime.jsx)("span", {
1385
+ className: "s2s-fieldLabel",
1386
+ children: t("keyGroupLabel")
1387
+ }), (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Menu, {
1388
+ open: groupMenuOpen,
1389
+ anchor: (0, react_jsx_runtime.jsx)("span", {
1390
+ className: "s2s-menuAnchor",
1391
+ children: (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1392
+ variant: "outline",
1393
+ onClick: () => {
1394
+ setGroupMenuOpen(true);
1395
+ },
1396
+ children: (0, react_jsx_runtime.jsxs)("span", {
1397
+ className: "s2s-selectTrigger",
1398
+ children: [(0, react_jsx_runtime.jsx)("span", { children: groupId === "" ? t("keyGroupPlaceholder") : groupNameOf(groupId) }), (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {
1399
+ size: 14,
1400
+ className: "s2s-selectChevron"
1401
+ })]
1402
+ })
1403
+ })
1404
+ }),
1405
+ items: groups.map((group) => ({
1406
+ id: String(group.id),
1407
+ label: group.name
1408
+ })),
1409
+ selectedId: groupId === "" ? undefined : String(groupId),
1410
+ onSelect: (id) => {
1411
+ pickGroup(Number(id));
1412
+ setGroupMenuOpen(false);
1413
+ },
1414
+ onClose: () => {
1415
+ setGroupMenuOpen(false);
1416
+ }
1417
+ })]
1418
+ }), (0, react_jsx_runtime.jsxs)("div", {
1419
+ className: "s2s-inline",
1420
+ children: [(0, react_jsx_runtime.jsx)("span", {
1421
+ className: "s2s-fieldLabel",
1422
+ children: t("keyNameLabel")
1423
+ }), (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Input, {
1424
+ className: "s2s-input",
1425
+ type: "text",
1426
+ value: nameTouched ? name$1 : groupId === "" ? "" : groupNameOf(groupId),
1427
+ placeholder: t("keyNameHint"),
1428
+ disabled: busy || groupId === "",
1429
+ onChange: (event) => {
1430
+ setName(event.target.value);
1431
+ setNameTouched(true);
1432
+ }
1433
+ })]
1434
+ })]
1435
+ })
1436
+ })
1437
+ ]
1438
+ });
1439
+ }
1440
+
1441
+ //#endregion
1442
+ //#region src/client/section/header.ts
1443
+ function maskEmail(email) {
1444
+ const trimmed = email.trim();
1445
+ const at = trimmed.indexOf("@");
1446
+ if (at <= 0) return trimmed.length > 0 ? `${trimmed.slice(0, 1)}***` : "—";
1447
+ return `${trimmed.slice(0, 1)}***${trimmed.slice(at)}`;
1448
+ }
1449
+ function accountLabelOf(profile) {
1450
+ const username = profile?.username?.trim();
1451
+ if (username !== undefined && username.length > 0) return username;
1452
+ const email = profile?.email?.trim();
1453
+ return email !== undefined && email.length > 0 ? maskEmail(email) : undefined;
1454
+ }
1455
+ function formatBalance(amount) {
1456
+ const digits = amount > 0 && amount < .01 ? 6 : 2;
1457
+ return new Intl.NumberFormat("en-US", {
1458
+ style: "currency",
1459
+ currency: "USD",
1460
+ minimumFractionDigits: digits,
1461
+ maximumFractionDigits: digits
1462
+ }).format(amount);
1463
+ }
1464
+
1465
+ //#endregion
1466
+ //#region src/client/section/Sub2apiSyncSection.tsx
1467
+ function messageOf$1(error) {
1468
+ return error instanceof Error ? error.message : String(error);
1469
+ }
1470
+ /** The instance display host: the URL's host when parseable, else the raw value. */
1471
+ function hostOf(instance) {
1472
+ const trimmed = instance.trim();
1473
+ if (trimmed.length === 0) return "—";
1474
+ try {
1475
+ return new URL(trimmed).host;
1476
+ } catch {
1477
+ return trimmed;
1478
+ }
1479
+ }
1480
+ function actionKeyOf$1(action) {
1481
+ switch (action) {
1482
+ case "created": return "actionCreated";
1483
+ case "updated": return "actionUpdated";
1484
+ case "unchanged": return "actionUnchanged";
1485
+ case "failed": return "actionFailed";
1486
+ }
1487
+ }
1488
+ function actionToneOf(action) {
1489
+ switch (action) {
1490
+ case "created": return "success";
1491
+ case "updated": return "info";
1492
+ case "unchanged": return "outline";
1493
+ case "failed": return "danger";
1494
+ }
1495
+ }
1496
+ function Sub2apiSyncSection({ t, api, config, useStatus: useStatus$1 }) {
1497
+ const status = useStatus$1((value) => value);
1498
+ const snapshot$1 = useConfigForm(config);
1499
+ const configValues = snapshot$1.value;
1500
+ const writable = snapshot$1.writable;
1501
+ const auth = status?.auth;
1502
+ const signedIn = auth?.signedIn ?? false;
1503
+ const syncRunning = status?.syncRunning ?? false;
1504
+ const configuredBaseURL = configValues?.baseURL;
1505
+ const dotState = signedIn ? "done" : "idle";
1506
+ const instanceOrigin = (() => {
1507
+ try {
1508
+ return new URL(status?.instance ?? "").origin;
1509
+ } catch {
1510
+ return "";
1511
+ }
1512
+ })();
1513
+ const iconSrc = instanceOrigin !== "" ? `${ROUTES.icon}?instance=${encodeURIComponent(instanceOrigin)}` : undefined;
1514
+ const [faviconBroken, setFaviconBroken] = (0, react.useState)(false);
1515
+ (0, react.useEffect)(() => {
1516
+ setFaviconBroken(false);
1517
+ }, [iconSrc]);
1518
+ const previousSignedIn = (0, react.useRef)(signedIn);
1519
+ (0, react.useEffect)(() => {
1520
+ const was = previousSignedIn.current;
1521
+ previousSignedIn.current = signedIn;
1522
+ if (was && !signedIn) void setField(config, "selectedKeys", []);
1523
+ }, [signedIn, config]);
1524
+ const instanceMarker = (configValues?.baseURL ?? "").trim().replace(/\/+$/, "").toLowerCase();
1525
+ const previousInstance = (0, react.useRef)(undefined);
1526
+ (0, react.useEffect)(() => {
1527
+ const markerKey = `${PKG_NAME}:last-instance`;
1528
+ let stored;
1529
+ try {
1530
+ stored = window.localStorage.getItem(markerKey) ?? undefined;
1531
+ window.localStorage.setItem(markerKey, instanceMarker);
1532
+ } catch {}
1533
+ const was = previousInstance.current ?? stored;
1534
+ previousInstance.current = instanceMarker;
1535
+ if (was !== undefined && was.length > 0 && was !== instanceMarker) {
1536
+ void setField(config, "selectedKeys", []);
1537
+ void api.resetSyncState(was).catch(() => undefined);
1538
+ }
1539
+ }, [
1540
+ instanceMarker,
1541
+ api,
1542
+ config
1543
+ ]);
1544
+ const [siteName, setSiteName] = (0, react.useState)(undefined);
1545
+ (0, react.useEffect)(() => {
1546
+ if (!signedIn || instanceOrigin === "") {
1547
+ setSiteName(undefined);
1548
+ return;
1549
+ }
1550
+ let alive = true;
1551
+ api.site().then((response) => {
1552
+ if (alive) setSiteName(response.name);
1553
+ }).catch(() => {
1554
+ if (alive) setSiteName(undefined);
1555
+ });
1556
+ return () => {
1557
+ alive = false;
1558
+ };
1559
+ }, [
1560
+ api,
1561
+ signedIn,
1562
+ instanceOrigin
1563
+ ]);
1564
+ const [account, setAccount] = (0, react.useState)(undefined);
1565
+ (0, react.useEffect)(() => {
1566
+ if (!signedIn || syncRunning) return;
1567
+ let alive = true;
1568
+ api.account().then((response) => {
1569
+ if (!alive) return;
1570
+ if (response.error !== undefined) console.warn("[sub2api-sync] account:", response.error);
1571
+ setAccount({
1572
+ username: response.username,
1573
+ email: response.email,
1574
+ balance: response.balance
1575
+ });
1576
+ }).catch((cause) => {
1577
+ console.warn("[sub2api-sync] account fetch failed", cause);
1578
+ if (alive) setAccount(undefined);
1579
+ });
1580
+ return () => {
1581
+ alive = false;
1582
+ };
1583
+ }, [
1584
+ api,
1585
+ signedIn,
1586
+ syncRunning
1587
+ ]);
1588
+ const accountSegment = accountLabelOf(account);
1589
+ const nameLine = siteName !== undefined && siteName.length > 0 ? t("nameLineSite", {
1590
+ name: siteName,
1591
+ instance: hostOf(status?.instance ?? "")
1592
+ }) : t("nameLine", { instance: hostOf(status?.instance ?? "") });
1593
+ const secondary = !signedIn ? t("signedOut") : auth?.kind === "oauth" ? accountSegment !== undefined ? t("signedInOAuthAccount", { account: accountSegment }) : t("signedInOAuth") : t("signedInAs", { email: auth?.email ?? "" });
1594
+ const routeCount = status?.routes.length ?? 0;
1595
+ const selectedCount = configValues?.selectedKeys?.length ?? 0;
1596
+ const report = status?.lastReport;
1597
+ const [signInOpen, setSignInOpen] = (0, react.useState)(false);
1598
+ const [accountBusy, setAccountBusy] = (0, react.useState)(false);
1599
+ const [accountFeedback, setAccountFeedback] = (0, react.useState)(undefined);
1600
+ const [groups, setGroups] = (0, react.useState)(undefined);
1601
+ const fetchGroups = (0, react.useCallback)(() => {
1602
+ api.groups().then((response) => {
1603
+ setGroups(response.groups);
1604
+ }).catch(() => {
1605
+ setGroups(undefined);
1606
+ });
1607
+ }, [api]);
1608
+ (0, react.useEffect)(() => {
1609
+ if (!signedIn) {
1610
+ setGroups(undefined);
1611
+ return;
1612
+ }
1613
+ fetchGroups();
1614
+ }, [signedIn, fetchGroups]);
1615
+ const [syncBusy, setSyncBusy] = (0, react.useState)(false);
1616
+ const [syncFeedback, setSyncFeedback] = (0, react.useState)(undefined);
1617
+ const signOut = () => {
1618
+ setAccountBusy(true);
1619
+ setAccountFeedback(undefined);
1620
+ api.logout().then(() => {
1621
+ setAccountFeedback({
1622
+ tone: "ok",
1623
+ text: t("signOutDone")
1624
+ });
1625
+ return refreshStatus();
1626
+ }).catch((cause) => {
1627
+ setAccountFeedback({
1628
+ tone: "error",
1629
+ text: t("actionFailedLabel", { message: messageOf$1(cause) })
1630
+ });
1631
+ }).finally(() => {
1632
+ setAccountBusy(false);
1633
+ });
1634
+ };
1635
+ const onSignedIn = () => {
1636
+ setAccountFeedback({
1637
+ tone: "ok",
1638
+ text: t("signedInNotice")
1639
+ });
1640
+ void refreshStatus();
1641
+ };
1642
+ const syncNow = () => {
1643
+ setSyncBusy(true);
1644
+ setSyncFeedback(undefined);
1645
+ api.sync().then(async (response) => {
1646
+ setSyncFeedback({
1647
+ tone: "ok",
1648
+ text: t("syncDone")
1649
+ });
1650
+ await pruneInvalidSelection(config, response.report);
1651
+ return refreshStatus();
1652
+ }).catch((cause) => {
1653
+ setSyncFeedback({
1654
+ tone: "error",
1655
+ text: t("actionFailedLabel", { message: messageOf$1(cause) })
1656
+ });
1657
+ }).finally(() => {
1658
+ setSyncBusy(false);
1659
+ });
1660
+ };
1661
+ const [removingRoute, setRemovingRoute] = (0, react.useState)(undefined);
1662
+ const removeRoute = (route) => {
1663
+ const keyId = route.keyId;
1664
+ if (keyId === undefined || removingRoute !== undefined) return;
1665
+ setRemovingRoute(route.route);
1666
+ api.removeKey(keyId).then(async () => {
1667
+ setSyncFeedback({
1668
+ tone: "ok",
1669
+ text: t("entryRemoveDone", { name: route.displayName ?? route.route })
1670
+ });
1671
+ await dropSelectedKey(config, keyId).catch(() => undefined);
1672
+ await refreshStatus();
1673
+ }).catch((cause) => {
1674
+ setSyncFeedback({
1675
+ tone: "error",
1676
+ text: t("actionFailedLabel", { message: messageOf$1(cause) })
1677
+ });
1678
+ }).finally(() => {
1679
+ setRemovingRoute(undefined);
1680
+ });
1681
+ };
1682
+ return (0, react_jsx_runtime.jsxs)("div", {
1683
+ className: "s2s-section",
1684
+ children: [
1685
+ snapshot$1.status === "ready" && !writable && (0, react_jsx_runtime.jsx)("p", {
1686
+ className: "s2s-warn",
1687
+ children: t("localOnlyWarning")
1688
+ }),
1689
+ snapshot$1.status === "unavailable" && (0, react_jsx_runtime.jsx)("p", {
1690
+ className: "s2s-warn",
1691
+ children: t("configUnavailable")
1692
+ }),
1693
+ (0, react_jsx_runtime.jsx)("section", {
1694
+ className: "s2s-card",
1695
+ children: (0, react_jsx_runtime.jsxs)("div", {
1696
+ className: "s2s-row",
1697
+ children: [(0, react_jsx_runtime.jsxs)("div", {
1698
+ className: "s2s-identity",
1699
+ children: [(0, react_jsx_runtime.jsx)("div", {
1700
+ className: "s2s-avatar",
1701
+ children: signedIn && iconSrc !== undefined && !faviconBroken ? (0, react_jsx_runtime.jsx)("img", {
1702
+ className: "s2s-favicon",
1703
+ src: iconSrc,
1704
+ alt: "",
1705
+ onError: () => {
1706
+ setFaviconBroken(true);
1707
+ }
1708
+ }) : (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.StateDot, {
1709
+ state: dotState,
1710
+ size: 16
1711
+ })
1712
+ }), (0, react_jsx_runtime.jsxs)("div", {
1713
+ className: "s2s-identityCopy",
1714
+ children: [(0, react_jsx_runtime.jsx)("span", {
1715
+ className: "s2s-name",
1716
+ children: nameLine
1717
+ }), (0, react_jsx_runtime.jsx)("span", {
1718
+ className: "s2s-secondary",
1719
+ children: secondary
1720
+ })]
1721
+ })]
1722
+ }), (0, react_jsx_runtime.jsxs)("div", {
1723
+ className: "s2s-actions",
1724
+ children: [
1725
+ accountFeedback !== undefined && (0, react_jsx_runtime.jsx)("span", {
1726
+ className: `s2s-feedback ${accountFeedback.tone === "ok" ? "s2s-feedbackOk" : "s2s-feedbackError"}`,
1727
+ children: accountFeedback.text
1728
+ }),
1729
+ signedIn && account?.balance !== undefined && (0, react_jsx_runtime.jsx)("span", {
1730
+ className: "s2s-balance",
1731
+ title: t("balanceTitle"),
1732
+ children: t("balanceLabel", { amount: formatBalance(account.balance) })
1733
+ }),
1734
+ signedIn ? (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1735
+ variant: "outline",
1736
+ disabled: accountBusy || syncRunning,
1737
+ title: syncRunning ? t("syncing") : undefined,
1738
+ onClick: signOut,
1739
+ children: t("signOut")
1740
+ }) : (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1741
+ variant: "primary",
1742
+ disabled: accountBusy,
1743
+ onClick: () => {
1744
+ setSignInOpen(true);
1745
+ },
1746
+ children: t("signIn")
1747
+ })
1748
+ ]
1749
+ })]
1750
+ })
1751
+ }),
1752
+ !signedIn && (0, react_jsx_runtime.jsxs)("section", {
1753
+ className: "s2s-card",
1754
+ children: [(0, react_jsx_runtime.jsx)("div", {
1755
+ className: "s2s-row",
1756
+ children: (0, react_jsx_runtime.jsx)("span", {
1757
+ className: "s2s-name",
1758
+ children: t("accountTitle")
1759
+ })
1760
+ }), (0, react_jsx_runtime.jsx)("p", {
1761
+ className: "s2s-feedback s2s-hint",
1762
+ children: t("accountSignedOutHint")
1763
+ })]
1764
+ }),
1765
+ signedIn && (0, react_jsx_runtime.jsx)(KeysCard, {
1766
+ api,
1767
+ config,
1768
+ groups: groups ?? [],
1769
+ t
1770
+ }),
1771
+ (0, react_jsx_runtime.jsxs)("section", {
1772
+ className: "s2s-card",
1773
+ children: [
1774
+ (0, react_jsx_runtime.jsxs)("div", {
1775
+ className: "s2s-row",
1776
+ children: [(0, react_jsx_runtime.jsx)("span", {
1777
+ className: "s2s-name",
1778
+ children: t("syncTitle")
1779
+ }), (0, react_jsx_runtime.jsxs)("div", {
1780
+ className: "s2s-actions",
1781
+ children: [(syncRunning || syncBusy) && (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.TextShimmer, {
1782
+ active: true,
1783
+ children: t("syncing")
1784
+ }), (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1785
+ variant: "primary",
1786
+ disabled: !signedIn || !writable || syncBusy || syncRunning || selectedCount === 0,
1787
+ onClick: syncNow,
1788
+ children: t("syncNow")
1789
+ })]
1790
+ })]
1791
+ }),
1792
+ routeCount > 0 && (0, react_jsx_runtime.jsx)("p", {
1793
+ className: "s2s-feedback s2s-hint",
1794
+ children: t("routesSummary", { count: routeCount })
1795
+ }),
1796
+ status?.routes.map((route) => (0, react_jsx_runtime.jsxs)("div", {
1797
+ className: "s2s-reportLine",
1798
+ children: [
1799
+ (0, react_jsx_runtime.jsx)("span", {
1800
+ className: "s2s-reportMark",
1801
+ "data-mark": "ok",
1802
+ "aria-hidden": "true"
1803
+ }),
1804
+ (0, react_jsx_runtime.jsxs)("span", {
1805
+ className: "s2s-reportText",
1806
+ children: [
1807
+ t("entryModels", {
1808
+ name: route.displayName ?? route.route,
1809
+ count: route.models
1810
+ }),
1811
+ " ",
1812
+ route.api !== undefined ? (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Tag, {
1813
+ tone: "info",
1814
+ children: route.api
1815
+ }) : null
1816
+ ]
1817
+ }),
1818
+ route.keyId !== undefined && (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
1819
+ variant: "ghost",
1820
+ size: "sm",
1821
+ className: "s2s-reportRemove",
1822
+ disabled: removingRoute !== undefined,
1823
+ onClick: () => {
1824
+ removeRoute(route);
1825
+ },
1826
+ children: t("entryRemove")
1827
+ })
1828
+ ]
1829
+ }, route.route)),
1830
+ syncFeedback !== undefined && (0, react_jsx_runtime.jsx)("p", {
1831
+ className: `s2s-feedback ${syncFeedback.tone === "ok" ? "s2s-feedbackOk" : "s2s-feedbackError"}`,
1832
+ children: syncFeedback.text
1833
+ }),
1834
+ signedIn && (0, react_jsx_runtime.jsx)(SyncReportView, {
1835
+ t,
1836
+ report
1837
+ })
1838
+ ]
1839
+ }),
1840
+ (0, react_jsx_runtime.jsx)(SignInDialog, {
1841
+ open: signInOpen,
1842
+ onClose: () => {
1843
+ setSignInOpen(false);
1844
+ },
1845
+ api,
1846
+ config,
1847
+ baseURL: configuredBaseURL,
1848
+ writable,
1849
+ onSignedIn,
1850
+ t
1851
+ })
1852
+ ]
1853
+ });
1854
+ }
1855
+ /**
1856
+ * The last sync report — a hint about the previous run (meta line, per-entry
1857
+ * outcomes, removals, warnings). No actions live here: removal belongs on
1858
+ * the registered-routes list above, which reflects the live state.
1859
+ */
1860
+ function SyncReportView({ t, report }) {
1861
+ if (report === undefined) return (0, react_jsx_runtime.jsx)("p", {
1862
+ className: "s2s-feedback s2s-hint",
1863
+ children: t("reportEmpty")
1864
+ });
1865
+ return (0, react_jsx_runtime.jsxs)("div", {
1866
+ className: "s2s-column",
1867
+ children: [
1868
+ (0, react_jsx_runtime.jsx)("span", {
1869
+ className: "s2s-hint",
1870
+ children: t("reportMeta", {
1871
+ writes: report.writes,
1872
+ attempts: report.attempts
1873
+ })
1874
+ }),
1875
+ report.entries.map((entry, index) => (0, react_jsx_runtime.jsxs)("div", {
1876
+ className: "s2s-reportLine",
1877
+ children: [(0, react_jsx_runtime.jsx)("span", {
1878
+ className: "s2s-reportMark",
1879
+ "data-mark": entry.action === "failed" ? "fail" : "ok",
1880
+ "aria-hidden": "true"
1881
+ }), (0, react_jsx_runtime.jsxs)("span", {
1882
+ className: `s2s-reportText${entry.action === "failed" ? " s2s-reportError" : ""}`,
1883
+ children: [
1884
+ entry.action === "failed" ? t("entryError", {
1885
+ name: entry.displayName ?? entry.route,
1886
+ error: entry.error ?? ""
1887
+ }) : t("entryModels", {
1888
+ name: entry.displayName ?? entry.route,
1889
+ count: entry.models ?? 0
1890
+ }),
1891
+ " ",
1892
+ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Tag, {
1893
+ tone: actionToneOf(entry.action),
1894
+ children: t(actionKeyOf$1(entry.action))
1895
+ })
1896
+ ]
1897
+ })]
1898
+ }, `${entry.groupId}-${index}`)),
1899
+ report.removed.length > 0 && (0, react_jsx_runtime.jsx)("span", {
1900
+ className: "s2s-reportText",
1901
+ children: t("reportRemoved", { routes: report.removed.join(", ") })
1902
+ }),
1903
+ report.warnings.map((warning, index) => (0, react_jsx_runtime.jsx)("span", {
1904
+ className: "s2s-reportText s2s-reportError",
1905
+ children: t("reportWarningLine", { message: warning })
1906
+ }, index))
1907
+ ]
1908
+ });
1909
+ }
1910
+
1911
+ //#endregion
1912
+ //#region src/client/bundleConfig/BundleConfigCard.tsx
1913
+ function parseNumber(text) {
1914
+ const trimmed = text.trim();
1915
+ if (trimmed.length === 0) return "";
1916
+ const value = Number(trimmed);
1917
+ return Number.isFinite(value) ? value : "invalid";
1918
+ }
1919
+ function messageOf(error) {
1920
+ return error instanceof Error ? error.message : String(error);
1921
+ }
1922
+ function BundleConfigCard({ view, form, t }) {
1923
+ if (view !== "page" || form === undefined) return null;
1924
+ return (0, react_jsx_runtime.jsx)(BundleConfigFields, {
1925
+ form,
1926
+ t
1927
+ });
1928
+ }
1929
+ function BundleConfigFields({ form, t }) {
1930
+ const values = form.state.value ?? {};
1931
+ const writable = form.state.writable;
1932
+ const [busy, setBusy] = (0, react.useState)(false);
1933
+ const [feedback, setFeedback] = (0, react.useState)(undefined);
1934
+ const run = (ops) => {
1935
+ setBusy(true);
1936
+ setFeedback(undefined);
1937
+ form.mutate(ops).then((accepted) => {
1938
+ setFeedback(accepted ? {
1939
+ tone: "ok",
1940
+ text: t("savedNotice")
1941
+ } : {
1942
+ tone: "error",
1943
+ text: t("writeRefused")
1944
+ });
1945
+ }).catch((cause) => {
1946
+ setFeedback({
1947
+ tone: "error",
1948
+ text: t("actionFailedLabel", { message: messageOf(cause) })
1949
+ });
1950
+ }).finally(() => {
1951
+ setBusy(false);
1952
+ });
1953
+ };
1954
+ const [templateDraft, setTemplateDraft] = (0, react.useState)(values.providerNameTemplate ?? "");
1955
+ (0, react.useEffect)(() => {
1956
+ setTemplateDraft(values.providerNameTemplate ?? "");
1957
+ }, [values.providerNameTemplate]);
1958
+ const [quotaDraft, setQuotaDraft] = (0, react.useState)(values.keyQuota === undefined ? "" : String(values.keyQuota));
1959
+ (0, react.useEffect)(() => {
1960
+ setQuotaDraft(values.keyQuota === undefined ? "" : String(values.keyQuota));
1961
+ }, [values.keyQuota]);
1962
+ const [expiryDraft, setExpiryDraft] = (0, react.useState)(values.keyExpiresInDays === undefined ? "" : String(values.keyExpiresInDays));
1963
+ (0, react.useEffect)(() => {
1964
+ setExpiryDraft(values.keyExpiresInDays === undefined ? "" : String(values.keyExpiresInDays));
1965
+ }, [values.keyExpiresInDays]);
1966
+ const [intervalDraft, setIntervalDraft] = (0, react.useState)(values.syncIntervalMs === undefined ? "" : String(values.syncIntervalMs));
1967
+ (0, react.useEffect)(() => {
1968
+ setIntervalDraft(values.syncIntervalMs === undefined ? "" : String(values.syncIntervalMs));
1969
+ }, [values.syncIntervalMs]);
1970
+ const templateInvalid = !templateDraft.includes("{keyName}");
1971
+ const quotaParsed = parseNumber(quotaDraft);
1972
+ const quotaInvalid = quotaParsed === "invalid" || quotaParsed !== "" && quotaParsed < 0;
1973
+ const expiryParsed = parseNumber(expiryDraft);
1974
+ const expiryInvalid = expiryParsed === "invalid" || expiryParsed !== "" && (!Number.isInteger(expiryParsed) || expiryParsed < 1);
1975
+ const intervalParsed = parseNumber(intervalDraft);
1976
+ const intervalInvalid = intervalParsed === "invalid" || intervalParsed !== "" && (!Number.isInteger(intervalParsed) || intervalParsed < 6e4);
1977
+ const numberOp = (field, parsed) => parsed === "" ? {
1978
+ op: "unset",
1979
+ path: [field]
1980
+ } : {
1981
+ op: "set",
1982
+ path: [field],
1983
+ value: parsed
1984
+ };
1985
+ return (0, react_jsx_runtime.jsx)("div", {
1986
+ className: "s2s-section",
1987
+ children: (0, react_jsx_runtime.jsxs)("section", {
1988
+ className: "s2s-card",
1989
+ children: [
1990
+ (0, react_jsx_runtime.jsx)(TextFieldRow, {
1991
+ id: "sub2api-sync-provider-name-template",
1992
+ label: t("bundleTemplateLabel"),
1993
+ value: templateDraft,
1994
+ hint: t("bundleTemplateHint"),
1995
+ invalid: templateInvalid,
1996
+ invalidHint: t("bundleTemplateInvalid"),
1997
+ disabled: !writable || busy,
1998
+ saveDisabled: templateDraft.trim() === (values.providerNameTemplate ?? ""),
1999
+ onChange: setTemplateDraft,
2000
+ saveLabel: t("save"),
2001
+ onSave: () => {
2002
+ run([{
2003
+ op: "set",
2004
+ path: ["providerNameTemplate"],
2005
+ value: templateDraft.trim()
2006
+ }]);
2007
+ }
2008
+ }),
2009
+ (0, react_jsx_runtime.jsx)("div", { className: "s2s-divider" }),
2010
+ (0, react_jsx_runtime.jsx)(TextFieldRow, {
2011
+ id: "sub2api-sync-key-quota",
2012
+ label: t("bundleKeyQuotaLabel"),
2013
+ value: quotaDraft,
2014
+ hint: t("bundleKeyQuotaHint"),
2015
+ invalid: quotaInvalid,
2016
+ invalidHint: t("bundleNumberInvalid"),
2017
+ disabled: !writable || busy,
2018
+ saveDisabled: quotaDraft.trim() === (values.keyQuota === undefined ? "" : String(values.keyQuota)),
2019
+ onChange: setQuotaDraft,
2020
+ saveLabel: t("save"),
2021
+ onSave: () => {
2022
+ run([numberOp("keyQuota", quotaParsed)]);
2023
+ }
2024
+ }),
2025
+ (0, react_jsx_runtime.jsx)("div", { className: "s2s-divider" }),
2026
+ (0, react_jsx_runtime.jsx)(TextFieldRow, {
2027
+ id: "sub2api-sync-key-expiry",
2028
+ label: t("bundleKeyExpiresLabel"),
2029
+ value: expiryDraft,
2030
+ hint: t("bundleKeyExpiresHint"),
2031
+ invalid: expiryInvalid,
2032
+ invalidHint: t("bundleNumberInvalid"),
2033
+ disabled: !writable || busy,
2034
+ saveDisabled: expiryDraft.trim() === (values.keyExpiresInDays === undefined ? "" : String(values.keyExpiresInDays)),
2035
+ onChange: setExpiryDraft,
2036
+ saveLabel: t("save"),
2037
+ onSave: () => {
2038
+ run([numberOp("keyExpiresInDays", expiryParsed)]);
2039
+ }
2040
+ }),
2041
+ (0, react_jsx_runtime.jsx)("div", { className: "s2s-divider" }),
2042
+ (0, react_jsx_runtime.jsx)(TextFieldRow, {
2043
+ id: "sub2api-sync-sync-interval",
2044
+ label: t("bundleIntervalLabel"),
2045
+ value: intervalDraft,
2046
+ hint: t("bundleIntervalHint"),
2047
+ invalid: intervalInvalid,
2048
+ invalidHint: t("bundleIntervalInvalid"),
2049
+ disabled: !writable || busy,
2050
+ saveDisabled: intervalDraft.trim() === (values.syncIntervalMs === undefined ? "" : String(values.syncIntervalMs)),
2051
+ onChange: setIntervalDraft,
2052
+ saveLabel: t("save"),
2053
+ onSave: () => {
2054
+ run([numberOp("syncIntervalMs", intervalParsed)]);
2055
+ }
2056
+ }),
2057
+ (0, react_jsx_runtime.jsx)("div", { className: "s2s-divider" }),
2058
+ (0, react_jsx_runtime.jsx)(SwitchRow, {
2059
+ label: t("bundleDeleteOrphanLabel"),
2060
+ hint: t("bundleDeleteOrphanHint"),
2061
+ checked: values.deleteOrphanKeys === true,
2062
+ disabled: !writable || busy,
2063
+ onChange: (next) => {
2064
+ run([{
2065
+ op: "set",
2066
+ path: ["deleteOrphanKeys"],
2067
+ value: next
2068
+ }]);
2069
+ }
2070
+ }),
2071
+ (0, react_jsx_runtime.jsx)(SwitchRow, {
2072
+ label: t("bundleEnrichLabel"),
2073
+ hint: t("bundleEnrichHint"),
2074
+ checked: values.enrichModelMeta !== false,
2075
+ disabled: !writable || busy,
2076
+ onChange: (next) => {
2077
+ run([{
2078
+ op: "set",
2079
+ path: ["enrichModelMeta"],
2080
+ value: next
2081
+ }]);
2082
+ }
2083
+ }),
2084
+ (0, react_jsx_runtime.jsx)(SwitchRow, {
2085
+ label: t("bundleAllowRemoteLabel"),
2086
+ checked: values.allowRemoteActions === true,
2087
+ disabled: !writable || busy,
2088
+ onChange: (next) => {
2089
+ run([{
2090
+ op: "set",
2091
+ path: ["allowRemoteActions"],
2092
+ value: next
2093
+ }]);
2094
+ }
2095
+ }),
2096
+ (0, react_jsx_runtime.jsx)("p", {
2097
+ className: "s2s-feedback s2s-feedbackError",
2098
+ children: t("bundleAllowRemoteWarning")
2099
+ }),
2100
+ feedback !== undefined && (0, react_jsx_runtime.jsx)("p", {
2101
+ className: `s2s-feedback ${feedback.tone === "ok" ? "s2s-feedbackOk" : "s2s-feedbackError"}`,
2102
+ children: feedback.text
2103
+ }),
2104
+ (0, react_jsx_runtime.jsx)("span", {
2105
+ className: "s2s-hint",
2106
+ children: t("bundleAdvancedHint")
2107
+ })
2108
+ ]
2109
+ })
2110
+ });
2111
+ }
2112
+ /** One labeled text field with its own save control. */
2113
+ function TextFieldRow(props) {
2114
+ return (0, react_jsx_runtime.jsxs)("div", {
2115
+ className: "s2s-column",
2116
+ children: [(0, react_jsx_runtime.jsxs)("div", {
2117
+ className: "s2s-row",
2118
+ children: [(0, react_jsx_runtime.jsx)("label", {
2119
+ className: "s2s-fieldLabel",
2120
+ htmlFor: props.id,
2121
+ children: props.label
2122
+ }), (0, react_jsx_runtime.jsxs)("div", {
2123
+ className: "s2s-actions",
2124
+ children: [(0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Input, {
2125
+ id: props.id,
2126
+ className: "s2s-input",
2127
+ type: "text",
2128
+ value: props.value,
2129
+ disabled: props.disabled,
2130
+ "aria-invalid": props.invalid,
2131
+ onChange: (event) => {
2132
+ props.onChange(event.target.value);
2133
+ }
2134
+ }), (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
2135
+ variant: "primary",
2136
+ disabled: props.disabled || props.saveDisabled || props.invalid,
2137
+ onClick: props.onSave,
2138
+ children: props.saveLabel
2139
+ })]
2140
+ })]
2141
+ }), props.invalid ? (0, react_jsx_runtime.jsx)("span", {
2142
+ className: "s2s-feedback s2s-feedbackError",
2143
+ children: props.invalidHint
2144
+ }) : (0, react_jsx_runtime.jsx)("span", {
2145
+ className: "s2s-hint",
2146
+ children: props.hint
2147
+ })]
2148
+ });
2149
+ }
2150
+ /** One labeled switch row with its hint. */
2151
+ function SwitchRow(props) {
2152
+ return (0, react_jsx_runtime.jsxs)("div", {
2153
+ className: "s2s-column",
2154
+ children: [(0, react_jsx_runtime.jsxs)("div", {
2155
+ className: "s2s-row",
2156
+ children: [(0, react_jsx_runtime.jsx)("span", {
2157
+ className: "s2s-fieldLabel",
2158
+ children: props.label
2159
+ }), (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Switch, {
2160
+ checked: props.checked,
2161
+ disabled: props.disabled,
2162
+ label: props.label,
2163
+ onChange: props.onChange
2164
+ })]
2165
+ }), props.hint !== undefined && (0, react_jsx_runtime.jsx)("span", {
2166
+ className: "s2s-hint",
2167
+ children: props.hint
2168
+ })]
2169
+ });
2170
+ }
2171
+
2172
+ //#endregion
2173
+ //#region src/client/models/badge.ts
2174
+ function isSyncedRoute(routeId) {
2175
+ return isOwnedRoute(routeId);
2176
+ }
2177
+ function badgeModel(routeId, status) {
2178
+ if (status === undefined || !isOwnedRoute(routeId)) return undefined;
2179
+ const route = status.routes.find((candidate) => candidate.route === routeId);
2180
+ if (route === undefined) return undefined;
2181
+ const entry = status.lastReport?.entries.find((candidate) => candidate.route === routeId);
2182
+ return {
2183
+ models: route.models,
2184
+ ...route.displayName !== undefined ? { displayName: route.displayName } : {},
2185
+ ...entry?.action !== undefined ? { lastAction: entry.action } : {},
2186
+ ...entry?.error !== undefined ? { lastError: entry.error } : {},
2187
+ ...status.lastReportAt !== undefined ? { lastReportAt: status.lastReportAt } : {}
2188
+ };
2189
+ }
2190
+ function footerModel(status) {
2191
+ return {
2192
+ signedIn: status?.auth.signedIn === true,
2193
+ groups: status?.routes.length ?? 0,
2194
+ ...status?.lastReportAt !== undefined ? { lastReportAt: status.lastReportAt } : {}
2195
+ };
2196
+ }
2197
+ function relativeMinutes(at, now) {
2198
+ const minutes = Math.max(0, Math.floor((now - at) / 6e4));
2199
+ if (minutes < 1) return { key: "footerJustNow" };
2200
+ return {
2201
+ key: "footerMinutesAgo",
2202
+ count: minutes
2203
+ };
2204
+ }
2205
+
2206
+ //#endregion
2207
+ //#region src/client/models/ProviderBadge.tsx
2208
+ function actionKeyOf(action) {
2209
+ switch (action) {
2210
+ case "created": return "actionCreated";
2211
+ case "updated": return "actionUpdated";
2212
+ case "unchanged": return "actionUnchanged";
2213
+ case "failed": return "actionFailed";
2214
+ }
2215
+ }
2216
+ function ProviderBadge(props) {
2217
+ const { provider, t } = props;
2218
+ if (!isSyncedRoute(provider.provider)) return null;
2219
+ return (0, react_jsx_runtime.jsx)(ProviderBadgeBody, {
2220
+ routeId: provider.provider,
2221
+ t
2222
+ });
2223
+ }
2224
+ function ProviderBadgeBody({ routeId, t }) {
2225
+ const status = useStatus();
2226
+ const badge = badgeModel(routeId, status);
2227
+ if (badge === undefined) return null;
2228
+ return (0, react_jsx_runtime.jsxs)("div", {
2229
+ className: "s2s-badgeRow",
2230
+ children: [
2231
+ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Tag, {
2232
+ tone: "info",
2233
+ children: t("badgeManagedTag")
2234
+ }),
2235
+ (0, react_jsx_runtime.jsxs)("span", {
2236
+ className: "s2s-badgeMeta",
2237
+ children: [t("badgeModels", { count: badge.models }), badge.lastAction !== undefined ? ` · ${t(actionKeyOf(badge.lastAction))}` : ""]
2238
+ }),
2239
+ (0, react_jsx_runtime.jsx)("span", {
2240
+ className: "s2s-badgeHint",
2241
+ children: t("badgeManageHint")
2242
+ })
2243
+ ]
2244
+ });
2245
+ }
2246
+
2247
+ //#endregion
2248
+ //#region src/client/models/ModelsFooter.tsx
2249
+ function ModelsFooter(props) {
2250
+ const { t } = props;
2251
+ const status = useStatus();
2252
+ const footer = footerModel(status);
2253
+ const when = footer.lastReportAt !== undefined ? (() => {
2254
+ const relative = relativeMinutes(footer.lastReportAt, Date.now());
2255
+ return t(relative.key, relative.count !== undefined ? { count: relative.count } : {});
2256
+ })() : t("footerNeverSynced");
2257
+ return (0, react_jsx_runtime.jsxs)("div", {
2258
+ className: "s2s-footerStrip",
2259
+ children: [(0, react_jsx_runtime.jsx)("span", { children: footer.signedIn ? t("footerSyncedCount", { count: footer.groups }) : t("footerSignedOut") }), (0, react_jsx_runtime.jsx)("span", { children: when })]
2260
+ });
2261
+ }
2262
+
2263
+ //#endregion
2264
+ //#region src/client/plugins/Detail.tsx
2265
+ function isOwnBundle(subject) {
2266
+ return subject.kind === "bundle" && subject.pkg.name === PKG_NAME;
2267
+ }
2268
+ function DetailBadge(props) {
2269
+ const { subject, t } = props;
2270
+ if (!isOwnBundle(subject)) return null;
2271
+ return (0, react_jsx_runtime.jsx)(DetailBadgeBody, { t });
2272
+ }
2273
+ function DetailBadgeBody({ t }) {
2274
+ const status = useStatus();
2275
+ const footer = footerModel(status);
2276
+ return (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Tag, {
2277
+ tone: footer.signedIn ? "success" : "quiet",
2278
+ children: footer.signedIn ? t("detailBadgeSynced", { count: footer.groups }) : t("detailBadgeSignedOut")
2279
+ });
2280
+ }
2281
+ function DetailSection(props) {
2282
+ const { subject, t } = props;
2283
+ if (!isOwnBundle(subject)) return null;
2284
+ return (0, react_jsx_runtime.jsx)(DetailSectionBody, { t });
2285
+ }
2286
+ function DetailSectionBody({ t }) {
2287
+ const status = useStatus();
2288
+ const report = status?.lastReport;
2289
+ return (0, react_jsx_runtime.jsxs)("section", {
2290
+ className: "s2s-card",
2291
+ children: [(0, react_jsx_runtime.jsx)("span", {
2292
+ className: "s2s-name",
2293
+ children: t("detailSectionTitle")
2294
+ }), report === undefined ? (0, react_jsx_runtime.jsx)("span", {
2295
+ className: "s2s-badgeHint",
2296
+ children: t("detailSectionEmpty")
2297
+ }) : (0, react_jsx_runtime.jsxs)("ul", {
2298
+ className: "s2s-steps",
2299
+ children: [report.entries.map((entry) => (0, react_jsx_runtime.jsx)("li", {
2300
+ className: "s2s-reportLine",
2301
+ children: (0, react_jsx_runtime.jsx)("span", {
2302
+ className: entry.action === "failed" ? "s2s-reportError" : "s2s-reportText",
2303
+ children: entry.action === "failed" ? t("entryError", {
2304
+ name: entry.displayName ?? entry.route,
2305
+ error: entry.error ?? ""
2306
+ }) : t("entryModels", {
2307
+ name: entry.displayName ?? entry.route,
2308
+ count: entry.models ?? 0
2309
+ })
2310
+ })
2311
+ }, entry.route)), report.removed.length > 0 && (0, react_jsx_runtime.jsx)("li", {
2312
+ className: "s2s-reportLine",
2313
+ children: t("reportRemoved", { routes: report.removed.join(", ") })
2314
+ })]
2315
+ })]
2316
+ });
2317
+ }
2318
+
2319
+ //#endregion
2320
+ //#region src/client/plugins/ActivationGuide.tsx
2321
+ function ActivationGuide(props) {
2322
+ const { t, onDismiss, onOpenDetails } = props;
2323
+ return (0, react_jsx_runtime.jsxs)("div", {
2324
+ className: "s2s-card",
2325
+ children: [
2326
+ (0, react_jsx_runtime.jsx)("span", {
2327
+ className: "s2s-name",
2328
+ children: t("activationTitle")
2329
+ }),
2330
+ (0, react_jsx_runtime.jsxs)("ol", {
2331
+ className: "s2s-steps",
2332
+ children: [
2333
+ (0, react_jsx_runtime.jsx)("li", { children: t("activationStep1") }),
2334
+ (0, react_jsx_runtime.jsx)("li", { children: t("activationStep2") }),
2335
+ (0, react_jsx_runtime.jsx)("li", { children: t("activationStep3") })
2336
+ ]
2337
+ }),
2338
+ (0, react_jsx_runtime.jsxs)("div", {
2339
+ className: "s2s-actions",
2340
+ children: [(0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
2341
+ variant: "primary",
2342
+ onClick: onOpenDetails,
2343
+ children: t("activationOpenConfig")
2344
+ }), (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
2345
+ variant: "ghost",
2346
+ onClick: onDismiss,
2347
+ children: t("activationDismiss")
2348
+ })]
2349
+ })
2350
+ ]
2351
+ });
2352
+ }
2353
+
2354
+ //#endregion
2355
+ //#region src/client/mount.ts
2356
+ function mount(ctx) {
2357
+ ensureCss();
2358
+ ctx.effect(() => ctx.locale.register(NS, dictionaries), "sub2api-sync: dictionaries");
2359
+ const t = ctx.locale.bind(NS);
2360
+ const config = ctx.configForms.get(NS);
2361
+ const sectionOps = {
2362
+ api: createPluginApi(),
2363
+ config,
2364
+ hooks: { status: statusSource }
2365
+ };
2366
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
2367
+ name: "settings.section",
2368
+ id: "sub2api-sync",
2369
+ order: 30,
2370
+ label: () => t("nav"),
2371
+ locale: NS,
2372
+ inject: () => sectionOps
2373
+ }, Sub2apiSyncSection));
2374
+ ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
2375
+ name: "plugins.bundle.config",
2376
+ key: PKG_NAME,
2377
+ locale: NS
2378
+ }, BundleConfigCard));
2379
+ ctx.slots.inject("settings.models.provider-card", () => ctx.slots.register({
2380
+ name: "settings.models.provider-card",
2381
+ key: "llm-pi-ai",
2382
+ locale: NS
2383
+ }, ProviderBadge));
2384
+ ctx.slots.inject("settings.models.footer", () => ctx.slots.register({
2385
+ name: "settings.models.footer",
2386
+ id: "sub2api-sync",
2387
+ order: 10,
2388
+ locale: NS
2389
+ }, ModelsFooter));
2390
+ ctx.slots.inject("plugins.detail.badge", () => ctx.slots.register({
2391
+ name: "plugins.detail.badge",
2392
+ id: "sub2api-sync",
2393
+ locale: NS
2394
+ }, DetailBadge));
2395
+ ctx.slots.inject("plugins.detail.section", () => ctx.slots.register({
2396
+ name: "plugins.detail.section",
2397
+ id: "sub2api-sync",
2398
+ locale: NS
2399
+ }, DetailSection));
2400
+ ctx.slots.inject("plugins.bundle.activation", () => ctx.slots.register({
2401
+ name: "plugins.bundle.activation",
2402
+ key: PKG_NAME,
2403
+ locale: NS
2404
+ }, ActivationGuide));
2405
+ }
2406
+
2407
+ //#endregion
2408
+ //#region src/client/index.tsx
2409
+ const name = "dsh-sub2api-sync-client";
2410
+ const inject = [
2411
+ "slots",
2412
+ "locale",
2413
+ "configForms"
2414
+ ];
2415
+ function apply(ctx) {
2416
+ mount(ctx);
2417
+ }
2418
+
2419
+ //#endregion
2420
+ exports.apply = apply
2421
+ exports.inject = inject
2422
+ exports.name = name
2423
+ return module.exports;
2424
+ }
2425
+ });