dsh-ds-balance 2.3.1 → 3.1.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.
Files changed (42) hide show
  1. package/README.md +84 -178
  2. package/README_zh.md +140 -0
  3. package/lib/adapters/host-account.d.ts +22 -1
  4. package/lib/adapters/host-account.js +13 -0
  5. package/lib/client.js +619 -461
  6. package/lib/config.d.ts +28 -19
  7. package/lib/config.js +27 -19
  8. package/lib/domain/credential.d.ts +41 -0
  9. package/lib/domain/credential.js +52 -0
  10. package/lib/http/handlers.d.ts +33 -2
  11. package/lib/http/handlers.js +124 -31
  12. package/lib/http/routes.d.ts +5 -1
  13. package/lib/http/routes.js +2 -1
  14. package/lib/http/wire.d.ts +58 -1
  15. package/lib/http/wire.js +3 -1
  16. package/lib/index.d.ts +1 -1
  17. package/lib/index.js +35 -6
  18. package/lib/ports/account.d.ts +25 -0
  19. package/lib/services/key-resolver.d.ts +33 -3
  20. package/lib/services/key-resolver.js +41 -12
  21. package/lib/services/source-readers.js +3 -3
  22. package/lib/services/source-selector.d.ts +10 -0
  23. package/lib/services/source-selector.js +14 -4
  24. package/lib/services/sources-service.d.ts +148 -0
  25. package/lib/services/sources-service.js +209 -0
  26. package/lib/types/client/api-types.d.ts +8 -0
  27. package/lib/types/client/data.d.ts +72 -0
  28. package/lib/types/client/icons/currency.d.ts +26 -0
  29. package/lib/types/client/icons.d.ts +92 -0
  30. package/lib/types/client/locales.d.ts +15 -13
  31. package/lib/types/client/model.d.ts +10 -0
  32. package/lib/types/client/settings/AccountAvatar.d.ts +27 -0
  33. package/lib/types/client/settings/BalanceSettingsCard.d.ts +8 -1
  34. package/lib/types/client/settings/fields.d.ts +70 -39
  35. package/lib/types/client/settings/source-status.d.ts +43 -0
  36. package/lib/types/client/settings/use-config-form.d.ts +1 -27
  37. package/lib/types/client/settings/use-sources.d.ts +38 -0
  38. package/lib/version.d.ts +1 -1
  39. package/lib/version.js +1 -1
  40. package/package.json +35 -35
  41. package/README_en.md +0 -237
  42. package/lib/types/client/settings/use-credential-state.d.ts +0 -36
package/lib/client.js CHANGED
@@ -23,20 +23,19 @@ var __copyProps = (to, from, except, desc) => {
23
23
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
24
24
 
25
25
  // src/client/index.tsx
26
- var client_exports = {};
27
- __export(client_exports, {
26
+ var index_exports = {};
27
+ __export(index_exports, {
28
28
  BUNDLE_CONFIG_KEY: () => BUNDLE_CONFIG_KEY,
29
29
  ENTRY_ID: () => ENTRY_ID,
30
30
  SIDEBAR_ENTRY_ID: () => SIDEBAR_ENTRY_ID,
31
31
  apply: () => apply,
32
32
  inject: () => inject
33
33
  });
34
- module.exports = __toCommonJS(client_exports);
35
- var import_react6 = require("react");
34
+ module.exports = __toCommonJS(index_exports);
35
+ var import_react7 = require("react");
36
36
 
37
37
  // src/client/settings/BalanceSettingsCard.tsx
38
- var import_react4 = require("react");
39
- var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");
38
+ var import_react5 = require("react");
40
39
 
41
40
  // src/client/locales.ts
42
41
  var NS = "ds-balance";
@@ -50,6 +49,9 @@ var zh = {
50
49
  // 代码里只做拼接,绝不写死括号字符。
51
50
  "popover.source.key": "\uFF08API Key\uFF09",
52
51
  "popover.source.account": "\uFF08\u8D26\u53F7\u767B\u5F55\uFF09",
52
+ // 有身份时用它替掉上面那两句:括号里直接放「哪一把凭据」。括号照旧在词典里
53
+ // (中文全角、英文半角带前置空格),`{identity}` 由 interpolate 填。
54
+ "popover.source.identity": "\uFF08{identity}\uFF09",
53
55
  "popover.total": "\u4F59\u989D",
54
56
  "popover.granted": "\u8D60\u9001",
55
57
  "popover.toppedUp": "\u5145\u503C",
@@ -94,9 +96,6 @@ var zh = {
94
96
  "settings.group.refresh": "\u5237\u65B0",
95
97
  "settings.group.display": "\u5C55\u793A",
96
98
  "settings.group.thresholds": "\u9608\u503C",
97
- "settings.field.apiKey": "API Key",
98
- "settings.field.apiKeyRef": "API \u5BC6\u94A5\u5F15\u7528\u540D",
99
- "settings.field.baseUrl": "\u63A5\u53E3\u5730\u5740",
100
99
  "settings.field.serverRefreshSeconds": "\u670D\u52A1\u7AEF\u5237\u65B0\u9891\u7387\uFF08\u79D2\uFF09",
101
100
  "settings.field.clientPollSeconds": "\u5BA2\u6237\u7AEF\u8F6E\u8BE2\u9891\u7387\uFF08\u79D2\uFF09",
102
101
  "settings.field.manualRefreshCooldownSeconds": "\u624B\u52A8\u5237\u65B0\u51B7\u5374\uFF08\u79D2\uFF09",
@@ -105,8 +104,35 @@ var zh = {
105
104
  "settings.field.cnyCritical": "CNY \u544A\u6025",
106
105
  "settings.field.usdWarn": "USD \u9884\u8B66",
107
106
  "settings.field.usdCritical": "USD \u544A\u6025",
108
- "settings.hint.apiKeyRef": "\u51ED\u636E\u5B58\u50A8\u91CC\u7684\u6761\u76EE\u540D\uFF1B\u975E\u7A7A\u65F6\u4F18\u5148\u4E8E API Key\u3002",
109
- "settings.hint.baseUrl": "\u7559\u7A7A\u5219\u4F7F\u7528\u63D0\u4F9B\u65B9\u9ED8\u8BA4\u5730\u5740\u3002",
107
+ // ── 「连接」组:两条取数途径的只读状态 ──────────────────────────
108
+ // 措辞逐条对齐官方:账号那几句取自 ui-settings-account 的 signedIn / signedOut /
109
+ // profileUnavailable;密钥那几句取自 ui-settings-models 的 apiKeySet / apiKeyUnset。
110
+ // 官方那套词在 settings.account / settings.models 命名空间,别的插件拿不到,所以抄进来。
111
+ //
112
+ // **3.0 起这一段只剩两句话**:组级说明 + 每行第二行的「来历」。
113
+ // 状态徽章(已配置密钥。/ 未配置密钥。…)整套删了 —— 它与左边那盏灯说同一件事,
114
+ // 而「一个事实说两遍」正是本轮要消掉的东西。判据见 source-status.ts。
115
+ "settings.sources.title": "\u53D6\u6570\u9014\u5F84",
116
+ "settings.sources.key": "API Key",
117
+ "settings.sources.account": "\u8D26\u53F7\u767B\u5F55",
118
+ // 组级说明:一句替掉原来每行各一句的近义说明。
119
+ "settings.sources.groupNote": "\u4E24\u6761\u9014\u5F84\u90FD\u8DDF\u968F\u5B98\u65B9\uFF1A\u5BC6\u94A5\u7528\u5B98\u65B9\u6A21\u578B\u9875\u914D\u597D\u7684\u90A3\u4E00\u4EFD\uFF0C\u8D26\u53F7\u7528\u5BBF\u4E3B\u5DF2\u767B\u5F55\u7684\u90A3\u4E2A\u3002\u672C\u63D2\u4EF6\u4E0D\u81EA\u5DF1\u4FDD\u5B58\u51ED\u636E\u3002",
120
+ // 第二行的「来历」:这把凭据是从哪一层取到的。取值来自官方 describe() 的 source。
121
+ "settings.sources.sourceSaved": "\u53D6\u81EA\u5B98\u65B9\u6A21\u578B\u9875\u4FDD\u5B58\u7684\u51ED\u636E",
122
+ "settings.sources.sourceEnv": "\u53D6\u81EA\u542F\u52A8\u73AF\u5883\u53D8\u91CF {name}",
123
+ "settings.sources.sourceUnknown": "\u6765\u6E90\u672A\u786E\u8BA4",
124
+ // 账号那条唯一需要说出来的一句:那是**部署事实**,用户做什么都没用;
125
+ // 而整行留空会被读成「插件坏了」。未登录不说 —— 它是正常状态。
126
+ "settings.sources.accountNoService": "\u672C\u90E8\u7F72\u6CA1\u6709\u8D26\u53F7\u670D\u52A1\uFF0C\u8FD9\u6761\u9014\u5F84\u4E0D\u53EF\u7528\u3002",
127
+ // 测试。结果借每行右槽显示 4 秒(排在凭据身份**左边**,所以身份不会被顶掉),
128
+ // 所以文案要短。成败由**官方那对圆圈图标**表达(check-circle / close-circle),
129
+ // 不用 `✓` / `✗` 字符:字符的基线、字重、间距都跟着字体走,与旁边那行等宽掩码并排会歪。
130
+ "settings.test": "\u6D4B\u8BD5",
131
+ "settings.testing": "\u6B63\u5728\u6D4B\u8BD5\u2026",
132
+ "settings.test.ok": "{ms}ms",
133
+ "settings.test.noKey": "\u6CA1\u6709\u53EF\u7528\u51ED\u636E",
134
+ "settings.test.notSignedIn": "\u5C1A\u672A\u767B\u5F55",
135
+ "settings.test.fail": "\u8FDE\u63A5\u5931\u8D25\uFF1A{message}",
110
136
  "settings.hint.displayCurrency": "\u6240\u9009\u5E01\u79CD\u4E0D\u5B58\u5728\u65F6\uFF0C\u56DE\u843D\u5230\u8D26\u6237\u5B9E\u9645\u6301\u6709\u7684\u5E01\u79CD\u3002",
111
137
  "settings.hint.threshold": "\u540E\u7AEF\u5224\u5B9A\uFF1B\u754C\u9762\u989C\u8272\u8DDF\u968F\u8FD4\u56DE\u7684\u4E25\u91CD\u5EA6\u3002",
112
138
  "settings.hint.thresholdPair": "\u544A\u6025\u503C\u5FC5\u987B\u4F4E\u4E8E\u9884\u8B66\u503C",
@@ -114,18 +140,6 @@ var zh = {
114
140
  "settings.hint.clientPollSeconds": "\u5237\u65B0\u663E\u793A\u7684\u95F4\u9694\uFF1B\u6B63\u5E38\u65F6\u53EA\u8BFB\u7F13\u5B58\uFF0C\u8BFB\u4E0D\u5230\u65F6\u624D\u8BBF\u95EE\u4E0A\u6E38\u3002",
115
141
  "settings.hint.manualRefreshCooldownSeconds": "\u4E24\u6B21\u624B\u52A8\u5237\u65B0\u4E4B\u95F4\u7684\u6700\u77ED\u95F4\u9694\u3002",
116
142
  "settings.currency.auto": "\u81EA\u52A8\uFF08\u8DDF\u968F\u8D26\u6237\uFF09",
117
- // 凭据行的**状态徽章**:两条逐字用官方措辞 —— 取自 ui-settings-plugins 的
118
- // webSearchApiKeySet / webSearchApiKeyUnset(后者去掉搜索相关的后半句)。
119
- "settings.credential.configured": "\u5DF2\u914D\u7F6E\u5BC6\u94A5\u3002",
120
- "settings.credential.notConfigured": "\u672A\u914D\u7F6E\u5BC6\u94A5\u3002",
121
- // 凭据行下方的说明。官方 webSearchApiKeyHint 是一句与状态无关的常量说明,这里照做:
122
- // 状态由徽章交代,说明只讲字段契约。
123
- "settings.hint.credential": "\u4E0D\u5199\u5165\u8BBE\u7F6E\u6587\u4EF6\u3002\u7559\u7A7A\u8868\u793A\u4FDD\u6301\u5F53\u524D\u5BC6\u94A5\u3002",
124
- "settings.group.customized": "\u81EA\u5B9A\u4E49\u8BBE\u7F6E",
125
- "settings.test": "\u6D4B\u8BD5\u8FDE\u63A5",
126
- "settings.testing": "\u6D4B\u8BD5\u4E2D\u2026",
127
- "settings.test.ok": "\u8FDE\u63A5\u6B63\u5E38\u3002",
128
- "settings.test.fail": "\u8FDE\u63A5\u5931\u8D25\uFF1A{message}",
129
143
  "settings.save": "\u4FDD\u5B58",
130
144
  "settings.saving": "\u4FDD\u5B58\u4E2D\u2026",
131
145
  "settings.failed": "\u4FDD\u5B58\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002",
@@ -155,6 +169,7 @@ var en = {
155
169
  "popover.title": "Account balance",
156
170
  "popover.source.key": " (API key)",
157
171
  "popover.source.account": " (account sign-in)",
172
+ "popover.source.identity": " ({identity})",
158
173
  "popover.total": "Balance",
159
174
  "popover.granted": "Granted",
160
175
  "popover.toppedUp": "Topped up",
@@ -180,9 +195,6 @@ var en = {
180
195
  "settings.group.refresh": "Refresh",
181
196
  "settings.group.display": "Display",
182
197
  "settings.group.thresholds": "Thresholds",
183
- "settings.field.apiKey": "API key",
184
- "settings.field.apiKeyRef": "API key reference",
185
- "settings.field.baseUrl": "Endpoint",
186
198
  "settings.field.serverRefreshSeconds": "Server refresh (seconds)",
187
199
  "settings.field.clientPollSeconds": "Client poll (seconds)",
188
200
  "settings.field.manualRefreshCooldownSeconds": "Manual refresh cooldown (seconds)",
@@ -191,8 +203,21 @@ var en = {
191
203
  "settings.field.cnyCritical": "CNY critical",
192
204
  "settings.field.usdWarn": "USD warning",
193
205
  "settings.field.usdCritical": "USD critical",
194
- "settings.hint.apiKeyRef": "Credential store entry name; takes precedence over the API key.",
195
- "settings.hint.baseUrl": "Leave blank to use the provider default.",
206
+ // 与 zh 逐条对应,理由写在 zh 那一段。
207
+ "settings.sources.title": "Balance sources",
208
+ "settings.sources.key": "API key",
209
+ "settings.sources.account": "Account sign-in",
210
+ "settings.sources.groupNote": "Both sources follow the official setup: the key comes from the official Models page, the account is the one signed in to the host. This plugin stores no credential of its own.",
211
+ "settings.sources.sourceSaved": "From the credential saved on the Models page",
212
+ "settings.sources.sourceEnv": "From the launch env var {name}",
213
+ "settings.sources.sourceUnknown": "Source unconfirmed",
214
+ "settings.sources.accountNoService": "This deployment ships no account service, so this source is unavailable.",
215
+ "settings.test": "Test",
216
+ "settings.testing": "Testing\u2026",
217
+ "settings.test.ok": "{ms}ms",
218
+ "settings.test.noKey": "No credential available",
219
+ "settings.test.notSignedIn": "Not signed in",
220
+ "settings.test.fail": "Connection failed: {message}",
196
221
  "settings.hint.displayCurrency": "Falls back to a currency the account actually holds.",
197
222
  "settings.hint.threshold": "Decided on the server; the UI colour follows the returned severity.",
198
223
  "settings.hint.thresholdPair": "Critical must be lower than warning",
@@ -200,14 +225,6 @@ var en = {
200
225
  "settings.hint.clientPollSeconds": "How often the display refreshes; cache-only while healthy.",
201
226
  "settings.hint.manualRefreshCooldownSeconds": "Minimum gap between two manual refreshes.",
202
227
  "settings.currency.auto": "Automatic (follow account)",
203
- "settings.credential.configured": "A key is configured.",
204
- "settings.credential.notConfigured": "No key is configured.",
205
- "settings.hint.credential": "Stored outside the settings file. Leave blank to keep the current key.",
206
- "settings.group.customized": "Customized settings",
207
- "settings.test": "Test connection",
208
- "settings.testing": "Testing\u2026",
209
- "settings.test.ok": "Connection looks good.",
210
- "settings.test.fail": "Connection failed: {message}",
211
228
  "settings.save": "Save",
212
229
  "settings.saving": "Saving\u2026",
213
230
  "settings.failed": "Saving failed. Try again.",
@@ -229,10 +246,85 @@ function interpolate(template, params) {
229
246
  );
230
247
  }
231
248
 
232
- // src/client/settings/fields.tsx
249
+ // src/client/icons.ts
250
+ var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");
251
+
252
+ // src/client/icons/currency.tsx
253
+ var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
254
+ var import_jsx_runtime = require("react/jsx-runtime");
255
+ function CurrencyOutlineIcon({ size = 14, className }) {
256
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
257
+ "svg",
258
+ {
259
+ width: size,
260
+ height: size,
261
+ className,
262
+ viewBox: "0 0 16 16",
263
+ fill: "none",
264
+ xmlns: "http://www.w3.org/2000/svg",
265
+ "aria-hidden": "true",
266
+ strokeWidth: import_dsh_client_ui_primitives.ICON_REGULAR_STROKE,
267
+ children: [
268
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4.6 3.4 8 7.6 11.4 3.4", stroke: "currentColor" }),
269
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 7.6V12.6", stroke: "currentColor" }),
270
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5.6 9.1H10.4", stroke: "currentColor" }),
271
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5.6 11H10.4", stroke: "currentColor" })
272
+ ]
273
+ }
274
+ );
275
+ }
276
+
277
+ // src/client/icons.ts
278
+ var GROUP_ICONS = {
279
+ connection: import_dsh_client_ui_primitives2.IconLinkOutlineRegular,
280
+ display: CurrencyOutlineIcon,
281
+ thresholds: import_dsh_client_ui_primitives2.IconWarningOutlineRegular,
282
+ refresh: import_dsh_client_ui_primitives2.IconRefreshOutlineRegular
283
+ };
284
+ var RESULT_ICONS = {
285
+ ok: import_dsh_client_ui_primitives2.IconCheckOutlineRegular,
286
+ fail: import_dsh_client_ui_primitives2.IconCloseOutlineRegular
287
+ };
288
+ var HEADER_CHEVRONS = {
289
+ closed: import_dsh_client_ui_primitives2.IconChevronDownOutlineRegular,
290
+ open: import_dsh_client_ui_primitives2.IconChevronUpOutlineRegular
291
+ };
292
+ var SELECTOR_ICON = import_dsh_client_ui_primitives2.IconChevronDownOutlineRegular;
293
+
294
+ // src/client/settings/AccountAvatar.tsx
233
295
  var import_react = require("react");
234
296
 
235
- // node_modules/clsx/dist/clsx.mjs
297
+ // src/client/settings/AccountAvatar.module.css
298
+ var AccountAvatar_default = {
299
+ avatar: "AccountAvatar_avatar",
300
+ image: "AccountAvatar_image"
301
+ };
302
+
303
+ // src/client/settings/AccountAvatar.tsx
304
+ var import_jsx_runtime2 = require("react/jsx-runtime");
305
+ function AccountAvatar({ url }) {
306
+ const [failed, setFailed] = (0, import_react.useState)(false);
307
+ (0, import_react.useEffect)(() => {
308
+ setFailed(false);
309
+ }, [url]);
310
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: AccountAvatar_default.avatar, children: url === null || failed ? null : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
311
+ "img",
312
+ {
313
+ className: AccountAvatar_default.image,
314
+ src: url,
315
+ alt: "",
316
+ referrerPolicy: "no-referrer",
317
+ onError: () => {
318
+ setFailed(true);
319
+ }
320
+ }
321
+ ) });
322
+ }
323
+
324
+ // src/client/settings/fields.tsx
325
+ var import_react2 = require("react");
326
+
327
+ // node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
236
328
  function r(e) {
237
329
  var t, f, n = "";
238
330
  if ("string" == typeof e || "number" == typeof e) n += e;
@@ -249,16 +341,35 @@ function clsx() {
249
341
  var clsx_default = clsx;
250
342
 
251
343
  // src/client/settings/fields.tsx
252
- var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
344
+ var import_dsh_client_ui_primitives3 = require("@deepseek-ai/dsh-client-ui-primitives");
253
345
 
254
346
  // src/client/settings/fields.module.css
255
347
  var fields_default = {
256
348
  group: "fields_group",
349
+ groupHead: "fields_groupHead",
350
+ groupLeading: "fields_groupLeading",
351
+ groupIcon: "fields_groupIcon",
352
+ groupChevronClosed: "fields_groupChevronClosed",
353
+ groupChevronOpen: "fields_groupChevronOpen",
354
+ groupTitle: "fields_groupTitle",
257
355
  groupLast: "fields_groupLast",
258
356
  groupNote: "fields_groupNote",
259
357
  fields: "fields_fields",
260
358
  details: "fields_details",
261
359
  detailsBody: "fields_detailsBody",
360
+ source: "fields_source",
361
+ sourceHead: "fields_sourceHead",
362
+ dot: "fields_dot",
363
+ dotOk: "fields_dotOk",
364
+ dotMissing: "fields_dotMissing",
365
+ sourceName: "fields_sourceName",
366
+ sourceSlot: "fields_sourceSlot",
367
+ sourceIdentity: "fields_sourceIdentity",
368
+ sourceLiveText: "fields_sourceLiveText",
369
+ sourceIdentityMono: "fields_sourceIdentityMono",
370
+ sourceLive: "fields_sourceLive",
371
+ sourceLiveIcon: "fields_sourceLiveIcon",
372
+ sourceDetail: "fields_sourceDetail",
262
373
  field: "fields_field",
263
374
  row: "fields_row",
264
375
  rowText: "fields_rowText",
@@ -280,27 +391,46 @@ var fields_default = {
280
391
  hint: "fields_hint",
281
392
  pairNote: "fields_pairNote",
282
393
  invalid: "fields_invalid",
283
- actions: "fields_actions",
284
- resultOk: "fields_resultOk",
285
- resultFail: "fields_resultFail"
394
+ actions: "fields_actions"
286
395
  };
287
396
 
288
397
  // src/client/settings/fields.tsx
289
- var import_jsx_runtime = require("react/jsx-runtime");
398
+ var import_jsx_runtime3 = require("react/jsx-runtime");
290
399
  function FieldGroup(props) {
291
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
292
- import_dsh_client_ui_primitives.DisclosureRow,
400
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
401
+ "div",
293
402
  {
294
403
  className: clsx_default(fields_default.group, props.last === true && fields_default.groupLast),
295
- icon: props.icon,
296
- title: props.title,
297
- open: props.open,
298
- expandable: true,
299
- expandOnRowClick: true,
300
- onToggle: props.onToggle,
404
+ "data-open": props.open || void 0,
301
405
  children: [
302
- props.note === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: fields_default.groupNote, children: props.note }),
303
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: fields_default.fields, children: props.children })
406
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
407
+ "div",
408
+ {
409
+ className: fields_default.groupHead,
410
+ "data-disclosure-row": true,
411
+ role: "button",
412
+ tabIndex: 0,
413
+ "aria-expanded": props.open,
414
+ onClick: props.onToggle,
415
+ onKeyDown: (event) => {
416
+ if (event.key !== "Enter" && event.key !== " ") return;
417
+ event.preventDefault();
418
+ props.onToggle();
419
+ },
420
+ children: [
421
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: fields_default.groupLeading, children: [
422
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: fields_default.groupIcon, children: props.icon }),
423
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(HEADER_CHEVRONS.closed, { className: fields_default.groupChevronClosed }),
424
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(HEADER_CHEVRONS.open, { className: fields_default.groupChevronOpen })
425
+ ] }),
426
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: fields_default.groupTitle, children: props.title })
427
+ ]
428
+ }
429
+ ),
430
+ props.open ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
431
+ props.note === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: fields_default.groupNote, children: props.note }),
432
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: fields_default.fields, children: props.children })
433
+ ] }) : null
304
434
  ]
305
435
  }
306
436
  );
@@ -308,10 +438,10 @@ function FieldGroup(props) {
308
438
  function FieldBadges(props) {
309
439
  const status = props.status ?? null;
310
440
  if (status === null && !props.pending && !props.resettable) return null;
311
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: fields_default.badges, children: [
312
- status === null ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: status.tone, children: status.label }),
313
- props.pending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "neutral", children: props.pendingLabel }) : null,
314
- props.resettable ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
441
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: fields_default.badges, children: [
442
+ status === null ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_dsh_client_ui_primitives3.Tag, { tone: status.tone, children: status.label }),
443
+ props.pending ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_dsh_client_ui_primitives3.Tag, { tone: "neutral", children: props.pendingLabel }) : null,
444
+ props.resettable ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
315
445
  "button",
316
446
  {
317
447
  type: "button",
@@ -326,10 +456,10 @@ function FieldBadges(props) {
326
456
  function FieldFrame(props) {
327
457
  const note = props.invalid ? props.invalidNote ?? props.hint : props.hint;
328
458
  const hasNote = note !== void 0 && note !== null && note !== "";
329
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: fields_default.field, children: [
330
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: fields_default.head, children: [
331
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: fields_default.label, htmlFor: props.id, children: props.label }),
332
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
459
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: fields_default.field, children: [
460
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: fields_default.head, children: [
461
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: fields_default.label, htmlFor: props.id, children: props.label }),
462
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
333
463
  FieldBadges,
334
464
  {
335
465
  status: props.status ?? null,
@@ -343,12 +473,12 @@ function FieldFrame(props) {
343
473
  )
344
474
  ] }),
345
475
  props.children,
346
- hasNote ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: props.invalid ? fields_default.invalid : fields_default.hint, children: note }) : null
476
+ hasNote ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: props.invalid ? fields_default.invalid : fields_default.hint, children: note }) : null
347
477
  ] });
348
478
  }
349
479
  function TextControl(props) {
350
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
351
- import_dsh_client_ui_primitives.Input,
480
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
481
+ import_dsh_client_ui_primitives3.Input,
352
482
  {
353
483
  id: props.id,
354
484
  className: props.invalid ? fields_default.inputInvalid : void 0,
@@ -364,40 +494,55 @@ function TextControl(props) {
364
494
  }
365
495
  );
366
496
  }
367
- function ReadOnlyControl(props) {
368
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
369
- import_dsh_client_ui_primitives.Input,
497
+ function SourceRow(props) {
498
+ const live = props.live ?? null;
499
+ const identity = props.status.identity === "" ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
500
+ "span",
370
501
  {
371
- id: props.id,
372
- className: fields_default.inputStatic,
373
- type: "text",
374
- value: "",
375
- readOnly: true
502
+ className: clsx_default(fields_default.sourceIdentity, props.monoIdentity === true && fields_default.sourceIdentityMono),
503
+ title: props.status.identity,
504
+ children: props.status.identity
376
505
  }
377
506
  );
378
- }
379
- function DetailsGroup(props) {
380
- const [open, setOpen] = (0, import_react.useState)(props.defaultOpen === true);
381
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: fields_default.details, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
382
- import_dsh_client_ui_primitives.DisclosureRow,
383
- {
384
- icon: null,
385
- title: props.title,
386
- open,
387
- expandable: true,
388
- expandOnRowClick: true,
389
- onToggle: () => {
390
- setOpen((value) => !value);
391
- },
392
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: fields_default.detailsBody, children: props.children })
393
- }
394
- ) });
507
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: fields_default.source, children: [
508
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: fields_default.sourceHead, children: [
509
+ props.leading === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
510
+ "span",
511
+ {
512
+ className: clsx_default(fields_default.dot, props.status.tone === "ok" ? fields_default.dotOk : fields_default.dotMissing),
513
+ "aria-hidden": "true"
514
+ }
515
+ ) : props.leading,
516
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: fields_default.sourceName, children: props.label }),
517
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: fields_default.sourceSlot, children: [
518
+ live === null ? null : (
519
+ // 成功只是通报(polite);失败要用户处理(assertive)。**不上色**,成败靠图标形状。
520
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
521
+ "span",
522
+ {
523
+ className: fields_default.sourceLive,
524
+ role: live.kind === "busy" ? void 0 : live.ok ? "status" : "alert",
525
+ children: live.kind === "busy" ? (
526
+ // 官方原语的转圈:`ongoing` 是它唯一带运动的档。
527
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_dsh_client_ui_primitives3.StateDot, { state: "ongoing", size: 14 })
528
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
529
+ live.ok ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RESULT_ICONS.ok, { className: fields_default.sourceLiveIcon }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RESULT_ICONS.fail, { className: fields_default.sourceLiveIcon }),
530
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: fields_default.sourceLiveText, title: live.text, children: live.text })
531
+ ] })
532
+ }
533
+ )
534
+ ),
535
+ identity
536
+ ] })
537
+ ] }),
538
+ props.status.detail === "" ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: fields_default.sourceDetail, children: props.status.detail })
539
+ ] });
395
540
  }
396
541
  function SelectorControl(props) {
397
- const [open, setOpen] = (0, import_react.useState)(false);
542
+ const [open, setOpen] = (0, import_react2.useState)(false);
398
543
  const selected = props.options.find((option) => option.id === props.selectedId);
399
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
400
- import_dsh_client_ui_primitives.Menu,
544
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
545
+ import_dsh_client_ui_primitives3.Menu,
401
546
  {
402
547
  open,
403
548
  onClose: () => {
@@ -412,7 +557,7 @@ function SelectorControl(props) {
412
557
  align: "end",
413
558
  portal: true,
414
559
  className: fields_default.selectorAnchor,
415
- anchor: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
560
+ anchor: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
416
561
  "button",
417
562
  {
418
563
  id: props.id,
@@ -427,7 +572,7 @@ function SelectorControl(props) {
427
572
  },
428
573
  children: [
429
574
  selected?.label ?? "",
430
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: fields_default.selectorChevron })
575
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SELECTOR_ICON, { className: fields_default.selectorChevron })
431
576
  ]
432
577
  }
433
578
  )
@@ -435,24 +580,11 @@ function SelectorControl(props) {
435
580
  );
436
581
  }
437
582
  function ActionRow(props) {
438
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
439
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: fields_default.actions, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { type: "button", variant: "outline", disabled: props.disabled, onClick: props.onClick, children: props.label }) }),
440
- props.result === null ? null : (
441
- // 成功只是通报,用 polite 的 status;失败需要用户处理,按 06 §6.7 的「行内错误」用 alert。
442
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
443
- "p",
444
- {
445
- className: props.result.ok ? fields_default.resultOk : fields_default.resultFail,
446
- role: props.result.ok ? "status" : "alert",
447
- children: props.result.text
448
- }
449
- )
450
- )
451
- ] });
583
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: fields_default.actions, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_dsh_client_ui_primitives3.Button, { type: "button", variant: "outline", disabled: props.disabled, onClick: props.onClick, children: props.label }) });
452
584
  }
453
585
 
454
586
  // src/client/settings/use-config-form.ts
455
- var import_react2 = require("react");
587
+ var import_react3 = require("react");
456
588
  function isTextSpec(spec) {
457
589
  return spec.kind === "text" || spec.kind === "secret" || spec.kind === "number";
458
590
  }
@@ -532,15 +664,6 @@ var CONFIG_FIELDS = [
532
664
  var SPEC_BY_FIELD = new Map(
533
665
  CONFIG_FIELDS.map((spec) => [spec.field, spec])
534
666
  );
535
- var TEST_LATENCY_MS = 800;
536
- function probeFailure(baseUrl, apiKey, apiKeyRef) {
537
- const url = baseUrl.trim();
538
- if (url !== "" && !/^https?:\/\//i.test(url))
539
- return "base URL must start with http:// or https://";
540
- if (apiKey.trim() === "" && apiKeyRef.trim() === "")
541
- return "no API key or credential reference configured";
542
- return null;
543
- }
544
667
  function isRecord(input) {
545
668
  return typeof input === "object" && input !== null && !Array.isArray(input);
546
669
  }
@@ -684,8 +807,8 @@ function fieldOf(snapshot, name, edit) {
684
807
  return fieldStateOf(snapshot, spec, edit);
685
808
  }
686
809
  function useConfigForm(form) {
687
- const cacheRef = (0, import_react2.useRef)(null);
688
- const readSnapshot = (0, import_react2.useCallback)(() => {
810
+ const cacheRef = (0, import_react3.useRef)(null);
811
+ const readSnapshot = (0, import_react3.useCallback)(() => {
689
812
  const raw = form.getSnapshot();
690
813
  const value = asRecord(raw.value);
691
814
  const user = asRecord(raw.user);
@@ -699,26 +822,15 @@ function useConfigForm(form) {
699
822
  cacheRef.current = { value, user, writable, available, snapshot: snapshot2 };
700
823
  return snapshot2;
701
824
  }, [form]);
702
- const subscribe = (0, import_react2.useCallback)((listener) => form.subscribe(listener), [form]);
703
- const snapshot = (0, import_react2.useSyncExternalStore)(subscribe, readSnapshot);
704
- const [staged, setStaged] = (0, import_react2.useState)(() => /* @__PURE__ */ new Map());
705
- const [saving, setSaving] = (0, import_react2.useState)(false);
706
- const [failed, setFailed] = (0, import_react2.useState)(false);
707
- const [blurred, setBlurred] = (0, import_react2.useState)(() => /* @__PURE__ */ new Set());
708
- const savingRef = (0, import_react2.useRef)(false);
709
- const [test, setTest] = (0, import_react2.useState)({ running: false, ok: null, message: "" });
710
- const testTimerRef = (0, import_react2.useRef)(null);
711
- const testRunningRef = (0, import_react2.useRef)(false);
712
- (0, import_react2.useEffect)(
713
- () => () => {
714
- if (testTimerRef.current !== null) clearTimeout(testTimerRef.current);
715
- testTimerRef.current = null;
716
- testRunningRef.current = false;
717
- },
718
- []
719
- );
720
- const plan = (0, import_react2.useMemo)(() => planWrites(snapshot, staged), [snapshot, staged]);
721
- const pairsInvalid = (0, import_react2.useMemo)(
825
+ const subscribe = (0, import_react3.useCallback)((listener) => form.subscribe(listener), [form]);
826
+ const snapshot = (0, import_react3.useSyncExternalStore)(subscribe, readSnapshot);
827
+ const [staged, setStaged] = (0, import_react3.useState)(() => /* @__PURE__ */ new Map());
828
+ const [saving, setSaving] = (0, import_react3.useState)(false);
829
+ const [failed, setFailed] = (0, import_react3.useState)(false);
830
+ const [blurred, setBlurred] = (0, import_react3.useState)(() => /* @__PURE__ */ new Set());
831
+ const savingRef = (0, import_react3.useRef)(false);
832
+ const plan = (0, import_react3.useMemo)(() => planWrites(snapshot, staged), [snapshot, staged]);
833
+ const pairsInvalid = (0, import_react3.useMemo)(
722
834
  () => THRESHOLD_PAIRS.some(
723
835
  (pair) => !thresholdsOk(
724
836
  pair,
@@ -729,7 +841,7 @@ function useConfigForm(form) {
729
841
  [snapshot, staged]
730
842
  );
731
843
  const invalid = plan.some((item) => item.write === void 0) || pairsInvalid;
732
- const stage = (0, import_react2.useCallback)((field2, edit2) => {
844
+ const stage = (0, import_react3.useCallback)((field2, edit2) => {
733
845
  setStaged((current) => {
734
846
  const next = new Map(current);
735
847
  if (edit2 === null) next.delete(field2);
@@ -737,46 +849,30 @@ function useConfigForm(form) {
737
849
  return next;
738
850
  });
739
851
  setFailed(false);
740
- if (testTimerRef.current !== null) {
741
- clearTimeout(testTimerRef.current);
742
- testTimerRef.current = null;
743
- }
744
- testRunningRef.current = false;
745
- setTest(
746
- (current) => current.running || current.ok !== null ? { running: false, ok: null, message: "" } : current
747
- );
748
852
  }, []);
749
- const edit = (0, import_react2.useCallback)(
853
+ const edit = (0, import_react3.useCallback)(
750
854
  (field2, text) => {
751
855
  stage(field2, { kind: "text", text });
752
856
  },
753
857
  [stage]
754
858
  );
755
- const setValue = (0, import_react2.useCallback)(
859
+ const setValue = (0, import_react3.useCallback)(
756
860
  (field2, value) => {
757
861
  stage(field2, { kind: "value", value });
758
862
  },
759
863
  [stage]
760
864
  );
761
- const resetField = (0, import_react2.useCallback)(
865
+ const resetField = (0, import_react3.useCallback)(
762
866
  (field2) => {
763
867
  stage(field2, { kind: "clear" });
764
868
  },
765
869
  [stage]
766
870
  );
767
- const discard = (0, import_react2.useCallback)(() => {
871
+ const discard = (0, import_react3.useCallback)(() => {
768
872
  setStaged((current) => current.size === 0 ? current : /* @__PURE__ */ new Map());
769
873
  setFailed(false);
770
- if (testTimerRef.current !== null) {
771
- clearTimeout(testTimerRef.current);
772
- testTimerRef.current = null;
773
- }
774
- testRunningRef.current = false;
775
- setTest(
776
- (current) => current.running || current.ok !== null ? { running: false, ok: null, message: "" } : current
777
- );
778
874
  }, []);
779
- const save = (0, import_react2.useCallback)(async () => {
875
+ const save = (0, import_react3.useCallback)(async () => {
780
876
  if (savingRef.current) return;
781
877
  const writes = planWrites(readSnapshot(), staged);
782
878
  if (writes.length === 0) return;
@@ -803,31 +899,11 @@ function useConfigForm(form) {
803
899
  if (landed) setStaged(/* @__PURE__ */ new Map());
804
900
  setFailed(!landed);
805
901
  }, [form, readSnapshot, staged]);
806
- const runTest = (0, import_react2.useCallback)(() => {
807
- if (testRunningRef.current) return;
808
- const current = readSnapshot();
809
- const draftOf = (field2) => {
810
- const spec = SPEC_BY_FIELD.get(field2);
811
- const edit2 = staged.get(field2);
812
- if (edit2 !== void 0 && edit2.kind === "text") return edit2.text;
813
- return spec !== void 0 && isTextSpec(spec) ? spec.format(current.value[field2]) : "";
814
- };
815
- const failure = probeFailure(draftOf("baseUrl"), draftOf("apiKey"), draftOf("apiKeyRef"));
816
- testRunningRef.current = true;
817
- setTest({ running: true, ok: null, message: "" });
818
- testTimerRef.current = setTimeout(() => {
819
- testTimerRef.current = null;
820
- testRunningRef.current = false;
821
- setTest(
822
- failure === null ? { running: false, ok: true, message: "" } : { running: false, ok: false, message: failure }
823
- );
824
- }, TEST_LATENCY_MS);
825
- }, [readSnapshot, staged]);
826
- const field = (0, import_react2.useCallback)(
902
+ const field = (0, import_react3.useCallback)(
827
903
  (name) => fieldOf(snapshot, name, staged.get(name)),
828
904
  [snapshot, staged]
829
905
  );
830
- const thresholdPairOk = (0, import_react2.useCallback)(
906
+ const thresholdPairOk = (0, import_react3.useCallback)(
831
907
  (currency) => {
832
908
  const pair = THRESHOLD_PAIRS.find((item) => item.currency === currency);
833
909
  if (pair === void 0) return true;
@@ -839,10 +915,10 @@ function useConfigForm(form) {
839
915
  },
840
916
  [snapshot, staged]
841
917
  );
842
- const touch = (0, import_react2.useCallback)((name) => {
918
+ const touch = (0, import_react3.useCallback)((name) => {
843
919
  setBlurred((current) => current.has(name) ? current : new Set(current).add(name));
844
920
  }, []);
845
- const touched = (0, import_react2.useCallback)((name) => blurred.has(name), [blurred]);
921
+ const touched = (0, import_react3.useCallback)((name) => blurred.has(name), [blurred]);
846
922
  return {
847
923
  state: {
848
924
  available: snapshot.available,
@@ -860,19 +936,25 @@ function useConfigForm(form) {
860
936
  save,
861
937
  thresholdPairOk,
862
938
  touched,
863
- touch,
864
- test,
865
- runTest
939
+ touch
866
940
  };
867
941
  }
868
942
 
869
- // src/client/settings/use-credential-state.ts
870
- var import_react3 = require("react");
943
+ // src/client/settings/use-sources.ts
944
+ var import_react4 = require("react");
871
945
 
872
946
  // src/client/data.ts
873
947
  var BALANCE_PATH = "/api/v1/balance";
874
948
  var REFRESH_PATH = "/api/v1/balance/refresh";
875
- var CONFIG_PATH = "/api/v1/config";
949
+ var SOURCES_PATH = "/api/v1/sources";
950
+ var TEST_PATH = "/api/v1/test-connection";
951
+ function unknownSources() {
952
+ return {
953
+ revision: -1,
954
+ key: { ref: "", configured: false, source: null, masked: "", tag8: "" },
955
+ account: { available: false, signedIn: false, name: null, avatarUrl: null, contact: null }
956
+ };
957
+ }
876
958
  var UNREACHABLE_CODE = "PLUGIN_UNREACHABLE";
877
959
  function pendingView() {
878
960
  return {
@@ -880,6 +962,8 @@ function pendingView() {
880
962
  schemaVersion: 1,
881
963
  // 占位视图没有取数路可言;处境为 checking / offline 时界面都不渲染来源标签。
882
964
  source: "deepseek-http",
965
+ // 占位视图也就没有「哪一份凭据」可言;界面同样不渲染标签。
966
+ sourceIdentity: null,
883
967
  situation: "checking",
884
968
  state: "empty",
885
969
  stale: false,
@@ -924,23 +1008,6 @@ async function requestBalance(options) {
924
1008
  });
925
1009
  return await readJson(response, BALANCE_PATH);
926
1010
  }
927
- function readCredential(value) {
928
- if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
929
- const record = value;
930
- if (typeof record.ref !== "string" || record.ref === "") return null;
931
- return {
932
- ref: record.ref,
933
- configured: record.configured === true,
934
- source: typeof record.source === "string" ? record.source : null,
935
- writable: record.writable === true
936
- };
937
- }
938
- async function requestConfig(options = {}) {
939
- const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
940
- const response = await fetchImpl(CONFIG_PATH, { headers: { accept: "application/json" } });
941
- const body = await readJson(response, CONFIG_PATH);
942
- return { ...body, credential: readCredential(body?.credential) };
943
- }
944
1011
  async function requestRefresh(options = {}) {
945
1012
  const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
946
1013
  const response = await fetchImpl(REFRESH_PATH, {
@@ -953,27 +1020,122 @@ async function requestRefresh(options = {}) {
953
1020
  });
954
1021
  return await readJson(response, REFRESH_PATH);
955
1022
  }
1023
+ function stringOrNull(value) {
1024
+ return typeof value === "string" && value !== "" ? value : null;
1025
+ }
1026
+ function readKeySource(value) {
1027
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
1028
+ return unknownSources().key;
1029
+ }
1030
+ const record = value;
1031
+ return {
1032
+ ref: typeof record.ref === "string" ? record.ref : "",
1033
+ configured: record.configured === true,
1034
+ source: stringOrNull(record.source),
1035
+ masked: typeof record.masked === "string" ? record.masked : "",
1036
+ tag8: typeof record.tag8 === "string" ? record.tag8 : ""
1037
+ };
1038
+ }
1039
+ function readAccountSource(value) {
1040
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
1041
+ return unknownSources().account;
1042
+ }
1043
+ const record = value;
1044
+ return {
1045
+ available: record.available === true,
1046
+ signedIn: record.signedIn === true,
1047
+ name: stringOrNull(record.name),
1048
+ avatarUrl: stringOrNull(record.avatarUrl),
1049
+ contact: stringOrNull(record.contact)
1050
+ };
1051
+ }
1052
+ async function requestSources(options = {}) {
1053
+ const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
1054
+ const url = options.refresh === true ? `${SOURCES_PATH}?refresh=1` : SOURCES_PATH;
1055
+ const response = await fetchImpl(url, { headers: { accept: "application/json" } });
1056
+ const body = await readJson(response, SOURCES_PATH);
1057
+ return {
1058
+ revision: typeof body?.revision === "number" ? body.revision : -1,
1059
+ key: readKeySource(body?.key),
1060
+ account: readAccountSource(body?.account)
1061
+ };
1062
+ }
1063
+ async function requestTest(source, options = {}) {
1064
+ const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
1065
+ const response = await fetchImpl(TEST_PATH, {
1066
+ method: "POST",
1067
+ headers: { "content-type": "application/json", accept: "application/json" },
1068
+ body: JSON.stringify({ source })
1069
+ });
1070
+ const body = await readJson(response, TEST_PATH);
1071
+ return { ...body, source };
1072
+ }
956
1073
 
957
- // src/client/settings/use-credential-state.ts
958
- function credentialViewOf(credential, overridden) {
959
- if (overridden) return "overridden";
960
- if (credential?.writable !== true) return "env";
961
- return credential.configured ? "configured" : "notConfigured";
962
- }
963
- function useCredentialState(ref) {
964
- const [state, setState] = (0, import_react3.useState)(null);
965
- (0, import_react3.useEffect)(() => {
966
- let cancelled = false;
967
- void requestConfig().then((body) => {
968
- if (!cancelled) setState(body?.credential ?? null);
969
- }).catch(() => {
970
- if (!cancelled) setState(null);
971
- });
1074
+ // src/client/settings/use-sources.ts
1075
+ function useSources(enabled) {
1076
+ const [sources, setSources] = (0, import_react4.useState)(unknownSources);
1077
+ const [loading, setLoading] = (0, import_react4.useState)(false);
1078
+ const aliveRef = (0, import_react4.useRef)(true);
1079
+ (0, import_react4.useEffect)(() => {
1080
+ aliveRef.current = true;
972
1081
  return () => {
973
- cancelled = true;
1082
+ aliveRef.current = false;
974
1083
  };
975
- }, [ref]);
976
- return state;
1084
+ }, []);
1085
+ const load = (0, import_react4.useCallback)(async (force) => {
1086
+ setLoading(true);
1087
+ try {
1088
+ const next = await requestSources(force ? { refresh: true } : {});
1089
+ if (aliveRef.current) setSources(next);
1090
+ } catch {
1091
+ } finally {
1092
+ if (aliveRef.current) setLoading(false);
1093
+ }
1094
+ }, []);
1095
+ (0, import_react4.useEffect)(() => {
1096
+ if (!enabled) return;
1097
+ void load(false);
1098
+ }, [enabled, load]);
1099
+ (0, import_react4.useEffect)(() => {
1100
+ if (!enabled) return;
1101
+ const onFocus = () => {
1102
+ void load(true);
1103
+ };
1104
+ window.addEventListener("focus", onFocus);
1105
+ return () => {
1106
+ window.removeEventListener("focus", onFocus);
1107
+ };
1108
+ }, [enabled, load]);
1109
+ const refresh = (0, import_react4.useCallback)(async () => {
1110
+ await load(true);
1111
+ }, [load]);
1112
+ return { sources, loading, refresh };
1113
+ }
1114
+
1115
+ // src/client/settings/source-status.ts
1116
+ function keyStatusOf(key, t) {
1117
+ if (!key.configured) return { tone: "missing", identity: "", detail: "" };
1118
+ return {
1119
+ tone: "ok",
1120
+ identity: key.masked,
1121
+ detail: sourceLabelOf(key, t)
1122
+ };
1123
+ }
1124
+ function sourceLabelOf(key, t) {
1125
+ if (key.source === "file") return t("settings.sources.sourceSaved");
1126
+ if (key.source === null) return t("settings.sources.sourceUnknown");
1127
+ return t("settings.sources.sourceEnv").replace("{name}", () => key.ref);
1128
+ }
1129
+ function accountStatusOf(account, t) {
1130
+ if (!account.available) {
1131
+ return { tone: "missing", identity: "", detail: t("settings.sources.accountNoService") };
1132
+ }
1133
+ if (!account.signedIn) return { tone: "missing", identity: "", detail: "" };
1134
+ return {
1135
+ tone: "ok",
1136
+ identity: account.name ?? "",
1137
+ detail: account.contact ?? ""
1138
+ };
977
1139
  }
978
1140
 
979
1141
  // src/client/settings/BalanceSettingsCard.module.css
@@ -981,38 +1143,27 @@ var BalanceSettingsCard_default = {
981
1143
  form: "BalanceSettingsCard_form",
982
1144
  readOnly: "BalanceSettingsCard_readOnly",
983
1145
  footer: "BalanceSettingsCard_footer",
1146
+ save: "BalanceSettingsCard_save",
984
1147
  failed: "BalanceSettingsCard_failed",
985
1148
  notice: "BalanceSettingsCard_notice"
986
1149
  };
987
1150
 
988
1151
  // src/client/settings/BalanceSettingsCard.tsx
989
- var import_jsx_runtime2 = require("react/jsx-runtime");
990
- var DEFAULT_API_KEY_REF = "DEEPSEEK_API_KEY";
991
- var CREDENTIAL_BADGE = {
992
- // 官方那张卡右上角只有一句话:「已配置密钥。」——继承来的、自己填的、环境提供的都算配置好了。
993
- // 四档判据留着,是因为「覆盖 / 未覆盖」决定二级折叠里那两个字段的状态;徽章本身不区分它们。
994
- env: "settings.credential.configured",
995
- configured: "settings.credential.configured",
996
- notConfigured: "settings.credential.notConfigured",
997
- overridden: "settings.credential.configured"
998
- };
1152
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1153
+ var RESULT_TTL_MS = 4e3;
999
1154
  var DEFAULT_GROUP_OPEN = {
1000
- connection: false,
1155
+ connection: true,
1001
1156
  display: false,
1002
1157
  thresholds: false,
1003
1158
  refresh: false
1004
1159
  };
1005
1160
  var GROUP_FIELDS = {
1006
- connection: ["apiKey", "apiKeyRef", "baseUrl"],
1161
+ connection: [],
1007
1162
  display: ["displayCurrency"],
1008
1163
  thresholds: ["cnyWarn", "cnyCritical", "usdWarn", "usdCritical"],
1009
1164
  refresh: ["serverRefreshSeconds", "clientPollSeconds", "manualRefreshCooldownSeconds"]
1010
1165
  };
1011
1166
  var FIELD_IDS = {
1012
- apiKey: "ds-balance-api-key",
1013
- apiKeyRef: "ds-balance-api-key-ref",
1014
- apiKeyState: "ds-balance-api-key-state",
1015
- baseUrl: "ds-balance-base-url",
1016
1167
  serverRefreshSeconds: "ds-balance-server-refresh-seconds",
1017
1168
  clientPollSeconds: "ds-balance-client-poll-seconds",
1018
1169
  manualRefreshCooldownSeconds: "ds-balance-manual-refresh-cooldown-seconds",
@@ -1022,63 +1173,30 @@ var FIELD_IDS = {
1022
1173
  usdWarn: "ds-balance-usd-warn",
1023
1174
  usdCritical: "ds-balance-usd-critical"
1024
1175
  };
1025
- function isFilled(value) {
1026
- if (value === void 0 || value === null) return false;
1027
- return typeof value !== "string" || value.trim() !== "";
1028
- }
1029
1176
  function BalanceSettingsCard({ t, form: scopedForm }) {
1030
1177
  const form = useConfigForm(scopedForm);
1031
- const [groupOpen, setGroupOpen] = (0, import_react4.useState)(DEFAULT_GROUP_OPEN);
1178
+ const [groupOpen, setGroupOpen] = (0, import_react5.useState)(DEFAULT_GROUP_OPEN);
1179
+ const { available, writable, dirty, invalid, saving, failed } = form.state;
1180
+ const disabled = !writable || saving;
1181
+ const { sources, loading, refresh } = useSources(available);
1182
+ const [testing, setTesting] = (0, import_react5.useState)(false);
1183
+ const [outcomes, setOutcomes] = (0, import_react5.useState)(null);
1184
+ const outcomeTimer = (0, import_react5.useRef)(null);
1185
+ (0, import_react5.useEffect)(
1186
+ () => () => {
1187
+ if (outcomeTimer.current !== null) clearTimeout(outcomeTimer.current);
1188
+ outcomeTimer.current = null;
1189
+ },
1190
+ []
1191
+ );
1032
1192
  const toggleGroup = (key) => {
1033
1193
  setGroupOpen((current) => ({ ...current, [key]: !current[key] }));
1034
1194
  };
1035
1195
  const groupOpenNow = (key) => groupOpen[key] || GROUP_FIELDS[key].some((name) => form.field(name).invalid) || // 成对校验不是字段级 invalid,得单独问一次:否则收起的阈值组会挡住「保存为什么是灰的」。
1036
1196
  key === "thresholds" && THRESHOLD_PAIRS.some((pair) => !form.thresholdPairOk(pair.currency));
1037
- const { available, writable, dirty, invalid, saving, failed } = form.state;
1038
- const disabled = !writable || saving;
1039
- const credentialStatus = (name) => {
1040
- const state = form.field(name);
1041
- if (state.stored) return { label: t("settings.overridden"), tone: "neutral" };
1042
- if (isFilled(state.effective)) return { label: t("settings.configured"), tone: "neutral" };
1043
- return { label: t("settings.notConfigured"), tone: "quiet" };
1044
- };
1045
- const textRow = (name, labelKey, hintKey, withStatus) => {
1046
- const state = form.field(name);
1047
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1048
- FieldFrame,
1049
- {
1050
- id: FIELD_IDS[name] ?? name,
1051
- label: t(labelKey),
1052
- status: withStatus ? credentialStatus(name) : null,
1053
- pending: state.dirty,
1054
- resettable: state.overridden || state.dirty,
1055
- pendingLabel: t("settings.unsaved"),
1056
- resetLabel: t("settings.reset"),
1057
- invalid: state.invalid,
1058
- hint: t(hintKey),
1059
- disabled,
1060
- onReset: () => {
1061
- form.resetField(name);
1062
- },
1063
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1064
- TextControl,
1065
- {
1066
- id: FIELD_IDS[name] ?? name,
1067
- text: state.text,
1068
- numeric: false,
1069
- invalid: state.invalid,
1070
- disabled,
1071
- onEdit: (text) => {
1072
- form.edit(name, text);
1073
- }
1074
- }
1075
- )
1076
- }
1077
- );
1078
- };
1079
1197
  const numberRow = (name, labelKey, hintKey, note) => {
1080
1198
  const state = form.field(name);
1081
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1199
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1082
1200
  FieldFrame,
1083
1201
  {
1084
1202
  id: FIELD_IDS[name] ?? name,
@@ -1095,7 +1213,7 @@ function BalanceSettingsCard({ t, form: scopedForm }) {
1095
1213
  form.resetField(name);
1096
1214
  },
1097
1215
  children: [
1098
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1216
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1099
1217
  TextControl,
1100
1218
  {
1101
1219
  id: FIELD_IDS[name] ?? name,
@@ -1121,40 +1239,20 @@ function BalanceSettingsCard({ t, form: scopedForm }) {
1121
1239
  if (pair === void 0 || form.thresholdPairOk(currency2)) return null;
1122
1240
  const drafting = form.field(pair.warn).dirty || form.field(pair.critical).dirty;
1123
1241
  if (drafting && !form.touched(pair.warn) && !form.touched(pair.critical)) return null;
1124
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: fields_default.pairNote, role: "alert", children: t("settings.hint.thresholdPair") });
1242
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: fields_default.pairNote, role: "alert", children: t("settings.hint.thresholdPair") });
1125
1243
  };
1126
- const apiKey = form.field("apiKey");
1127
- const apiKeyRef = form.field("apiKeyRef");
1128
- const effectiveRef = typeof apiKeyRef.effective === "string" && apiKeyRef.effective !== "" ? apiKeyRef.effective : DEFAULT_API_KEY_REF;
1129
- const credential = useCredentialState(effectiveRef);
1130
- const credentialView = credentialViewOf(credential, apiKey.stored || apiKeyRef.stored);
1131
1244
  const currency = form.field("displayCurrency");
1132
1245
  const currencyId = typeof currency.value === "string" && currency.value !== "" ? currency.value : AUTO_CURRENCY;
1133
1246
  const currencyOptions = [
1134
1247
  { id: AUTO_CURRENCY, label: t("settings.currency.auto") },
1135
1248
  ...currencyCodes(currency.value, currency.effective).map((code) => ({ id: code, label: code }))
1136
1249
  ];
1137
- const currencyRow = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: fields_default.row, children: [
1138
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: fields_default.rowText, children: [
1139
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: fields_default.rowTitleLine, children: [
1140
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: fields_default.rowTitle, children: t("settings.field.displayCurrency") }),
1141
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1142
- FieldBadges,
1143
- {
1144
- pending: currency.dirty,
1145
- resettable: currency.dirty,
1146
- pendingLabel: t("settings.unsaved"),
1147
- resetLabel: t("settings.reset"),
1148
- disabled,
1149
- onReset: () => {
1150
- form.resetField("displayCurrency");
1151
- }
1152
- }
1153
- )
1154
- ] }),
1155
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: fields_default.hint, children: t("settings.hint.displayCurrency") })
1250
+ const currencyRow = /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: fields_default.row, children: [
1251
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: fields_default.rowText, children: [
1252
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: fields_default.rowTitleLine, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: fields_default.rowTitle, children: t("settings.field.displayCurrency") }) }),
1253
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: fields_default.hint, children: t("settings.hint.displayCurrency") })
1156
1254
  ] }),
1157
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1255
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1158
1256
  SelectorControl,
1159
1257
  {
1160
1258
  id: FIELD_IDS.displayCurrency ?? "displayCurrency",
@@ -1168,62 +1266,113 @@ function BalanceSettingsCard({ t, form: scopedForm }) {
1168
1266
  }
1169
1267
  )
1170
1268
  ] });
1171
- const testResult = form.test.ok === null ? null : form.test.ok ? { ok: true, text: t("settings.test.ok") } : { ok: false, text: interpolate(t("settings.test.fail"), { message: form.test.message }) };
1269
+ const runTest = () => {
1270
+ if (testing) return;
1271
+ setTesting(true);
1272
+ setOutcomes(null);
1273
+ if (outcomeTimer.current !== null) clearTimeout(outcomeTimer.current);
1274
+ void (async () => {
1275
+ const [keyResult, accountResult] = await Promise.all([
1276
+ runOne("deepseek-http"),
1277
+ runOne("deepseek-account")
1278
+ ]);
1279
+ setOutcomes(
1280
+ /* @__PURE__ */ new Map([
1281
+ ["key", keyResult],
1282
+ ["account", accountResult]
1283
+ ])
1284
+ );
1285
+ setTesting(false);
1286
+ outcomeTimer.current = setTimeout(() => {
1287
+ outcomeTimer.current = null;
1288
+ setOutcomes(null);
1289
+ }, RESULT_TTL_MS);
1290
+ await refresh();
1291
+ })();
1292
+ };
1293
+ const runOne = async (source) => {
1294
+ try {
1295
+ const result = await requestTest(source);
1296
+ if (result.ok) {
1297
+ return {
1298
+ ok: true,
1299
+ text: t("settings.test.ok").replace("{ms}", () => String(result.latencyMs))
1300
+ };
1301
+ }
1302
+ if (result.code === "NO_KEY") {
1303
+ return {
1304
+ ok: false,
1305
+ text: source === "deepseek-account" ? t("settings.test.notSignedIn") : t("settings.test.noKey")
1306
+ };
1307
+ }
1308
+ return {
1309
+ ok: false,
1310
+ text: interpolate(t("settings.test.fail"), {
1311
+ message: result.message ?? result.code ?? ""
1312
+ })
1313
+ };
1314
+ } catch {
1315
+ return { ok: false, text: interpolate(t("settings.test.fail"), { message: "unreachable" }) };
1316
+ }
1317
+ };
1318
+ const liveOf = (row) => {
1319
+ if (testing) return { kind: "busy" };
1320
+ const outcome = outcomes?.get(row);
1321
+ if (outcome === void 0) return null;
1322
+ return { kind: "result", ok: outcome.ok, text: outcome.text };
1323
+ };
1172
1324
  const footerNote = failed ? { text: t("settings.failed"), className: BalanceSettingsCard_default.failed } : invalid ? { text: t("settings.invalid"), className: BalanceSettingsCard_default.notice } : null;
1173
1325
  if (!available) return null;
1326
+ const keyStatus = keyStatusOf(sources.key, t);
1327
+ const accountStatus = accountStatusOf(sources.account, t);
1174
1328
  return (
1175
1329
  // 页面把这一格渲染在自己的 <section> 里:无外框的一列控件,所以根节点是 div、不是 li。
1176
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: BalanceSettingsCard_default.form, children: [
1177
- writable ? null : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: BalanceSettingsCard_default.readOnly, role: "status", children: t("settings.readOnly") }),
1178
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1330
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: BalanceSettingsCard_default.form, children: [
1331
+ writable ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: BalanceSettingsCard_default.readOnly, role: "status", children: t("settings.readOnly") }),
1332
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1179
1333
  FieldGroup,
1180
1334
  {
1181
- icon: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.IconApiOutlineRegular, { size: 14 }),
1335
+ icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(GROUP_ICONS.connection, { size: 14 }),
1182
1336
  title: t("settings.group.connection"),
1337
+ note: t("settings.sources.groupNote"),
1183
1338
  open: groupOpenNow("connection"),
1184
1339
  onToggle: () => {
1185
1340
  toggleGroup("connection");
1186
1341
  },
1187
- children: [
1188
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1189
- FieldFrame,
1342
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: fields_default.fields, children: [
1343
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1344
+ SourceRow,
1345
+ {
1346
+ label: t("settings.sources.key"),
1347
+ status: keyStatus,
1348
+ live: liveOf("key"),
1349
+ monoIdentity: true
1350
+ }
1351
+ ),
1352
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1353
+ SourceRow,
1190
1354
  {
1191
- id: FIELD_IDS.apiKeyState ?? "apiKeyState",
1192
- label: t("settings.field.apiKey"),
1193
- status: {
1194
- label: t(CREDENTIAL_BADGE[credentialView]),
1195
- tone: credentialView === "notConfigured" ? "quiet" : "neutral"
1196
- },
1197
- pending: false,
1198
- resettable: false,
1199
- pendingLabel: t("settings.unsaved"),
1200
- resetLabel: t("settings.reset"),
1201
- invalid: false,
1202
- hint: t("settings.hint.credential"),
1203
- disabled: true,
1204
- onReset: () => {
1205
- },
1206
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ReadOnlyControl, { id: FIELD_IDS.apiKeyState ?? "apiKeyState" })
1355
+ label: t("settings.sources.account"),
1356
+ status: accountStatus,
1357
+ live: liveOf("account"),
1358
+ ...sources.account.signedIn ? { leading: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(AccountAvatar, { url: sources.account.avatarUrl }) } : {}
1207
1359
  }
1208
1360
  ),
1209
- textRow("baseUrl", "settings.field.baseUrl", "settings.hint.baseUrl", false),
1210
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: fields_default.field, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1361
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: fields_default.field, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1211
1362
  ActionRow,
1212
1363
  {
1213
- label: form.test.running ? t("settings.testing") : t("settings.test"),
1214
- disabled: disabled || form.test.running,
1215
- result: testResult,
1216
- onClick: form.runTest
1364
+ label: testing ? t("settings.testing") : t("settings.test"),
1365
+ disabled: disabled || testing || loading,
1366
+ onClick: runTest
1217
1367
  }
1218
- ) }, "test"),
1219
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DetailsGroup, { title: t("settings.group.customized"), children: textRow("apiKeyRef", "settings.field.apiKeyRef", "settings.hint.apiKeyRef", true) })
1220
- ]
1368
+ ) })
1369
+ ] })
1221
1370
  }
1222
1371
  ),
1223
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1372
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1224
1373
  FieldGroup,
1225
1374
  {
1226
- icon: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.IconGlobeOutlineRegular, { size: 14 }),
1375
+ icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(GROUP_ICONS.display, { size: 14 }),
1227
1376
  title: t("settings.group.display"),
1228
1377
  open: groupOpenNow("display"),
1229
1378
  onToggle: () => {
@@ -1232,10 +1381,10 @@ function BalanceSettingsCard({ t, form: scopedForm }) {
1232
1381
  children: currencyRow
1233
1382
  }
1234
1383
  ),
1235
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1384
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1236
1385
  FieldGroup,
1237
1386
  {
1238
- icon: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.IconWarningOutlineRegular, { size: 14 }),
1387
+ icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(GROUP_ICONS.thresholds, { size: 14 }),
1239
1388
  title: t("settings.group.thresholds"),
1240
1389
  note: t("settings.hint.threshold"),
1241
1390
  open: groupOpenNow("thresholds"),
@@ -1250,10 +1399,10 @@ function BalanceSettingsCard({ t, form: scopedForm }) {
1250
1399
  ]
1251
1400
  }
1252
1401
  ),
1253
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1402
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1254
1403
  FieldGroup,
1255
1404
  {
1256
- icon: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.IconRefreshOutlineRegular, { size: 14 }),
1405
+ icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(GROUP_ICONS.refresh, { size: 14 }),
1257
1406
  title: t("settings.group.refresh"),
1258
1407
  note: t("settings.hint.refreshAdvanced"),
1259
1408
  open: groupOpenNow("refresh"),
@@ -1280,13 +1429,13 @@ function BalanceSettingsCard({ t, form: scopedForm }) {
1280
1429
  ]
1281
1430
  }
1282
1431
  ),
1283
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: BalanceSettingsCard_default.footer, children: [
1284
- footerNote === null ? null : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: footerNote.className, role: "status", children: footerNote.text }),
1285
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1286
- import_dsh_client_ui_primitives2.Button,
1432
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: BalanceSettingsCard_default.footer, children: [
1433
+ footerNote === null ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: footerNote.className, role: "status", children: footerNote.text }),
1434
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1435
+ "button",
1287
1436
  {
1288
1437
  type: "button",
1289
- variant: "primary",
1438
+ className: BalanceSettingsCard_default.save,
1290
1439
  disabled: !dirty || invalid || saving || !writable,
1291
1440
  onClick: () => {
1292
1441
  void form.save();
@@ -1300,9 +1449,9 @@ function BalanceSettingsCard({ t, form: scopedForm }) {
1300
1449
  }
1301
1450
 
1302
1451
  // src/client/sidebar/SidebarBalance.tsx
1303
- var import_react5 = require("react");
1452
+ var import_react6 = require("react");
1304
1453
  var import_react_dom = require("react-dom");
1305
- var import_dsh_client_ui_primitives4 = require("@deepseek-ai/dsh-client-ui-primitives");
1454
+ var import_dsh_client_ui_primitives5 = require("@deepseek-ai/dsh-client-ui-primitives");
1306
1455
 
1307
1456
  // src/client/model.ts
1308
1457
  function dotStateOf(severity) {
@@ -1404,6 +1553,12 @@ function sourceLabelKeyOf(source) {
1404
1553
  if (source === "deepseek-account") return "popover.source.account";
1405
1554
  return null;
1406
1555
  }
1556
+ function sourceIdentityOf(response) {
1557
+ const value = response.sourceIdentity;
1558
+ if (typeof value !== "string") return null;
1559
+ const trimmed = value.trim();
1560
+ return trimmed === "" ? null : trimmed;
1561
+ }
1407
1562
  function cooldownUntilOf(now, configuredSeconds, rejectedRemainingMs) {
1408
1563
  const span = rejectedRemainingMs === null ? Math.max(0, configuredSeconds) * MS_PER_SECOND : Math.max(0, rejectedRemainingMs);
1409
1564
  return now + span;
@@ -1572,6 +1727,8 @@ function make(patch) {
1572
1727
  requestId: "req_mock",
1573
1728
  schemaVersion: 1,
1574
1729
  source: "deepseek-http",
1730
+ // 与后端同形:Key 那条路回首页片段掩码,账号那条路回账号名(下面 account 场景覆写)。
1731
+ sourceIdentity: "sk-c08d*****3b84",
1575
1732
  situation: "ok",
1576
1733
  state: "ok",
1577
1734
  stale: false,
@@ -1592,7 +1749,7 @@ var scenarios = {
1592
1749
  /** 正常。 */
1593
1750
  ok: make({}),
1594
1751
  /** 正常,但数字来自账号登录那条路(用来眼看「来源标签」)。 */
1595
- account: make({ source: "deepseek-account" }),
1752
+ account: make({ source: "deepseek-account", sourceIdentity: "\u5F20\u4E09" }),
1596
1753
  /** 余额偏低但可用。 */
1597
1754
  warn: make({
1598
1755
  situation: "low",
@@ -1848,7 +2005,7 @@ function createConfigSlotProbe(options = {}) {
1848
2005
  }
1849
2006
 
1850
2007
  // src/client/sidebar/BalancePopover.tsx
1851
- var import_dsh_client_ui_primitives3 = require("@deepseek-ai/dsh-client-ui-primitives");
2008
+ var import_dsh_client_ui_primitives4 = require("@deepseek-ai/dsh-client-ui-primitives");
1852
2009
 
1853
2010
  // src/client/sidebar/BalancePopover.module.css
1854
2011
  var BalancePopover_default = {
@@ -1871,7 +2028,7 @@ var BalancePopover_default = {
1871
2028
  };
1872
2029
 
1873
2030
  // src/client/sidebar/BalancePopover.tsx
1874
- var import_jsx_runtime3 = require("react/jsx-runtime");
2031
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1875
2032
  var BALANCE_PLACEHOLDER = "--";
1876
2033
  var AGE_UNIT = {
1877
2034
  seconds: "second",
@@ -1927,7 +2084,7 @@ function BalancePopover(props) {
1927
2084
  const pluginsLabel = t(
1928
2085
  pluginsAction?.reachesConfig === true ? "popover.action.openPluginConfig" : "popover.action.openPlugins"
1929
2086
  );
1930
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2087
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1931
2088
  "section",
1932
2089
  {
1933
2090
  ref: panelRef,
@@ -1936,9 +2093,9 @@ function BalancePopover(props) {
1936
2093
  role: "dialog",
1937
2094
  "aria-label": t("popover.title"),
1938
2095
  children: [
1939
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: BalancePopover_default.title, children: [
1940
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: BalancePopover_default.titleMain, children: [
1941
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2096
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: BalancePopover_default.title, children: [
2097
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: BalancePopover_default.titleMain, children: [
2098
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1942
2099
  "a",
1943
2100
  {
1944
2101
  className: BalancePopover_default.titleLabel,
@@ -1946,37 +2103,37 @@ function BalancePopover(props) {
1946
2103
  target: "_blank",
1947
2104
  rel: "noopener noreferrer",
1948
2105
  children: [
1949
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_dsh_client_ui_primitives3.FishLogo, { size: 14 }),
2106
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_dsh_client_ui_primitives4.FishLogo, { size: 14 }),
1950
2107
  t("popover.title")
1951
2108
  ]
1952
2109
  }
1953
2110
  ),
1954
- sourceLabel === null ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: BalancePopover_default.sourceLabel, children: sourceLabel })
2111
+ sourceLabel === null ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: BalancePopover_default.sourceLabel, title: sourceLabel, children: sourceLabel })
1955
2112
  ] }),
1956
- pluginsAction === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_dsh_client_ui_primitives3.Tooltip, { label: pluginsLabel, side: "top", delayMs: 500, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2113
+ pluginsAction === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_dsh_client_ui_primitives4.Tooltip, { label: pluginsLabel, side: "top", delayMs: 500, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1957
2114
  "button",
1958
2115
  {
1959
2116
  type: "button",
1960
2117
  className: BalancePopover_default.iconButton,
1961
2118
  "aria-label": pluginsLabel,
1962
2119
  onClick: pluginsAction.open,
1963
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_dsh_client_ui_primitives3.IconPluginPinwheelOutlineRegular, { size: 16 })
2120
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_dsh_client_ui_primitives4.IconPluginPinwheelOutlineRegular, { size: 16 })
1964
2121
  }
1965
2122
  ) })
1966
2123
  ] }),
1967
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: BalancePopover_default.titleRule, "aria-hidden": true }),
1968
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("dl", { className: BalancePopover_default.details, children: [
1969
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dt", { children: t("popover.total") }),
1970
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dd", { children: totalText }),
1971
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dt", { children: t("popover.granted") }),
1972
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dd", { children: grantedText }),
1973
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dt", { children: t("popover.toppedUp") }),
1974
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dd", { children: toppedUpText })
2124
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: BalancePopover_default.titleRule, "aria-hidden": true }),
2125
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("dl", { className: BalancePopover_default.details, children: [
2126
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dt", { children: t("popover.total") }),
2127
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dd", { children: totalText }),
2128
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dt", { children: t("popover.granted") }),
2129
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dd", { children: grantedText }),
2130
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dt", { children: t("popover.toppedUp") }),
2131
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dd", { children: toppedUpText })
1975
2132
  ] }),
1976
- shown !== null && !selection.matchesPreference ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: BalancePopover_default.notice, children: [
1977
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: BalancePopover_default.noticeText, children: interpolate(t("popover.mismatch"), { wanted: displayCurrency, shown: shown.currency }) }),
1978
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: BalancePopover_default.noticeActions, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1979
- import_dsh_client_ui_primitives3.Button,
2133
+ shown !== null && !selection.matchesPreference ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: BalancePopover_default.notice, children: [
2134
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: BalancePopover_default.noticeText, children: interpolate(t("popover.mismatch"), { wanted: displayCurrency, shown: shown.currency }) }),
2135
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: BalancePopover_default.noticeActions, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2136
+ import_dsh_client_ui_primitives4.Button,
1980
2137
  {
1981
2138
  size: "sm",
1982
2139
  variant: "outline",
@@ -1986,11 +2143,11 @@ function BalancePopover(props) {
1986
2143
  }
1987
2144
  ) })
1988
2145
  ] }) : null,
1989
- configSlotWarning === null ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: BalancePopover_default.notice, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: BalancePopover_default.noticeText, children: configSlotWarning }) }),
1990
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: BalancePopover_default.footer, children: [
1991
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: BalancePopover_default.updated, children: updatedText }),
1992
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: BalancePopover_default.status, role: "status", children: statusText }),
1993
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_dsh_client_ui_primitives3.Tooltip, { label: refreshLabel, side: "top", delayMs: 500, disabled: refreshing || cooling, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2146
+ configSlotWarning === null ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: BalancePopover_default.notice, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: BalancePopover_default.noticeText, children: configSlotWarning }) }),
2147
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: BalancePopover_default.footer, children: [
2148
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: BalancePopover_default.updated, children: updatedText }),
2149
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: BalancePopover_default.status, role: "status", children: statusText }),
2150
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_dsh_client_ui_primitives4.Tooltip, { label: refreshLabel, side: "top", delayMs: 500, disabled: refreshing || cooling, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1994
2151
  "button",
1995
2152
  {
1996
2153
  type: "button",
@@ -2000,7 +2157,7 @@ function BalancePopover(props) {
2000
2157
  "data-refreshing": refreshing || void 0,
2001
2158
  "data-cooling": cooling || void 0,
2002
2159
  onClick: onRefresh,
2003
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_dsh_client_ui_primitives3.IconRefreshOutlineRegular, { size: 16 })
2160
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_dsh_client_ui_primitives4.IconRefreshOutlineRegular, { size: 16 })
2004
2161
  }
2005
2162
  ) })
2006
2163
  ] })
@@ -2020,7 +2177,7 @@ var PercentRing_default = {
2020
2177
  };
2021
2178
 
2022
2179
  // src/client/sidebar/PercentRing.tsx
2023
- var import_jsx_runtime4 = require("react/jsx-runtime");
2180
+ var import_jsx_runtime6 = require("react/jsx-runtime");
2024
2181
  var VIEW = 16;
2025
2182
  var STROKE = 1;
2026
2183
  var RING_INSET = 1;
@@ -2042,7 +2199,7 @@ function PercentRing({
2042
2199
  title
2043
2200
  }) {
2044
2201
  const clamped = Number.isFinite(ratio) ? Math.min(1, Math.max(0, ratio)) : 1;
2045
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2202
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2046
2203
  "svg",
2047
2204
  {
2048
2205
  className: PercentRing_default.ring,
@@ -2053,9 +2210,9 @@ function PercentRing({
2053
2210
  "aria-hidden": "true",
2054
2211
  focusable: "false",
2055
2212
  children: [
2056
- title === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("title", { children: title }),
2057
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { className: PercentRing_default.track, cx: CENTER, cy: CENTER, r: RADIUS }),
2058
- state === "ongoing" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("g", { className: PercentRing_default.spin, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2213
+ title === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("title", { children: title }),
2214
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { className: PercentRing_default.track, cx: CENTER, cy: CENTER, r: RADIUS }),
2215
+ state === "ongoing" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("g", { className: PercentRing_default.spin, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2059
2216
  "circle",
2060
2217
  {
2061
2218
  className: PercentRing_default.fill,
@@ -2069,7 +2226,7 @@ function PercentRing({
2069
2226
  /* dash 取弧长、gap 取整周长,于是只画出一条弧;rotate 让接缝落在 12 点而不是 3 点
2070
2227
  (中心跟着 VIEW 走,16 格时是 rotate(-90 8 8))。
2071
2228
  比例为 0 时整条弧不画:dash 长度为 0 配上圆头线帽会在 12 点留下一个点。 */
2072
- clamped === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2229
+ clamped === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2073
2230
  "circle",
2074
2231
  {
2075
2232
  className: PercentRing_default.fill,
@@ -2081,8 +2238,8 @@ function PercentRing({
2081
2238
  }
2082
2239
  )
2083
2240
  ),
2084
- marker === null ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("g", { className: PercentRing_default.marker, children: marker === "cross" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
2085
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2241
+ marker === null ? null : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("g", { className: PercentRing_default.marker, children: marker === "cross" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2242
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2086
2243
  "line",
2087
2244
  {
2088
2245
  x1: CENTER - MARK_ARM,
@@ -2091,7 +2248,7 @@ function PercentRing({
2091
2248
  y2: CENTER + MARK_ARM
2092
2249
  }
2093
2250
  ),
2094
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2251
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2095
2252
  "line",
2096
2253
  {
2097
2254
  x1: CENTER + MARK_ARM,
@@ -2100,9 +2257,9 @@ function PercentRing({
2100
2257
  y2: CENTER + MARK_ARM
2101
2258
  }
2102
2259
  )
2103
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
2104
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: CENTER, y1: CENTER - MARK_ARM, x2: CENTER, y2: CENTER + MARK_ARM }),
2105
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: CENTER - MARK_ARM, y1: CENTER, x2: CENTER + MARK_ARM, y2: CENTER })
2260
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2261
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: CENTER, y1: CENTER - MARK_ARM, x2: CENTER, y2: CENTER + MARK_ARM }),
2262
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: CENTER - MARK_ARM, y1: CENTER, x2: CENTER + MARK_ARM, y2: CENTER })
2106
2263
  ] }) })
2107
2264
  ]
2108
2265
  }
@@ -2119,7 +2276,7 @@ var SidebarBalance_default = {
2119
2276
  };
2120
2277
 
2121
2278
  // src/client/sidebar/SidebarBalance.tsx
2122
- var import_jsx_runtime5 = require("react/jsx-runtime");
2279
+ var import_jsx_runtime7 = require("react/jsx-runtime");
2123
2280
  var REFRESH_SIMULATION_MS = 600;
2124
2281
  var PANEL_GAP = 8;
2125
2282
  var PANEL_MARGIN = 12;
@@ -2127,8 +2284,8 @@ var MEASURE_STYLE = { visibility: "hidden", left: 0, top: 0 };
2127
2284
  var MS_PER_SECOND2 = 1e3;
2128
2285
  var TICK_MS = MS_PER_SECOND2;
2129
2286
  function useConfigSlotState(probe) {
2130
- const [state, setState] = (0, import_react5.useState)(() => probe.getSnapshot());
2131
- (0, import_react5.useEffect)(() => {
2287
+ const [state, setState] = (0, import_react6.useState)(() => probe.getSnapshot());
2288
+ (0, import_react6.useEffect)(() => {
2132
2289
  setState(probe.getSnapshot());
2133
2290
  return probe.subscribe(() => {
2134
2291
  setState(probe.getSnapshot());
@@ -2137,8 +2294,8 @@ function useConfigSlotState(probe) {
2137
2294
  return state;
2138
2295
  }
2139
2296
  function usePluginsNavigation(navigation) {
2140
- const [action, setAction] = (0, import_react5.useState)(() => navigation?.getSnapshot());
2141
- (0, import_react5.useEffect)(() => {
2297
+ const [action, setAction] = (0, import_react6.useState)(() => navigation?.getSnapshot());
2298
+ (0, import_react6.useEffect)(() => {
2142
2299
  if (navigation === void 0) {
2143
2300
  setAction(void 0);
2144
2301
  return;
@@ -2154,8 +2311,8 @@ function ringSpecFor(presentation) {
2154
2311
  return { state: presentation.ring, marker: presentation.marker };
2155
2312
  }
2156
2313
  function useRouteProvider(hint) {
2157
- const [provider, setProvider] = (0, import_react5.useState)(() => hint?.getSnapshot());
2158
- (0, import_react5.useEffect)(() => {
2314
+ const [provider, setProvider] = (0, import_react6.useState)(() => hint?.getSnapshot());
2315
+ (0, import_react6.useEffect)(() => {
2159
2316
  if (hint === void 0) {
2160
2317
  setProvider(void 0);
2161
2318
  return;
@@ -2176,35 +2333,35 @@ function SidebarBalance({
2176
2333
  pluginsNavigation,
2177
2334
  routeHint
2178
2335
  }) {
2179
- const [view, setView] = (0, import_react5.useState)(() => {
2336
+ const [view, setView] = (0, import_react6.useState)(() => {
2180
2337
  const response2 = resolveScenario() === null ? pendingView() : currentBalance();
2181
2338
  return { response: response2, seenAt: Date.now(), seenAgeMs: response2.ageMs };
2182
2339
  });
2183
2340
  const response = view.response;
2184
- const [mock, setMock] = (0, import_react5.useState)(() => resolveScenario() !== null);
2185
- const loadedRef = (0, import_react5.useRef)(false);
2186
- const [open, setOpen] = (0, import_react5.useState)(false);
2187
- const [refreshing, setRefreshing] = (0, import_react5.useState)(false);
2188
- const [cooldownUntil, setCooldownUntil] = (0, import_react5.useState)(0);
2189
- const [now, setNow] = (0, import_react5.useState)(() => Date.now());
2341
+ const [mock, setMock] = (0, import_react6.useState)(() => resolveScenario() !== null);
2342
+ const loadedRef = (0, import_react6.useRef)(false);
2343
+ const [open, setOpen] = (0, import_react6.useState)(false);
2344
+ const [refreshing, setRefreshing] = (0, import_react6.useState)(false);
2345
+ const [cooldownUntil, setCooldownUntil] = (0, import_react6.useState)(0);
2346
+ const [now, setNow] = (0, import_react6.useState)(() => Date.now());
2190
2347
  const provider = useRouteProvider(routeHint);
2191
- const rootRef = (0, import_react5.useRef)(null);
2192
- const panelRef = (0, import_react5.useRef)(null);
2193
- const refreshTimer = (0, import_react5.useRef)(void 0);
2194
- const lastScenario = (0, import_react5.useRef)(null);
2195
- const acceptBalance = (0, import_react5.useCallback)((next) => {
2348
+ const rootRef = (0, import_react6.useRef)(null);
2349
+ const panelRef = (0, import_react6.useRef)(null);
2350
+ const refreshTimer = (0, import_react6.useRef)(void 0);
2351
+ const lastScenario = (0, import_react6.useRef)(null);
2352
+ const acceptBalance = (0, import_react6.useCallback)((next) => {
2196
2353
  setView({ response: next, seenAt: Date.now(), seenAgeMs: next.ageMs });
2197
2354
  }, []);
2198
- const showUnreachable = (0, import_react5.useCallback)((error) => {
2355
+ const showUnreachable = (0, import_react6.useCallback)((error) => {
2199
2356
  const message = error instanceof Error ? error.message : String(error);
2200
2357
  setView((current) => ({ ...current, response: unreachableView(message) }));
2201
2358
  }, []);
2202
- const markFresh = (0, import_react5.useCallback)(() => {
2359
+ const markFresh = (0, import_react6.useCallback)(() => {
2203
2360
  setView((current) => ({ ...current, seenAt: Date.now(), seenAgeMs: 0 }));
2204
2361
  }, []);
2205
2362
  const preference = config.displayCurrency;
2206
- const selection = (0, import_react5.useMemo)(() => selectionOf(response, preference), [response, preference]);
2207
- (0, import_react5.useEffect)(
2363
+ const selection = (0, import_react6.useMemo)(() => selectionOf(response, preference), [response, preference]);
2364
+ (0, import_react6.useEffect)(
2208
2365
  () => subscribeScenario((key) => {
2209
2366
  const active = resolveScenario() !== null;
2210
2367
  setMock(active);
@@ -2217,7 +2374,7 @@ function SidebarBalance({
2217
2374
  }),
2218
2375
  [acceptBalance]
2219
2376
  );
2220
- (0, import_react5.useEffect)(() => {
2377
+ (0, import_react6.useEffect)(() => {
2221
2378
  if (mock) return;
2222
2379
  let cancelled = false;
2223
2380
  const load = async () => {
@@ -2251,19 +2408,19 @@ function SidebarBalance({
2251
2408
  acceptBalance,
2252
2409
  showUnreachable
2253
2410
  ]);
2254
- (0, import_react5.useEffect)(
2411
+ (0, import_react6.useEffect)(
2255
2412
  () => () => {
2256
2413
  if (refreshTimer.current !== void 0) window.clearTimeout(refreshTimer.current);
2257
2414
  },
2258
2415
  []
2259
2416
  );
2260
- const closeNow = (0, import_react5.useCallback)(() => {
2417
+ const closeNow = (0, import_react6.useCallback)(() => {
2261
2418
  setOpen(false);
2262
2419
  }, []);
2263
- const toggleOpen = (0, import_react5.useCallback)(() => {
2420
+ const toggleOpen = (0, import_react6.useCallback)(() => {
2264
2421
  setOpen((value) => !value);
2265
2422
  }, []);
2266
- const pos = (0, import_dsh_client_ui_primitives4.useAnchoredPosition)({
2423
+ const pos = (0, import_dsh_client_ui_primitives5.useAnchoredPosition)({
2267
2424
  open,
2268
2425
  anchorRef: rootRef,
2269
2426
  panelRef,
@@ -2271,11 +2428,11 @@ function SidebarBalance({
2271
2428
  gap: PANEL_GAP,
2272
2429
  margin: PANEL_MARGIN
2273
2430
  });
2274
- (0, import_react5.useLayoutEffect)(() => {
2431
+ (0, import_react6.useLayoutEffect)(() => {
2275
2432
  if (!open) return;
2276
2433
  setNow(Date.now());
2277
2434
  }, [open]);
2278
- (0, import_react5.useEffect)(() => {
2435
+ (0, import_react6.useEffect)(() => {
2279
2436
  if (!open) return;
2280
2437
  const id = window.setInterval(() => {
2281
2438
  setNow(Date.now());
@@ -2284,7 +2441,7 @@ function SidebarBalance({
2284
2441
  window.clearInterval(id);
2285
2442
  };
2286
2443
  }, [open]);
2287
- (0, import_react5.useEffect)(() => {
2444
+ (0, import_react6.useEffect)(() => {
2288
2445
  if (cooldownUntil === 0) return;
2289
2446
  const remaining = cooldownUntil - Date.now();
2290
2447
  if (remaining <= 0) return;
@@ -2295,7 +2452,7 @@ function SidebarBalance({
2295
2452
  window.clearTimeout(id);
2296
2453
  };
2297
2454
  }, [cooldownUntil]);
2298
- (0, import_react5.useEffect)(() => {
2455
+ (0, import_react6.useEffect)(() => {
2299
2456
  if (!open) return;
2300
2457
  const onKeyDown = (event) => {
2301
2458
  if (event.key === "Escape") closeNow();
@@ -2305,8 +2462,8 @@ function SidebarBalance({
2305
2462
  document.removeEventListener("keydown", onKeyDown);
2306
2463
  };
2307
2464
  }, [open, closeNow]);
2308
- (0, import_dsh_client_ui_primitives4.useDismissOnOutsidePointer)(rootRef, open, setOpen, panelRef);
2309
- const handleRefresh = (0, import_react5.useCallback)(() => {
2465
+ (0, import_dsh_client_ui_primitives5.useDismissOnOutsidePointer)(rootRef, open, setOpen, panelRef);
2466
+ const handleRefresh = (0, import_react6.useCallback)(() => {
2310
2467
  if (refreshing) return;
2311
2468
  if (Date.now() < cooldownUntil) return;
2312
2469
  setRefreshing(true);
@@ -2351,13 +2508,13 @@ function SidebarBalance({
2351
2508
  showUnreachable,
2352
2509
  markFresh
2353
2510
  ]);
2354
- const handleUseShown = (0, import_react5.useCallback)(() => {
2511
+ const handleUseShown = (0, import_react6.useCallback)(() => {
2355
2512
  const current = selection.shown;
2356
2513
  if (current === null) return;
2357
2514
  void onSelectCurrency(current.currency);
2358
2515
  }, [selection, onSelectCurrency]);
2359
2516
  const pluginsAction = usePluginsNavigation(pluginsNavigation);
2360
- const popoverPluginsAction = (0, import_react5.useMemo)(
2517
+ const popoverPluginsAction = (0, import_react6.useMemo)(
2361
2518
  () => pluginsAction === void 0 ? void 0 : {
2362
2519
  reachesConfig: pluginsAction.reachesConfig,
2363
2520
  open: () => {
@@ -2395,10 +2552,11 @@ function SidebarBalance({
2395
2552
  const ariaLabel = ariaParts.join(" ");
2396
2553
  const cooldownSeconds = cooldownSecondsOf(cooldownUntil, now);
2397
2554
  const sourceKey = shown === null ? null : sourceLabelKeyOf(response.source);
2398
- const sourceLabel = sourceKey === null ? null : t(sourceKey);
2555
+ const sourceIdentity = shown === null ? null : sourceIdentityOf(response);
2556
+ const sourceLabel = shown === null ? null : sourceIdentity !== null ? interpolate(t("popover.source.identity"), { identity: sourceIdentity }) : sourceKey === null ? null : t(sourceKey);
2399
2557
  const ageMs = currentAgeMs(view.seenAt, view.seenAgeMs, now);
2400
2558
  const hoverText = hoverTextOf(shown, stateText);
2401
- const trigger = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2559
+ const trigger = /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2402
2560
  "button",
2403
2561
  {
2404
2562
  type: "button",
@@ -2406,11 +2564,11 @@ function SidebarBalance({
2406
2564
  "aria-label": ariaLabel,
2407
2565
  "aria-expanded": open,
2408
2566
  onClick: toggleOpen,
2409
- children: wide ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
2410
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: SidebarBalance_default.icon, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PercentRing, { state: ring.state, marker: ring.marker, ratio: ringRatio, size: 16 }) }),
2411
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: SidebarBalance_default.label, children: t("sidebar.label") }),
2412
- markerLabel === null ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_dsh_client_ui_primitives4.Tooltip, { label: markerHint, side: "bottom", delayMs: 500, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: SidebarBalance_default.marker, role: "img", "aria-label": markerLabel, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_dsh_client_ui_primitives4.IconWarningOutlineRegular, { size: 12 }) }) })
2413
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2567
+ children: wide ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2568
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: SidebarBalance_default.icon, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PercentRing, { state: ring.state, marker: ring.marker, ratio: ringRatio, size: 16 }) }),
2569
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: SidebarBalance_default.label, children: t("sidebar.label") }),
2570
+ markerLabel === null ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_dsh_client_ui_primitives5.Tooltip, { label: markerHint, side: "bottom", delayMs: 500, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: SidebarBalance_default.marker, role: "img", "aria-label": markerLabel, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_dsh_client_ui_primitives5.IconWarningOutlineRegular, { size: 12 }) }) })
2571
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2414
2572
  PercentRing,
2415
2573
  {
2416
2574
  state: ring.state,
@@ -2422,10 +2580,10 @@ function SidebarBalance({
2422
2580
  )
2423
2581
  }
2424
2582
  );
2425
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { ref: rootRef, className: SidebarBalance_default.root, "data-mode": wide ? "wide" : "rail", tabIndex: -1, children: [
2426
- wide && hoverText !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_dsh_client_ui_primitives4.Tooltip, { label: hoverText, side: "right", delayMs: 500, disabled: open, children: trigger }) : trigger,
2583
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { ref: rootRef, className: SidebarBalance_default.root, "data-mode": wide ? "wide" : "rail", tabIndex: -1, children: [
2584
+ wide && hoverText !== null ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_dsh_client_ui_primitives5.Tooltip, { label: hoverText, side: "right", delayMs: 500, disabled: open, children: trigger }) : trigger,
2427
2585
  open ? (0, import_react_dom.createPortal)(
2428
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2586
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2429
2587
  BalancePopover,
2430
2588
  {
2431
2589
  t,
@@ -2510,7 +2668,7 @@ function createRouteHint(services) {
2510
2668
  }
2511
2669
 
2512
2670
  // src/client/index.tsx
2513
- var import_jsx_runtime6 = require("react/jsx-runtime");
2671
+ var import_jsx_runtime8 = require("react/jsx-runtime");
2514
2672
  var ENTRY_ID = "dsh-ds-balance";
2515
2673
  var BUNDLE_CONFIG_KEY = "dsh-ds-balance";
2516
2674
  var SIDEBAR_ENTRY_ID = "ds-balance";
@@ -2533,9 +2691,9 @@ function asRecord2(value) {
2533
2691
  return isRecord2(value) ? value : {};
2534
2692
  }
2535
2693
  function useFormSnapshot(form) {
2536
- const subscribe = (0, import_react6.useCallback)((listener) => form.subscribe(listener), [form]);
2537
- const getSnapshot = (0, import_react6.useCallback)(() => form.getSnapshot(), [form]);
2538
- return (0, import_react6.useSyncExternalStore)(subscribe, getSnapshot);
2694
+ const subscribe = (0, import_react7.useCallback)((listener) => form.subscribe(listener), [form]);
2695
+ const getSnapshot = (0, import_react7.useCallback)(() => form.getSnapshot(), [form]);
2696
+ return (0, import_react7.useSyncExternalStore)(subscribe, getSnapshot);
2539
2697
  }
2540
2698
  function useScopeValue(form) {
2541
2699
  return asRecord2(useFormSnapshot(form).value);
@@ -2567,11 +2725,11 @@ function readSignature(value) {
2567
2725
  function SidebarSeatComponent(props) {
2568
2726
  const value = useScopeValue(props.form);
2569
2727
  const writable = useScopeWritable(props.form);
2570
- const selectCurrency = (0, import_react6.useCallback)(
2728
+ const selectCurrency = (0, import_react7.useCallback)(
2571
2729
  (currency) => writeFieldValue(props.form, "displayCurrency", currency),
2572
2730
  [props.form]
2573
2731
  );
2574
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2732
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2575
2733
  SidebarBalance,
2576
2734
  {
2577
2735
  wide: props.seat.wide,
@@ -2593,7 +2751,7 @@ function SidebarSeatComponent(props) {
2593
2751
  }
2594
2752
  function SettingsSeatComponent(props) {
2595
2753
  const t = translateOf(props.seat.t);
2596
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BalanceSettingsCard, { t, form: props.form });
2754
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(BalanceSettingsCard, { t, form: props.form });
2597
2755
  }
2598
2756
  var PLUGINS_PANEL_ID = "plugins";
2599
2757
  function createPluginsNavigation() {
@@ -2702,14 +2860,14 @@ function apply(ctx) {
2702
2860
  CONFIG_SLOT,
2703
2861
  () => ctx.slots.register(
2704
2862
  { name: CONFIG_SLOT, key: BUNDLE_CONFIG_KEY, locale: NS },
2705
- (seat) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SettingsSeatComponent, { seat, form })
2863
+ (seat) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SettingsSeatComponent, { seat, form })
2706
2864
  )
2707
2865
  );
2708
2866
  ctx.slots.inject(
2709
2867
  "sidebar.footer.action",
2710
2868
  () => ctx.slots.register(
2711
2869
  { name: "sidebar.footer.action", id: SIDEBAR_ENTRY_ID, order: 0, locale: NS },
2712
- (seat) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2870
+ (seat) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2713
2871
  SidebarSeatComponent,
2714
2872
  {
2715
2873
  seat,
@@ -2723,7 +2881,7 @@ function apply(ctx) {
2723
2881
  );
2724
2882
  });
2725
2883
  }
2726
- var __dsbCss = "/* src/client/settings/fields.module.css */\n.fields_group {\n padding: 12px 0 0;\n}\n.fields_groupLast {\n padding-bottom: 12px;\n}\n.fields_groupNote {\n margin: 4px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.fields_fields {\n display: flex;\n flex-direction: column;\n}\n.fields_fields > * + * {\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n.fields_details {\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n margin-top: 12px;\n padding-top: 12px;\n}\n.fields_detailsBody {\n padding-top: 10px;\n}\n.fields_field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 0;\n}\n.fields_row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 0;\n}\n.fields_rowText {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding-right: 48px;\n}\n.fields_rowTitleLine {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.fields_rowTitle {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n font-weight: 500;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.fields_head {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.fields_label {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n font-weight: 500;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.fields_badges {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n.fields_reset {\n border: none;\n background: none;\n padding: 0;\n font: inherit;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n.fields_reset:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n}\n.fields_reset:disabled {\n cursor: default;\n}\n.fields_inputStatic,\n.fields_inputStatic:hover {\n pointer-events: none;\n}\n.fields_inputStatic input,\n.fields_inputStatic input:hover,\n.fields_inputStatic input:focus,\n.fields_inputStatic input:focus-visible,\n.fields_inputStatic input:active {\n border-color: var(--dsw-alias-border-l4);\n background: var(--dsw-alias-bg-layer-3);\n outline: none;\n box-shadow: none;\n cursor: default;\n}\n.fields_inputInvalid input {\n border: 1px solid var(--dsw-alias-state-error-primary);\n}\n.fields_inputInvalid input:focus-visible {\n border-color: var(--dsw-alias-brand-primary);\n}\n.fields_inputWithAction {\n position: relative;\n display: flex;\n align-items: center;\n}\n.fields_inputWithAction .fields_input,\n.fields_inputWithAction .fields_inputInvalid {\n padding-right: 38px;\n}\n.fields_reveal {\n position: absolute;\n right: 4px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: none;\n border-radius: 50%;\n corner-shape: round;\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.fields_reveal:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n.fields_revealOn {\n color: var(--dsw-alias-label-primary);\n}\n.fields_reveal:disabled {\n cursor: default;\n opacity: 0.4;\n}\n.fields_selectorAnchor {\n flex: none;\n}\n.fields_selector {\n display: inline-flex;\n align-items: center;\n gap: 12px;\n flex: none;\n max-width: 100%;\n height: 36px;\n padding: 0 14px;\n border: none;\n border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-module-platform);\n font: inherit;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-primary);\n text-align: left;\n cursor: pointer;\n}\n.fields_selector:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.fields_selector:disabled {\n color: var(--dsw-alias-label-tertiary);\n cursor: default;\n}\n.fields_selectorChevron {\n flex: none;\n}\n.fields_hint {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.fields_pairNote {\n display: flex;\n align-items: center;\n gap: 6px;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-warn-primary);\n}\n.fields_pairNote::before {\n content: \"\";\n flex: none;\n width: 5px;\n height: 5px;\n border-radius: 50%;\n corner-shape: round;\n background: currentColor;\n}\n.fields_invalid {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-error-primary);\n}\n.fields_actions {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.fields_resultOk {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-success-primary);\n}\n.fields_resultFail {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-error-primary);\n}\n\n/* src/client/settings/BalanceSettingsCard.module.css */\n.BalanceSettingsCard_form {\n display: flex;\n flex-direction: column;\n}\n.BalanceSettingsCard_readOnly {\n margin: 0 0 12px;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.BalanceSettingsCard_footer {\n display: flex;\n align-items: center;\n gap: 8px;\n padding-top: 16px;\n}\n.BalanceSettingsCard_failed {\n flex: 1;\n min-width: 0;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-error-primary);\n}\n.BalanceSettingsCard_notice {\n flex: 1;\n min-width: 0;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* src/client/sidebar/BalancePopover.module.css */\n.BalancePopover_panel {\n position: fixed;\n isolation: isolate;\n z-index: 1100;\n box-sizing: border-box;\n width: max-content;\n min-width: min(300px, calc(100vw - 24px));\n max-width: min(440px, calc(100vw - 24px));\n padding: 16px;\n border: 0;\n border-radius: var(--dsw-radius-lg);\n --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);\n box-shadow: var(--dsw-elevation-prominent);\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n cursor: default;\n}\n.BalancePopover_panel::before {\n position: absolute;\n z-index: -1;\n inset: 0;\n border-radius: inherit;\n background: var(--dsw-specific-menu);\n backdrop-filter: var(--dsw-menu-backdrop-filter);\n content: \"\";\n pointer-events: none;\n}\n.BalancePopover_title {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n margin-bottom: 8px;\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n.BalancePopover_titleMain {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n.BalancePopover_titleLabel {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n cursor: pointer;\n color: inherit;\n text-decoration: underline;\n text-underline-offset: 2px;\n}\n.BalancePopover_titleLabel:hover,\n.BalancePopover_titleLabel:visited {\n color: inherit;\n}\n.BalancePopover_sourceLabel {\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n font-weight: 400;\n}\n.BalancePopover_titleLabel svg {\n flex: none;\n width: 14px;\n height: auto;\n}\n.BalancePopover_iconButton {\n position: relative;\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n margin-block: -5px;\n border: none;\n border-radius: var(--dsw-radius-sm);\n padding: 0;\n background: transparent;\n cursor: pointer;\n color: var(--dsw-alias-label-secondary);\n}\n.BalancePopover_iconButton:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.BalancePopover_iconButton:focus-visible {\n outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color);\n outline-offset: -2px;\n}\n.BalancePopover_titleRule {\n margin-bottom: 10px;\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n.BalancePopover_details {\n display: grid;\n grid-template-columns: minmax(76px, auto) minmax(0, 1fr);\n gap: 6px 16px;\n margin: 0;\n color: var(--dsw-alias-label-tertiary);\n}\n.BalancePopover_details dt,\n.BalancePopover_details dd {\n min-width: 0;\n margin: 0;\n}\n.BalancePopover_details dd {\n color: var(--dsw-alias-label-secondary);\n font-variant-numeric: tabular-nums;\n text-align: right;\n}\n.BalancePopover_notice {\n margin-top: 12px;\n}\n.BalancePopover_noticeText {\n margin: 0 0 8px;\n color: var(--dsw-alias-label-tertiary);\n}\n.BalancePopover_noticeActions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n}\n.BalancePopover_footer {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 12px;\n}\n.BalancePopover_updated {\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n}\n.BalancePopover_status {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n text-align: right;\n color: var(--dsw-alias-label-tertiary);\n}\n.BalancePopover_refresh {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 0;\n border: none;\n border-radius: var(--dsw-radius-sm);\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.BalancePopover_refresh:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n.BalancePopover_refresh:focus-visible {\n outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color);\n outline-offset: -2px;\n}\n.BalancePopover_refresh:disabled {\n opacity: 0.4;\n cursor: default;\n}\n.BalancePopover_refresh[data-cooling] {\n color: var(--dsw-alias-label-caption);\n}\n.BalancePopover_refresh[data-refreshing] svg {\n animation: BalancePopover_balance-refresh-spin 900ms linear infinite;\n}\n@keyframes BalancePopover_balance-refresh-spin {\n to {\n transform: rotate(360deg);\n }\n}\n@media (prefers-reduced-motion: reduce) {\n .BalancePopover_refresh[data-refreshing] svg {\n animation-duration: 2400ms;\n }\n}\n\n/* src/client/sidebar/PercentRing.module.css */\n.PercentRing_ring {\n display: block;\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n}\n.PercentRing_ring[data-state=done] {\n color: var(--dsw-alias-state-success-primary);\n}\n.PercentRing_ring[data-state=warning] {\n color: var(--dsw-alias-state-warn-primary);\n}\n.PercentRing_ring[data-state=error] {\n color: var(--dsw-alias-state-error-primary);\n}\n.PercentRing_ring[data-state=idle] {\n color: var(--dsw-alias-state-idle-primary);\n}\n.PercentRing_track {\n fill: none;\n stroke: var(--dsw-alias-border-l3);\n stroke-width: 1;\n}\n.PercentRing_fill {\n fill: none;\n stroke: currentColor;\n stroke-width: 1;\n stroke-linecap: round;\n}\n.PercentRing_marker {\n fill: none;\n stroke: currentColor;\n stroke-width: 1;\n stroke-linecap: round;\n}\n.PercentRing_spin {\n transform-origin: center;\n animation: PercentRing_dsb-ring-spin 1.5s linear infinite;\n}\n@keyframes PercentRing_dsb-ring-spin {\n to {\n transform: rotate(360deg);\n }\n}\n@media (prefers-reduced-motion: reduce) {\n .PercentRing_spin {\n animation: none;\n }\n}\n\n/* src/client/sidebar/SidebarBalance.module.css */\n.SidebarBalance_root {\n position: relative;\n display: flex;\n min-width: 0;\n}\n.SidebarBalance_root[data-mode=wide] {\n flex: none;\n width: calc(100% + 4px);\n margin: 4px -2px 0;\n}\n.SidebarBalance_root[data-mode=rail] {\n flex: none;\n margin: 8px 0 0;\n}\n.SidebarBalance_trigger {\n display: flex;\n align-items: center;\n box-sizing: border-box;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 14px;\n line-height: 22px;\n cursor: pointer;\n overflow: hidden;\n}\n.SidebarBalance_trigger:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.SidebarBalance_trigger:focus-visible {\n outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color);\n outline-offset: -2px;\n}\n.SidebarBalance_root[data-mode=wide] .SidebarBalance_trigger {\n flex: none;\n gap: 8px;\n width: 100%;\n height: 42px;\n padding: 0 10px 0 8px;\n border-radius: var(--dsw-radius-md);\n}\n.SidebarBalance_root[data-mode=rail] .SidebarBalance_trigger {\n flex: none;\n justify-content: center;\n width: 36px;\n height: 36px;\n padding: 0;\n border-radius: var(--dsw-radius-md);\n}\n.SidebarBalance_icon {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n}\n.SidebarBalance_label {\n flex: 0 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.SidebarBalance_marker {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n color: var(--dsw-alias-label-tertiary);\n position: relative;\n top: 1.63px;\n}\n\n/* src/client/sidebar/footer-stack.module.css */\ndiv:has(> [data-slot=\"sidebar.footer.action\"]) {\n flex-direction: column;\n}\n";
2884
+ var __dsbCss = "/* src/client/settings/AccountAvatar.module.css */\n.AccountAvatar_avatar {\n box-sizing: border-box;\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n border-radius: 50%;\n corner-shape: round;\n overflow: hidden;\n background: var(--dsw-alias-bg-skeleton);\n}\n.AccountAvatar_image {\n display: block;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n corner-shape: round;\n object-fit: cover;\n}\n\n/* src/client/settings/fields.module.css */\n.fields_group {\n display: flex;\n flex-direction: column;\n width: 100%;\n min-width: 0;\n padding: 12px 0 0;\n}\n.fields_groupHead {\n position: relative;\n display: flex;\n align-items: center;\n height: calc(24px + var(--dsh-content-font-delta, 0px));\n min-width: 0;\n color: var(--dsw-alias-label-tertiary);\n transition: color 100ms ease;\n cursor: pointer;\n}\n.fields_groupHead:hover {\n color: var(--dsw-alias-label-secondary);\n}\n.fields_groupLeading {\n position: relative;\n flex: none;\n width: calc(16px + var(--dsh-content-font-delta, 0px));\n height: calc(16px + var(--dsh-content-font-delta, 0px));\n display: inline-flex;\n align-items: center;\n justify-content: center;\n margin-right: 6px;\n}\n.fields_groupLeading svg {\n width: calc(14px + var(--dsh-content-font-delta, 0px));\n height: calc(14px + var(--dsh-content-font-delta, 0px));\n}\n.fields_groupIcon {\n display: inline-flex;\n opacity: 1;\n transition: opacity 100ms ease;\n}\n.fields_groupChevronClosed,\n.fields_groupChevronOpen {\n position: absolute;\n inset: 0;\n margin: auto;\n opacity: 0;\n pointer-events: none;\n transition: opacity 100ms ease;\n}\n.fields_groupHead[aria-expanded=false]:hover .fields_groupChevronClosed,\n.fields_groupHead[aria-expanded=false]:focus-visible .fields_groupChevronClosed,\n.fields_groupHead[aria-expanded=true]:hover .fields_groupChevronOpen,\n.fields_groupHead[aria-expanded=true]:focus-visible .fields_groupChevronOpen {\n opacity: 1;\n}\n.fields_groupHead:hover .fields_groupIcon,\n.fields_groupHead:focus-visible .fields_groupIcon {\n opacity: 0;\n}\n.fields_groupTitle {\n flex: none;\n font-size: var(--dsh-content-font-size-secondary, 13px);\n line-height: calc(24px + var(--dsh-content-font-delta, 0px));\n color: inherit;\n}\n@media (prefers-reduced-motion: reduce) {\n .fields_groupHead {\n transition: none;\n }\n .fields_groupIcon,\n .fields_groupChevronClosed,\n .fields_groupChevronOpen {\n transition: none;\n }\n}\n.fields_groupLast {\n padding-bottom: 12px;\n}\n.fields_groupNote {\n margin: 4px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.fields_fields {\n display: flex;\n flex-direction: column;\n}\n.fields_fields > * + * {\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n.fields_details {\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n margin-top: 12px;\n padding-top: 12px;\n}\n.fields_detailsBody {\n padding-top: 10px;\n}\n.fields_source {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 0;\n}\n.fields_sourceHead {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.fields_dot {\n box-sizing: border-box;\n display: inline-block;\n flex: none;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n corner-shape: round;\n}\n.fields_dotOk {\n background: var(--dsw-alias-state-success-primary);\n}\n.fields_dotMissing {\n background: var(--dsw-alias-state-error-primary);\n}\n.fields_sourceName {\n flex: none;\n font-size: 13px;\n font-weight: 500;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.fields_sourceSlot {\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 6px;\n}\n.fields_sourceIdentity,\n.fields_sourceLiveText {\n min-width: 0;\n font-size: 12px;\n line-height: 1.5;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--dsw-alias-label-tertiary);\n}\n.fields_sourceIdentityMono {\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Consolas,\n monospace;\n}\n.fields_sourceLive {\n flex: 0 1 auto;\n min-width: 0;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n.fields_sourceLiveIcon {\n flex: none;\n}\n.fields_sourceDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n}\n.fields_field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 0;\n}\n.fields_row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 0;\n}\n.fields_rowText {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding-right: 48px;\n}\n.fields_rowTitleLine {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.fields_rowTitle {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n font-weight: 500;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.fields_head {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.fields_label {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n font-weight: 500;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.fields_badges {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n.fields_reset {\n border: none;\n background: none;\n padding: 0;\n font: inherit;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n.fields_reset:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n}\n.fields_reset:disabled {\n cursor: default;\n}\n.fields_inputStatic,\n.fields_inputStatic:hover {\n pointer-events: none;\n}\n.fields_inputStatic input,\n.fields_inputStatic input:hover,\n.fields_inputStatic input:focus,\n.fields_inputStatic input:focus-visible,\n.fields_inputStatic input:active {\n border-color: var(--dsw-alias-border-l4);\n background: var(--dsw-alias-bg-layer-3);\n outline: none;\n box-shadow: none;\n cursor: default;\n}\n.fields_inputInvalid input {\n border: 1px solid var(--dsw-alias-state-error-primary);\n}\n.fields_inputInvalid input:focus-visible {\n border-color: var(--dsw-alias-brand-primary);\n}\n.fields_inputWithAction {\n position: relative;\n display: flex;\n align-items: center;\n}\n.fields_inputWithAction .fields_input,\n.fields_inputWithAction .fields_inputInvalid {\n padding-right: 38px;\n}\n.fields_reveal {\n position: absolute;\n right: 4px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: none;\n border-radius: 50%;\n corner-shape: round;\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.fields_reveal:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n.fields_revealOn {\n color: var(--dsw-alias-label-primary);\n}\n.fields_reveal:disabled {\n cursor: default;\n opacity: 0.4;\n}\n.fields_selectorAnchor {\n flex: none;\n}\n.fields_selector {\n display: inline-flex;\n align-items: center;\n gap: 12px;\n flex: none;\n max-width: 100%;\n height: 36px;\n padding: 0 14px;\n border: none;\n border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-module-platform);\n font: inherit;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-primary);\n text-align: left;\n cursor: pointer;\n}\n.fields_selector:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.fields_selector:disabled {\n color: var(--dsw-alias-label-tertiary);\n cursor: default;\n}\n.fields_selectorChevron {\n flex: none;\n}\n.fields_hint {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.fields_pairNote {\n display: flex;\n align-items: center;\n gap: 6px;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-warn-primary);\n}\n.fields_pairNote::before {\n content: \"\";\n flex: none;\n width: 5px;\n height: 5px;\n border-radius: 50%;\n corner-shape: round;\n background: currentColor;\n}\n.fields_invalid {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-error-primary);\n}\n.fields_actions {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n/* src/client/settings/BalanceSettingsCard.module.css */\n.BalanceSettingsCard_form {\n display: flex;\n flex-direction: column;\n}\n.BalanceSettingsCard_readOnly {\n margin: 0 0 12px;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.BalanceSettingsCard_footer {\n display: flex;\n align-items: center;\n gap: 8px;\n padding-top: 16px;\n}\n.BalanceSettingsCard_save {\n appearance: none;\n border: 1px solid transparent;\n border-radius: var(--dsw-radius-md);\n padding: 5px 14px;\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n cursor: pointer;\n background: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n}\n.BalanceSettingsCard_save:disabled {\n opacity: 0.4;\n cursor: default;\n}\n.BalanceSettingsCard_save:focus-visible {\n outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color);\n outline-offset: 1px;\n}\n.BalanceSettingsCard_failed {\n flex: 1;\n min-width: 0;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-error-primary);\n}\n.BalanceSettingsCard_notice {\n flex: 1;\n min-width: 0;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* src/client/sidebar/BalancePopover.module.css */\n.BalancePopover_panel {\n position: fixed;\n isolation: isolate;\n z-index: 1100;\n box-sizing: border-box;\n width: max-content;\n min-width: min(300px, calc(100vw - 24px));\n max-width: min(440px, calc(100vw - 24px));\n padding: 16px;\n border: 0;\n border-radius: var(--dsw-radius-lg);\n --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);\n box-shadow: var(--dsw-elevation-prominent);\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n cursor: default;\n}\n.BalancePopover_panel::before {\n position: absolute;\n z-index: -1;\n inset: 0;\n border-radius: inherit;\n background: var(--dsw-specific-menu);\n backdrop-filter: var(--dsw-menu-backdrop-filter);\n content: \"\";\n pointer-events: none;\n}\n.BalancePopover_title {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n margin-bottom: 8px;\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n.BalancePopover_titleMain {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n.BalancePopover_titleLabel {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n cursor: pointer;\n color: inherit;\n text-decoration: underline;\n text-underline-offset: 2px;\n}\n.BalancePopover_titleLabel:hover,\n.BalancePopover_titleLabel:visited {\n color: inherit;\n}\n.BalancePopover_sourceLabel {\n flex: 0 1 auto;\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n color: var(--dsw-alias-label-tertiary);\n font-weight: 400;\n}\n.BalancePopover_titleLabel svg {\n flex: none;\n width: 14px;\n height: auto;\n}\n.BalancePopover_iconButton {\n position: relative;\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n margin-block: -5px;\n border: none;\n border-radius: var(--dsw-radius-sm);\n padding: 0;\n background: transparent;\n cursor: pointer;\n color: var(--dsw-alias-label-secondary);\n}\n.BalancePopover_iconButton:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.BalancePopover_iconButton:focus-visible {\n outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color);\n outline-offset: -2px;\n}\n.BalancePopover_titleRule {\n margin-bottom: 10px;\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n.BalancePopover_details {\n display: grid;\n grid-template-columns: minmax(76px, auto) minmax(0, 1fr);\n gap: 6px 16px;\n margin: 0;\n color: var(--dsw-alias-label-tertiary);\n}\n.BalancePopover_details dt,\n.BalancePopover_details dd {\n min-width: 0;\n margin: 0;\n}\n.BalancePopover_details dd {\n color: var(--dsw-alias-label-secondary);\n font-variant-numeric: tabular-nums;\n text-align: right;\n}\n.BalancePopover_notice {\n margin-top: 12px;\n}\n.BalancePopover_noticeText {\n margin: 0 0 8px;\n color: var(--dsw-alias-label-tertiary);\n}\n.BalancePopover_noticeActions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n}\n.BalancePopover_footer {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 12px;\n}\n.BalancePopover_updated {\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n}\n.BalancePopover_status {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n text-align: right;\n color: var(--dsw-alias-label-tertiary);\n}\n.BalancePopover_refresh {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 0;\n border: none;\n border-radius: var(--dsw-radius-sm);\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.BalancePopover_refresh:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n.BalancePopover_refresh:focus-visible {\n outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color);\n outline-offset: -2px;\n}\n.BalancePopover_refresh:disabled {\n opacity: 0.4;\n cursor: default;\n}\n.BalancePopover_refresh[data-cooling] {\n color: var(--dsw-alias-label-caption);\n}\n.BalancePopover_refresh[data-refreshing] svg {\n animation: BalancePopover_balance-refresh-spin 900ms linear infinite;\n}\n@keyframes BalancePopover_balance-refresh-spin {\n to {\n transform: rotate(360deg);\n }\n}\n@media (prefers-reduced-motion: reduce) {\n .BalancePopover_refresh[data-refreshing] svg {\n animation-duration: 2400ms;\n }\n}\n\n/* src/client/sidebar/PercentRing.module.css */\n.PercentRing_ring {\n display: block;\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n}\n.PercentRing_ring[data-state=done] {\n color: var(--dsw-alias-state-success-primary);\n}\n.PercentRing_ring[data-state=warning] {\n color: var(--dsw-alias-state-warn-primary);\n}\n.PercentRing_ring[data-state=error] {\n color: var(--dsw-alias-state-error-primary);\n}\n.PercentRing_ring[data-state=idle] {\n color: var(--dsw-alias-state-idle-primary);\n}\n.PercentRing_track {\n fill: none;\n stroke: var(--dsw-alias-border-l3);\n stroke-width: 1;\n}\n.PercentRing_fill {\n fill: none;\n stroke: currentColor;\n stroke-width: 1;\n stroke-linecap: round;\n}\n.PercentRing_marker {\n fill: none;\n stroke: currentColor;\n stroke-width: 1;\n stroke-linecap: round;\n}\n.PercentRing_spin {\n transform-origin: center;\n animation: PercentRing_dsb-ring-spin 1.5s linear infinite;\n}\n@keyframes PercentRing_dsb-ring-spin {\n to {\n transform: rotate(360deg);\n }\n}\n@media (prefers-reduced-motion: reduce) {\n .PercentRing_spin {\n animation: none;\n }\n}\n\n/* src/client/sidebar/SidebarBalance.module.css */\n.SidebarBalance_root {\n position: relative;\n display: flex;\n min-width: 0;\n}\n.SidebarBalance_root[data-mode=wide] {\n flex: none;\n width: calc(100% + 4px);\n margin: 4px -2px 0;\n}\n.SidebarBalance_root[data-mode=rail] {\n flex: none;\n margin: 8px 0 0;\n}\n.SidebarBalance_trigger {\n display: flex;\n align-items: center;\n box-sizing: border-box;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 14px;\n line-height: 22px;\n cursor: pointer;\n overflow: hidden;\n}\n.SidebarBalance_trigger:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.SidebarBalance_trigger:focus-visible {\n outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color);\n outline-offset: -2px;\n}\n.SidebarBalance_root[data-mode=wide] .SidebarBalance_trigger {\n flex: none;\n gap: 8px;\n width: 100%;\n height: 42px;\n padding: 0 10px 0 8px;\n border-radius: var(--dsw-radius-md);\n}\n.SidebarBalance_root[data-mode=rail] .SidebarBalance_trigger {\n flex: none;\n justify-content: center;\n width: 36px;\n height: 36px;\n padding: 0;\n border-radius: var(--dsw-radius-md);\n}\n.SidebarBalance_icon {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n}\n.SidebarBalance_label {\n flex: 0 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.SidebarBalance_marker {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n color: var(--dsw-alias-label-tertiary);\n position: relative;\n top: 1.63px;\n}\n\n/* src/client/sidebar/footer-stack.module.css */\ndiv:has(> [data-slot=\"sidebar.footer.action\"]) {\n flex-direction: column;\n}\n";
2727
2885
  var __dsbTagId = "dsh-ds-balance/client.css";
2728
2886
  if (typeof document !== 'undefined' && document.querySelector('style[data-plugin-css="' + __dsbTagId + '"]') === null) {
2729
2887
  var __dsbTag = document.createElement('style');