dsh-connect-trae 3.0.0 → 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 +196 -0
- package/README.en.md +3 -2
- package/README.md +4 -2
- package/lib/client.js +966 -341
- package/lib/index.d.ts +227 -11
- package/lib/index.js +711 -223
- 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. */
|
|
@@ -103,6 +114,20 @@ window.__ModuleLoader__.load({
|
|
|
103
114
|
return typeof accounts === "object" && accounts !== null ? accounts : {};
|
|
104
115
|
}
|
|
105
116
|
/**
|
|
117
|
+
* Whether the host-side "follow the Trae app's sign-in" switch is on (issue
|
|
118
|
+
* #29). Absent reads as OFF, so an existing configuration keeps the strict
|
|
119
|
+
* bind it shipped with and the switch is an opt-in — the same opt-out
|
|
120
|
+
* semantics as {@link regionEnabledOf}, for the same reason: a config written
|
|
121
|
+
* before this field existed cannot ask for behavior it never declared.
|
|
122
|
+
*
|
|
123
|
+
* Reads the whole settings section (or the bare field); deep-unwraps first
|
|
124
|
+
* because on DSH 0.1.7 a volatile field arrives as a `{get(): T}` live
|
|
125
|
+
* reference, whose `typeof === 'object'` would otherwise pass a naive check.
|
|
126
|
+
*/
|
|
127
|
+
function followAppLoginEnabled(configured) {
|
|
128
|
+
return unwrapVolatileDeep(configured)?.followAppLogin === true;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
106
131
|
* Build the next `regions` settings value for the card's save. The write
|
|
107
132
|
* targets ONLY the signed-in account's region slot; every other region's slot
|
|
108
133
|
* is carried over untouched, so switching accounts never clobbers the other
|
|
@@ -245,9 +270,9 @@ window.__ModuleLoader__.load({
|
|
|
245
270
|
.dsm-trae-tab:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
246
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)}
|
|
247
272
|
.dsm-trae-tab-off{opacity:.55}
|
|
248
|
-
.dsm-trae-
|
|
249
|
-
|
|
250
|
-
.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}
|
|
251
276
|
.dsm-trae-tab-dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:6px;vertical-align:baseline}
|
|
252
277
|
.dsm-trae-tab-hint{margin:0;color:var(--dsw-alias-label-tertiary,#999);font-size:12px;line-height:18px}
|
|
253
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)}
|
|
@@ -255,11 +280,109 @@ window.__ModuleLoader__.load({
|
|
|
255
280
|
.dsm-trae-usage-account-copy{display:flex;flex-direction:column;gap:3px;min-width:0}
|
|
256
281
|
.dsm-trae-usage-expiry{padding-left:19px;color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:12px;line-height:18px}
|
|
257
282
|
.dsm-trae-account-picker{display:block}
|
|
258
|
-
|
|
259
|
-
.
|
|
260
|
-
|
|
261
|
-
.
|
|
262
|
-
|
|
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}
|
|
263
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}
|
|
264
387
|
.dsm-trae-usage-list{display:flex;flex-direction:column;gap:10px}
|
|
265
388
|
.dsm-trae-usage-text{margin:0;font-size:14px;line-height:22px;color:var(--dsw-alias-label-secondary,#b8b8b8)}
|
|
@@ -302,11 +425,10 @@ window.__ModuleLoader__.load({
|
|
|
302
425
|
checkbox rendered unstyled and read as absent — the whole reason the switch
|
|
303
426
|
looked missing. */
|
|
304
427
|
.dsm-trae-models-actions{display:flex;align-items:center;gap:8px;flex:none}
|
|
305
|
-
}
|
|
306
|
-
.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)}
|
|
307
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)}
|
|
308
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}
|
|
309
|
-
.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))}
|
|
310
432
|
.dsm-trae-usage-switch-button:disabled{opacity:.5;cursor:not-allowed}
|
|
311
433
|
.dsm-trae-usage-note{margin:8px 0 0;color:var(--dsw-alias-label-tertiary,#999);font-size:11px;line-height:16px}
|
|
312
434
|
.dsm-trae-models-hint{margin:4px 0 0;color:var(--dsw-alias-label-tertiary,#999);font-size:11px;line-height:16px}
|
|
@@ -315,7 +437,7 @@ window.__ModuleLoader__.load({
|
|
|
315
437
|
the model. */
|
|
316
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}
|
|
317
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)}
|
|
318
|
-
.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)}
|
|
319
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)}
|
|
320
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)}
|
|
321
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}
|
|
@@ -461,7 +583,7 @@ window.__ModuleLoader__.load({
|
|
|
461
583
|
background:var(--dsw-alias-label-dimmed,rgba(127,127,127,.5));
|
|
462
584
|
}
|
|
463
585
|
.dsm-trae-composer-panel-table tr.dsm-trae-composer-panel-row-current .dsm-trae-composer-panel-dot{
|
|
464
|
-
background:var(--dsw-state-success,#
|
|
586
|
+
background:var(--dsw-alias-state-success-primary,#22c55e);
|
|
465
587
|
}
|
|
466
588
|
.dsm-trae-composer-panel-foot{
|
|
467
589
|
display:flex;justify-content:space-between;gap:8px;
|
|
@@ -472,6 +594,52 @@ window.__ModuleLoader__.load({
|
|
|
472
594
|
.dsm-trae-composer-panel-empty{margin:0;color:var(--dsw-alias-label-tertiary,GrayText)}
|
|
473
595
|
.dsm-trae-composer-panel-error{margin:8px 0 0;color:var(--dsw-alias-state-error-primary,#d92d20);font-size:11px;line-height:16px}
|
|
474
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
|
+
}
|
|
475
643
|
//#endregion
|
|
476
644
|
//#region src/client/TraeUsageCard.tsx
|
|
477
645
|
/**
|
|
@@ -544,24 +712,6 @@ window.__ModuleLoader__.load({
|
|
|
544
712
|
return formatNumber(value);
|
|
545
713
|
}
|
|
546
714
|
/**
|
|
547
|
-
* The subscription row's verdict.
|
|
548
|
-
*
|
|
549
|
-
* Two independent things can entitle an international account: a package row
|
|
550
|
-
* (`has_package`) and a plan identity (`user_pay_identity_str`). Reading only
|
|
551
|
-
* the first is what rendered a Pro member as "No active package" in issue #19,
|
|
552
|
-
* so either one counts. The tier string is carried separately so the row can
|
|
553
|
-
* name the plan instead of just asserting a boolean.
|
|
554
|
-
*/
|
|
555
|
-
function payVerdict(payStatus) {
|
|
556
|
-
const tier = payStatus.payIdentityStr?.trim();
|
|
557
|
-
const paid = payStatus.hasPackage || traePlanIsPaid(payStatus.payIdentityStr);
|
|
558
|
-
return {
|
|
559
|
-
paid,
|
|
560
|
-
...tier === void 0 || tier === "" ? {} : { tier },
|
|
561
|
-
label: paid ? "row.subscribed" : "row.noPackage"
|
|
562
|
-
};
|
|
563
|
-
}
|
|
564
|
-
/**
|
|
565
715
|
* The verdict badge for one model, or null when it has not been tested.
|
|
566
716
|
*
|
|
567
717
|
* `unknown` renders as inconclusive, never as a failure: a throttle or a dropped
|
|
@@ -627,14 +777,6 @@ window.__ModuleLoader__.load({
|
|
|
627
777
|
/** Region whose on/off checkbox write is in flight, so its box can't race. */
|
|
628
778
|
const [togglingRegion, setTogglingRegion] = (0, react.useState)(void 0);
|
|
629
779
|
/**
|
|
630
|
-
* Whether a check-in claim is in flight. A single flag rather than a
|
|
631
|
-
* per-region map: the claim exists only on the CN tab, and one shared flag
|
|
632
|
-
* keeps a tab switch from stranding a second concurrent claim.
|
|
633
|
-
*/
|
|
634
|
-
const [claiming, setClaiming] = (0, react.useState)(false);
|
|
635
|
-
/** Last claim refusal, shown until the next successful refresh. */
|
|
636
|
-
const [claimError, setClaimError] = (0, react.useState)(void 0);
|
|
637
|
-
/**
|
|
638
780
|
* Outcome of the last "refresh accounts" press (issue #25).
|
|
639
781
|
*
|
|
640
782
|
* The rescan answers 200 with a fresh account list whether or not anything
|
|
@@ -645,21 +787,62 @@ window.__ModuleLoader__.load({
|
|
|
645
787
|
*/
|
|
646
788
|
const [rescanNote, setRescanNote] = (0, react.useState)(void 0);
|
|
647
789
|
/**
|
|
648
|
-
*
|
|
649
|
-
*
|
|
650
|
-
*
|
|
651
|
-
*
|
|
652
|
-
*
|
|
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.
|
|
812
|
+
*/
|
|
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.
|
|
653
835
|
*/
|
|
654
|
-
const [
|
|
836
|
+
const [deviceSpentByRegion, setDeviceSpentByRegion] = (0, react.useState)({
|
|
837
|
+
cn: {},
|
|
838
|
+
ai: {}
|
|
839
|
+
});
|
|
655
840
|
/**
|
|
656
841
|
* A refused or unpersisted settings write, surfaced instead of silently
|
|
657
842
|
* reverting. On 0.1.7 a rejected write resolves normally, so without this
|
|
658
843
|
* the control would flip and then quietly snap back on the next snapshot.
|
|
659
844
|
*/
|
|
660
845
|
const [writeError, setWriteError] = (0, react.useState)(void 0);
|
|
661
|
-
/** True while the composer-credit switch write is in flight. */
|
|
662
|
-
const [togglingCredits, setTogglingCredits] = (0, react.useState)(false);
|
|
663
846
|
const mounted = (0, react.useRef)(true);
|
|
664
847
|
(0, react.useEffect)(() => {
|
|
665
848
|
mounted.current = true;
|
|
@@ -668,21 +851,78 @@ window.__ModuleLoader__.load({
|
|
|
668
851
|
};
|
|
669
852
|
}, []);
|
|
670
853
|
/**
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
*
|
|
674
|
-
*
|
|
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.
|
|
675
903
|
*/
|
|
676
|
-
const
|
|
904
|
+
const writePreference = async (field, value) => {
|
|
677
905
|
if (settingsScope === void 0 || settingsScope.getSnapshot().writable !== true) return;
|
|
678
|
-
|
|
906
|
+
setSavingSettings(true);
|
|
679
907
|
setWriteError(void 0);
|
|
908
|
+
optimisticSettings.current = {
|
|
909
|
+
...optimisticSettings.current,
|
|
910
|
+
[field]: value
|
|
911
|
+
};
|
|
912
|
+
setSettingsRevision((revision) => revision + 1);
|
|
680
913
|
try {
|
|
681
|
-
await writeSettingsField(settingsScope,
|
|
914
|
+
await writeSettingsField(settingsScope, field, value, (readBack) => field === "followAppLogin" ? followAppLoginEnabled(readBack) === value : unwrapVolatileDeep(readBack)?.showPointsInMainUi === value);
|
|
682
915
|
} catch (error) {
|
|
683
916
|
if (mounted.current) setWriteError(error instanceof Error ? error.message : t("row.requestFailed"));
|
|
684
917
|
} finally {
|
|
685
|
-
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);
|
|
686
926
|
}
|
|
687
927
|
};
|
|
688
928
|
(0, react.useEffect)(() => settingsScope?.subscribe(() => {
|
|
@@ -747,13 +987,12 @@ window.__ModuleLoader__.load({
|
|
|
747
987
|
accounts: []
|
|
748
988
|
};
|
|
749
989
|
/**
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
*
|
|
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.
|
|
753
993
|
*/
|
|
754
994
|
(0, react.useEffect)(() => {
|
|
755
|
-
|
|
756
|
-
setDeviceSpentNotice(false);
|
|
995
|
+
setClaimingFor(void 0);
|
|
757
996
|
}, [activeRegion]);
|
|
758
997
|
(0, react.useEffect)(() => {
|
|
759
998
|
if (!open || !activeRegionOn || status.status !== "signed-in") return;
|
|
@@ -783,17 +1022,42 @@ window.__ModuleLoader__.load({
|
|
|
783
1022
|
});
|
|
784
1023
|
const body = await response.json();
|
|
785
1024
|
if (!response.ok || !Array.isArray(body.accounts)) throw new Error(`HTTP ${response.status}`);
|
|
786
|
-
|
|
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];
|
|
787
1049
|
const configured = configuredAccountsOf(settingsScope?.getSnapshot().value);
|
|
788
|
-
const
|
|
789
|
-
|
|
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", {
|
|
790
1054
|
...configured,
|
|
791
|
-
[activeRegion]:
|
|
792
|
-
}, (readBack) => configuredAccountsOf(readBack)[activeRegion] ===
|
|
1055
|
+
[activeRegion]: inUse.id
|
|
1056
|
+
}, (readBack) => configuredAccountsOf(readBack)[activeRegion] === inUse.id);
|
|
793
1057
|
await refreshUsage(activeRegion);
|
|
794
1058
|
if (mounted.current) setRescanNote(body.accounts.length === 0 ? t("row.accountsRefreshedNone") : t("row.accountsRefreshed", {
|
|
795
1059
|
count: String(body.accounts.length),
|
|
796
|
-
selected:
|
|
1060
|
+
selected: saved ? ` · ${t("row.accountsSaved")} ${inUse.accountName ?? inUse.id}` : ` · ${t("row.accountsKept")} ${inUse?.accountName ?? configured[activeRegion] ?? "—"}`
|
|
797
1061
|
}));
|
|
798
1062
|
} catch (error) {
|
|
799
1063
|
if (mounted.current) {
|
|
@@ -815,6 +1079,7 @@ window.__ModuleLoader__.load({
|
|
|
815
1079
|
[activeRegion]: accountId
|
|
816
1080
|
}, (readBack) => configuredAccountsOf(readBack)[activeRegion] === accountId);
|
|
817
1081
|
await refreshUsage(activeRegion);
|
|
1082
|
+
loadAccountStatus(accountId, activeRegion);
|
|
818
1083
|
} catch (error) {
|
|
819
1084
|
if (mounted.current) setWriteError(error instanceof Error ? error.message : t("row.requestFailed"));
|
|
820
1085
|
} finally {
|
|
@@ -822,15 +1087,76 @@ window.__ModuleLoader__.load({
|
|
|
822
1087
|
}
|
|
823
1088
|
};
|
|
824
1089
|
/**
|
|
825
|
-
*
|
|
1090
|
+
* Follow the Trae app's current sign-in from the hint row (issue #29).
|
|
1091
|
+
*
|
|
1092
|
+
* One click, and it goes through the SAME `switchAccount` write the account
|
|
1093
|
+
* dropdown uses — so the card has exactly one writer for `accounts`, the Host
|
|
1094
|
+
* sees the change through `loader/volatile-update` like every other card
|
|
1095
|
+
* write, and there is no second settings path to keep in sync. The app's
|
|
1096
|
+
* sign-in is `status.appLogin`, which the Host only reports when it differs
|
|
1097
|
+
* from the bound account, so a click here always moves the binding to what
|
|
1098
|
+
* the app is actually logged into.
|
|
1099
|
+
*/
|
|
1100
|
+
const followAppLogin = async () => {
|
|
1101
|
+
if (status.status !== "signed-in" || status.appLogin === void 0) return;
|
|
1102
|
+
await switchAccount(status.appLogin.id);
|
|
1103
|
+
};
|
|
1104
|
+
/**
|
|
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.
|
|
1108
|
+
*/
|
|
1109
|
+
const loadAccountStatus = async (accountId, region = activeRegion) => {
|
|
1110
|
+
try {
|
|
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;
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
/**
|
|
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).
|
|
826
1150
|
*
|
|
827
1151
|
* The Host route owns every guard that matters — POST only, loopback origin
|
|
828
1152
|
* only, CN only, and a status read before the claim — because a browser-side
|
|
829
|
-
* check is not a guard. The button's own `disabled` is therefore a
|
|
830
|
-
* (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.
|
|
831
1157
|
*
|
|
832
1158
|
* A refusal is not thrown away: the route answers `claimed: false` with the
|
|
833
|
-
* upstream's business code and message, and the
|
|
1159
|
+
* upstream's business code and message, and the row shows it. Three answers
|
|
834
1160
|
* are distinguished rather than collapsed into "failed":
|
|
835
1161
|
*
|
|
836
1162
|
* - `alreadyCheckedIn` — this ACCOUNT was already paid today; show it as
|
|
@@ -841,32 +1167,88 @@ window.__ModuleLoader__.load({
|
|
|
841
1167
|
* blame the plugin for an upstream rule.
|
|
842
1168
|
* - anything else — a real refusal, reported verbatim.
|
|
843
1169
|
*/
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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
|
+
});
|
|
848
1189
|
try {
|
|
849
1190
|
const response = await fetch(withTraeRegion(TRAE_CHECKIN_PATH, activeRegion), {
|
|
850
1191
|
method: "POST",
|
|
851
|
-
headers: {
|
|
852
|
-
|
|
1192
|
+
headers: {
|
|
1193
|
+
accept: "application/json",
|
|
1194
|
+
"content-type": "application/json"
|
|
1195
|
+
},
|
|
1196
|
+
credentials: "same-origin",
|
|
1197
|
+
body: JSON.stringify({ accountId })
|
|
853
1198
|
});
|
|
854
1199
|
const body = await response.json().catch(() => void 0);
|
|
855
1200
|
if (!response.ok) throw new Error(body?.error ?? `HTTP ${response.status}`);
|
|
856
1201
|
if (body?.claimed === false && body.deviceCheckedIn === true) {
|
|
1202
|
+
setDeviceSpentByRegion((prev) => ({
|
|
1203
|
+
...prev,
|
|
1204
|
+
[activeRegion]: {
|
|
1205
|
+
...prev[activeRegion],
|
|
1206
|
+
[accountId]: true
|
|
1207
|
+
}
|
|
1208
|
+
}));
|
|
857
1209
|
await refreshUsage(activeRegion);
|
|
858
|
-
|
|
1210
|
+
await reloadAccountStatuses(activeRegion);
|
|
859
1211
|
return;
|
|
860
1212
|
}
|
|
861
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}`);
|
|
862
1214
|
await refreshUsage(activeRegion);
|
|
1215
|
+
await reloadAccountStatuses(activeRegion);
|
|
863
1216
|
} catch (error) {
|
|
864
|
-
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
|
+
}
|
|
865
1227
|
} finally {
|
|
866
|
-
if (mounted.current)
|
|
1228
|
+
if (mounted.current) setClaimingFor(void 0);
|
|
867
1229
|
}
|
|
868
1230
|
};
|
|
869
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
|
+
/**
|
|
870
1252
|
* Switch one region's provider off or on (issue #11). The write carries the
|
|
871
1253
|
* region's whole slot through untouched — only `enabled` changes — so the
|
|
872
1254
|
* user's directory, model picks, image opt-ins and budgets survive a
|
|
@@ -886,16 +1268,17 @@ window.__ModuleLoader__.load({
|
|
|
886
1268
|
}
|
|
887
1269
|
};
|
|
888
1270
|
/**
|
|
889
|
-
* One-click availability test for
|
|
1271
|
+
* One-click availability test for a list of models of the active region.
|
|
890
1272
|
*
|
|
891
|
-
*
|
|
892
|
-
*
|
|
893
|
-
*
|
|
894
|
-
*
|
|
895
|
-
*
|
|
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.
|
|
896
1280
|
*/
|
|
897
|
-
const
|
|
898
|
-
const ids = [...activeEnabledIds];
|
|
1281
|
+
const testModels = async (ids) => {
|
|
899
1282
|
setTestError(void 0);
|
|
900
1283
|
setTestSummary(void 0);
|
|
901
1284
|
if (ids.length === 0) {
|
|
@@ -963,6 +1346,33 @@ window.__ModuleLoader__.load({
|
|
|
963
1346
|
if (mounted.current) setTesting(void 0);
|
|
964
1347
|
}
|
|
965
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
|
+
};
|
|
966
1376
|
const refreshModels = async () => {
|
|
967
1377
|
setBusy(true);
|
|
968
1378
|
try {
|
|
@@ -1039,8 +1449,20 @@ window.__ModuleLoader__.load({
|
|
|
1039
1449
|
* message — it never sends one by itself.
|
|
1040
1450
|
*/
|
|
1041
1451
|
const rowImageIsAutomatic = (model) => model.multimodal === true;
|
|
1042
|
-
/**
|
|
1043
|
-
|
|
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
|
+
*/
|
|
1044
1466
|
const activeContextBudgets = draft?.contextBudgets ?? savedContextBudgets;
|
|
1045
1467
|
const dirty = draft !== void 0;
|
|
1046
1468
|
/** Apply an edit to the ACTIVE tab's draft, seeding it from the current view. */
|
|
@@ -1123,6 +1545,85 @@ window.__ModuleLoader__.load({
|
|
|
1123
1545
|
}
|
|
1124
1546
|
};
|
|
1125
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
|
+
};
|
|
1126
1627
|
const label = status.status === "signed-in" ? t("row.signedIn", { accountName: status.accountName }) : status.status === "error" ? t("row.requestFailed") : t("row.signedOut");
|
|
1127
1628
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
1128
1629
|
className: `dsm-plugin-card${open ? " dsm-plugin-card-open" : ""}`,
|
|
@@ -1185,7 +1686,7 @@ window.__ModuleLoader__.load({
|
|
|
1185
1686
|
style: dotStyle(regionStatus.status)
|
|
1186
1687
|
}), item === "cn" ? t("row.tabCn") : t("row.tabAi")]
|
|
1187
1688
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1188
|
-
className: "dsm-trae-tab-switch",
|
|
1689
|
+
className: "dsm-trae-switch dsm-trae-tab-switch",
|
|
1189
1690
|
title: t("row.tabSwitchHint"),
|
|
1190
1691
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1191
1692
|
type: "checkbox",
|
|
@@ -1214,71 +1715,174 @@ window.__ModuleLoader__.load({
|
|
|
1214
1715
|
role: "status",
|
|
1215
1716
|
children: t("row.tabOffNotice")
|
|
1216
1717
|
}),
|
|
1217
|
-
|
|
1218
|
-
className: "dsm-trae-
|
|
1219
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1220
|
-
className: "dsm-trae-
|
|
1221
|
-
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"),
|
|
1222
1723
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1223
1724
|
type: "checkbox",
|
|
1224
|
-
checked:
|
|
1225
|
-
disabled: settingsScope?.getSnapshot().writable !== true ||
|
|
1725
|
+
checked: settingsEdits.followAppLogin,
|
|
1726
|
+
disabled: settingsScope?.getSnapshot().writable !== true || savingSettings,
|
|
1727
|
+
"aria-describedby": followHintId,
|
|
1226
1728
|
onChange: (event) => {
|
|
1227
|
-
|
|
1729
|
+
writePreference("followAppLogin", event.currentTarget.checked);
|
|
1228
1730
|
}
|
|
1229
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
},
|
|
1255
|
-
|
|
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"),
|
|
1748
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1749
|
+
type: "checkbox",
|
|
1750
|
+
checked: settingsEdits.showPointsInMainUi,
|
|
1751
|
+
disabled: settingsScope?.getSnapshot().writable !== true || savingSettings,
|
|
1752
|
+
"aria-describedby": creditsHintId,
|
|
1753
|
+
onChange: (event) => {
|
|
1754
|
+
writePreference("showPointsInMainUi", event.currentTarget.checked);
|
|
1755
|
+
}
|
|
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
|
+
})]
|
|
1256
1770
|
})]
|
|
1257
1771
|
}),
|
|
1258
|
-
|
|
1259
|
-
className: "dsm-trae-
|
|
1772
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1773
|
+
className: "dsm-trae-accounts",
|
|
1260
1774
|
"aria-label": t("row.accountsTitle"),
|
|
1261
1775
|
children: [
|
|
1262
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1263
|
-
className: "dsm-trae-
|
|
1264
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
1265
|
-
className: "dsm-trae-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
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();
|
|
1270
1790
|
},
|
|
1271
|
-
children:
|
|
1272
|
-
|
|
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",
|
|
1273
1829
|
children: [
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
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
|
+
})
|
|
1279
1883
|
]
|
|
1280
|
-
}, account.id)
|
|
1281
|
-
})
|
|
1884
|
+
}, account.id);
|
|
1885
|
+
})]
|
|
1282
1886
|
}),
|
|
1283
1887
|
status.status === "signed-in" && status.alternatives !== void 0 && status.alternatives.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1284
1888
|
className: "dsm-trae-usage-alternative",
|
|
@@ -1301,138 +1905,33 @@ window.__ModuleLoader__.load({
|
|
|
1301
1905
|
children: t("row.creditsAlternativeSwitch")
|
|
1302
1906
|
})]
|
|
1303
1907
|
}) : null,
|
|
1304
|
-
|
|
1305
|
-
className: "dsm-trae-usage-
|
|
1908
|
+
status.status === "signed-in" && status.appLogin !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1909
|
+
className: "dsm-trae-usage-alternative",
|
|
1306
1910
|
role: "status",
|
|
1307
|
-
children:
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
className: "dsm-trae-usage-list",
|
|
1314
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1315
|
-
className: "dsm-trae-usage-stats dsm-trae-usage-stats-two",
|
|
1316
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1317
|
-
className: "dsm-trae-usage-stat dsm-trae-usage-stat-general",
|
|
1318
|
-
children: [
|
|
1319
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1320
|
-
className: "dsm-trae-usage-stat-head",
|
|
1321
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1322
|
-
className: "dsm-trae-usage-stat-label",
|
|
1323
|
-
children: t("row.subscriptionLabel")
|
|
1324
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1325
|
-
className: `dsm-trae-usage-stat-badge ${payPlan.paid ? "dsm-trae-usage-stat-badge-on" : "dsm-trae-usage-stat-badge-off"}`,
|
|
1326
|
-
children: t(payPlan.label)
|
|
1327
|
-
})]
|
|
1328
|
-
}),
|
|
1329
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1330
|
-
className: "dsm-trae-usage-stat-value dsm-trae-usage-stat-value-general",
|
|
1331
|
-
children: status.payStatus.inTrial && status.payStatus.trialEndTimeMs > 0 ? t("row.trialUntil", { date: formatDateTime(status.payStatus.trialEndTimeMs) }) : [t(payPlan.label), payPlan.tier].filter(Boolean).join(" · ")
|
|
1332
|
-
}),
|
|
1333
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1334
|
-
className: "dsm-trae-usage-stat-hint",
|
|
1335
|
-
children: status.payStatus.enableSoloLite || status.payStatus.enableSoloCoder || status.payStatus.enableSoloBuilder || status.payStatus.enableSoloWeb ? [
|
|
1336
|
-
status.payStatus.enableSoloLite ? "SOLO Lite" : "",
|
|
1337
|
-
status.payStatus.enableSoloCoder ? "SOLO Coder" : "",
|
|
1338
|
-
status.payStatus.enableSoloBuilder ? "SOLO Builder" : "",
|
|
1339
|
-
status.payStatus.enableSoloWeb ? "SOLO Web" : ""
|
|
1340
|
-
].filter(Boolean).join(" · ") : status.payStatus.isDollarUsageBilling ? "dollar usage billing" : ""
|
|
1341
|
-
})
|
|
1342
|
-
]
|
|
1343
|
-
})
|
|
1344
|
-
})
|
|
1345
|
-
}),
|
|
1346
|
-
status.payStatusError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1347
|
-
className: "dsm-trae-usage-error",
|
|
1348
|
-
children: t("row.payStatusError", { message: status.payStatusError })
|
|
1349
|
-
}),
|
|
1350
|
-
status.credits === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1351
|
-
className: "dsm-trae-usage-list",
|
|
1352
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1353
|
-
className: "dsm-trae-usage-stats dsm-trae-usage-stats-two",
|
|
1354
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1355
|
-
className: "dsm-trae-usage-stat dsm-trae-usage-stat-work",
|
|
1356
|
-
children: [
|
|
1357
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1358
|
-
className: "dsm-trae-usage-stat-head",
|
|
1359
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1360
|
-
className: "dsm-trae-usage-stat-label",
|
|
1361
|
-
children: t("row.workCreditsLabel")
|
|
1362
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1363
|
-
className: "dsm-trae-usage-stat-badge dsm-trae-usage-stat-badge-off",
|
|
1364
|
-
children: t("row.workNotUsable")
|
|
1365
|
-
})]
|
|
1366
|
-
}),
|
|
1367
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1368
|
-
className: "dsm-trae-usage-stat-value dsm-trae-usage-stat-value-work",
|
|
1369
|
-
children: formatNumber(status.credits.workAvailable)
|
|
1370
|
-
}),
|
|
1371
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1372
|
-
className: "dsm-trae-usage-stat-hint",
|
|
1373
|
-
children: t("row.workUsableHint")
|
|
1374
|
-
})
|
|
1375
|
-
]
|
|
1376
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1377
|
-
className: "dsm-trae-usage-stat dsm-trae-usage-stat-general",
|
|
1378
|
-
children: [
|
|
1379
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1380
|
-
className: "dsm-trae-usage-stat-head",
|
|
1381
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1382
|
-
className: "dsm-trae-usage-stat-label",
|
|
1383
|
-
children: t("row.generalCreditsLabel")
|
|
1384
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1385
|
-
className: "dsm-trae-usage-stat-badge dsm-trae-usage-stat-badge-on",
|
|
1386
|
-
children: t("row.generalUsable")
|
|
1387
|
-
})]
|
|
1388
|
-
}),
|
|
1389
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1390
|
-
className: "dsm-trae-usage-stat-value dsm-trae-usage-stat-value-general",
|
|
1391
|
-
children: formatNumber(status.credits.generalAvailable)
|
|
1392
|
-
}),
|
|
1393
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1394
|
-
className: "dsm-trae-usage-stat-hint",
|
|
1395
|
-
children: t("row.generalUsableHint")
|
|
1396
|
-
})
|
|
1397
|
-
]
|
|
1398
|
-
})]
|
|
1399
|
-
})
|
|
1400
|
-
}),
|
|
1401
|
-
status.creditsError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1402
|
-
className: "dsm-trae-usage-error",
|
|
1403
|
-
children: t("row.creditsError", { message: status.creditsError })
|
|
1404
|
-
}),
|
|
1405
|
-
status.checkin === void 0 ? null : (() => {
|
|
1406
|
-
const accountClaimed = status.checkin.checkedIn;
|
|
1407
|
-
const deviceSpent = !accountClaimed && (status.checkin.didCheckedIn || deviceSpentNotice);
|
|
1408
|
-
const reward = formatNumber(status.checkin.credits);
|
|
1409
|
-
const bonus = status.checkin.extraCredits === void 0 ? "" : ` + ${formatNumber(status.checkin.extraCredits)}`;
|
|
1410
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1411
|
-
className: "dsm-trae-checkin",
|
|
1412
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1413
|
-
className: "dsm-trae-checkin-copy",
|
|
1414
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1415
|
-
className: "dsm-trae-checkin-label",
|
|
1416
|
-
children: t("row.checkinLabel")
|
|
1417
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1418
|
-
className: "dsm-trae-checkin-hint",
|
|
1419
|
-
children: [t("row.checkinReward", { reward: `${reward}${bonus}` }), accountClaimed ? ` · ${t("row.checkinDoneHint")}` : ""]
|
|
1420
|
-
})]
|
|
1911
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1912
|
+
className: "dsm-trae-usage-alternative-text",
|
|
1913
|
+
children: t("row.appLoginMismatch", {
|
|
1914
|
+
account: status.appLogin.accountName,
|
|
1915
|
+
bound: status.accountName
|
|
1916
|
+
})
|
|
1421
1917
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1422
1918
|
type: "button",
|
|
1423
|
-
className: "dsm-
|
|
1424
|
-
disabled:
|
|
1919
|
+
className: "dsm-trae-usage-switch-button",
|
|
1920
|
+
disabled: switchingAccount || settingsScope?.getSnapshot()?.writable !== true,
|
|
1425
1921
|
onClick: () => {
|
|
1426
|
-
|
|
1922
|
+
followAppLogin();
|
|
1427
1923
|
},
|
|
1428
|
-
children:
|
|
1924
|
+
children: t("row.appLoginFollow", { account: status.appLogin.accountName })
|
|
1429
1925
|
})]
|
|
1430
|
-
})
|
|
1431
|
-
|
|
1926
|
+
}) : null,
|
|
1927
|
+
rescanNote === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1928
|
+
className: "dsm-trae-usage-note",
|
|
1432
1929
|
role: "status",
|
|
1433
|
-
children:
|
|
1434
|
-
})
|
|
1435
|
-
|
|
1930
|
+
children: rescanNote
|
|
1931
|
+
})
|
|
1932
|
+
]
|
|
1933
|
+
}),
|
|
1934
|
+
status.status === "signed-in" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1436
1935
|
status.checkin !== void 0 && !status.checkin.enabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1437
1936
|
className: "dsm-trae-usage-error",
|
|
1438
1937
|
children: t("row.checkinDisabled")
|
|
@@ -1441,11 +1940,6 @@ window.__ModuleLoader__.load({
|
|
|
1441
1940
|
className: "dsm-trae-usage-error",
|
|
1442
1941
|
children: t("row.checkinError", { message: status.checkinError })
|
|
1443
1942
|
}),
|
|
1444
|
-
claimError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1445
|
-
className: "dsm-trae-usage-error",
|
|
1446
|
-
role: "alert",
|
|
1447
|
-
children: t("row.checkinError", { message: claimError })
|
|
1448
|
-
}),
|
|
1449
1943
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1450
1944
|
className: "dsm-trae-models",
|
|
1451
1945
|
"aria-label": t("row.modelsTitle"),
|
|
@@ -1460,27 +1954,43 @@ window.__ModuleLoader__.load({
|
|
|
1460
1954
|
children: t("row.modelsSummary", { count: activeEnabledIds.size })
|
|
1461
1955
|
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1462
1956
|
className: "dsm-trae-models-actions",
|
|
1463
|
-
children: [
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
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
|
+
})
|
|
1482
1992
|
})
|
|
1483
|
-
|
|
1993
|
+
]
|
|
1484
1994
|
})]
|
|
1485
1995
|
}),
|
|
1486
1996
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -1822,20 +2332,30 @@ window.__ModuleLoader__.load({
|
|
|
1822
2332
|
*/
|
|
1823
2333
|
const COMPOSER_POINTS_REFRESH_INTERVAL_MS = 3e5;
|
|
1824
2334
|
/**
|
|
1825
|
-
* Provider routes
|
|
1826
|
-
*
|
|
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:
|
|
2339
|
+
*
|
|
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.
|
|
1827
2345
|
*
|
|
1828
|
-
*
|
|
1829
|
-
*
|
|
1830
|
-
*
|
|
1831
|
-
*
|
|
1832
|
-
*
|
|
1833
|
-
* — a number-slot with no number — while an international model is selected.
|
|
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.
|
|
1834
2351
|
*
|
|
1835
|
-
* Mirrors
|
|
1836
|
-
*
|
|
2352
|
+
* Mirrors `TRAE_PROVIDERS` on the host side; duplicated here because the client
|
|
2353
|
+
* bundle must not import the host entry.
|
|
1837
2354
|
*/
|
|
1838
|
-
const TRAE_COMPOSER_PROVIDERS = {
|
|
2355
|
+
const TRAE_COMPOSER_PROVIDERS = {
|
|
2356
|
+
"trae": "cn",
|
|
2357
|
+
"trae-global": "ai"
|
|
2358
|
+
};
|
|
1839
2359
|
/** The general credit balance out of one usage document, or undefined. */
|
|
1840
2360
|
function generalCreditsOf(usage) {
|
|
1841
2361
|
if (usage.status !== "signed-in") return void 0;
|
|
@@ -1858,6 +2378,16 @@ window.__ModuleLoader__.load({
|
|
|
1858
2378
|
const activeRegion = region ?? owned;
|
|
1859
2379
|
const [usage, setUsage] = (0, react.useState)(void 0);
|
|
1860
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);
|
|
1861
2391
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
1862
2392
|
const [switchingId, setSwitchingId] = (0, react.useState)(void 0);
|
|
1863
2393
|
const [failed, setFailed] = (0, react.useState)(false);
|
|
@@ -1943,17 +2473,51 @@ window.__ModuleLoader__.load({
|
|
|
1943
2473
|
controller.abort();
|
|
1944
2474
|
};
|
|
1945
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]);
|
|
1946
2508
|
(0, react.useEffect)(() => {
|
|
1947
2509
|
if (!open || activeRegion === void 0) return void 0;
|
|
1948
2510
|
const controller = new AbortController();
|
|
1949
|
-
|
|
2511
|
+
if (activeRegion === "ai") fetchAccountPlans(controller.signal);
|
|
2512
|
+
else fetchAccountCredits(controller.signal);
|
|
1950
2513
|
return () => {
|
|
1951
2514
|
controller.abort();
|
|
1952
2515
|
};
|
|
1953
2516
|
}, [
|
|
1954
2517
|
open,
|
|
1955
2518
|
activeRegion,
|
|
1956
|
-
fetchAccountCredits
|
|
2519
|
+
fetchAccountCredits,
|
|
2520
|
+
fetchAccountPlans
|
|
1957
2521
|
]);
|
|
1958
2522
|
(0, react.useEffect)(() => {
|
|
1959
2523
|
if (!open) return void 0;
|
|
@@ -1990,7 +2554,8 @@ window.__ModuleLoader__.load({
|
|
|
1990
2554
|
const readBack = configuredAccountsOf(settingsScope.getSnapshot().value);
|
|
1991
2555
|
if (accepted === false || readBack[activeRegion] !== accountId) throw new Error("the Host did not persist the switch");
|
|
1992
2556
|
await fetchUsage();
|
|
1993
|
-
await
|
|
2557
|
+
if (activeRegion === "ai") await fetchAccountPlans();
|
|
2558
|
+
else await fetchAccountCredits();
|
|
1994
2559
|
} catch {
|
|
1995
2560
|
if (mounted.current) setFailed(true);
|
|
1996
2561
|
} finally {
|
|
@@ -1999,10 +2564,21 @@ window.__ModuleLoader__.load({
|
|
|
1999
2564
|
};
|
|
2000
2565
|
if (owned === void 0) return null;
|
|
2001
2566
|
const signedIn = usage?.status === "signed-in" ? usage : void 0;
|
|
2002
|
-
const general = signedIn === void 0 ? void 0 : generalCreditsOf(signedIn);
|
|
2003
2567
|
const signedOut = usage !== void 0 && usage.status === "signed-out";
|
|
2004
|
-
const
|
|
2005
|
-
|
|
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");
|
|
2006
2582
|
/**
|
|
2007
2583
|
* The table's rows: every account in this region, with its general balance.
|
|
2008
2584
|
*
|
|
@@ -2012,7 +2588,11 @@ window.__ModuleLoader__.load({
|
|
|
2012
2588
|
* than a spinner the user has to wait on.
|
|
2013
2589
|
*/
|
|
2014
2590
|
const selectedId = accountCredits?.find((account) => account.selected)?.id ?? (signedIn === void 0 ? void 0 : signedIn.accountId);
|
|
2015
|
-
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) => ({
|
|
2016
2596
|
id: account.id,
|
|
2017
2597
|
accountName: account.accountName,
|
|
2018
2598
|
selected: account.selected
|
|
@@ -2049,13 +2629,13 @@ window.__ModuleLoader__.load({
|
|
|
2049
2629
|
top: 0
|
|
2050
2630
|
},
|
|
2051
2631
|
role: "dialog",
|
|
2052
|
-
"aria-label": t("composer.panelTitle"),
|
|
2632
|
+
"aria-label": activeRegion === "ai" ? t("composer.panelTitleGlobal") : t("composer.panelTitle"),
|
|
2053
2633
|
children: [
|
|
2054
2634
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2055
2635
|
className: "dsm-trae-composer-panel-head",
|
|
2056
2636
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2057
2637
|
className: "dsm-trae-composer-panel-title",
|
|
2058
|
-
children: t("composer.panelTitle")
|
|
2638
|
+
children: activeRegion === "ai" ? t("composer.panelTitleGlobal") : t("composer.panelTitle")
|
|
2059
2639
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2060
2640
|
type: "button",
|
|
2061
2641
|
className: "dsm-trae-composer-panel-refresh",
|
|
@@ -2080,7 +2660,7 @@ window.__ModuleLoader__.load({
|
|
|
2080
2660
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
2081
2661
|
scope: "col",
|
|
2082
2662
|
className: "dsm-trae-composer-panel-num",
|
|
2083
|
-
children: t("composer.generalCredits")
|
|
2663
|
+
children: activeRegion === "ai" ? t("composer.plan") : t("composer.generalCredits")
|
|
2084
2664
|
})] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: rows.map((row) => {
|
|
2085
2665
|
const current = row.id === selectedId;
|
|
2086
2666
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
|
|
@@ -2102,7 +2682,10 @@ window.__ModuleLoader__.load({
|
|
|
2102
2682
|
})
|
|
2103
2683
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2104
2684
|
className: "dsm-trae-composer-panel-num",
|
|
2105
|
-
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)
|
|
2106
2689
|
})]
|
|
2107
2690
|
}, row.id);
|
|
2108
2691
|
}) })]
|
|
@@ -2201,10 +2784,10 @@ window.__ModuleLoader__.load({
|
|
|
2201
2784
|
"row.collapse": "Collapse",
|
|
2202
2785
|
"row.tabCn": "Domestic",
|
|
2203
2786
|
"row.tabAi": "Global",
|
|
2204
|
-
"row.tabHint": "
|
|
2205
|
-
"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.",
|
|
2206
2789
|
"row.tabSwitchAria": "Offer the {region} provider to DSH",
|
|
2207
|
-
"row.tabOffNotice": "This provider is
|
|
2790
|
+
"row.tabOffNotice": "This provider is off: its models are not offered to DSH. Account and settings are kept.",
|
|
2208
2791
|
"row.settingsWriteFailed": "The setting was not saved: {message}",
|
|
2209
2792
|
"row.signedOut": "Not signed in",
|
|
2210
2793
|
"row.signedOutHint": "Sign in once in the Trae desktop app; this plugin follows that sign-in automatically.",
|
|
@@ -2229,52 +2812,68 @@ window.__ModuleLoader__.load({
|
|
|
2229
2812
|
"row.creditsConsumedLabel": "Consumed",
|
|
2230
2813
|
"row.creditsTotalLabel": "Total",
|
|
2231
2814
|
"row.creditsError": "Usage unavailable: {message}",
|
|
2232
|
-
"row.checkinLabel": "Daily check-in",
|
|
2233
2815
|
"row.checkinReward": "{reward} credits per day",
|
|
2234
|
-
"row.checkinClaim": "
|
|
2816
|
+
"row.checkinClaim": "Check in",
|
|
2817
|
+
"row.checkinClaimWith": "Check in +{reward}",
|
|
2235
2818
|
"row.checkinClaiming": "Claiming…",
|
|
2236
|
-
"row.checkinClaimed": "Claimed
|
|
2237
|
-
"row.checkinDoneHint": "come back tomorrow",
|
|
2819
|
+
"row.checkinClaimed": "Claimed",
|
|
2238
2820
|
"row.checkinRefused": "Trae refused the check-in (code {code})",
|
|
2239
2821
|
"row.checkinError": "Check-in failed: {message}",
|
|
2240
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.",
|
|
2241
2823
|
"row.checkinDisabled": "This account has no check-in activity available.",
|
|
2242
|
-
"row.
|
|
2243
|
-
"row.regionAi": "International",
|
|
2824
|
+
"row.checkinDeviceSpentShort": "Device used",
|
|
2244
2825
|
"row.subscriptionLabel": "Subscription",
|
|
2245
2826
|
"row.subscribed": "Active package",
|
|
2246
2827
|
"row.noPackage": "No active package",
|
|
2828
|
+
"row.planTier": "{tier} plan",
|
|
2247
2829
|
"row.trialUntil": "Trial until {date}",
|
|
2248
2830
|
"row.payStatusError": "Subscription status unavailable: {message}",
|
|
2831
|
+
"row.payStatusUnknown": "Plan not loaded yet",
|
|
2249
2832
|
"row.refresh": "Refresh",
|
|
2250
2833
|
"row.refreshTokens": "Refresh accounts & tokens",
|
|
2251
2834
|
"row.refreshCredits": "Refresh usage",
|
|
2252
2835
|
"row.refreshing": "Refreshing…",
|
|
2253
2836
|
"row.accountsTitle": "Trae account",
|
|
2254
|
-
"row.accountsHint": "
|
|
2837
|
+
"row.accountsHint": "Sign-ins detected on this machine. Tokens are never shown or saved.",
|
|
2255
2838
|
"row.accountsRescan": "Detect accounts again",
|
|
2256
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",
|
|
2257
2849
|
"row.modelsTitle": "Models",
|
|
2258
2850
|
"row.modelsSummary": "{count} enabled",
|
|
2259
2851
|
"row.modelsRefresh": "Refresh from Trae",
|
|
2260
2852
|
"row.modelsRefreshing": "Refreshing models…",
|
|
2261
2853
|
"row.modelsTestEnabled": "Test enabled models",
|
|
2854
|
+
"row.modelsTestAll": "Test all models",
|
|
2262
2855
|
"row.modelsTesting": "Testing {done}/{total}…",
|
|
2263
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.",
|
|
2264
2858
|
"row.showPointsInMainUi": "Show credits in the composer row",
|
|
2265
|
-
"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>.",
|
|
2266
2861
|
"composer.switchTo": "Switch to {account}",
|
|
2267
2862
|
"composer.noAccounts": "No account in this region",
|
|
2268
2863
|
"composer.panelTitle": "Trae credits",
|
|
2864
|
+
"composer.panelTitleGlobal": "Trae Global plan",
|
|
2269
2865
|
"composer.account": "Account",
|
|
2270
2866
|
"composer.generalCredits": "General credits",
|
|
2867
|
+
"composer.plan": "Plan",
|
|
2271
2868
|
"composer.lastRefresh": "Last refreshed",
|
|
2272
2869
|
"composer.signedOut": "Not signed in",
|
|
2273
2870
|
"composer.points": "Trae CN",
|
|
2871
|
+
"composer.pointsGlobal": "Trae Global",
|
|
2274
2872
|
"composer.refresh": "Refresh credits",
|
|
2275
2873
|
"composer.refreshing": "Refreshing credits…",
|
|
2276
2874
|
"composer.refreshFailed": "Could not refresh credits",
|
|
2277
2875
|
"row.accountsRefreshed": "Accounts refreshed: {count} found{selected}",
|
|
2876
|
+
"row.accountsSaved": "bound to",
|
|
2278
2877
|
"row.accountsKept": "still bound to",
|
|
2279
2878
|
"row.accountsRefreshedNone": "Accounts refreshed: no usable Trae account found",
|
|
2280
2879
|
"row.accountsRescanFailed": "Could not refresh accounts: {message}",
|
|
@@ -2285,6 +2884,11 @@ window.__ModuleLoader__.load({
|
|
|
2285
2884
|
"row.creditsExhausted": "The bound account ({account}) has run out of credit",
|
|
2286
2885
|
"row.creditsAlternative": "Another account on this machine still has credit: {accounts}",
|
|
2287
2886
|
"row.creditsAlternativeSwitch": "Switch to it",
|
|
2887
|
+
"row.appLoginMismatch": "Trae is signed in to {account}, but this plugin is bound to {bound}.",
|
|
2888
|
+
"row.appLoginFollow": "Use {account}",
|
|
2889
|
+
"row.followAppLogin": "Follow the account Trae is signed in to",
|
|
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",
|
|
2288
2892
|
"row.modelsTestEmpty": "Enable at least one model first.",
|
|
2289
2893
|
"row.modelsTestError": "Test failed: {message}",
|
|
2290
2894
|
"row.modelsTestDone": "{available} of {total} answered.",
|
|
@@ -2317,10 +2921,10 @@ window.__ModuleLoader__.load({
|
|
|
2317
2921
|
"row.collapse": "收起",
|
|
2318
2922
|
"row.tabCn": "国内版",
|
|
2319
2923
|
"row.tabAi": "国际版",
|
|
2320
|
-
"row.tabHint": "
|
|
2321
|
-
"row.tabSwitchHint": "
|
|
2924
|
+
"row.tabHint": "国内版与国际版是两个独立供应商,各有账号、用量与模型;两边同时生效,互不影响。",
|
|
2925
|
+
"row.tabSwitchHint": "关闭该供应商:其模型不再提供给 DSH;账号与设置保留,可随时恢复。",
|
|
2322
2926
|
"row.tabSwitchAria": "是否向 DSH 提供{region}供应商",
|
|
2323
|
-
"row.tabOffNotice": "
|
|
2927
|
+
"row.tabOffNotice": "该供应商已关闭,模型不会提供给 DSH。账号与设置均已保留。",
|
|
2324
2928
|
"row.settingsWriteFailed": "设置未能保存:{message}",
|
|
2325
2929
|
"row.signedOut": "未登录",
|
|
2326
2930
|
"row.signedOutHint": "在 Trae 桌面 App 里登录一次即可,插件会自动跟随当前登录的账号。",
|
|
@@ -2345,52 +2949,68 @@ window.__ModuleLoader__.load({
|
|
|
2345
2949
|
"row.creditsConsumedLabel": "消耗",
|
|
2346
2950
|
"row.creditsTotalLabel": "合计",
|
|
2347
2951
|
"row.creditsError": "用量查询失败:{message}",
|
|
2348
|
-
"row.checkinLabel": "每日签到",
|
|
2349
2952
|
"row.checkinReward": "每日 {reward} 积分",
|
|
2350
|
-
"row.checkinClaim": "
|
|
2953
|
+
"row.checkinClaim": "签到",
|
|
2954
|
+
"row.checkinClaimWith": "签到 +{reward}",
|
|
2351
2955
|
"row.checkinClaiming": "领取中…",
|
|
2352
|
-
"row.checkinClaimed": "
|
|
2353
|
-
"row.checkinDoneHint": "明天再来",
|
|
2956
|
+
"row.checkinClaimed": "已领取",
|
|
2354
2957
|
"row.checkinRefused": "Trae 拒绝了本次签到(业务码 {code})",
|
|
2355
2958
|
"row.checkinError": "签到失败:{message}",
|
|
2356
2959
|
"row.checkinDeviceSpent": "本机今日的签到已用掉——Trae 按「每台设备每天一次」限制,所以当前账号在本机今天无法再签。可换一台设备领取,或用今天已签到的那台设备/账号,或明天再来。",
|
|
2357
2960
|
"row.checkinDisabled": "该账号当前没有可用的签到活动。",
|
|
2358
|
-
"row.
|
|
2359
|
-
"row.regionAi": "国际版",
|
|
2961
|
+
"row.checkinDeviceSpentShort": "本机已签",
|
|
2360
2962
|
"row.subscriptionLabel": "订阅状态",
|
|
2361
|
-
"row.subscribed": "
|
|
2362
|
-
"row.noPackage": "
|
|
2963
|
+
"row.subscribed": "已有套餐",
|
|
2964
|
+
"row.noPackage": "无套餐",
|
|
2965
|
+
"row.planTier": "{tier} 套餐",
|
|
2363
2966
|
"row.trialUntil": "试用至 {date}",
|
|
2364
2967
|
"row.payStatusError": "订阅状态查询失败:{message}",
|
|
2968
|
+
"row.payStatusUnknown": "套餐尚未读到",
|
|
2365
2969
|
"row.refresh": "刷新",
|
|
2366
2970
|
"row.refreshTokens": "刷新账号与 Token",
|
|
2367
2971
|
"row.refreshCredits": "刷新用量",
|
|
2368
2972
|
"row.refreshing": "正在刷新…",
|
|
2369
2973
|
"row.accountsTitle": "Trae 账号",
|
|
2370
|
-
"row.accountsHint": "
|
|
2974
|
+
"row.accountsHint": "本机检测到的登录账号;Token 不会显示或保存。",
|
|
2371
2975
|
"row.accountsRescan": "重新检测账号",
|
|
2372
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": "插件副本",
|
|
2373
2986
|
"row.modelsTitle": "模型",
|
|
2374
2987
|
"row.modelsSummary": "已启用 {count} 个",
|
|
2375
2988
|
"row.modelsRefresh": "从 Trae 刷新",
|
|
2376
2989
|
"row.modelsRefreshing": "正在刷新模型…",
|
|
2377
2990
|
"row.modelsTestEnabled": "一键测试启用模型",
|
|
2991
|
+
"row.modelsTestAll": "一键测试所有模型",
|
|
2378
2992
|
"row.modelsTesting": "正在测试 {done}/{total}…",
|
|
2379
2993
|
"row.modelsTestHint": "给每个已启用模型发一条最小消息,看哪些真的能应答。会消耗少量额度。",
|
|
2994
|
+
"row.modelsTestAllHint": "「测试所有模型」会遍历目录里的每一个模型(含未启用的),消耗的额度明显多于「仅测试启用」。",
|
|
2380
2995
|
"row.showPointsInMainUi": "在输入框工具栏显示积分读数",
|
|
2381
|
-
"row.showPointsInMainUiHint": "
|
|
2996
|
+
"row.showPointsInMainUiHint": "在输入框工具栏显示可点击读数(选中本插件模型时出现):国内版为「Trae CN · 余额」,国际版为「Trae 国际版 · 套餐」。",
|
|
2997
|
+
"row.showPointsInMainUiHintAi": "在输入框工具栏显示可点击读数(选中本插件模型时出现):国际版为「Trae 国际版 · 套餐」。",
|
|
2382
2998
|
"composer.switchTo": "切换到 {account}",
|
|
2383
2999
|
"composer.noAccounts": "该区域暂无账号",
|
|
2384
3000
|
"composer.panelTitle": "Trae 积分",
|
|
3001
|
+
"composer.panelTitleGlobal": "Trae 国际版套餐",
|
|
2385
3002
|
"composer.account": "账号",
|
|
2386
3003
|
"composer.generalCredits": "通用积分",
|
|
3004
|
+
"composer.plan": "套餐",
|
|
2387
3005
|
"composer.lastRefresh": "最近刷新",
|
|
2388
3006
|
"composer.signedOut": "未登录",
|
|
2389
3007
|
"composer.points": "Trae CN",
|
|
3008
|
+
"composer.pointsGlobal": "Trae 国际版",
|
|
2390
3009
|
"composer.refresh": "刷新积分",
|
|
2391
3010
|
"composer.refreshing": "正在刷新积分…",
|
|
2392
3011
|
"composer.refreshFailed": "积分刷新失败",
|
|
2393
3012
|
"row.accountsRefreshed": "已刷新账号:找到 {count} 个账号{selected}",
|
|
3013
|
+
"row.accountsSaved": "已绑定",
|
|
2394
3014
|
"row.accountsKept": "仍绑定",
|
|
2395
3015
|
"row.accountsRefreshedNone": "已刷新账号:未找到可用的 Trae 账号",
|
|
2396
3016
|
"row.accountsRescanFailed": "刷新账号失败:{message}",
|
|
@@ -2401,6 +3021,11 @@ window.__ModuleLoader__.load({
|
|
|
2401
3021
|
"row.creditsExhausted": "当前绑定账号({account})额度已用尽",
|
|
2402
3022
|
"row.creditsAlternative": "本机还有可用账号:{accounts}",
|
|
2403
3023
|
"row.creditsAlternativeSwitch": "切换到它",
|
|
3024
|
+
"row.appLoginMismatch": "Trae 已登录 {account},但本插件绑定的是 {bound}。",
|
|
3025
|
+
"row.appLoginFollow": "改用 {account}",
|
|
3026
|
+
"row.followAppLogin": "跟随 Trae 当前登录的账号",
|
|
3027
|
+
"row.followAppLoginHint": "在 Trae 桌面端切换登录后自动换绑到新账号。默认关闭,已有绑定保持不变。",
|
|
3028
|
+
"row.settingsDirty": "有未保存的修改",
|
|
2404
3029
|
"row.modelsTestEmpty": "请先启用至少一个模型。",
|
|
2405
3030
|
"row.modelsTestError": "测试失败:{message}",
|
|
2406
3031
|
"row.modelsTestDone": "{total} 个里有 {available} 个正常应答。",
|