dsh-subscriptions 0.2.0 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +220 -180
- package/lib/auth/claude-code-creds.js +31 -9
- package/lib/auth/device-flow.js +16 -9
- package/lib/auth/oauth-flow.js +13 -10
- package/lib/auth/rpc.d.ts +8 -4
- package/lib/auth/rpc.js +89 -25
- package/lib/auth/store.js +80 -31
- package/lib/client/CursorCard.d.ts +1 -1
- package/lib/client/CursorCard.js +50 -16
- package/lib/client/ExternalUsageCards.d.ts +1 -1
- package/lib/client/ExternalUsageCards.js +37 -16
- package/lib/client/ImageGallery.d.ts +2 -2
- package/lib/client/ImageGallery.js +92 -33
- package/lib/client/ImageGenerateToolview.js +50 -14
- package/lib/client/OllamaCard.d.ts +1 -1
- package/lib/client/OllamaCard.js +26 -8
- package/lib/client/OllamaSettingsManager.js +113 -30
- package/lib/client/ProviderAccountManager.d.ts +1 -1
- package/lib/client/ProviderAccountManager.js +164 -49
- package/lib/client/ProviderModelEditor.js +137 -34
- package/lib/client/SpeedSelect.d.ts +62 -14
- package/lib/client/SpeedSelect.js +261 -49
- package/lib/client/SubscriptionUsageBadge.d.ts +4 -10
- package/lib/client/SubscriptionUsageBadge.js +300 -112
- package/lib/client/SubscriptionsSection.d.ts +2 -0
- package/lib/client/SubscriptionsSection.js +265 -85
- package/lib/client/UsageBadgeDisplaySetting.js +35 -12
- package/lib/client/UsageMeter.d.ts +1 -1
- package/lib/client/UsageMeter.js +62 -24
- package/lib/client/VideoGenerateToolview.js +69 -17
- package/lib/client/account-preferences.d.ts +8 -0
- package/lib/client/account-preferences.js +20 -5
- package/lib/client/host-contract.d.ts +11 -2
- package/lib/client/host-contract.js +20 -3
- package/lib/client/index.d.ts +5 -5
- package/lib/client/index.js +34 -12
- package/lib/client/locales.d.ts +22 -0
- package/lib/client/locales.js +73 -51
- package/lib/client/model-filter.d.ts +37 -0
- package/lib/client/model-filter.js +45 -0
- package/lib/client/ollama-draft.js +21 -12
- package/lib/client/reset-action.js +6 -4
- package/lib/client/reset-credits-view.d.ts +1 -1
- package/lib/client/reset-credits-view.js +66 -17
- package/lib/client/subscription-card-styles.js +94 -24
- package/lib/client/usage-color-preferences.js +4 -2
- package/lib/client/usage-pace.js +30 -12
- package/lib/client/usage-window-filter.d.ts +7 -0
- package/lib/client/usage-window-filter.js +12 -0
- package/lib/client.js +480 -136
- package/lib/client.js.map +1 -1
- package/lib/http.js +1 -1
- package/lib/index.d.ts +3 -3
- package/lib/index.js +167 -76
- package/lib/model-defaults.js +4 -2
- package/lib/provider-settings.d.ts +17 -0
- package/lib/provider-settings.js +79 -16
- package/lib/providers/account-preferences.js +51 -22
- package/lib/providers/accounts.js +15 -9
- package/lib/providers/antigravity-oauth-client.js +2 -2
- package/lib/providers/antigravity.js +141 -68
- package/lib/providers/catalog-store.js +120 -41
- package/lib/providers/claude-cli-version.js +3 -3
- package/lib/providers/claude.d.ts +9 -2
- package/lib/providers/claude.js +153 -90
- package/lib/providers/codex-client-version.js +3 -3
- package/lib/providers/codex-search.js +40 -13
- package/lib/providers/codex.js +196 -112
- package/lib/providers/common.js +71 -39
- package/lib/providers/copilot.js +143 -82
- package/lib/providers/cursor-adapter.js +42 -18
- package/lib/providers/cursor-auth.d.ts +1 -1
- package/lib/providers/cursor-auth.js +69 -24
- package/lib/providers/cursor-model-settings.js +14 -5
- package/lib/providers/cursor-usage.js +27 -13
- package/lib/providers/external-usage-controller.js +14 -5
- package/lib/providers/external-usage.js +59 -23
- package/lib/providers/grok.js +152 -89
- package/lib/providers/image-pool.js +29 -11
- package/lib/providers/minimax-usage.js +31 -11
- package/lib/providers/npm-cli-version.js +10 -3
- package/lib/providers/ollama-settings.js +24 -8
- package/lib/providers/ollama-web.js +29 -11
- package/lib/providers/ollama.js +129 -66
- package/lib/providers/pool-family.js +11 -3
- package/lib/providers/pool-health.js +28 -8
- package/lib/providers/pool-usage.js +16 -9
- package/lib/providers/pool.d.ts +8 -0
- package/lib/providers/pool.js +81 -42
- package/lib/providers/rate-limit.js +8 -2
- package/lib/providers/replay.js +40 -17
- package/lib/providers/reset-redemption.js +18 -6
- package/lib/tools/image-generate.js +123 -65
- package/lib/tools/registration.js +5 -2
- package/lib/tools/video-generate.js +51 -31
- package/lib/tools/x-search.js +50 -22
- package/lib/translate/anthropic.js +85 -37
- package/lib/translate/antigravity-schema.js +71 -28
- package/lib/translate/antigravity-thinking.js +22 -9
- package/lib/translate/antigravity.js +151 -52
- package/lib/translate/chat-completions.js +67 -23
- package/lib/translate/resolved.js +55 -21
- package/lib/translate/responses.js +69 -29
- package/lib/translate/sse.js +4 -1
- package/package.json +27 -4
|
@@ -27,13 +27,14 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
27
27
|
import { subscriptionChromeCss } from './provider-settings-styles.js';
|
|
28
28
|
import { createPortal } from 'react-dom';
|
|
29
29
|
import * as primitives from '@deepseek-ai/dsh-client-ui-primitives';
|
|
30
|
-
import { useAnchoredPosition, useDismissOnOutsidePointer } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
30
|
+
import { useAnchoredPosition, useDismissOnOutsidePointer, } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
31
31
|
import { callSubscriptionsAuth } from './SubscriptionsSection.js';
|
|
32
32
|
import { UsageMeter } from './UsageMeter.js';
|
|
33
33
|
import { displayUsedPercent } from './usage-pace.js';
|
|
34
34
|
import { en } from './locales.js';
|
|
35
35
|
import { hostIcon } from './host-icons.js';
|
|
36
|
-
import {
|
|
36
|
+
import { HOST_CONTRACT, markerSelector } from './host-contract.js';
|
|
37
|
+
import { USAGE_BADGE_REFRESH_EVENT, useUsageBadgeMode, } from './usage-badge-preferences.js';
|
|
37
38
|
/** DSH renamed the data icon in 0.1.7; retain older supported hosts too. */
|
|
38
39
|
export function usageBadgeIcon(icons) {
|
|
39
40
|
return hostIcon(icons, 'DataOutline');
|
|
@@ -51,7 +52,7 @@ const PANEL_GAP = 8;
|
|
|
51
52
|
const PANEL_MARGIN = 12;
|
|
52
53
|
/** The account the collapsed pill reads: the default one, else the first listed. */
|
|
53
54
|
export function pillAccountOf(d) {
|
|
54
|
-
return d.accounts.find(a => a.isDefault) ?? d.accounts[0];
|
|
55
|
+
return d.accounts.find((a) => a.isDefault) ?? d.accounts[0];
|
|
55
56
|
}
|
|
56
57
|
const PROVIDER_NAMES = {
|
|
57
58
|
codex: 'Codex',
|
|
@@ -62,7 +63,7 @@ const PROVIDER_NAMES = {
|
|
|
62
63
|
'cursor-subscription': 'Cursor',
|
|
63
64
|
'opencode-go': 'OpenCode Go',
|
|
64
65
|
'kimi-coding': 'Kimi Code',
|
|
65
|
-
|
|
66
|
+
minimax: 'MiniMax',
|
|
66
67
|
'minimax-cn': 'MiniMax CN',
|
|
67
68
|
'ollama-cloud': 'Ollama Cloud',
|
|
68
69
|
};
|
|
@@ -91,13 +92,17 @@ export function groupUsageDisplays(roster, windowsOf, plans) {
|
|
|
91
92
|
const row = {
|
|
92
93
|
key: account.key,
|
|
93
94
|
isDefault: account.isDefault,
|
|
94
|
-
...account.account === undefined ? {} : { account: account.account },
|
|
95
|
-
...plan === undefined ? {} : { plan },
|
|
95
|
+
...(account.account === undefined ? {} : { account: account.account }),
|
|
96
|
+
...(plan === undefined ? {} : { plan }),
|
|
96
97
|
windows,
|
|
97
98
|
};
|
|
98
99
|
const display = byProvider.get(provider);
|
|
99
100
|
if (display === undefined)
|
|
100
|
-
byProvider.set(provider, {
|
|
101
|
+
byProvider.set(provider, {
|
|
102
|
+
provider,
|
|
103
|
+
name: PROVIDER_NAMES[provider],
|
|
104
|
+
accounts: [row],
|
|
105
|
+
});
|
|
101
106
|
else
|
|
102
107
|
display.accounts.push(row);
|
|
103
108
|
}
|
|
@@ -113,7 +118,13 @@ export async function loadBadgeRoster(rpc) {
|
|
|
113
118
|
const roster = [];
|
|
114
119
|
const refreshed = new Set();
|
|
115
120
|
if (subscriptions.status === 'fulfilled') {
|
|
116
|
-
for (const provider of [
|
|
121
|
+
for (const provider of [
|
|
122
|
+
'codex',
|
|
123
|
+
'claude',
|
|
124
|
+
'grok',
|
|
125
|
+
'copilot',
|
|
126
|
+
'antigravity',
|
|
127
|
+
]) {
|
|
117
128
|
refreshed.add(provider);
|
|
118
129
|
for (const account of accountsOf(subscriptions.value.providers[provider]))
|
|
119
130
|
roster.push({ provider, account });
|
|
@@ -123,17 +134,23 @@ export async function loadBadgeRoster(rpc) {
|
|
|
123
134
|
refreshed.add('cursor-subscription');
|
|
124
135
|
if (cursor.value.authenticated)
|
|
125
136
|
roster.push({
|
|
126
|
-
provider: 'cursor-subscription',
|
|
137
|
+
provider: 'cursor-subscription',
|
|
138
|
+
account: { key: 'cursor', isDefault: true },
|
|
127
139
|
});
|
|
128
140
|
}
|
|
129
141
|
if (external.status === 'fulfilled') {
|
|
130
142
|
for (const [source, provider] of [
|
|
131
|
-
['opencode-go', 'opencode-go'],
|
|
143
|
+
['opencode-go', 'opencode-go'],
|
|
144
|
+
['kimi-code', 'kimi-coding'],
|
|
145
|
+
['minimax', 'minimax'],
|
|
146
|
+
['minimax-cn', 'minimax-cn'],
|
|
147
|
+
['ollama-cloud', 'ollama-cloud'],
|
|
132
148
|
]) {
|
|
133
149
|
refreshed.add(provider);
|
|
134
150
|
if (external.value[source]?.configured)
|
|
135
151
|
roster.push({
|
|
136
|
-
provider,
|
|
152
|
+
provider,
|
|
153
|
+
account: { key: source, isDefault: true },
|
|
137
154
|
});
|
|
138
155
|
}
|
|
139
156
|
}
|
|
@@ -142,8 +159,14 @@ export async function loadBadgeRoster(rpc) {
|
|
|
142
159
|
export async function usageOf(rpc, { provider, account }) {
|
|
143
160
|
if (provider === 'cursor-subscription')
|
|
144
161
|
return callSubscriptionsAuth(rpc, 'cursorUsage', {});
|
|
145
|
-
if (provider === 'opencode-go' ||
|
|
146
|
-
|
|
162
|
+
if (provider === 'opencode-go' ||
|
|
163
|
+
provider === 'kimi-coding' ||
|
|
164
|
+
provider === 'minimax' ||
|
|
165
|
+
provider === 'minimax-cn' ||
|
|
166
|
+
provider === 'ollama-cloud') {
|
|
167
|
+
return callSubscriptionsAuth(rpc, 'externalUsage', {
|
|
168
|
+
source: provider === 'kimi-coding' ? 'kimi-code' : provider,
|
|
169
|
+
});
|
|
147
170
|
}
|
|
148
171
|
return callSubscriptionsAuth(rpc, 'usage', { provider, account: account.key });
|
|
149
172
|
}
|
|
@@ -173,9 +196,12 @@ export function windowLabel(w) {
|
|
|
173
196
|
if (w.scope !== undefined && w.scope !== '')
|
|
174
197
|
return w.scope;
|
|
175
198
|
switch (w.kind) {
|
|
176
|
-
case 'session':
|
|
177
|
-
|
|
178
|
-
|
|
199
|
+
case 'session':
|
|
200
|
+
return '5h';
|
|
201
|
+
case 'weekly':
|
|
202
|
+
return 'Wk';
|
|
203
|
+
default:
|
|
204
|
+
return 'W';
|
|
179
205
|
}
|
|
180
206
|
}
|
|
181
207
|
const ms = Math.max(0, w.resetsAt - Date.now());
|
|
@@ -198,10 +224,12 @@ function windowSegment(label, w) {
|
|
|
198
224
|
}
|
|
199
225
|
/** Keep model quotas separate: matching percentages do not imply a shared pool. */
|
|
200
226
|
export function prioritizeWindows(windows, model) {
|
|
201
|
-
return model === undefined
|
|
202
|
-
...windows
|
|
203
|
-
|
|
204
|
-
|
|
227
|
+
return model === undefined
|
|
228
|
+
? [...windows]
|
|
229
|
+
: [
|
|
230
|
+
...windows.filter((w) => w.scope === model),
|
|
231
|
+
...windows.filter((w) => w.scope !== model),
|
|
232
|
+
];
|
|
205
233
|
}
|
|
206
234
|
/** Small previews keep a live model catalog from taking over the dialog. */
|
|
207
235
|
export const WINDOW_PREVIEW_LIMIT = 4;
|
|
@@ -209,7 +237,9 @@ export function previewWindows(windows, model, provider) {
|
|
|
209
237
|
const ordered = prioritizeWindows(windows, model);
|
|
210
238
|
// Antigravity has many model-specific quotas: preview only the current model.
|
|
211
239
|
const limit = provider === 'antigravity'
|
|
212
|
-
? Math.min(2, model === undefined
|
|
240
|
+
? Math.min(2, model === undefined
|
|
241
|
+
? 0
|
|
242
|
+
: windows.filter((w) => w.scope === model).length)
|
|
213
243
|
: WINDOW_PREVIEW_LIMIT;
|
|
214
244
|
return { shown: ordered.slice(0, limit), hidden: ordered.slice(limit) };
|
|
215
245
|
}
|
|
@@ -217,27 +247,34 @@ export function previewWindows(windows, model, provider) {
|
|
|
217
247
|
export function compactSegment(d, model, t = fallbackTranslate) {
|
|
218
248
|
const windows = pillAccountOf(d).windows;
|
|
219
249
|
if (d.provider === 'antigravity') {
|
|
220
|
-
const matching = model === undefined ? [] : windows.filter(w => w.scope === model);
|
|
250
|
+
const matching = model === undefined ? [] : windows.filter((w) => w.scope === model);
|
|
221
251
|
if (matching.length === 0) {
|
|
222
|
-
return `${d.name} ${t(model === undefined
|
|
223
|
-
|
|
252
|
+
return `${d.name} ${t(model === undefined
|
|
253
|
+
? 'usageBadgeModelCount'
|
|
254
|
+
: 'usageBadgeModelUnavailable', {
|
|
255
|
+
count: new Set(windows.map((w) => w.scope).filter(Boolean)).size,
|
|
224
256
|
})}`;
|
|
225
257
|
}
|
|
226
|
-
const parts = matching
|
|
258
|
+
const parts = matching
|
|
259
|
+
.slice(0, 2)
|
|
260
|
+
.map((w) => windowSegment(w.kind === 'weekly' ? t('usageWeekly') : t('usageWindow'), w));
|
|
227
261
|
return `${d.name} ${parts.join(' · ')}`;
|
|
228
262
|
}
|
|
229
|
-
const parts = windows.slice(0, 2).map(w => windowSegment(windowLabel(w), w));
|
|
263
|
+
const parts = windows.slice(0, 2).map((w) => windowSegment(windowLabel(w), w));
|
|
230
264
|
if (windows.length > 2)
|
|
231
265
|
parts.push(`+${windows.length - 2}`);
|
|
232
266
|
return `${d.name} ${parts.join(' · ')}`;
|
|
233
267
|
}
|
|
234
268
|
/** Remember only the most recent subscription in this view, including its model scope. */
|
|
235
269
|
export function retainSubscriptionSelection(previous, current) {
|
|
236
|
-
return current !== undefined &&
|
|
270
|
+
return current !== undefined &&
|
|
271
|
+
Object.hasOwn(PROVIDER_NAMES, current.provider)
|
|
272
|
+
? current
|
|
273
|
+
: previous;
|
|
237
274
|
}
|
|
238
275
|
/** At most one provider; no known selection or no usage means no badge. */
|
|
239
276
|
export function collapsedDisplays(displays, current) {
|
|
240
|
-
const match = displays.find(d => d.provider === current);
|
|
277
|
+
const match = displays.find((d) => d.provider === current);
|
|
241
278
|
return match === undefined ? [] : [match];
|
|
242
279
|
}
|
|
243
280
|
/**
|
|
@@ -255,16 +292,20 @@ export function collapsedDisplays(displays, current) {
|
|
|
255
292
|
export function rotatingDisplay(displays, current, step) {
|
|
256
293
|
if (displays.length === 0)
|
|
257
294
|
return [];
|
|
258
|
-
const match = displays.find(d => d.provider === current);
|
|
295
|
+
const match = displays.find((d) => d.provider === current);
|
|
259
296
|
if (match !== undefined)
|
|
260
297
|
return [match];
|
|
261
298
|
const index = Number.isFinite(step) ? Math.floor(step) : 0;
|
|
262
|
-
return [
|
|
299
|
+
return [
|
|
300
|
+
displays[((index % displays.length) + displays.length) % displays.length],
|
|
301
|
+
];
|
|
263
302
|
}
|
|
264
303
|
/** Order for the expanded dialog: the current provider first, the rest in poll order. */
|
|
265
304
|
export function expandedDisplays(displays, current) {
|
|
266
|
-
const match = displays.find(d => d.provider === current);
|
|
267
|
-
return match === undefined
|
|
305
|
+
const match = displays.find((d) => d.provider === current);
|
|
306
|
+
return match === undefined
|
|
307
|
+
? displays
|
|
308
|
+
: [match, ...displays.filter((d) => d !== match)];
|
|
268
309
|
}
|
|
269
310
|
/**
|
|
270
311
|
* A provider's logged-in accounts, the effective default first. When no
|
|
@@ -274,9 +315,9 @@ export function expandedDisplays(displays, current) {
|
|
|
274
315
|
function accountsOf(status) {
|
|
275
316
|
if (status === undefined || status.accounts.length === 0)
|
|
276
317
|
return [];
|
|
277
|
-
const fallback = status.accounts.find(a => a.isDefault) ?? status.accounts[0];
|
|
318
|
+
const fallback = status.accounts.find((a) => a.isDefault) ?? status.accounts[0];
|
|
278
319
|
return status.accounts
|
|
279
|
-
.map(a => (a === fallback ? { ...a, isDefault: true } : a))
|
|
320
|
+
.map((a) => (a === fallback ? { ...a, isDefault: true } : a))
|
|
280
321
|
.sort((a, b) => Number(b.isDefault) - Number(a.isDefault));
|
|
281
322
|
}
|
|
282
323
|
/** English-dictionary fallback for a missing inject `t` (standalone renders). */
|
|
@@ -287,8 +328,12 @@ function fallbackTranslate(key, params) {
|
|
|
287
328
|
function usageWindowLabel(t, window) {
|
|
288
329
|
const base = window.kind === 'session'
|
|
289
330
|
? t('usageSession')
|
|
290
|
-
: window.kind === 'weekly'
|
|
291
|
-
|
|
331
|
+
: window.kind === 'weekly'
|
|
332
|
+
? t('usageWeekly')
|
|
333
|
+
: t('usageWindow');
|
|
334
|
+
return window.scope !== undefined && window.scope !== ''
|
|
335
|
+
? `${base} · ${window.scope}`
|
|
336
|
+
: base;
|
|
292
337
|
}
|
|
293
338
|
/**
|
|
294
339
|
* The composer subscription-usage badge: a pill reading e.g.
|
|
@@ -296,7 +341,9 @@ function usageWindowLabel(t, window) {
|
|
|
296
341
|
* every provider's accounts and their windows. Returns null when no data is
|
|
297
342
|
* available.
|
|
298
343
|
*/
|
|
299
|
-
|
|
344
|
+
const STATS_ROW_SELECTOR = markerSelector(HOST_CONTRACT.markers.composerStats.row);
|
|
345
|
+
const STATS_DOCK_SELECTOR = markerSelector(HOST_CONTRACT.markers.composerStats.dock);
|
|
346
|
+
export function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs, }) {
|
|
300
347
|
const translate = t ?? fallbackTranslate;
|
|
301
348
|
const [displays, setDisplays] = useState([]);
|
|
302
349
|
const [selection, setSelection] = useState(undefined);
|
|
@@ -343,7 +390,8 @@ export function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
|
|
|
343
390
|
// real signal, unlike a fetch failure.
|
|
344
391
|
const live = new Set(roster.map(({ provider, account }) => usageKeyOf(provider, account)));
|
|
345
392
|
for (const key of lastKnown.keys()) {
|
|
346
|
-
if (refreshed.has(key.split(':', 1)[0]) &&
|
|
393
|
+
if (refreshed.has(key.split(':', 1)[0]) &&
|
|
394
|
+
!live.has(key)) {
|
|
347
395
|
lastKnown.delete(key);
|
|
348
396
|
observationsRef.current.delete(key);
|
|
349
397
|
}
|
|
@@ -356,7 +404,10 @@ export function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
|
|
|
356
404
|
return;
|
|
357
405
|
for (const { provider, account } of roster) {
|
|
358
406
|
const key = usageKeyOf(provider, account);
|
|
359
|
-
observationsRef.current.set(key, {
|
|
407
|
+
observationsRef.current.set(key, {
|
|
408
|
+
...observationsRef.current.get(key),
|
|
409
|
+
stale: true,
|
|
410
|
+
});
|
|
360
411
|
}
|
|
361
412
|
const plans = new Map();
|
|
362
413
|
for (const r of results) {
|
|
@@ -372,24 +423,39 @@ export function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
|
|
|
372
423
|
continue;
|
|
373
424
|
}
|
|
374
425
|
lastKnown.set(key, usage.windows);
|
|
375
|
-
observationsRef.current.set(key, {
|
|
426
|
+
observationsRef.current.set(key, {
|
|
427
|
+
observedAt: usage.observedAt ?? Date.now(),
|
|
428
|
+
stale: usage.stale === true,
|
|
429
|
+
});
|
|
376
430
|
}
|
|
377
|
-
setDisplays(previous => [
|
|
378
|
-
...groupUsageDisplays(roster, lastKnown, plans).map(display => ({
|
|
379
|
-
|
|
431
|
+
setDisplays((previous) => [
|
|
432
|
+
...groupUsageDisplays(roster, lastKnown, plans).map((display) => ({
|
|
433
|
+
...display,
|
|
434
|
+
accounts: display.accounts.map((account) => ({
|
|
435
|
+
...account,
|
|
380
436
|
...observationsRef.current.get(`${display.provider}:${account.key}`),
|
|
381
437
|
})),
|
|
382
438
|
})),
|
|
383
|
-
...previous
|
|
384
|
-
|
|
439
|
+
...previous
|
|
440
|
+
.filter((display) => !refreshed.has(display.provider))
|
|
441
|
+
.map((display) => ({
|
|
442
|
+
...display,
|
|
443
|
+
accounts: display.accounts.map((account) => ({
|
|
444
|
+
...account,
|
|
445
|
+
stale: true,
|
|
446
|
+
})),
|
|
385
447
|
})),
|
|
386
448
|
]);
|
|
387
449
|
}
|
|
388
450
|
catch {
|
|
389
451
|
// Keep cached numbers, but never present failed observations as current.
|
|
390
452
|
if (mountedRef.current)
|
|
391
|
-
setDisplays(previous => previous.map(display => ({
|
|
392
|
-
|
|
453
|
+
setDisplays((previous) => previous.map((display) => ({
|
|
454
|
+
...display,
|
|
455
|
+
accounts: display.accounts.map((account) => ({
|
|
456
|
+
...account,
|
|
457
|
+
stale: true,
|
|
458
|
+
})),
|
|
393
459
|
})));
|
|
394
460
|
}
|
|
395
461
|
finally {
|
|
@@ -398,14 +464,18 @@ export function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
|
|
|
398
464
|
}, [rpc]);
|
|
399
465
|
useEffect(() => {
|
|
400
466
|
mountedRef.current = true;
|
|
401
|
-
return () => {
|
|
467
|
+
return () => {
|
|
468
|
+
mountedRef.current = false;
|
|
469
|
+
};
|
|
402
470
|
}, []);
|
|
403
471
|
// Hidden renders nothing, so it polls nothing; showing it again refreshes at once.
|
|
404
472
|
useEffect(() => {
|
|
405
473
|
if (displayMode === 'hidden')
|
|
406
474
|
return;
|
|
407
475
|
void refresh();
|
|
408
|
-
const timer = setInterval(() => {
|
|
476
|
+
const timer = setInterval(() => {
|
|
477
|
+
void refresh();
|
|
478
|
+
}, USAGE_POLL_INTERVAL_MS);
|
|
409
479
|
window.addEventListener(USAGE_BADGE_REFRESH_EVENT, refresh);
|
|
410
480
|
return () => {
|
|
411
481
|
clearInterval(timer);
|
|
@@ -422,12 +492,18 @@ export function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
|
|
|
422
492
|
if (read === undefined || inflight)
|
|
423
493
|
return;
|
|
424
494
|
inflight = true;
|
|
425
|
-
void read()
|
|
495
|
+
void read()
|
|
496
|
+
.then((model) => {
|
|
426
497
|
if (cancelled)
|
|
427
498
|
return;
|
|
428
|
-
setLastSubscription(previous => retainSubscriptionSelection(previous, model));
|
|
499
|
+
setLastSubscription((previous) => retainSubscriptionSelection(previous, model));
|
|
429
500
|
setSelection(model);
|
|
430
|
-
}, () => {
|
|
501
|
+
}, () => {
|
|
502
|
+
/* keep the last known provider; the next tick retries */
|
|
503
|
+
})
|
|
504
|
+
.finally(() => {
|
|
505
|
+
inflight = false;
|
|
506
|
+
});
|
|
431
507
|
};
|
|
432
508
|
reload();
|
|
433
509
|
const timer = setInterval(reload, MODEL_POLL_INTERVAL_MS);
|
|
@@ -436,7 +512,14 @@ export function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
|
|
|
436
512
|
clearInterval(timer);
|
|
437
513
|
};
|
|
438
514
|
}, []);
|
|
439
|
-
const pos = useAnchoredPosition({
|
|
515
|
+
const pos = useAnchoredPosition({
|
|
516
|
+
open,
|
|
517
|
+
anchorRef: rootRef,
|
|
518
|
+
panelRef,
|
|
519
|
+
side: 'top',
|
|
520
|
+
gap: PANEL_GAP,
|
|
521
|
+
margin: PANEL_MARGIN,
|
|
522
|
+
});
|
|
440
523
|
useDismissOnOutsidePointer(rootRef, open, setOpen, panelRef);
|
|
441
524
|
useEffect(() => {
|
|
442
525
|
if (!open)
|
|
@@ -446,15 +529,20 @@ export function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
|
|
|
446
529
|
setOpen(false);
|
|
447
530
|
};
|
|
448
531
|
document.addEventListener('keydown', onKeyDown);
|
|
449
|
-
return () => {
|
|
532
|
+
return () => {
|
|
533
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
534
|
+
};
|
|
450
535
|
}, [open]);
|
|
451
|
-
// Sit on the host's stats row when there is one.
|
|
452
|
-
//
|
|
453
|
-
//
|
|
454
|
-
//
|
|
455
|
-
// on
|
|
456
|
-
// has steps or tokens, and unmounts
|
|
457
|
-
//
|
|
536
|
+
// Sit on the host's stats row when there is one. Hosts through 0.2.0 render
|
|
537
|
+
// their time/token pills in a row apart from the dock, marked
|
|
538
|
+
// `data-composer-stats`; every dock entry is its own row in the composer
|
|
539
|
+
// bar, so a badge rendered in place lands under those pills, and rendering
|
|
540
|
+
// into the row makes it a third pill on their line. The marker is watched
|
|
541
|
+
// (the row mounts only once the session has steps or tokens, and unmounts
|
|
542
|
+
// with them). From 0.2.1 the shipped pills are dock entries like this
|
|
543
|
+
// badge, each anchored `data-composer-stat`, so the in-place seat already
|
|
544
|
+
// shares their row; seeing a docked anchor ends the watch for a row that
|
|
545
|
+
// will not appear.
|
|
458
546
|
const [statsRow, setStatsRow] = useState(null);
|
|
459
547
|
useEffect(() => {
|
|
460
548
|
const seat = seatRef.current;
|
|
@@ -463,11 +551,19 @@ export function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
|
|
|
463
551
|
const scope = statsScopeOf(seat);
|
|
464
552
|
if (scope === null)
|
|
465
553
|
return;
|
|
466
|
-
const find = () => scope.querySelector(
|
|
467
|
-
|
|
468
|
-
|
|
554
|
+
const find = () => scope.querySelector(STATS_ROW_SELECTOR);
|
|
555
|
+
const settle = () => {
|
|
556
|
+
const row = find();
|
|
557
|
+
setStatsRow(row);
|
|
558
|
+
if (row === null && scope.querySelector(STATS_DOCK_SELECTOR) !== null)
|
|
559
|
+
observer.disconnect();
|
|
560
|
+
};
|
|
561
|
+
const observer = new MutationObserver(settle);
|
|
469
562
|
observer.observe(scope, { childList: true, subtree: true });
|
|
470
|
-
|
|
563
|
+
settle();
|
|
564
|
+
return () => {
|
|
565
|
+
observer.disconnect();
|
|
566
|
+
};
|
|
471
567
|
}, []);
|
|
472
568
|
useEffect(() => {
|
|
473
569
|
if (displayMode === 'hidden')
|
|
@@ -481,8 +577,12 @@ export function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
|
|
|
481
577
|
const interval = rotationMs !== undefined && Number.isFinite(rotationMs) && rotationMs > 0
|
|
482
578
|
? rotationMs
|
|
483
579
|
: BADGE_ROTATION_INTERVAL_MS;
|
|
484
|
-
const timer = setInterval(() => {
|
|
485
|
-
|
|
580
|
+
const timer = setInterval(() => {
|
|
581
|
+
setRotation((step) => step + 1);
|
|
582
|
+
}, interval);
|
|
583
|
+
return () => {
|
|
584
|
+
clearInterval(timer);
|
|
585
|
+
};
|
|
486
586
|
}, [displayMode, rotationMs]);
|
|
487
587
|
const seat = _jsx("span", { ref: seatRef, style: styles.seat, "aria-hidden": true });
|
|
488
588
|
// `always` follows the live model, not the retained subscription: the
|
|
@@ -495,7 +595,9 @@ export function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
|
|
|
495
595
|
// Model scope only while the pill shows the current model's provider;
|
|
496
596
|
// a rotated provider has no live model, so it reads at account level.
|
|
497
597
|
const pillModel = displayMode === 'always'
|
|
498
|
-
?
|
|
598
|
+
? current === collapsed[0]?.provider
|
|
599
|
+
? selection?.model
|
|
600
|
+
: undefined
|
|
499
601
|
: badgeSelection?.model;
|
|
500
602
|
const label = compactSegment(collapsed[0], pillModel, translate);
|
|
501
603
|
// The dialog opens from the badge, so it follows the provider and model the
|
|
@@ -504,7 +606,9 @@ export function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
|
|
|
504
606
|
// the provider that was showing when it opened instead of reordering live.
|
|
505
607
|
const dialogProvider = open && displayMode === 'always' ? dialogLead : badgeSelection?.provider;
|
|
506
608
|
const dialogModel = open && displayMode === 'always'
|
|
507
|
-
?
|
|
609
|
+
? dialogLead === current
|
|
610
|
+
? selection?.model
|
|
611
|
+
: undefined
|
|
508
612
|
: badgeSelection?.model;
|
|
509
613
|
const expanded = expandedDisplays(displays, dialogProvider);
|
|
510
614
|
const title = translate('usageBadgeTitle');
|
|
@@ -515,18 +619,29 @@ export function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
|
|
|
515
619
|
if (next)
|
|
516
620
|
void refresh();
|
|
517
621
|
};
|
|
518
|
-
const pill = (_jsxs("span", { ref: rootRef, style: styles.anchor, children: [_jsx("style", { children: subscriptionChromeCss }), _jsxs("button", { type: "button", className: "dsh-subscription-usage-pill", style: { ...styles.pill, ...(hover || open ? styles.pillActive : {}) }, "aria-haspopup": "dialog", "aria-expanded": open, "aria-label": `${title} · ${label}`, title: title, onMouseEnter: () => {
|
|
519
|
-
|
|
622
|
+
const pill = (_jsxs("span", { ref: rootRef, style: styles.anchor, children: [_jsx("style", { children: subscriptionChromeCss }), _jsxs("button", { type: "button", className: "dsh-subscription-usage-pill", style: { ...styles.pill, ...(hover || open ? styles.pillActive : {}) }, "aria-haspopup": "dialog", "aria-expanded": open, "aria-label": `${title} · ${label}`, title: title, onMouseEnter: () => {
|
|
623
|
+
setHover(true);
|
|
624
|
+
}, onMouseLeave: () => {
|
|
625
|
+
setHover(false);
|
|
626
|
+
}, onClick: toggle, children: [_jsx(UsageBadgeIcon, {}), _jsx("span", { style: styles.label, children: label })] }), open &&
|
|
627
|
+
createPortal(_jsxs("div", { ref: panelRef, role: "dialog", "aria-label": title, className: "dsh-subscription-usage-panel", style: { ...styles.panel, ...(pos ?? MEASURE_STYLE) }, children: [_jsx("div", { style: styles.title, children: _jsxs("span", { style: styles.titleLabel, children: [_jsx(UsageBadgeIcon, {}), title] }) }), _jsx("div", { style: styles.titleRule, "aria-hidden": true }), expanded.map((d, index) => (_jsxs("section", { style: index === 0 ? undefined : styles.section, children: [_jsxs("div", { style: styles.providerRow, children: [_jsxs("span", { style: styles.providerName, children: [d.name, d.provider === current && (_jsx("span", { style: styles.currentTag, children: translate('usageBadgeCurrent') }))] }), d.accounts.length === 1 && (_jsx(AccountMeta, { account: d.accounts[0], translate: translate }))] }), d.accounts.map((account, accountIndex) => (_jsxs("div", { style: accountIndex === 0 ? undefined : styles.accountBlock, children: [d.accounts.length > 1 && (_jsx("div", { style: styles.accountRow, children: _jsx(AccountMeta, { account: account, translate: translate }) })), _jsx(AccountWindows, { windows: account.windows, observedAt: account.observedAt, stale: account.stale, model: d.provider === dialogProvider ? dialogModel : undefined, provider: d.provider, translate: translate }, `${d.provider}:${dialogModel ?? ''}`)] }, account.key)))] }, d.provider)))] }), document.body)] }));
|
|
628
|
+
return (_jsxs(_Fragment, { children: [seat, statsRow !== null && statsRow.isConnected
|
|
629
|
+
? createPortal(pill, statsRow)
|
|
630
|
+
: pill] }));
|
|
520
631
|
}
|
|
632
|
+
/** How far statsScopeOf walks looking for `composerStats.row`. */
|
|
633
|
+
const LEGACY_STATS_SCOPE_DEPTH = 4;
|
|
521
634
|
/**
|
|
522
|
-
*
|
|
523
|
-
*
|
|
524
|
-
*
|
|
635
|
+
* Scope for the legacy stats row. The nearest ancestor within
|
|
636
|
+
* LEGACY_STATS_SCOPE_DEPTH that contains `composerStats.row`, so a badge
|
|
637
|
+
* does not adopt another composer's pills. When none does, the seat's
|
|
638
|
+
* parent: the slot outlet, which holds sibling `composerStats.dock` anchors
|
|
639
|
+
* on hosts whose pills are dock entries.
|
|
525
640
|
*/
|
|
526
641
|
function statsScopeOf(seat) {
|
|
527
642
|
let node = seat.parentElement;
|
|
528
|
-
for (let depth = 0; node !== null && depth <
|
|
529
|
-
if (node.querySelector(
|
|
643
|
+
for (let depth = 0; node !== null && depth < LEGACY_STATS_SCOPE_DEPTH; depth++) {
|
|
644
|
+
if (node.querySelector(STATS_ROW_SELECTOR) !== null)
|
|
530
645
|
return node;
|
|
531
646
|
node = node.parentElement;
|
|
532
647
|
}
|
|
@@ -537,21 +652,25 @@ function statsScopeOf(seat) {
|
|
|
537
652
|
* marks the default account (the one direct routes serve and the collapsed
|
|
538
653
|
* pill reads), the same glyph Settings → 订阅 uses.
|
|
539
654
|
*/
|
|
540
|
-
function AccountMeta({ account, translate }) {
|
|
541
|
-
const parts = [
|
|
542
|
-
.
|
|
543
|
-
|
|
655
|
+
function AccountMeta({ account, translate, }) {
|
|
656
|
+
const parts = [
|
|
657
|
+
account.account,
|
|
658
|
+
account.plan === undefined
|
|
659
|
+
? undefined
|
|
660
|
+
: translate('usagePlan', { plan: account.plan }),
|
|
661
|
+
].filter((part) => part !== undefined && part !== '');
|
|
662
|
+
return (_jsxs("span", { style: styles.providerMeta, title: account.account, children: [account.isDefault && (_jsxs("span", { style: styles.defaultStar, "aria-label": "default", children: ["\u2605", ' '] })), parts.join(' · ')] }));
|
|
544
663
|
}
|
|
545
664
|
/** Preview each account independently; all remaining quotas stay accessible. */
|
|
546
|
-
export function AccountWindows({ windows, model, provider, translate, observedAt, stale }) {
|
|
665
|
+
export function AccountWindows({ windows, model, provider, translate, observedAt, stale, }) {
|
|
547
666
|
const { shown, hidden } = previewWindows(windows, model, provider);
|
|
548
667
|
const rows = (items) => (_jsx("dl", { style: styles.details, children: items.map((w, i) => (_jsx(WindowRow, { label: `${usageWindowLabel(translate, w)}${model !== undefined && w.scope === model ? ` · ${translate('usageBadgeCurrent')}` : ''}`, window: w, t: translate, observedAt: observedAt, stale: stale }, i))) }));
|
|
549
|
-
return _jsxs(_Fragment, { children: [rows(shown), hidden.length > 0 && _jsxs("details", { style: styles.moreWindows, children: [_jsx("summary", { style: styles.moreSummary, children: translate('usageBadgeMoreWindows', { count: hidden.length }) }), rows(hidden)] })] });
|
|
668
|
+
return (_jsxs(_Fragment, { children: [rows(shown), hidden.length > 0 && (_jsxs("details", { style: styles.moreWindows, children: [_jsx("summary", { style: styles.moreSummary, children: translate('usageBadgeMoreWindows', { count: hidden.length }) }), rows(hidden)] }))] }));
|
|
550
669
|
}
|
|
551
670
|
/** One `dt`/`dd` pair: window name → `25% · 6d1h`, with the bar underneath. */
|
|
552
|
-
function WindowRow({ label, window: w, t, observedAt, stale }) {
|
|
671
|
+
function WindowRow({ label, window: w, t, observedAt, stale, }) {
|
|
553
672
|
const percent = displayUsedPercent(w.usedPercent);
|
|
554
|
-
return (_jsxs(_Fragment, { children: [_jsx("dt", { style: styles.dt, children: label }), _jsxs("dd", { style: styles.dd, children: [percent === undefined ? t('usageMeterInvalid') : `${percent}%`, w.resetsAt !== undefined && Number.isFinite(w.resetsAt) && _jsxs("span", { style: styles.reset, children: [" \u00B7 ", windowLabel(w)] })] }), _jsx(UsageMeter, { window: w, t: t, style: styles.bar, observedAt: observedAt, stale: stale })] }));
|
|
673
|
+
return (_jsxs(_Fragment, { children: [_jsx("dt", { style: styles.dt, children: label }), _jsxs("dd", { style: styles.dd, children: [percent === undefined ? t('usageMeterInvalid') : `${percent}%`, w.resetsAt !== undefined && Number.isFinite(w.resetsAt) && (_jsxs("span", { style: styles.reset, children: [" \u00B7 ", windowLabel(w)] }))] }), _jsx(UsageMeter, { window: w, t: t, style: styles.bar, observedAt: observedAt, stale: stale })] }));
|
|
555
674
|
}
|
|
556
675
|
/**
|
|
557
676
|
* Unplaced portal panel: hidden but laid out so the clamp measures real
|
|
@@ -564,12 +683,22 @@ const styles = {
|
|
|
564
683
|
// Mirrors the host StatsPills pill so the badge reads as a sibling of the
|
|
565
684
|
// shipped time/token pills.
|
|
566
685
|
pill: {
|
|
567
|
-
boxSizing: 'border-box',
|
|
686
|
+
boxSizing: 'border-box',
|
|
687
|
+
maxWidth: '100%',
|
|
568
688
|
color: 'var(--dsw-alias-label-tertiary)',
|
|
569
|
-
font: 'inherit',
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
689
|
+
font: 'inherit',
|
|
690
|
+
fontSize: 'calc(var(--dsh-content-font-size-secondary, 13px) - 1px)',
|
|
691
|
+
fontVariantNumeric: 'tabular-nums',
|
|
692
|
+
lineHeight: 'calc(20px + var(--dsh-content-font-delta-secondary, 0px))',
|
|
693
|
+
whiteSpace: 'nowrap',
|
|
694
|
+
background: 'transparent',
|
|
695
|
+
border: 'none',
|
|
696
|
+
borderRadius: 999,
|
|
697
|
+
alignItems: 'center',
|
|
698
|
+
gap: 6,
|
|
699
|
+
padding: '1px 8px',
|
|
700
|
+
display: 'inline-flex',
|
|
701
|
+
cursor: 'pointer',
|
|
573
702
|
},
|
|
574
703
|
pillActive: {
|
|
575
704
|
background: 'var(--dsw-alias-interactive-bg-hover)',
|
|
@@ -581,52 +710,111 @@ const styles = {
|
|
|
581
710
|
// without the blur the transcript behind the dialog stays sharp and shows
|
|
582
711
|
// through the fill.
|
|
583
712
|
panel: {
|
|
584
|
-
position: 'fixed',
|
|
713
|
+
position: 'fixed',
|
|
714
|
+
zIndex: 1100,
|
|
715
|
+
boxSizing: 'border-box',
|
|
585
716
|
background: 'var(--dsw-specific-menu)',
|
|
586
717
|
backdropFilter: 'var(--dsw-menu-backdrop-filter)',
|
|
587
|
-
width: 'max-content',
|
|
588
|
-
|
|
718
|
+
width: 'max-content',
|
|
719
|
+
minWidth: 'min(300px, 100vw - 24px)',
|
|
720
|
+
maxWidth: 'min(440px, 100vw - 24px)',
|
|
721
|
+
maxHeight: 'min(560px, 100dvh - 24px)',
|
|
722
|
+
overflowY: 'auto',
|
|
723
|
+
overscrollBehavior: 'contain',
|
|
589
724
|
boxShadow: 'var(--dsw-elevation-prominent)',
|
|
590
|
-
color: 'var(--dsw-alias-label-secondary)',
|
|
591
|
-
|
|
725
|
+
color: 'var(--dsw-alias-label-secondary)',
|
|
726
|
+
cursor: 'default',
|
|
727
|
+
border: 0,
|
|
728
|
+
borderRadius: 'var(--dsw-radius-lg, 16px)',
|
|
729
|
+
padding: 16,
|
|
730
|
+
fontSize: 12,
|
|
731
|
+
lineHeight: '18px',
|
|
592
732
|
},
|
|
593
733
|
title: {
|
|
594
|
-
color: 'var(--dsw-alias-label-primary)',
|
|
595
|
-
|
|
734
|
+
color: 'var(--dsw-alias-label-primary)',
|
|
735
|
+
display: 'flex',
|
|
736
|
+
justifyContent: 'space-between',
|
|
737
|
+
gap: 16,
|
|
738
|
+
marginBottom: 8,
|
|
739
|
+
fontWeight: 500,
|
|
740
|
+
},
|
|
741
|
+
titleLabel: {
|
|
742
|
+
alignItems: 'center',
|
|
743
|
+
gap: 6,
|
|
744
|
+
minWidth: 0,
|
|
745
|
+
display: 'inline-flex',
|
|
746
|
+
},
|
|
747
|
+
titleRule: {
|
|
748
|
+
borderTop: '0.5px solid var(--dsw-alias-border-l2)',
|
|
749
|
+
marginBottom: 10,
|
|
750
|
+
},
|
|
751
|
+
section: {
|
|
752
|
+
marginTop: 12,
|
|
753
|
+
paddingTop: 10,
|
|
754
|
+
borderTop: '0.5px solid var(--dsw-alias-border-l2)',
|
|
596
755
|
},
|
|
597
|
-
titleLabel: { alignItems: 'center', gap: 6, minWidth: 0, display: 'inline-flex' },
|
|
598
|
-
titleRule: { borderTop: '0.5px solid var(--dsw-alias-border-l2)', marginBottom: 10 },
|
|
599
|
-
section: { marginTop: 12, paddingTop: 10, borderTop: '0.5px solid var(--dsw-alias-border-l2)' },
|
|
600
756
|
providerRow: {
|
|
601
|
-
display: 'flex',
|
|
757
|
+
display: 'flex',
|
|
758
|
+
justifyContent: 'space-between',
|
|
759
|
+
alignItems: 'baseline',
|
|
760
|
+
gap: 16,
|
|
761
|
+
marginBottom: 6,
|
|
762
|
+
},
|
|
763
|
+
providerName: {
|
|
764
|
+
color: 'var(--dsw-alias-label-primary)',
|
|
765
|
+
fontWeight: 500,
|
|
766
|
+
display: 'inline-flex',
|
|
767
|
+
alignItems: 'center',
|
|
768
|
+
gap: 6,
|
|
602
769
|
},
|
|
603
|
-
providerName: { color: 'var(--dsw-alias-label-primary)', fontWeight: 500, display: 'inline-flex', alignItems: 'center', gap: 6 },
|
|
604
770
|
currentTag: {
|
|
605
|
-
fontSize: 10,
|
|
606
|
-
|
|
771
|
+
fontSize: 10,
|
|
772
|
+
lineHeight: '14px',
|
|
773
|
+
fontWeight: 400,
|
|
774
|
+
padding: '0 5px',
|
|
775
|
+
borderRadius: 7,
|
|
776
|
+
color: 'var(--dsw-alias-label-secondary)',
|
|
777
|
+
background: 'var(--dsw-alias-interactive-bg-hover)',
|
|
607
778
|
},
|
|
608
779
|
providerMeta: {
|
|
609
|
-
color: 'var(--dsw-alias-label-tertiary)',
|
|
610
|
-
|
|
780
|
+
color: 'var(--dsw-alias-label-tertiary)',
|
|
781
|
+
minWidth: 0,
|
|
782
|
+
overflow: 'hidden',
|
|
783
|
+
textOverflow: 'ellipsis',
|
|
784
|
+
whiteSpace: 'nowrap',
|
|
611
785
|
},
|
|
612
786
|
// Same star and color as the default-account marker in Settings → 订阅.
|
|
613
787
|
defaultStar: { color: 'var(--dsw-alias-state-warn-label)' },
|
|
614
788
|
accountBlock: { marginTop: 8 },
|
|
615
789
|
accountRow: { display: 'flex', marginBottom: 4 },
|
|
616
790
|
details: {
|
|
617
|
-
color: 'var(--dsw-alias-label-tertiary)',
|
|
618
|
-
|
|
791
|
+
color: 'var(--dsw-alias-label-tertiary)',
|
|
792
|
+
display: 'grid',
|
|
793
|
+
gridTemplateColumns: 'minmax(0, 1fr) max-content',
|
|
794
|
+
gap: '4px 16px',
|
|
795
|
+
margin: 0,
|
|
619
796
|
},
|
|
620
797
|
moreWindows: { marginTop: 8 },
|
|
621
|
-
moreSummary: {
|
|
798
|
+
moreSummary: {
|
|
799
|
+
cursor: 'pointer',
|
|
800
|
+
color: 'var(--dsw-alias-label-secondary)',
|
|
801
|
+
marginBottom: 8,
|
|
802
|
+
},
|
|
622
803
|
dt: { minWidth: 0, margin: 0, overflowWrap: 'anywhere' },
|
|
623
804
|
dd: {
|
|
624
|
-
minWidth: 0,
|
|
625
|
-
|
|
805
|
+
minWidth: 0,
|
|
806
|
+
margin: 0,
|
|
807
|
+
color: 'var(--dsw-alias-label-secondary)',
|
|
808
|
+
fontVariantNumeric: 'tabular-nums',
|
|
809
|
+
textAlign: 'right',
|
|
626
810
|
},
|
|
627
811
|
reset: { color: 'var(--dsw-alias-label-tertiary)' },
|
|
628
812
|
bar: {
|
|
629
|
-
gridColumn: '1 / -1',
|
|
630
|
-
|
|
813
|
+
gridColumn: '1 / -1',
|
|
814
|
+
height: 4,
|
|
815
|
+
borderRadius: 2,
|
|
816
|
+
overflow: 'hidden',
|
|
817
|
+
background: 'var(--dsw-alias-border-l2)',
|
|
818
|
+
marginBottom: 2,
|
|
631
819
|
},
|
|
632
820
|
};
|