dsh-ds-balance 2.1.3 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -18
- package/README_en.md +72 -18
- package/lib/adapters/domain-core-store.d.ts +2 -2
- package/lib/adapters/domain-core-store.js +3 -1
- package/lib/adapters/host-account.d.ts +66 -0
- package/lib/adapters/host-account.js +68 -0
- package/lib/client.js +437 -95
- package/lib/domain/account.d.ts +40 -0
- package/lib/domain/account.js +74 -0
- package/lib/domain/balance.d.ts +20 -1
- package/lib/domain/normalize.d.ts +3 -2
- package/lib/domain/normalize.js +3 -2
- package/lib/domain/severity.d.ts +8 -2
- package/lib/domain/severity.js +11 -4
- package/lib/domain/situation.d.ts +104 -0
- package/lib/domain/situation.js +93 -0
- package/lib/http/handlers.d.ts +1 -1
- package/lib/http/handlers.js +29 -5
- package/lib/http/wire.d.ts +11 -1
- package/lib/http/wire.js +2 -0
- package/lib/index.js +122 -6
- package/lib/ports/account.d.ts +27 -0
- package/lib/ports/account.js +9 -0
- package/lib/services/balance-service.d.ts +66 -94
- package/lib/services/balance-service.js +132 -196
- package/lib/services/balance-source.d.ts +174 -0
- package/lib/services/balance-source.js +294 -0
- package/lib/services/scheduler.d.ts +8 -2
- package/lib/services/scheduler.js +1 -1
- package/lib/services/source-readers.d.ts +26 -0
- package/lib/services/source-readers.js +73 -0
- package/lib/services/source-selector.d.ts +50 -0
- package/lib/services/source-selector.js +64 -0
- package/lib/types/client/api-types.d.ts +16 -0
- package/lib/types/client/data.d.ts +12 -3
- package/lib/types/client/index.d.ts +6 -0
- package/lib/types/client/locales.d.ts +10 -18
- package/lib/types/client/mock/index.d.ts +12 -11
- package/lib/types/client/mock/scenarios.d.ts +13 -5
- package/lib/types/client/model.d.ts +24 -25
- package/lib/types/client/route-hint.d.ts +61 -0
- package/lib/types/client/sidebar/BalancePopover.d.ts +7 -0
- package/lib/types/client/sidebar/PercentRing.d.ts +13 -5
- package/lib/types/client/sidebar/SidebarBalance.d.ts +9 -1
- package/lib/types/client/situation.d.ts +164 -0
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +25 -23
package/lib/client.js
CHANGED
|
@@ -42,11 +42,14 @@ var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primi
|
|
|
42
42
|
var NS = "ds-balance";
|
|
43
43
|
var zh = {
|
|
44
44
|
"sidebar.label": "DeepSeek \u4F59\u989D",
|
|
45
|
-
"sidebar.aria.balance": "DeepSeek \u4F59\u989D",
|
|
46
45
|
"sidebar.aria.ring": "DeepSeek \u4F59\u989D\u72B6\u6001",
|
|
47
46
|
"sidebar.aria.refresh": "\u5237\u65B0\u4F59\u989D",
|
|
48
47
|
"sidebar.aria.mismatch": "\u663E\u793A\u7684\u5E01\u79CD\u4E0E\u4F60\u9009\u62E9\u7684\u4E0D\u4E00\u81F4",
|
|
49
48
|
"popover.title": "\u8D26\u6237\u4F59\u989D",
|
|
49
|
+
// 来源标签:跟着数据来源走。**括号属于词典** —— 中文全角、英文半角带前置空格,
|
|
50
|
+
// 代码里只做拼接,绝不写死括号字符。
|
|
51
|
+
"popover.source.key": "\uFF08API Key\uFF09",
|
|
52
|
+
"popover.source.account": "\uFF08\u8D26\u53F7\u767B\u5F55\uFF09",
|
|
50
53
|
"popover.total": "\u4F59\u989D",
|
|
51
54
|
"popover.granted": "\u8D60\u9001",
|
|
52
55
|
"popover.toppedUp": "\u5145\u503C",
|
|
@@ -60,12 +63,33 @@ var zh = {
|
|
|
60
63
|
// 措辞就跟着落点分档:一句含糊的话盖不住两种落点。见 BalancePopover 的 PluginsAction。
|
|
61
64
|
"popover.action.openPlugins": "\u6253\u5F00\u63D2\u4EF6\u9875",
|
|
62
65
|
"popover.action.openPluginConfig": "\u6253\u5F00\u63D2\u4EF6\u914D\u7F6E\u9875",
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
// 处境文案:**一个处境一句**,环 / 悬停 / 浮层都从这里取。
|
|
67
|
+
// 键名前缀是 situation 而不是 state:后者是契约字段名,而「处境」是界面概念
|
|
68
|
+
// (一个处境可能吃掉多个 state + severity + error 的组合)。
|
|
69
|
+
//
|
|
70
|
+
// **措辞是给用户看的,不是给开发者看的**。这七句会出现在两处,都必须能**单独读懂**:
|
|
71
|
+
// - **悬停气泡**(收起态与展开态同一份,见 `client/situation.ts` 的 `hoverTextOf`):
|
|
72
|
+
// 数字**可信**时气泡显示金额本身(只有 `ok` / `low` / `critical` 三个处境,
|
|
73
|
+
// 它们的 `textKey` 是 `null`);其余八个处境显示下面这些句子 —— 包括
|
|
74
|
+
// `stale` 与 `account-unavailable`,它们**手里有数字也显文案**;
|
|
75
|
+
// - 浮层与读屏的 `aria-label`。
|
|
76
|
+
//
|
|
77
|
+
// 所以每一句都要能单独回答「**这一格现在怎么了**」:
|
|
78
|
+
//
|
|
79
|
+
// - 不写「插件内部错误」:用户不知道也不关心是哪个组件坏了,他要的是「现在拿不到」。
|
|
80
|
+
// 改「服务异常」—— 说的是同一件事,但主语是这个功能,不是我们的代码。
|
|
81
|
+
// - `unavailable` 刻意**不带「暂时」**。它接住的是 `fetch-failed` 那一整片错误码,
|
|
82
|
+
// 里面既有等一等就好的(网络 / 超时 / 429 / 5xx),也有**等不好的**:
|
|
83
|
+
// `UPSTREAM_401`(key 失效)与 `UPSTREAM_402`(欠费)。对后者说「暂时」是假话,
|
|
84
|
+
// 而且是有害的假话 —— 用户该去改凭据、去充值,不是在原地等。
|
|
85
|
+
// 宁可少说一层时间性,也不能说反。
|
|
86
|
+
"situation.checking": "\u6B63\u5728\u83B7\u53D6",
|
|
87
|
+
"situation.unavailable": "\u8BFB\u4E0D\u5230\u4F59\u989D",
|
|
88
|
+
"situation.internalError": "\u670D\u52A1\u5F02\u5E38",
|
|
89
|
+
"situation.noCredential": "\u5C1A\u672A\u914D\u7F6E\u51ED\u636E",
|
|
90
|
+
"situation.accountUnavailable": "\u8D26\u6237\u4E0D\u53EF\u7528",
|
|
91
|
+
"situation.stale": "\u6570\u636E\u5DF2\u8FC7\u671F",
|
|
92
|
+
"situation.emptyWallet": "\u6682\u65E0\u4F59\u989D",
|
|
69
93
|
"settings.group.connection": "\u8FDE\u63A5",
|
|
70
94
|
"settings.group.refresh": "\u5237\u65B0",
|
|
71
95
|
"settings.group.display": "\u5C55\u793A",
|
|
@@ -81,15 +105,13 @@ var zh = {
|
|
|
81
105
|
"settings.field.cnyCritical": "CNY \u544A\u6025",
|
|
82
106
|
"settings.field.usdWarn": "USD \u9884\u8B66",
|
|
83
107
|
"settings.field.usdCritical": "USD \u544A\u6025",
|
|
84
|
-
"settings.hint.apiKey": "\u7559\u7A7A\u5219\u6539\u7528\u5F15\u7528\u540D\u4ECE\u51ED\u636E\u5B58\u50A8\u8BFB\u53D6\u3002",
|
|
85
108
|
"settings.hint.apiKeyRef": "\u51ED\u636E\u5B58\u50A8\u91CC\u7684\u6761\u76EE\u540D\uFF1B\u975E\u7A7A\u65F6\u4F18\u5148\u4E8E API Key\u3002",
|
|
86
109
|
"settings.hint.baseUrl": "\u7559\u7A7A\u5219\u4F7F\u7528\u63D0\u4F9B\u65B9\u9ED8\u8BA4\u5730\u5740\u3002",
|
|
87
110
|
"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",
|
|
88
|
-
"settings.hint.number": "\u8BF7\u586B\u6570\u5B57\uFF1B\u7559\u7A7A\u8868\u793A\u4F7F\u7528\u9ED8\u8BA4\u503C\u3002",
|
|
89
111
|
"settings.hint.threshold": "\u540E\u7AEF\u5224\u5B9A\uFF1B\u754C\u9762\u989C\u8272\u8DDF\u968F\u8FD4\u56DE\u7684\u4E25\u91CD\u5EA6\u3002",
|
|
90
112
|
"settings.hint.thresholdPair": "\u544A\u6025\u503C\u5FC5\u987B\u4F4E\u4E8E\u9884\u8B66\u503C",
|
|
91
113
|
"settings.hint.serverRefreshSeconds": "\u8BBF\u95EE\u5B98\u65B9\u63A5\u53E3\u7684\u95F4\u9694\u3002",
|
|
92
|
-
"settings.hint.clientPollSeconds": "\u5237\u65B0\u663E\u793A\u7684\u95F4\u9694\uFF1B\u53EA\u8BFB\
|
|
114
|
+
"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",
|
|
93
115
|
"settings.hint.manualRefreshCooldownSeconds": "\u4E24\u6B21\u624B\u52A8\u5237\u65B0\u4E4B\u95F4\u7684\u6700\u77ED\u95F4\u9694\u3002",
|
|
94
116
|
"settings.currency.auto": "\u81EA\u52A8\uFF08\u8DDF\u968F\u8D26\u6237\uFF09",
|
|
95
117
|
// 凭据行的**状态徽章**:两条逐字用官方措辞 —— 取自 ui-settings-plugins 的
|
|
@@ -115,21 +137,24 @@ var zh = {
|
|
|
115
137
|
"settings.overridden": "\u5DF2\u8986\u76D6",
|
|
116
138
|
"settings.reset": "\u91CD\u7F6E",
|
|
117
139
|
"settings.invalidNumber": "\u8BF7\u586B\u6570\u5B57\uFF1B\u7559\u7A7A\u8868\u793A\u4F7F\u7528\u9ED8\u8BA4\u503C\u3002",
|
|
118
|
-
"settings.hint.refreshAdvanced": "\u4E0D\u5E38\u6539\uFF1A\u9ED8\u8BA4\u503C\u9002\u7528\u4E8E\u5927\u591A\u6570\u60C5\u51B5\u3002"
|
|
119
|
-
"settings.currencyMismatch": "\u5F53\u524D\u8D26\u6237\u65E0 {wanted} \u4F59\u989D\uFF0C\u5B9E\u9645\u663E\u793A {shown}",
|
|
140
|
+
"settings.hint.refreshAdvanced": "\u4E0D\u5E38\u6539\uFF1A\u9ED8\u8BA4\u503C\u9002\u7528\u4E8E\u5927\u591A\u6570\u60C5\u51B5\u3002"
|
|
120
141
|
// 这里曾有一条 'settings.summary'(该行 Configure 子页的说明行)。回退到
|
|
121
142
|
// plugins.bundle.config 之后没有任何渲染路径会问 summary 档,于是连同它一起删了 ——
|
|
122
143
|
// 详见 src/client/index.tsx 的 SettingsSeat。
|
|
123
|
-
|
|
124
|
-
|
|
144
|
+
// 本轮又删掉四条**孤儿键**(词典里有、全仓没人引用):`sidebar.aria.balance`
|
|
145
|
+
// (与 sidebar.label 逐字相同,rail 的 aria 走 aria.ring)、`settings.hint.apiKey`
|
|
146
|
+
// (那一行用的是 settings.hint.credential)、`settings.hint.number`
|
|
147
|
+
// (真正用的是 settings.invalidNumber,两者文案原本就重复)、
|
|
148
|
+
// `settings.currencyMismatch`(出到屏幕的是 popover.mismatch)。
|
|
125
149
|
};
|
|
126
150
|
var en = {
|
|
127
151
|
"sidebar.label": "DeepSeek balance",
|
|
128
|
-
"sidebar.aria.balance": "DeepSeek balance",
|
|
129
152
|
"sidebar.aria.ring": "DeepSeek balance status",
|
|
130
153
|
"sidebar.aria.refresh": "Refresh balance",
|
|
131
154
|
"sidebar.aria.mismatch": "Shown currency differs from your selection",
|
|
132
155
|
"popover.title": "Account balance",
|
|
156
|
+
"popover.source.key": " (API key)",
|
|
157
|
+
"popover.source.account": " (account sign-in)",
|
|
133
158
|
"popover.total": "Balance",
|
|
134
159
|
"popover.granted": "Granted",
|
|
135
160
|
"popover.toppedUp": "Topped up",
|
|
@@ -141,12 +166,16 @@ var en = {
|
|
|
141
166
|
"popover.action.useShown": "Use {shown}",
|
|
142
167
|
"popover.action.openPlugins": "Open the Plugins page",
|
|
143
168
|
"popover.action.openPluginConfig": "Open the plugin configuration page",
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
"
|
|
148
|
-
"
|
|
149
|
-
"
|
|
169
|
+
// 与 zh 逐条对应,理由写在 zh 那一段。
|
|
170
|
+
// `unavailable` 同样**不带 Temporarily**:它接住 401(key 失效)/ 402(欠费)这类
|
|
171
|
+
// 「等不好」的错误,说「暂时」会把人劝去干等。
|
|
172
|
+
"situation.checking": "Loading",
|
|
173
|
+
"situation.unavailable": "Balance unavailable",
|
|
174
|
+
"situation.internalError": "Service error",
|
|
175
|
+
"situation.noCredential": "No credential configured",
|
|
176
|
+
"situation.accountUnavailable": "Account unavailable",
|
|
177
|
+
"situation.stale": "Data is stale",
|
|
178
|
+
"situation.emptyWallet": "No balance",
|
|
150
179
|
"settings.group.connection": "Connection",
|
|
151
180
|
"settings.group.refresh": "Refresh",
|
|
152
181
|
"settings.group.display": "Display",
|
|
@@ -162,15 +191,13 @@ var en = {
|
|
|
162
191
|
"settings.field.cnyCritical": "CNY critical",
|
|
163
192
|
"settings.field.usdWarn": "USD warning",
|
|
164
193
|
"settings.field.usdCritical": "USD critical",
|
|
165
|
-
"settings.hint.apiKey": "Leave empty to resolve from the credential reference instead.",
|
|
166
194
|
"settings.hint.apiKeyRef": "Credential store entry name; takes precedence over the API key.",
|
|
167
195
|
"settings.hint.baseUrl": "Leave blank to use the provider default.",
|
|
168
196
|
"settings.hint.displayCurrency": "Falls back to a currency the account actually holds.",
|
|
169
|
-
"settings.hint.number": "Enter a number; leave empty to use the default.",
|
|
170
197
|
"settings.hint.threshold": "Decided on the server; the UI colour follows the returned severity.",
|
|
171
198
|
"settings.hint.thresholdPair": "Critical must be lower than warning",
|
|
172
199
|
"settings.hint.serverRefreshSeconds": "How often the official API is called.",
|
|
173
|
-
"settings.hint.clientPollSeconds": "How often the display refreshes;
|
|
200
|
+
"settings.hint.clientPollSeconds": "How often the display refreshes; cache-only while healthy.",
|
|
174
201
|
"settings.hint.manualRefreshCooldownSeconds": "Minimum gap between two manual refreshes.",
|
|
175
202
|
"settings.currency.auto": "Automatic (follow account)",
|
|
176
203
|
"settings.credential.configured": "A key is configured.",
|
|
@@ -192,10 +219,8 @@ var en = {
|
|
|
192
219
|
"settings.overridden": "Overridden",
|
|
193
220
|
"settings.reset": "Reset",
|
|
194
221
|
"settings.invalidNumber": "Enter a number; leave empty to use the default.",
|
|
195
|
-
"settings.hint.refreshAdvanced": "Rarely changed: the defaults suit most setups."
|
|
196
|
-
|
|
197
|
-
"dev.title": "Dev scenario",
|
|
198
|
-
"dev.hint": "UI development only; does not affect real configuration."
|
|
222
|
+
"settings.hint.refreshAdvanced": "Rarely changed: the defaults suit most setups."
|
|
223
|
+
// 与 zh 同批删掉四条孤儿键,以及 `dev.*`(见 zh 那段末尾的说明)。
|
|
199
224
|
};
|
|
200
225
|
function interpolate(template, params) {
|
|
201
226
|
return template.replace(
|
|
@@ -853,6 +878,9 @@ function pendingView() {
|
|
|
853
878
|
return {
|
|
854
879
|
requestId: "",
|
|
855
880
|
schemaVersion: 1,
|
|
881
|
+
// 占位视图没有取数路可言;处境为 checking / offline 时界面都不渲染来源标签。
|
|
882
|
+
source: "deepseek-http",
|
|
883
|
+
situation: "checking",
|
|
856
884
|
state: "empty",
|
|
857
885
|
stale: false,
|
|
858
886
|
fetchedAt: 0,
|
|
@@ -870,6 +898,7 @@ function pendingView() {
|
|
|
870
898
|
function unreachableView(message) {
|
|
871
899
|
return {
|
|
872
900
|
...pendingView(),
|
|
901
|
+
situation: "offline",
|
|
873
902
|
state: "error",
|
|
874
903
|
error: { code: UNREACHABLE_CODE, message }
|
|
875
904
|
};
|
|
@@ -884,7 +913,11 @@ async function readJson(response, path) {
|
|
|
884
913
|
}
|
|
885
914
|
async function requestBalance(options) {
|
|
886
915
|
const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
|
|
887
|
-
const
|
|
916
|
+
const query = new URLSearchParams({ currency: options.currency });
|
|
917
|
+
if (options.provider !== void 0 && options.provider !== "") {
|
|
918
|
+
query.set("provider", options.provider);
|
|
919
|
+
}
|
|
920
|
+
const url = `${BALANCE_PATH}?${query.toString()}`;
|
|
888
921
|
const response = await fetchImpl(url, {
|
|
889
922
|
headers: { accept: "application/json" },
|
|
890
923
|
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
@@ -913,7 +946,10 @@ async function requestRefresh(options = {}) {
|
|
|
913
946
|
const response = await fetchImpl(REFRESH_PATH, {
|
|
914
947
|
method: "POST",
|
|
915
948
|
headers: { "content-type": "application/json", accept: "application/json" },
|
|
916
|
-
body: JSON.stringify({
|
|
949
|
+
body: JSON.stringify({
|
|
950
|
+
reason: options.reason ?? "manual",
|
|
951
|
+
...options.provider === void 0 || options.provider === "" ? {} : { provider: options.provider }
|
|
952
|
+
})
|
|
917
953
|
});
|
|
918
954
|
return await readJson(response, REFRESH_PATH);
|
|
919
955
|
}
|
|
@@ -1283,12 +1319,6 @@ function dotStateOf(severity) {
|
|
|
1283
1319
|
return "idle";
|
|
1284
1320
|
}
|
|
1285
1321
|
}
|
|
1286
|
-
function ringSpecOf(severity) {
|
|
1287
|
-
return {
|
|
1288
|
-
state: dotStateOf(severity),
|
|
1289
|
-
marker: severity === "unavailable" ? "cross" : null
|
|
1290
|
-
};
|
|
1291
|
-
}
|
|
1292
1322
|
var DECIMAL_SCALE = 100000000n;
|
|
1293
1323
|
var ARC_STEPS = 10000n;
|
|
1294
1324
|
var DECIMAL_PATTERN = /^(-?)(\d+)(?:\.(\d*))?$/;
|
|
@@ -1369,6 +1399,11 @@ function currentAgeMs(seenAt, seenAgeMs, now) {
|
|
|
1369
1399
|
return Math.max(0, base + (now - seenAt));
|
|
1370
1400
|
}
|
|
1371
1401
|
var MS_PER_SECOND = 1e3;
|
|
1402
|
+
function sourceLabelKeyOf(source) {
|
|
1403
|
+
if (source === "deepseek-http") return "popover.source.key";
|
|
1404
|
+
if (source === "deepseek-account") return "popover.source.account";
|
|
1405
|
+
return null;
|
|
1406
|
+
}
|
|
1372
1407
|
function cooldownUntilOf(now, configuredSeconds, rejectedRemainingMs) {
|
|
1373
1408
|
const span = rejectedRemainingMs === null ? Math.max(0, configuredSeconds) * MS_PER_SECOND : Math.max(0, rejectedRemainingMs);
|
|
1374
1409
|
return now + span;
|
|
@@ -1377,6 +1412,153 @@ function cooldownSecondsOf(until, now) {
|
|
|
1377
1412
|
return Math.max(0, Math.ceil((until - now) / MS_PER_SECOND));
|
|
1378
1413
|
}
|
|
1379
1414
|
|
|
1415
|
+
// src/client/situation.ts
|
|
1416
|
+
var SITUATIONS = [
|
|
1417
|
+
"checking",
|
|
1418
|
+
"offline",
|
|
1419
|
+
"internal-error",
|
|
1420
|
+
"no-credential",
|
|
1421
|
+
"fetch-failed",
|
|
1422
|
+
"account-unavailable",
|
|
1423
|
+
"stale",
|
|
1424
|
+
"empty-wallet",
|
|
1425
|
+
"ok",
|
|
1426
|
+
"low",
|
|
1427
|
+
"critical"
|
|
1428
|
+
];
|
|
1429
|
+
var SHAPES = {
|
|
1430
|
+
// —— 客户端本地事实(宿主不可能知道这两件事)——
|
|
1431
|
+
checking: {
|
|
1432
|
+
family: "pending",
|
|
1433
|
+
ring: "ongoing",
|
|
1434
|
+
marker: null,
|
|
1435
|
+
arc: "none",
|
|
1436
|
+
textKey: "situation.checking",
|
|
1437
|
+
severityColoured: false
|
|
1438
|
+
},
|
|
1439
|
+
offline: {
|
|
1440
|
+
family: "unreadable",
|
|
1441
|
+
ring: "error",
|
|
1442
|
+
marker: "cross",
|
|
1443
|
+
arc: "none",
|
|
1444
|
+
textKey: "situation.unavailable",
|
|
1445
|
+
severityColoured: false
|
|
1446
|
+
},
|
|
1447
|
+
// —— 宿主判定 ——
|
|
1448
|
+
"internal-error": {
|
|
1449
|
+
family: "unreadable",
|
|
1450
|
+
ring: "error",
|
|
1451
|
+
marker: "cross",
|
|
1452
|
+
arc: "none",
|
|
1453
|
+
textKey: "situation.internalError",
|
|
1454
|
+
severityColoured: false
|
|
1455
|
+
},
|
|
1456
|
+
"no-credential": {
|
|
1457
|
+
family: "needs-credential",
|
|
1458
|
+
ring: "idle",
|
|
1459
|
+
marker: "plus",
|
|
1460
|
+
arc: "none",
|
|
1461
|
+
textKey: "situation.noCredential",
|
|
1462
|
+
severityColoured: false
|
|
1463
|
+
},
|
|
1464
|
+
"fetch-failed": {
|
|
1465
|
+
family: "unreadable",
|
|
1466
|
+
ring: "error",
|
|
1467
|
+
marker: "cross",
|
|
1468
|
+
arc: "none",
|
|
1469
|
+
textKey: "situation.unavailable",
|
|
1470
|
+
severityColoured: false
|
|
1471
|
+
},
|
|
1472
|
+
"account-unavailable": {
|
|
1473
|
+
family: "unreadable",
|
|
1474
|
+
// **颜色定死为红,不吃 severity。**
|
|
1475
|
+
// 它是「账户停用」—— 这个事实本身就决定了红,与余额多少无关;
|
|
1476
|
+
// 而上游对欠费账户可能给空的余额列表(selected=null → severity=unknown → 灰环),
|
|
1477
|
+
// 那会把「停用」画成「没信息」。定死红环是**结构保证**,不依赖另一处的判定顺序。
|
|
1478
|
+
ring: "error",
|
|
1479
|
+
// **叉:与「余额告急」分开的唯一办法。**
|
|
1480
|
+
// 从前它靠「红环无叉」与 critical 区分,但余额恰好为 0 的 critical 也是红环无叉无弧 ——
|
|
1481
|
+
// 两者在屏幕上逐像素相同(见 test/render-matrix.test.ts 的「两枚红环」一组)。
|
|
1482
|
+
// 叉的语义从「读不到」扩到「读不到**或用不了**」:两者同属「这里没有可用的数字」。
|
|
1483
|
+
marker: "cross",
|
|
1484
|
+
arc: "none",
|
|
1485
|
+
textKey: "situation.accountUnavailable",
|
|
1486
|
+
severityColoured: false
|
|
1487
|
+
},
|
|
1488
|
+
stale: {
|
|
1489
|
+
family: "gauge",
|
|
1490
|
+
ring: "done",
|
|
1491
|
+
marker: null,
|
|
1492
|
+
arc: "gauge",
|
|
1493
|
+
textKey: "situation.stale",
|
|
1494
|
+
severityColoured: true
|
|
1495
|
+
},
|
|
1496
|
+
"empty-wallet": {
|
|
1497
|
+
family: "empty",
|
|
1498
|
+
ring: "idle",
|
|
1499
|
+
marker: null,
|
|
1500
|
+
arc: "none",
|
|
1501
|
+
textKey: "situation.emptyWallet",
|
|
1502
|
+
severityColoured: false
|
|
1503
|
+
},
|
|
1504
|
+
ok: {
|
|
1505
|
+
family: "gauge",
|
|
1506
|
+
ring: "done",
|
|
1507
|
+
marker: null,
|
|
1508
|
+
arc: "gauge",
|
|
1509
|
+
textKey: null,
|
|
1510
|
+
severityColoured: true
|
|
1511
|
+
},
|
|
1512
|
+
low: {
|
|
1513
|
+
family: "gauge",
|
|
1514
|
+
ring: "warning",
|
|
1515
|
+
marker: null,
|
|
1516
|
+
arc: "gauge",
|
|
1517
|
+
textKey: null,
|
|
1518
|
+
severityColoured: true
|
|
1519
|
+
},
|
|
1520
|
+
critical: {
|
|
1521
|
+
family: "gauge",
|
|
1522
|
+
ring: "error",
|
|
1523
|
+
marker: null,
|
|
1524
|
+
arc: "gauge",
|
|
1525
|
+
textKey: null,
|
|
1526
|
+
severityColoured: true
|
|
1527
|
+
}
|
|
1528
|
+
};
|
|
1529
|
+
function presentationOf(situation, severity) {
|
|
1530
|
+
const shape = SHAPES[situation];
|
|
1531
|
+
if (!shape.severityColoured) return shape;
|
|
1532
|
+
return { ...shape, ring: dotStateOf(severity) };
|
|
1533
|
+
}
|
|
1534
|
+
function hoverTextOf(shown, stateText) {
|
|
1535
|
+
if (stateText !== null) return stateText;
|
|
1536
|
+
return shown === null ? null : formatMoney(shown.total, shown.currency);
|
|
1537
|
+
}
|
|
1538
|
+
function isSituation(value) {
|
|
1539
|
+
return typeof value === "string" && SITUATIONS.includes(value);
|
|
1540
|
+
}
|
|
1541
|
+
function legacySituationOf(response) {
|
|
1542
|
+
const hasSnapshot = response.fetchedAt > 0 || response.selected !== null;
|
|
1543
|
+
const code = response.error?.code;
|
|
1544
|
+
if (!hasSnapshot) {
|
|
1545
|
+
if (code === "NO_KEY") return "no-credential";
|
|
1546
|
+
if (code === "PLUGIN_UNREACHABLE") return "offline";
|
|
1547
|
+
return response.state === "empty" && code === void 0 ? "checking" : "fetch-failed";
|
|
1548
|
+
}
|
|
1549
|
+
if (!response.isAvailable) return "account-unavailable";
|
|
1550
|
+
if (response.selected === null)
|
|
1551
|
+
return response.state === "stale" ? "fetch-failed" : "empty-wallet";
|
|
1552
|
+
if (response.state === "stale") return "stale";
|
|
1553
|
+
if (response.severity === "critical") return "critical";
|
|
1554
|
+
if (response.severity === "warn") return "low";
|
|
1555
|
+
if (response.severity === "unavailable" || response.severity === "unknown") return "fetch-failed";
|
|
1556
|
+
return "ok";
|
|
1557
|
+
}
|
|
1558
|
+
function situationOfResponse(response) {
|
|
1559
|
+
return isSituation(response.situation) ? response.situation : legacySituationOf(response);
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1380
1562
|
// src/client/mock/scenarios.ts
|
|
1381
1563
|
var T0 = 176e10;
|
|
1382
1564
|
var cny = (total, granted, toppedUp) => ({
|
|
@@ -1389,6 +1571,8 @@ function make(patch) {
|
|
|
1389
1571
|
return {
|
|
1390
1572
|
requestId: "req_mock",
|
|
1391
1573
|
schemaVersion: 1,
|
|
1574
|
+
source: "deepseek-http",
|
|
1575
|
+
situation: "ok",
|
|
1392
1576
|
state: "ok",
|
|
1393
1577
|
stale: false,
|
|
1394
1578
|
fetchedAt: T0,
|
|
@@ -1407,27 +1591,33 @@ function make(patch) {
|
|
|
1407
1591
|
var scenarios = {
|
|
1408
1592
|
/** 正常。 */
|
|
1409
1593
|
ok: make({}),
|
|
1594
|
+
/** 正常,但数字来自账号登录那条路(用来眼看「来源标签」)。 */
|
|
1595
|
+
account: make({ source: "deepseek-account" }),
|
|
1410
1596
|
/** 余额偏低但可用。 */
|
|
1411
1597
|
warn: make({
|
|
1598
|
+
situation: "low",
|
|
1412
1599
|
severity: "warn",
|
|
1413
1600
|
balances: [cny("8.00000000", "0.00000000", "8.00000000")],
|
|
1414
1601
|
selected: { currency: "CNY", total: "8.00000000" }
|
|
1415
1602
|
}),
|
|
1416
1603
|
/** 余额告急。 */
|
|
1417
1604
|
critical: make({
|
|
1605
|
+
situation: "critical",
|
|
1418
1606
|
severity: "critical",
|
|
1419
1607
|
balances: [cny("3.00000000", "0.00000000", "3.00000000")],
|
|
1420
1608
|
selected: { currency: "CNY", total: "3.00000000" }
|
|
1421
1609
|
}),
|
|
1422
|
-
/** 账户不可用(余额耗尽 /
|
|
1610
|
+
/** 账户不可用(余额耗尽 / 欠费)。读到了,但上游说这个账户停用。 */
|
|
1423
1611
|
unavailable: make({
|
|
1612
|
+
situation: "account-unavailable",
|
|
1424
1613
|
severity: "unavailable",
|
|
1425
1614
|
isAvailable: false,
|
|
1426
1615
|
balances: [cny("0.00000000", "0.00000000", "0.00000000")],
|
|
1427
1616
|
selected: { currency: "CNY", total: "0.00000000" }
|
|
1428
1617
|
}),
|
|
1429
|
-
/**
|
|
1618
|
+
/** 有旧值但本次刷新失败:数字照常显示,文案说它旧。 */
|
|
1430
1619
|
stale: make({
|
|
1620
|
+
situation: "stale",
|
|
1431
1621
|
state: "stale",
|
|
1432
1622
|
stale: true,
|
|
1433
1623
|
severity: "unknown",
|
|
@@ -1435,8 +1625,9 @@ var scenarios = {
|
|
|
1435
1625
|
fetchedAt: T0 - 36e5,
|
|
1436
1626
|
error: { code: "NO_NETWORK", message: "fetch failed" }
|
|
1437
1627
|
}),
|
|
1438
|
-
/**
|
|
1628
|
+
/** 无值失败:接入了却读不到(没有旧快照)。环是叉。 */
|
|
1439
1629
|
error: make({
|
|
1630
|
+
situation: "fetch-failed",
|
|
1440
1631
|
state: "error",
|
|
1441
1632
|
stale: false,
|
|
1442
1633
|
severity: "unknown",
|
|
@@ -1446,8 +1637,33 @@ var scenarios = {
|
|
|
1446
1637
|
ageMs: 0,
|
|
1447
1638
|
error: { code: "UPSTREAM_5XX", message: "upstream returned 500" }
|
|
1448
1639
|
}),
|
|
1449
|
-
/**
|
|
1640
|
+
/** 插件自己的端点不可达(旧契约里没有这个信号,靠客户端本地合成)。 */
|
|
1641
|
+
offline: make({
|
|
1642
|
+
situation: "offline",
|
|
1643
|
+
state: "error",
|
|
1644
|
+
stale: false,
|
|
1645
|
+
severity: "unknown",
|
|
1646
|
+
isAvailable: false,
|
|
1647
|
+
balances: [],
|
|
1648
|
+
selected: null,
|
|
1649
|
+
ageMs: 0,
|
|
1650
|
+
error: { code: UNREACHABLE_CODE, message: "balance endpoint responded 502" }
|
|
1651
|
+
}),
|
|
1652
|
+
/** 插件自己出错了(装配错误之类):与「上游坏了」分开说。 */
|
|
1653
|
+
internalError: make({
|
|
1654
|
+
situation: "internal-error",
|
|
1655
|
+
state: "error",
|
|
1656
|
+
stale: false,
|
|
1657
|
+
severity: "unknown",
|
|
1658
|
+
isAvailable: false,
|
|
1659
|
+
balances: [],
|
|
1660
|
+
selected: null,
|
|
1661
|
+
ageMs: 0,
|
|
1662
|
+
error: { code: "STORAGE_ERROR", message: "no ledger for source deepseek-account" }
|
|
1663
|
+
}),
|
|
1664
|
+
/** 未配置:尚无任何解释。 */
|
|
1450
1665
|
empty: make({
|
|
1666
|
+
situation: "empty-wallet",
|
|
1451
1667
|
state: "empty",
|
|
1452
1668
|
severity: "unknown",
|
|
1453
1669
|
isAvailable: false,
|
|
@@ -1455,8 +1671,9 @@ var scenarios = {
|
|
|
1455
1671
|
selected: null,
|
|
1456
1672
|
ageMs: 0
|
|
1457
1673
|
}),
|
|
1458
|
-
/** 未配置且带 NO_KEY
|
|
1674
|
+
/** 未配置且带 NO_KEY 错误 —— 需要用户去配置的那一族。 */
|
|
1459
1675
|
noKey: make({
|
|
1676
|
+
situation: "no-credential",
|
|
1460
1677
|
state: "empty",
|
|
1461
1678
|
severity: "unknown",
|
|
1462
1679
|
isAvailable: false,
|
|
@@ -1465,6 +1682,16 @@ var scenarios = {
|
|
|
1465
1682
|
ageMs: 0,
|
|
1466
1683
|
error: { code: "NO_KEY", message: "no API key configured" }
|
|
1467
1684
|
}),
|
|
1685
|
+
/** 首帧:还没问到后端。 */
|
|
1686
|
+
checking: make({
|
|
1687
|
+
situation: "checking",
|
|
1688
|
+
state: "empty",
|
|
1689
|
+
severity: "unknown",
|
|
1690
|
+
isAvailable: false,
|
|
1691
|
+
balances: [],
|
|
1692
|
+
selected: null,
|
|
1693
|
+
ageMs: 0
|
|
1694
|
+
}),
|
|
1468
1695
|
/** 多币种。`selected` 显式写出,不靠 `make()` 的默认值兜。 */
|
|
1469
1696
|
multiCurrency: make({
|
|
1470
1697
|
balances: [
|
|
@@ -1489,6 +1716,7 @@ var scenarios = {
|
|
|
1489
1716
|
}),
|
|
1490
1717
|
/** 账户完全没有余额。 */
|
|
1491
1718
|
noBalanceAtAll: make({
|
|
1719
|
+
situation: "empty-wallet",
|
|
1492
1720
|
severity: "unknown",
|
|
1493
1721
|
isAvailable: true,
|
|
1494
1722
|
balances: [],
|
|
@@ -1626,7 +1854,9 @@ var import_dsh_client_ui_primitives3 = require("@deepseek-ai/dsh-client-ui-primi
|
|
|
1626
1854
|
var BalancePopover_default = {
|
|
1627
1855
|
panel: "BalancePopover_panel",
|
|
1628
1856
|
title: "BalancePopover_title",
|
|
1857
|
+
titleMain: "BalancePopover_titleMain",
|
|
1629
1858
|
titleLabel: "BalancePopover_titleLabel",
|
|
1859
|
+
sourceLabel: "BalancePopover_sourceLabel",
|
|
1630
1860
|
iconButton: "BalancePopover_iconButton",
|
|
1631
1861
|
titleRule: "BalancePopover_titleRule",
|
|
1632
1862
|
details: "BalancePopover_details",
|
|
@@ -1669,6 +1899,7 @@ function BalancePopover(props) {
|
|
|
1669
1899
|
selection,
|
|
1670
1900
|
displayCurrency,
|
|
1671
1901
|
ageMs,
|
|
1902
|
+
sourceLabel,
|
|
1672
1903
|
refreshing,
|
|
1673
1904
|
cooldownSeconds,
|
|
1674
1905
|
configSlotWarning,
|
|
@@ -1706,19 +1937,22 @@ function BalancePopover(props) {
|
|
|
1706
1937
|
"aria-label": t("popover.title"),
|
|
1707
1938
|
children: [
|
|
1708
1939
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: BalancePopover_default.title, children: [
|
|
1709
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1940
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: BalancePopover_default.titleMain, children: [
|
|
1941
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1942
|
+
"a",
|
|
1943
|
+
{
|
|
1944
|
+
className: BalancePopover_default.titleLabel,
|
|
1945
|
+
href: "https://platform.deepseek.com/usage",
|
|
1946
|
+
target: "_blank",
|
|
1947
|
+
rel: "noopener noreferrer",
|
|
1948
|
+
children: [
|
|
1949
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_dsh_client_ui_primitives3.FishLogo, { size: 14 }),
|
|
1950
|
+
t("popover.title")
|
|
1951
|
+
]
|
|
1952
|
+
}
|
|
1953
|
+
),
|
|
1954
|
+
sourceLabel === null ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: BalancePopover_default.sourceLabel, children: sourceLabel })
|
|
1955
|
+
] }),
|
|
1722
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)(
|
|
1723
1957
|
"button",
|
|
1724
1958
|
{
|
|
@@ -1780,7 +2014,9 @@ var PercentRing_default = {
|
|
|
1780
2014
|
ring: "PercentRing_ring",
|
|
1781
2015
|
track: "PercentRing_track",
|
|
1782
2016
|
fill: "PercentRing_fill",
|
|
1783
|
-
|
|
2017
|
+
marker: "PercentRing_marker",
|
|
2018
|
+
spin: "PercentRing_spin",
|
|
2019
|
+
"dsb-ring-spin": "PercentRing_dsb-ring-spin"
|
|
1784
2020
|
};
|
|
1785
2021
|
|
|
1786
2022
|
// src/client/sidebar/PercentRing.tsx
|
|
@@ -1791,10 +2027,13 @@ var RING_INSET = 1;
|
|
|
1791
2027
|
var RADIUS = VIEW / 2 - RING_INSET - STROKE / 2;
|
|
1792
2028
|
var CENTER = VIEW / 2;
|
|
1793
2029
|
var CIRCUMFERENCE = 2 * Math.PI * RADIUS;
|
|
1794
|
-
var
|
|
2030
|
+
var INNER_RADIUS = RADIUS - STROKE / 2;
|
|
2031
|
+
var MARK_ARM = INNER_RADIUS / 2 / Math.SQRT2;
|
|
1795
2032
|
function round3(value) {
|
|
1796
2033
|
return Math.round(value * 1e3) / 1e3;
|
|
1797
2034
|
}
|
|
2035
|
+
var SPIN_ARC_RATIO = 12 / (2 * Math.PI * 9.5);
|
|
2036
|
+
var SPIN_ARC = round3(CIRCUMFERENCE * SPIN_ARC_RATIO);
|
|
1798
2037
|
function PercentRing({
|
|
1799
2038
|
state,
|
|
1800
2039
|
marker = null,
|
|
@@ -1816,37 +2055,55 @@ function PercentRing({
|
|
|
1816
2055
|
children: [
|
|
1817
2056
|
title === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("title", { children: title }),
|
|
1818
2057
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { className: PercentRing_default.track, cx: CENTER, cy: CENTER, r: RADIUS }),
|
|
1819
|
-
|
|
2058
|
+
state === "ongoing" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("g", { className: PercentRing_default.spin, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1820
2059
|
"circle",
|
|
1821
2060
|
{
|
|
1822
2061
|
className: PercentRing_default.fill,
|
|
1823
2062
|
cx: CENTER,
|
|
1824
2063
|
cy: CENTER,
|
|
1825
2064
|
r: RADIUS,
|
|
1826
|
-
strokeDasharray: `${
|
|
2065
|
+
strokeDasharray: `${SPIN_ARC} ${round3(CIRCUMFERENCE)}`,
|
|
1827
2066
|
transform: `rotate(-90 ${CENTER} ${CENTER})`
|
|
1828
2067
|
}
|
|
2068
|
+
) }) : (
|
|
2069
|
+
/* dash 取弧长、gap 取整周长,于是只画出一条弧;rotate 让接缝落在 12 点而不是 3 点
|
|
2070
|
+
(中心跟着 VIEW 走,16 格时是 rotate(-90 8 8))。
|
|
2071
|
+
比例为 0 时整条弧不画:dash 长度为 0 配上圆头线帽会在 12 点留下一个点。 */
|
|
2072
|
+
clamped === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2073
|
+
"circle",
|
|
2074
|
+
{
|
|
2075
|
+
className: PercentRing_default.fill,
|
|
2076
|
+
cx: CENTER,
|
|
2077
|
+
cy: CENTER,
|
|
2078
|
+
r: RADIUS,
|
|
2079
|
+
strokeDasharray: `${round3(CIRCUMFERENCE * clamped)} ${round3(CIRCUMFERENCE)}`,
|
|
2080
|
+
transform: `rotate(-90 ${CENTER} ${CENTER})`
|
|
2081
|
+
}
|
|
2082
|
+
)
|
|
1829
2083
|
),
|
|
1830
|
-
marker ===
|
|
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: [
|
|
1831
2085
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1832
2086
|
"line",
|
|
1833
2087
|
{
|
|
1834
|
-
x1: CENTER -
|
|
1835
|
-
y1: CENTER -
|
|
1836
|
-
x2: CENTER +
|
|
1837
|
-
y2: CENTER +
|
|
2088
|
+
x1: CENTER - MARK_ARM,
|
|
2089
|
+
y1: CENTER - MARK_ARM,
|
|
2090
|
+
x2: CENTER + MARK_ARM,
|
|
2091
|
+
y2: CENTER + MARK_ARM
|
|
1838
2092
|
}
|
|
1839
2093
|
),
|
|
1840
2094
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1841
2095
|
"line",
|
|
1842
2096
|
{
|
|
1843
|
-
x1: CENTER +
|
|
1844
|
-
y1: CENTER -
|
|
1845
|
-
x2: CENTER -
|
|
1846
|
-
y2: CENTER +
|
|
2097
|
+
x1: CENTER + MARK_ARM,
|
|
2098
|
+
y1: CENTER - MARK_ARM,
|
|
2099
|
+
x2: CENTER - MARK_ARM,
|
|
2100
|
+
y2: CENTER + MARK_ARM
|
|
1847
2101
|
}
|
|
1848
2102
|
)
|
|
1849
|
-
] }) :
|
|
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 })
|
|
2106
|
+
] }) })
|
|
1850
2107
|
]
|
|
1851
2108
|
}
|
|
1852
2109
|
);
|
|
@@ -1893,19 +2150,22 @@ function usePluginsNavigation(navigation) {
|
|
|
1893
2150
|
}, [navigation]);
|
|
1894
2151
|
return action;
|
|
1895
2152
|
}
|
|
1896
|
-
function
|
|
1897
|
-
|
|
1898
|
-
return response.error?.code === "NO_KEY" ? "state.noKey" : "state.error";
|
|
1899
|
-
if (response.state === "empty")
|
|
1900
|
-
return response.error?.code === "NO_KEY" ? "state.noKey" : "state.empty";
|
|
1901
|
-
if (response.state === "stale") return "state.stale";
|
|
1902
|
-
if (!response.isAvailable) return "state.unavailable";
|
|
1903
|
-
if (selection.shown === null) return "state.noBalance";
|
|
1904
|
-
return null;
|
|
2153
|
+
function ringSpecFor(presentation) {
|
|
2154
|
+
return { state: presentation.ring, marker: presentation.marker };
|
|
1905
2155
|
}
|
|
1906
|
-
function
|
|
1907
|
-
const
|
|
1908
|
-
|
|
2156
|
+
function useRouteProvider(hint) {
|
|
2157
|
+
const [provider, setProvider] = (0, import_react5.useState)(() => hint?.getSnapshot());
|
|
2158
|
+
(0, import_react5.useEffect)(() => {
|
|
2159
|
+
if (hint === void 0) {
|
|
2160
|
+
setProvider(void 0);
|
|
2161
|
+
return;
|
|
2162
|
+
}
|
|
2163
|
+
setProvider(hint.getSnapshot());
|
|
2164
|
+
return hint.subscribe(() => {
|
|
2165
|
+
setProvider(hint.getSnapshot());
|
|
2166
|
+
});
|
|
2167
|
+
}, [hint]);
|
|
2168
|
+
return provider;
|
|
1909
2169
|
}
|
|
1910
2170
|
function SidebarBalance({
|
|
1911
2171
|
wide,
|
|
@@ -1913,7 +2173,8 @@ function SidebarBalance({
|
|
|
1913
2173
|
config,
|
|
1914
2174
|
configSlotProbe,
|
|
1915
2175
|
onSelectCurrency,
|
|
1916
|
-
pluginsNavigation
|
|
2176
|
+
pluginsNavigation,
|
|
2177
|
+
routeHint
|
|
1917
2178
|
}) {
|
|
1918
2179
|
const [view, setView] = (0, import_react5.useState)(() => {
|
|
1919
2180
|
const response2 = resolveScenario() === null ? pendingView() : currentBalance();
|
|
@@ -1926,6 +2187,7 @@ function SidebarBalance({
|
|
|
1926
2187
|
const [refreshing, setRefreshing] = (0, import_react5.useState)(false);
|
|
1927
2188
|
const [cooldownUntil, setCooldownUntil] = (0, import_react5.useState)(0);
|
|
1928
2189
|
const [now, setNow] = (0, import_react5.useState)(() => Date.now());
|
|
2190
|
+
const provider = useRouteProvider(routeHint);
|
|
1929
2191
|
const rootRef = (0, import_react5.useRef)(null);
|
|
1930
2192
|
const panelRef = (0, import_react5.useRef)(null);
|
|
1931
2193
|
const refreshTimer = (0, import_react5.useRef)(void 0);
|
|
@@ -1960,7 +2222,7 @@ function SidebarBalance({
|
|
|
1960
2222
|
let cancelled = false;
|
|
1961
2223
|
const load = async () => {
|
|
1962
2224
|
try {
|
|
1963
|
-
const next = await requestBalance({ currency: preference });
|
|
2225
|
+
const next = await requestBalance({ currency: preference, provider });
|
|
1964
2226
|
if (cancelled) return;
|
|
1965
2227
|
loadedRef.current = true;
|
|
1966
2228
|
acceptBalance(next);
|
|
@@ -1983,6 +2245,7 @@ function SidebarBalance({
|
|
|
1983
2245
|
}, [
|
|
1984
2246
|
mock,
|
|
1985
2247
|
preference,
|
|
2248
|
+
provider,
|
|
1986
2249
|
config.clientPollSeconds,
|
|
1987
2250
|
config.configSignature,
|
|
1988
2251
|
acceptBalance,
|
|
@@ -2066,9 +2329,9 @@ function SidebarBalance({
|
|
|
2066
2329
|
void (async () => {
|
|
2067
2330
|
let rejectedRemainingMs = null;
|
|
2068
2331
|
try {
|
|
2069
|
-
const result = await requestRefresh({ reason: "manual" });
|
|
2332
|
+
const result = await requestRefresh({ reason: "manual", provider });
|
|
2070
2333
|
if (!result.triggered) rejectedRemainingMs = Math.max(0, result.cooldownMs);
|
|
2071
|
-
const next = await requestBalance({ currency: preference });
|
|
2334
|
+
const next = await requestBalance({ currency: preference, provider });
|
|
2072
2335
|
loadedRef.current = true;
|
|
2073
2336
|
acceptBalance(next);
|
|
2074
2337
|
} catch (error) {
|
|
@@ -2082,6 +2345,7 @@ function SidebarBalance({
|
|
|
2082
2345
|
config.manualRefreshCooldownSeconds,
|
|
2083
2346
|
mock,
|
|
2084
2347
|
preference,
|
|
2348
|
+
provider,
|
|
2085
2349
|
refreshing,
|
|
2086
2350
|
acceptBalance,
|
|
2087
2351
|
showUnreachable,
|
|
@@ -2105,18 +2369,20 @@ function SidebarBalance({
|
|
|
2105
2369
|
);
|
|
2106
2370
|
const configSlotState = useConfigSlotState(configSlotProbe);
|
|
2107
2371
|
const shown = selection.shown;
|
|
2108
|
-
const
|
|
2109
|
-
const
|
|
2372
|
+
const situation = situationOfResponse(response);
|
|
2373
|
+
const presentation = presentationOf(situation, response.severity);
|
|
2374
|
+
const ring = ringSpecFor(presentation);
|
|
2375
|
+
const ringRatio = presentation.arc === "gauge" ? ringRatioOf(
|
|
2110
2376
|
shown === null ? null : shown.total,
|
|
2111
2377
|
shown === null ? void 0 : config.warnThresholdOf(shown.currency),
|
|
2112
2378
|
response.severity
|
|
2113
|
-
);
|
|
2114
|
-
const stateKey =
|
|
2379
|
+
) : 0;
|
|
2380
|
+
const stateKey = presentation.textKey;
|
|
2115
2381
|
const stateText = stateKey === null ? null : t(stateKey);
|
|
2116
2382
|
let markerLabel = null;
|
|
2117
2383
|
let markerHint = "";
|
|
2118
2384
|
if (shown === null) {
|
|
2119
|
-
markerLabel = t(stateKey ?? "
|
|
2385
|
+
markerLabel = t(stateKey ?? "situation.emptyWallet");
|
|
2120
2386
|
markerHint = markerLabel;
|
|
2121
2387
|
} else if (!selection.matchesPreference) {
|
|
2122
2388
|
markerLabel = t("sidebar.aria.mismatch");
|
|
@@ -2128,9 +2394,10 @@ function SidebarBalance({
|
|
|
2128
2394
|
if (markerLabel !== null) ariaParts.push(markerLabel);
|
|
2129
2395
|
const ariaLabel = ariaParts.join(" ");
|
|
2130
2396
|
const cooldownSeconds = cooldownSecondsOf(cooldownUntil, now);
|
|
2397
|
+
const sourceKey = shown === null ? null : sourceLabelKeyOf(response.source);
|
|
2398
|
+
const sourceLabel = sourceKey === null ? null : t(sourceKey);
|
|
2131
2399
|
const ageMs = currentAgeMs(view.seenAt, view.seenAgeMs, now);
|
|
2132
|
-
const
|
|
2133
|
-
const hoverLabel = shown === null ? stateText : formatMoney(shown.total, shown.currency);
|
|
2400
|
+
const hoverText = hoverTextOf(shown, stateText);
|
|
2134
2401
|
const trigger = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2135
2402
|
"button",
|
|
2136
2403
|
{
|
|
@@ -2150,13 +2417,13 @@ function SidebarBalance({
|
|
|
2150
2417
|
marker: ring.marker,
|
|
2151
2418
|
ratio: ringRatio,
|
|
2152
2419
|
size: 18,
|
|
2153
|
-
title:
|
|
2420
|
+
title: hoverText ?? void 0
|
|
2154
2421
|
}
|
|
2155
2422
|
)
|
|
2156
2423
|
}
|
|
2157
2424
|
);
|
|
2158
2425
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { ref: rootRef, className: SidebarBalance_default.root, "data-mode": wide ? "wide" : "rail", tabIndex: -1, children: [
|
|
2159
|
-
wide &&
|
|
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,
|
|
2160
2427
|
open ? (0, import_react_dom.createPortal)(
|
|
2161
2428
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2162
2429
|
BalancePopover,
|
|
@@ -2165,6 +2432,7 @@ function SidebarBalance({
|
|
|
2165
2432
|
selection,
|
|
2166
2433
|
displayCurrency: preference,
|
|
2167
2434
|
ageMs,
|
|
2435
|
+
sourceLabel,
|
|
2168
2436
|
refreshing,
|
|
2169
2437
|
cooldownSeconds,
|
|
2170
2438
|
configSlotWarning: configSlotState === "missing" ? CONFIG_SLOT_WARNING : null,
|
|
@@ -2181,6 +2449,66 @@ function SidebarBalance({
|
|
|
2181
2449
|
] });
|
|
2182
2450
|
}
|
|
2183
2451
|
|
|
2452
|
+
// src/client/route-hint.ts
|
|
2453
|
+
function safely(read) {
|
|
2454
|
+
try {
|
|
2455
|
+
return read();
|
|
2456
|
+
} catch {
|
|
2457
|
+
return void 0;
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
function projectionOf(ctx) {
|
|
2461
|
+
return safely(() => {
|
|
2462
|
+
const current = ctx.uiSession?.adapter?.current?.getSnapshot?.();
|
|
2463
|
+
const key = current?.key;
|
|
2464
|
+
if (key === void 0) return void 0;
|
|
2465
|
+
return ctx.sessions?.binding?.(key)?.session?.projections?.faceOf?.("modelSelection");
|
|
2466
|
+
});
|
|
2467
|
+
}
|
|
2468
|
+
function createRouteHint(services) {
|
|
2469
|
+
const listeners2 = /* @__PURE__ */ new Set();
|
|
2470
|
+
let stopCurrent;
|
|
2471
|
+
let stopProjection;
|
|
2472
|
+
const notify = () => {
|
|
2473
|
+
for (const listener of [...listeners2]) listener();
|
|
2474
|
+
};
|
|
2475
|
+
const bindProjection = () => {
|
|
2476
|
+
stopProjection?.();
|
|
2477
|
+
stopProjection = safely(() => projectionOf(services())?.subscribe?.(notify));
|
|
2478
|
+
};
|
|
2479
|
+
const start = () => {
|
|
2480
|
+
stopCurrent = safely(
|
|
2481
|
+
() => services().uiSession?.adapter?.current?.subscribe?.(() => {
|
|
2482
|
+
bindProjection();
|
|
2483
|
+
notify();
|
|
2484
|
+
})
|
|
2485
|
+
);
|
|
2486
|
+
bindProjection();
|
|
2487
|
+
};
|
|
2488
|
+
const stop = () => {
|
|
2489
|
+
stopCurrent?.();
|
|
2490
|
+
stopProjection?.();
|
|
2491
|
+
stopCurrent = void 0;
|
|
2492
|
+
stopProjection = void 0;
|
|
2493
|
+
};
|
|
2494
|
+
return {
|
|
2495
|
+
getSnapshot() {
|
|
2496
|
+
const value = safely(
|
|
2497
|
+
() => projectionOf(services())?.getSnapshot?.()?.next?.provider
|
|
2498
|
+
);
|
|
2499
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
2500
|
+
},
|
|
2501
|
+
subscribe(listener) {
|
|
2502
|
+
listeners2.add(listener);
|
|
2503
|
+
if (listeners2.size === 1) start();
|
|
2504
|
+
return () => {
|
|
2505
|
+
listeners2.delete(listener);
|
|
2506
|
+
if (listeners2.size === 0) stop();
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
};
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2184
2512
|
// src/client/index.tsx
|
|
2185
2513
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
2186
2514
|
var ENTRY_ID = "dsh-ds-balance";
|
|
@@ -2250,6 +2578,7 @@ function SidebarSeatComponent(props) {
|
|
|
2250
2578
|
t: translateOf(props.seat.t),
|
|
2251
2579
|
configSlotProbe: props.configSlotProbe,
|
|
2252
2580
|
pluginsNavigation: props.pluginsNavigation,
|
|
2581
|
+
routeHint: props.routeHint,
|
|
2253
2582
|
onSelectCurrency: selectCurrency,
|
|
2254
2583
|
config: {
|
|
2255
2584
|
displayCurrency: readDisplayCurrency(value),
|
|
@@ -2329,6 +2658,18 @@ function apply(ctx) {
|
|
|
2329
2658
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "ds-balance: dictionaries");
|
|
2330
2659
|
const configSlotProbe = createConfigSlotProbe();
|
|
2331
2660
|
ctx.effect(() => () => configSlotProbe.dispose(), "ds-balance: config slot probe");
|
|
2661
|
+
const routeServices = {};
|
|
2662
|
+
for (const name of ["uiSession", "sessions"]) {
|
|
2663
|
+
ctx.inject([name], (serviceCtx) => {
|
|
2664
|
+
serviceCtx.effect(() => {
|
|
2665
|
+
routeServices[name] = serviceCtx[name];
|
|
2666
|
+
return () => {
|
|
2667
|
+
delete routeServices[name];
|
|
2668
|
+
};
|
|
2669
|
+
}, `ds-balance: route hint (${name})`);
|
|
2670
|
+
});
|
|
2671
|
+
}
|
|
2672
|
+
const routeHint = createRouteHint(() => routeServices);
|
|
2332
2673
|
const pluginsNavigation = createPluginsNavigation();
|
|
2333
2674
|
ctx.inject(["pluginNavigation"], (navCtx) => {
|
|
2334
2675
|
const face = navCtx["pluginNavigation"];
|
|
@@ -2374,14 +2715,15 @@ function apply(ctx) {
|
|
|
2374
2715
|
seat,
|
|
2375
2716
|
form,
|
|
2376
2717
|
configSlotProbe,
|
|
2377
|
-
pluginsNavigation
|
|
2718
|
+
pluginsNavigation,
|
|
2719
|
+
routeHint
|
|
2378
2720
|
}
|
|
2379
2721
|
)
|
|
2380
2722
|
)
|
|
2381
2723
|
);
|
|
2382
2724
|
});
|
|
2383
2725
|
}
|
|
2384
|
-
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: 999px;\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: 18px;\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: 999px;\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: 12px;\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 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_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_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 border: none;\n border-radius: 50%;\n corner-shape: round;\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: 2px solid var(--dsw-alias-brand-primary);\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: 999px;\n corner-shape: round;\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: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\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_cross {\n fill: none;\n stroke: currentColor;\n stroke-width: 1;\n stroke-linecap: round;\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: 2px solid var(--dsw-alias-brand-primary);\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: 12px;\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: 12px;\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";
|
|
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";
|
|
2385
2727
|
var __dsbTagId = "dsh-ds-balance/client.css";
|
|
2386
2728
|
if (typeof document !== 'undefined' && document.querySelector('style[data-plugin-css="' + __dsbTagId + '"]') === null) {
|
|
2387
2729
|
var __dsbTag = document.createElement('style');
|