dsh-connect-trae 3.0.1 → 3.3.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/CHANGELOG.md +178 -0
- package/README.en.md +3 -2
- package/README.md +3 -2
- package/lib/client.js +912 -370
- package/lib/index.d.ts +118 -7
- package/lib/index.js +375 -54
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -33,8 +33,19 @@ window.__ModuleLoader__.load({
|
|
|
33
33
|
*/
|
|
34
34
|
const TRAE_ACCOUNT_CREDITS_PATH = "/plugins/dsh-connect-trae/account-credits";
|
|
35
35
|
/**
|
|
36
|
+
* Per-account status (check-in / credits / subscription) for the account table.
|
|
37
|
+
*
|
|
38
|
+
* Each row of the account table reads its OWN state through this route — a CN
|
|
39
|
+
* row carries its check-in state and balance, an ai row its subscription.
|
|
40
|
+
* POST with the accountId in the body: one upstream read per account, paid
|
|
41
|
+
* only when the table is actually shown.
|
|
42
|
+
*/
|
|
43
|
+
const TRAE_ACCOUNT_STATUS_PATH = "/plugins/dsh-connect-trae/account-status";
|
|
44
|
+
/**
|
|
36
45
|
* Plugin-owned daily check-in claim endpoint. POST, loopback-only, and the only
|
|
37
|
-
* route in this plugin that changes upstream account state.
|
|
46
|
+
* route in this plugin that changes upstream account state. The accountId in
|
|
47
|
+
* the body names WHICH account to claim for, so the button can sit in each
|
|
48
|
+
* account row.
|
|
38
49
|
*/
|
|
39
50
|
const TRAE_CHECKIN_PATH = "/plugins/dsh-connect-trae/checkin";
|
|
40
51
|
/** Query parameter naming the region a card request addresses. */
|
|
@@ -259,9 +270,9 @@ window.__ModuleLoader__.load({
|
|
|
259
270
|
.dsm-trae-tab:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
260
271
|
.dsm-trae-tab-active{color:var(--dsw-alias-label-primary,#e6e6e6);background:var(--dsw-alias-bg-layer-2,#232529);box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l2,#3a3d45)}
|
|
261
272
|
.dsm-trae-tab-off{opacity:.55}
|
|
262
|
-
.dsm-trae-
|
|
263
|
-
|
|
264
|
-
.dsm-trae-tab-switch
|
|
273
|
+
/* Same checkbox as the settings block (.dsm-trae-switch); only the padding
|
|
274
|
+
differs, so it sits inside the tab cell without crowding the label. */
|
|
275
|
+
.dsm-trae-tab-switch{flex:none;padding:0 8px 0 2px}
|
|
265
276
|
.dsm-trae-tab-dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:6px;vertical-align:baseline}
|
|
266
277
|
.dsm-trae-tab-hint{margin:0;color:var(--dsw-alias-label-tertiary,#999);font-size:12px;line-height:18px}
|
|
267
278
|
.dsm-trae-tab-off-notice{margin:0;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:10px;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px;line-height:18px;background:var(--dsw-alias-bg-layer-3,#2a2c33)}
|
|
@@ -269,11 +280,109 @@ window.__ModuleLoader__.load({
|
|
|
269
280
|
.dsm-trae-usage-account-copy{display:flex;flex-direction:column;gap:3px;min-width:0}
|
|
270
281
|
.dsm-trae-usage-expiry{padding-left:19px;color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:12px;line-height:18px}
|
|
271
282
|
.dsm-trae-account-picker{display:block}
|
|
272
|
-
|
|
273
|
-
.
|
|
274
|
-
|
|
275
|
-
.
|
|
276
|
-
|
|
283
|
+
/* The 设置 block: label + hint on the left, control on the right, one save for
|
|
284
|
+
the whole block. It replaces the bare checkbox rows that used to write on
|
|
285
|
+
every click — a draft needs a visible way back out, so each row carries its
|
|
286
|
+
own explanation and the footer carries the buttons. */
|
|
287
|
+
/* The preference switches: BARE LINES, one per switch, each with its label
|
|
288
|
+
wrapped around the box so the whole line is a click target.
|
|
289
|
+
*
|
|
290
|
+
* The 设置 block used to be a bordered box with a heading, two rows and a
|
|
291
|
+
* 「保存」/「放弃」 footer. That is gone with the draft it existed to serve — this
|
|
292
|
+
* is the sibling dsh-connect-workbuddy card's shape, where a preference is a
|
|
293
|
+
* checkbox on a line that commits on the click. */
|
|
294
|
+
.dsm-trae-prefs{display:flex;flex-direction:column;gap:8px}
|
|
295
|
+
.dsm-trae-pref{display:flex;align-items:flex-start;gap:8px;font-size:13px;line-height:18px;color:var(--dsw-alias-label-secondary,#c6c9d0);cursor:pointer}
|
|
296
|
+
.dsm-trae-pref input{flex:none;margin:2px 0 0;cursor:pointer;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
|
|
297
|
+
.dsm-trae-pref input:disabled{cursor:default}
|
|
298
|
+
.dsm-trae-pref:has(input:disabled){opacity:.6;cursor:default}
|
|
299
|
+
.dsm-trae-pref b{font-weight:500;color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
300
|
+
.dsm-trae-pref-hint{color:var(--dsw-alias-label-tertiary,#9aa0a8)}
|
|
301
|
+
.dsm-trae-pref span{min-width:0}
|
|
302
|
+
/* The card's checkboxes are the platform's own, styled only with
|
|
303
|
+
accent-color — the SAME idiom .dsm-trae-mainui-switch-label above already
|
|
304
|
+
uses for the composer-line switch, and the one the sibling
|
|
305
|
+
dsh-connect-workbuddy card uses for its tab and credits boxes.
|
|
306
|
+
*
|
|
307
|
+
* These were drawn by hand for one release: a 34x20 track with a 14px knob,
|
|
308
|
+
* --dsw-alias-brand-primary when checked. That token is DSH's CONTRAST colour
|
|
309
|
+
* (#0f1115 light / #f9fafb dark), not an accent, so a checked box came out as a
|
|
310
|
+
* solid black pill — heavier than anything else in the card, and unrelated to
|
|
311
|
+
* the native box sitting two centimetres away in the composer toolbar. The
|
|
312
|
+
* platform checkbox tracks the host's own accent and dark mode for free. */
|
|
313
|
+
.dsm-trae-switch{display:inline-flex;align-items:center;cursor:pointer}
|
|
314
|
+
.dsm-trae-switch input{margin:0;cursor:pointer;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
|
|
315
|
+
.dsm-trae-switch input:disabled{cursor:default}
|
|
316
|
+
.dsm-trae-switch:has(input:disabled){opacity:.6;cursor:default}
|
|
317
|
+
/* The account block is a real table: the question it answers is "which of these
|
|
318
|
+
accounts, and what is the action on each one" — a dropdown can only ever show
|
|
319
|
+
the one already chosen. */
|
|
320
|
+
.dsm-trae-accounts{display:flex;flex-direction:column;gap:10px;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:14px;background:var(--dsw-alias-bg-layer-2,#24262c)}
|
|
321
|
+
.dsm-trae-accounts-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
|
|
322
|
+
/* The refresh button must not be squeezed into two lines by a long hint. */
|
|
323
|
+
.dsm-trae-accounts-head>button{flex:none;white-space:nowrap}
|
|
324
|
+
.dsm-trae-accounts-title{margin:0;font-size:13px;font-weight:600;line-height:18px;color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
325
|
+
.dsm-trae-accounts-hint{margin:3px 0 0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,#9aa0a8)}
|
|
326
|
+
/* The account rows: two lines each — who the account is, then what it is worth
|
|
327
|
+
— with the one action that applies to THAT row on the right. A four-column
|
|
328
|
+
table was the wrong instrument here: every field demanded its own header, the
|
|
329
|
+
columns drifted with the content ("LaoDing" vs a full expiry stamp), and the
|
|
330
|
+
bound row repeated its "in use" badge in two different columns. */
|
|
331
|
+
/* The account table, shaped like the sibling WorkBuddy card's pool table: a
|
|
332
|
+
bordered container, a tinted header row, and one grid row per account whose
|
|
333
|
+
tracks are FRACTIONS of the container rather than sizes derived from the
|
|
334
|
+
content. That is the whole point — the previous four-column table let each
|
|
335
|
+
column grow with its own text, so the columns did not line up from row to row
|
|
336
|
+
and the table read as noise in a narrow panel. */
|
|
337
|
+
.dsm-trae-account-table{display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:12px;overflow:hidden;background:var(--dsw-alias-bg-layer-2,#24262c)}
|
|
338
|
+
/* Four tracks where the region has a check-in column, three where it does not
|
|
339
|
+
(the international region has no check-in at all: the ug/* family answers
|
|
340
|
+
404 on every ai gateway, so the column is absent rather than empty and
|
|
341
|
+
permanently unknown). The freed track goes to the account name. */
|
|
342
|
+
.dsm-trae-account-row{display:grid;grid-template-columns:1.7fr 1.1fr 1fr 1.1fr;gap:10px;align-items:center;padding:9px 12px;border-top:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.18));font-size:13px;color:var(--dsw-alias-label-secondary,#c6c9d0)}
|
|
343
|
+
.dsm-trae-account-table:not(.dsm-trae-account-table-checkin) .dsm-trae-account-row{grid-template-columns:2.2fr 1.2fr 1.1fr}
|
|
344
|
+
.dsm-trae-account-row:first-child{border-top:0}
|
|
345
|
+
.dsm-trae-account-row-head{padding:7px 12px;background:var(--dsw-alias-bg-layer-3,#2a2c33);color:var(--dsw-alias-label-tertiary,#999);font-size:11px;font-weight:600;letter-spacing:.02em;text-transform:uppercase}
|
|
346
|
+
/* The serving row, findable at a glance: the accent bar is what distinguishes it
|
|
347
|
+
in a long table, where the 7%-opacity wash alone reads as a zebra stripe. */
|
|
348
|
+
.dsm-trae-account-row-current{background:rgba(86,134,254,.07);box-shadow:inset 3px 0 0 var(--dsw-alias-brand-primary,#5686fe)}
|
|
349
|
+
.dsm-trae-account-who{display:flex;align-items:center;gap:8px;min-width:0}
|
|
350
|
+
.dsm-trae-account-who .dsm-trae-usage-dot{margin-right:0;flex:none}
|
|
351
|
+
.dsm-trae-account-name{display:flex;flex-direction:column;gap:1px;min-width:0}
|
|
352
|
+
.dsm-trae-account-name b{font-weight:500;color:var(--dsw-alias-label-primary,#e6e6e6);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
353
|
+
.dsm-trae-account-row-current .dsm-trae-account-name b{font-weight:600}
|
|
354
|
+
/* Declared AFTER the blanket span rule above so it wins: the badge must not
|
|
355
|
+
inherit the muted 11px tertiary treatment the edition line uses. */
|
|
356
|
+
.dsm-trae-account-name .dsm-trae-account-badge{align-self:flex-start;margin-top:2px;padding:1px 6px;border-radius:999px;font-size:10px;line-height:15px;font-weight:600;white-space:nowrap;color:var(--dsw-alias-brand-primary,#5686fe);background:rgba(86,134,254,.14)}
|
|
357
|
+
.dsm-trae-account-meta{display:flex;align-items:center;gap:5px;flex-wrap:wrap;font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary,#9aa0a8);min-width:0}
|
|
358
|
+
.dsm-trae-account-meta i{font-style:normal;opacity:.4}
|
|
359
|
+
.dsm-trae-account-note{font-size:11px;line-height:16px;color:var(--dsw-alias-state-error-primary,#ef4444)}
|
|
360
|
+
/* Its own track, in tabular figures, so digits in different rows sit on the same
|
|
361
|
+
column of the grid instead of jittering with the glyph widths. */
|
|
362
|
+
.dsm-trae-account-worth{display:flex;flex-direction:column;gap:1px;min-width:0;font-variant-numeric:tabular-nums}
|
|
363
|
+
.dsm-trae-account-value{color:var(--dsw-alias-label-primary,#e6e6e6);font-weight:600}
|
|
364
|
+
.dsm-trae-account-value-on{color:var(--dsw-alias-state-success-primary,#22a06b)}
|
|
365
|
+
.dsm-trae-account-checkin-cell{display:flex;align-items:center;min-width:0}
|
|
366
|
+
.dsm-trae-account-actions{display:flex;align-items:center;gap:6px;justify-content:flex-end;flex:none}
|
|
367
|
+
/* The per-row switch is the shared outline button, only smaller: one button
|
|
368
|
+
vocabulary in the card instead of a bespoke one per block. */
|
|
369
|
+
.dsm-trae-account-row .dsm-trae-account-switch{padding:2px 10px;font-size:12px}
|
|
370
|
+
@media (max-width:760px){.dsm-trae-account-row,.dsm-trae-account-table:not(.dsm-trae-account-table-checkin) .dsm-trae-account-row{grid-template-columns:1fr 1fr;row-gap:6px}.dsm-trae-account-row-head{display:none}}
|
|
371
|
+
/* The check-in is the only action on this card that reaches upstream account
|
|
372
|
+
state, so it is the one button allowed a colour. Everything else in the row
|
|
373
|
+
defers to it. */
|
|
374
|
+
.dsm-trae-account-checkin{appearance:none;font:inherit;cursor:pointer;white-space:nowrap;border:1px solid rgba(86,134,254,.4);border-radius:7px;padding:2px 9px;font-size:12px;line-height:18px;font-weight:500;color:var(--dsw-alias-brand-primary,#5686fe);background:rgba(86,134,254,.08)}
|
|
375
|
+
.dsm-trae-account-checkin:hover:not(:disabled){background:rgba(86,134,254,.16)}
|
|
376
|
+
.dsm-trae-account-checkin:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
377
|
+
/* A disabled check-in still STATES something ("claimed", "device used"), so it
|
|
378
|
+
stays readable — clearly inactive, not invisible. */
|
|
379
|
+
.dsm-trae-account-checkin:disabled{opacity:.55;cursor:default;border-color:transparent;color:var(--dsw-alias-label-tertiary,#9aa0a8);background:transparent}
|
|
380
|
+
/* The switch is the row's LIVE control, so it is brighter than the inert
|
|
381
|
+
check-in labels beside it: same shape, different weight. */
|
|
382
|
+
.dsm-trae-account-switch{appearance:none;font:inherit;cursor:pointer;white-space:nowrap;border:0;border-radius:7px;padding:3px 8px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary,#c6c9d0);background:transparent}
|
|
383
|
+
.dsm-trae-account-switch:hover:not(:disabled){color:var(--dsw-alias-brand-primary,#5686fe);background:rgba(86,134,254,.12)}
|
|
384
|
+
.dsm-trae-account-switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
385
|
+
.dsm-trae-account-switch:disabled{opacity:.45;cursor:default}
|
|
277
386
|
.dsm-trae-mark{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:5px;font-size:11px;font-weight:700;color:#111;background:linear-gradient(135deg,#ffd36e,#ffb84d);margin-right:6px}
|
|
278
387
|
.dsm-trae-usage-list{display:flex;flex-direction:column;gap:10px}
|
|
279
388
|
.dsm-trae-usage-text{margin:0;font-size:14px;line-height:22px;color:var(--dsw-alias-label-secondary,#b8b8b8)}
|
|
@@ -316,11 +425,10 @@ window.__ModuleLoader__.load({
|
|
|
316
425
|
checkbox rendered unstyled and read as absent — the whole reason the switch
|
|
317
426
|
looked missing. */
|
|
318
427
|
.dsm-trae-models-actions{display:flex;align-items:center;gap:8px;flex:none}
|
|
319
|
-
}
|
|
320
|
-
.dsm-trae-usage-alternative{display:flex;align-items:flex-start;gap:10px;margin-top:10px;padding:10px 12px;border:1px solid var(--dsw-alias-state-warning-border,#7a5b12);border-radius:10px;background:var(--dsw-alias-state-warning-bg,#2a2313)}
|
|
428
|
+
.dsm-trae-usage-alternative{display:flex;align-items:flex-start;gap:10px;margin-top:10px;padding:10px 12px;border:1px solid var(--dsw-alias-state-warn-secondary,#fbbf24);border-radius:10px;background:var(--dsw-alias-state-warn-tertiary,#fef3c7)}
|
|
321
429
|
.dsm-trae-usage-alternative-text{margin:0;flex:1 1 auto;font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
322
430
|
.dsm-trae-usage-switch-button{flex:0 0 auto;padding:5px 10px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:8px;background:var(--dsw-alias-bg-layer-3,#2c2f36);color:var(--dsw-alias-label-primary,#e6e6e6);font-size:12px;line-height:16px;cursor:pointer}
|
|
323
|
-
.dsm-trae-usage-switch-button:hover:not(:disabled){background:var(--dsw-alias-bg-
|
|
431
|
+
.dsm-trae-usage-switch-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.14))}
|
|
324
432
|
.dsm-trae-usage-switch-button:disabled{opacity:.5;cursor:not-allowed}
|
|
325
433
|
.dsm-trae-usage-note{margin:8px 0 0;color:var(--dsw-alias-label-tertiary,#999);font-size:11px;line-height:16px}
|
|
326
434
|
.dsm-trae-models-hint{margin:4px 0 0;color:var(--dsw-alias-label-tertiary,#999);font-size:11px;line-height:16px}
|
|
@@ -329,7 +437,7 @@ window.__ModuleLoader__.load({
|
|
|
329
437
|
the model. */
|
|
330
438
|
.dsm-trae-model-probe{flex:none;border-radius:999px;padding:1px 7px;font-size:10px;font-weight:500;line-height:15px;white-space:nowrap}
|
|
331
439
|
.dsm-trae-model-probe-available{color:var(--dsw-alias-state-success-primary,#22a06b);border:1px solid color-mix(in srgb,var(--dsw-alias-state-success-primary,#22a06b) 40%,transparent)}
|
|
332
|
-
.dsm-trae-model-probe-gated,.dsm-trae-model-probe-unsupported{color:var(--dsw-alias-state-
|
|
440
|
+
.dsm-trae-model-probe-gated,.dsm-trae-model-probe-unsupported{color:var(--dsw-alias-state-warn-primary,#f59e0b);border:1px solid color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 40%,transparent)}
|
|
333
441
|
.dsm-trae-model-probe-auth{color:var(--dsw-alias-state-error-primary,#d92d20);border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary,#d92d20) 40%,transparent)}
|
|
334
442
|
.dsm-trae-model-probe-rate-limited,.dsm-trae-model-probe-unknown{color:var(--dsw-alias-label-tertiary,#999);border:1px solid color-mix(in srgb,var(--dsw-alias-label-tertiary,#999) 35%,transparent)}
|
|
335
443
|
.dsm-trae-model-meta{display:flex;align-items:center;gap:7px 12px;flex-wrap:wrap;color:var(--dsw-alias-label-tertiary,#999);font-size:11px;line-height:16px}
|
|
@@ -475,7 +583,7 @@ window.__ModuleLoader__.load({
|
|
|
475
583
|
background:var(--dsw-alias-label-dimmed,rgba(127,127,127,.5));
|
|
476
584
|
}
|
|
477
585
|
.dsm-trae-composer-panel-table tr.dsm-trae-composer-panel-row-current .dsm-trae-composer-panel-dot{
|
|
478
|
-
background:var(--dsw-state-success,#
|
|
586
|
+
background:var(--dsw-alias-state-success-primary,#22c55e);
|
|
479
587
|
}
|
|
480
588
|
.dsm-trae-composer-panel-foot{
|
|
481
589
|
display:flex;justify-content:space-between;gap:8px;
|
|
@@ -486,6 +594,52 @@ window.__ModuleLoader__.load({
|
|
|
486
594
|
.dsm-trae-composer-panel-empty{margin:0;color:var(--dsw-alias-label-tertiary,GrayText)}
|
|
487
595
|
.dsm-trae-composer-panel-error{margin:8px 0 0;color:var(--dsw-alias-state-error-primary,#d92d20);font-size:11px;line-height:16px}
|
|
488
596
|
`;
|
|
597
|
+
//#endregion
|
|
598
|
+
//#region src/client/plan-text.ts
|
|
599
|
+
/**
|
|
600
|
+
* The international region's "what plan is this account on" text.
|
|
601
|
+
*
|
|
602
|
+
* WHY THIS IS ITS OWN MODULE: three surfaces now answer that one question — the
|
|
603
|
+
* card's subscription box, the card's per-account rows, and the composer
|
|
604
|
+
* readout. Each previously carried its own copy of the verdict, which is how the
|
|
605
|
+
* card's box and its roster row were able to disagree about one account's tier
|
|
606
|
+
* (the box read the usage document's top-level `payStatus`, which the ai document
|
|
607
|
+
* does not always carry, while the row read the per-account state). A second
|
|
608
|
+
* implementation of the same rule is free to drift; there is now exactly one.
|
|
609
|
+
*
|
|
610
|
+
* @module dsh-connect-trae/client/plan-text
|
|
611
|
+
*/
|
|
612
|
+
/**
|
|
613
|
+
* Decide whether an international account is on a paid plan, and name the plan.
|
|
614
|
+
*
|
|
615
|
+
* Two independent things can entitle it: a package row (`has_package`) and a
|
|
616
|
+
* plan identity (`user_pay_identity_str`). Reading only the first is what
|
|
617
|
+
* rendered a Pro member as "No active package" in issue #19, so either one
|
|
618
|
+
* counts. The tier string rides separately so a caller can name the plan instead
|
|
619
|
+
* of only asserting a boolean.
|
|
620
|
+
*/
|
|
621
|
+
function planVerdict(payStatus) {
|
|
622
|
+
const tier = payStatus.payIdentityStr?.trim();
|
|
623
|
+
const paid = payStatus.hasPackage || traePlanIsPaid(payStatus.payIdentityStr);
|
|
624
|
+
return {
|
|
625
|
+
paid,
|
|
626
|
+
...tier === void 0 || tier === "" ? {} : { tier },
|
|
627
|
+
label: paid ? "row.subscribed" : "row.noPackage"
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* The verdict as ONE short string, for a surface that has a single slot.
|
|
632
|
+
*
|
|
633
|
+
* The composer readout is that surface: it is a few characters wide in the tool
|
|
634
|
+
* row, so it gets the tier name alone (`Pro`, `Free`) rather than the card's
|
|
635
|
+
* two-line label-plus-tier. When the upstream named no tier the label stands in,
|
|
636
|
+
* which is still strictly more than the dash the readout used to show for the
|
|
637
|
+
* whole international region.
|
|
638
|
+
*/
|
|
639
|
+
function planText(payStatus, t) {
|
|
640
|
+
const verdict = planVerdict(payStatus);
|
|
641
|
+
return verdict.tier === void 0 ? t(verdict.label) : verdict.tier;
|
|
642
|
+
}
|
|
489
643
|
//#endregion
|
|
490
644
|
//#region src/client/TraeUsageCard.tsx
|
|
491
645
|
/**
|
|
@@ -558,24 +712,6 @@ window.__ModuleLoader__.load({
|
|
|
558
712
|
return formatNumber(value);
|
|
559
713
|
}
|
|
560
714
|
/**
|
|
561
|
-
* The subscription row's verdict.
|
|
562
|
-
*
|
|
563
|
-
* Two independent things can entitle an international account: a package row
|
|
564
|
-
* (`has_package`) and a plan identity (`user_pay_identity_str`). Reading only
|
|
565
|
-
* the first is what rendered a Pro member as "No active package" in issue #19,
|
|
566
|
-
* so either one counts. The tier string is carried separately so the row can
|
|
567
|
-
* name the plan instead of just asserting a boolean.
|
|
568
|
-
*/
|
|
569
|
-
function payVerdict(payStatus) {
|
|
570
|
-
const tier = payStatus.payIdentityStr?.trim();
|
|
571
|
-
const paid = payStatus.hasPackage || traePlanIsPaid(payStatus.payIdentityStr);
|
|
572
|
-
return {
|
|
573
|
-
paid,
|
|
574
|
-
...tier === void 0 || tier === "" ? {} : { tier },
|
|
575
|
-
label: paid ? "row.subscribed" : "row.noPackage"
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
/**
|
|
579
715
|
* The verdict badge for one model, or null when it has not been tested.
|
|
580
716
|
*
|
|
581
717
|
* `unknown` renders as inconclusive, never as a failure: a throttle or a dropped
|
|
@@ -641,14 +777,6 @@ window.__ModuleLoader__.load({
|
|
|
641
777
|
/** Region whose on/off checkbox write is in flight, so its box can't race. */
|
|
642
778
|
const [togglingRegion, setTogglingRegion] = (0, react.useState)(void 0);
|
|
643
779
|
/**
|
|
644
|
-
* Whether a check-in claim is in flight. A single flag rather than a
|
|
645
|
-
* per-region map: the claim exists only on the CN tab, and one shared flag
|
|
646
|
-
* keeps a tab switch from stranding a second concurrent claim.
|
|
647
|
-
*/
|
|
648
|
-
const [claiming, setClaiming] = (0, react.useState)(false);
|
|
649
|
-
/** Last claim refusal, shown until the next successful refresh. */
|
|
650
|
-
const [claimError, setClaimError] = (0, react.useState)(void 0);
|
|
651
|
-
/**
|
|
652
780
|
* Outcome of the last "refresh accounts" press (issue #25).
|
|
653
781
|
*
|
|
654
782
|
* The rescan answers 200 with a fresh account list whether or not anything
|
|
@@ -659,21 +787,62 @@ window.__ModuleLoader__.load({
|
|
|
659
787
|
*/
|
|
660
788
|
const [rescanNote, setRescanNote] = (0, react.useState)(void 0);
|
|
661
789
|
/**
|
|
662
|
-
*
|
|
663
|
-
*
|
|
664
|
-
*
|
|
665
|
-
*
|
|
666
|
-
*
|
|
790
|
+
* A preference the user just flipped, held only until its write lands.
|
|
791
|
+
*
|
|
792
|
+
* The 设置 block used to stage both switches in a draft written by 「保存」.
|
|
793
|
+
* That is GONE: the sibling dsh-connect-workbuddy card commits its preference
|
|
794
|
+
* checkboxes on the click, and a Save button for two booleans asks the user to
|
|
795
|
+
* remember something that has no reason to need remembering.
|
|
796
|
+
*
|
|
797
|
+
* What remains is the smallest thing that keeps a click honest: the box reads
|
|
798
|
+
* this optimistic value until the write settles, then falls back to the
|
|
799
|
+
* committed one. So a flip shows immediately, and a write that FAILS snaps the
|
|
800
|
+
* box back to the Host's value instead of leaving it claiming one that never
|
|
801
|
+
* landed.
|
|
802
|
+
*/
|
|
803
|
+
const optimisticSettings = (0, react.useRef)({});
|
|
804
|
+
/** True while a preference is crossing the wire; serializes both writers. */
|
|
805
|
+
const [savingSettings, setSavingSettings] = (0, react.useState)(false);
|
|
806
|
+
/**
|
|
807
|
+
* Per-account status (check-in / balances / subscription) shown in each row of
|
|
808
|
+
* the account table, keyed by region then account id. Loaded per account via
|
|
809
|
+
* `TRAE_ACCOUNT_STATUS_PATH` once the table is shown; a row that failed to
|
|
810
|
+
* load (or that has not been read yet) simply renders without its figure,
|
|
811
|
+
* exactly as the composer table treats a missing balance.
|
|
667
812
|
*/
|
|
668
|
-
const [
|
|
813
|
+
const [accountStatusByRegion, setAccountStatusByRegion] = (0, react.useState)({
|
|
814
|
+
cn: {},
|
|
815
|
+
ai: {}
|
|
816
|
+
});
|
|
817
|
+
/** Which account row is claiming right now, so only its button spins. */
|
|
818
|
+
const [claimingFor, setClaimingFor] = (0, react.useState)(void 0);
|
|
819
|
+
/**
|
|
820
|
+
* Per-account claim refusals, keyed by region then account id. A refusal is
|
|
821
|
+
* shown next to the row that caused it rather than in one global error line,
|
|
822
|
+
* because the check-in button now lives per account.
|
|
823
|
+
*/
|
|
824
|
+
const [claimErrorByRegion, setClaimErrorByRegion] = (0, react.useState)({
|
|
825
|
+
cn: {},
|
|
826
|
+
ai: {}
|
|
827
|
+
});
|
|
828
|
+
/**
|
|
829
|
+
* Whether this MACHINE has spent today's check-in for a given account — the
|
|
830
|
+
* "device is spent but this account was never rewarded" state, per account so
|
|
831
|
+
* it appears next to the row that owns it. Kept separate from the claim error
|
|
832
|
+
* because it is not a failure: it is the upstream's one-per-device-per-day
|
|
833
|
+
* rule, and rendering it through the "签到失败:…" line would present a normal
|
|
834
|
+
* rule as a plugin fault.
|
|
835
|
+
*/
|
|
836
|
+
const [deviceSpentByRegion, setDeviceSpentByRegion] = (0, react.useState)({
|
|
837
|
+
cn: {},
|
|
838
|
+
ai: {}
|
|
839
|
+
});
|
|
669
840
|
/**
|
|
670
841
|
* A refused or unpersisted settings write, surfaced instead of silently
|
|
671
842
|
* reverting. On 0.1.7 a rejected write resolves normally, so without this
|
|
672
843
|
* the control would flip and then quietly snap back on the next snapshot.
|
|
673
844
|
*/
|
|
674
845
|
const [writeError, setWriteError] = (0, react.useState)(void 0);
|
|
675
|
-
/** True while the composer-credit switch write is in flight. */
|
|
676
|
-
const [togglingCredits, setTogglingCredits] = (0, react.useState)(false);
|
|
677
846
|
const mounted = (0, react.useRef)(true);
|
|
678
847
|
(0, react.useEffect)(() => {
|
|
679
848
|
mounted.current = true;
|
|
@@ -682,21 +851,78 @@ window.__ModuleLoader__.load({
|
|
|
682
851
|
};
|
|
683
852
|
}, []);
|
|
684
853
|
/**
|
|
685
|
-
*
|
|
686
|
-
*
|
|
687
|
-
*
|
|
688
|
-
*
|
|
854
|
+
* The 设置 block: its committed values, its draft, and the ONE write path.
|
|
855
|
+
*
|
|
856
|
+
* The block commits on 「保存」 rather than on every click, so 「放弃」 has a
|
|
857
|
+
* meaning and a half-finished edit cannot land in the settings document. Both
|
|
858
|
+
* fields are written through `writeSettingsField`, which read-back checks the
|
|
859
|
+
* document — `set()` resolving has never been proof a value landed on this
|
|
860
|
+
* Host, so a saved-looking switch that silently reverted is exactly the
|
|
861
|
+
* failure that guard exists for.
|
|
862
|
+
*
|
|
863
|
+
* Committed values are read from the settings document, never echoed from
|
|
864
|
+
* local state: a write made elsewhere (the DSH settings form, another window)
|
|
865
|
+
* then shows up here instead of being shadowed by a stale copy.
|
|
866
|
+
*/
|
|
867
|
+
const committedSettings = {
|
|
868
|
+
followAppLogin: followAppLoginEnabled(settingsScope?.getSnapshot().value),
|
|
869
|
+
showPointsInMainUi: unwrapVolatileDeep(settingsScope?.getSnapshot().value)?.showPointsInMainUi === true
|
|
870
|
+
};
|
|
871
|
+
/**
|
|
872
|
+
* Ids for the block's label/hint wiring. `useId`, not constants: DSH can mount
|
|
873
|
+
* this card twice at once (the settings ROW and the expanded PAGE), and two
|
|
874
|
+
* copies sharing an id would leave the second switch labelled by the first
|
|
875
|
+
* card's element.
|
|
876
|
+
*/
|
|
877
|
+
/**
|
|
878
|
+
* What the two boxes render: the committed value, with any in-flight flip laid
|
|
879
|
+
* over it. Reading BOTH sources here is what lets the write above stay simple —
|
|
880
|
+
* it never has to write into component state that a Host refresh would fight.
|
|
881
|
+
*/
|
|
882
|
+
const settingsEdits = {
|
|
883
|
+
followAppLogin: optimisticSettings.current.followAppLogin ?? committedSettings.followAppLogin,
|
|
884
|
+
showPointsInMainUi: optimisticSettings.current.showPointsInMainUi ?? committedSettings.showPointsInMainUi
|
|
885
|
+
};
|
|
886
|
+
const settingsIds = (0, react.useId)();
|
|
887
|
+
const followLabelId = `${settingsIds}-follow`;
|
|
888
|
+
const followHintId = `${settingsIds}-follow-hint`;
|
|
889
|
+
const creditsLabelId = `${settingsIds}-credits`;
|
|
890
|
+
const creditsHintId = `${settingsIds}-credits-hint`;
|
|
891
|
+
/**
|
|
892
|
+
* Write ONE preference switch, on the click that flipped it.
|
|
893
|
+
*
|
|
894
|
+
* This replaced a draft + 「保存」/「放弃」 pair, which is what the sibling
|
|
895
|
+
* dsh-connect-workbuddy card does NOT have: its switches are plain checkboxes
|
|
896
|
+
* on a bare line that commit immediately, and a card that needs a Save button
|
|
897
|
+
* for two booleans is asking the user to remember something for no reason.
|
|
898
|
+
*
|
|
899
|
+
* The two writers are SERIALIZED by `savingSettings`, because they write the
|
|
900
|
+
* same settings document. The pattern is the tab bar's `toggleRegion`: flip
|
|
901
|
+
* optimistically so the box responds, then re-read the Host so a failed write
|
|
902
|
+
* snaps it back instead of leaving the box claiming a value that never landed.
|
|
689
903
|
*/
|
|
690
|
-
const
|
|
904
|
+
const writePreference = async (field, value) => {
|
|
691
905
|
if (settingsScope === void 0 || settingsScope.getSnapshot().writable !== true) return;
|
|
692
|
-
|
|
906
|
+
setSavingSettings(true);
|
|
693
907
|
setWriteError(void 0);
|
|
908
|
+
optimisticSettings.current = {
|
|
909
|
+
...optimisticSettings.current,
|
|
910
|
+
[field]: value
|
|
911
|
+
};
|
|
912
|
+
setSettingsRevision((revision) => revision + 1);
|
|
694
913
|
try {
|
|
695
|
-
await writeSettingsField(settingsScope,
|
|
914
|
+
await writeSettingsField(settingsScope, field, value, (readBack) => field === "followAppLogin" ? followAppLoginEnabled(readBack) === value : unwrapVolatileDeep(readBack)?.showPointsInMainUi === value);
|
|
696
915
|
} catch (error) {
|
|
697
916
|
if (mounted.current) setWriteError(error instanceof Error ? error.message : t("row.requestFailed"));
|
|
698
917
|
} finally {
|
|
699
|
-
if (mounted.current)
|
|
918
|
+
if (mounted.current) {
|
|
919
|
+
optimisticSettings.current = {
|
|
920
|
+
...optimisticSettings.current,
|
|
921
|
+
[field]: void 0
|
|
922
|
+
};
|
|
923
|
+
setSettingsRevision((revision) => revision + 1);
|
|
924
|
+
}
|
|
925
|
+
if (mounted.current) setSavingSettings(false);
|
|
700
926
|
}
|
|
701
927
|
};
|
|
702
928
|
(0, react.useEffect)(() => settingsScope?.subscribe(() => {
|
|
@@ -761,13 +987,12 @@ window.__ModuleLoader__.load({
|
|
|
761
987
|
accounts: []
|
|
762
988
|
};
|
|
763
989
|
/**
|
|
764
|
-
*
|
|
765
|
-
*
|
|
766
|
-
*
|
|
990
|
+
* Clear in-flight claim state when the tab changes. `activeRegion` is a single
|
|
991
|
+
* value across both tabs, so a switch must not leave one row's claim spinning
|
|
992
|
+
* under the other tab.
|
|
767
993
|
*/
|
|
768
994
|
(0, react.useEffect)(() => {
|
|
769
|
-
|
|
770
|
-
setDeviceSpentNotice(false);
|
|
995
|
+
setClaimingFor(void 0);
|
|
771
996
|
}, [activeRegion]);
|
|
772
997
|
(0, react.useEffect)(() => {
|
|
773
998
|
if (!open || !activeRegionOn || status.status !== "signed-in") return;
|
|
@@ -797,17 +1022,42 @@ window.__ModuleLoader__.load({
|
|
|
797
1022
|
});
|
|
798
1023
|
const body = await response.json();
|
|
799
1024
|
if (!response.ok || !Array.isArray(body.accounts)) throw new Error(`HTTP ${response.status}`);
|
|
800
|
-
|
|
1025
|
+
/**
|
|
1026
|
+
* What this press must persist, and why it is NOT `accounts.find(selected)`.
|
|
1027
|
+
*
|
|
1028
|
+
* The route's `selected` flag answers "which account is IN USE" — and the
|
|
1029
|
+
* store reports the account it is bound to, OR, when no binding has ever
|
|
1030
|
+
* been saved, the DETECTED default (`preferred`, i.e. whatever the Trae
|
|
1031
|
+
* app is signed in to). Those are the same value on a machine that has
|
|
1032
|
+
* been used for a while and diverge on exactly the press where it matters:
|
|
1033
|
+
* a fresh install, or a Trae sign-in the plugin has not recorded yet.
|
|
1034
|
+
*
|
|
1035
|
+
* Reading the flag alone therefore meant "nothing was explicitly bound"
|
|
1036
|
+
* reduced to "the detected default is what is in use, write nothing" —
|
|
1037
|
+
* so the account the user just refreshed was shown as bound while
|
|
1038
|
+
* `accounts.<region>` stayed EMPTY, and the next store rebuild (restart,
|
|
1039
|
+
* or a Trae sign-in change) silently re-defaulted somewhere else. That is
|
|
1040
|
+
* the "refreshed account was not saved" report.
|
|
1041
|
+
*
|
|
1042
|
+
* The rule is now: persist the account this region is actually using
|
|
1043
|
+
* (the store's verdict, which is the truth the card is displaying), and
|
|
1044
|
+
* fall back to the region's own roster only when the route returned no
|
|
1045
|
+
* verdict at all. A row that is already the SAVED binding is left alone —
|
|
1046
|
+
* one press must not rewrite the settings document for nothing.
|
|
1047
|
+
*/
|
|
1048
|
+
const inUse = body.accounts.find((account) => account.selected) ?? body.accounts[0];
|
|
801
1049
|
const configured = configuredAccountsOf(settingsScope?.getSnapshot().value);
|
|
802
|
-
const
|
|
803
|
-
|
|
1050
|
+
const alreadyBound = inUse !== void 0 && inUse.id === configured[activeRegion];
|
|
1051
|
+
const writable = settingsScope !== void 0 && settingsScope.getSnapshot().writable === true;
|
|
1052
|
+
const saved = inUse !== void 0 && !alreadyBound && writable;
|
|
1053
|
+
if (saved && settingsScope !== void 0 && inUse !== void 0) await writeSettingsField(settingsScope, "accounts", {
|
|
804
1054
|
...configured,
|
|
805
|
-
[activeRegion]:
|
|
806
|
-
}, (readBack) => configuredAccountsOf(readBack)[activeRegion] ===
|
|
1055
|
+
[activeRegion]: inUse.id
|
|
1056
|
+
}, (readBack) => configuredAccountsOf(readBack)[activeRegion] === inUse.id);
|
|
807
1057
|
await refreshUsage(activeRegion);
|
|
808
1058
|
if (mounted.current) setRescanNote(body.accounts.length === 0 ? t("row.accountsRefreshedNone") : t("row.accountsRefreshed", {
|
|
809
1059
|
count: String(body.accounts.length),
|
|
810
|
-
selected:
|
|
1060
|
+
selected: saved ? ` · ${t("row.accountsSaved")} ${inUse.accountName ?? inUse.id}` : ` · ${t("row.accountsKept")} ${inUse?.accountName ?? configured[activeRegion] ?? "—"}`
|
|
811
1061
|
}));
|
|
812
1062
|
} catch (error) {
|
|
813
1063
|
if (mounted.current) {
|
|
@@ -829,6 +1079,7 @@ window.__ModuleLoader__.load({
|
|
|
829
1079
|
[activeRegion]: accountId
|
|
830
1080
|
}, (readBack) => configuredAccountsOf(readBack)[activeRegion] === accountId);
|
|
831
1081
|
await refreshUsage(activeRegion);
|
|
1082
|
+
loadAccountStatus(accountId, activeRegion);
|
|
832
1083
|
} catch (error) {
|
|
833
1084
|
if (mounted.current) setWriteError(error instanceof Error ? error.message : t("row.requestFailed"));
|
|
834
1085
|
} finally {
|
|
@@ -851,30 +1102,61 @@ window.__ModuleLoader__.load({
|
|
|
851
1102
|
await switchAccount(status.appLogin.id);
|
|
852
1103
|
};
|
|
853
1104
|
/**
|
|
854
|
-
*
|
|
855
|
-
*
|
|
856
|
-
*
|
|
857
|
-
* with until the user ticks this.
|
|
1105
|
+
* Load one account row's per-account state (check-in / balances /
|
|
1106
|
+
* subscription) and cache it for the row. Called when the table renders and
|
|
1107
|
+
* again after a claim so the reward/refusal lands on the row that owns it.
|
|
858
1108
|
*/
|
|
859
|
-
const
|
|
860
|
-
if (settingsScope === void 0 || settingsScope.getSnapshot().writable !== true) return;
|
|
861
|
-
setWriteError(void 0);
|
|
1109
|
+
const loadAccountStatus = async (accountId, region = activeRegion) => {
|
|
862
1110
|
try {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
1111
|
+
const url = `${withTraeRegion(TRAE_ACCOUNT_STATUS_PATH, region)}&accountId=${encodeURIComponent(accountId)}`;
|
|
1112
|
+
const response = await fetch(url, {
|
|
1113
|
+
headers: { accept: "application/json" },
|
|
1114
|
+
credentials: "same-origin"
|
|
1115
|
+
});
|
|
1116
|
+
const body = await response.json().catch(() => ({}));
|
|
1117
|
+
if (!response.ok) throw new Error(body.error ?? `HTTP ${response.status}`);
|
|
1118
|
+
if (mounted.current) setAccountStatusByRegion((prev) => ({
|
|
1119
|
+
...prev,
|
|
1120
|
+
[region]: {
|
|
1121
|
+
...prev[region],
|
|
1122
|
+
[accountId]: body
|
|
1123
|
+
}
|
|
1124
|
+
}));
|
|
1125
|
+
return body;
|
|
1126
|
+
} catch {
|
|
1127
|
+
return;
|
|
866
1128
|
}
|
|
867
1129
|
};
|
|
868
1130
|
/**
|
|
869
|
-
*
|
|
1131
|
+
* Re-read EVERY row of one region, not just the one that was acted on.
|
|
1132
|
+
*
|
|
1133
|
+
* Trae spends the DEVICE's daily check-in, not the account's, so one claim
|
|
1134
|
+
* changes what every other row may do: they go from "check in" to "device
|
|
1135
|
+
* already used". Re-reading only the claimed account left its siblings
|
|
1136
|
+
* advertising a claim that could only come back 9095 — a row of live buttons
|
|
1137
|
+
* of which exactly one could work, which is the same false promise the 3.2.0
|
|
1138
|
+
* note refused bulk check-in for. Reading the siblings instead of guessing
|
|
1139
|
+
* them means the server's own `didCheckedIn` decides, so a sibling that is
|
|
1140
|
+
* still genuinely claimable (the device was not spent by this claim) keeps
|
|
1141
|
+
* its button.
|
|
1142
|
+
*/
|
|
1143
|
+
const reloadAccountStatuses = async (region) => {
|
|
1144
|
+
if (status.status !== "signed-in") return;
|
|
1145
|
+
const ids = status.accounts.map((account) => account.id);
|
|
1146
|
+
await Promise.all(ids.map((id) => loadAccountStatus(id, region)));
|
|
1147
|
+
};
|
|
1148
|
+
/**
|
|
1149
|
+
* Claim today's check-in reward for ONE named account row (CN only).
|
|
870
1150
|
*
|
|
871
1151
|
* The Host route owns every guard that matters — POST only, loopback origin
|
|
872
1152
|
* only, CN only, and a status read before the claim — because a browser-side
|
|
873
|
-
* check is not a guard. The button's own `disabled` is therefore a
|
|
874
|
-
* (it stops an obviously pointless click), not the protection.
|
|
1153
|
+
* check is not a guard. The row button's own `disabled` is therefore a
|
|
1154
|
+
* courtesy (it stops an obviously pointless click), not the protection. The
|
|
1155
|
+
* body carries the `accountId`, so the claim reaches THAT account's
|
|
1156
|
+
* credential without moving the whole binding.
|
|
875
1157
|
*
|
|
876
1158
|
* A refusal is not thrown away: the route answers `claimed: false` with the
|
|
877
|
-
* upstream's business code and message, and the
|
|
1159
|
+
* upstream's business code and message, and the row shows it. Three answers
|
|
878
1160
|
* are distinguished rather than collapsed into "failed":
|
|
879
1161
|
*
|
|
880
1162
|
* - `alreadyCheckedIn` — this ACCOUNT was already paid today; show it as
|
|
@@ -885,32 +1167,88 @@ window.__ModuleLoader__.load({
|
|
|
885
1167
|
* blame the plugin for an upstream rule.
|
|
886
1168
|
* - anything else — a real refusal, reported verbatim.
|
|
887
1169
|
*/
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1170
|
+
const claimFor = async (accountId) => {
|
|
1171
|
+
if (activeRegion !== "cn") return;
|
|
1172
|
+
setClaimingFor(accountId);
|
|
1173
|
+
setClaimErrorByRegion((prev) => {
|
|
1174
|
+
const next = { ...prev[activeRegion] };
|
|
1175
|
+
delete next[accountId];
|
|
1176
|
+
return {
|
|
1177
|
+
...prev,
|
|
1178
|
+
[activeRegion]: next
|
|
1179
|
+
};
|
|
1180
|
+
});
|
|
1181
|
+
setDeviceSpentByRegion((prev) => {
|
|
1182
|
+
const next = { ...prev[activeRegion] };
|
|
1183
|
+
delete next[accountId];
|
|
1184
|
+
return {
|
|
1185
|
+
...prev,
|
|
1186
|
+
[activeRegion]: next
|
|
1187
|
+
};
|
|
1188
|
+
});
|
|
892
1189
|
try {
|
|
893
1190
|
const response = await fetch(withTraeRegion(TRAE_CHECKIN_PATH, activeRegion), {
|
|
894
1191
|
method: "POST",
|
|
895
|
-
headers: {
|
|
896
|
-
|
|
1192
|
+
headers: {
|
|
1193
|
+
accept: "application/json",
|
|
1194
|
+
"content-type": "application/json"
|
|
1195
|
+
},
|
|
1196
|
+
credentials: "same-origin",
|
|
1197
|
+
body: JSON.stringify({ accountId })
|
|
897
1198
|
});
|
|
898
1199
|
const body = await response.json().catch(() => void 0);
|
|
899
1200
|
if (!response.ok) throw new Error(body?.error ?? `HTTP ${response.status}`);
|
|
900
1201
|
if (body?.claimed === false && body.deviceCheckedIn === true) {
|
|
1202
|
+
setDeviceSpentByRegion((prev) => ({
|
|
1203
|
+
...prev,
|
|
1204
|
+
[activeRegion]: {
|
|
1205
|
+
...prev[activeRegion],
|
|
1206
|
+
[accountId]: true
|
|
1207
|
+
}
|
|
1208
|
+
}));
|
|
901
1209
|
await refreshUsage(activeRegion);
|
|
902
|
-
|
|
1210
|
+
await reloadAccountStatuses(activeRegion);
|
|
903
1211
|
return;
|
|
904
1212
|
}
|
|
905
1213
|
if (body?.claimed === false && body.alreadyCheckedIn !== true) throw new Error(body.message === void 0 || body.message === "" ? t("row.checkinRefused", { code: String(body.code ?? "") }) : `${t("row.checkinRefused", { code: String(body.code ?? "") })}: ${body.message}`);
|
|
906
1214
|
await refreshUsage(activeRegion);
|
|
1215
|
+
await reloadAccountStatuses(activeRegion);
|
|
907
1216
|
} catch (error) {
|
|
908
|
-
if (mounted.current)
|
|
1217
|
+
if (mounted.current) {
|
|
1218
|
+
const message = error instanceof Error ? error.message : t("row.requestFailed");
|
|
1219
|
+
setClaimErrorByRegion((prev) => ({
|
|
1220
|
+
...prev,
|
|
1221
|
+
[activeRegion]: {
|
|
1222
|
+
...prev[activeRegion],
|
|
1223
|
+
[accountId]: message
|
|
1224
|
+
}
|
|
1225
|
+
}));
|
|
1226
|
+
}
|
|
909
1227
|
} finally {
|
|
910
|
-
if (mounted.current)
|
|
1228
|
+
if (mounted.current) setClaimingFor(void 0);
|
|
911
1229
|
}
|
|
912
1230
|
};
|
|
913
1231
|
/**
|
|
1232
|
+
* Load every account row's per-account status when the card shows a
|
|
1233
|
+
* signed-in region. This is what makes the check-in buttons, balances, and
|
|
1234
|
+
* (ai) subscription rows appear. It is scoped to a stable `status.accounts`
|
|
1235
|
+
* identity so switching tabs or re-reading the document does not re-trigger a
|
|
1236
|
+
* burst of upstream calls for rows already loaded.
|
|
1237
|
+
*/
|
|
1238
|
+
(0, react.useEffect)(() => {
|
|
1239
|
+
if (!open || !activeRegionOn || status.status !== "signed-in" || status.accounts.length === 0) return;
|
|
1240
|
+
status.accounts.map((account) => account.id).join("\0");
|
|
1241
|
+
const loadedIds = accountStatusByRegion[activeRegion];
|
|
1242
|
+
const todo = status.accounts.filter((account) => loadedIds[account.id] === void 0);
|
|
1243
|
+
if (todo.length === 0) return;
|
|
1244
|
+
for (const account of todo) loadAccountStatus(account.id, activeRegion);
|
|
1245
|
+
}, [
|
|
1246
|
+
open,
|
|
1247
|
+
activeRegionOn,
|
|
1248
|
+
status,
|
|
1249
|
+
accountStatusByRegion
|
|
1250
|
+
]);
|
|
1251
|
+
/**
|
|
914
1252
|
* Switch one region's provider off or on (issue #11). The write carries the
|
|
915
1253
|
* region's whole slot through untouched — only `enabled` changes — so the
|
|
916
1254
|
* user's directory, model picks, image opt-ins and budgets survive a
|
|
@@ -930,16 +1268,17 @@ window.__ModuleLoader__.load({
|
|
|
930
1268
|
}
|
|
931
1269
|
};
|
|
932
1270
|
/**
|
|
933
|
-
* One-click availability test for
|
|
1271
|
+
* One-click availability test for a list of models of the active region.
|
|
934
1272
|
*
|
|
935
|
-
*
|
|
936
|
-
*
|
|
937
|
-
*
|
|
938
|
-
*
|
|
939
|
-
*
|
|
1273
|
+
* Shared by the「enabled models」and「all models」buttons: the only difference
|
|
1274
|
+
* between them is which ids are sent — the enabled rows or the whole visible
|
|
1275
|
+
* catalog. Each run goes one model per request, deliberately: the host probe
|
|
1276
|
+
* is sequential anyway (Trae throttles bursts), and a per-model round trip
|
|
1277
|
+
* lets each row show its verdict as soon as it is known instead of the card
|
|
1278
|
+
* sitting blank for a minute. User-started, and it stops on the first
|
|
1279
|
+
* authentication failure — every later probe would only repeat that answer.
|
|
940
1280
|
*/
|
|
941
|
-
const
|
|
942
|
-
const ids = [...activeEnabledIds];
|
|
1281
|
+
const testModels = async (ids) => {
|
|
943
1282
|
setTestError(void 0);
|
|
944
1283
|
setTestSummary(void 0);
|
|
945
1284
|
if (ids.length === 0) {
|
|
@@ -1007,6 +1346,33 @@ window.__ModuleLoader__.load({
|
|
|
1007
1346
|
if (mounted.current) setTesting(void 0);
|
|
1008
1347
|
}
|
|
1009
1348
|
};
|
|
1349
|
+
/** Test only the enabled models of the active region. */
|
|
1350
|
+
const testEnabledModels = async () => {
|
|
1351
|
+
const ids = [...activeEnabledIds];
|
|
1352
|
+
if (ids.length === 0) {
|
|
1353
|
+
setTestError(t("row.modelsTestEmpty"));
|
|
1354
|
+
setTestSummary(void 0);
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
testModels(ids);
|
|
1358
|
+
};
|
|
1359
|
+
/**
|
|
1360
|
+
* One-click availability test for EVERY model in the active region's catalog,
|
|
1361
|
+
* enabled or not — including the rows that are off and the rows the SOLO
|
|
1362
|
+
* channel does not serve. The host validates each id against the live catalog
|
|
1363
|
+
* and reports rows it does not know as skipped, so passing the whole visible
|
|
1364
|
+
* list cannot spend quota on arbitrary config names. Costs quota per model —
|
|
1365
|
+
* meaningfully more than the「enabled only」run, which the hint states.
|
|
1366
|
+
*/
|
|
1367
|
+
const testAllModels = async () => {
|
|
1368
|
+
const ids = visibleModels.map((model) => model.id);
|
|
1369
|
+
if (ids.length === 0) {
|
|
1370
|
+
setTestError(t("row.modelsTestEmpty"));
|
|
1371
|
+
setTestSummary(void 0);
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
testModels(ids);
|
|
1375
|
+
};
|
|
1010
1376
|
const refreshModels = async () => {
|
|
1011
1377
|
setBusy(true);
|
|
1012
1378
|
try {
|
|
@@ -1083,8 +1449,20 @@ window.__ModuleLoader__.load({
|
|
|
1083
1449
|
* message — it never sends one by itself.
|
|
1084
1450
|
*/
|
|
1085
1451
|
const rowImageIsAutomatic = (model) => model.multimodal === true;
|
|
1086
|
-
/**
|
|
1087
|
-
|
|
1452
|
+
/**
|
|
1453
|
+
* The international tab's plan is rendered by `rowValue`, in the account
|
|
1454
|
+
* table's 额度 column — one place, per account, beside the account it belongs
|
|
1455
|
+
* to.
|
|
1456
|
+
*
|
|
1457
|
+
* There used to be a SECOND surface for it: a standalone 「订阅状态」 box below
|
|
1458
|
+
* the table, reporting the bound account's plan on its own. It was added to
|
|
1459
|
+
* fix 「国际版没有那个积分框」 — an asymmetry against the CN tab's credit panel
|
|
1460
|
+
* — but the table's 额度 column had since absorbed exactly that figure, so the
|
|
1461
|
+
* box repeated the row directly above it. Two renderers of one verdict is the
|
|
1462
|
+
* drift risk `plan-text.ts` exists to prevent, and the box could only ever
|
|
1463
|
+
* restate what the marked 「当前使用」 row already said. It is gone; the row is
|
|
1464
|
+
* the single surface.
|
|
1465
|
+
*/
|
|
1088
1466
|
const activeContextBudgets = draft?.contextBudgets ?? savedContextBudgets;
|
|
1089
1467
|
const dirty = draft !== void 0;
|
|
1090
1468
|
/** Apply an edit to the ACTIVE tab's draft, seeding it from the current view. */
|
|
@@ -1167,6 +1545,85 @@ window.__ModuleLoader__.load({
|
|
|
1167
1545
|
}
|
|
1168
1546
|
};
|
|
1169
1547
|
const title = t("row.title");
|
|
1548
|
+
/**
|
|
1549
|
+
* Row labels for the account table.
|
|
1550
|
+
*
|
|
1551
|
+
* The raw `edition` token ("solo") told the user nothing about WHICH product a
|
|
1552
|
+
* credential came from, which is exactly what made a leftover TRAE SOLO login
|
|
1553
|
+
* indistinguishable from the Trae CN one they thought they were bound to
|
|
1554
|
+
* (issue #25). Spell the product out, and keep the region as the second axis.
|
|
1555
|
+
*/
|
|
1556
|
+
const editionLabel = (edition) => t(`row.edition${edition === "cn" ? "Cn" : edition === "sg" ? "Sg" : edition === "solo" ? "Solo" : "SoloSg"}`);
|
|
1557
|
+
const sourceLabel = (source) => source === "desktop" ? t("row.sourceDesktop") : source === "cli" ? t("row.sourceCli") : t("row.sourceDsh");
|
|
1558
|
+
/**
|
|
1559
|
+
* The trailing value on an account row's meta line: the account's general
|
|
1560
|
+
* balance (CN) or its subscription verdict (ai).
|
|
1561
|
+
*
|
|
1562
|
+
* A status that has not loaded — or whose read failed — renders NOTHING
|
|
1563
|
+
* rather than a dash. The row already names the account, its edition and its
|
|
1564
|
+
* source; a column of placeholder glyphs was pure noise, and it made every
|
|
1565
|
+
* row look like something was wrong. `undefined` means "no figure", never
|
|
1566
|
+
* "zero".
|
|
1567
|
+
*/
|
|
1568
|
+
const rowValue = (state) => {
|
|
1569
|
+
if (activeRegion === "ai") {
|
|
1570
|
+
const payStatus = state?.payStatus;
|
|
1571
|
+
if (payStatus === void 0) return void 0;
|
|
1572
|
+
const verdict = planVerdict(payStatus);
|
|
1573
|
+
if (!verdict.paid) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1574
|
+
className: "dsm-trae-account-value",
|
|
1575
|
+
children: t("row.noPackage")
|
|
1576
|
+
});
|
|
1577
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1578
|
+
className: "dsm-trae-account-value dsm-trae-account-value-on",
|
|
1579
|
+
children: verdict.tier === void 0 ? t("row.subscribed") : t("row.planTier", { tier: verdict.tier })
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
const figure = state?.credits?.generalAvailable;
|
|
1583
|
+
if (figure === void 0) return void 0;
|
|
1584
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1585
|
+
className: "dsm-trae-account-value",
|
|
1586
|
+
children: t("row.accountBalance", { credits: formatNumber(figure) })
|
|
1587
|
+
});
|
|
1588
|
+
};
|
|
1589
|
+
/**
|
|
1590
|
+
* The CN check-in control for one account row.
|
|
1591
|
+
*
|
|
1592
|
+
* The reward rides INSIDE the button ("签到 +150") instead of sitting beside
|
|
1593
|
+
* it as a second line, so the row says what the click pays without also
|
|
1594
|
+
* carrying a "150 credits per day" caption. Four states, none of which lies:
|
|
1595
|
+
*
|
|
1596
|
+
* - claimable — live, and names the payout.
|
|
1597
|
+
* - claiming — this row's own claim is in flight; only its button changes.
|
|
1598
|
+
* - claimed — `checkedIn`: this ACCOUNT was paid, so it reads as done.
|
|
1599
|
+
* - device spent — `didCheckedIn` (or a 9095 refusal from this row's click):
|
|
1600
|
+
* the DEVICE is spent but this account was never rewarded, so the button
|
|
1601
|
+
* says exactly that and stays disabled rather than reporting "done" for an
|
|
1602
|
+
* account that got nothing. The full rule lives in `title` — rendered as a
|
|
1603
|
+
* paragraph it dominated the row and drowned out the account itself.
|
|
1604
|
+
*
|
|
1605
|
+
* Until the row's status lands the button waits, disabled, so the row keeps
|
|
1606
|
+
* its shape instead of reflowing the moment the read returns.
|
|
1607
|
+
*/
|
|
1608
|
+
const checkinControl = (account, state) => {
|
|
1609
|
+
const checkin = state?.checkin;
|
|
1610
|
+
const reward = checkin === void 0 ? void 0 : formatNumber(checkin.credits);
|
|
1611
|
+
const claimed = checkin?.checkedIn === true;
|
|
1612
|
+
const deviceSpent = deviceSpentByRegion[activeRegion][account.id] === true || checkin?.didCheckedIn === true;
|
|
1613
|
+
const spending = claimingFor === account.id;
|
|
1614
|
+
const usable = checkin !== void 0 && checkin.enabled;
|
|
1615
|
+
const title = reward === void 0 || !usable ? t("row.checkinDisabled") : deviceSpent && !claimed ? t("row.checkinDeviceSpent") : claimed ? t("row.checkinClaimed") : t("row.checkinReward", { reward });
|
|
1616
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1617
|
+
type: "button",
|
|
1618
|
+
className: "dsm-trae-account-checkin",
|
|
1619
|
+
disabled: !usable || claimed || deviceSpent || spending,
|
|
1620
|
+
title,
|
|
1621
|
+
onClick: () => {
|
|
1622
|
+
claimFor(account.id);
|
|
1623
|
+
},
|
|
1624
|
+
children: spending ? t("row.checkinClaiming") : claimed ? t("row.checkinClaimed") : deviceSpent ? t("row.checkinDeviceSpentShort") : !usable || reward === void 0 ? t("row.checkinClaim") : t("row.checkinClaimWith", { reward })
|
|
1625
|
+
});
|
|
1626
|
+
};
|
|
1170
1627
|
const label = status.status === "signed-in" ? t("row.signedIn", { accountName: status.accountName }) : status.status === "error" ? t("row.requestFailed") : t("row.signedOut");
|
|
1171
1628
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
1172
1629
|
className: `dsm-plugin-card${open ? " dsm-plugin-card-open" : ""}`,
|
|
@@ -1229,7 +1686,7 @@ window.__ModuleLoader__.load({
|
|
|
1229
1686
|
style: dotStyle(regionStatus.status)
|
|
1230
1687
|
}), item === "cn" ? t("row.tabCn") : t("row.tabAi")]
|
|
1231
1688
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1232
|
-
className: "dsm-trae-tab-switch",
|
|
1689
|
+
className: "dsm-trae-switch dsm-trae-tab-switch",
|
|
1233
1690
|
title: t("row.tabSwitchHint"),
|
|
1234
1691
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1235
1692
|
type: "checkbox",
|
|
@@ -1258,86 +1715,174 @@ window.__ModuleLoader__.load({
|
|
|
1258
1715
|
role: "status",
|
|
1259
1716
|
children: t("row.tabOffNotice")
|
|
1260
1717
|
}),
|
|
1261
|
-
|
|
1262
|
-
className: "dsm-trae-
|
|
1263
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1264
|
-
className: "dsm-trae-
|
|
1265
|
-
title: t("row.
|
|
1718
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1719
|
+
className: "dsm-trae-prefs",
|
|
1720
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1721
|
+
className: "dsm-trae-pref",
|
|
1722
|
+
title: t("row.followAppLoginHint"),
|
|
1266
1723
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1267
1724
|
type: "checkbox",
|
|
1268
|
-
checked:
|
|
1269
|
-
disabled: settingsScope?.getSnapshot().writable !== true ||
|
|
1725
|
+
checked: settingsEdits.followAppLogin,
|
|
1726
|
+
disabled: settingsScope?.getSnapshot().writable !== true || savingSettings,
|
|
1727
|
+
"aria-describedby": followHintId,
|
|
1270
1728
|
onChange: (event) => {
|
|
1271
|
-
|
|
1729
|
+
writePreference("followAppLogin", event.currentTarget.checked);
|
|
1272
1730
|
}
|
|
1273
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1731
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1732
|
+
id: followHintId,
|
|
1733
|
+
children: [
|
|
1734
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", {
|
|
1735
|
+
id: followLabelId,
|
|
1736
|
+
children: t("row.followAppLogin")
|
|
1737
|
+
}),
|
|
1738
|
+
" ",
|
|
1739
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1740
|
+
className: "dsm-trae-pref-hint",
|
|
1741
|
+
children: t("row.followAppLoginHint")
|
|
1742
|
+
})
|
|
1743
|
+
]
|
|
1744
|
+
})]
|
|
1745
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1746
|
+
className: "dsm-trae-pref",
|
|
1747
|
+
title: t(activeRegion === "cn" ? "row.showPointsInMainUiHint" : "row.showPointsInMainUiHintAi"),
|
|
1281
1748
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1282
1749
|
type: "checkbox",
|
|
1283
|
-
checked:
|
|
1284
|
-
disabled: settingsScope?.getSnapshot().writable !== true,
|
|
1750
|
+
checked: settingsEdits.showPointsInMainUi,
|
|
1751
|
+
disabled: settingsScope?.getSnapshot().writable !== true || savingSettings,
|
|
1752
|
+
"aria-describedby": creditsHintId,
|
|
1285
1753
|
onChange: (event) => {
|
|
1286
|
-
|
|
1754
|
+
writePreference("showPointsInMainUi", event.currentTarget.checked);
|
|
1287
1755
|
}
|
|
1288
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label })]
|
|
1303
|
-
}), status.status === "signed-in" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1304
|
-
className: "dsm-trae-usage-expiry",
|
|
1305
|
-
children: t("row.tokenExpiry", { expiresAt: formatDateTime(status.tokenExpiresAtMs) })
|
|
1306
|
-
}) : null]
|
|
1307
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1308
|
-
type: "button",
|
|
1309
|
-
className: "dsm-btn dsm-btn-outline",
|
|
1310
|
-
disabled: busy,
|
|
1311
|
-
onClick: () => {
|
|
1312
|
-
rescanAccounts();
|
|
1313
|
-
},
|
|
1314
|
-
children: busy ? t("row.accountsScanning") : t("row.refreshTokens")
|
|
1756
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1757
|
+
id: creditsHintId,
|
|
1758
|
+
children: [
|
|
1759
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", {
|
|
1760
|
+
id: creditsLabelId,
|
|
1761
|
+
children: t("row.showPointsInMainUi")
|
|
1762
|
+
}),
|
|
1763
|
+
" ",
|
|
1764
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1765
|
+
className: "dsm-trae-pref-hint",
|
|
1766
|
+
children: t(activeRegion === "cn" ? "row.showPointsInMainUiHint" : "row.showPointsInMainUiHintAi")
|
|
1767
|
+
})
|
|
1768
|
+
]
|
|
1769
|
+
})]
|
|
1315
1770
|
})]
|
|
1316
1771
|
}),
|
|
1317
|
-
|
|
1318
|
-
className: "dsm-trae-
|
|
1772
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1773
|
+
className: "dsm-trae-accounts",
|
|
1319
1774
|
"aria-label": t("row.accountsTitle"),
|
|
1320
1775
|
children: [
|
|
1321
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1322
|
-
className: "dsm-trae-
|
|
1323
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
1324
|
-
className: "dsm-trae-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1776
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1777
|
+
className: "dsm-trae-accounts-head",
|
|
1778
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1779
|
+
className: "dsm-trae-accounts-title",
|
|
1780
|
+
children: t("row.accountsTitle")
|
|
1781
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1782
|
+
className: "dsm-trae-accounts-hint",
|
|
1783
|
+
children: t("row.accountsHint")
|
|
1784
|
+
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1785
|
+
type: "button",
|
|
1786
|
+
className: "dsm-btn dsm-btn-outline",
|
|
1787
|
+
disabled: busy,
|
|
1788
|
+
onClick: () => {
|
|
1789
|
+
rescanAccounts();
|
|
1329
1790
|
},
|
|
1330
|
-
children:
|
|
1331
|
-
|
|
1791
|
+
children: busy ? t("row.accountsScanning") : t("row.refreshTokens")
|
|
1792
|
+
})]
|
|
1793
|
+
}),
|
|
1794
|
+
status.status !== "signed-in" || status.accounts.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1795
|
+
className: "dsm-trae-usage-account",
|
|
1796
|
+
role: "status",
|
|
1797
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1798
|
+
className: "dsm-trae-usage-account-copy",
|
|
1799
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1800
|
+
className: "dsm-trae-usage-status",
|
|
1801
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1802
|
+
"aria-hidden": "true",
|
|
1803
|
+
className: "dsm-trae-usage-dot",
|
|
1804
|
+
style: dotStyle(status.status)
|
|
1805
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label })]
|
|
1806
|
+
}), status.status === "signed-in" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1807
|
+
className: "dsm-trae-usage-expiry",
|
|
1808
|
+
children: t("row.tokenExpiry", { expiresAt: formatDateTime(status.tokenExpiresAtMs) })
|
|
1809
|
+
}) : null]
|
|
1810
|
+
})
|
|
1811
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1812
|
+
className: `dsm-trae-account-table${activeRegion === "cn" ? " dsm-trae-account-table-checkin" : ""}`,
|
|
1813
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1814
|
+
className: "dsm-trae-account-row dsm-trae-account-row-head",
|
|
1815
|
+
role: "row",
|
|
1816
|
+
children: [
|
|
1817
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.accountColumnAccount") }),
|
|
1818
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.accountColumnValue") }),
|
|
1819
|
+
activeRegion === "cn" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.accountColumnCheckin") }) : null,
|
|
1820
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {})
|
|
1821
|
+
]
|
|
1822
|
+
}), status.accounts.map((account) => {
|
|
1823
|
+
const current = status.status === "signed-in" && account.id === status.accountId;
|
|
1824
|
+
const state = accountStatusByRegion[activeRegion][account.id];
|
|
1825
|
+
const error = claimErrorByRegion[activeRegion][account.id];
|
|
1826
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1827
|
+
className: `dsm-trae-account-row${current ? " dsm-trae-account-row-current" : ""}`,
|
|
1828
|
+
role: "row",
|
|
1332
1829
|
children: [
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1830
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1831
|
+
className: "dsm-trae-account-who",
|
|
1832
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1833
|
+
"aria-hidden": "true",
|
|
1834
|
+
className: "dsm-trae-usage-dot",
|
|
1835
|
+
style: dotStyle(account.selected ? "signed-in" : "signed-out")
|
|
1836
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1837
|
+
className: "dsm-trae-account-name",
|
|
1838
|
+
children: [
|
|
1839
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: account.accountName }),
|
|
1840
|
+
current ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1841
|
+
className: "dsm-trae-account-badge",
|
|
1842
|
+
children: t("row.accountCurrentBadge")
|
|
1843
|
+
}) : null,
|
|
1844
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1845
|
+
className: "dsm-trae-account-meta",
|
|
1846
|
+
children: [
|
|
1847
|
+
editionLabel(account.edition),
|
|
1848
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
|
|
1849
|
+
"aria-hidden": "true",
|
|
1850
|
+
children: "·"
|
|
1851
|
+
}),
|
|
1852
|
+
sourceLabel(account.source)
|
|
1853
|
+
]
|
|
1854
|
+
}),
|
|
1855
|
+
error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1856
|
+
className: "dsm-trae-account-note",
|
|
1857
|
+
role: "alert",
|
|
1858
|
+
children: t("row.checkinError", { message: error })
|
|
1859
|
+
})
|
|
1860
|
+
]
|
|
1861
|
+
})]
|
|
1862
|
+
}),
|
|
1863
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1864
|
+
className: "dsm-trae-account-worth",
|
|
1865
|
+
children: rowValue(state)
|
|
1866
|
+
}),
|
|
1867
|
+
activeRegion === "cn" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1868
|
+
className: "dsm-trae-account-checkin-cell",
|
|
1869
|
+
children: checkinControl(account, state)
|
|
1870
|
+
}) : null,
|
|
1871
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1872
|
+
className: "dsm-trae-account-actions",
|
|
1873
|
+
children: current ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1874
|
+
type: "button",
|
|
1875
|
+
className: "dsm-btn dsm-btn-outline dsm-trae-account-switch",
|
|
1876
|
+
disabled: switchingAccount || settingsScope?.getSnapshot()?.writable !== true,
|
|
1877
|
+
onClick: () => {
|
|
1878
|
+
switchAccount(account.id);
|
|
1879
|
+
},
|
|
1880
|
+
children: switchingAccount ? t("row.accountSwitching") : t("row.accountSwitch")
|
|
1881
|
+
})
|
|
1882
|
+
})
|
|
1338
1883
|
]
|
|
1339
|
-
}, account.id)
|
|
1340
|
-
})
|
|
1884
|
+
}, account.id);
|
|
1885
|
+
})]
|
|
1341
1886
|
}),
|
|
1342
1887
|
status.status === "signed-in" && status.alternatives !== void 0 && status.alternatives.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1343
1888
|
className: "dsm-trae-usage-alternative",
|
|
@@ -1365,7 +1910,10 @@ window.__ModuleLoader__.load({
|
|
|
1365
1910
|
role: "status",
|
|
1366
1911
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1367
1912
|
className: "dsm-trae-usage-alternative-text",
|
|
1368
|
-
children: t("row.appLoginMismatch", {
|
|
1913
|
+
children: t("row.appLoginMismatch", {
|
|
1914
|
+
account: status.appLogin.accountName,
|
|
1915
|
+
bound: status.accountName
|
|
1916
|
+
})
|
|
1369
1917
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1370
1918
|
type: "button",
|
|
1371
1919
|
className: "dsm-trae-usage-switch-button",
|
|
@@ -1373,7 +1921,7 @@ window.__ModuleLoader__.load({
|
|
|
1373
1921
|
onClick: () => {
|
|
1374
1922
|
followAppLogin();
|
|
1375
1923
|
},
|
|
1376
|
-
children: t("row.appLoginFollow")
|
|
1924
|
+
children: t("row.appLoginFollow", { account: status.appLogin.accountName })
|
|
1377
1925
|
})]
|
|
1378
1926
|
}) : null,
|
|
1379
1927
|
rescanNote === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -1382,132 +1930,8 @@ window.__ModuleLoader__.load({
|
|
|
1382
1930
|
children: rescanNote
|
|
1383
1931
|
})
|
|
1384
1932
|
]
|
|
1385
|
-
})
|
|
1933
|
+
}),
|
|
1386
1934
|
status.status === "signed-in" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1387
|
-
status.payStatus === void 0 || payPlan === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1388
|
-
className: "dsm-trae-usage-list",
|
|
1389
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1390
|
-
className: "dsm-trae-usage-stats dsm-trae-usage-stats-two",
|
|
1391
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1392
|
-
className: "dsm-trae-usage-stat dsm-trae-usage-stat-general",
|
|
1393
|
-
children: [
|
|
1394
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1395
|
-
className: "dsm-trae-usage-stat-head",
|
|
1396
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1397
|
-
className: "dsm-trae-usage-stat-label",
|
|
1398
|
-
children: t("row.subscriptionLabel")
|
|
1399
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1400
|
-
className: `dsm-trae-usage-stat-badge ${payPlan.paid ? "dsm-trae-usage-stat-badge-on" : "dsm-trae-usage-stat-badge-off"}`,
|
|
1401
|
-
children: t(payPlan.label)
|
|
1402
|
-
})]
|
|
1403
|
-
}),
|
|
1404
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1405
|
-
className: "dsm-trae-usage-stat-value dsm-trae-usage-stat-value-general",
|
|
1406
|
-
children: status.payStatus.inTrial && status.payStatus.trialEndTimeMs > 0 ? t("row.trialUntil", { date: formatDateTime(status.payStatus.trialEndTimeMs) }) : [t(payPlan.label), payPlan.tier].filter(Boolean).join(" · ")
|
|
1407
|
-
}),
|
|
1408
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1409
|
-
className: "dsm-trae-usage-stat-hint",
|
|
1410
|
-
children: status.payStatus.enableSoloLite || status.payStatus.enableSoloCoder || status.payStatus.enableSoloBuilder || status.payStatus.enableSoloWeb ? [
|
|
1411
|
-
status.payStatus.enableSoloLite ? "SOLO Lite" : "",
|
|
1412
|
-
status.payStatus.enableSoloCoder ? "SOLO Coder" : "",
|
|
1413
|
-
status.payStatus.enableSoloBuilder ? "SOLO Builder" : "",
|
|
1414
|
-
status.payStatus.enableSoloWeb ? "SOLO Web" : ""
|
|
1415
|
-
].filter(Boolean).join(" · ") : status.payStatus.isDollarUsageBilling ? "dollar usage billing" : ""
|
|
1416
|
-
})
|
|
1417
|
-
]
|
|
1418
|
-
})
|
|
1419
|
-
})
|
|
1420
|
-
}),
|
|
1421
|
-
status.payStatusError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1422
|
-
className: "dsm-trae-usage-error",
|
|
1423
|
-
children: t("row.payStatusError", { message: status.payStatusError })
|
|
1424
|
-
}),
|
|
1425
|
-
status.credits === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1426
|
-
className: "dsm-trae-usage-list",
|
|
1427
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1428
|
-
className: "dsm-trae-usage-stats dsm-trae-usage-stats-two",
|
|
1429
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1430
|
-
className: "dsm-trae-usage-stat dsm-trae-usage-stat-work",
|
|
1431
|
-
children: [
|
|
1432
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1433
|
-
className: "dsm-trae-usage-stat-head",
|
|
1434
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1435
|
-
className: "dsm-trae-usage-stat-label",
|
|
1436
|
-
children: t("row.workCreditsLabel")
|
|
1437
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1438
|
-
className: "dsm-trae-usage-stat-badge dsm-trae-usage-stat-badge-off",
|
|
1439
|
-
children: t("row.workNotUsable")
|
|
1440
|
-
})]
|
|
1441
|
-
}),
|
|
1442
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1443
|
-
className: "dsm-trae-usage-stat-value dsm-trae-usage-stat-value-work",
|
|
1444
|
-
children: formatNumber(status.credits.workAvailable)
|
|
1445
|
-
}),
|
|
1446
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1447
|
-
className: "dsm-trae-usage-stat-hint",
|
|
1448
|
-
children: t("row.workUsableHint")
|
|
1449
|
-
})
|
|
1450
|
-
]
|
|
1451
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1452
|
-
className: "dsm-trae-usage-stat dsm-trae-usage-stat-general",
|
|
1453
|
-
children: [
|
|
1454
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1455
|
-
className: "dsm-trae-usage-stat-head",
|
|
1456
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1457
|
-
className: "dsm-trae-usage-stat-label",
|
|
1458
|
-
children: t("row.generalCreditsLabel")
|
|
1459
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1460
|
-
className: "dsm-trae-usage-stat-badge dsm-trae-usage-stat-badge-on",
|
|
1461
|
-
children: t("row.generalUsable")
|
|
1462
|
-
})]
|
|
1463
|
-
}),
|
|
1464
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1465
|
-
className: "dsm-trae-usage-stat-value dsm-trae-usage-stat-value-general",
|
|
1466
|
-
children: formatNumber(status.credits.generalAvailable)
|
|
1467
|
-
}),
|
|
1468
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1469
|
-
className: "dsm-trae-usage-stat-hint",
|
|
1470
|
-
children: t("row.generalUsableHint")
|
|
1471
|
-
})
|
|
1472
|
-
]
|
|
1473
|
-
})]
|
|
1474
|
-
})
|
|
1475
|
-
}),
|
|
1476
|
-
status.creditsError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1477
|
-
className: "dsm-trae-usage-error",
|
|
1478
|
-
children: t("row.creditsError", { message: status.creditsError })
|
|
1479
|
-
}),
|
|
1480
|
-
status.checkin === void 0 ? null : (() => {
|
|
1481
|
-
const accountClaimed = status.checkin.checkedIn;
|
|
1482
|
-
const deviceSpent = !accountClaimed && (status.checkin.didCheckedIn || deviceSpentNotice);
|
|
1483
|
-
const reward = formatNumber(status.checkin.credits);
|
|
1484
|
-
const bonus = status.checkin.extraCredits === void 0 ? "" : ` + ${formatNumber(status.checkin.extraCredits)}`;
|
|
1485
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1486
|
-
className: "dsm-trae-checkin",
|
|
1487
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1488
|
-
className: "dsm-trae-checkin-copy",
|
|
1489
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1490
|
-
className: "dsm-trae-checkin-label",
|
|
1491
|
-
children: t("row.checkinLabel")
|
|
1492
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1493
|
-
className: "dsm-trae-checkin-hint",
|
|
1494
|
-
children: [t("row.checkinReward", { reward: `${reward}${bonus}` }), accountClaimed ? ` · ${t("row.checkinDoneHint")}` : ""]
|
|
1495
|
-
})]
|
|
1496
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1497
|
-
type: "button",
|
|
1498
|
-
className: "dsm-btn dsm-btn-primary dsm-trae-checkin-button",
|
|
1499
|
-
disabled: !status.checkin.enabled || accountClaimed || deviceSpent || claiming,
|
|
1500
|
-
onClick: () => {
|
|
1501
|
-
claimCheckin();
|
|
1502
|
-
},
|
|
1503
|
-
children: claiming ? t("row.checkinClaiming") : accountClaimed ? t("row.checkinClaimed") : t("row.checkinClaim")
|
|
1504
|
-
})]
|
|
1505
|
-
}), deviceSpent ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1506
|
-
className: "dsm-trae-checkin-note",
|
|
1507
|
-
role: "status",
|
|
1508
|
-
children: t("row.checkinDeviceSpent")
|
|
1509
|
-
}) : null] });
|
|
1510
|
-
})(),
|
|
1511
1935
|
status.checkin !== void 0 && !status.checkin.enabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1512
1936
|
className: "dsm-trae-usage-error",
|
|
1513
1937
|
children: t("row.checkinDisabled")
|
|
@@ -1516,11 +1940,6 @@ window.__ModuleLoader__.load({
|
|
|
1516
1940
|
className: "dsm-trae-usage-error",
|
|
1517
1941
|
children: t("row.checkinError", { message: status.checkinError })
|
|
1518
1942
|
}),
|
|
1519
|
-
claimError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1520
|
-
className: "dsm-trae-usage-error",
|
|
1521
|
-
role: "alert",
|
|
1522
|
-
children: t("row.checkinError", { message: claimError })
|
|
1523
|
-
}),
|
|
1524
1943
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1525
1944
|
className: "dsm-trae-models",
|
|
1526
1945
|
"aria-label": t("row.modelsTitle"),
|
|
@@ -1535,27 +1954,43 @@ window.__ModuleLoader__.load({
|
|
|
1535
1954
|
children: t("row.modelsSummary", { count: activeEnabledIds.size })
|
|
1536
1955
|
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1537
1956
|
className: "dsm-trae-models-actions",
|
|
1538
|
-
children: [
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1957
|
+
children: [
|
|
1958
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1959
|
+
type: "button",
|
|
1960
|
+
className: "dsm-btn dsm-btn-outline",
|
|
1961
|
+
disabled: busy,
|
|
1962
|
+
onClick: () => {
|
|
1963
|
+
refreshModels();
|
|
1964
|
+
},
|
|
1965
|
+
children: busy ? t("row.modelsRefreshing") : t("row.modelsRefresh")
|
|
1966
|
+
}),
|
|
1967
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1968
|
+
type: "button",
|
|
1969
|
+
className: "dsm-btn dsm-btn-outline",
|
|
1970
|
+
disabled: busy || testing !== void 0 || activeEnabledIds.size === 0,
|
|
1971
|
+
title: t("row.modelsTestHint"),
|
|
1972
|
+
onClick: () => {
|
|
1973
|
+
testEnabledModels();
|
|
1974
|
+
},
|
|
1975
|
+
children: testing === void 0 ? t("row.modelsTestEnabled") : t("row.modelsTesting", {
|
|
1976
|
+
done: testing.done,
|
|
1977
|
+
total: testing.total
|
|
1978
|
+
})
|
|
1979
|
+
}),
|
|
1980
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1981
|
+
type: "button",
|
|
1982
|
+
className: "dsm-btn dsm-btn-outline",
|
|
1983
|
+
disabled: busy || testing !== void 0 || visibleModels.length === 0,
|
|
1984
|
+
title: t("row.modelsTestAllHint"),
|
|
1985
|
+
onClick: () => {
|
|
1986
|
+
testAllModels();
|
|
1987
|
+
},
|
|
1988
|
+
children: testing === void 0 ? t("row.modelsTestAll") : t("row.modelsTesting", {
|
|
1989
|
+
done: testing.done,
|
|
1990
|
+
total: testing.total
|
|
1991
|
+
})
|
|
1557
1992
|
})
|
|
1558
|
-
|
|
1993
|
+
]
|
|
1559
1994
|
})]
|
|
1560
1995
|
}),
|
|
1561
1996
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -1897,20 +2332,30 @@ window.__ModuleLoader__.load({
|
|
|
1897
2332
|
*/
|
|
1898
2333
|
const COMPOSER_POINTS_REFRESH_INTERVAL_MS = 3e5;
|
|
1899
2334
|
/**
|
|
1900
|
-
* Provider routes
|
|
1901
|
-
*
|
|
2335
|
+
* Provider routes this readout serves, mapped to the region to read from.
|
|
2336
|
+
*
|
|
2337
|
+
* BOTH regions, but they do not show the same KIND of number, and that is the
|
|
2338
|
+
* data's doing rather than a preference:
|
|
1902
2339
|
*
|
|
1903
|
-
*
|
|
1904
|
-
*
|
|
1905
|
-
* `credits` on it (`webStatusFor` returns early
|
|
1906
|
-
*
|
|
1907
|
-
*
|
|
1908
|
-
* — a number-slot with no number — while an international model is selected.
|
|
2340
|
+
* - `cn` is credit-based, so the readout shows the account's general balance.
|
|
2341
|
+
* - `ai` is subscription-based. The Host builds its usage document from
|
|
2342
|
+
* `payStatus` and never sets `credits` on it (`webStatusFor` returns early
|
|
2343
|
+
* for `region === 'ai'`), so there is no balance to show. What it does carry
|
|
2344
|
+
* is the PLAN, so the readout names the plan (`Pro` / `Free`) instead.
|
|
1909
2345
|
*
|
|
1910
|
-
*
|
|
1911
|
-
*
|
|
2346
|
+
* The international route was previously omitted entirely, which meant the
|
|
2347
|
+
* readout vanished while an international model was selected — the asymmetry a
|
|
2348
|
+
* user reported. Omitting it was the safer half of a real problem (a plan-shaped
|
|
2349
|
+
* document templated into a number slot renders a dash), but the fix is to
|
|
2350
|
+
* render what that side actually has, not to render nothing.
|
|
2351
|
+
*
|
|
2352
|
+
* Mirrors `TRAE_PROVIDERS` on the host side; duplicated here because the client
|
|
2353
|
+
* bundle must not import the host entry.
|
|
1912
2354
|
*/
|
|
1913
|
-
const TRAE_COMPOSER_PROVIDERS = {
|
|
2355
|
+
const TRAE_COMPOSER_PROVIDERS = {
|
|
2356
|
+
"trae": "cn",
|
|
2357
|
+
"trae-global": "ai"
|
|
2358
|
+
};
|
|
1914
2359
|
/** The general credit balance out of one usage document, or undefined. */
|
|
1915
2360
|
function generalCreditsOf(usage) {
|
|
1916
2361
|
if (usage.status !== "signed-in") return void 0;
|
|
@@ -1933,6 +2378,16 @@ window.__ModuleLoader__.load({
|
|
|
1933
2378
|
const activeRegion = region ?? owned;
|
|
1934
2379
|
const [usage, setUsage] = (0, react.useState)(void 0);
|
|
1935
2380
|
const [accountCredits, setAccountCredits] = (0, react.useState)(void 0);
|
|
2381
|
+
/**
|
|
2382
|
+
* Per-account PLANS, international region only.
|
|
2383
|
+
*
|
|
2384
|
+
* The CN panel reads the credit route; that route is CN-shaped (it calls the
|
|
2385
|
+
* credit snapshot, which the ai document does not carry), so the international
|
|
2386
|
+
* panel reads the per-account STATUS route instead — the same one the card's
|
|
2387
|
+
* roster uses, which answers `payStatus` for ai. Reads only while the panel is
|
|
2388
|
+
* open, for the same reason the credit route does.
|
|
2389
|
+
*/
|
|
2390
|
+
const [accountPlans, setAccountPlans] = (0, react.useState)(void 0);
|
|
1936
2391
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
1937
2392
|
const [switchingId, setSwitchingId] = (0, react.useState)(void 0);
|
|
1938
2393
|
const [failed, setFailed] = (0, react.useState)(false);
|
|
@@ -2018,17 +2473,51 @@ window.__ModuleLoader__.load({
|
|
|
2018
2473
|
controller.abort();
|
|
2019
2474
|
};
|
|
2020
2475
|
}, [owned, fetchUsage]);
|
|
2476
|
+
/**
|
|
2477
|
+
* Every account's PLAN, one status read per account — ai only.
|
|
2478
|
+
*
|
|
2479
|
+
* Costs one upstream call per account, paid only while the panel is open (the
|
|
2480
|
+
* same rule the credit route follows). A failed read leaves that account's plan
|
|
2481
|
+
* undefined, which the row renders as a dash: the row still switches accounts,
|
|
2482
|
+
* which is the panel's primary job.
|
|
2483
|
+
*/
|
|
2484
|
+
const fetchAccountPlans = (0, react.useCallback)(async (signal) => {
|
|
2485
|
+
if (activeRegion !== "ai") return;
|
|
2486
|
+
const roster = usage?.status === "signed-in" ? usage.accounts.filter((account) => account.region === "ai") : [];
|
|
2487
|
+
if (roster.length === 0) {
|
|
2488
|
+
setAccountPlans({});
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
const entries = await Promise.all(roster.map(async (account) => {
|
|
2492
|
+
try {
|
|
2493
|
+
const response = await fetch(`${withTraeRegion(TRAE_ACCOUNT_STATUS_PATH, "ai")}&accountId=${encodeURIComponent(account.id)}`, {
|
|
2494
|
+
headers: { accept: "application/json" },
|
|
2495
|
+
credentials: "same-origin",
|
|
2496
|
+
...signal === void 0 ? {} : { signal }
|
|
2497
|
+
});
|
|
2498
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
2499
|
+
const state = await response.json();
|
|
2500
|
+
return [account.id, state.payStatus];
|
|
2501
|
+
} catch {
|
|
2502
|
+
return [account.id, void 0];
|
|
2503
|
+
}
|
|
2504
|
+
}));
|
|
2505
|
+
if (!mounted.current || signal?.aborted === true) return;
|
|
2506
|
+
setAccountPlans(Object.fromEntries(entries));
|
|
2507
|
+
}, [activeRegion, usage]);
|
|
2021
2508
|
(0, react.useEffect)(() => {
|
|
2022
2509
|
if (!open || activeRegion === void 0) return void 0;
|
|
2023
2510
|
const controller = new AbortController();
|
|
2024
|
-
|
|
2511
|
+
if (activeRegion === "ai") fetchAccountPlans(controller.signal);
|
|
2512
|
+
else fetchAccountCredits(controller.signal);
|
|
2025
2513
|
return () => {
|
|
2026
2514
|
controller.abort();
|
|
2027
2515
|
};
|
|
2028
2516
|
}, [
|
|
2029
2517
|
open,
|
|
2030
2518
|
activeRegion,
|
|
2031
|
-
fetchAccountCredits
|
|
2519
|
+
fetchAccountCredits,
|
|
2520
|
+
fetchAccountPlans
|
|
2032
2521
|
]);
|
|
2033
2522
|
(0, react.useEffect)(() => {
|
|
2034
2523
|
if (!open) return void 0;
|
|
@@ -2065,7 +2554,8 @@ window.__ModuleLoader__.load({
|
|
|
2065
2554
|
const readBack = configuredAccountsOf(settingsScope.getSnapshot().value);
|
|
2066
2555
|
if (accepted === false || readBack[activeRegion] !== accountId) throw new Error("the Host did not persist the switch");
|
|
2067
2556
|
await fetchUsage();
|
|
2068
|
-
await
|
|
2557
|
+
if (activeRegion === "ai") await fetchAccountPlans();
|
|
2558
|
+
else await fetchAccountCredits();
|
|
2069
2559
|
} catch {
|
|
2070
2560
|
if (mounted.current) setFailed(true);
|
|
2071
2561
|
} finally {
|
|
@@ -2074,10 +2564,21 @@ window.__ModuleLoader__.load({
|
|
|
2074
2564
|
};
|
|
2075
2565
|
if (owned === void 0) return null;
|
|
2076
2566
|
const signedIn = usage?.status === "signed-in" ? usage : void 0;
|
|
2077
|
-
const general = signedIn === void 0 ? void 0 : generalCreditsOf(signedIn);
|
|
2078
2567
|
const signedOut = usage !== void 0 && usage.status === "signed-out";
|
|
2079
|
-
const
|
|
2080
|
-
|
|
2568
|
+
const general = signedIn === void 0 ? void 0 : generalCreditsOf(signedIn);
|
|
2569
|
+
/**
|
|
2570
|
+
* What the readout shows after the separator.
|
|
2571
|
+
*
|
|
2572
|
+
* Region-dependent because the two sides answer different questions: CN has a
|
|
2573
|
+
* spendable balance, ai has a plan. Reading the credit field on an ai document
|
|
2574
|
+
* would always be `undefined` and print the dash this change exists to remove.
|
|
2575
|
+
*/
|
|
2576
|
+
const valueText = activeRegion === "ai" ? signedIn?.payStatus === void 0 ? "—" : planText(signedIn.payStatus, t) : general === void 0 ? "—" : formatCredits(general);
|
|
2577
|
+
/**
|
|
2578
|
+
* The label before the separator. Per REGION, not one hardcoded 'Trae CN':
|
|
2579
|
+
* the international readout sat next to the wrong product name otherwise.
|
|
2580
|
+
*/
|
|
2581
|
+
const label = activeRegion === "ai" ? t("composer.pointsGlobal") : t("composer.points");
|
|
2081
2582
|
/**
|
|
2082
2583
|
* The table's rows: every account in this region, with its general balance.
|
|
2083
2584
|
*
|
|
@@ -2087,7 +2588,11 @@ window.__ModuleLoader__.load({
|
|
|
2087
2588
|
* than a spinner the user has to wait on.
|
|
2088
2589
|
*/
|
|
2089
2590
|
const selectedId = accountCredits?.find((account) => account.selected)?.id ?? (signedIn === void 0 ? void 0 : signedIn.accountId);
|
|
2090
|
-
const rows = (
|
|
2591
|
+
const rows = (activeRegion === "ai" ? signedIn === void 0 ? [] : signedIn.accounts.filter((account) => account.region === "ai").map((account) => ({
|
|
2592
|
+
id: account.id,
|
|
2593
|
+
accountName: account.accountName,
|
|
2594
|
+
selected: account.selected
|
|
2595
|
+
})) : accountCredits ?? (signedIn === void 0 ? [] : signedIn.accounts.filter((account) => account.region === activeRegion).map((account) => ({
|
|
2091
2596
|
id: account.id,
|
|
2092
2597
|
accountName: account.accountName,
|
|
2093
2598
|
selected: account.selected
|
|
@@ -2124,13 +2629,13 @@ window.__ModuleLoader__.load({
|
|
|
2124
2629
|
top: 0
|
|
2125
2630
|
},
|
|
2126
2631
|
role: "dialog",
|
|
2127
|
-
"aria-label": t("composer.panelTitle"),
|
|
2632
|
+
"aria-label": activeRegion === "ai" ? t("composer.panelTitleGlobal") : t("composer.panelTitle"),
|
|
2128
2633
|
children: [
|
|
2129
2634
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2130
2635
|
className: "dsm-trae-composer-panel-head",
|
|
2131
2636
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2132
2637
|
className: "dsm-trae-composer-panel-title",
|
|
2133
|
-
children: t("composer.panelTitle")
|
|
2638
|
+
children: activeRegion === "ai" ? t("composer.panelTitleGlobal") : t("composer.panelTitle")
|
|
2134
2639
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2135
2640
|
type: "button",
|
|
2136
2641
|
className: "dsm-trae-composer-panel-refresh",
|
|
@@ -2155,7 +2660,7 @@ window.__ModuleLoader__.load({
|
|
|
2155
2660
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
2156
2661
|
scope: "col",
|
|
2157
2662
|
className: "dsm-trae-composer-panel-num",
|
|
2158
|
-
children: t("composer.generalCredits")
|
|
2663
|
+
children: activeRegion === "ai" ? t("composer.plan") : t("composer.generalCredits")
|
|
2159
2664
|
})] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: rows.map((row) => {
|
|
2160
2665
|
const current = row.id === selectedId;
|
|
2161
2666
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
|
|
@@ -2177,7 +2682,10 @@ window.__ModuleLoader__.load({
|
|
|
2177
2682
|
})
|
|
2178
2683
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2179
2684
|
className: "dsm-trae-composer-panel-num",
|
|
2180
|
-
children:
|
|
2685
|
+
children: activeRegion === "ai" ? (() => {
|
|
2686
|
+
const payStatus = row.id === selectedId && signedIn?.payStatus !== void 0 ? signedIn.payStatus : accountPlans?.[row.id];
|
|
2687
|
+
return payStatus === void 0 ? "—" : planText(payStatus, t);
|
|
2688
|
+
})() : row.generalAvailable === void 0 ? "—" : formatCredits(row.generalAvailable)
|
|
2181
2689
|
})]
|
|
2182
2690
|
}, row.id);
|
|
2183
2691
|
}) })]
|
|
@@ -2276,10 +2784,10 @@ window.__ModuleLoader__.load({
|
|
|
2276
2784
|
"row.collapse": "Collapse",
|
|
2277
2785
|
"row.tabCn": "Domestic",
|
|
2278
2786
|
"row.tabAi": "Global",
|
|
2279
|
-
"row.tabHint": "
|
|
2280
|
-
"row.tabSwitchHint": "
|
|
2787
|
+
"row.tabHint": "Two independent providers, each with its own account, usage, and models. Both are live at once and never affect each other.",
|
|
2788
|
+
"row.tabSwitchHint": "Turns this provider off: its models leave the DSH picker. Account and settings are kept.",
|
|
2281
2789
|
"row.tabSwitchAria": "Offer the {region} provider to DSH",
|
|
2282
|
-
"row.tabOffNotice": "This provider is
|
|
2790
|
+
"row.tabOffNotice": "This provider is off: its models are not offered to DSH. Account and settings are kept.",
|
|
2283
2791
|
"row.settingsWriteFailed": "The setting was not saved: {message}",
|
|
2284
2792
|
"row.signedOut": "Not signed in",
|
|
2285
2793
|
"row.signedOutHint": "Sign in once in the Trae desktop app; this plugin follows that sign-in automatically.",
|
|
@@ -2304,52 +2812,68 @@ window.__ModuleLoader__.load({
|
|
|
2304
2812
|
"row.creditsConsumedLabel": "Consumed",
|
|
2305
2813
|
"row.creditsTotalLabel": "Total",
|
|
2306
2814
|
"row.creditsError": "Usage unavailable: {message}",
|
|
2307
|
-
"row.checkinLabel": "Daily check-in",
|
|
2308
2815
|
"row.checkinReward": "{reward} credits per day",
|
|
2309
|
-
"row.checkinClaim": "
|
|
2816
|
+
"row.checkinClaim": "Check in",
|
|
2817
|
+
"row.checkinClaimWith": "Check in +{reward}",
|
|
2310
2818
|
"row.checkinClaiming": "Claiming…",
|
|
2311
|
-
"row.checkinClaimed": "Claimed
|
|
2312
|
-
"row.checkinDoneHint": "come back tomorrow",
|
|
2819
|
+
"row.checkinClaimed": "Claimed",
|
|
2313
2820
|
"row.checkinRefused": "Trae refused the check-in (code {code})",
|
|
2314
2821
|
"row.checkinError": "Check-in failed: {message}",
|
|
2315
2822
|
"row.checkinDeviceSpent": "This machine already used today's check-in — Trae allows one per device per day, so this account cannot claim here today. Claim it on another device or from the account that checked in, or come back tomorrow.",
|
|
2316
2823
|
"row.checkinDisabled": "This account has no check-in activity available.",
|
|
2317
|
-
"row.
|
|
2318
|
-
"row.regionAi": "International",
|
|
2824
|
+
"row.checkinDeviceSpentShort": "Device used",
|
|
2319
2825
|
"row.subscriptionLabel": "Subscription",
|
|
2320
2826
|
"row.subscribed": "Active package",
|
|
2321
2827
|
"row.noPackage": "No active package",
|
|
2828
|
+
"row.planTier": "{tier} plan",
|
|
2322
2829
|
"row.trialUntil": "Trial until {date}",
|
|
2323
2830
|
"row.payStatusError": "Subscription status unavailable: {message}",
|
|
2831
|
+
"row.payStatusUnknown": "Plan not loaded yet",
|
|
2324
2832
|
"row.refresh": "Refresh",
|
|
2325
2833
|
"row.refreshTokens": "Refresh accounts & tokens",
|
|
2326
2834
|
"row.refreshCredits": "Refresh usage",
|
|
2327
2835
|
"row.refreshing": "Refreshing…",
|
|
2328
2836
|
"row.accountsTitle": "Trae account",
|
|
2329
|
-
"row.accountsHint": "
|
|
2837
|
+
"row.accountsHint": "Sign-ins detected on this machine. Tokens are never shown or saved.",
|
|
2330
2838
|
"row.accountsRescan": "Detect accounts again",
|
|
2331
2839
|
"row.accountsScanning": "Detecting…",
|
|
2840
|
+
"row.accountsNone": "This region reports no usable account.",
|
|
2841
|
+
"row.accountCurrentBadge": "in use",
|
|
2842
|
+
"row.accountSwitch": "Switch",
|
|
2843
|
+
"row.accountSwitching": "Switching…",
|
|
2844
|
+
"row.accountColumnAccount": "Account",
|
|
2845
|
+
"row.accountColumnValue": "Balance",
|
|
2846
|
+
"row.accountColumnCheckin": "Check-in",
|
|
2847
|
+
"row.accountBalance": "{credits} credits",
|
|
2848
|
+
"row.sourceDsh": "plugin copy",
|
|
2332
2849
|
"row.modelsTitle": "Models",
|
|
2333
2850
|
"row.modelsSummary": "{count} enabled",
|
|
2334
2851
|
"row.modelsRefresh": "Refresh from Trae",
|
|
2335
2852
|
"row.modelsRefreshing": "Refreshing models…",
|
|
2336
2853
|
"row.modelsTestEnabled": "Test enabled models",
|
|
2854
|
+
"row.modelsTestAll": "Test all models",
|
|
2337
2855
|
"row.modelsTesting": "Testing {done}/{total}…",
|
|
2338
2856
|
"row.modelsTestHint": "Sends one minimal message per enabled model to see which ones actually answer. Costs a small amount of quota.",
|
|
2857
|
+
"row.modelsTestAllHint": "The「test all」button probes every model in the catalog, enabled or not, so it spends noticeably more quota than the enabled-only run.",
|
|
2339
2858
|
"row.showPointsInMainUi": "Show credits in the composer row",
|
|
2340
|
-
"row.showPointsInMainUiHint": "
|
|
2859
|
+
"row.showPointsInMainUiHint": "A clickable readout in the composer tool row while a model from this plugin is selected: Trae CN · <balance> on the CN side, Trae Global · <plan> on the international one.",
|
|
2860
|
+
"row.showPointsInMainUiHintAi": "A clickable readout in the composer tool row while a model from this plugin is selected: Trae Global · <plan>.",
|
|
2341
2861
|
"composer.switchTo": "Switch to {account}",
|
|
2342
2862
|
"composer.noAccounts": "No account in this region",
|
|
2343
2863
|
"composer.panelTitle": "Trae credits",
|
|
2864
|
+
"composer.panelTitleGlobal": "Trae Global plan",
|
|
2344
2865
|
"composer.account": "Account",
|
|
2345
2866
|
"composer.generalCredits": "General credits",
|
|
2867
|
+
"composer.plan": "Plan",
|
|
2346
2868
|
"composer.lastRefresh": "Last refreshed",
|
|
2347
2869
|
"composer.signedOut": "Not signed in",
|
|
2348
2870
|
"composer.points": "Trae CN",
|
|
2871
|
+
"composer.pointsGlobal": "Trae Global",
|
|
2349
2872
|
"composer.refresh": "Refresh credits",
|
|
2350
2873
|
"composer.refreshing": "Refreshing credits…",
|
|
2351
2874
|
"composer.refreshFailed": "Could not refresh credits",
|
|
2352
2875
|
"row.accountsRefreshed": "Accounts refreshed: {count} found{selected}",
|
|
2876
|
+
"row.accountsSaved": "bound to",
|
|
2353
2877
|
"row.accountsKept": "still bound to",
|
|
2354
2878
|
"row.accountsRefreshedNone": "Accounts refreshed: no usable Trae account found",
|
|
2355
2879
|
"row.accountsRescanFailed": "Could not refresh accounts: {message}",
|
|
@@ -2360,10 +2884,11 @@ window.__ModuleLoader__.load({
|
|
|
2360
2884
|
"row.creditsExhausted": "The bound account ({account}) has run out of credit",
|
|
2361
2885
|
"row.creditsAlternative": "Another account on this machine still has credit: {accounts}",
|
|
2362
2886
|
"row.creditsAlternativeSwitch": "Switch to it",
|
|
2363
|
-
"row.appLoginMismatch": "Trae is signed in to {account}, but this plugin is
|
|
2364
|
-
"row.appLoginFollow": "
|
|
2887
|
+
"row.appLoginMismatch": "Trae is signed in to {account}, but this plugin is bound to {bound}.",
|
|
2888
|
+
"row.appLoginFollow": "Use {account}",
|
|
2365
2889
|
"row.followAppLogin": "Follow the account Trae is signed in to",
|
|
2366
|
-
"row.followAppLoginHint": "
|
|
2890
|
+
"row.followAppLoginHint": "Rebind automatically when you switch accounts in the Trae desktop app. Off by default, so the current binding is kept.",
|
|
2891
|
+
"row.settingsDirty": "Unsaved changes",
|
|
2367
2892
|
"row.modelsTestEmpty": "Enable at least one model first.",
|
|
2368
2893
|
"row.modelsTestError": "Test failed: {message}",
|
|
2369
2894
|
"row.modelsTestDone": "{available} of {total} answered.",
|
|
@@ -2396,10 +2921,10 @@ window.__ModuleLoader__.load({
|
|
|
2396
2921
|
"row.collapse": "收起",
|
|
2397
2922
|
"row.tabCn": "国内版",
|
|
2398
2923
|
"row.tabAi": "国际版",
|
|
2399
|
-
"row.tabHint": "
|
|
2400
|
-
"row.tabSwitchHint": "
|
|
2924
|
+
"row.tabHint": "国内版与国际版是两个独立供应商,各有账号、用量与模型;两边同时生效,互不影响。",
|
|
2925
|
+
"row.tabSwitchHint": "关闭该供应商:其模型不再提供给 DSH;账号与设置保留,可随时恢复。",
|
|
2401
2926
|
"row.tabSwitchAria": "是否向 DSH 提供{region}供应商",
|
|
2402
|
-
"row.tabOffNotice": "
|
|
2927
|
+
"row.tabOffNotice": "该供应商已关闭,模型不会提供给 DSH。账号与设置均已保留。",
|
|
2403
2928
|
"row.settingsWriteFailed": "设置未能保存:{message}",
|
|
2404
2929
|
"row.signedOut": "未登录",
|
|
2405
2930
|
"row.signedOutHint": "在 Trae 桌面 App 里登录一次即可,插件会自动跟随当前登录的账号。",
|
|
@@ -2424,52 +2949,68 @@ window.__ModuleLoader__.load({
|
|
|
2424
2949
|
"row.creditsConsumedLabel": "消耗",
|
|
2425
2950
|
"row.creditsTotalLabel": "合计",
|
|
2426
2951
|
"row.creditsError": "用量查询失败:{message}",
|
|
2427
|
-
"row.checkinLabel": "每日签到",
|
|
2428
2952
|
"row.checkinReward": "每日 {reward} 积分",
|
|
2429
|
-
"row.checkinClaim": "
|
|
2953
|
+
"row.checkinClaim": "签到",
|
|
2954
|
+
"row.checkinClaimWith": "签到 +{reward}",
|
|
2430
2955
|
"row.checkinClaiming": "领取中…",
|
|
2431
|
-
"row.checkinClaimed": "
|
|
2432
|
-
"row.checkinDoneHint": "明天再来",
|
|
2956
|
+
"row.checkinClaimed": "已领取",
|
|
2433
2957
|
"row.checkinRefused": "Trae 拒绝了本次签到(业务码 {code})",
|
|
2434
2958
|
"row.checkinError": "签到失败:{message}",
|
|
2435
2959
|
"row.checkinDeviceSpent": "本机今日的签到已用掉——Trae 按「每台设备每天一次」限制,所以当前账号在本机今天无法再签。可换一台设备领取,或用今天已签到的那台设备/账号,或明天再来。",
|
|
2436
2960
|
"row.checkinDisabled": "该账号当前没有可用的签到活动。",
|
|
2437
|
-
"row.
|
|
2438
|
-
"row.regionAi": "国际版",
|
|
2961
|
+
"row.checkinDeviceSpentShort": "本机已签",
|
|
2439
2962
|
"row.subscriptionLabel": "订阅状态",
|
|
2440
|
-
"row.subscribed": "
|
|
2441
|
-
"row.noPackage": "
|
|
2963
|
+
"row.subscribed": "已有套餐",
|
|
2964
|
+
"row.noPackage": "无套餐",
|
|
2965
|
+
"row.planTier": "{tier} 套餐",
|
|
2442
2966
|
"row.trialUntil": "试用至 {date}",
|
|
2443
2967
|
"row.payStatusError": "订阅状态查询失败:{message}",
|
|
2968
|
+
"row.payStatusUnknown": "套餐尚未读到",
|
|
2444
2969
|
"row.refresh": "刷新",
|
|
2445
2970
|
"row.refreshTokens": "刷新账号与 Token",
|
|
2446
2971
|
"row.refreshCredits": "刷新用量",
|
|
2447
2972
|
"row.refreshing": "正在刷新…",
|
|
2448
2973
|
"row.accountsTitle": "Trae 账号",
|
|
2449
|
-
"row.accountsHint": "
|
|
2974
|
+
"row.accountsHint": "本机检测到的登录账号;Token 不会显示或保存。",
|
|
2450
2975
|
"row.accountsRescan": "重新检测账号",
|
|
2451
2976
|
"row.accountsScanning": "正在检测…",
|
|
2977
|
+
"row.accountsNone": "该区域没有可列出的账号。",
|
|
2978
|
+
"row.accountCurrentBadge": "当前使用",
|
|
2979
|
+
"row.accountSwitch": "切换",
|
|
2980
|
+
"row.accountSwitching": "切换中…",
|
|
2981
|
+
"row.accountColumnAccount": "账号",
|
|
2982
|
+
"row.accountColumnValue": "额度",
|
|
2983
|
+
"row.accountColumnCheckin": "签到",
|
|
2984
|
+
"row.accountBalance": "积分 {credits}",
|
|
2985
|
+
"row.sourceDsh": "插件副本",
|
|
2452
2986
|
"row.modelsTitle": "模型",
|
|
2453
2987
|
"row.modelsSummary": "已启用 {count} 个",
|
|
2454
2988
|
"row.modelsRefresh": "从 Trae 刷新",
|
|
2455
2989
|
"row.modelsRefreshing": "正在刷新模型…",
|
|
2456
2990
|
"row.modelsTestEnabled": "一键测试启用模型",
|
|
2991
|
+
"row.modelsTestAll": "一键测试所有模型",
|
|
2457
2992
|
"row.modelsTesting": "正在测试 {done}/{total}…",
|
|
2458
2993
|
"row.modelsTestHint": "给每个已启用模型发一条最小消息,看哪些真的能应答。会消耗少量额度。",
|
|
2994
|
+
"row.modelsTestAllHint": "「测试所有模型」会遍历目录里的每一个模型(含未启用的),消耗的额度明显多于「仅测试启用」。",
|
|
2459
2995
|
"row.showPointsInMainUi": "在输入框工具栏显示积分读数",
|
|
2460
|
-
"row.showPointsInMainUiHint": "
|
|
2996
|
+
"row.showPointsInMainUiHint": "在输入框工具栏显示可点击读数(选中本插件模型时出现):国内版为「Trae CN · 余额」,国际版为「Trae 国际版 · 套餐」。",
|
|
2997
|
+
"row.showPointsInMainUiHintAi": "在输入框工具栏显示可点击读数(选中本插件模型时出现):国际版为「Trae 国际版 · 套餐」。",
|
|
2461
2998
|
"composer.switchTo": "切换到 {account}",
|
|
2462
2999
|
"composer.noAccounts": "该区域暂无账号",
|
|
2463
3000
|
"composer.panelTitle": "Trae 积分",
|
|
3001
|
+
"composer.panelTitleGlobal": "Trae 国际版套餐",
|
|
2464
3002
|
"composer.account": "账号",
|
|
2465
3003
|
"composer.generalCredits": "通用积分",
|
|
3004
|
+
"composer.plan": "套餐",
|
|
2466
3005
|
"composer.lastRefresh": "最近刷新",
|
|
2467
3006
|
"composer.signedOut": "未登录",
|
|
2468
3007
|
"composer.points": "Trae CN",
|
|
3008
|
+
"composer.pointsGlobal": "Trae 国际版",
|
|
2469
3009
|
"composer.refresh": "刷新积分",
|
|
2470
3010
|
"composer.refreshing": "正在刷新积分…",
|
|
2471
3011
|
"composer.refreshFailed": "积分刷新失败",
|
|
2472
3012
|
"row.accountsRefreshed": "已刷新账号:找到 {count} 个账号{selected}",
|
|
3013
|
+
"row.accountsSaved": "已绑定",
|
|
2473
3014
|
"row.accountsKept": "仍绑定",
|
|
2474
3015
|
"row.accountsRefreshedNone": "已刷新账号:未找到可用的 Trae 账号",
|
|
2475
3016
|
"row.accountsRescanFailed": "刷新账号失败:{message}",
|
|
@@ -2480,10 +3021,11 @@ window.__ModuleLoader__.load({
|
|
|
2480
3021
|
"row.creditsExhausted": "当前绑定账号({account})额度已用尽",
|
|
2481
3022
|
"row.creditsAlternative": "本机还有可用账号:{accounts}",
|
|
2482
3023
|
"row.creditsAlternativeSwitch": "切换到它",
|
|
2483
|
-
"row.appLoginMismatch": "Trae
|
|
2484
|
-
"row.appLoginFollow": "
|
|
3024
|
+
"row.appLoginMismatch": "Trae 已登录 {account},但本插件绑定的是 {bound}。",
|
|
3025
|
+
"row.appLoginFollow": "改用 {account}",
|
|
2485
3026
|
"row.followAppLogin": "跟随 Trae 当前登录的账号",
|
|
2486
|
-
"row.followAppLoginHint": "在 Trae
|
|
3027
|
+
"row.followAppLoginHint": "在 Trae 桌面端切换登录后自动换绑到新账号。默认关闭,已有绑定保持不变。",
|
|
3028
|
+
"row.settingsDirty": "有未保存的修改",
|
|
2487
3029
|
"row.modelsTestEmpty": "请先启用至少一个模型。",
|
|
2488
3030
|
"row.modelsTestError": "测试失败:{message}",
|
|
2489
3031
|
"row.modelsTestDone": "{total} 个里有 {available} 个正常应答。",
|