dsh-plugin-subscription-oauth 0.12.8 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/auth/provider-controller.d.ts +7 -0
- package/lib/auth/provider-controller.js +1 -0
- package/lib/auth/registry.d.ts +12 -0
- package/lib/auth/registry.js +7 -0
- package/lib/auth/rpc.js +6 -0
- package/lib/auth/workbuddy-flow.d.ts +23 -1
- package/lib/auth/workbuddy-flow.js +110 -2
- package/lib/client/SubscriptionsSection.js +35 -5
- package/lib/client/locales.d.ts +12 -0
- package/lib/client/locales.js +12 -0
- package/lib/client.js +95 -23
- package/lib/client.js.map +1 -1
- package/lib/protocol.d.ts +23 -1
- package/lib/protocol.js +1 -0
- package/lib/providers/workbuddy-credits.d.ts +23 -0
- package/lib/providers/workbuddy-credits.js +52 -0
- package/lib/providers/workbuddy-provider.js +25 -3
- package/lib/providers/workbuddy.d.ts +27 -0
- package/lib/providers/workbuddy.js +251 -1
- package/package.json +1 -1
|
@@ -77,6 +77,13 @@ export interface ProviderUsageRead {
|
|
|
77
77
|
plan?: string;
|
|
78
78
|
renewsAt?: number;
|
|
79
79
|
windows: readonly UsageWindow[];
|
|
80
|
+
/** Daily check-in state, when the provider states one. */
|
|
81
|
+
checkin?: {
|
|
82
|
+
active: boolean;
|
|
83
|
+
todayCheckedIn: boolean;
|
|
84
|
+
streakDays: number;
|
|
85
|
+
todayCredit: number;
|
|
86
|
+
};
|
|
80
87
|
/**
|
|
81
88
|
* Why these figures are not the provider's primary accounting.
|
|
82
89
|
*
|
|
@@ -496,6 +496,7 @@ export class ProviderController {
|
|
|
496
496
|
fetchedAt: Date.now(),
|
|
497
497
|
...read.plan === undefined ? {} : { plan: read.plan },
|
|
498
498
|
...read.renewsAt === undefined ? {} : { renewsAt: read.renewsAt },
|
|
499
|
+
...read.checkin === undefined ? {} : { checkin: read.checkin },
|
|
499
500
|
...read.usageError === undefined ? {} : { substitute: true },
|
|
500
501
|
};
|
|
501
502
|
this.usageByAccount.set(account, entry);
|
package/lib/auth/registry.d.ts
CHANGED
|
@@ -32,6 +32,12 @@ export interface ProviderRoute {
|
|
|
32
32
|
removeModel(id: string): Promise<ProviderStatus>;
|
|
33
33
|
usage(force?: boolean): Promise<ProviderUsageResult>;
|
|
34
34
|
reload(): Promise<ProviderStatus>;
|
|
35
|
+
/** Claim one account's daily check-in reward, when the route has one. */
|
|
36
|
+
checkin?(account: string): Promise<{
|
|
37
|
+
credit: number;
|
|
38
|
+
streakDays: number;
|
|
39
|
+
already: boolean;
|
|
40
|
+
}>;
|
|
35
41
|
/** Fast and image variants for one model, when the route's discovery reported them. */
|
|
36
42
|
modelVariants?(id: string): Promise<ModelVariants | undefined> | ModelVariants | undefined;
|
|
37
43
|
}
|
|
@@ -101,4 +107,10 @@ export declare class SubscriptionRegistry {
|
|
|
101
107
|
setAutoOn(enabled: boolean): Promise<{
|
|
102
108
|
enabled: boolean;
|
|
103
109
|
}>;
|
|
110
|
+
/** Claim one account's daily check-in reward, when the route supports it. */
|
|
111
|
+
checkin(provider: string, account: string): Promise<{
|
|
112
|
+
credit: number;
|
|
113
|
+
streakDays: number;
|
|
114
|
+
already: boolean;
|
|
115
|
+
}>;
|
|
104
116
|
}
|
package/lib/auth/registry.js
CHANGED
|
@@ -161,4 +161,11 @@ export class SubscriptionRegistry {
|
|
|
161
161
|
await this.preferences.setAutoOn(enabled);
|
|
162
162
|
return { enabled };
|
|
163
163
|
}
|
|
164
|
+
/** Claim one account's daily check-in reward, when the route supports it. */
|
|
165
|
+
async checkin(provider, account) {
|
|
166
|
+
const route = this.route(provider);
|
|
167
|
+
if (route.checkin === undefined)
|
|
168
|
+
throw new ProviderError(`${provider} has no check-in`, BAD_REQUEST);
|
|
169
|
+
return route.checkin(account);
|
|
170
|
+
}
|
|
164
171
|
}
|
package/lib/auth/rpc.js
CHANGED
|
@@ -163,6 +163,12 @@ async function runEndpoint(registry, endpoint, payload, balance) {
|
|
|
163
163
|
throw new ProviderError('setAutoOn requires an enabled flag', BAD_REQUEST);
|
|
164
164
|
return registry.setAutoOn(enabled);
|
|
165
165
|
}
|
|
166
|
+
case 'checkin': {
|
|
167
|
+
const account = stringField(payload, 'account');
|
|
168
|
+
if (account === undefined)
|
|
169
|
+
throw new ProviderError('checkin requires an account', BAD_REQUEST);
|
|
170
|
+
return registry.checkin(requiredProvider(payload, endpoint), account);
|
|
171
|
+
}
|
|
166
172
|
case 'deepseek-balance': {
|
|
167
173
|
// The DeepSeek account's balance, read on the node half through the host's own
|
|
168
174
|
// account service. The browser cannot call that service directly (it sits behind
|
|
@@ -101,8 +101,30 @@ export interface WorkBuddyUsageWindow {
|
|
|
101
101
|
limit?: number;
|
|
102
102
|
resetsAt?: number;
|
|
103
103
|
}
|
|
104
|
+
/** The account's check-in state, from the billing activity endpoint. */
|
|
105
|
+
export interface WorkBuddyCheckin {
|
|
106
|
+
active: boolean;
|
|
107
|
+
todayCheckedIn: boolean;
|
|
108
|
+
streakDays: number;
|
|
109
|
+
todayCredit: number;
|
|
110
|
+
}
|
|
111
|
+
/** Read the account's daily check-in state, without changing anything. */
|
|
112
|
+
export declare function readWorkBuddyCheckin(token: WorkBuddyTokenSet, fetchFn: typeof fetch): Promise<WorkBuddyCheckin>;
|
|
113
|
+
/** Claim today's check-in reward; resolves with the credit granted and the streak. */
|
|
114
|
+
export declare function claimWorkBuddyCheckin(token: WorkBuddyTokenSet, fetchFn: typeof fetch): Promise<{
|
|
115
|
+
credit: number;
|
|
116
|
+
streakDays: number;
|
|
117
|
+
}>;
|
|
118
|
+
/** The account's precise remaining credits, summed across its live packages. */
|
|
119
|
+
export declare function readWorkBuddyCredits(token: WorkBuddyTokenSet, fetchFn: typeof fetch): Promise<{
|
|
120
|
+
remaining: number;
|
|
121
|
+
nearestExpiryMs?: number;
|
|
122
|
+
} | null>;
|
|
104
123
|
/** Read the account's plan and metered credits for the Settings card. */
|
|
105
|
-
export declare function readWorkBuddyUsage(token: WorkBuddyTokenSet, plan: string | undefined, fetchFn: typeof fetch
|
|
124
|
+
export declare function readWorkBuddyUsage(token: WorkBuddyTokenSet, plan: string | undefined, fetchFn: typeof fetch, spend?: {
|
|
125
|
+
spent: number;
|
|
126
|
+
}): Promise<{
|
|
106
127
|
plan: string;
|
|
107
128
|
windows: WorkBuddyUsageWindow[];
|
|
129
|
+
checkin?: WorkBuddyCheckin;
|
|
108
130
|
}>;
|
|
@@ -311,6 +311,79 @@ async function workbuddyMeter(token, path, body, fetchFn) {
|
|
|
311
311
|
throw new Error(String(env.msg ?? `error ${code}`));
|
|
312
312
|
return isRecord(env.data) ? env.data : null;
|
|
313
313
|
}
|
|
314
|
+
/** Read the account's daily check-in state, without changing anything. */
|
|
315
|
+
export async function readWorkBuddyCheckin(token, fetchFn) {
|
|
316
|
+
const data = await workbuddyMeter(token, '/billing/meter/checkin-activity-status', {}, fetchFn);
|
|
317
|
+
if (data === null)
|
|
318
|
+
return { active: false, todayCheckedIn: false, streakDays: 0, todayCredit: 0 };
|
|
319
|
+
const num = (key) => typeof data[key] === 'number' ? data[key] : 0;
|
|
320
|
+
return {
|
|
321
|
+
active: data.active === true,
|
|
322
|
+
todayCheckedIn: data.today_checked_in === true,
|
|
323
|
+
streakDays: num('streak_days'),
|
|
324
|
+
todayCredit: num('today_credit'),
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
/** Claim today's check-in reward; resolves with the credit granted and the streak. */
|
|
328
|
+
export async function claimWorkBuddyCheckin(token, fetchFn) {
|
|
329
|
+
const data = await workbuddyMeter(token, '/billing/meter/daily-checkin', {}, fetchFn);
|
|
330
|
+
if (data === null)
|
|
331
|
+
return { credit: 0, streakDays: 0 };
|
|
332
|
+
const num = (key) => typeof data[key] === 'number' ? data[key] : 0;
|
|
333
|
+
return { credit: num('credit'), streakDays: num('streak_days') };
|
|
334
|
+
}
|
|
335
|
+
/** The account's precise remaining credits, summed across its live packages. */
|
|
336
|
+
export async function readWorkBuddyCredits(token, fetchFn) {
|
|
337
|
+
const pad = (n) => n.toString().padStart(2, '0');
|
|
338
|
+
const fmt = (date) => `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
|
339
|
+
const now = new Date();
|
|
340
|
+
const data = await workbuddyMeter(token, '/billing/meter/get-user-resource', {
|
|
341
|
+
PageNumber: 1,
|
|
342
|
+
PageSize: 100,
|
|
343
|
+
ProductCode: 'p_tcaca',
|
|
344
|
+
Status: [0, 3],
|
|
345
|
+
PackageEndTimeRangeBegin: fmt(now),
|
|
346
|
+
PackageEndTimeRangeEnd: fmt(new Date(now.getTime() + 3185136e6)),
|
|
347
|
+
}, fetchFn);
|
|
348
|
+
if (data === null)
|
|
349
|
+
return null;
|
|
350
|
+
const response = isRecord(data.Response) ? data.Response : {};
|
|
351
|
+
const inner = isRecord(response.Data) ? response.Data : {};
|
|
352
|
+
const accounts = Array.isArray(inner.Accounts) ? inner.Accounts.filter(isRecord) : [];
|
|
353
|
+
const parseDate = (raw) => {
|
|
354
|
+
if (typeof raw === 'number' && raw > 0xe8d4a51000)
|
|
355
|
+
return raw;
|
|
356
|
+
if (typeof raw === 'string' && raw !== '') {
|
|
357
|
+
const parsed = Date.parse(raw);
|
|
358
|
+
if (!Number.isNaN(parsed))
|
|
359
|
+
return parsed;
|
|
360
|
+
}
|
|
361
|
+
return undefined;
|
|
362
|
+
};
|
|
363
|
+
let remaining = 0;
|
|
364
|
+
let nearestExpiryMs;
|
|
365
|
+
let livePackages = 0;
|
|
366
|
+
for (const account of accounts) {
|
|
367
|
+
const num = (key) => capacity(account[key]);
|
|
368
|
+
const monthly = num('CapacityType') === 4;
|
|
369
|
+
const remain = monthly ? num('CycleCapacityRemain') : num('CapacityRemain');
|
|
370
|
+
const capped = remain < 0 ? 0 : remain;
|
|
371
|
+
const cycleEndMs = parseDate(account.CycleEndTime);
|
|
372
|
+
const expiresAtMs = monthly ? undefined : parseDate(account.ExpiredTime) ?? cycleEndMs;
|
|
373
|
+
if (!monthly && (capped <= 0 || (expiresAtMs !== undefined && expiresAtMs <= Date.now())))
|
|
374
|
+
continue;
|
|
375
|
+
livePackages += 1;
|
|
376
|
+
remaining += capped;
|
|
377
|
+
if (expiresAtMs !== undefined && (nearestExpiryMs === undefined || expiresAtMs < nearestExpiryMs)) {
|
|
378
|
+
nearestExpiryMs = expiresAtMs;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
// No live package reads as "this account doesn't use the package list" — an enterprise
|
|
382
|
+
// account, say — so the caller falls through to its own meter.
|
|
383
|
+
if (livePackages === 0)
|
|
384
|
+
return null;
|
|
385
|
+
return { remaining, ...nearestExpiryMs === undefined ? {} : { nearestExpiryMs } };
|
|
386
|
+
}
|
|
314
387
|
/** The account's credits from its resource summary: the plan's used against granted. */
|
|
315
388
|
function usageOfSummary(summary, plan) {
|
|
316
389
|
const isPaid = summary?.IsPaidUser === true;
|
|
@@ -349,7 +422,34 @@ function enterpriseUsageOf(data) {
|
|
|
349
422
|
return window;
|
|
350
423
|
}
|
|
351
424
|
/** Read the account's plan and metered credits for the Settings card. */
|
|
352
|
-
export async function readWorkBuddyUsage(token, plan, fetchFn) {
|
|
425
|
+
export async function readWorkBuddyUsage(token, plan, fetchFn, spend) {
|
|
426
|
+
// The precise figures: the live packages' remaining and total size, and the account's
|
|
427
|
+
// accumulated spend from the real per-turn credits. Spent-against-limit reads as the
|
|
428
|
+
// true billed share, not an estimate from tokens.
|
|
429
|
+
try {
|
|
430
|
+
const credits = await readWorkBuddyCredits(token, fetchFn);
|
|
431
|
+
if (credits !== null) {
|
|
432
|
+
const window = { label: 'Credits', used: spend?.spent ?? 0 };
|
|
433
|
+
if (credits.remaining > 0 || (spend?.spent ?? 0) > 0) {
|
|
434
|
+
const limit = credits.remaining + (spend?.spent ?? 0);
|
|
435
|
+
window.limit = limit;
|
|
436
|
+
window.usedPercent = limit > 0 ? (100 * (spend?.spent ?? 0)) / limit : 0;
|
|
437
|
+
}
|
|
438
|
+
if (credits.nearestExpiryMs !== undefined)
|
|
439
|
+
window.resetsAt = credits.nearestExpiryMs;
|
|
440
|
+
const out = { plan: plan ?? 'Free', windows: [window] };
|
|
441
|
+
try {
|
|
442
|
+
out.checkin = await readWorkBuddyCheckin(token, fetchFn);
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
// A check-in read failure never hides the credits.
|
|
446
|
+
}
|
|
447
|
+
return out;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
catch {
|
|
451
|
+
// Fall through to the summary when the package list can't be read.
|
|
452
|
+
}
|
|
353
453
|
const summary = await workbuddyMeter(token, '/billing/meter/get-user-resource-summary', {}, fetchFn);
|
|
354
454
|
const out = usageOfSummary(summary, plan);
|
|
355
455
|
// An enterprise account's summary has no packages: its credits are the enterprise meter's.
|
|
@@ -357,12 +457,20 @@ export async function readWorkBuddyUsage(token, plan, fetchFn) {
|
|
|
357
457
|
try {
|
|
358
458
|
const enterprise = enterpriseUsageOf(await workbuddyMeter(token, '/billing/meter/get-enterprise-user-usage', {}, fetchFn));
|
|
359
459
|
if (enterprise !== null) {
|
|
360
|
-
|
|
460
|
+
out.windows = [enterprise];
|
|
461
|
+
out.plan = plan ?? 'Enterprise';
|
|
361
462
|
}
|
|
362
463
|
}
|
|
363
464
|
catch {
|
|
364
465
|
// The personal-account refusal (10001) leaves the card as the summary said.
|
|
365
466
|
}
|
|
366
467
|
}
|
|
468
|
+
// Fold the check-in state in beside the credits: the card shows today's status and streak.
|
|
469
|
+
try {
|
|
470
|
+
out.checkin = await readWorkBuddyCheckin(token, fetchFn);
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
// A check-in read failure never hides the credits.
|
|
474
|
+
}
|
|
367
475
|
return out;
|
|
368
476
|
}
|
|
@@ -313,12 +313,13 @@ function windowAmount(window) {
|
|
|
313
313
|
return `${String(window.used)}/${String(window.limit)}`;
|
|
314
314
|
return '—';
|
|
315
315
|
}
|
|
316
|
-
/** One account's metered windows, rendered under its account row. */
|
|
316
|
+
/** One account's metered windows and check-in, rendered under its account row. */
|
|
317
317
|
function AccountUsage(props) {
|
|
318
|
-
const { entry, t } = props;
|
|
318
|
+
const { entry, t, busyAction, onCheckin } = props;
|
|
319
319
|
if (entry === undefined)
|
|
320
320
|
return null;
|
|
321
|
-
|
|
321
|
+
const checkin = entry.checkin;
|
|
322
|
+
return (_jsxs("div", { style: styles.usageInline, children: [checkin?.active === true && (_jsxs("div", { style: styles.usageLine, children: [_jsxs("span", { style: styles.modelName, children: [t('checkinTitle'), ' · ', checkin.todayCheckedIn ? t('checkinDone', { days: checkin.streakDays }) : t('checkinPending')] }), !checkin.todayCheckedIn && onCheckin !== undefined && (_jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.pushRight, disabled: busyAction, onClick: onCheckin, children: t('checkinClaim') }))] })), entry.windows.map((window) => (_jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageLine, children: [_jsxs("span", { style: styles.modelName, children: [windowLabel(t, window), ' · ', windowAmount(window)] }), _jsx("span", { style: styles.modelMeta, children: window.resetsAt === undefined ? '' : formatReset(window.resetsAt, t) })] }), window.usedPercent !== undefined && (_jsx("div", { className: "dsh-subscription-meter", children: _jsx("span", { style: { width: `${String(Math.min(100, Math.max(0, window.usedPercent)))}%` }, "data-exhausted": window.usedPercent >= 90 ? 'true' : 'false' }) }))] }, `${entry.account}-${window.label}`))), entry.windows.length === 0 && (_jsx("p", { style: styles.statusLine, children: t('usageNone') }))] }));
|
|
322
323
|
}
|
|
323
324
|
/** The plan-and-usage block for the whole route. */
|
|
324
325
|
function UsageBlock(props) {
|
|
@@ -523,7 +524,7 @@ function ProviderCard(props) {
|
|
|
523
524
|
const usageEntry = (status.usage ?? []).find((entry) => entry.account === account.account);
|
|
524
525
|
return (_jsxs("div", { style: styles.accountRow, children: [_jsxs("div", { style: styles.accountHeader, children: [_jsx("button", { type: "button", style: styles.starButton, title: account.isDefault ? t('defaultBadge') : t('makeDefault'), "aria-label": account.isDefault ? t('defaultBadge') : t('makeDefault'), disabled: busyAction || account.isDefault, onClick: () => { props.onSetDefault(account.account); }, children: account.isDefault ? '★' : '☆' }), _jsx("span", { style: styles.accountName, children: account.label }), account.isDefault && _jsx("span", { style: styles.badgeDefault, children: t('defaultBadge') }), account.needsLogin === true && (_jsx("span", { style: styles.badgeWarn, title: t('accountNeedsLoginHint'), children: t('accountNeedsLogin') })), usageEntry?.plan !== undefined && (_jsx("span", { style: styles.badge, children: t('usagePlan', { plan: usageEntry.plan }) })), _jsx("span", { style: styles.statusLine, children: account.expired
|
|
525
526
|
? t('accountExpired')
|
|
526
|
-
: t('accountExpires', { time: formatTime(account.expiresAt) }) }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.pushRight, disabled: busyAction, onClick: () => { props.onLogout(account.account); }, children: t('logout') })] }), _jsx(AccountUsage, { entry: usageEntry, t: t })] }, account.account));
|
|
527
|
+
: t('accountExpires', { time: formatTime(account.expiresAt) }) }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.pushRight, disabled: busyAction, onClick: () => { props.onLogout(account.account); }, children: t('logout') })] }), _jsx(AccountUsage, { entry: usageEntry, t: t, busyAction: busyAction, ...props.onCheckin === undefined ? {} : { onCheckin: () => { props.onCheckin?.(account.account); } } })] }, account.account));
|
|
527
528
|
}), status.loggedIn && (_jsxs("div", { style: styles.catalog, children: [_jsxs("div", { style: styles.catalogHeader, children: [_jsxs("button", { type: "button", className: "dsh-subscription-disclosure", style: styles.disclosure, "aria-expanded": modelsOpen, "aria-controls": `dsh-subscription-models-${status.provider}`, onClick: toggleModelsOpen, children: [_jsx("span", { style: styles.chevron, "aria-hidden": "true", children: modelsOpen ? '▾' : '▸' }), _jsx("span", { style: styles.catalogTitle, children: t('modelsTitle') })] }), _jsxs("span", { style: styles.catalogMeta, children: [t('modelsCount', { count: models.length }), ' · ', liveCatalog ? t('modelsLive') : t('modelsBuiltIn'), hiddenCount === 0 ? '' : ` ${t('modelsHiddenCount', { count: hiddenCount })}`] }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.pushRight, disabled: busyAction, onClick: props.onRefreshModels, children: busyAction ? t('refreshing') : t('refresh') }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", disabled: busyAction || !connectable, title: t('modelsAddHint'), onClick: () => { setAdding((open) => !open); }, children: t('modelsAdd') })] }), adding && (_jsxs("form", { style: styles.addRow, onSubmit: (event) => {
|
|
528
529
|
event.preventDefault();
|
|
529
530
|
const id = draft.trim();
|
|
@@ -616,6 +617,35 @@ export function SubscriptionsSection(props) {
|
|
|
616
617
|
setBusyAction(false);
|
|
617
618
|
}
|
|
618
619
|
}, [call, refresh, t]);
|
|
620
|
+
/** Claim one account's daily check-in reward, then refresh the figures. */
|
|
621
|
+
const checkin = useCallback(async (provider, account) => {
|
|
622
|
+
if (call === undefined)
|
|
623
|
+
return;
|
|
624
|
+
setBusyAction(true);
|
|
625
|
+
try {
|
|
626
|
+
const result = await call('checkin', { provider, account });
|
|
627
|
+
if (!mounted.current)
|
|
628
|
+
return;
|
|
629
|
+
setMessages((previous) => ({
|
|
630
|
+
...previous,
|
|
631
|
+
[provider]: {
|
|
632
|
+
tone: 'success',
|
|
633
|
+
text: result.already
|
|
634
|
+
? t('checkinAlready', { days: result.streakDays })
|
|
635
|
+
: t('checkinClaimed', { credit: result.credit, days: result.streakDays }),
|
|
636
|
+
},
|
|
637
|
+
}));
|
|
638
|
+
await refresh();
|
|
639
|
+
}
|
|
640
|
+
catch (caught) {
|
|
641
|
+
if (mounted.current)
|
|
642
|
+
setError(t('actionFailed', { message: messageOf(caught) }));
|
|
643
|
+
}
|
|
644
|
+
finally {
|
|
645
|
+
if (mounted.current)
|
|
646
|
+
setBusyAction(false);
|
|
647
|
+
}
|
|
648
|
+
}, [call, refresh, t]);
|
|
619
649
|
const stopPolling = useCallback(() => {
|
|
620
650
|
if (timer.current !== undefined) {
|
|
621
651
|
clearInterval(timer.current);
|
|
@@ -859,5 +889,5 @@ export function SubscriptionsSection(props) {
|
|
|
859
889
|
}, [act, call]);
|
|
860
890
|
if (call === undefined)
|
|
861
891
|
return _jsx("p", { style: styles.intro, children: t('unavailable') });
|
|
862
|
-
return (_jsxs("div", { className: "dsh-subscriptions-settings", style: styles.section, children: [_jsx("style", { children: subscriptionsCss }), _jsxs("div", { style: styles.sectionHeader, children: [_jsx("h2", { style: styles.heading, children: t('nav') }), _jsx("p", { style: styles.intro, children: t('intro') })] }), error !== undefined && _jsx("p", { className: "dsh-subscription-error", children: error }), status === undefined && _jsx("p", { style: styles.statusLine, children: t('loading') }), orderedProviders.map((provider) => (_jsx(ProviderCard, { status: provider, t: t, busyAction: busyAction, ...messages[provider.provider] === undefined ? {} : { message: messages[provider.provider] }, onLogin: () => { login(provider.provider); }, onCancel: () => { cancel(provider.provider); }, onLogout: (account) => { logout(provider.provider, account); }, onSetDefault: (account) => { setDefault(provider.provider, account); }, onRefreshToken: (account) => { refreshToken(provider.provider, account); }, onRefreshModels: () => { refreshModels(provider.provider); }, onSetModelVisible: (id, visible) => { setModelVisible(provider.provider, id, visible); }, onSetModelOrder: (ids) => { setModelOrder(provider.provider, ids); }, onAddModel: (id) => { addModel(provider.provider, id); }, onRemoveModel: (id) => { removeModel(provider.provider, id); }, onRefreshUsage: () => { refreshUsage(provider.provider); }, onReload: () => { reload(provider.provider); }, ...provider.provider !== 'auto' ? {} : { autoOn, onSetAutoOn: (enabled) => { void setAuto(enabled); } } }, provider.provider)))] }));
|
|
892
|
+
return (_jsxs("div", { className: "dsh-subscriptions-settings", style: styles.section, children: [_jsx("style", { children: subscriptionsCss }), _jsxs("div", { style: styles.sectionHeader, children: [_jsx("h2", { style: styles.heading, children: t('nav') }), _jsx("p", { style: styles.intro, children: t('intro') })] }), error !== undefined && _jsx("p", { className: "dsh-subscription-error", children: error }), status === undefined && _jsx("p", { style: styles.statusLine, children: t('loading') }), orderedProviders.map((provider) => (_jsx(ProviderCard, { status: provider, t: t, busyAction: busyAction, ...messages[provider.provider] === undefined ? {} : { message: messages[provider.provider] }, onLogin: () => { login(provider.provider); }, onCancel: () => { cancel(provider.provider); }, onLogout: (account) => { logout(provider.provider, account); }, onSetDefault: (account) => { setDefault(provider.provider, account); }, onRefreshToken: (account) => { refreshToken(provider.provider, account); }, onRefreshModels: () => { refreshModels(provider.provider); }, onSetModelVisible: (id, visible) => { setModelVisible(provider.provider, id, visible); }, onSetModelOrder: (ids) => { setModelOrder(provider.provider, ids); }, onAddModel: (id) => { addModel(provider.provider, id); }, onRemoveModel: (id) => { removeModel(provider.provider, id); }, onRefreshUsage: () => { refreshUsage(provider.provider); }, onReload: () => { reload(provider.provider); }, ...provider.provider !== 'auto' ? {} : { autoOn, onSetAutoOn: (enabled) => { void setAuto(enabled); } }, ...provider.provider !== 'workbuddy' ? {} : { onCheckin: (account) => { void checkin(provider.provider, account); } } }, provider.provider)))] }));
|
|
863
893
|
}
|
package/lib/client/locales.d.ts
CHANGED
|
@@ -61,6 +61,12 @@ export declare const en: {
|
|
|
61
61
|
readonly modelsAutoChainOff: "Take out of Auto";
|
|
62
62
|
readonly modelsAutoChainEmpty: "No model is enabled, so Auto has nothing to try.";
|
|
63
63
|
readonly autoSwitchLabel: "Offer Auto in the model picker";
|
|
64
|
+
readonly checkinTitle: "Daily check-in";
|
|
65
|
+
readonly checkinPending: "not checked in today";
|
|
66
|
+
readonly checkinDone: "checked in · {days}-day streak";
|
|
67
|
+
readonly checkinClaim: "Check in";
|
|
68
|
+
readonly checkinClaimed: "Checked in, +{credit} credits (streak {days}d)";
|
|
69
|
+
readonly checkinAlready: "Already checked in today (streak {days}d)";
|
|
64
70
|
readonly modelsPinned: "pinned";
|
|
65
71
|
readonly usageTitle: "Plan and usage";
|
|
66
72
|
readonly usagePlan: "Plan: {plan}";
|
|
@@ -159,6 +165,12 @@ export declare const zh: {
|
|
|
159
165
|
modelsAutoChainOff: string;
|
|
160
166
|
modelsAutoChainEmpty: string;
|
|
161
167
|
autoSwitchLabel: string;
|
|
168
|
+
checkinTitle: string;
|
|
169
|
+
checkinPending: string;
|
|
170
|
+
checkinDone: string;
|
|
171
|
+
checkinClaim: string;
|
|
172
|
+
checkinClaimed: string;
|
|
173
|
+
checkinAlready: string;
|
|
162
174
|
modelsPinned: string;
|
|
163
175
|
usageTitle: string;
|
|
164
176
|
usagePlan: string;
|
package/lib/client/locales.js
CHANGED
|
@@ -61,6 +61,12 @@ export const en = {
|
|
|
61
61
|
modelsAutoChainOff: 'Take out of Auto',
|
|
62
62
|
modelsAutoChainEmpty: 'No model is enabled, so Auto has nothing to try.',
|
|
63
63
|
autoSwitchLabel: 'Offer Auto in the model picker',
|
|
64
|
+
checkinTitle: 'Daily check-in',
|
|
65
|
+
checkinPending: 'not checked in today',
|
|
66
|
+
checkinDone: 'checked in · {days}-day streak',
|
|
67
|
+
checkinClaim: 'Check in',
|
|
68
|
+
checkinClaimed: 'Checked in, +{credit} credits (streak {days}d)',
|
|
69
|
+
checkinAlready: 'Already checked in today (streak {days}d)',
|
|
64
70
|
modelsPinned: 'pinned',
|
|
65
71
|
usageTitle: 'Plan and usage',
|
|
66
72
|
usagePlan: 'Plan: {plan}',
|
|
@@ -157,6 +163,12 @@ export const zh = {
|
|
|
157
163
|
modelsAutoChainOff: '从自动中移除',
|
|
158
164
|
modelsAutoChainEmpty: '没有启用任何模型,自动没有可尝试的模型。',
|
|
159
165
|
autoSwitchLabel: '在模型选择器中提供 Auto',
|
|
166
|
+
checkinTitle: '每日签到',
|
|
167
|
+
checkinPending: '今日未签到',
|
|
168
|
+
checkinDone: '已签到 · 连签 {days} 天',
|
|
169
|
+
checkinClaim: '签到',
|
|
170
|
+
checkinClaimed: '签到成功,+{credit} 积分(连签 {days} 天)',
|
|
171
|
+
checkinAlready: '今日已签到(连签 {days} 天)',
|
|
160
172
|
modelsPinned: '固定',
|
|
161
173
|
usageTitle: '套餐与额度',
|
|
162
174
|
usagePlan: '套餐:{plan}',
|
package/lib/client.js
CHANGED
|
@@ -92,6 +92,12 @@ const en = {
|
|
|
92
92
|
modelsAutoChainOff: "Take out of Auto",
|
|
93
93
|
modelsAutoChainEmpty: "No model is enabled, so Auto has nothing to try.",
|
|
94
94
|
autoSwitchLabel: "Offer Auto in the model picker",
|
|
95
|
+
checkinTitle: "Daily check-in",
|
|
96
|
+
checkinPending: "not checked in today",
|
|
97
|
+
checkinDone: "checked in · {days}-day streak",
|
|
98
|
+
checkinClaim: "Check in",
|
|
99
|
+
checkinClaimed: "Checked in, +{credit} credits (streak {days}d)",
|
|
100
|
+
checkinAlready: "Already checked in today (streak {days}d)",
|
|
95
101
|
modelsPinned: "pinned",
|
|
96
102
|
usageTitle: "Plan and usage",
|
|
97
103
|
usagePlan: "Plan: {plan}",
|
|
@@ -188,6 +194,12 @@ const zh = {
|
|
|
188
194
|
modelsAutoChainOff: "从自动中移除",
|
|
189
195
|
modelsAutoChainEmpty: "没有启用任何模型,自动没有可尝试的模型。",
|
|
190
196
|
autoSwitchLabel: "在模型选择器中提供 Auto",
|
|
197
|
+
checkinTitle: "每日签到",
|
|
198
|
+
checkinPending: "今日未签到",
|
|
199
|
+
checkinDone: "已签到 · 连签 {days} 天",
|
|
200
|
+
checkinClaim: "签到",
|
|
201
|
+
checkinClaimed: "签到成功,+{credit} 积分(连签 {days} 天)",
|
|
202
|
+
checkinAlready: "今日已签到(连签 {days} 天)",
|
|
191
203
|
modelsPinned: "固定",
|
|
192
204
|
usageTitle: "套餐与额度",
|
|
193
205
|
usagePlan: "套餐:{plan}",
|
|
@@ -1327,38 +1339,60 @@ function windowAmount$1(window$1) {
|
|
|
1327
1339
|
if (window$1.used !== void 0 && window$1.limit !== void 0) return `${String(window$1.used)}/${String(window$1.limit)}`;
|
|
1328
1340
|
return "—";
|
|
1329
1341
|
}
|
|
1330
|
-
/** One account's metered windows, rendered under its account row. */
|
|
1342
|
+
/** One account's metered windows and check-in, rendered under its account row. */
|
|
1331
1343
|
function AccountUsage(props) {
|
|
1332
|
-
const { entry, t } = props;
|
|
1344
|
+
const { entry, t, busyAction, onCheckin } = props;
|
|
1333
1345
|
if (entry === void 0) return null;
|
|
1346
|
+
const checkin = entry.checkin;
|
|
1334
1347
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1335
1348
|
style: styles.usageInline,
|
|
1336
|
-
children: [
|
|
1337
|
-
|
|
1338
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1349
|
+
children: [
|
|
1350
|
+
checkin?.active === true && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1339
1351
|
style: styles.usageLine,
|
|
1340
1352
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1341
1353
|
style: styles.modelName,
|
|
1342
1354
|
children: [
|
|
1343
|
-
|
|
1355
|
+
t("checkinTitle"),
|
|
1344
1356
|
" · ",
|
|
1345
|
-
|
|
1357
|
+
checkin.todayCheckedIn ? t("checkinDone", { days: checkin.streakDays }) : t("checkinPending")
|
|
1346
1358
|
]
|
|
1347
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
1348
|
-
|
|
1349
|
-
|
|
1359
|
+
}), !checkin.todayCheckedIn && onCheckin !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1360
|
+
type: "button",
|
|
1361
|
+
className: "dsh-subscription-quiet",
|
|
1362
|
+
style: styles.pushRight,
|
|
1363
|
+
disabled: busyAction,
|
|
1364
|
+
onClick: onCheckin,
|
|
1365
|
+
children: t("checkinClaim")
|
|
1350
1366
|
})]
|
|
1351
|
-
}),
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1367
|
+
}),
|
|
1368
|
+
entry.windows.map((window$1) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1369
|
+
style: styles.usageRow,
|
|
1370
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1371
|
+
style: styles.usageLine,
|
|
1372
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1373
|
+
style: styles.modelName,
|
|
1374
|
+
children: [
|
|
1375
|
+
windowLabel(t, window$1),
|
|
1376
|
+
" · ",
|
|
1377
|
+
windowAmount$1(window$1)
|
|
1378
|
+
]
|
|
1379
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1380
|
+
style: styles.modelMeta,
|
|
1381
|
+
children: window$1.resetsAt === void 0 ? "" : formatReset$1(window$1.resetsAt, t)
|
|
1382
|
+
})]
|
|
1383
|
+
}), window$1.usedPercent !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1384
|
+
className: "dsh-subscription-meter",
|
|
1385
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1386
|
+
style: { width: `${String(Math.min(100, Math.max(0, window$1.usedPercent)))}%` },
|
|
1387
|
+
"data-exhausted": window$1.usedPercent >= 90 ? "true" : "false"
|
|
1388
|
+
})
|
|
1389
|
+
})]
|
|
1390
|
+
}, `${entry.account}-${window$1.label}`)),
|
|
1391
|
+
entry.windows.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1392
|
+
style: styles.statusLine,
|
|
1393
|
+
children: t("usageNone")
|
|
1394
|
+
})
|
|
1395
|
+
]
|
|
1362
1396
|
});
|
|
1363
1397
|
}
|
|
1364
1398
|
/** The pending-login block: a device code, or a link to the sign-in page. */
|
|
@@ -1703,7 +1737,11 @@ function ProviderCard(props) {
|
|
|
1703
1737
|
]
|
|
1704
1738
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountUsage, {
|
|
1705
1739
|
entry: usageEntry,
|
|
1706
|
-
t
|
|
1740
|
+
t,
|
|
1741
|
+
busyAction,
|
|
1742
|
+
...props.onCheckin === void 0 ? {} : { onCheckin: () => {
|
|
1743
|
+
props.onCheckin?.(account.account);
|
|
1744
|
+
} }
|
|
1707
1745
|
})]
|
|
1708
1746
|
}, account.account);
|
|
1709
1747
|
}),
|
|
@@ -2112,6 +2150,37 @@ function SubscriptionsSection(props) {
|
|
|
2112
2150
|
refresh,
|
|
2113
2151
|
t
|
|
2114
2152
|
]);
|
|
2153
|
+
/** Claim one account's daily check-in reward, then refresh the figures. */
|
|
2154
|
+
const checkin = (0, react.useCallback)(async (provider, account) => {
|
|
2155
|
+
if (call === void 0) return;
|
|
2156
|
+
setBusyAction(true);
|
|
2157
|
+
try {
|
|
2158
|
+
const result = await call("checkin", {
|
|
2159
|
+
provider,
|
|
2160
|
+
account
|
|
2161
|
+
});
|
|
2162
|
+
if (!mounted.current) return;
|
|
2163
|
+
setMessages((previous) => ({
|
|
2164
|
+
...previous,
|
|
2165
|
+
[provider]: {
|
|
2166
|
+
tone: "success",
|
|
2167
|
+
text: result.already ? t("checkinAlready", { days: result.streakDays }) : t("checkinClaimed", {
|
|
2168
|
+
credit: result.credit,
|
|
2169
|
+
days: result.streakDays
|
|
2170
|
+
})
|
|
2171
|
+
}
|
|
2172
|
+
}));
|
|
2173
|
+
await refresh();
|
|
2174
|
+
} catch (caught) {
|
|
2175
|
+
if (mounted.current) setError(t("actionFailed", { message: messageOf(caught) }));
|
|
2176
|
+
} finally {
|
|
2177
|
+
if (mounted.current) setBusyAction(false);
|
|
2178
|
+
}
|
|
2179
|
+
}, [
|
|
2180
|
+
call,
|
|
2181
|
+
refresh,
|
|
2182
|
+
t
|
|
2183
|
+
]);
|
|
2115
2184
|
const stopPolling = (0, react.useCallback)(() => {
|
|
2116
2185
|
if (timer.current !== void 0) {
|
|
2117
2186
|
clearInterval(timer.current);
|
|
@@ -2431,7 +2500,10 @@ function SubscriptionsSection(props) {
|
|
|
2431
2500
|
onSetAutoOn: (enabled) => {
|
|
2432
2501
|
setAuto(enabled);
|
|
2433
2502
|
}
|
|
2434
|
-
}
|
|
2503
|
+
},
|
|
2504
|
+
...provider.provider !== "workbuddy" ? {} : { onCheckin: (account) => {
|
|
2505
|
+
checkin(provider.provider, account);
|
|
2506
|
+
} }
|
|
2435
2507
|
}, provider.provider))
|
|
2436
2508
|
]
|
|
2437
2509
|
});
|