dsh-connect-trae 3.0.1 → 3.4.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 +198 -0
- package/README.en.md +4 -2
- package/README.md +4 -2
- package/lib/client.js +1051 -378
- package/lib/index.d.ts +148 -7
- package/lib/index.js +453 -56
- 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. */
|
|
@@ -232,7 +243,7 @@ window.__ModuleLoader__.load({
|
|
|
232
243
|
.dsm-plugin-card-title{color:var(--dsw-alias-label-primary,#e6e6e6);font-size:15px;font-weight:600;line-height:1.4}
|
|
233
244
|
.dsm-plugin-card-description{color:var(--dsw-alias-label-tertiary,#999);font-size:13px;line-height:1.5}
|
|
234
245
|
/* Pure-CSS caret: the host primitives' chevron icon names are not part of any
|
|
235
|
-
stable contract across DSH releases, so no static import is used
|
|
246
|
+
stable contract across DSH releases, so no static import is used 鈥?a border
|
|
236
247
|
caret in the plugin's own CSS is version-proof. */
|
|
237
248
|
.dsm-plugin-card-chevron{color:var(--dsw-alias-label-tertiary,#999);flex:none;width:16px;height:16px;position:relative;transition:transform .16s}
|
|
238
249
|
.dsm-plugin-card-chevron::before{content:"";display:block;position:absolute;left:4px;top:5px;width:7px;height:7px;border-right:1.6px solid currentColor;border-bottom:1.6px solid currentColor;transform:rotate(45deg)}
|
|
@@ -259,9 +270,9 @@ window.__ModuleLoader__.load({
|
|
|
259
270
|
.dsm-trae-tab:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
260
271
|
.dsm-trae-tab-active{color:var(--dsw-alias-label-primary,#e6e6e6);background:var(--dsw-alias-bg-layer-2,#232529);box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l2,#3a3d45)}
|
|
261
272
|
.dsm-trae-tab-off{opacity:.55}
|
|
262
|
-
.dsm-trae-
|
|
263
|
-
|
|
264
|
-
.dsm-trae-tab-switch
|
|
273
|
+
/* Same checkbox as the settings block (.dsm-trae-switch); only the padding
|
|
274
|
+
differs, so it sits inside the tab cell without crowding the label. */
|
|
275
|
+
.dsm-trae-tab-switch{flex:none;padding:0 8px 0 2px}
|
|
265
276
|
.dsm-trae-tab-dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:6px;vertical-align:baseline}
|
|
266
277
|
.dsm-trae-tab-hint{margin:0;color:var(--dsw-alias-label-tertiary,#999);font-size:12px;line-height:18px}
|
|
267
278
|
.dsm-trae-tab-off-notice{margin:0;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:10px;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px;line-height:18px;background:var(--dsw-alias-bg-layer-3,#2a2c33)}
|
|
@@ -269,11 +280,109 @@ window.__ModuleLoader__.load({
|
|
|
269
280
|
.dsm-trae-usage-account-copy{display:flex;flex-direction:column;gap:3px;min-width:0}
|
|
270
281
|
.dsm-trae-usage-expiry{padding-left:19px;color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:12px;line-height:18px}
|
|
271
282
|
.dsm-trae-account-picker{display:block}
|
|
272
|
-
|
|
273
|
-
.
|
|
274
|
-
|
|
275
|
-
.
|
|
276
|
-
|
|
283
|
+
/* The 璁剧疆 block: label + hint on the left, control on the right, one save for
|
|
284
|
+
the whole block. It replaces the bare checkbox rows that used to write on
|
|
285
|
+
every click 鈥?a draft needs a visible way back out, so each row carries its
|
|
286
|
+
own explanation and the footer carries the buttons. */
|
|
287
|
+
/* The preference switches: BARE LINES, one per switch, each with its label
|
|
288
|
+
wrapped around the box so the whole line is a click target.
|
|
289
|
+
*
|
|
290
|
+
* The 璁剧疆 block used to be a bordered box with a heading, two rows and a
|
|
291
|
+
* 銆屼繚瀛樸€?銆屾斁寮冦€?footer. That is gone with the draft it existed to serve 鈥?this
|
|
292
|
+
* is the sibling dsh-connect-workbuddy card's shape, where a preference is a
|
|
293
|
+
* checkbox on a line that commits on the click. */
|
|
294
|
+
.dsm-trae-prefs{display:flex;flex-direction:column;gap:8px}
|
|
295
|
+
.dsm-trae-pref{display:flex;align-items:flex-start;gap:8px;font-size:13px;line-height:18px;color:var(--dsw-alias-label-secondary,#c6c9d0);cursor:pointer}
|
|
296
|
+
.dsm-trae-pref input{flex:none;margin:2px 0 0;cursor:pointer;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
|
|
297
|
+
.dsm-trae-pref input:disabled{cursor:default}
|
|
298
|
+
.dsm-trae-pref:has(input:disabled){opacity:.6;cursor:default}
|
|
299
|
+
.dsm-trae-pref b{font-weight:500;color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
300
|
+
.dsm-trae-pref-hint{color:var(--dsw-alias-label-tertiary,#9aa0a8)}
|
|
301
|
+
.dsm-trae-pref span{min-width:0}
|
|
302
|
+
/* The card's checkboxes are the platform's own, styled only with
|
|
303
|
+
accent-color 鈥?the SAME idiom .dsm-trae-mainui-switch-label above already
|
|
304
|
+
uses for the composer-line switch, and the one the sibling
|
|
305
|
+
dsh-connect-workbuddy card uses for its tab and credits boxes.
|
|
306
|
+
*
|
|
307
|
+
* These were drawn by hand for one release: a 34x20 track with a 14px knob,
|
|
308
|
+
* --dsw-alias-brand-primary when checked. That token is DSH's CONTRAST colour
|
|
309
|
+
* (#0f1115 light / #f9fafb dark), not an accent, so a checked box came out as a
|
|
310
|
+
* solid black pill 鈥?heavier than anything else in the card, and unrelated to
|
|
311
|
+
* the native box sitting two centimetres away in the composer toolbar. The
|
|
312
|
+
* platform checkbox tracks the host's own accent and dark mode for free. */
|
|
313
|
+
.dsm-trae-switch{display:inline-flex;align-items:center;cursor:pointer}
|
|
314
|
+
.dsm-trae-switch input{margin:0;cursor:pointer;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
|
|
315
|
+
.dsm-trae-switch input:disabled{cursor:default}
|
|
316
|
+
.dsm-trae-switch:has(input:disabled){opacity:.6;cursor:default}
|
|
317
|
+
/* The account block is a real table: the question it answers is "which of these
|
|
318
|
+
accounts, and what is the action on each one" 鈥?a dropdown can only ever show
|
|
319
|
+
the one already chosen. */
|
|
320
|
+
.dsm-trae-accounts{display:flex;flex-direction:column;gap:10px;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:14px;background:var(--dsw-alias-bg-layer-2,#24262c)}
|
|
321
|
+
.dsm-trae-accounts-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
|
|
322
|
+
/* The refresh button must not be squeezed into two lines by a long hint. */
|
|
323
|
+
.dsm-trae-accounts-head>button{flex:none;white-space:nowrap}
|
|
324
|
+
.dsm-trae-accounts-title{margin:0;font-size:13px;font-weight:600;line-height:18px;color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
325
|
+
.dsm-trae-accounts-hint{margin:3px 0 0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,#9aa0a8)}
|
|
326
|
+
/* The account rows: two lines each 鈥?who the account is, then what it is worth
|
|
327
|
+
鈥?with the one action that applies to THAT row on the right. A four-column
|
|
328
|
+
table was the wrong instrument here: every field demanded its own header, the
|
|
329
|
+
columns drifted with the content ("LaoDing" vs a full expiry stamp), and the
|
|
330
|
+
bound row repeated its "in use" badge in two different columns. */
|
|
331
|
+
/* The account table, shaped like the sibling WorkBuddy card's pool table: a
|
|
332
|
+
bordered container, a tinted header row, and one grid row per account whose
|
|
333
|
+
tracks are FRACTIONS of the container rather than sizes derived from the
|
|
334
|
+
content. That is the whole point 鈥?the previous four-column table let each
|
|
335
|
+
column grow with its own text, so the columns did not line up from row to row
|
|
336
|
+
and the table read as noise in a narrow panel. */
|
|
337
|
+
.dsm-trae-account-table{display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:12px;overflow:hidden;background:var(--dsw-alias-bg-layer-2,#24262c)}
|
|
338
|
+
/* Four tracks where the region has a check-in column, three where it does not
|
|
339
|
+
(the international region has no check-in at all: the ug/* family answers
|
|
340
|
+
404 on every ai gateway, so the column is absent rather than empty and
|
|
341
|
+
permanently unknown). The freed track goes to the account name. */
|
|
342
|
+
.dsm-trae-account-row{display:grid;grid-template-columns:1.7fr 1.1fr 1fr 1.1fr;gap:10px;align-items:center;padding:9px 12px;border-top:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.18));font-size:13px;color:var(--dsw-alias-label-secondary,#c6c9d0)}
|
|
343
|
+
.dsm-trae-account-table:not(.dsm-trae-account-table-checkin) .dsm-trae-account-row{grid-template-columns:2.2fr 1.2fr 1.1fr}
|
|
344
|
+
.dsm-trae-account-row:first-child{border-top:0}
|
|
345
|
+
.dsm-trae-account-row-head{padding:7px 12px;background:var(--dsw-alias-bg-layer-3,#2a2c33);color:var(--dsw-alias-label-tertiary,#999);font-size:11px;font-weight:600;letter-spacing:.02em;text-transform:uppercase}
|
|
346
|
+
/* The serving row, findable at a glance: the accent bar is what distinguishes it
|
|
347
|
+
in a long table, where the 7%-opacity wash alone reads as a zebra stripe. */
|
|
348
|
+
.dsm-trae-account-row-current{background:rgba(86,134,254,.07);box-shadow:inset 3px 0 0 var(--dsw-alias-brand-primary,#5686fe)}
|
|
349
|
+
.dsm-trae-account-who{display:flex;align-items:center;gap:8px;min-width:0}
|
|
350
|
+
.dsm-trae-account-who .dsm-trae-usage-dot{margin-right:0;flex:none}
|
|
351
|
+
.dsm-trae-account-name{display:flex;flex-direction:column;gap:1px;min-width:0}
|
|
352
|
+
.dsm-trae-account-name b{font-weight:500;color:var(--dsw-alias-label-primary,#e6e6e6);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
353
|
+
.dsm-trae-account-row-current .dsm-trae-account-name b{font-weight:600}
|
|
354
|
+
/* Declared AFTER the blanket span rule above so it wins: the badge must not
|
|
355
|
+
inherit the muted 11px tertiary treatment the edition line uses. */
|
|
356
|
+
.dsm-trae-account-name .dsm-trae-account-badge{align-self:flex-start;margin-top:2px;padding:1px 6px;border-radius:999px;font-size:10px;line-height:15px;font-weight:600;white-space:nowrap;color:var(--dsw-alias-brand-primary,#5686fe);background:rgba(86,134,254,.14)}
|
|
357
|
+
.dsm-trae-account-meta{display:flex;align-items:center;gap:5px;flex-wrap:wrap;font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary,#9aa0a8);min-width:0}
|
|
358
|
+
.dsm-trae-account-meta i{font-style:normal;opacity:.4}
|
|
359
|
+
.dsm-trae-account-note{font-size:11px;line-height:16px;color:var(--dsw-alias-state-error-primary,#ef4444)}
|
|
360
|
+
/* Its own track, in tabular figures, so digits in different rows sit on the same
|
|
361
|
+
column of the grid instead of jittering with the glyph widths. */
|
|
362
|
+
.dsm-trae-account-worth{display:flex;flex-direction:column;gap:1px;min-width:0;font-variant-numeric:tabular-nums}
|
|
363
|
+
.dsm-trae-account-value{color:var(--dsw-alias-label-primary,#e6e6e6);font-weight:600}
|
|
364
|
+
.dsm-trae-account-value-on{color:var(--dsw-alias-state-success-primary,#22a06b)}
|
|
365
|
+
.dsm-trae-account-checkin-cell{display:flex;align-items:center;min-width:0}
|
|
366
|
+
.dsm-trae-account-actions{display:flex;align-items:center;gap:6px;justify-content:flex-end;flex:none}
|
|
367
|
+
/* The per-row switch is the shared outline button, only smaller: one button
|
|
368
|
+
vocabulary in the card instead of a bespoke one per block. */
|
|
369
|
+
.dsm-trae-account-row .dsm-trae-account-switch{padding:2px 10px;font-size:12px}
|
|
370
|
+
@media (max-width:760px){.dsm-trae-account-row,.dsm-trae-account-table:not(.dsm-trae-account-table-checkin) .dsm-trae-account-row{grid-template-columns:1fr 1fr;row-gap:6px}.dsm-trae-account-row-head{display:none}}
|
|
371
|
+
/* The check-in is the only action on this card that reaches upstream account
|
|
372
|
+
state, so it is the one button allowed a colour. Everything else in the row
|
|
373
|
+
defers to it. */
|
|
374
|
+
.dsm-trae-account-checkin{appearance:none;font:inherit;cursor:pointer;white-space:nowrap;border:1px solid rgba(86,134,254,.4);border-radius:7px;padding:2px 9px;font-size:12px;line-height:18px;font-weight:500;color:var(--dsw-alias-brand-primary,#5686fe);background:rgba(86,134,254,.08)}
|
|
375
|
+
.dsm-trae-account-checkin:hover:not(:disabled){background:rgba(86,134,254,.16)}
|
|
376
|
+
.dsm-trae-account-checkin:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
377
|
+
/* A disabled check-in still STATES something ("claimed", "device used"), so it
|
|
378
|
+
stays readable 鈥?clearly inactive, not invisible. */
|
|
379
|
+
.dsm-trae-account-checkin:disabled{opacity:.55;cursor:default;border-color:transparent;color:var(--dsw-alias-label-tertiary,#9aa0a8);background:transparent}
|
|
380
|
+
/* The switch is the row's LIVE control, so it is brighter than the inert
|
|
381
|
+
check-in labels beside it: same shape, different weight. */
|
|
382
|
+
.dsm-trae-account-switch{appearance:none;font:inherit;cursor:pointer;white-space:nowrap;border:0;border-radius:7px;padding:3px 8px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary,#c6c9d0);background:transparent}
|
|
383
|
+
.dsm-trae-account-switch:hover:not(:disabled){color:var(--dsw-alias-brand-primary,#5686fe);background:rgba(86,134,254,.12)}
|
|
384
|
+
.dsm-trae-account-switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
385
|
+
.dsm-trae-account-switch:disabled{opacity:.45;cursor:default}
|
|
277
386
|
.dsm-trae-mark{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:5px;font-size:11px;font-weight:700;color:#111;background:linear-gradient(135deg,#ffd36e,#ffb84d);margin-right:6px}
|
|
278
387
|
.dsm-trae-usage-list{display:flex;flex-direction:column;gap:10px}
|
|
279
388
|
.dsm-trae-usage-text{margin:0;font-size:14px;line-height:22px;color:var(--dsw-alias-label-secondary,#b8b8b8)}
|
|
@@ -313,23 +422,30 @@ window.__ModuleLoader__.load({
|
|
|
313
422
|
the hint tooltip rather than looking like its read-only sibling. */
|
|
314
423
|
/* The sidebar-credit switch. Two instances share this style: the card's own
|
|
315
424
|
row at the top and the copy beside the model actions (2.9.3). Without it the
|
|
316
|
-
checkbox rendered unstyled and read as absent
|
|
425
|
+
checkbox rendered unstyled and read as absent 鈥?the whole reason the switch
|
|
317
426
|
looked missing. */
|
|
318
427
|
.dsm-trae-models-actions{display:flex;align-items:center;gap:8px;flex:none}
|
|
319
|
-
}
|
|
320
|
-
.dsm-trae-usage-alternative{display:flex;align-items:flex-start;gap:10px;margin-top:10px;padding:10px 12px;border:1px solid var(--dsw-alias-state-warning-border,#7a5b12);border-radius:10px;background:var(--dsw-alias-state-warning-bg,#2a2313)}
|
|
428
|
+
.dsm-trae-usage-alternative{display:flex;align-items:flex-start;gap:10px;margin-top:10px;padding:10px 12px;border:1px solid var(--dsw-alias-state-warn-secondary,#fbbf24);border-radius:10px;background:var(--dsw-alias-state-warn-tertiary,#fef3c7)}
|
|
321
429
|
.dsm-trae-usage-alternative-text{margin:0;flex:1 1 auto;font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
322
430
|
.dsm-trae-usage-switch-button{flex:0 0 auto;padding:5px 10px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:8px;background:var(--dsw-alias-bg-layer-3,#2c2f36);color:var(--dsw-alias-label-primary,#e6e6e6);font-size:12px;line-height:16px;cursor:pointer}
|
|
323
|
-
.dsm-trae-usage-switch-button:hover:not(:disabled){background:var(--dsw-alias-bg-
|
|
431
|
+
.dsm-trae-usage-switch-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.14))}
|
|
324
432
|
.dsm-trae-usage-switch-button:disabled{opacity:.5;cursor:not-allowed}
|
|
325
433
|
.dsm-trae-usage-note{margin:8px 0 0;color:var(--dsw-alias-label-tertiary,#999);font-size:11px;line-height:16px}
|
|
434
|
+
/* Credit-expiry notice, card copy. Three tones by urgency, so the 24-hour case
|
|
435
|
+
cannot be mistaken for the 7-day one at a glance. Reads as an advisory, not
|
|
436
|
+
an error: the account works fine, the credit is simply about to lapse. */
|
|
437
|
+
.dsm-trae-expiry{display:flex;flex-direction:column;gap:2px;margin-top:10px;padding:10px 12px;border:1px solid;border-radius:10px;font-size:12px;line-height:18px}
|
|
438
|
+
.dsm-trae-expiry-soon{border-color:var(--dsw-alias-state-warn-secondary,#fbbf24);background:var(--dsw-alias-state-warn-tertiary,#fef3c7);color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
439
|
+
.dsm-trae-expiry-week{border-color:var(--dsw-alias-border-l2,#3a3d45);background:var(--dsw-alias-bg-layer-3,#2c2f36);color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
440
|
+
.dsm-trae-expiry-lapsed{border-color:var(--dsw-alias-border-l2,#3a3d45);background:var(--dsw-alias-bg-layer-3,#2c2f36);color:var(--dsw-alias-label-tertiary,#999)}
|
|
441
|
+
.dsm-trae-expiry-detail{font-size:11px;line-height:16px;opacity:.8}
|
|
326
442
|
.dsm-trae-models-hint{margin:4px 0 0;color:var(--dsw-alias-label-tertiary,#999);font-size:11px;line-height:16px}
|
|
327
443
|
/* Verdict badge. Only a real refusal is coloured as a warning; an unknown
|
|
328
444
|
verdict stays neutral because an inconclusive probe is not evidence against
|
|
329
445
|
the model. */
|
|
330
446
|
.dsm-trae-model-probe{flex:none;border-radius:999px;padding:1px 7px;font-size:10px;font-weight:500;line-height:15px;white-space:nowrap}
|
|
331
447
|
.dsm-trae-model-probe-available{color:var(--dsw-alias-state-success-primary,#22a06b);border:1px solid color-mix(in srgb,var(--dsw-alias-state-success-primary,#22a06b) 40%,transparent)}
|
|
332
|
-
.dsm-trae-model-probe-gated,.dsm-trae-model-probe-unsupported{color:var(--dsw-alias-state-
|
|
448
|
+
.dsm-trae-model-probe-gated,.dsm-trae-model-probe-unsupported{color:var(--dsw-alias-state-warn-primary,#f59e0b);border:1px solid color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 40%,transparent)}
|
|
333
449
|
.dsm-trae-model-probe-auth{color:var(--dsw-alias-state-error-primary,#d92d20);border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary,#d92d20) 40%,transparent)}
|
|
334
450
|
.dsm-trae-model-probe-rate-limited,.dsm-trae-model-probe-unknown{color:var(--dsw-alias-label-tertiary,#999);border:1px solid color-mix(in srgb,var(--dsw-alias-label-tertiary,#999) 35%,transparent)}
|
|
335
451
|
.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}
|
|
@@ -368,8 +484,8 @@ window.__ModuleLoader__.load({
|
|
|
368
484
|
*
|
|
369
485
|
* SEPARATE from `TRAE_CARD_CSS`, and that is not tidiness: the card's CSS is
|
|
370
486
|
* only injected when the settings card module loads, so putting the composer's
|
|
371
|
-
* rules in it means the readout renders UNSTYLED in the composer toolbar
|
|
372
|
-
* which is exactly what shipped in 2.13.0
|
|
487
|
+
* rules in it means the readout renders UNSTYLED in the composer toolbar 鈥?
|
|
488
|
+
* which is exactly what shipped in 2.13.0鈥?.13.4. The trigger fell back to the
|
|
373
489
|
* browser's default button box, and the panel, without `position: fixed`, had
|
|
374
490
|
* its inline left/top ignored and rendered nowhere useful.
|
|
375
491
|
*
|
|
@@ -377,14 +493,14 @@ window.__ModuleLoader__.load({
|
|
|
377
493
|
* renders has a rule here, so a rule cannot go missing without a test failing.
|
|
378
494
|
*/
|
|
379
495
|
const COMPOSER_POINTS_CSS = `
|
|
380
|
-
/* The readout is plain composer text
|
|
496
|
+
/* The readout is plain composer text 鈥?no border, no fill at rest. The padding
|
|
381
497
|
is only the click target, invisible until the pointer arrives. */
|
|
382
498
|
.dsm-trae-composer-points{display:inline-flex;align-items:center;min-width:0}
|
|
383
499
|
.dsm-trae-composer-points-trigger{
|
|
384
500
|
appearance:none;display:inline-flex;align-items:center;align-self:stretch;
|
|
385
501
|
padding:2px 6px;border:0;border-radius:6px;background:transparent;
|
|
386
502
|
/* EXPLICIT, not inherit. The composer row's own font and body colour are
|
|
387
|
-
noticeably larger and darker than the neighbouring controls
|
|
503
|
+
noticeably larger and darker than the neighbouring controls 鈥?a credit
|
|
388
504
|
readout is secondary information and must not shout. 12px matches this
|
|
389
505
|
plugin's original line; label-tertiary matches the shell's own composer-row
|
|
390
506
|
control (ContextMeter), which is the sibling this reads alongside. */
|
|
@@ -396,7 +512,7 @@ window.__ModuleLoader__.load({
|
|
|
396
512
|
.dsm-trae-composer-points-trigger:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.14))}
|
|
397
513
|
.dsm-trae-composer-points-trigger:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
398
514
|
/* The panel is portaled to document.body and positioned from the trigger, so it
|
|
399
|
-
MUST be fixed
|
|
515
|
+
MUST be fixed 鈥?otherwise the left/top it measures are ignored and it lands in
|
|
400
516
|
document flow. All material comes from shell tokens so it follows the theme. */
|
|
401
517
|
.dsm-trae-composer-panel{
|
|
402
518
|
position:fixed;z-index:1100;box-sizing:border-box;
|
|
@@ -450,7 +566,7 @@ window.__ModuleLoader__.load({
|
|
|
450
566
|
color:var(--dsw-alias-label-primary,CanvasText);
|
|
451
567
|
}
|
|
452
568
|
/* The current account reads as current without shouting: a filled dot and a
|
|
453
|
-
slightly heavier weight. Disabled
|
|
569
|
+
slightly heavier weight. Disabled 鈥?pressing it would write a selection that
|
|
454
570
|
has not changed. */
|
|
455
571
|
.dsm-trae-composer-panel-table tbody tr.dsm-trae-composer-panel-row-current td{font-weight:600}
|
|
456
572
|
.dsm-trae-composer-panel-switch{
|
|
@@ -475,7 +591,7 @@ window.__ModuleLoader__.load({
|
|
|
475
591
|
background:var(--dsw-alias-label-dimmed,rgba(127,127,127,.5));
|
|
476
592
|
}
|
|
477
593
|
.dsm-trae-composer-panel-table tr.dsm-trae-composer-panel-row-current .dsm-trae-composer-panel-dot{
|
|
478
|
-
background:var(--dsw-state-success,#
|
|
594
|
+
background:var(--dsw-alias-state-success-primary,#22c55e);
|
|
479
595
|
}
|
|
480
596
|
.dsm-trae-composer-panel-foot{
|
|
481
597
|
display:flex;justify-content:space-between;gap:8px;
|
|
@@ -485,7 +601,77 @@ window.__ModuleLoader__.load({
|
|
|
485
601
|
}
|
|
486
602
|
.dsm-trae-composer-panel-empty{margin:0;color:var(--dsw-alias-label-tertiary,GrayText)}
|
|
487
603
|
.dsm-trae-composer-panel-error{margin:8px 0 0;color:var(--dsw-alias-state-error-primary,#d92d20);font-size:11px;line-height:16px}
|
|
604
|
+
/* Credit-expiry notice. Three tones by urgency, so the 24-hour case cannot be
|
|
605
|
+
mistaken for the 7-day one at a glance. Reads as an advisory, not an error:
|
|
606
|
+
the account works fine, the credit is simply about to lapse. */
|
|
607
|
+
.dsm-trae-expiry{
|
|
608
|
+
display:flex;flex-direction:column;gap:2px;
|
|
609
|
+
margin-top:9px;padding:8px 10px;border:1px solid;border-radius:8px;
|
|
610
|
+
font-size:11px;line-height:16px;
|
|
611
|
+
}
|
|
612
|
+
.dsm-trae-expiry-soon{
|
|
613
|
+
border-color:var(--dsw-alias-state-warn-secondary,#fbbf24);
|
|
614
|
+
background:var(--dsw-alias-state-warn-tertiary,#fef3c7);
|
|
615
|
+
color:var(--dsw-alias-label-primary,#1f2329);
|
|
616
|
+
}
|
|
617
|
+
.dsm-trae-expiry-week{
|
|
618
|
+
border-color:var(--dsw-alias-border-l2,rgba(127,127,127,.36));
|
|
619
|
+
background:transparent;
|
|
620
|
+
color:var(--dsw-alias-label-secondary,GrayText);
|
|
621
|
+
}
|
|
622
|
+
.dsm-trae-expiry-lapsed{
|
|
623
|
+
border-color:var(--dsw-alias-border-l2,rgba(127,127,127,.36));
|
|
624
|
+
background:transparent;
|
|
625
|
+
color:var(--dsw-alias-label-tertiary,GrayText);
|
|
626
|
+
}
|
|
627
|
+
.dsm-trae-expiry-detail{font-size:11px;line-height:16px;opacity:.85}
|
|
488
628
|
`;
|
|
629
|
+
//#endregion
|
|
630
|
+
//#region src/client/plan-text.ts
|
|
631
|
+
/**
|
|
632
|
+
* The international region's "what plan is this account on" text.
|
|
633
|
+
*
|
|
634
|
+
* WHY THIS IS ITS OWN MODULE: three surfaces now answer that one question — the
|
|
635
|
+
* card's subscription box, the card's per-account rows, and the composer
|
|
636
|
+
* readout. Each previously carried its own copy of the verdict, which is how the
|
|
637
|
+
* card's box and its roster row were able to disagree about one account's tier
|
|
638
|
+
* (the box read the usage document's top-level `payStatus`, which the ai document
|
|
639
|
+
* does not always carry, while the row read the per-account state). A second
|
|
640
|
+
* implementation of the same rule is free to drift; there is now exactly one.
|
|
641
|
+
*
|
|
642
|
+
* @module dsh-connect-trae/client/plan-text
|
|
643
|
+
*/
|
|
644
|
+
/**
|
|
645
|
+
* Decide whether an international account is on a paid plan, and name the plan.
|
|
646
|
+
*
|
|
647
|
+
* Two independent things can entitle it: a package row (`has_package`) and a
|
|
648
|
+
* plan identity (`user_pay_identity_str`). Reading only the first is what
|
|
649
|
+
* rendered a Pro member as "No active package" in issue #19, so either one
|
|
650
|
+
* counts. The tier string rides separately so a caller can name the plan instead
|
|
651
|
+
* of only asserting a boolean.
|
|
652
|
+
*/
|
|
653
|
+
function planVerdict(payStatus) {
|
|
654
|
+
const tier = payStatus.payIdentityStr?.trim();
|
|
655
|
+
const paid = payStatus.hasPackage || traePlanIsPaid(payStatus.payIdentityStr);
|
|
656
|
+
return {
|
|
657
|
+
paid,
|
|
658
|
+
...tier === void 0 || tier === "" ? {} : { tier },
|
|
659
|
+
label: paid ? "row.subscribed" : "row.noPackage"
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* The verdict as ONE short string, for a surface that has a single slot.
|
|
664
|
+
*
|
|
665
|
+
* The composer readout is that surface: it is a few characters wide in the tool
|
|
666
|
+
* row, so it gets the tier name alone (`Pro`, `Free`) rather than the card's
|
|
667
|
+
* two-line label-plus-tier. When the upstream named no tier the label stands in,
|
|
668
|
+
* which is still strictly more than the dash the readout used to show for the
|
|
669
|
+
* whole international region.
|
|
670
|
+
*/
|
|
671
|
+
function planText(payStatus, t) {
|
|
672
|
+
const verdict = planVerdict(payStatus);
|
|
673
|
+
return verdict.tier === void 0 ? t(verdict.label) : verdict.tier;
|
|
674
|
+
}
|
|
489
675
|
//#endregion
|
|
490
676
|
//#region src/client/TraeUsageCard.tsx
|
|
491
677
|
/**
|
|
@@ -558,24 +744,6 @@ window.__ModuleLoader__.load({
|
|
|
558
744
|
return formatNumber(value);
|
|
559
745
|
}
|
|
560
746
|
/**
|
|
561
|
-
* The subscription row's verdict.
|
|
562
|
-
*
|
|
563
|
-
* Two independent things can entitle an international account: a package row
|
|
564
|
-
* (`has_package`) and a plan identity (`user_pay_identity_str`). Reading only
|
|
565
|
-
* the first is what rendered a Pro member as "No active package" in issue #19,
|
|
566
|
-
* so either one counts. The tier string is carried separately so the row can
|
|
567
|
-
* name the plan instead of just asserting a boolean.
|
|
568
|
-
*/
|
|
569
|
-
function payVerdict(payStatus) {
|
|
570
|
-
const tier = payStatus.payIdentityStr?.trim();
|
|
571
|
-
const paid = payStatus.hasPackage || traePlanIsPaid(payStatus.payIdentityStr);
|
|
572
|
-
return {
|
|
573
|
-
paid,
|
|
574
|
-
...tier === void 0 || tier === "" ? {} : { tier },
|
|
575
|
-
label: paid ? "row.subscribed" : "row.noPackage"
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
/**
|
|
579
747
|
* The verdict badge for one model, or null when it has not been tested.
|
|
580
748
|
*
|
|
581
749
|
* `unknown` renders as inconclusive, never as a failure: a throttle or a dropped
|
|
@@ -601,6 +769,53 @@ window.__ModuleLoader__.load({
|
|
|
601
769
|
function dotStyle(status) {
|
|
602
770
|
return { background: status === "signed-in" ? "var(--dsw-alias-state-success-primary, #22a06b)" : status === "error" ? "var(--dsw-alias-state-error-primary, #d92d20)" : "var(--dsw-alias-label-dimmed, #9aa0a6)" };
|
|
603
771
|
}
|
|
772
|
+
/**
|
|
773
|
+
* A warning that credit is about to lapse.
|
|
774
|
+
*
|
|
775
|
+
* The balance alone cannot express this: a pack lapsing in an hour and one
|
|
776
|
+
* lapsing next month both render as "150", so a user watching only the total
|
|
777
|
+
* loses the first one silently. The notice names the amount, the pack count and
|
|
778
|
+
* the soonest expiry, and escalates 7d → 24h → already-lapsed.
|
|
779
|
+
*
|
|
780
|
+
* Renders nothing when the upstream reported no end times at all — absence of
|
|
781
|
+
* expiry data is not evidence that nothing lapses, so the component stays
|
|
782
|
+
* silent rather than reassuring.
|
|
783
|
+
*/
|
|
784
|
+
function ExpiryNotice({ expiry, t, formatNumber: fmt, formatDateTime: fmtTime }) {
|
|
785
|
+
const { expired, within24h, within7d } = expiry;
|
|
786
|
+
let message;
|
|
787
|
+
let soonest;
|
|
788
|
+
let tone;
|
|
789
|
+
if (within24h.packs > 0) {
|
|
790
|
+
message = t("row.expiryWarningSoon", {
|
|
791
|
+
total: fmt(within24h.total),
|
|
792
|
+
packs: String(within24h.packs)
|
|
793
|
+
});
|
|
794
|
+
soonest = within24h.earliestExpiresAtMs;
|
|
795
|
+
tone = "dsm-trae-expiry-soon";
|
|
796
|
+
} else if (within7d.packs > 0) {
|
|
797
|
+
message = t("row.expiryWarningWeek", {
|
|
798
|
+
total: fmt(within7d.total),
|
|
799
|
+
packs: String(within7d.packs)
|
|
800
|
+
});
|
|
801
|
+
soonest = within7d.earliestExpiresAtMs;
|
|
802
|
+
tone = "dsm-trae-expiry-week";
|
|
803
|
+
} else if (expired.packs > 0) {
|
|
804
|
+
message = t("row.expiryWarningLapsed", {
|
|
805
|
+
total: fmt(expired.total),
|
|
806
|
+
packs: String(expired.packs)
|
|
807
|
+
});
|
|
808
|
+
tone = "dsm-trae-expiry-lapsed";
|
|
809
|
+
} else return null;
|
|
810
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
811
|
+
className: `dsm-trae-expiry ${tone}`,
|
|
812
|
+
role: "status",
|
|
813
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: message }), soonest === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
814
|
+
className: "dsm-trae-expiry-detail",
|
|
815
|
+
children: t("row.expiryEarliest", { time: fmtTime(soonest) })
|
|
816
|
+
})]
|
|
817
|
+
});
|
|
818
|
+
}
|
|
604
819
|
/** Render Trae sign-in state and the total usage summary as one expandable card. */
|
|
605
820
|
function TraeUsageCard({ t, settingsScope, view }) {
|
|
606
821
|
if (t === void 0) throw new Error("Trae usage card requires its translation function");
|
|
@@ -641,14 +856,6 @@ window.__ModuleLoader__.load({
|
|
|
641
856
|
/** Region whose on/off checkbox write is in flight, so its box can't race. */
|
|
642
857
|
const [togglingRegion, setTogglingRegion] = (0, react.useState)(void 0);
|
|
643
858
|
/**
|
|
644
|
-
* Whether a check-in claim is in flight. A single flag rather than a
|
|
645
|
-
* per-region map: the claim exists only on the CN tab, and one shared flag
|
|
646
|
-
* keeps a tab switch from stranding a second concurrent claim.
|
|
647
|
-
*/
|
|
648
|
-
const [claiming, setClaiming] = (0, react.useState)(false);
|
|
649
|
-
/** Last claim refusal, shown until the next successful refresh. */
|
|
650
|
-
const [claimError, setClaimError] = (0, react.useState)(void 0);
|
|
651
|
-
/**
|
|
652
859
|
* Outcome of the last "refresh accounts" press (issue #25).
|
|
653
860
|
*
|
|
654
861
|
* The rescan answers 200 with a fresh account list whether or not anything
|
|
@@ -659,21 +866,62 @@ window.__ModuleLoader__.load({
|
|
|
659
866
|
*/
|
|
660
867
|
const [rescanNote, setRescanNote] = (0, react.useState)(void 0);
|
|
661
868
|
/**
|
|
662
|
-
*
|
|
663
|
-
*
|
|
664
|
-
*
|
|
665
|
-
*
|
|
666
|
-
*
|
|
869
|
+
* A preference the user just flipped, held only until its write lands.
|
|
870
|
+
*
|
|
871
|
+
* The 设置 block used to stage both switches in a draft written by 「保存」.
|
|
872
|
+
* That is GONE: the sibling dsh-connect-workbuddy card commits its preference
|
|
873
|
+
* checkboxes on the click, and a Save button for two booleans asks the user to
|
|
874
|
+
* remember something that has no reason to need remembering.
|
|
875
|
+
*
|
|
876
|
+
* What remains is the smallest thing that keeps a click honest: the box reads
|
|
877
|
+
* this optimistic value until the write settles, then falls back to the
|
|
878
|
+
* committed one. So a flip shows immediately, and a write that FAILS snaps the
|
|
879
|
+
* box back to the Host's value instead of leaving it claiming one that never
|
|
880
|
+
* landed.
|
|
667
881
|
*/
|
|
668
|
-
const
|
|
882
|
+
const optimisticSettings = (0, react.useRef)({});
|
|
883
|
+
/** True while a preference is crossing the wire; serializes both writers. */
|
|
884
|
+
const [savingSettings, setSavingSettings] = (0, react.useState)(false);
|
|
885
|
+
/**
|
|
886
|
+
* Per-account status (check-in / balances / subscription) shown in each row of
|
|
887
|
+
* the account table, keyed by region then account id. Loaded per account via
|
|
888
|
+
* `TRAE_ACCOUNT_STATUS_PATH` once the table is shown; a row that failed to
|
|
889
|
+
* load (or that has not been read yet) simply renders without its figure,
|
|
890
|
+
* exactly as the composer table treats a missing balance.
|
|
891
|
+
*/
|
|
892
|
+
const [accountStatusByRegion, setAccountStatusByRegion] = (0, react.useState)({
|
|
893
|
+
cn: {},
|
|
894
|
+
ai: {}
|
|
895
|
+
});
|
|
896
|
+
/** Which account row is claiming right now, so only its button spins. */
|
|
897
|
+
const [claimingFor, setClaimingFor] = (0, react.useState)(void 0);
|
|
898
|
+
/**
|
|
899
|
+
* Per-account claim refusals, keyed by region then account id. A refusal is
|
|
900
|
+
* shown next to the row that caused it rather than in one global error line,
|
|
901
|
+
* because the check-in button now lives per account.
|
|
902
|
+
*/
|
|
903
|
+
const [claimErrorByRegion, setClaimErrorByRegion] = (0, react.useState)({
|
|
904
|
+
cn: {},
|
|
905
|
+
ai: {}
|
|
906
|
+
});
|
|
907
|
+
/**
|
|
908
|
+
* Whether this MACHINE has spent today's check-in for a given account — the
|
|
909
|
+
* "device is spent but this account was never rewarded" state, per account so
|
|
910
|
+
* it appears next to the row that owns it. Kept separate from the claim error
|
|
911
|
+
* because it is not a failure: it is the upstream's one-per-device-per-day
|
|
912
|
+
* rule, and rendering it through the "签到失败:…" line would present a normal
|
|
913
|
+
* rule as a plugin fault.
|
|
914
|
+
*/
|
|
915
|
+
const [deviceSpentByRegion, setDeviceSpentByRegion] = (0, react.useState)({
|
|
916
|
+
cn: {},
|
|
917
|
+
ai: {}
|
|
918
|
+
});
|
|
669
919
|
/**
|
|
670
920
|
* A refused or unpersisted settings write, surfaced instead of silently
|
|
671
921
|
* reverting. On 0.1.7 a rejected write resolves normally, so without this
|
|
672
922
|
* the control would flip and then quietly snap back on the next snapshot.
|
|
673
923
|
*/
|
|
674
924
|
const [writeError, setWriteError] = (0, react.useState)(void 0);
|
|
675
|
-
/** True while the composer-credit switch write is in flight. */
|
|
676
|
-
const [togglingCredits, setTogglingCredits] = (0, react.useState)(false);
|
|
677
925
|
const mounted = (0, react.useRef)(true);
|
|
678
926
|
(0, react.useEffect)(() => {
|
|
679
927
|
mounted.current = true;
|
|
@@ -682,21 +930,78 @@ window.__ModuleLoader__.load({
|
|
|
682
930
|
};
|
|
683
931
|
}, []);
|
|
684
932
|
/**
|
|
685
|
-
*
|
|
686
|
-
*
|
|
687
|
-
*
|
|
688
|
-
*
|
|
933
|
+
* The 设置 block: its committed values, its draft, and the ONE write path.
|
|
934
|
+
*
|
|
935
|
+
* The block commits on 「保存」 rather than on every click, so 「放弃」 has a
|
|
936
|
+
* meaning and a half-finished edit cannot land in the settings document. Both
|
|
937
|
+
* fields are written through `writeSettingsField`, which read-back checks the
|
|
938
|
+
* document — `set()` resolving has never been proof a value landed on this
|
|
939
|
+
* Host, so a saved-looking switch that silently reverted is exactly the
|
|
940
|
+
* failure that guard exists for.
|
|
941
|
+
*
|
|
942
|
+
* Committed values are read from the settings document, never echoed from
|
|
943
|
+
* local state: a write made elsewhere (the DSH settings form, another window)
|
|
944
|
+
* then shows up here instead of being shadowed by a stale copy.
|
|
945
|
+
*/
|
|
946
|
+
const committedSettings = {
|
|
947
|
+
followAppLogin: followAppLoginEnabled(settingsScope?.getSnapshot().value),
|
|
948
|
+
showPointsInMainUi: unwrapVolatileDeep(settingsScope?.getSnapshot().value)?.showPointsInMainUi === true
|
|
949
|
+
};
|
|
950
|
+
/**
|
|
951
|
+
* Ids for the block's label/hint wiring. `useId`, not constants: DSH can mount
|
|
952
|
+
* this card twice at once (the settings ROW and the expanded PAGE), and two
|
|
953
|
+
* copies sharing an id would leave the second switch labelled by the first
|
|
954
|
+
* card's element.
|
|
955
|
+
*/
|
|
956
|
+
/**
|
|
957
|
+
* What the two boxes render: the committed value, with any in-flight flip laid
|
|
958
|
+
* over it. Reading BOTH sources here is what lets the write above stay simple —
|
|
959
|
+
* it never has to write into component state that a Host refresh would fight.
|
|
960
|
+
*/
|
|
961
|
+
const settingsEdits = {
|
|
962
|
+
followAppLogin: optimisticSettings.current.followAppLogin ?? committedSettings.followAppLogin,
|
|
963
|
+
showPointsInMainUi: optimisticSettings.current.showPointsInMainUi ?? committedSettings.showPointsInMainUi
|
|
964
|
+
};
|
|
965
|
+
const settingsIds = (0, react.useId)();
|
|
966
|
+
const followLabelId = `${settingsIds}-follow`;
|
|
967
|
+
const followHintId = `${settingsIds}-follow-hint`;
|
|
968
|
+
const creditsLabelId = `${settingsIds}-credits`;
|
|
969
|
+
const creditsHintId = `${settingsIds}-credits-hint`;
|
|
970
|
+
/**
|
|
971
|
+
* Write ONE preference switch, on the click that flipped it.
|
|
972
|
+
*
|
|
973
|
+
* This replaced a draft + 「保存」/「放弃」 pair, which is what the sibling
|
|
974
|
+
* dsh-connect-workbuddy card does NOT have: its switches are plain checkboxes
|
|
975
|
+
* on a bare line that commit immediately, and a card that needs a Save button
|
|
976
|
+
* for two booleans is asking the user to remember something for no reason.
|
|
977
|
+
*
|
|
978
|
+
* The two writers are SERIALIZED by `savingSettings`, because they write the
|
|
979
|
+
* same settings document. The pattern is the tab bar's `toggleRegion`: flip
|
|
980
|
+
* optimistically so the box responds, then re-read the Host so a failed write
|
|
981
|
+
* snaps it back instead of leaving the box claiming a value that never landed.
|
|
689
982
|
*/
|
|
690
|
-
const
|
|
983
|
+
const writePreference = async (field, value) => {
|
|
691
984
|
if (settingsScope === void 0 || settingsScope.getSnapshot().writable !== true) return;
|
|
692
|
-
|
|
985
|
+
setSavingSettings(true);
|
|
693
986
|
setWriteError(void 0);
|
|
987
|
+
optimisticSettings.current = {
|
|
988
|
+
...optimisticSettings.current,
|
|
989
|
+
[field]: value
|
|
990
|
+
};
|
|
991
|
+
setSettingsRevision((revision) => revision + 1);
|
|
694
992
|
try {
|
|
695
|
-
await writeSettingsField(settingsScope,
|
|
993
|
+
await writeSettingsField(settingsScope, field, value, (readBack) => field === "followAppLogin" ? followAppLoginEnabled(readBack) === value : unwrapVolatileDeep(readBack)?.showPointsInMainUi === value);
|
|
696
994
|
} catch (error) {
|
|
697
995
|
if (mounted.current) setWriteError(error instanceof Error ? error.message : t("row.requestFailed"));
|
|
698
996
|
} finally {
|
|
699
|
-
if (mounted.current)
|
|
997
|
+
if (mounted.current) {
|
|
998
|
+
optimisticSettings.current = {
|
|
999
|
+
...optimisticSettings.current,
|
|
1000
|
+
[field]: void 0
|
|
1001
|
+
};
|
|
1002
|
+
setSettingsRevision((revision) => revision + 1);
|
|
1003
|
+
}
|
|
1004
|
+
if (mounted.current) setSavingSettings(false);
|
|
700
1005
|
}
|
|
701
1006
|
};
|
|
702
1007
|
(0, react.useEffect)(() => settingsScope?.subscribe(() => {
|
|
@@ -761,13 +1066,12 @@ window.__ModuleLoader__.load({
|
|
|
761
1066
|
accounts: []
|
|
762
1067
|
};
|
|
763
1068
|
/**
|
|
764
|
-
*
|
|
765
|
-
*
|
|
766
|
-
*
|
|
1069
|
+
* Clear in-flight claim state when the tab changes. `activeRegion` is a single
|
|
1070
|
+
* value across both tabs, so a switch must not leave one row's claim spinning
|
|
1071
|
+
* under the other tab.
|
|
767
1072
|
*/
|
|
768
1073
|
(0, react.useEffect)(() => {
|
|
769
|
-
|
|
770
|
-
setDeviceSpentNotice(false);
|
|
1074
|
+
setClaimingFor(void 0);
|
|
771
1075
|
}, [activeRegion]);
|
|
772
1076
|
(0, react.useEffect)(() => {
|
|
773
1077
|
if (!open || !activeRegionOn || status.status !== "signed-in") return;
|
|
@@ -797,17 +1101,42 @@ window.__ModuleLoader__.load({
|
|
|
797
1101
|
});
|
|
798
1102
|
const body = await response.json();
|
|
799
1103
|
if (!response.ok || !Array.isArray(body.accounts)) throw new Error(`HTTP ${response.status}`);
|
|
800
|
-
|
|
1104
|
+
/**
|
|
1105
|
+
* What this press must persist, and why it is NOT `accounts.find(selected)`.
|
|
1106
|
+
*
|
|
1107
|
+
* The route's `selected` flag answers "which account is IN USE" — and the
|
|
1108
|
+
* store reports the account it is bound to, OR, when no binding has ever
|
|
1109
|
+
* been saved, the DETECTED default (`preferred`, i.e. whatever the Trae
|
|
1110
|
+
* app is signed in to). Those are the same value on a machine that has
|
|
1111
|
+
* been used for a while and diverge on exactly the press where it matters:
|
|
1112
|
+
* a fresh install, or a Trae sign-in the plugin has not recorded yet.
|
|
1113
|
+
*
|
|
1114
|
+
* Reading the flag alone therefore meant "nothing was explicitly bound"
|
|
1115
|
+
* reduced to "the detected default is what is in use, write nothing" —
|
|
1116
|
+
* so the account the user just refreshed was shown as bound while
|
|
1117
|
+
* `accounts.<region>` stayed EMPTY, and the next store rebuild (restart,
|
|
1118
|
+
* or a Trae sign-in change) silently re-defaulted somewhere else. That is
|
|
1119
|
+
* the "refreshed account was not saved" report.
|
|
1120
|
+
*
|
|
1121
|
+
* The rule is now: persist the account this region is actually using
|
|
1122
|
+
* (the store's verdict, which is the truth the card is displaying), and
|
|
1123
|
+
* fall back to the region's own roster only when the route returned no
|
|
1124
|
+
* verdict at all. A row that is already the SAVED binding is left alone —
|
|
1125
|
+
* one press must not rewrite the settings document for nothing.
|
|
1126
|
+
*/
|
|
1127
|
+
const inUse = body.accounts.find((account) => account.selected) ?? body.accounts[0];
|
|
801
1128
|
const configured = configuredAccountsOf(settingsScope?.getSnapshot().value);
|
|
802
|
-
const
|
|
803
|
-
|
|
1129
|
+
const alreadyBound = inUse !== void 0 && inUse.id === configured[activeRegion];
|
|
1130
|
+
const writable = settingsScope !== void 0 && settingsScope.getSnapshot().writable === true;
|
|
1131
|
+
const saved = inUse !== void 0 && !alreadyBound && writable;
|
|
1132
|
+
if (saved && settingsScope !== void 0 && inUse !== void 0) await writeSettingsField(settingsScope, "accounts", {
|
|
804
1133
|
...configured,
|
|
805
|
-
[activeRegion]:
|
|
806
|
-
}, (readBack) => configuredAccountsOf(readBack)[activeRegion] ===
|
|
1134
|
+
[activeRegion]: inUse.id
|
|
1135
|
+
}, (readBack) => configuredAccountsOf(readBack)[activeRegion] === inUse.id);
|
|
807
1136
|
await refreshUsage(activeRegion);
|
|
808
1137
|
if (mounted.current) setRescanNote(body.accounts.length === 0 ? t("row.accountsRefreshedNone") : t("row.accountsRefreshed", {
|
|
809
1138
|
count: String(body.accounts.length),
|
|
810
|
-
selected:
|
|
1139
|
+
selected: saved ? ` · ${t("row.accountsSaved")} ${inUse.accountName ?? inUse.id}` : ` · ${t("row.accountsKept")} ${inUse?.accountName ?? configured[activeRegion] ?? "—"}`
|
|
811
1140
|
}));
|
|
812
1141
|
} catch (error) {
|
|
813
1142
|
if (mounted.current) {
|
|
@@ -829,6 +1158,7 @@ window.__ModuleLoader__.load({
|
|
|
829
1158
|
[activeRegion]: accountId
|
|
830
1159
|
}, (readBack) => configuredAccountsOf(readBack)[activeRegion] === accountId);
|
|
831
1160
|
await refreshUsage(activeRegion);
|
|
1161
|
+
loadAccountStatus(accountId, activeRegion);
|
|
832
1162
|
} catch (error) {
|
|
833
1163
|
if (mounted.current) setWriteError(error instanceof Error ? error.message : t("row.requestFailed"));
|
|
834
1164
|
} finally {
|
|
@@ -851,30 +1181,61 @@ window.__ModuleLoader__.load({
|
|
|
851
1181
|
await switchAccount(status.appLogin.id);
|
|
852
1182
|
};
|
|
853
1183
|
/**
|
|
854
|
-
*
|
|
855
|
-
*
|
|
856
|
-
*
|
|
857
|
-
* with until the user ticks this.
|
|
1184
|
+
* Load one account row's per-account state (check-in / balances /
|
|
1185
|
+
* subscription) and cache it for the row. Called when the table renders and
|
|
1186
|
+
* again after a claim so the reward/refusal lands on the row that owns it.
|
|
858
1187
|
*/
|
|
859
|
-
const
|
|
860
|
-
if (settingsScope === void 0 || settingsScope.getSnapshot().writable !== true) return;
|
|
861
|
-
setWriteError(void 0);
|
|
1188
|
+
const loadAccountStatus = async (accountId, region = activeRegion) => {
|
|
862
1189
|
try {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
1190
|
+
const url = `${withTraeRegion(TRAE_ACCOUNT_STATUS_PATH, region)}&accountId=${encodeURIComponent(accountId)}`;
|
|
1191
|
+
const response = await fetch(url, {
|
|
1192
|
+
headers: { accept: "application/json" },
|
|
1193
|
+
credentials: "same-origin"
|
|
1194
|
+
});
|
|
1195
|
+
const body = await response.json().catch(() => ({}));
|
|
1196
|
+
if (!response.ok) throw new Error(body.error ?? `HTTP ${response.status}`);
|
|
1197
|
+
if (mounted.current) setAccountStatusByRegion((prev) => ({
|
|
1198
|
+
...prev,
|
|
1199
|
+
[region]: {
|
|
1200
|
+
...prev[region],
|
|
1201
|
+
[accountId]: body
|
|
1202
|
+
}
|
|
1203
|
+
}));
|
|
1204
|
+
return body;
|
|
1205
|
+
} catch {
|
|
1206
|
+
return;
|
|
866
1207
|
}
|
|
867
1208
|
};
|
|
868
1209
|
/**
|
|
869
|
-
*
|
|
1210
|
+
* Re-read EVERY row of one region, not just the one that was acted on.
|
|
1211
|
+
*
|
|
1212
|
+
* Trae spends the DEVICE's daily check-in, not the account's, so one claim
|
|
1213
|
+
* changes what every other row may do: they go from "check in" to "device
|
|
1214
|
+
* already used". Re-reading only the claimed account left its siblings
|
|
1215
|
+
* advertising a claim that could only come back 9095 — a row of live buttons
|
|
1216
|
+
* of which exactly one could work, which is the same false promise the 3.2.0
|
|
1217
|
+
* note refused bulk check-in for. Reading the siblings instead of guessing
|
|
1218
|
+
* them means the server's own `didCheckedIn` decides, so a sibling that is
|
|
1219
|
+
* still genuinely claimable (the device was not spent by this claim) keeps
|
|
1220
|
+
* its button.
|
|
1221
|
+
*/
|
|
1222
|
+
const reloadAccountStatuses = async (region) => {
|
|
1223
|
+
if (status.status !== "signed-in") return;
|
|
1224
|
+
const ids = status.accounts.map((account) => account.id);
|
|
1225
|
+
await Promise.all(ids.map((id) => loadAccountStatus(id, region)));
|
|
1226
|
+
};
|
|
1227
|
+
/**
|
|
1228
|
+
* Claim today's check-in reward for ONE named account row (CN only).
|
|
870
1229
|
*
|
|
871
1230
|
* The Host route owns every guard that matters — POST only, loopback origin
|
|
872
1231
|
* only, CN only, and a status read before the claim — because a browser-side
|
|
873
|
-
* check is not a guard. The button's own `disabled` is therefore a
|
|
874
|
-
* (it stops an obviously pointless click), not the protection.
|
|
1232
|
+
* check is not a guard. The row button's own `disabled` is therefore a
|
|
1233
|
+
* courtesy (it stops an obviously pointless click), not the protection. The
|
|
1234
|
+
* body carries the `accountId`, so the claim reaches THAT account's
|
|
1235
|
+
* credential without moving the whole binding.
|
|
875
1236
|
*
|
|
876
1237
|
* A refusal is not thrown away: the route answers `claimed: false` with the
|
|
877
|
-
* upstream's business code and message, and the
|
|
1238
|
+
* upstream's business code and message, and the row shows it. Three answers
|
|
878
1239
|
* are distinguished rather than collapsed into "failed":
|
|
879
1240
|
*
|
|
880
1241
|
* - `alreadyCheckedIn` — this ACCOUNT was already paid today; show it as
|
|
@@ -885,32 +1246,88 @@ window.__ModuleLoader__.load({
|
|
|
885
1246
|
* blame the plugin for an upstream rule.
|
|
886
1247
|
* - anything else — a real refusal, reported verbatim.
|
|
887
1248
|
*/
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1249
|
+
const claimFor = async (accountId) => {
|
|
1250
|
+
if (activeRegion !== "cn") return;
|
|
1251
|
+
setClaimingFor(accountId);
|
|
1252
|
+
setClaimErrorByRegion((prev) => {
|
|
1253
|
+
const next = { ...prev[activeRegion] };
|
|
1254
|
+
delete next[accountId];
|
|
1255
|
+
return {
|
|
1256
|
+
...prev,
|
|
1257
|
+
[activeRegion]: next
|
|
1258
|
+
};
|
|
1259
|
+
});
|
|
1260
|
+
setDeviceSpentByRegion((prev) => {
|
|
1261
|
+
const next = { ...prev[activeRegion] };
|
|
1262
|
+
delete next[accountId];
|
|
1263
|
+
return {
|
|
1264
|
+
...prev,
|
|
1265
|
+
[activeRegion]: next
|
|
1266
|
+
};
|
|
1267
|
+
});
|
|
892
1268
|
try {
|
|
893
1269
|
const response = await fetch(withTraeRegion(TRAE_CHECKIN_PATH, activeRegion), {
|
|
894
1270
|
method: "POST",
|
|
895
|
-
headers: {
|
|
896
|
-
|
|
1271
|
+
headers: {
|
|
1272
|
+
accept: "application/json",
|
|
1273
|
+
"content-type": "application/json"
|
|
1274
|
+
},
|
|
1275
|
+
credentials: "same-origin",
|
|
1276
|
+
body: JSON.stringify({ accountId })
|
|
897
1277
|
});
|
|
898
1278
|
const body = await response.json().catch(() => void 0);
|
|
899
1279
|
if (!response.ok) throw new Error(body?.error ?? `HTTP ${response.status}`);
|
|
900
1280
|
if (body?.claimed === false && body.deviceCheckedIn === true) {
|
|
1281
|
+
setDeviceSpentByRegion((prev) => ({
|
|
1282
|
+
...prev,
|
|
1283
|
+
[activeRegion]: {
|
|
1284
|
+
...prev[activeRegion],
|
|
1285
|
+
[accountId]: true
|
|
1286
|
+
}
|
|
1287
|
+
}));
|
|
901
1288
|
await refreshUsage(activeRegion);
|
|
902
|
-
|
|
1289
|
+
await reloadAccountStatuses(activeRegion);
|
|
903
1290
|
return;
|
|
904
1291
|
}
|
|
905
1292
|
if (body?.claimed === false && body.alreadyCheckedIn !== true) throw new Error(body.message === void 0 || body.message === "" ? t("row.checkinRefused", { code: String(body.code ?? "") }) : `${t("row.checkinRefused", { code: String(body.code ?? "") })}: ${body.message}`);
|
|
906
1293
|
await refreshUsage(activeRegion);
|
|
1294
|
+
await reloadAccountStatuses(activeRegion);
|
|
907
1295
|
} catch (error) {
|
|
908
|
-
if (mounted.current)
|
|
1296
|
+
if (mounted.current) {
|
|
1297
|
+
const message = error instanceof Error ? error.message : t("row.requestFailed");
|
|
1298
|
+
setClaimErrorByRegion((prev) => ({
|
|
1299
|
+
...prev,
|
|
1300
|
+
[activeRegion]: {
|
|
1301
|
+
...prev[activeRegion],
|
|
1302
|
+
[accountId]: message
|
|
1303
|
+
}
|
|
1304
|
+
}));
|
|
1305
|
+
}
|
|
909
1306
|
} finally {
|
|
910
|
-
if (mounted.current)
|
|
1307
|
+
if (mounted.current) setClaimingFor(void 0);
|
|
911
1308
|
}
|
|
912
1309
|
};
|
|
913
1310
|
/**
|
|
1311
|
+
* Load every account row's per-account status when the card shows a
|
|
1312
|
+
* signed-in region. This is what makes the check-in buttons, balances, and
|
|
1313
|
+
* (ai) subscription rows appear. It is scoped to a stable `status.accounts`
|
|
1314
|
+
* identity so switching tabs or re-reading the document does not re-trigger a
|
|
1315
|
+
* burst of upstream calls for rows already loaded.
|
|
1316
|
+
*/
|
|
1317
|
+
(0, react.useEffect)(() => {
|
|
1318
|
+
if (!open || !activeRegionOn || status.status !== "signed-in" || status.accounts.length === 0) return;
|
|
1319
|
+
status.accounts.map((account) => account.id).join("\0");
|
|
1320
|
+
const loadedIds = accountStatusByRegion[activeRegion];
|
|
1321
|
+
const todo = status.accounts.filter((account) => loadedIds[account.id] === void 0);
|
|
1322
|
+
if (todo.length === 0) return;
|
|
1323
|
+
for (const account of todo) loadAccountStatus(account.id, activeRegion);
|
|
1324
|
+
}, [
|
|
1325
|
+
open,
|
|
1326
|
+
activeRegionOn,
|
|
1327
|
+
status,
|
|
1328
|
+
accountStatusByRegion
|
|
1329
|
+
]);
|
|
1330
|
+
/**
|
|
914
1331
|
* Switch one region's provider off or on (issue #11). The write carries the
|
|
915
1332
|
* region's whole slot through untouched — only `enabled` changes — so the
|
|
916
1333
|
* user's directory, model picks, image opt-ins and budgets survive a
|
|
@@ -930,16 +1347,17 @@ window.__ModuleLoader__.load({
|
|
|
930
1347
|
}
|
|
931
1348
|
};
|
|
932
1349
|
/**
|
|
933
|
-
* One-click availability test for
|
|
1350
|
+
* One-click availability test for a list of models of the active region.
|
|
934
1351
|
*
|
|
935
|
-
*
|
|
936
|
-
*
|
|
937
|
-
*
|
|
938
|
-
*
|
|
939
|
-
*
|
|
1352
|
+
* Shared by the「enabled models」and「all models」buttons: the only difference
|
|
1353
|
+
* between them is which ids are sent — the enabled rows or the whole visible
|
|
1354
|
+
* catalog. Each run goes one model per request, deliberately: the host probe
|
|
1355
|
+
* is sequential anyway (Trae throttles bursts), and a per-model round trip
|
|
1356
|
+
* lets each row show its verdict as soon as it is known instead of the card
|
|
1357
|
+
* sitting blank for a minute. User-started, and it stops on the first
|
|
1358
|
+
* authentication failure — every later probe would only repeat that answer.
|
|
940
1359
|
*/
|
|
941
|
-
const
|
|
942
|
-
const ids = [...activeEnabledIds];
|
|
1360
|
+
const testModels = async (ids) => {
|
|
943
1361
|
setTestError(void 0);
|
|
944
1362
|
setTestSummary(void 0);
|
|
945
1363
|
if (ids.length === 0) {
|
|
@@ -1007,6 +1425,33 @@ window.__ModuleLoader__.load({
|
|
|
1007
1425
|
if (mounted.current) setTesting(void 0);
|
|
1008
1426
|
}
|
|
1009
1427
|
};
|
|
1428
|
+
/** Test only the enabled models of the active region. */
|
|
1429
|
+
const testEnabledModels = async () => {
|
|
1430
|
+
const ids = [...activeEnabledIds];
|
|
1431
|
+
if (ids.length === 0) {
|
|
1432
|
+
setTestError(t("row.modelsTestEmpty"));
|
|
1433
|
+
setTestSummary(void 0);
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
testModels(ids);
|
|
1437
|
+
};
|
|
1438
|
+
/**
|
|
1439
|
+
* One-click availability test for EVERY model in the active region's catalog,
|
|
1440
|
+
* enabled or not — including the rows that are off and the rows the SOLO
|
|
1441
|
+
* channel does not serve. The host validates each id against the live catalog
|
|
1442
|
+
* and reports rows it does not know as skipped, so passing the whole visible
|
|
1443
|
+
* list cannot spend quota on arbitrary config names. Costs quota per model —
|
|
1444
|
+
* meaningfully more than the「enabled only」run, which the hint states.
|
|
1445
|
+
*/
|
|
1446
|
+
const testAllModels = async () => {
|
|
1447
|
+
const ids = visibleModels.map((model) => model.id);
|
|
1448
|
+
if (ids.length === 0) {
|
|
1449
|
+
setTestError(t("row.modelsTestEmpty"));
|
|
1450
|
+
setTestSummary(void 0);
|
|
1451
|
+
return;
|
|
1452
|
+
}
|
|
1453
|
+
testModels(ids);
|
|
1454
|
+
};
|
|
1010
1455
|
const refreshModels = async () => {
|
|
1011
1456
|
setBusy(true);
|
|
1012
1457
|
try {
|
|
@@ -1083,8 +1528,20 @@ window.__ModuleLoader__.load({
|
|
|
1083
1528
|
* message — it never sends one by itself.
|
|
1084
1529
|
*/
|
|
1085
1530
|
const rowImageIsAutomatic = (model) => model.multimodal === true;
|
|
1086
|
-
/**
|
|
1087
|
-
|
|
1531
|
+
/**
|
|
1532
|
+
* The international tab's plan is rendered by `rowValue`, in the account
|
|
1533
|
+
* table's 额度 column — one place, per account, beside the account it belongs
|
|
1534
|
+
* to.
|
|
1535
|
+
*
|
|
1536
|
+
* There used to be a SECOND surface for it: a standalone 「订阅状态」 box below
|
|
1537
|
+
* the table, reporting the bound account's plan on its own. It was added to
|
|
1538
|
+
* fix 「国际版没有那个积分框」 — an asymmetry against the CN tab's credit panel
|
|
1539
|
+
* — but the table's 额度 column had since absorbed exactly that figure, so the
|
|
1540
|
+
* box repeated the row directly above it. Two renderers of one verdict is the
|
|
1541
|
+
* drift risk `plan-text.ts` exists to prevent, and the box could only ever
|
|
1542
|
+
* restate what the marked 「当前使用」 row already said. It is gone; the row is
|
|
1543
|
+
* the single surface.
|
|
1544
|
+
*/
|
|
1088
1545
|
const activeContextBudgets = draft?.contextBudgets ?? savedContextBudgets;
|
|
1089
1546
|
const dirty = draft !== void 0;
|
|
1090
1547
|
/** Apply an edit to the ACTIVE tab's draft, seeding it from the current view. */
|
|
@@ -1167,6 +1624,85 @@ window.__ModuleLoader__.load({
|
|
|
1167
1624
|
}
|
|
1168
1625
|
};
|
|
1169
1626
|
const title = t("row.title");
|
|
1627
|
+
/**
|
|
1628
|
+
* Row labels for the account table.
|
|
1629
|
+
*
|
|
1630
|
+
* The raw `edition` token ("solo") told the user nothing about WHICH product a
|
|
1631
|
+
* credential came from, which is exactly what made a leftover TRAE SOLO login
|
|
1632
|
+
* indistinguishable from the Trae CN one they thought they were bound to
|
|
1633
|
+
* (issue #25). Spell the product out, and keep the region as the second axis.
|
|
1634
|
+
*/
|
|
1635
|
+
const editionLabel = (edition) => t(`row.edition${edition === "cn" ? "Cn" : edition === "sg" ? "Sg" : edition === "solo" ? "Solo" : "SoloSg"}`);
|
|
1636
|
+
const sourceLabel = (source) => source === "desktop" ? t("row.sourceDesktop") : source === "cli" ? t("row.sourceCli") : t("row.sourceDsh");
|
|
1637
|
+
/**
|
|
1638
|
+
* The trailing value on an account row's meta line: the account's general
|
|
1639
|
+
* balance (CN) or its subscription verdict (ai).
|
|
1640
|
+
*
|
|
1641
|
+
* A status that has not loaded — or whose read failed — renders NOTHING
|
|
1642
|
+
* rather than a dash. The row already names the account, its edition and its
|
|
1643
|
+
* source; a column of placeholder glyphs was pure noise, and it made every
|
|
1644
|
+
* row look like something was wrong. `undefined` means "no figure", never
|
|
1645
|
+
* "zero".
|
|
1646
|
+
*/
|
|
1647
|
+
const rowValue = (state) => {
|
|
1648
|
+
if (activeRegion === "ai") {
|
|
1649
|
+
const payStatus = state?.payStatus;
|
|
1650
|
+
if (payStatus === void 0) return void 0;
|
|
1651
|
+
const verdict = planVerdict(payStatus);
|
|
1652
|
+
if (!verdict.paid) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1653
|
+
className: "dsm-trae-account-value",
|
|
1654
|
+
children: t("row.noPackage")
|
|
1655
|
+
});
|
|
1656
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1657
|
+
className: "dsm-trae-account-value dsm-trae-account-value-on",
|
|
1658
|
+
children: verdict.tier === void 0 ? t("row.subscribed") : t("row.planTier", { tier: verdict.tier })
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
const figure = state?.credits?.generalAvailable;
|
|
1662
|
+
if (figure === void 0) return void 0;
|
|
1663
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1664
|
+
className: "dsm-trae-account-value",
|
|
1665
|
+
children: t("row.accountBalance", { credits: formatNumber(figure) })
|
|
1666
|
+
});
|
|
1667
|
+
};
|
|
1668
|
+
/**
|
|
1669
|
+
* The CN check-in control for one account row.
|
|
1670
|
+
*
|
|
1671
|
+
* The reward rides INSIDE the button ("签到 +150") instead of sitting beside
|
|
1672
|
+
* it as a second line, so the row says what the click pays without also
|
|
1673
|
+
* carrying a "150 credits per day" caption. Four states, none of which lies:
|
|
1674
|
+
*
|
|
1675
|
+
* - claimable — live, and names the payout.
|
|
1676
|
+
* - claiming — this row's own claim is in flight; only its button changes.
|
|
1677
|
+
* - claimed — `checkedIn`: this ACCOUNT was paid, so it reads as done.
|
|
1678
|
+
* - device spent — `didCheckedIn` (or a 9095 refusal from this row's click):
|
|
1679
|
+
* the DEVICE is spent but this account was never rewarded, so the button
|
|
1680
|
+
* says exactly that and stays disabled rather than reporting "done" for an
|
|
1681
|
+
* account that got nothing. The full rule lives in `title` — rendered as a
|
|
1682
|
+
* paragraph it dominated the row and drowned out the account itself.
|
|
1683
|
+
*
|
|
1684
|
+
* Until the row's status lands the button waits, disabled, so the row keeps
|
|
1685
|
+
* its shape instead of reflowing the moment the read returns.
|
|
1686
|
+
*/
|
|
1687
|
+
const checkinControl = (account, state) => {
|
|
1688
|
+
const checkin = state?.checkin;
|
|
1689
|
+
const reward = checkin === void 0 ? void 0 : formatNumber(checkin.credits);
|
|
1690
|
+
const claimed = checkin?.checkedIn === true;
|
|
1691
|
+
const deviceSpent = deviceSpentByRegion[activeRegion][account.id] === true || checkin?.didCheckedIn === true;
|
|
1692
|
+
const spending = claimingFor === account.id;
|
|
1693
|
+
const usable = checkin !== void 0 && checkin.enabled;
|
|
1694
|
+
const title = reward === void 0 || !usable ? t("row.checkinDisabled") : deviceSpent && !claimed ? t("row.checkinDeviceSpent") : claimed ? t("row.checkinClaimed") : t("row.checkinReward", { reward });
|
|
1695
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1696
|
+
type: "button",
|
|
1697
|
+
className: "dsm-trae-account-checkin",
|
|
1698
|
+
disabled: !usable || claimed || deviceSpent || spending,
|
|
1699
|
+
title,
|
|
1700
|
+
onClick: () => {
|
|
1701
|
+
claimFor(account.id);
|
|
1702
|
+
},
|
|
1703
|
+
children: spending ? t("row.checkinClaiming") : claimed ? t("row.checkinClaimed") : deviceSpent ? t("row.checkinDeviceSpentShort") : !usable || reward === void 0 ? t("row.checkinClaim") : t("row.checkinClaimWith", { reward })
|
|
1704
|
+
});
|
|
1705
|
+
};
|
|
1170
1706
|
const label = status.status === "signed-in" ? t("row.signedIn", { accountName: status.accountName }) : status.status === "error" ? t("row.requestFailed") : t("row.signedOut");
|
|
1171
1707
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
1172
1708
|
className: `dsm-plugin-card${open ? " dsm-plugin-card-open" : ""}`,
|
|
@@ -1229,7 +1765,7 @@ window.__ModuleLoader__.load({
|
|
|
1229
1765
|
style: dotStyle(regionStatus.status)
|
|
1230
1766
|
}), item === "cn" ? t("row.tabCn") : t("row.tabAi")]
|
|
1231
1767
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1232
|
-
className: "dsm-trae-tab-switch",
|
|
1768
|
+
className: "dsm-trae-switch dsm-trae-tab-switch",
|
|
1233
1769
|
title: t("row.tabSwitchHint"),
|
|
1234
1770
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1235
1771
|
type: "checkbox",
|
|
@@ -1258,86 +1794,174 @@ window.__ModuleLoader__.load({
|
|
|
1258
1794
|
role: "status",
|
|
1259
1795
|
children: t("row.tabOffNotice")
|
|
1260
1796
|
}),
|
|
1261
|
-
|
|
1262
|
-
className: "dsm-trae-
|
|
1263
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1264
|
-
className: "dsm-trae-
|
|
1265
|
-
title: t("row.
|
|
1797
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1798
|
+
className: "dsm-trae-prefs",
|
|
1799
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1800
|
+
className: "dsm-trae-pref",
|
|
1801
|
+
title: t("row.followAppLoginHint"),
|
|
1266
1802
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1267
1803
|
type: "checkbox",
|
|
1268
|
-
checked:
|
|
1269
|
-
disabled: settingsScope?.getSnapshot().writable !== true ||
|
|
1804
|
+
checked: settingsEdits.followAppLogin,
|
|
1805
|
+
disabled: settingsScope?.getSnapshot().writable !== true || savingSettings,
|
|
1806
|
+
"aria-describedby": followHintId,
|
|
1270
1807
|
onChange: (event) => {
|
|
1271
|
-
|
|
1808
|
+
writePreference("followAppLogin", event.currentTarget.checked);
|
|
1272
1809
|
}
|
|
1273
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1810
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1811
|
+
id: followHintId,
|
|
1812
|
+
children: [
|
|
1813
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", {
|
|
1814
|
+
id: followLabelId,
|
|
1815
|
+
children: t("row.followAppLogin")
|
|
1816
|
+
}),
|
|
1817
|
+
" ",
|
|
1818
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1819
|
+
className: "dsm-trae-pref-hint",
|
|
1820
|
+
children: t("row.followAppLoginHint")
|
|
1821
|
+
})
|
|
1822
|
+
]
|
|
1823
|
+
})]
|
|
1824
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1825
|
+
className: "dsm-trae-pref",
|
|
1826
|
+
title: t(activeRegion === "cn" ? "row.showPointsInMainUiHint" : "row.showPointsInMainUiHintAi"),
|
|
1281
1827
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1282
1828
|
type: "checkbox",
|
|
1283
|
-
checked:
|
|
1284
|
-
disabled: settingsScope?.getSnapshot().writable !== true,
|
|
1829
|
+
checked: settingsEdits.showPointsInMainUi,
|
|
1830
|
+
disabled: settingsScope?.getSnapshot().writable !== true || savingSettings,
|
|
1831
|
+
"aria-describedby": creditsHintId,
|
|
1285
1832
|
onChange: (event) => {
|
|
1286
|
-
|
|
1833
|
+
writePreference("showPointsInMainUi", event.currentTarget.checked);
|
|
1287
1834
|
}
|
|
1288
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label })]
|
|
1303
|
-
}), status.status === "signed-in" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1304
|
-
className: "dsm-trae-usage-expiry",
|
|
1305
|
-
children: t("row.tokenExpiry", { expiresAt: formatDateTime(status.tokenExpiresAtMs) })
|
|
1306
|
-
}) : null]
|
|
1307
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1308
|
-
type: "button",
|
|
1309
|
-
className: "dsm-btn dsm-btn-outline",
|
|
1310
|
-
disabled: busy,
|
|
1311
|
-
onClick: () => {
|
|
1312
|
-
rescanAccounts();
|
|
1313
|
-
},
|
|
1314
|
-
children: busy ? t("row.accountsScanning") : t("row.refreshTokens")
|
|
1835
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1836
|
+
id: creditsHintId,
|
|
1837
|
+
children: [
|
|
1838
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", {
|
|
1839
|
+
id: creditsLabelId,
|
|
1840
|
+
children: t("row.showPointsInMainUi")
|
|
1841
|
+
}),
|
|
1842
|
+
" ",
|
|
1843
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1844
|
+
className: "dsm-trae-pref-hint",
|
|
1845
|
+
children: t(activeRegion === "cn" ? "row.showPointsInMainUiHint" : "row.showPointsInMainUiHintAi")
|
|
1846
|
+
})
|
|
1847
|
+
]
|
|
1848
|
+
})]
|
|
1315
1849
|
})]
|
|
1316
1850
|
}),
|
|
1317
|
-
|
|
1318
|
-
className: "dsm-trae-
|
|
1851
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1852
|
+
className: "dsm-trae-accounts",
|
|
1319
1853
|
"aria-label": t("row.accountsTitle"),
|
|
1320
1854
|
children: [
|
|
1321
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1322
|
-
className: "dsm-trae-
|
|
1323
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
1324
|
-
className: "dsm-trae-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1855
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1856
|
+
className: "dsm-trae-accounts-head",
|
|
1857
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1858
|
+
className: "dsm-trae-accounts-title",
|
|
1859
|
+
children: t("row.accountsTitle")
|
|
1860
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1861
|
+
className: "dsm-trae-accounts-hint",
|
|
1862
|
+
children: t("row.accountsHint")
|
|
1863
|
+
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1864
|
+
type: "button",
|
|
1865
|
+
className: "dsm-btn dsm-btn-outline",
|
|
1866
|
+
disabled: busy,
|
|
1867
|
+
onClick: () => {
|
|
1868
|
+
rescanAccounts();
|
|
1329
1869
|
},
|
|
1330
|
-
children:
|
|
1331
|
-
|
|
1870
|
+
children: busy ? t("row.accountsScanning") : t("row.refreshTokens")
|
|
1871
|
+
})]
|
|
1872
|
+
}),
|
|
1873
|
+
status.status !== "signed-in" || status.accounts.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1874
|
+
className: "dsm-trae-usage-account",
|
|
1875
|
+
role: "status",
|
|
1876
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1877
|
+
className: "dsm-trae-usage-account-copy",
|
|
1878
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1879
|
+
className: "dsm-trae-usage-status",
|
|
1880
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1881
|
+
"aria-hidden": "true",
|
|
1882
|
+
className: "dsm-trae-usage-dot",
|
|
1883
|
+
style: dotStyle(status.status)
|
|
1884
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label })]
|
|
1885
|
+
}), status.status === "signed-in" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1886
|
+
className: "dsm-trae-usage-expiry",
|
|
1887
|
+
children: t("row.tokenExpiry", { expiresAt: formatDateTime(status.tokenExpiresAtMs) })
|
|
1888
|
+
}) : null]
|
|
1889
|
+
})
|
|
1890
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1891
|
+
className: `dsm-trae-account-table${activeRegion === "cn" ? " dsm-trae-account-table-checkin" : ""}`,
|
|
1892
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1893
|
+
className: "dsm-trae-account-row dsm-trae-account-row-head",
|
|
1894
|
+
role: "row",
|
|
1895
|
+
children: [
|
|
1896
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.accountColumnAccount") }),
|
|
1897
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.accountColumnValue") }),
|
|
1898
|
+
activeRegion === "cn" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.accountColumnCheckin") }) : null,
|
|
1899
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {})
|
|
1900
|
+
]
|
|
1901
|
+
}), status.accounts.map((account) => {
|
|
1902
|
+
const current = status.status === "signed-in" && account.id === status.accountId;
|
|
1903
|
+
const state = accountStatusByRegion[activeRegion][account.id];
|
|
1904
|
+
const error = claimErrorByRegion[activeRegion][account.id];
|
|
1905
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1906
|
+
className: `dsm-trae-account-row${current ? " dsm-trae-account-row-current" : ""}`,
|
|
1907
|
+
role: "row",
|
|
1332
1908
|
children: [
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1909
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1910
|
+
className: "dsm-trae-account-who",
|
|
1911
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1912
|
+
"aria-hidden": "true",
|
|
1913
|
+
className: "dsm-trae-usage-dot",
|
|
1914
|
+
style: dotStyle(account.selected ? "signed-in" : "signed-out")
|
|
1915
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1916
|
+
className: "dsm-trae-account-name",
|
|
1917
|
+
children: [
|
|
1918
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: account.accountName }),
|
|
1919
|
+
current ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1920
|
+
className: "dsm-trae-account-badge",
|
|
1921
|
+
children: t("row.accountCurrentBadge")
|
|
1922
|
+
}) : null,
|
|
1923
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1924
|
+
className: "dsm-trae-account-meta",
|
|
1925
|
+
children: [
|
|
1926
|
+
editionLabel(account.edition),
|
|
1927
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
|
|
1928
|
+
"aria-hidden": "true",
|
|
1929
|
+
children: "·"
|
|
1930
|
+
}),
|
|
1931
|
+
sourceLabel(account.source)
|
|
1932
|
+
]
|
|
1933
|
+
}),
|
|
1934
|
+
error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1935
|
+
className: "dsm-trae-account-note",
|
|
1936
|
+
role: "alert",
|
|
1937
|
+
children: t("row.checkinError", { message: error })
|
|
1938
|
+
})
|
|
1939
|
+
]
|
|
1940
|
+
})]
|
|
1941
|
+
}),
|
|
1942
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1943
|
+
className: "dsm-trae-account-worth",
|
|
1944
|
+
children: rowValue(state)
|
|
1945
|
+
}),
|
|
1946
|
+
activeRegion === "cn" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1947
|
+
className: "dsm-trae-account-checkin-cell",
|
|
1948
|
+
children: checkinControl(account, state)
|
|
1949
|
+
}) : null,
|
|
1950
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1951
|
+
className: "dsm-trae-account-actions",
|
|
1952
|
+
children: current ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1953
|
+
type: "button",
|
|
1954
|
+
className: "dsm-btn dsm-btn-outline dsm-trae-account-switch",
|
|
1955
|
+
disabled: switchingAccount || settingsScope?.getSnapshot()?.writable !== true,
|
|
1956
|
+
onClick: () => {
|
|
1957
|
+
switchAccount(account.id);
|
|
1958
|
+
},
|
|
1959
|
+
children: switchingAccount ? t("row.accountSwitching") : t("row.accountSwitch")
|
|
1960
|
+
})
|
|
1961
|
+
})
|
|
1338
1962
|
]
|
|
1339
|
-
}, account.id)
|
|
1340
|
-
})
|
|
1963
|
+
}, account.id);
|
|
1964
|
+
})]
|
|
1341
1965
|
}),
|
|
1342
1966
|
status.status === "signed-in" && status.alternatives !== void 0 && status.alternatives.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1343
1967
|
className: "dsm-trae-usage-alternative",
|
|
@@ -1360,12 +1984,21 @@ window.__ModuleLoader__.load({
|
|
|
1360
1984
|
children: t("row.creditsAlternativeSwitch")
|
|
1361
1985
|
})]
|
|
1362
1986
|
}) : null,
|
|
1987
|
+
status.status === "signed-in" && status.credits?.expiry !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExpiryNotice, {
|
|
1988
|
+
expiry: status.credits.expiry,
|
|
1989
|
+
t,
|
|
1990
|
+
formatNumber,
|
|
1991
|
+
formatDateTime
|
|
1992
|
+
}) : null,
|
|
1363
1993
|
status.status === "signed-in" && status.appLogin !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1364
1994
|
className: "dsm-trae-usage-alternative",
|
|
1365
1995
|
role: "status",
|
|
1366
1996
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1367
1997
|
className: "dsm-trae-usage-alternative-text",
|
|
1368
|
-
children: t("row.appLoginMismatch", {
|
|
1998
|
+
children: t("row.appLoginMismatch", {
|
|
1999
|
+
account: status.appLogin.accountName,
|
|
2000
|
+
bound: status.accountName
|
|
2001
|
+
})
|
|
1369
2002
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1370
2003
|
type: "button",
|
|
1371
2004
|
className: "dsm-trae-usage-switch-button",
|
|
@@ -1373,7 +2006,7 @@ window.__ModuleLoader__.load({
|
|
|
1373
2006
|
onClick: () => {
|
|
1374
2007
|
followAppLogin();
|
|
1375
2008
|
},
|
|
1376
|
-
children: t("row.appLoginFollow")
|
|
2009
|
+
children: t("row.appLoginFollow", { account: status.appLogin.accountName })
|
|
1377
2010
|
})]
|
|
1378
2011
|
}) : null,
|
|
1379
2012
|
rescanNote === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -1382,132 +2015,8 @@ window.__ModuleLoader__.load({
|
|
|
1382
2015
|
children: rescanNote
|
|
1383
2016
|
})
|
|
1384
2017
|
]
|
|
1385
|
-
})
|
|
2018
|
+
}),
|
|
1386
2019
|
status.status === "signed-in" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1387
|
-
status.payStatus === void 0 || payPlan === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1388
|
-
className: "dsm-trae-usage-list",
|
|
1389
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1390
|
-
className: "dsm-trae-usage-stats dsm-trae-usage-stats-two",
|
|
1391
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1392
|
-
className: "dsm-trae-usage-stat dsm-trae-usage-stat-general",
|
|
1393
|
-
children: [
|
|
1394
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1395
|
-
className: "dsm-trae-usage-stat-head",
|
|
1396
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1397
|
-
className: "dsm-trae-usage-stat-label",
|
|
1398
|
-
children: t("row.subscriptionLabel")
|
|
1399
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1400
|
-
className: `dsm-trae-usage-stat-badge ${payPlan.paid ? "dsm-trae-usage-stat-badge-on" : "dsm-trae-usage-stat-badge-off"}`,
|
|
1401
|
-
children: t(payPlan.label)
|
|
1402
|
-
})]
|
|
1403
|
-
}),
|
|
1404
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1405
|
-
className: "dsm-trae-usage-stat-value dsm-trae-usage-stat-value-general",
|
|
1406
|
-
children: status.payStatus.inTrial && status.payStatus.trialEndTimeMs > 0 ? t("row.trialUntil", { date: formatDateTime(status.payStatus.trialEndTimeMs) }) : [t(payPlan.label), payPlan.tier].filter(Boolean).join(" · ")
|
|
1407
|
-
}),
|
|
1408
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1409
|
-
className: "dsm-trae-usage-stat-hint",
|
|
1410
|
-
children: status.payStatus.enableSoloLite || status.payStatus.enableSoloCoder || status.payStatus.enableSoloBuilder || status.payStatus.enableSoloWeb ? [
|
|
1411
|
-
status.payStatus.enableSoloLite ? "SOLO Lite" : "",
|
|
1412
|
-
status.payStatus.enableSoloCoder ? "SOLO Coder" : "",
|
|
1413
|
-
status.payStatus.enableSoloBuilder ? "SOLO Builder" : "",
|
|
1414
|
-
status.payStatus.enableSoloWeb ? "SOLO Web" : ""
|
|
1415
|
-
].filter(Boolean).join(" · ") : status.payStatus.isDollarUsageBilling ? "dollar usage billing" : ""
|
|
1416
|
-
})
|
|
1417
|
-
]
|
|
1418
|
-
})
|
|
1419
|
-
})
|
|
1420
|
-
}),
|
|
1421
|
-
status.payStatusError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1422
|
-
className: "dsm-trae-usage-error",
|
|
1423
|
-
children: t("row.payStatusError", { message: status.payStatusError })
|
|
1424
|
-
}),
|
|
1425
|
-
status.credits === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1426
|
-
className: "dsm-trae-usage-list",
|
|
1427
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1428
|
-
className: "dsm-trae-usage-stats dsm-trae-usage-stats-two",
|
|
1429
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1430
|
-
className: "dsm-trae-usage-stat dsm-trae-usage-stat-work",
|
|
1431
|
-
children: [
|
|
1432
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1433
|
-
className: "dsm-trae-usage-stat-head",
|
|
1434
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1435
|
-
className: "dsm-trae-usage-stat-label",
|
|
1436
|
-
children: t("row.workCreditsLabel")
|
|
1437
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1438
|
-
className: "dsm-trae-usage-stat-badge dsm-trae-usage-stat-badge-off",
|
|
1439
|
-
children: t("row.workNotUsable")
|
|
1440
|
-
})]
|
|
1441
|
-
}),
|
|
1442
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1443
|
-
className: "dsm-trae-usage-stat-value dsm-trae-usage-stat-value-work",
|
|
1444
|
-
children: formatNumber(status.credits.workAvailable)
|
|
1445
|
-
}),
|
|
1446
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1447
|
-
className: "dsm-trae-usage-stat-hint",
|
|
1448
|
-
children: t("row.workUsableHint")
|
|
1449
|
-
})
|
|
1450
|
-
]
|
|
1451
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1452
|
-
className: "dsm-trae-usage-stat dsm-trae-usage-stat-general",
|
|
1453
|
-
children: [
|
|
1454
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1455
|
-
className: "dsm-trae-usage-stat-head",
|
|
1456
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1457
|
-
className: "dsm-trae-usage-stat-label",
|
|
1458
|
-
children: t("row.generalCreditsLabel")
|
|
1459
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1460
|
-
className: "dsm-trae-usage-stat-badge dsm-trae-usage-stat-badge-on",
|
|
1461
|
-
children: t("row.generalUsable")
|
|
1462
|
-
})]
|
|
1463
|
-
}),
|
|
1464
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1465
|
-
className: "dsm-trae-usage-stat-value dsm-trae-usage-stat-value-general",
|
|
1466
|
-
children: formatNumber(status.credits.generalAvailable)
|
|
1467
|
-
}),
|
|
1468
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1469
|
-
className: "dsm-trae-usage-stat-hint",
|
|
1470
|
-
children: t("row.generalUsableHint")
|
|
1471
|
-
})
|
|
1472
|
-
]
|
|
1473
|
-
})]
|
|
1474
|
-
})
|
|
1475
|
-
}),
|
|
1476
|
-
status.creditsError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1477
|
-
className: "dsm-trae-usage-error",
|
|
1478
|
-
children: t("row.creditsError", { message: status.creditsError })
|
|
1479
|
-
}),
|
|
1480
|
-
status.checkin === void 0 ? null : (() => {
|
|
1481
|
-
const accountClaimed = status.checkin.checkedIn;
|
|
1482
|
-
const deviceSpent = !accountClaimed && (status.checkin.didCheckedIn || deviceSpentNotice);
|
|
1483
|
-
const reward = formatNumber(status.checkin.credits);
|
|
1484
|
-
const bonus = status.checkin.extraCredits === void 0 ? "" : ` + ${formatNumber(status.checkin.extraCredits)}`;
|
|
1485
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1486
|
-
className: "dsm-trae-checkin",
|
|
1487
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1488
|
-
className: "dsm-trae-checkin-copy",
|
|
1489
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1490
|
-
className: "dsm-trae-checkin-label",
|
|
1491
|
-
children: t("row.checkinLabel")
|
|
1492
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1493
|
-
className: "dsm-trae-checkin-hint",
|
|
1494
|
-
children: [t("row.checkinReward", { reward: `${reward}${bonus}` }), accountClaimed ? ` · ${t("row.checkinDoneHint")}` : ""]
|
|
1495
|
-
})]
|
|
1496
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1497
|
-
type: "button",
|
|
1498
|
-
className: "dsm-btn dsm-btn-primary dsm-trae-checkin-button",
|
|
1499
|
-
disabled: !status.checkin.enabled || accountClaimed || deviceSpent || claiming,
|
|
1500
|
-
onClick: () => {
|
|
1501
|
-
claimCheckin();
|
|
1502
|
-
},
|
|
1503
|
-
children: claiming ? t("row.checkinClaiming") : accountClaimed ? t("row.checkinClaimed") : t("row.checkinClaim")
|
|
1504
|
-
})]
|
|
1505
|
-
}), deviceSpent ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1506
|
-
className: "dsm-trae-checkin-note",
|
|
1507
|
-
role: "status",
|
|
1508
|
-
children: t("row.checkinDeviceSpent")
|
|
1509
|
-
}) : null] });
|
|
1510
|
-
})(),
|
|
1511
2020
|
status.checkin !== void 0 && !status.checkin.enabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1512
2021
|
className: "dsm-trae-usage-error",
|
|
1513
2022
|
children: t("row.checkinDisabled")
|
|
@@ -1516,11 +2025,6 @@ window.__ModuleLoader__.load({
|
|
|
1516
2025
|
className: "dsm-trae-usage-error",
|
|
1517
2026
|
children: t("row.checkinError", { message: status.checkinError })
|
|
1518
2027
|
}),
|
|
1519
|
-
claimError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1520
|
-
className: "dsm-trae-usage-error",
|
|
1521
|
-
role: "alert",
|
|
1522
|
-
children: t("row.checkinError", { message: claimError })
|
|
1523
|
-
}),
|
|
1524
2028
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1525
2029
|
className: "dsm-trae-models",
|
|
1526
2030
|
"aria-label": t("row.modelsTitle"),
|
|
@@ -1535,27 +2039,43 @@ window.__ModuleLoader__.load({
|
|
|
1535
2039
|
children: t("row.modelsSummary", { count: activeEnabledIds.size })
|
|
1536
2040
|
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1537
2041
|
className: "dsm-trae-models-actions",
|
|
1538
|
-
children: [
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
2042
|
+
children: [
|
|
2043
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2044
|
+
type: "button",
|
|
2045
|
+
className: "dsm-btn dsm-btn-outline",
|
|
2046
|
+
disabled: busy,
|
|
2047
|
+
onClick: () => {
|
|
2048
|
+
refreshModels();
|
|
2049
|
+
},
|
|
2050
|
+
children: busy ? t("row.modelsRefreshing") : t("row.modelsRefresh")
|
|
2051
|
+
}),
|
|
2052
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2053
|
+
type: "button",
|
|
2054
|
+
className: "dsm-btn dsm-btn-outline",
|
|
2055
|
+
disabled: busy || testing !== void 0 || activeEnabledIds.size === 0,
|
|
2056
|
+
title: t("row.modelsTestHint"),
|
|
2057
|
+
onClick: () => {
|
|
2058
|
+
testEnabledModels();
|
|
2059
|
+
},
|
|
2060
|
+
children: testing === void 0 ? t("row.modelsTestEnabled") : t("row.modelsTesting", {
|
|
2061
|
+
done: testing.done,
|
|
2062
|
+
total: testing.total
|
|
2063
|
+
})
|
|
2064
|
+
}),
|
|
2065
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2066
|
+
type: "button",
|
|
2067
|
+
className: "dsm-btn dsm-btn-outline",
|
|
2068
|
+
disabled: busy || testing !== void 0 || visibleModels.length === 0,
|
|
2069
|
+
title: t("row.modelsTestAllHint"),
|
|
2070
|
+
onClick: () => {
|
|
2071
|
+
testAllModels();
|
|
2072
|
+
},
|
|
2073
|
+
children: testing === void 0 ? t("row.modelsTestAll") : t("row.modelsTesting", {
|
|
2074
|
+
done: testing.done,
|
|
2075
|
+
total: testing.total
|
|
2076
|
+
})
|
|
1557
2077
|
})
|
|
1558
|
-
|
|
2078
|
+
]
|
|
1559
2079
|
})]
|
|
1560
2080
|
}),
|
|
1561
2081
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -1897,20 +2417,30 @@ window.__ModuleLoader__.load({
|
|
|
1897
2417
|
*/
|
|
1898
2418
|
const COMPOSER_POINTS_REFRESH_INTERVAL_MS = 3e5;
|
|
1899
2419
|
/**
|
|
1900
|
-
* Provider routes
|
|
1901
|
-
*
|
|
2420
|
+
* Provider routes this readout serves, mapped to the region to read from.
|
|
2421
|
+
*
|
|
2422
|
+
* BOTH regions, but they do not show the same KIND of number, and that is the
|
|
2423
|
+
* data's doing rather than a preference:
|
|
1902
2424
|
*
|
|
1903
|
-
*
|
|
1904
|
-
*
|
|
1905
|
-
* `credits` on it (`webStatusFor` returns early
|
|
1906
|
-
*
|
|
1907
|
-
*
|
|
1908
|
-
* — a number-slot with no number — while an international model is selected.
|
|
2425
|
+
* - `cn` is credit-based, so the readout shows the account's general balance.
|
|
2426
|
+
* - `ai` is subscription-based. The Host builds its usage document from
|
|
2427
|
+
* `payStatus` and never sets `credits` on it (`webStatusFor` returns early
|
|
2428
|
+
* for `region === 'ai'`), so there is no balance to show. What it does carry
|
|
2429
|
+
* is the PLAN, so the readout names the plan (`Pro` / `Free`) instead.
|
|
1909
2430
|
*
|
|
1910
|
-
*
|
|
1911
|
-
*
|
|
2431
|
+
* The international route was previously omitted entirely, which meant the
|
|
2432
|
+
* readout vanished while an international model was selected — the asymmetry a
|
|
2433
|
+
* user reported. Omitting it was the safer half of a real problem (a plan-shaped
|
|
2434
|
+
* document templated into a number slot renders a dash), but the fix is to
|
|
2435
|
+
* render what that side actually has, not to render nothing.
|
|
2436
|
+
*
|
|
2437
|
+
* Mirrors `TRAE_PROVIDERS` on the host side; duplicated here because the client
|
|
2438
|
+
* bundle must not import the host entry.
|
|
1912
2439
|
*/
|
|
1913
|
-
const TRAE_COMPOSER_PROVIDERS = {
|
|
2440
|
+
const TRAE_COMPOSER_PROVIDERS = {
|
|
2441
|
+
"trae": "cn",
|
|
2442
|
+
"trae-global": "ai"
|
|
2443
|
+
};
|
|
1914
2444
|
/** The general credit balance out of one usage document, or undefined. */
|
|
1915
2445
|
function generalCreditsOf(usage) {
|
|
1916
2446
|
if (usage.status !== "signed-in") return void 0;
|
|
@@ -1920,6 +2450,36 @@ window.__ModuleLoader__.load({
|
|
|
1920
2450
|
function formatCredits(value) {
|
|
1921
2451
|
return value.toLocaleString("en-US", { maximumFractionDigits: 0 });
|
|
1922
2452
|
}
|
|
2453
|
+
/** The soonest expiry any bucket names, or undefined when none carries one. */
|
|
2454
|
+
function soonestExpiryOf(expiry) {
|
|
2455
|
+
return expiry.within24h.earliestExpiresAtMs ?? expiry.within7d.earliestExpiresAtMs ?? void 0;
|
|
2456
|
+
}
|
|
2457
|
+
/**
|
|
2458
|
+
* Which urgency tone the notice takes.
|
|
2459
|
+
*
|
|
2460
|
+
* Escalation order matters: a live expiry the user can still act on outranks a
|
|
2461
|
+
* lapsed one they cannot, even when the lapsed amount is larger.
|
|
2462
|
+
*/
|
|
2463
|
+
function expiryToneOf(expiry) {
|
|
2464
|
+
if (expiry.within24h.packs > 0) return "dsm-trae-expiry-soon";
|
|
2465
|
+
if (expiry.within7d.packs > 0) return "dsm-trae-expiry-week";
|
|
2466
|
+
return "dsm-trae-expiry-lapsed";
|
|
2467
|
+
}
|
|
2468
|
+
/** The notice's headline sentence, naming the amount and the pack count. */
|
|
2469
|
+
function expiryMessageOf(expiry, t) {
|
|
2470
|
+
if (expiry.within24h.packs > 0) return t("row.expiryWarningSoon", {
|
|
2471
|
+
total: formatCredits(expiry.within24h.total),
|
|
2472
|
+
packs: String(expiry.within24h.packs)
|
|
2473
|
+
});
|
|
2474
|
+
if (expiry.within7d.packs > 0) return t("row.expiryWarningWeek", {
|
|
2475
|
+
total: formatCredits(expiry.within7d.total),
|
|
2476
|
+
packs: String(expiry.within7d.packs)
|
|
2477
|
+
});
|
|
2478
|
+
return t("row.expiryWarningLapsed", {
|
|
2479
|
+
total: formatCredits(expiry.expired.total),
|
|
2480
|
+
packs: String(expiry.expired.packs)
|
|
2481
|
+
});
|
|
2482
|
+
}
|
|
1923
2483
|
function formatClock(value) {
|
|
1924
2484
|
return new Date(value).toLocaleTimeString("zh-CN", {
|
|
1925
2485
|
hour: "2-digit",
|
|
@@ -1933,6 +2493,16 @@ window.__ModuleLoader__.load({
|
|
|
1933
2493
|
const activeRegion = region ?? owned;
|
|
1934
2494
|
const [usage, setUsage] = (0, react.useState)(void 0);
|
|
1935
2495
|
const [accountCredits, setAccountCredits] = (0, react.useState)(void 0);
|
|
2496
|
+
/**
|
|
2497
|
+
* Per-account PLANS, international region only.
|
|
2498
|
+
*
|
|
2499
|
+
* The CN panel reads the credit route; that route is CN-shaped (it calls the
|
|
2500
|
+
* credit snapshot, which the ai document does not carry), so the international
|
|
2501
|
+
* panel reads the per-account STATUS route instead — the same one the card's
|
|
2502
|
+
* roster uses, which answers `payStatus` for ai. Reads only while the panel is
|
|
2503
|
+
* open, for the same reason the credit route does.
|
|
2504
|
+
*/
|
|
2505
|
+
const [accountPlans, setAccountPlans] = (0, react.useState)(void 0);
|
|
1936
2506
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
1937
2507
|
const [switchingId, setSwitchingId] = (0, react.useState)(void 0);
|
|
1938
2508
|
const [failed, setFailed] = (0, react.useState)(false);
|
|
@@ -2018,17 +2588,51 @@ window.__ModuleLoader__.load({
|
|
|
2018
2588
|
controller.abort();
|
|
2019
2589
|
};
|
|
2020
2590
|
}, [owned, fetchUsage]);
|
|
2591
|
+
/**
|
|
2592
|
+
* Every account's PLAN, one status read per account — ai only.
|
|
2593
|
+
*
|
|
2594
|
+
* Costs one upstream call per account, paid only while the panel is open (the
|
|
2595
|
+
* same rule the credit route follows). A failed read leaves that account's plan
|
|
2596
|
+
* undefined, which the row renders as a dash: the row still switches accounts,
|
|
2597
|
+
* which is the panel's primary job.
|
|
2598
|
+
*/
|
|
2599
|
+
const fetchAccountPlans = (0, react.useCallback)(async (signal) => {
|
|
2600
|
+
if (activeRegion !== "ai") return;
|
|
2601
|
+
const roster = usage?.status === "signed-in" ? usage.accounts.filter((account) => account.region === "ai") : [];
|
|
2602
|
+
if (roster.length === 0) {
|
|
2603
|
+
setAccountPlans({});
|
|
2604
|
+
return;
|
|
2605
|
+
}
|
|
2606
|
+
const entries = await Promise.all(roster.map(async (account) => {
|
|
2607
|
+
try {
|
|
2608
|
+
const response = await fetch(`${withTraeRegion(TRAE_ACCOUNT_STATUS_PATH, "ai")}&accountId=${encodeURIComponent(account.id)}`, {
|
|
2609
|
+
headers: { accept: "application/json" },
|
|
2610
|
+
credentials: "same-origin",
|
|
2611
|
+
...signal === void 0 ? {} : { signal }
|
|
2612
|
+
});
|
|
2613
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
2614
|
+
const state = await response.json();
|
|
2615
|
+
return [account.id, state.payStatus];
|
|
2616
|
+
} catch {
|
|
2617
|
+
return [account.id, void 0];
|
|
2618
|
+
}
|
|
2619
|
+
}));
|
|
2620
|
+
if (!mounted.current || signal?.aborted === true) return;
|
|
2621
|
+
setAccountPlans(Object.fromEntries(entries));
|
|
2622
|
+
}, [activeRegion, usage]);
|
|
2021
2623
|
(0, react.useEffect)(() => {
|
|
2022
2624
|
if (!open || activeRegion === void 0) return void 0;
|
|
2023
2625
|
const controller = new AbortController();
|
|
2024
|
-
|
|
2626
|
+
if (activeRegion === "ai") fetchAccountPlans(controller.signal);
|
|
2627
|
+
else fetchAccountCredits(controller.signal);
|
|
2025
2628
|
return () => {
|
|
2026
2629
|
controller.abort();
|
|
2027
2630
|
};
|
|
2028
2631
|
}, [
|
|
2029
2632
|
open,
|
|
2030
2633
|
activeRegion,
|
|
2031
|
-
fetchAccountCredits
|
|
2634
|
+
fetchAccountCredits,
|
|
2635
|
+
fetchAccountPlans
|
|
2032
2636
|
]);
|
|
2033
2637
|
(0, react.useEffect)(() => {
|
|
2034
2638
|
if (!open) return void 0;
|
|
@@ -2065,7 +2669,8 @@ window.__ModuleLoader__.load({
|
|
|
2065
2669
|
const readBack = configuredAccountsOf(settingsScope.getSnapshot().value);
|
|
2066
2670
|
if (accepted === false || readBack[activeRegion] !== accountId) throw new Error("the Host did not persist the switch");
|
|
2067
2671
|
await fetchUsage();
|
|
2068
|
-
await
|
|
2672
|
+
if (activeRegion === "ai") await fetchAccountPlans();
|
|
2673
|
+
else await fetchAccountCredits();
|
|
2069
2674
|
} catch {
|
|
2070
2675
|
if (mounted.current) setFailed(true);
|
|
2071
2676
|
} finally {
|
|
@@ -2074,10 +2679,21 @@ window.__ModuleLoader__.load({
|
|
|
2074
2679
|
};
|
|
2075
2680
|
if (owned === void 0) return null;
|
|
2076
2681
|
const signedIn = usage?.status === "signed-in" ? usage : void 0;
|
|
2077
|
-
const general = signedIn === void 0 ? void 0 : generalCreditsOf(signedIn);
|
|
2078
2682
|
const signedOut = usage !== void 0 && usage.status === "signed-out";
|
|
2079
|
-
const
|
|
2080
|
-
|
|
2683
|
+
const general = signedIn === void 0 ? void 0 : generalCreditsOf(signedIn);
|
|
2684
|
+
/**
|
|
2685
|
+
* What the readout shows after the separator.
|
|
2686
|
+
*
|
|
2687
|
+
* Region-dependent because the two sides answer different questions: CN has a
|
|
2688
|
+
* spendable balance, ai has a plan. Reading the credit field on an ai document
|
|
2689
|
+
* would always be `undefined` and print the dash this change exists to remove.
|
|
2690
|
+
*/
|
|
2691
|
+
const valueText = activeRegion === "ai" ? signedIn?.payStatus === void 0 ? "—" : planText(signedIn.payStatus, t) : general === void 0 ? "—" : formatCredits(general);
|
|
2692
|
+
/**
|
|
2693
|
+
* The label before the separator. Per REGION, not one hardcoded 'Trae CN':
|
|
2694
|
+
* the international readout sat next to the wrong product name otherwise.
|
|
2695
|
+
*/
|
|
2696
|
+
const label = activeRegion === "ai" ? t("composer.pointsGlobal") : t("composer.points");
|
|
2081
2697
|
/**
|
|
2082
2698
|
* The table's rows: every account in this region, with its general balance.
|
|
2083
2699
|
*
|
|
@@ -2087,7 +2703,11 @@ window.__ModuleLoader__.load({
|
|
|
2087
2703
|
* than a spinner the user has to wait on.
|
|
2088
2704
|
*/
|
|
2089
2705
|
const selectedId = accountCredits?.find((account) => account.selected)?.id ?? (signedIn === void 0 ? void 0 : signedIn.accountId);
|
|
2090
|
-
const rows = (
|
|
2706
|
+
const rows = (activeRegion === "ai" ? signedIn === void 0 ? [] : signedIn.accounts.filter((account) => account.region === "ai").map((account) => ({
|
|
2707
|
+
id: account.id,
|
|
2708
|
+
accountName: account.accountName,
|
|
2709
|
+
selected: account.selected
|
|
2710
|
+
})) : accountCredits ?? (signedIn === void 0 ? [] : signedIn.accounts.filter((account) => account.region === activeRegion).map((account) => ({
|
|
2091
2711
|
id: account.id,
|
|
2092
2712
|
accountName: account.accountName,
|
|
2093
2713
|
selected: account.selected
|
|
@@ -2124,13 +2744,13 @@ window.__ModuleLoader__.load({
|
|
|
2124
2744
|
top: 0
|
|
2125
2745
|
},
|
|
2126
2746
|
role: "dialog",
|
|
2127
|
-
"aria-label": t("composer.panelTitle"),
|
|
2747
|
+
"aria-label": activeRegion === "ai" ? t("composer.panelTitleGlobal") : t("composer.panelTitle"),
|
|
2128
2748
|
children: [
|
|
2129
2749
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2130
2750
|
className: "dsm-trae-composer-panel-head",
|
|
2131
2751
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2132
2752
|
className: "dsm-trae-composer-panel-title",
|
|
2133
|
-
children: t("composer.panelTitle")
|
|
2753
|
+
children: activeRegion === "ai" ? t("composer.panelTitleGlobal") : t("composer.panelTitle")
|
|
2134
2754
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2135
2755
|
type: "button",
|
|
2136
2756
|
className: "dsm-trae-composer-panel-refresh",
|
|
@@ -2155,7 +2775,7 @@ window.__ModuleLoader__.load({
|
|
|
2155
2775
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
2156
2776
|
scope: "col",
|
|
2157
2777
|
className: "dsm-trae-composer-panel-num",
|
|
2158
|
-
children: t("composer.generalCredits")
|
|
2778
|
+
children: activeRegion === "ai" ? t("composer.plan") : t("composer.generalCredits")
|
|
2159
2779
|
})] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: rows.map((row) => {
|
|
2160
2780
|
const current = row.id === selectedId;
|
|
2161
2781
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
|
|
@@ -2177,11 +2797,22 @@ window.__ModuleLoader__.load({
|
|
|
2177
2797
|
})
|
|
2178
2798
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2179
2799
|
className: "dsm-trae-composer-panel-num",
|
|
2180
|
-
children:
|
|
2800
|
+
children: activeRegion === "ai" ? (() => {
|
|
2801
|
+
const payStatus = row.id === selectedId && signedIn?.payStatus !== void 0 ? signedIn.payStatus : accountPlans?.[row.id];
|
|
2802
|
+
return payStatus === void 0 ? "—" : planText(payStatus, t);
|
|
2803
|
+
})() : row.generalAvailable === void 0 ? "—" : formatCredits(row.generalAvailable)
|
|
2181
2804
|
})]
|
|
2182
2805
|
}, row.id);
|
|
2183
2806
|
}) })]
|
|
2184
2807
|
}),
|
|
2808
|
+
activeRegion === "cn" && signedIn?.credits?.expiry !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2809
|
+
className: `dsm-trae-expiry ${expiryToneOf(signedIn.credits.expiry)}`,
|
|
2810
|
+
role: "status",
|
|
2811
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: expiryMessageOf(signedIn.credits.expiry, t) }), soonestExpiryOf(signedIn.credits.expiry) === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2812
|
+
className: "dsm-trae-expiry-detail",
|
|
2813
|
+
children: t("row.expiryEarliest", { time: formatClock(soonestExpiryOf(signedIn.credits.expiry)) })
|
|
2814
|
+
})]
|
|
2815
|
+
}) : null,
|
|
2185
2816
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2186
2817
|
className: "dsm-trae-composer-panel-foot",
|
|
2187
2818
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
@@ -2276,10 +2907,10 @@ window.__ModuleLoader__.load({
|
|
|
2276
2907
|
"row.collapse": "Collapse",
|
|
2277
2908
|
"row.tabCn": "Domestic",
|
|
2278
2909
|
"row.tabAi": "Global",
|
|
2279
|
-
"row.tabHint": "
|
|
2280
|
-
"row.tabSwitchHint": "
|
|
2910
|
+
"row.tabHint": "Two independent providers, each with its own account, usage, and models. Both are live at once and never affect each other.",
|
|
2911
|
+
"row.tabSwitchHint": "Turns this provider off: its models leave the DSH picker. Account and settings are kept.",
|
|
2281
2912
|
"row.tabSwitchAria": "Offer the {region} provider to DSH",
|
|
2282
|
-
"row.tabOffNotice": "This provider is
|
|
2913
|
+
"row.tabOffNotice": "This provider is off: its models are not offered to DSH. Account and settings are kept.",
|
|
2283
2914
|
"row.settingsWriteFailed": "The setting was not saved: {message}",
|
|
2284
2915
|
"row.signedOut": "Not signed in",
|
|
2285
2916
|
"row.signedOutHint": "Sign in once in the Trae desktop app; this plugin follows that sign-in automatically.",
|
|
@@ -2304,52 +2935,68 @@ window.__ModuleLoader__.load({
|
|
|
2304
2935
|
"row.creditsConsumedLabel": "Consumed",
|
|
2305
2936
|
"row.creditsTotalLabel": "Total",
|
|
2306
2937
|
"row.creditsError": "Usage unavailable: {message}",
|
|
2307
|
-
"row.checkinLabel": "Daily check-in",
|
|
2308
2938
|
"row.checkinReward": "{reward} credits per day",
|
|
2309
|
-
"row.checkinClaim": "
|
|
2939
|
+
"row.checkinClaim": "Check in",
|
|
2940
|
+
"row.checkinClaimWith": "Check in +{reward}",
|
|
2310
2941
|
"row.checkinClaiming": "Claiming…",
|
|
2311
|
-
"row.checkinClaimed": "Claimed
|
|
2312
|
-
"row.checkinDoneHint": "come back tomorrow",
|
|
2942
|
+
"row.checkinClaimed": "Claimed",
|
|
2313
2943
|
"row.checkinRefused": "Trae refused the check-in (code {code})",
|
|
2314
2944
|
"row.checkinError": "Check-in failed: {message}",
|
|
2315
2945
|
"row.checkinDeviceSpent": "This machine already used today's check-in — Trae allows one per device per day, so this account cannot claim here today. Claim it on another device or from the account that checked in, or come back tomorrow.",
|
|
2316
2946
|
"row.checkinDisabled": "This account has no check-in activity available.",
|
|
2317
|
-
"row.
|
|
2318
|
-
"row.regionAi": "International",
|
|
2947
|
+
"row.checkinDeviceSpentShort": "Device used",
|
|
2319
2948
|
"row.subscriptionLabel": "Subscription",
|
|
2320
2949
|
"row.subscribed": "Active package",
|
|
2321
2950
|
"row.noPackage": "No active package",
|
|
2951
|
+
"row.planTier": "{tier} plan",
|
|
2322
2952
|
"row.trialUntil": "Trial until {date}",
|
|
2323
2953
|
"row.payStatusError": "Subscription status unavailable: {message}",
|
|
2954
|
+
"row.payStatusUnknown": "Plan not loaded yet",
|
|
2324
2955
|
"row.refresh": "Refresh",
|
|
2325
2956
|
"row.refreshTokens": "Refresh accounts & tokens",
|
|
2326
2957
|
"row.refreshCredits": "Refresh usage",
|
|
2327
2958
|
"row.refreshing": "Refreshing…",
|
|
2328
2959
|
"row.accountsTitle": "Trae account",
|
|
2329
|
-
"row.accountsHint": "
|
|
2960
|
+
"row.accountsHint": "Sign-ins detected on this machine. Tokens are never shown or saved.",
|
|
2330
2961
|
"row.accountsRescan": "Detect accounts again",
|
|
2331
2962
|
"row.accountsScanning": "Detecting…",
|
|
2963
|
+
"row.accountsNone": "This region reports no usable account.",
|
|
2964
|
+
"row.accountCurrentBadge": "in use",
|
|
2965
|
+
"row.accountSwitch": "Switch",
|
|
2966
|
+
"row.accountSwitching": "Switching…",
|
|
2967
|
+
"row.accountColumnAccount": "Account",
|
|
2968
|
+
"row.accountColumnValue": "Balance",
|
|
2969
|
+
"row.accountColumnCheckin": "Check-in",
|
|
2970
|
+
"row.accountBalance": "{credits} credits",
|
|
2971
|
+
"row.sourceDsh": "plugin copy",
|
|
2332
2972
|
"row.modelsTitle": "Models",
|
|
2333
2973
|
"row.modelsSummary": "{count} enabled",
|
|
2334
2974
|
"row.modelsRefresh": "Refresh from Trae",
|
|
2335
2975
|
"row.modelsRefreshing": "Refreshing models…",
|
|
2336
2976
|
"row.modelsTestEnabled": "Test enabled models",
|
|
2977
|
+
"row.modelsTestAll": "Test all models",
|
|
2337
2978
|
"row.modelsTesting": "Testing {done}/{total}…",
|
|
2338
2979
|
"row.modelsTestHint": "Sends one minimal message per enabled model to see which ones actually answer. Costs a small amount of quota.",
|
|
2980
|
+
"row.modelsTestAllHint": "The「test all」button probes every model in the catalog, enabled or not, so it spends noticeably more quota than the enabled-only run.",
|
|
2339
2981
|
"row.showPointsInMainUi": "Show credits in the composer row",
|
|
2340
|
-
"row.showPointsInMainUiHint": "
|
|
2982
|
+
"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.",
|
|
2983
|
+
"row.showPointsInMainUiHintAi": "A clickable readout in the composer tool row while a model from this plugin is selected: Trae Global · <plan>.",
|
|
2341
2984
|
"composer.switchTo": "Switch to {account}",
|
|
2342
2985
|
"composer.noAccounts": "No account in this region",
|
|
2343
2986
|
"composer.panelTitle": "Trae credits",
|
|
2987
|
+
"composer.panelTitleGlobal": "Trae Global plan",
|
|
2344
2988
|
"composer.account": "Account",
|
|
2345
2989
|
"composer.generalCredits": "General credits",
|
|
2990
|
+
"composer.plan": "Plan",
|
|
2346
2991
|
"composer.lastRefresh": "Last refreshed",
|
|
2347
2992
|
"composer.signedOut": "Not signed in",
|
|
2348
2993
|
"composer.points": "Trae CN",
|
|
2994
|
+
"composer.pointsGlobal": "Trae Global",
|
|
2349
2995
|
"composer.refresh": "Refresh credits",
|
|
2350
2996
|
"composer.refreshing": "Refreshing credits…",
|
|
2351
2997
|
"composer.refreshFailed": "Could not refresh credits",
|
|
2352
2998
|
"row.accountsRefreshed": "Accounts refreshed: {count} found{selected}",
|
|
2999
|
+
"row.accountsSaved": "bound to",
|
|
2353
3000
|
"row.accountsKept": "still bound to",
|
|
2354
3001
|
"row.accountsRefreshedNone": "Accounts refreshed: no usable Trae account found",
|
|
2355
3002
|
"row.accountsRescanFailed": "Could not refresh accounts: {message}",
|
|
@@ -2360,10 +3007,15 @@ window.__ModuleLoader__.load({
|
|
|
2360
3007
|
"row.creditsExhausted": "The bound account ({account}) has run out of credit",
|
|
2361
3008
|
"row.creditsAlternative": "Another account on this machine still has credit: {accounts}",
|
|
2362
3009
|
"row.creditsAlternativeSwitch": "Switch to it",
|
|
2363
|
-
"row.
|
|
2364
|
-
"row.
|
|
3010
|
+
"row.expiryWarningSoon": "{total} credit lapses within 24 hours ({packs} packs)",
|
|
3011
|
+
"row.expiryWarningWeek": "{total} credit lapses within 7 days ({packs} packs)",
|
|
3012
|
+
"row.expiryWarningLapsed": "{total} credit has already lapsed but is still counted in the balance ({packs} packs)",
|
|
3013
|
+
"row.expiryEarliest": "Earliest: {time}",
|
|
3014
|
+
"row.appLoginMismatch": "Trae is signed in to {account}, but this plugin is bound to {bound}.",
|
|
3015
|
+
"row.appLoginFollow": "Use {account}",
|
|
2365
3016
|
"row.followAppLogin": "Follow the account Trae is signed in to",
|
|
2366
|
-
"row.followAppLoginHint": "
|
|
3017
|
+
"row.followAppLoginHint": "Rebind automatically when you switch accounts in the Trae desktop app. Off by default, so the current binding is kept.",
|
|
3018
|
+
"row.settingsDirty": "Unsaved changes",
|
|
2367
3019
|
"row.modelsTestEmpty": "Enable at least one model first.",
|
|
2368
3020
|
"row.modelsTestError": "Test failed: {message}",
|
|
2369
3021
|
"row.modelsTestDone": "{available} of {total} answered.",
|
|
@@ -2396,10 +3048,10 @@ window.__ModuleLoader__.load({
|
|
|
2396
3048
|
"row.collapse": "收起",
|
|
2397
3049
|
"row.tabCn": "国内版",
|
|
2398
3050
|
"row.tabAi": "国际版",
|
|
2399
|
-
"row.tabHint": "
|
|
2400
|
-
"row.tabSwitchHint": "
|
|
3051
|
+
"row.tabHint": "国内版与国际版是两个独立供应商,各有账号、用量与模型;两边同时生效,互不影响。",
|
|
3052
|
+
"row.tabSwitchHint": "关闭该供应商:其模型不再提供给 DSH;账号与设置保留,可随时恢复。",
|
|
2401
3053
|
"row.tabSwitchAria": "是否向 DSH 提供{region}供应商",
|
|
2402
|
-
"row.tabOffNotice": "
|
|
3054
|
+
"row.tabOffNotice": "该供应商已关闭,模型不会提供给 DSH。账号与设置均已保留。",
|
|
2403
3055
|
"row.settingsWriteFailed": "设置未能保存:{message}",
|
|
2404
3056
|
"row.signedOut": "未登录",
|
|
2405
3057
|
"row.signedOutHint": "在 Trae 桌面 App 里登录一次即可,插件会自动跟随当前登录的账号。",
|
|
@@ -2424,52 +3076,68 @@ window.__ModuleLoader__.load({
|
|
|
2424
3076
|
"row.creditsConsumedLabel": "消耗",
|
|
2425
3077
|
"row.creditsTotalLabel": "合计",
|
|
2426
3078
|
"row.creditsError": "用量查询失败:{message}",
|
|
2427
|
-
"row.checkinLabel": "每日签到",
|
|
2428
3079
|
"row.checkinReward": "每日 {reward} 积分",
|
|
2429
|
-
"row.checkinClaim": "
|
|
3080
|
+
"row.checkinClaim": "签到",
|
|
3081
|
+
"row.checkinClaimWith": "签到 +{reward}",
|
|
2430
3082
|
"row.checkinClaiming": "领取中…",
|
|
2431
|
-
"row.checkinClaimed": "
|
|
2432
|
-
"row.checkinDoneHint": "明天再来",
|
|
3083
|
+
"row.checkinClaimed": "已领取",
|
|
2433
3084
|
"row.checkinRefused": "Trae 拒绝了本次签到(业务码 {code})",
|
|
2434
3085
|
"row.checkinError": "签到失败:{message}",
|
|
2435
3086
|
"row.checkinDeviceSpent": "本机今日的签到已用掉——Trae 按「每台设备每天一次」限制,所以当前账号在本机今天无法再签。可换一台设备领取,或用今天已签到的那台设备/账号,或明天再来。",
|
|
2436
3087
|
"row.checkinDisabled": "该账号当前没有可用的签到活动。",
|
|
2437
|
-
"row.
|
|
2438
|
-
"row.regionAi": "国际版",
|
|
3088
|
+
"row.checkinDeviceSpentShort": "本机已签",
|
|
2439
3089
|
"row.subscriptionLabel": "订阅状态",
|
|
2440
|
-
"row.subscribed": "
|
|
2441
|
-
"row.noPackage": "
|
|
3090
|
+
"row.subscribed": "已有套餐",
|
|
3091
|
+
"row.noPackage": "无套餐",
|
|
3092
|
+
"row.planTier": "{tier} 套餐",
|
|
2442
3093
|
"row.trialUntil": "试用至 {date}",
|
|
2443
3094
|
"row.payStatusError": "订阅状态查询失败:{message}",
|
|
3095
|
+
"row.payStatusUnknown": "套餐尚未读到",
|
|
2444
3096
|
"row.refresh": "刷新",
|
|
2445
3097
|
"row.refreshTokens": "刷新账号与 Token",
|
|
2446
3098
|
"row.refreshCredits": "刷新用量",
|
|
2447
3099
|
"row.refreshing": "正在刷新…",
|
|
2448
3100
|
"row.accountsTitle": "Trae 账号",
|
|
2449
|
-
"row.accountsHint": "
|
|
3101
|
+
"row.accountsHint": "本机检测到的登录账号;Token 不会显示或保存。",
|
|
2450
3102
|
"row.accountsRescan": "重新检测账号",
|
|
2451
3103
|
"row.accountsScanning": "正在检测…",
|
|
3104
|
+
"row.accountsNone": "该区域没有可列出的账号。",
|
|
3105
|
+
"row.accountCurrentBadge": "当前使用",
|
|
3106
|
+
"row.accountSwitch": "切换",
|
|
3107
|
+
"row.accountSwitching": "切换中…",
|
|
3108
|
+
"row.accountColumnAccount": "账号",
|
|
3109
|
+
"row.accountColumnValue": "额度",
|
|
3110
|
+
"row.accountColumnCheckin": "签到",
|
|
3111
|
+
"row.accountBalance": "积分 {credits}",
|
|
3112
|
+
"row.sourceDsh": "插件副本",
|
|
2452
3113
|
"row.modelsTitle": "模型",
|
|
2453
3114
|
"row.modelsSummary": "已启用 {count} 个",
|
|
2454
3115
|
"row.modelsRefresh": "从 Trae 刷新",
|
|
2455
3116
|
"row.modelsRefreshing": "正在刷新模型…",
|
|
2456
3117
|
"row.modelsTestEnabled": "一键测试启用模型",
|
|
3118
|
+
"row.modelsTestAll": "一键测试所有模型",
|
|
2457
3119
|
"row.modelsTesting": "正在测试 {done}/{total}…",
|
|
2458
3120
|
"row.modelsTestHint": "给每个已启用模型发一条最小消息,看哪些真的能应答。会消耗少量额度。",
|
|
3121
|
+
"row.modelsTestAllHint": "「测试所有模型」会遍历目录里的每一个模型(含未启用的),消耗的额度明显多于「仅测试启用」。",
|
|
2459
3122
|
"row.showPointsInMainUi": "在输入框工具栏显示积分读数",
|
|
2460
|
-
"row.showPointsInMainUiHint": "
|
|
3123
|
+
"row.showPointsInMainUiHint": "在输入框工具栏显示可点击读数(选中本插件模型时出现):国内版为「Trae CN · 余额」,国际版为「Trae 国际版 · 套餐」。",
|
|
3124
|
+
"row.showPointsInMainUiHintAi": "在输入框工具栏显示可点击读数(选中本插件模型时出现):国际版为「Trae 国际版 · 套餐」。",
|
|
2461
3125
|
"composer.switchTo": "切换到 {account}",
|
|
2462
3126
|
"composer.noAccounts": "该区域暂无账号",
|
|
2463
3127
|
"composer.panelTitle": "Trae 积分",
|
|
3128
|
+
"composer.panelTitleGlobal": "Trae 国际版套餐",
|
|
2464
3129
|
"composer.account": "账号",
|
|
2465
3130
|
"composer.generalCredits": "通用积分",
|
|
3131
|
+
"composer.plan": "套餐",
|
|
2466
3132
|
"composer.lastRefresh": "最近刷新",
|
|
2467
3133
|
"composer.signedOut": "未登录",
|
|
2468
3134
|
"composer.points": "Trae CN",
|
|
3135
|
+
"composer.pointsGlobal": "Trae 国际版",
|
|
2469
3136
|
"composer.refresh": "刷新积分",
|
|
2470
3137
|
"composer.refreshing": "正在刷新积分…",
|
|
2471
3138
|
"composer.refreshFailed": "积分刷新失败",
|
|
2472
3139
|
"row.accountsRefreshed": "已刷新账号:找到 {count} 个账号{selected}",
|
|
3140
|
+
"row.accountsSaved": "已绑定",
|
|
2473
3141
|
"row.accountsKept": "仍绑定",
|
|
2474
3142
|
"row.accountsRefreshedNone": "已刷新账号:未找到可用的 Trae 账号",
|
|
2475
3143
|
"row.accountsRescanFailed": "刷新账号失败:{message}",
|
|
@@ -2480,10 +3148,15 @@ window.__ModuleLoader__.load({
|
|
|
2480
3148
|
"row.creditsExhausted": "当前绑定账号({account})额度已用尽",
|
|
2481
3149
|
"row.creditsAlternative": "本机还有可用账号:{accounts}",
|
|
2482
3150
|
"row.creditsAlternativeSwitch": "切换到它",
|
|
2483
|
-
"row.
|
|
2484
|
-
"row.
|
|
3151
|
+
"row.expiryWarningSoon": "有 {total} 积分将在 24 小时内过期({packs} 个套餐)",
|
|
3152
|
+
"row.expiryWarningWeek": "有 {total} 积分将在 7 天内过期({packs} 个套餐)",
|
|
3153
|
+
"row.expiryWarningLapsed": "有 {total} 积分已过期但仍计入余额({packs} 个套餐)",
|
|
3154
|
+
"row.expiryEarliest": "最早到期:{time}",
|
|
3155
|
+
"row.appLoginMismatch": "Trae 已登录 {account},但本插件绑定的是 {bound}。",
|
|
3156
|
+
"row.appLoginFollow": "改用 {account}",
|
|
2485
3157
|
"row.followAppLogin": "跟随 Trae 当前登录的账号",
|
|
2486
|
-
"row.followAppLoginHint": "在 Trae
|
|
3158
|
+
"row.followAppLoginHint": "在 Trae 桌面端切换登录后自动换绑到新账号。默认关闭,已有绑定保持不变。",
|
|
3159
|
+
"row.settingsDirty": "有未保存的修改",
|
|
2487
3160
|
"row.modelsTestEmpty": "请先启用至少一个模型。",
|
|
2488
3161
|
"row.modelsTestError": "测试失败:{message}",
|
|
2489
3162
|
"row.modelsTestDone": "{total} 个里有 {available} 个正常应答。",
|