dsh-sub2api-sync 0.2.8 → 0.3.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/README.md +62 -80
- package/README.zh.md +15 -3
- package/docs/DETAIL.md +1 -0
- package/docs/DETAIL.zh.md +1 -0
- package/docs/preview.svg +36 -0
- package/docs/preview.zh.svg +36 -0
- package/icon.svg +10 -0
- package/lib/client.cjs +239 -36
- package/lib/client.js +239 -36
- package/lib/config.d.ts +12 -2
- package/lib/dshgo.d.ts +35 -0
- package/lib/index.js +329 -32
- package/lib/oauth.d.ts +15 -6
- package/lib/types.d.ts +7 -0
- package/locale/en.json +2 -1
- package/locale/zh.json +2 -1
- package/package.json +36 -2
package/lib/client.cjs
CHANGED
|
@@ -34,15 +34,17 @@ const PKG_NAME = __DSH_PLUGIN_NAME__;
|
|
|
34
34
|
const NS = "sub2api-sync";
|
|
35
35
|
const zh = {
|
|
36
36
|
nav: "Sub2API 同步",
|
|
37
|
-
localOnlyWarning: "
|
|
37
|
+
localOnlyWarning: "当前为跨设备连接:配置只读,仅本页生效",
|
|
38
38
|
configUnavailable: "配置暂不可用:宿主未连接或未暴露该设置条目",
|
|
39
|
+
routesUnreachableWarning: "动作路由不可达:当前连接可能经 DSH Remote 中继,或宿主半侧未就绪——登录、勾选与同步请回到电脑上的 DSH Web",
|
|
39
40
|
nameLine: "sub2api · {instance}",
|
|
40
41
|
nameLineSite: "{name} · {instance}",
|
|
41
42
|
balanceLabel: "余额 {amount}",
|
|
42
43
|
balanceTitle: "面板账号余额(同步结束自动刷新)",
|
|
43
|
-
signedOut: "
|
|
44
|
+
signedOut: "未连接面板",
|
|
44
45
|
signedInAs: "已登录:{email}",
|
|
45
46
|
signIn: "登录",
|
|
47
|
+
connectPanel: "连接面板",
|
|
46
48
|
signOut: "登出",
|
|
47
49
|
signOutDone: "已登出",
|
|
48
50
|
baseURLLabel: "实例地址",
|
|
@@ -51,8 +53,8 @@ const zh = {
|
|
|
51
53
|
savedNotice: "已保存",
|
|
52
54
|
writeRefused: "写入未被接受,已恢复为最新状态",
|
|
53
55
|
accountTitle: "面板账号",
|
|
54
|
-
accountSignedOutHint: "
|
|
55
|
-
signInTitle: "
|
|
56
|
+
accountSignedOutHint: "连接面板后自动发现分组并准备 key,勾选即可同步",
|
|
57
|
+
signInTitle: "连接 sub2api 面板",
|
|
56
58
|
channelLabel: "登录方式",
|
|
57
59
|
channelPassword: "密码登录",
|
|
58
60
|
channelOAuth: "网页授权",
|
|
@@ -66,7 +68,7 @@ const zh = {
|
|
|
66
68
|
signedInOAuthAccount: "{account} · 已通过网页授权登录",
|
|
67
69
|
emailPlaceholder: "you@example.com",
|
|
68
70
|
passwordLabel: "密码",
|
|
69
|
-
passwordHint: "
|
|
71
|
+
passwordHint: "留空则使用已保存的密码重新登录",
|
|
70
72
|
twoFactorPlaceholder: "6 位数字验证码",
|
|
71
73
|
twoFactorSubmit: "验证",
|
|
72
74
|
back: "返回",
|
|
@@ -94,7 +96,7 @@ const zh = {
|
|
|
94
96
|
actionFailed: "失败",
|
|
95
97
|
actionFailedLabel: "操作失败:{message}",
|
|
96
98
|
bundleTemplateLabel: "供应商名模板",
|
|
97
|
-
bundleTemplateHint: "
|
|
99
|
+
bundleTemplateHint: "供应商显示名模板,须包含 {keyName} 占位符",
|
|
98
100
|
bundleTemplateInvalid: "模板必须包含 {keyName} 占位符",
|
|
99
101
|
bundleKeyQuotaLabel: "新建 key 配额(USD)",
|
|
100
102
|
bundleKeyQuotaHint: "自动建 key 时应用;留空不设置",
|
|
@@ -108,8 +110,8 @@ const zh = {
|
|
|
108
110
|
bundleDeleteOrphanHint: "仅对记录过 keyId 的分组生效",
|
|
109
111
|
bundleEnrichLabel: "补全模型元数据",
|
|
110
112
|
bundleEnrichHint: "同步时按模型 id 从 models.dev 补全容量元数据;关闭则仅用内置快照(离线)",
|
|
111
|
-
bundleAllowRemoteLabel: "
|
|
112
|
-
bundleAllowRemoteWarning: "
|
|
113
|
+
bundleAllowRemoteLabel: "允许局域网内其他设备操作面板",
|
|
114
|
+
bundleAllowRemoteWarning: "开启后,同一局域网里的设备无需在本机登录,也能修改模型路由和登录信息——请只在家中等可信网络开启",
|
|
113
115
|
bundleAdvancedHint: "protocolOverrides / compatTemplate 高级字段请编辑 cordis.patch.yml",
|
|
114
116
|
badgeManagedTag: "Sub2API 同步",
|
|
115
117
|
badgeManageHint: "管理入口:设置 → Sub2API 同步",
|
|
@@ -122,7 +124,9 @@ const zh = {
|
|
|
122
124
|
detailBadgeSynced: "已同步 {count} 组",
|
|
123
125
|
detailBadgeSignedOut: "未连接面板",
|
|
124
126
|
detailSectionTitle: "最近同步",
|
|
125
|
-
detailSectionEmpty: "
|
|
127
|
+
detailSectionEmpty: "还没有同步过",
|
|
128
|
+
detailConnectTitle: "面板连接",
|
|
129
|
+
detailConnectHint: "连接面板后即可勾选 key 并同步,无需前往设置页",
|
|
126
130
|
activationTitle: "开始使用 Sub2API 同步",
|
|
127
131
|
activationStep1: "1. 在设置中连接面板(账号密码或网页授权)",
|
|
128
132
|
activationStep2: "2. 在 Keys 卡勾选要同步的 key(可跨分组)",
|
|
@@ -137,9 +141,9 @@ const zh = {
|
|
|
137
141
|
keyNameLabel: "Key 名称",
|
|
138
142
|
keyNameHint: "默认与分组同名",
|
|
139
143
|
keyGroupPlaceholder: "选择分组",
|
|
140
|
-
keyCreated: "
|
|
144
|
+
keyCreated: "已创建并勾选:{name}",
|
|
141
145
|
keysListEmpty: "还没有 key",
|
|
142
|
-
keysListLoading: "正在读取
|
|
146
|
+
keysListLoading: "正在读取 key…",
|
|
143
147
|
keysSearchPlaceholder: "搜索 key…",
|
|
144
148
|
keysSearchEmpty: "没有匹配的 key",
|
|
145
149
|
syncSelected: "同步所选({count})",
|
|
@@ -150,15 +154,17 @@ const zh = {
|
|
|
150
154
|
};
|
|
151
155
|
const en = {
|
|
152
156
|
nav: "Sub2API Sync",
|
|
153
|
-
localOnlyWarning: "
|
|
157
|
+
localOnlyWarning: "Cross-device connection: configuration is read-only and applies to this page only",
|
|
154
158
|
configUnavailable: "Configuration is unavailable: the host is not connected or does not expose this entry",
|
|
159
|
+
routesUnreachableWarning: "Action routes are unreachable: this connection may go through a DSH-Remote relay or the host half is not ready — sign in, select keys and sync from the desktop DSH Web",
|
|
155
160
|
nameLine: "sub2api · {instance}",
|
|
156
161
|
nameLineSite: "{name} · {instance}",
|
|
157
162
|
balanceLabel: "Balance {amount}",
|
|
158
163
|
balanceTitle: "Panel account balance (refreshed after each sync)",
|
|
159
|
-
signedOut: "
|
|
164
|
+
signedOut: "Not connected",
|
|
160
165
|
signedInAs: "Signed in as {email}",
|
|
161
166
|
signIn: "Sign in",
|
|
167
|
+
connectPanel: "Connect panel",
|
|
162
168
|
signOut: "Sign out",
|
|
163
169
|
signOutDone: "Signed out",
|
|
164
170
|
baseURLLabel: "Instance address",
|
|
@@ -167,8 +173,8 @@ const en = {
|
|
|
167
173
|
savedNotice: "Saved",
|
|
168
174
|
writeRefused: "The write was not accepted; the latest state was restored",
|
|
169
175
|
accountTitle: "Panel account",
|
|
170
|
-
accountSignedOutHint: "
|
|
171
|
-
signInTitle: "
|
|
176
|
+
accountSignedOutHint: "Connect the panel to discover its groups and prepare a key per group; tick the ones to sync",
|
|
177
|
+
signInTitle: "Connect to the sub2api panel",
|
|
172
178
|
channelLabel: "Sign-in channel",
|
|
173
179
|
channelPassword: "Password",
|
|
174
180
|
channelOAuth: "Web authorization",
|
|
@@ -182,7 +188,7 @@ const en = {
|
|
|
182
188
|
signedInOAuthAccount: "{account} · Signed in via web authorization",
|
|
183
189
|
emailPlaceholder: "you@example.com",
|
|
184
190
|
passwordLabel: "Password",
|
|
185
|
-
passwordHint: "Leave empty to
|
|
191
|
+
passwordHint: "Leave empty to sign in again with the stored password",
|
|
186
192
|
twoFactorPlaceholder: "6-digit code",
|
|
187
193
|
twoFactorSubmit: "Verify",
|
|
188
194
|
back: "Back",
|
|
@@ -210,7 +216,7 @@ const en = {
|
|
|
210
216
|
actionFailed: "failed",
|
|
211
217
|
actionFailedLabel: "Operation failed: {message}",
|
|
212
218
|
bundleTemplateLabel: "Provider name template",
|
|
213
|
-
bundleTemplateHint: "
|
|
219
|
+
bundleTemplateHint: "Provider display-name template; must contain the {keyName} placeholder",
|
|
214
220
|
bundleTemplateInvalid: "The template must contain the {keyName} placeholder",
|
|
215
221
|
bundleKeyQuotaLabel: "Created key quota (USD)",
|
|
216
222
|
bundleKeyQuotaHint: "Applied to auto-created keys; empty leaves it unset",
|
|
@@ -224,8 +230,8 @@ const en = {
|
|
|
224
230
|
bundleDeleteOrphanHint: "Only affects groups whose keyId was recorded",
|
|
225
231
|
bundleEnrichLabel: "Enrich model metadata",
|
|
226
232
|
bundleEnrichHint: "Fill per-model capacity facts from models.dev at sync; off = bundled snapshot only (offline)",
|
|
227
|
-
bundleAllowRemoteLabel: "Allow
|
|
228
|
-
bundleAllowRemoteWarning: "When enabled, same
|
|
233
|
+
bundleAllowRemoteLabel: "Allow other devices on this network to use the panel",
|
|
234
|
+
bundleAllowRemoteWarning: "When enabled, any device on the same network can change model routes and sign-in details — turn on only on networks you trust (such as home)",
|
|
229
235
|
bundleAdvancedHint: "Edit cordis.patch.yml for the advanced protocolOverrides / compatTemplate fields",
|
|
230
236
|
badgeManagedTag: "Sub2API Sync",
|
|
231
237
|
badgeManageHint: "Manage in Settings → Sub2API Sync",
|
|
@@ -238,7 +244,9 @@ const en = {
|
|
|
238
244
|
detailBadgeSynced: "{count} group(s) synced",
|
|
239
245
|
detailBadgeSignedOut: "Panel not connected",
|
|
240
246
|
detailSectionTitle: "Last sync",
|
|
241
|
-
detailSectionEmpty: "Never synced
|
|
247
|
+
detailSectionEmpty: "Never synced",
|
|
248
|
+
detailConnectTitle: "Panel connection",
|
|
249
|
+
detailConnectHint: "Connect the panel to pick keys and sync — no need to visit the settings page",
|
|
242
250
|
activationTitle: "Get started with Sub2API Sync",
|
|
243
251
|
activationStep1: "1. Connect the panel in Settings (account password or web authorization)",
|
|
244
252
|
activationStep2: "2. Tick the keys to sync on the Keys card (cross-group)",
|
|
@@ -253,7 +261,7 @@ const en = {
|
|
|
253
261
|
keyNameLabel: "Key name",
|
|
254
262
|
keyNameHint: "Defaults to the group name",
|
|
255
263
|
keyGroupPlaceholder: "Pick a group",
|
|
256
|
-
keyCreated: "Created and
|
|
264
|
+
keyCreated: "Created and ticked: {name}",
|
|
257
265
|
keysListEmpty: "No keys yet",
|
|
258
266
|
keysListLoading: "Loading keys…",
|
|
259
267
|
keysSearchPlaceholder: "Search keys…",
|
|
@@ -433,9 +441,20 @@ function createPluginApi() {
|
|
|
433
441
|
//#endregion
|
|
434
442
|
//#region src/client/status.ts
|
|
435
443
|
const POLL_MS = 5e3;
|
|
444
|
+
/** Consecutive failed rounds before the card shows the unreachable banner. */
|
|
445
|
+
const DEGRADED_AFTER = 2;
|
|
436
446
|
let snapshot;
|
|
437
447
|
let timer;
|
|
438
448
|
const listeners = new Set();
|
|
449
|
+
/** Reachability of the plugin's action routes, as observed by the poller. */
|
|
450
|
+
let reachable = true;
|
|
451
|
+
let failures = 0;
|
|
452
|
+
const healthListeners = new Set();
|
|
453
|
+
function publishHealth(next) {
|
|
454
|
+
if (next === reachable) return;
|
|
455
|
+
reachable = next;
|
|
456
|
+
for (const listener of healthListeners) listener();
|
|
457
|
+
}
|
|
439
458
|
function publish(next) {
|
|
440
459
|
if (next === snapshot) return;
|
|
441
460
|
snapshot = next;
|
|
@@ -445,7 +464,12 @@ function publish(next) {
|
|
|
445
464
|
async function poll() {
|
|
446
465
|
try {
|
|
447
466
|
publish(await callJson(ROUTES.status));
|
|
448
|
-
|
|
467
|
+
failures = 0;
|
|
468
|
+
publishHealth(true);
|
|
469
|
+
} catch {
|
|
470
|
+
failures += 1;
|
|
471
|
+
if (failures >= DEGRADED_AFTER) publishHealth(false);
|
|
472
|
+
}
|
|
449
473
|
}
|
|
450
474
|
function subscribe(listener) {
|
|
451
475
|
listeners.add(listener);
|
|
@@ -470,6 +494,18 @@ const statusSource = {
|
|
|
470
494
|
function useStatus() {
|
|
471
495
|
return (0, react.useSyncExternalStore)(subscribe, getSnapshot);
|
|
472
496
|
}
|
|
497
|
+
const statusHealthSource = {
|
|
498
|
+
getSnapshot: () => reachable,
|
|
499
|
+
subscribe(fn) {
|
|
500
|
+
healthListeners.add(fn);
|
|
501
|
+
return () => {
|
|
502
|
+
healthListeners.delete(fn);
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
function useStatusHealth() {
|
|
507
|
+
return (0, react.useSyncExternalStore)(statusHealthSource.subscribe, statusHealthSource.getSnapshot);
|
|
508
|
+
}
|
|
473
509
|
function getSnapshot() {
|
|
474
510
|
return snapshot;
|
|
475
511
|
}
|
|
@@ -577,13 +613,20 @@ const CSS = `
|
|
|
577
613
|
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
|
578
614
|
}
|
|
579
615
|
|
|
616
|
+
/* flex: 1 1 auto is load-bearing: a shrink-to-fit (flex: none) WRAPPING flex
|
|
617
|
+
container sizes itself by its largest item's contribution, not the sum of
|
|
618
|
+
items, so an input+save pair inside sized .s2s-actions to the input alone
|
|
619
|
+
and pushed the button onto a second right-aligned line (the stacked
|
|
620
|
+
config-field rows). Filling the row keeps one line on desktop; the wrap
|
|
621
|
+
below still degrades gracefully when the row itself runs out of space. */
|
|
580
622
|
.s2s-actions {
|
|
581
623
|
display: flex;
|
|
582
624
|
justify-content: flex-end;
|
|
583
625
|
align-items: center;
|
|
584
626
|
gap: 10px;
|
|
585
627
|
flex-wrap: wrap;
|
|
586
|
-
flex:
|
|
628
|
+
flex: 1 1 auto;
|
|
629
|
+
min-width: 0;
|
|
587
630
|
}
|
|
588
631
|
|
|
589
632
|
/* The account-balance pill in the identity header's action row. */
|
|
@@ -934,6 +977,88 @@ const CSS = `
|
|
|
934
977
|
font-size: 13px;
|
|
935
978
|
line-height: 22px;
|
|
936
979
|
}
|
|
980
|
+
|
|
981
|
+
/* Mobile (dsh-Go web island / narrow windows): stack the row layouts, keep
|
|
982
|
+
interactive rows at Android's 44dp touch floor, and let the anchored group
|
|
983
|
+
panel clear the soft keyboard. The desktop spec above is untouched — every
|
|
984
|
+
rule here is scoped to the narrow viewport. */
|
|
985
|
+
@media (max-width: 640px) {
|
|
986
|
+
.s2s-section {
|
|
987
|
+
gap: 12px;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
.s2s-row,
|
|
991
|
+
.s2s-inline {
|
|
992
|
+
flex-direction: column;
|
|
993
|
+
align-items: stretch;
|
|
994
|
+
gap: 8px;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
.s2s-actions {
|
|
998
|
+
justify-content: flex-start;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
.s2s-actions .s2s-feedback {
|
|
1002
|
+
white-space: normal;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
.s2s-keyRow {
|
|
1006
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1007
|
+
min-height: 44px;
|
|
1008
|
+
padding: 8px 10px;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
.s2s-keyRow > :last-child {
|
|
1012
|
+
justify-self: start;
|
|
1013
|
+
text-align: left;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
.s2s-selectField,
|
|
1017
|
+
.s2s-groupOption {
|
|
1018
|
+
min-height: 44px;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
.s2s-input,
|
|
1022
|
+
.s2s-groupPicker {
|
|
1023
|
+
flex-basis: 100%;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
.s2s-searchInput {
|
|
1027
|
+
width: 100%;
|
|
1028
|
+
flex: 1 1 auto;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
.s2s-pager {
|
|
1032
|
+
justify-content: center;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
.s2s-footerStrip {
|
|
1036
|
+
flex-direction: column;
|
|
1037
|
+
align-items: flex-start;
|
|
1038
|
+
gap: 6px;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
.s2s-reportRemove {
|
|
1042
|
+
margin-left: 0;
|
|
1043
|
+
align-self: flex-end;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/* Keyboard clearance: the fixed-position panel must not run under the
|
|
1047
|
+
soft keyboard; dvh tracks the visual viewport on Android WebViews. */
|
|
1048
|
+
.s2s-groupPanel {
|
|
1049
|
+
max-height: 60dvh;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/* Notch and gesture-bar insets on standalone web islands. */
|
|
1054
|
+
@supports (padding: max(0px)) {
|
|
1055
|
+
@media (max-width: 640px) {
|
|
1056
|
+
.s2s-section {
|
|
1057
|
+
padding-left: max(8px, env(safe-area-inset-left));
|
|
1058
|
+
padding-right: max(8px, env(safe-area-inset-right));
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
937
1062
|
`;
|
|
938
1063
|
function ensureCss() {
|
|
939
1064
|
if (document.querySelector("style[data-plugin=\"dsh-sub2api-sync\"]") !== null) return;
|
|
@@ -1824,6 +1949,7 @@ function actionToneOf(action) {
|
|
|
1824
1949
|
}
|
|
1825
1950
|
function Sub2apiSyncSection({ t, api, config, useStatus: useStatus$1 }) {
|
|
1826
1951
|
const status = useStatus$1((value) => value);
|
|
1952
|
+
const routesReachable = useStatusHealth();
|
|
1827
1953
|
const snapshot$1 = useConfigForm(config);
|
|
1828
1954
|
const configValues = snapshot$1.value;
|
|
1829
1955
|
const writable = snapshot$1.writable;
|
|
@@ -2019,6 +2145,10 @@ function Sub2apiSyncSection({ t, api, config, useStatus: useStatus$1 }) {
|
|
|
2019
2145
|
className: "s2s-warn",
|
|
2020
2146
|
children: t("configUnavailable")
|
|
2021
2147
|
}),
|
|
2148
|
+
!routesReachable && (0, react_jsx_runtime.jsx)("p", {
|
|
2149
|
+
className: "s2s-warn",
|
|
2150
|
+
children: t("routesUnreachableWarning")
|
|
2151
|
+
}),
|
|
2022
2152
|
(0, react_jsx_runtime.jsx)("section", {
|
|
2023
2153
|
className: "s2s-card",
|
|
2024
2154
|
children: (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2072,7 +2202,7 @@ function Sub2apiSyncSection({ t, api, config, useStatus: useStatus$1 }) {
|
|
|
2072
2202
|
onClick: () => {
|
|
2073
2203
|
setSignInOpen(true);
|
|
2074
2204
|
},
|
|
2075
|
-
children: t("
|
|
2205
|
+
children: t("connectPanel")
|
|
2076
2206
|
})
|
|
2077
2207
|
]
|
|
2078
2208
|
})]
|
|
@@ -2248,22 +2378,32 @@ function parseNumber(text) {
|
|
|
2248
2378
|
function messageOf(error) {
|
|
2249
2379
|
return error instanceof Error ? error.message : String(error);
|
|
2250
2380
|
}
|
|
2251
|
-
function BundleConfigCard({ view,
|
|
2252
|
-
if (view !== "page"
|
|
2381
|
+
function BundleConfigCard({ view, t, config }) {
|
|
2382
|
+
if (view !== "page") return null;
|
|
2253
2383
|
return (0, react_jsx_runtime.jsx)(BundleConfigFields, {
|
|
2254
|
-
|
|
2384
|
+
config,
|
|
2255
2385
|
t
|
|
2256
2386
|
});
|
|
2257
2387
|
}
|
|
2258
|
-
function BundleConfigFields({
|
|
2259
|
-
const
|
|
2260
|
-
const
|
|
2388
|
+
function BundleConfigFields({ config, t }) {
|
|
2389
|
+
const snapshot$1 = useConfigForm(config);
|
|
2390
|
+
const values = snapshot$1.value ?? {};
|
|
2391
|
+
const writable = snapshot$1.writable;
|
|
2261
2392
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
2262
2393
|
const [feedback, setFeedback] = (0, react.useState)(undefined);
|
|
2263
2394
|
const run = (ops) => {
|
|
2264
2395
|
setBusy(true);
|
|
2265
2396
|
setFeedback(undefined);
|
|
2266
|
-
|
|
2397
|
+
const apply$1 = async () => {
|
|
2398
|
+
for (const op of ops) {
|
|
2399
|
+
const field = op.path[0];
|
|
2400
|
+
if (field === undefined || field === "") return false;
|
|
2401
|
+
const accepted = op.op === "set" ? await setField(config, field, op.value) : await unsetField(config, field);
|
|
2402
|
+
if (!accepted) return false;
|
|
2403
|
+
}
|
|
2404
|
+
return true;
|
|
2405
|
+
};
|
|
2406
|
+
apply$1().then((accepted) => {
|
|
2267
2407
|
setFeedback(accepted ? {
|
|
2268
2408
|
tone: "ok",
|
|
2269
2409
|
text: t("savedNotice")
|
|
@@ -2523,6 +2663,9 @@ function footerModel(status) {
|
|
|
2523
2663
|
...status?.lastReportAt !== undefined ? { lastReportAt: status.lastReportAt } : {}
|
|
2524
2664
|
};
|
|
2525
2665
|
}
|
|
2666
|
+
function detailCardModel(status) {
|
|
2667
|
+
return status?.auth?.signedIn === true ? "report" : "connect";
|
|
2668
|
+
}
|
|
2526
2669
|
function relativeMinutes(at, now) {
|
|
2527
2670
|
const minutes = Math.max(0, Math.floor((now - at) / 6e4));
|
|
2528
2671
|
if (minutes < 1) return { key: "footerJustNow" };
|
|
@@ -2608,11 +2751,64 @@ function DetailBadgeBody({ t }) {
|
|
|
2608
2751
|
});
|
|
2609
2752
|
}
|
|
2610
2753
|
function DetailSection(props) {
|
|
2611
|
-
const { subject, t } = props;
|
|
2754
|
+
const { subject, t, api, config } = props;
|
|
2612
2755
|
if (!isOwnBundle(subject)) return null;
|
|
2613
|
-
return (0, react_jsx_runtime.jsx)(DetailSectionBody, {
|
|
2756
|
+
return (0, react_jsx_runtime.jsx)(DetailSectionBody, {
|
|
2757
|
+
t,
|
|
2758
|
+
api,
|
|
2759
|
+
config
|
|
2760
|
+
});
|
|
2761
|
+
}
|
|
2762
|
+
function DetailSectionBody({ t, api, config }) {
|
|
2763
|
+
const status = useStatus();
|
|
2764
|
+
const reachable$1 = useStatusHealth();
|
|
2765
|
+
const snapshot$1 = useConfigForm(config);
|
|
2766
|
+
const [signInOpen, setSignInOpen] = (0, react.useState)(false);
|
|
2767
|
+
if (detailCardModel(status) === "connect") return (0, react_jsx_runtime.jsxs)("section", {
|
|
2768
|
+
className: "s2s-card",
|
|
2769
|
+
children: [
|
|
2770
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
2771
|
+
className: "s2s-name",
|
|
2772
|
+
children: t("detailConnectTitle")
|
|
2773
|
+
}),
|
|
2774
|
+
(0, react_jsx_runtime.jsx)("p", {
|
|
2775
|
+
className: "s2s-hint",
|
|
2776
|
+
children: t("detailConnectHint")
|
|
2777
|
+
}),
|
|
2778
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
2779
|
+
className: "s2s-actions",
|
|
2780
|
+
children: (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2781
|
+
variant: "primary",
|
|
2782
|
+
disabled: !reachable$1,
|
|
2783
|
+
onClick: () => {
|
|
2784
|
+
setSignInOpen(true);
|
|
2785
|
+
},
|
|
2786
|
+
children: t("connectPanel")
|
|
2787
|
+
})
|
|
2788
|
+
}),
|
|
2789
|
+
!reachable$1 && (0, react_jsx_runtime.jsx)("p", {
|
|
2790
|
+
className: "s2s-warn",
|
|
2791
|
+
children: t("routesUnreachableWarning")
|
|
2792
|
+
}),
|
|
2793
|
+
(0, react_jsx_runtime.jsx)(SignInDialog, {
|
|
2794
|
+
open: signInOpen,
|
|
2795
|
+
onClose: () => {
|
|
2796
|
+
setSignInOpen(false);
|
|
2797
|
+
},
|
|
2798
|
+
api,
|
|
2799
|
+
config,
|
|
2800
|
+
baseURL: snapshot$1.value?.baseURL,
|
|
2801
|
+
writable: snapshot$1.writable,
|
|
2802
|
+
onSignedIn: () => {
|
|
2803
|
+
void refreshStatus();
|
|
2804
|
+
},
|
|
2805
|
+
t
|
|
2806
|
+
})
|
|
2807
|
+
]
|
|
2808
|
+
});
|
|
2809
|
+
return (0, react_jsx_runtime.jsx)(DetailReportCard, { t });
|
|
2614
2810
|
}
|
|
2615
|
-
function
|
|
2811
|
+
function DetailReportCard({ t }) {
|
|
2616
2812
|
const status = useStatus();
|
|
2617
2813
|
const report = status?.lastReport;
|
|
2618
2814
|
return (0, react_jsx_runtime.jsxs)("section", {
|
|
@@ -2702,10 +2898,12 @@ function mount(ctx) {
|
|
|
2702
2898
|
locale: NS,
|
|
2703
2899
|
inject: () => sectionOps
|
|
2704
2900
|
}, Sub2apiSyncSection));
|
|
2901
|
+
const bundleConfigOps = { config };
|
|
2705
2902
|
ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
|
|
2706
2903
|
name: "plugins.bundle.config",
|
|
2707
2904
|
key: PKG_NAME,
|
|
2708
|
-
locale: NS
|
|
2905
|
+
locale: NS,
|
|
2906
|
+
inject: () => bundleConfigOps
|
|
2709
2907
|
}, BundleConfigCard));
|
|
2710
2908
|
ctx.slots.inject("settings.models.provider-card", () => ctx.slots.register({
|
|
2711
2909
|
name: "settings.models.provider-card",
|
|
@@ -2723,10 +2921,15 @@ function mount(ctx) {
|
|
|
2723
2921
|
id: "sub2api-sync",
|
|
2724
2922
|
locale: NS
|
|
2725
2923
|
}, DetailBadge));
|
|
2924
|
+
const detailOps = {
|
|
2925
|
+
api: sectionOps.api,
|
|
2926
|
+
config
|
|
2927
|
+
};
|
|
2726
2928
|
ctx.slots.inject("plugins.detail.section", () => ctx.slots.register({
|
|
2727
2929
|
name: "plugins.detail.section",
|
|
2728
2930
|
id: "sub2api-sync",
|
|
2729
|
-
locale: NS
|
|
2931
|
+
locale: NS,
|
|
2932
|
+
inject: () => detailOps
|
|
2730
2933
|
}, DetailSection));
|
|
2731
2934
|
const activationOps = { openBundleDetails: () => {
|
|
2732
2935
|
const nav = ctx.pluginNavigation;
|