dsh-plugin-subscription-oauth 0.15.0 → 0.16.1
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/registry.d.ts +28 -0
- package/lib/auth/registry.js +7 -0
- package/lib/auth/rpc.js +6 -0
- package/lib/auth/workbuddy-flow.d.ts +24 -1
- package/lib/auth/workbuddy-flow.js +134 -19
- package/lib/client/SubscriptionsSection.js +40 -5
- package/lib/client/locales.d.ts +8 -0
- package/lib/client/locales.js +8 -0
- package/lib/client.js +64 -14
- package/lib/client.js.map +1 -1
- package/lib/protocol.d.ts +28 -1
- package/lib/protocol.js +1 -0
- package/lib/providers/workbuddy-provider.js +46 -1
- package/lib/providers/workbuddy.js +2 -19
- package/package.json +1 -1
package/lib/auth/registry.d.ts
CHANGED
|
@@ -38,6 +38,20 @@ export interface ProviderRoute {
|
|
|
38
38
|
streakDays: number;
|
|
39
39
|
already: boolean;
|
|
40
40
|
}>;
|
|
41
|
+
/** Read growth tasks and optionally auto-claim, when the route has them. */
|
|
42
|
+
tasks?(account: string, claim: boolean): Promise<{
|
|
43
|
+
tasks: {
|
|
44
|
+
taskCode: string;
|
|
45
|
+
title: string;
|
|
46
|
+
credit: number;
|
|
47
|
+
target: number;
|
|
48
|
+
current: number;
|
|
49
|
+
state: 'pending' | 'claimable' | 'claimed' | 'locked';
|
|
50
|
+
}[];
|
|
51
|
+
claimed: number;
|
|
52
|
+
claimable: number;
|
|
53
|
+
creditEarned: number;
|
|
54
|
+
}>;
|
|
41
55
|
/** Fast and image variants for one model, when the route's discovery reported them. */
|
|
42
56
|
modelVariants?(id: string): Promise<ModelVariants | undefined> | ModelVariants | undefined;
|
|
43
57
|
}
|
|
@@ -113,4 +127,18 @@ export declare class SubscriptionRegistry {
|
|
|
113
127
|
streakDays: number;
|
|
114
128
|
already: boolean;
|
|
115
129
|
}>;
|
|
130
|
+
/** Read one account's growth tasks and optionally auto-claim the claimable rewards. */
|
|
131
|
+
tasks(provider: string, account: string, claim: boolean): Promise<{
|
|
132
|
+
tasks: {
|
|
133
|
+
taskCode: string;
|
|
134
|
+
title: string;
|
|
135
|
+
credit: number;
|
|
136
|
+
target: number;
|
|
137
|
+
current: number;
|
|
138
|
+
state: 'pending' | 'claimable' | 'claimed' | 'locked';
|
|
139
|
+
}[];
|
|
140
|
+
claimed: number;
|
|
141
|
+
claimable: number;
|
|
142
|
+
creditEarned: number;
|
|
143
|
+
}>;
|
|
116
144
|
}
|
package/lib/auth/registry.js
CHANGED
|
@@ -168,4 +168,11 @@ export class SubscriptionRegistry {
|
|
|
168
168
|
throw new ProviderError(`${provider} has no check-in`, BAD_REQUEST);
|
|
169
169
|
return route.checkin(account);
|
|
170
170
|
}
|
|
171
|
+
/** Read one account's growth tasks and optionally auto-claim the claimable rewards. */
|
|
172
|
+
async tasks(provider, account, claim) {
|
|
173
|
+
const route = this.route(provider);
|
|
174
|
+
if (route.tasks === undefined)
|
|
175
|
+
throw new ProviderError(`${provider} has no tasks`, BAD_REQUEST);
|
|
176
|
+
return route.tasks(account, claim);
|
|
177
|
+
}
|
|
171
178
|
}
|
package/lib/auth/rpc.js
CHANGED
|
@@ -169,6 +169,12 @@ async function runEndpoint(registry, endpoint, payload, balance) {
|
|
|
169
169
|
throw new ProviderError('checkin requires an account', BAD_REQUEST);
|
|
170
170
|
return registry.checkin(requiredProvider(payload, endpoint), account);
|
|
171
171
|
}
|
|
172
|
+
case 'tasks': {
|
|
173
|
+
const account = stringField(payload, 'account');
|
|
174
|
+
if (account === undefined)
|
|
175
|
+
throw new ProviderError('tasks requires an account', BAD_REQUEST);
|
|
176
|
+
return registry.tasks(requiredProvider(payload, endpoint), account, booleanField(payload, 'claim') === true);
|
|
177
|
+
}
|
|
172
178
|
case 'deepseek-balance': {
|
|
173
179
|
// The DeepSeek account's balance, read on the node half through the host's own
|
|
174
180
|
// account service. The browser cannot call that service directly (it sits behind
|
|
@@ -85,7 +85,7 @@ export declare function finishWorkBuddySignIn(start: WorkBuddySignInStart, fetch
|
|
|
85
85
|
}>;
|
|
86
86
|
/** The domain a token names, else the API host. */
|
|
87
87
|
export declare function workbuddyDomain(token: Pick<WorkBuddyTokenSet, 'domain'>): string;
|
|
88
|
-
/** The headers WorkBuddy's
|
|
88
|
+
/** The headers WorkBuddy's chat gateway accepts: its own CLI's, nothing more. */
|
|
89
89
|
export declare function workbuddyHeaders(token: WorkBuddyTokenSet): Record<string, string>;
|
|
90
90
|
/** Whether a token is close enough to its end that a request should refresh it. */
|
|
91
91
|
export declare function workbuddyStale(token: Pick<WorkBuddyTokenSet, 'expiresAt'>): boolean;
|
|
@@ -108,6 +108,28 @@ export interface WorkBuddyCheckin {
|
|
|
108
108
|
streakDays: number;
|
|
109
109
|
todayCredit: number;
|
|
110
110
|
}
|
|
111
|
+
/** One growth task, as the activity endpoint frames it. */
|
|
112
|
+
export interface WorkBuddyTask {
|
|
113
|
+
taskCode: string;
|
|
114
|
+
title: string;
|
|
115
|
+
credit: number;
|
|
116
|
+
energy: number;
|
|
117
|
+
target: number;
|
|
118
|
+
current: number;
|
|
119
|
+
claimable: boolean;
|
|
120
|
+
claimed: boolean;
|
|
121
|
+
locked: boolean;
|
|
122
|
+
acceptStatus: string;
|
|
123
|
+
}
|
|
124
|
+
/** Read the account's growth tasks, parsed the way the pool plugin parses them. */
|
|
125
|
+
export declare function listWorkBuddyTasks(token: WorkBuddyTokenSet, fetchFn: typeof fetch): Promise<WorkBuddyTask[]>;
|
|
126
|
+
/** Accept (enrol in) tasks by code; replaying an accepted task is a safe no-op. */
|
|
127
|
+
export declare function acceptWorkBuddyTasks(token: WorkBuddyTokenSet, taskCodes: readonly string[], fetchFn: typeof fetch): Promise<void>;
|
|
128
|
+
/** Claim one task's reward; already-claimed answers zero and counts as success. */
|
|
129
|
+
export declare function claimWorkBuddyTask(token: WorkBuddyTokenSet, taskCode: string, fetchFn: typeof fetch): Promise<{
|
|
130
|
+
credit: number;
|
|
131
|
+
energy: number;
|
|
132
|
+
}>;
|
|
111
133
|
/** Read the account's daily check-in state, without changing anything. */
|
|
112
134
|
export declare function readWorkBuddyCheckin(token: WorkBuddyTokenSet, fetchFn: typeof fetch): Promise<WorkBuddyCheckin>;
|
|
113
135
|
/** Claim today's check-in reward; resolves with the credit granted and the streak. */
|
|
@@ -118,6 +140,7 @@ export declare function claimWorkBuddyCheckin(token: WorkBuddyTokenSet, fetchFn:
|
|
|
118
140
|
/** The account's precise remaining credits, summed across its live packages. */
|
|
119
141
|
export declare function readWorkBuddyCredits(token: WorkBuddyTokenSet, fetchFn: typeof fetch): Promise<{
|
|
120
142
|
remaining: number;
|
|
143
|
+
total: number;
|
|
121
144
|
nearestExpiryMs?: number;
|
|
122
145
|
} | null>;
|
|
123
146
|
/** Read the account's plan and metered credits for the Settings card. */
|
|
@@ -214,26 +214,21 @@ async function workbuddyEnterprise(token, who, fetchFn) {
|
|
|
214
214
|
const pick = same.length === 1 ? same[0] : last.length === 1 ? last[0] : mine.length === 1 ? mine[0] : undefined;
|
|
215
215
|
return pick && typeof pick.enterpriseId === 'string' ? pick.enterpriseId.trim() : '';
|
|
216
216
|
}
|
|
217
|
-
/** The headers WorkBuddy's
|
|
217
|
+
/** The headers WorkBuddy's chat gateway accepts: its own CLI's, nothing more. */
|
|
218
218
|
export function workbuddyHeaders(token) {
|
|
219
219
|
const headers = {
|
|
220
|
+
'Accept': 'application/json, text/plain, */*',
|
|
221
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
222
|
+
'Origin': 'https://copilot.tencent.com',
|
|
223
|
+
'Referer': 'https://copilot.tencent.com/',
|
|
224
|
+
'User-Agent': 'CLI/2.63.2 CodeBuddy/2.63.2',
|
|
225
|
+
'Content-Type': 'application/json',
|
|
220
226
|
'Authorization': `Bearer ${token.accessToken}`,
|
|
221
|
-
'X-User-Id': token.uid
|
|
227
|
+
...token.uid === undefined || token.uid === '' ? { 'X-No-User-Id': '1' } : { 'X-User-Id': token.uid },
|
|
228
|
+
...token.enterpriseId === undefined || token.enterpriseId === '' ? { 'X-No-Enterprise-Id': '1' } : { 'X-Enterprise-Id': token.enterpriseId },
|
|
222
229
|
'X-Domain': workbuddyDomain(token),
|
|
223
230
|
'X-Product': 'SaaS',
|
|
224
|
-
'X-IDE-Type': 'WorkBuddy',
|
|
225
|
-
'User-Agent': `WorkBuddy/${WORKBUDDY_UA_VERSION}`,
|
|
226
|
-
'X-Requested-With': 'XMLHttpRequest',
|
|
227
|
-
'X-Agent-Intent': 'craft',
|
|
228
|
-
'X-Agent-Purpose': 'conversation',
|
|
229
|
-
'X-IDE-Name': 'WorkBuddy',
|
|
230
|
-
'X-IDE-Version': WORKBUDDY_UA_VERSION,
|
|
231
231
|
};
|
|
232
|
-
const enterprise = typeof token.enterpriseId === 'string' ? token.enterpriseId.trim() : '';
|
|
233
|
-
if (enterprise !== '') {
|
|
234
|
-
headers['X-Enterprise-Id'] = enterprise;
|
|
235
|
-
headers['X-Tenant-Id'] = enterprise;
|
|
236
|
-
}
|
|
237
232
|
return headers;
|
|
238
233
|
}
|
|
239
234
|
/** Whether a token is close enough to its end that a request should refresh it. */
|
|
@@ -263,6 +258,118 @@ function workbuddyEnterpriseHeaders(token) {
|
|
|
263
258
|
const enterprise = typeof token.enterpriseId === 'string' ? token.enterpriseId.trim() : '';
|
|
264
259
|
return enterprise === '' ? {} : { 'X-Enterprise-Id': enterprise, 'X-Tenant-Id': enterprise };
|
|
265
260
|
}
|
|
261
|
+
/** The chat request headers: the account's, plus the CLI conventions. */
|
|
262
|
+
function chatHeaders(token) {
|
|
263
|
+
return {
|
|
264
|
+
'Accept': 'application/json, text/plain, */*',
|
|
265
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
266
|
+
'Origin': 'https://copilot.tencent.com',
|
|
267
|
+
'Referer': 'https://copilot.tencent.com/',
|
|
268
|
+
'User-Agent': 'CLI/2.63.2 CodeBuddy/2.63.2',
|
|
269
|
+
'Content-Type': 'application/json',
|
|
270
|
+
'Authorization': `Bearer ${token.accessToken}`,
|
|
271
|
+
...token.uid === undefined || token.uid === '' ? { 'X-No-User-Id': '1' } : { 'X-User-Id': token.uid },
|
|
272
|
+
...token.enterpriseId === undefined || token.enterpriseId === '' ? { 'X-No-Enterprise-Id': '1' } : { 'X-Enterprise-Id': token.enterpriseId },
|
|
273
|
+
'X-Product': 'SaaS',
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
/** Ask the chat host one question, returning the decoded envelope's data. */
|
|
277
|
+
async function chatCall(token, method, path, body, fetchFn) {
|
|
278
|
+
const response = await fetchFn(`https://copilot.tencent.com${path}`, {
|
|
279
|
+
method,
|
|
280
|
+
headers: chatHeaders(token),
|
|
281
|
+
...body === undefined ? {} : { body: JSON.stringify(body) },
|
|
282
|
+
signal: AbortSignal.timeout(20_000),
|
|
283
|
+
});
|
|
284
|
+
let env = {};
|
|
285
|
+
try {
|
|
286
|
+
env = JSON.parse(await response.text());
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
env = {};
|
|
290
|
+
}
|
|
291
|
+
const code = Number.parseInt(String(env.code ?? ''), 10) || 0;
|
|
292
|
+
if (!response.ok || code !== 0) {
|
|
293
|
+
throw new Error(`WorkBuddy ${path}: ${String(env.msg ?? `error ${code}`)}`);
|
|
294
|
+
}
|
|
295
|
+
return isRecord(env.data) ? env.data : null;
|
|
296
|
+
}
|
|
297
|
+
/** Read the account's growth tasks, parsed the way the pool plugin parses them. */
|
|
298
|
+
export async function listWorkBuddyTasks(token, fetchFn) {
|
|
299
|
+
const data = await chatCall(token, 'GET', '/v2/activity/growth/tasks', undefined, fetchFn);
|
|
300
|
+
if (data === null)
|
|
301
|
+
return [];
|
|
302
|
+
const raw = Array.isArray(data.tasks) ? data.tasks.filter(isRecord) : [];
|
|
303
|
+
const out = [];
|
|
304
|
+
for (const entry of raw) {
|
|
305
|
+
const taskCode = typeof entry.task_code === 'string' ? entry.task_code : '';
|
|
306
|
+
if (taskCode === '')
|
|
307
|
+
continue;
|
|
308
|
+
const num = (key) => capacity(entry[key]);
|
|
309
|
+
let current = num('current');
|
|
310
|
+
let target = num('target');
|
|
311
|
+
if (isRecord(entry.progress)) {
|
|
312
|
+
const nestedCurrent = capacity(entry.progress.current);
|
|
313
|
+
const nestedTarget = capacity(entry.progress.target);
|
|
314
|
+
if (nestedTarget > 0 || nestedCurrent > 0) {
|
|
315
|
+
current = nestedCurrent;
|
|
316
|
+
target = nestedTarget;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
const acceptStatus = typeof entry.accept_status === 'string' ? entry.accept_status : '';
|
|
320
|
+
const claimed = acceptStatus === 'claimed';
|
|
321
|
+
out.push({
|
|
322
|
+
taskCode,
|
|
323
|
+
title: typeof entry.title === 'string' && entry.title !== '' ? entry.title : taskCode,
|
|
324
|
+
credit: num('reward_credit'),
|
|
325
|
+
energy: num('reward_energy'),
|
|
326
|
+
target,
|
|
327
|
+
current,
|
|
328
|
+
claimable: !claimed && target > 0 && current >= target,
|
|
329
|
+
claimed,
|
|
330
|
+
locked: entry.locked === true,
|
|
331
|
+
acceptStatus,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
return out;
|
|
335
|
+
}
|
|
336
|
+
/** Accept (enrol in) tasks by code; replaying an accepted task is a safe no-op. */
|
|
337
|
+
export async function acceptWorkBuddyTasks(token, taskCodes, fetchFn) {
|
|
338
|
+
if (taskCodes.length === 0)
|
|
339
|
+
return;
|
|
340
|
+
await chatCall(token, 'POST', '/v2/activity/growth/tasks/accept', { task_codes: [...taskCodes] }, fetchFn);
|
|
341
|
+
}
|
|
342
|
+
/** Claim one task's reward; already-claimed answers zero and counts as success. */
|
|
343
|
+
export async function claimWorkBuddyTask(token, taskCode, fetchFn) {
|
|
344
|
+
const headers = {
|
|
345
|
+
...workbuddyHeaders(token),
|
|
346
|
+
'Accept': 'application/json, text/plain, */*',
|
|
347
|
+
'Origin': 'https://www.workbuddy.cn',
|
|
348
|
+
'Referer': 'https://www.workbuddy.cn/profile/growth-center',
|
|
349
|
+
'x-client-platform': 'web',
|
|
350
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
|
|
351
|
+
};
|
|
352
|
+
const response = await fetchFn(`https://www.workbuddy.cn/activity/growth/tasks/${encodeURIComponent(taskCode)}/claim`, {
|
|
353
|
+
method: 'POST',
|
|
354
|
+
headers,
|
|
355
|
+
signal: AbortSignal.timeout(20_000),
|
|
356
|
+
});
|
|
357
|
+
let env = {};
|
|
358
|
+
try {
|
|
359
|
+
env = JSON.parse(await response.text());
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
env = {};
|
|
363
|
+
}
|
|
364
|
+
const code = Number.parseInt(String(env.code ?? ''), 10) || 0;
|
|
365
|
+
if (!response.ok || code !== 0) {
|
|
366
|
+
throw new Error(`WorkBuddy task ${taskCode}: ${String(env.msg ?? `error ${code}`)}`);
|
|
367
|
+
}
|
|
368
|
+
const data = isRecord(env.data) ? env.data : {};
|
|
369
|
+
if (data.already_claimed === true)
|
|
370
|
+
return { credit: 0, energy: 0 };
|
|
371
|
+
return { credit: capacity(data.credit), energy: capacity(data.energy) };
|
|
372
|
+
}
|
|
266
373
|
/** A capacity the billing API sends, as a string or number; empty is 0. */
|
|
267
374
|
function capacity(value) {
|
|
268
375
|
if (value === null || value === undefined || value === '')
|
|
@@ -361,12 +468,14 @@ export async function readWorkBuddyCredits(token, fetchFn) {
|
|
|
361
468
|
return undefined;
|
|
362
469
|
};
|
|
363
470
|
let remaining = 0;
|
|
471
|
+
let totalSize = 0;
|
|
364
472
|
let nearestExpiryMs;
|
|
365
473
|
let livePackages = 0;
|
|
366
474
|
for (const account of accounts) {
|
|
367
475
|
const num = (key) => capacity(account[key]);
|
|
368
476
|
const monthly = num('CapacityType') === 4;
|
|
369
477
|
const remain = monthly ? num('CycleCapacityRemain') : num('CapacityRemain');
|
|
478
|
+
const size = monthly ? num('CycleCapacitySize') : num('CapacitySize');
|
|
370
479
|
const capped = remain < 0 ? 0 : remain;
|
|
371
480
|
const cycleEndMs = parseDate(account.CycleEndTime);
|
|
372
481
|
const expiresAtMs = monthly ? undefined : parseDate(account.ExpiredTime) ?? cycleEndMs;
|
|
@@ -374,6 +483,7 @@ export async function readWorkBuddyCredits(token, fetchFn) {
|
|
|
374
483
|
continue;
|
|
375
484
|
livePackages += 1;
|
|
376
485
|
remaining += capped;
|
|
486
|
+
totalSize += size > 0 ? size : capped;
|
|
377
487
|
if (expiresAtMs !== undefined && (nearestExpiryMs === undefined || expiresAtMs < nearestExpiryMs)) {
|
|
378
488
|
nearestExpiryMs = expiresAtMs;
|
|
379
489
|
}
|
|
@@ -382,7 +492,7 @@ export async function readWorkBuddyCredits(token, fetchFn) {
|
|
|
382
492
|
// account, say — so the caller falls through to its own meter.
|
|
383
493
|
if (livePackages === 0)
|
|
384
494
|
return null;
|
|
385
|
-
return { remaining, ...nearestExpiryMs === undefined ? {} : { nearestExpiryMs } };
|
|
495
|
+
return { remaining, total: totalSize, ...nearestExpiryMs === undefined ? {} : { nearestExpiryMs } };
|
|
386
496
|
}
|
|
387
497
|
/** The account's credits from its resource summary: the plan's used against granted. */
|
|
388
498
|
function usageOfSummary(summary, plan) {
|
|
@@ -430,10 +540,15 @@ export async function readWorkBuddyUsage(token, plan, fetchFn, spend) {
|
|
|
430
540
|
const credits = await readWorkBuddyCredits(token, fetchFn);
|
|
431
541
|
if (credits !== null) {
|
|
432
542
|
const window = { label: 'Credits', used: spend?.spent ?? 0 };
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
window.
|
|
543
|
+
// The bar reads spent against the total the packages grant, not remaining-plus-spent,
|
|
544
|
+
// so a fresh cycle's spend shows small against its full grant.
|
|
545
|
+
if (credits.total > 0) {
|
|
546
|
+
window.limit = credits.total;
|
|
547
|
+
window.usedPercent = (100 * (spend?.spent ?? 0)) / credits.total;
|
|
548
|
+
}
|
|
549
|
+
else if ((spend?.spent ?? 0) > 0) {
|
|
550
|
+
window.limit = spend?.spent ?? 0;
|
|
551
|
+
window.usedPercent = 100;
|
|
437
552
|
}
|
|
438
553
|
if (credits.nearestExpiryMs !== undefined)
|
|
439
554
|
window.resetsAt = credits.nearestExpiryMs;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* Subscriptions settings page.
|
|
4
4
|
*
|
|
@@ -315,11 +315,15 @@ function windowAmount(window) {
|
|
|
315
315
|
}
|
|
316
316
|
/** One account's metered windows and check-in, rendered under its account row. */
|
|
317
317
|
function AccountUsage(props) {
|
|
318
|
-
const { entry, t, busyAction, onCheckin } = props;
|
|
318
|
+
const { entry, t, busyAction, onCheckin, onTasks } = props;
|
|
319
319
|
if (entry === undefined)
|
|
320
320
|
return null;
|
|
321
321
|
const checkin = entry.checkin;
|
|
322
|
-
return (_jsxs("div", { style: styles.usageInline, children: [
|
|
322
|
+
return (_jsxs("div", { style: styles.usageInline, children: [_jsxs("div", { style: styles.usageLine, children: [_jsx("span", { style: styles.modelName, children: checkin?.active === true
|
|
323
|
+
? checkin.todayCheckedIn
|
|
324
|
+
? t('checkinDone', { days: checkin.streakDays })
|
|
325
|
+
: t('checkinPending')
|
|
326
|
+
: '' }), _jsxs("span", { style: styles.pushRight, children: [onTasks !== undefined && (_jsx("button", { type: "button", className: "dsh-subscription-quiet", disabled: busyAction, onClick: onTasks, children: t('tasksClaim') })), checkin?.active === true && !checkin.todayCheckedIn && onCheckin !== undefined && (_jsx("button", { type: "button", className: "dsh-subscription-quiet", 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') }))] }));
|
|
323
327
|
}
|
|
324
328
|
/** The plan-and-usage block for the whole route. */
|
|
325
329
|
function UsageBlock(props) {
|
|
@@ -524,7 +528,7 @@ function ProviderCard(props) {
|
|
|
524
528
|
const usageEntry = (status.usage ?? []).find((entry) => entry.account === account.account);
|
|
525
529
|
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
|
|
526
530
|
? t('accountExpired')
|
|
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));
|
|
531
|
+
: 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); } }, ...props.onTasks === undefined ? {} : { onTasks: () => { props.onTasks?.(account.account); } } })] }, account.account));
|
|
528
532
|
}), 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) => {
|
|
529
533
|
event.preventDefault();
|
|
530
534
|
const id = draft.trim();
|
|
@@ -646,6 +650,37 @@ export function SubscriptionsSection(props) {
|
|
|
646
650
|
setBusyAction(false);
|
|
647
651
|
}
|
|
648
652
|
}, [call, refresh, t]);
|
|
653
|
+
/** Auto-claim one account's growth-task rewards, then refresh the figures. */
|
|
654
|
+
const runTasks = useCallback(async (provider, account) => {
|
|
655
|
+
if (call === undefined)
|
|
656
|
+
return;
|
|
657
|
+
setBusyAction(true);
|
|
658
|
+
try {
|
|
659
|
+
const result = await call('tasks', { provider, account, claim: true });
|
|
660
|
+
if (!mounted.current)
|
|
661
|
+
return;
|
|
662
|
+
setMessages((previous) => ({
|
|
663
|
+
...previous,
|
|
664
|
+
[provider]: {
|
|
665
|
+
tone: 'success',
|
|
666
|
+
text: result.claimed > 0
|
|
667
|
+
? t('tasksClaimed', { count: result.claimed, credit: result.creditEarned })
|
|
668
|
+
: result.claimable > 0
|
|
669
|
+
? t('tasksClaimable', { count: result.claimable })
|
|
670
|
+
: t('tasksNone'),
|
|
671
|
+
},
|
|
672
|
+
}));
|
|
673
|
+
await refresh();
|
|
674
|
+
}
|
|
675
|
+
catch (caught) {
|
|
676
|
+
if (mounted.current)
|
|
677
|
+
setError(t('actionFailed', { message: messageOf(caught) }));
|
|
678
|
+
}
|
|
679
|
+
finally {
|
|
680
|
+
if (mounted.current)
|
|
681
|
+
setBusyAction(false);
|
|
682
|
+
}
|
|
683
|
+
}, [call, refresh, t]);
|
|
649
684
|
const stopPolling = useCallback(() => {
|
|
650
685
|
if (timer.current !== undefined) {
|
|
651
686
|
clearInterval(timer.current);
|
|
@@ -889,5 +924,5 @@ export function SubscriptionsSection(props) {
|
|
|
889
924
|
}, [act, call]);
|
|
890
925
|
if (call === undefined)
|
|
891
926
|
return _jsx("p", { style: styles.intro, children: t('unavailable') });
|
|
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)))] }));
|
|
927
|
+
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 !== 'workbuddy' ? {} : { onTasks: (account) => { void runTasks(provider.provider, account); } } }, provider.provider)))] }));
|
|
893
928
|
}
|
package/lib/client/locales.d.ts
CHANGED
|
@@ -67,6 +67,10 @@ export declare const en: {
|
|
|
67
67
|
readonly checkinClaim: "Check in";
|
|
68
68
|
readonly checkinClaimed: "Checked in, +{credit} credits (streak {days}d)";
|
|
69
69
|
readonly checkinAlready: "Already checked in today (streak {days}d)";
|
|
70
|
+
readonly tasksClaim: "Tasks";
|
|
71
|
+
readonly tasksClaimed: "Claimed {count} task rewards, +{credit} credits";
|
|
72
|
+
readonly tasksClaimable: "{count} tasks ready to claim but not yet earned";
|
|
73
|
+
readonly tasksNone: "No task rewards to claim right now";
|
|
70
74
|
readonly modelsPinned: "pinned";
|
|
71
75
|
readonly usageTitle: "Plan and usage";
|
|
72
76
|
readonly usagePlan: "Plan: {plan}";
|
|
@@ -171,6 +175,10 @@ export declare const zh: {
|
|
|
171
175
|
checkinClaim: string;
|
|
172
176
|
checkinClaimed: string;
|
|
173
177
|
checkinAlready: string;
|
|
178
|
+
tasksClaim: string;
|
|
179
|
+
tasksClaimed: string;
|
|
180
|
+
tasksClaimable: string;
|
|
181
|
+
tasksNone: string;
|
|
174
182
|
modelsPinned: string;
|
|
175
183
|
usageTitle: string;
|
|
176
184
|
usagePlan: string;
|
package/lib/client/locales.js
CHANGED
|
@@ -67,6 +67,10 @@ export const en = {
|
|
|
67
67
|
checkinClaim: 'Check in',
|
|
68
68
|
checkinClaimed: 'Checked in, +{credit} credits (streak {days}d)',
|
|
69
69
|
checkinAlready: 'Already checked in today (streak {days}d)',
|
|
70
|
+
tasksClaim: 'Tasks',
|
|
71
|
+
tasksClaimed: 'Claimed {count} task rewards, +{credit} credits',
|
|
72
|
+
tasksClaimable: '{count} tasks ready to claim but not yet earned',
|
|
73
|
+
tasksNone: 'No task rewards to claim right now',
|
|
70
74
|
modelsPinned: 'pinned',
|
|
71
75
|
usageTitle: 'Plan and usage',
|
|
72
76
|
usagePlan: 'Plan: {plan}',
|
|
@@ -169,6 +173,10 @@ export const zh = {
|
|
|
169
173
|
checkinClaim: '签到',
|
|
170
174
|
checkinClaimed: '签到成功,+{credit} 积分(连签 {days} 天)',
|
|
171
175
|
checkinAlready: '今日已签到(连签 {days} 天)',
|
|
176
|
+
tasksClaim: '做任务',
|
|
177
|
+
tasksClaimed: '已领取 {count} 个任务奖励,+{credit} 积分',
|
|
178
|
+
tasksClaimable: '有 {count} 个任务可领但还未达成',
|
|
179
|
+
tasksNone: '现在没有可领的任务奖励',
|
|
172
180
|
modelsPinned: '固定',
|
|
173
181
|
usageTitle: '套餐与额度',
|
|
174
182
|
usagePlan: '套餐:{plan}',
|
package/lib/client.js
CHANGED
|
@@ -98,6 +98,10 @@ const en = {
|
|
|
98
98
|
checkinClaim: "Check in",
|
|
99
99
|
checkinClaimed: "Checked in, +{credit} credits (streak {days}d)",
|
|
100
100
|
checkinAlready: "Already checked in today (streak {days}d)",
|
|
101
|
+
tasksClaim: "Tasks",
|
|
102
|
+
tasksClaimed: "Claimed {count} task rewards, +{credit} credits",
|
|
103
|
+
tasksClaimable: "{count} tasks ready to claim but not yet earned",
|
|
104
|
+
tasksNone: "No task rewards to claim right now",
|
|
101
105
|
modelsPinned: "pinned",
|
|
102
106
|
usageTitle: "Plan and usage",
|
|
103
107
|
usagePlan: "Plan: {plan}",
|
|
@@ -200,6 +204,10 @@ const zh = {
|
|
|
200
204
|
checkinClaim: "签到",
|
|
201
205
|
checkinClaimed: "签到成功,+{credit} 积分(连签 {days} 天)",
|
|
202
206
|
checkinAlready: "今日已签到(连签 {days} 天)",
|
|
207
|
+
tasksClaim: "做任务",
|
|
208
|
+
tasksClaimed: "已领取 {count} 个任务奖励,+{credit} 积分",
|
|
209
|
+
tasksClaimable: "有 {count} 个任务可领但还未达成",
|
|
210
|
+
tasksNone: "现在没有可领的任务奖励",
|
|
203
211
|
modelsPinned: "固定",
|
|
204
212
|
usageTitle: "套餐与额度",
|
|
205
213
|
usagePlan: "套餐:{plan}",
|
|
@@ -1341,28 +1349,32 @@ function windowAmount$1(window$1) {
|
|
|
1341
1349
|
}
|
|
1342
1350
|
/** One account's metered windows and check-in, rendered under its account row. */
|
|
1343
1351
|
function AccountUsage(props) {
|
|
1344
|
-
const { entry, t, busyAction, onCheckin } = props;
|
|
1352
|
+
const { entry, t, busyAction, onCheckin, onTasks } = props;
|
|
1345
1353
|
if (entry === void 0) return null;
|
|
1346
1354
|
const checkin = entry.checkin;
|
|
1347
1355
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1348
1356
|
style: styles.usageInline,
|
|
1349
1357
|
children: [
|
|
1350
|
-
|
|
1358
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1351
1359
|
style: styles.usageLine,
|
|
1352
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1360
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1353
1361
|
style: styles.modelName,
|
|
1354
|
-
children:
|
|
1355
|
-
|
|
1356
|
-
" · ",
|
|
1357
|
-
checkin.todayCheckedIn ? t("checkinDone", { days: checkin.streakDays }) : t("checkinPending")
|
|
1358
|
-
]
|
|
1359
|
-
}), !checkin.todayCheckedIn && onCheckin !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1360
|
-
type: "button",
|
|
1361
|
-
className: "dsh-subscription-quiet",
|
|
1362
|
+
children: checkin?.active === true ? checkin.todayCheckedIn ? t("checkinDone", { days: checkin.streakDays }) : t("checkinPending") : ""
|
|
1363
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1362
1364
|
style: styles.pushRight,
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1365
|
+
children: [onTasks !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1366
|
+
type: "button",
|
|
1367
|
+
className: "dsh-subscription-quiet",
|
|
1368
|
+
disabled: busyAction,
|
|
1369
|
+
onClick: onTasks,
|
|
1370
|
+
children: t("tasksClaim")
|
|
1371
|
+
}), checkin?.active === true && !checkin.todayCheckedIn && onCheckin !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1372
|
+
type: "button",
|
|
1373
|
+
className: "dsh-subscription-quiet",
|
|
1374
|
+
disabled: busyAction,
|
|
1375
|
+
onClick: onCheckin,
|
|
1376
|
+
children: t("checkinClaim")
|
|
1377
|
+
})]
|
|
1366
1378
|
})]
|
|
1367
1379
|
}),
|
|
1368
1380
|
entry.windows.map((window$1) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1741,6 +1753,9 @@ function ProviderCard(props) {
|
|
|
1741
1753
|
busyAction,
|
|
1742
1754
|
...props.onCheckin === void 0 ? {} : { onCheckin: () => {
|
|
1743
1755
|
props.onCheckin?.(account.account);
|
|
1756
|
+
} },
|
|
1757
|
+
...props.onTasks === void 0 ? {} : { onTasks: () => {
|
|
1758
|
+
props.onTasks?.(account.account);
|
|
1744
1759
|
} }
|
|
1745
1760
|
})]
|
|
1746
1761
|
}, account.account);
|
|
@@ -2181,6 +2196,38 @@ function SubscriptionsSection(props) {
|
|
|
2181
2196
|
refresh,
|
|
2182
2197
|
t
|
|
2183
2198
|
]);
|
|
2199
|
+
/** Auto-claim one account's growth-task rewards, then refresh the figures. */
|
|
2200
|
+
const runTasks = (0, react.useCallback)(async (provider, account) => {
|
|
2201
|
+
if (call === void 0) return;
|
|
2202
|
+
setBusyAction(true);
|
|
2203
|
+
try {
|
|
2204
|
+
const result = await call("tasks", {
|
|
2205
|
+
provider,
|
|
2206
|
+
account,
|
|
2207
|
+
claim: true
|
|
2208
|
+
});
|
|
2209
|
+
if (!mounted.current) return;
|
|
2210
|
+
setMessages((previous) => ({
|
|
2211
|
+
...previous,
|
|
2212
|
+
[provider]: {
|
|
2213
|
+
tone: "success",
|
|
2214
|
+
text: result.claimed > 0 ? t("tasksClaimed", {
|
|
2215
|
+
count: result.claimed,
|
|
2216
|
+
credit: result.creditEarned
|
|
2217
|
+
}) : result.claimable > 0 ? t("tasksClaimable", { count: result.claimable }) : t("tasksNone")
|
|
2218
|
+
}
|
|
2219
|
+
}));
|
|
2220
|
+
await refresh();
|
|
2221
|
+
} catch (caught) {
|
|
2222
|
+
if (mounted.current) setError(t("actionFailed", { message: messageOf(caught) }));
|
|
2223
|
+
} finally {
|
|
2224
|
+
if (mounted.current) setBusyAction(false);
|
|
2225
|
+
}
|
|
2226
|
+
}, [
|
|
2227
|
+
call,
|
|
2228
|
+
refresh,
|
|
2229
|
+
t
|
|
2230
|
+
]);
|
|
2184
2231
|
const stopPolling = (0, react.useCallback)(() => {
|
|
2185
2232
|
if (timer.current !== void 0) {
|
|
2186
2233
|
clearInterval(timer.current);
|
|
@@ -2503,6 +2550,9 @@ function SubscriptionsSection(props) {
|
|
|
2503
2550
|
},
|
|
2504
2551
|
...provider.provider !== "workbuddy" ? {} : { onCheckin: (account) => {
|
|
2505
2552
|
checkin(provider.provider, account);
|
|
2553
|
+
} },
|
|
2554
|
+
...provider.provider !== "workbuddy" ? {} : { onTasks: (account) => {
|
|
2555
|
+
runTasks(provider.provider, account);
|
|
2506
2556
|
} }
|
|
2507
2557
|
}, provider.provider))
|
|
2508
2558
|
]
|