dsh-connect-trae 4.4.3 → 4.4.5
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 +141 -4
- package/lib/client.js +576 -78
- package/lib/index.d.ts +341 -2
- package/lib/index.js +500 -65
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -411,6 +411,182 @@ window.__ModuleLoader__.load({
|
|
|
411
411
|
return tier !== void 0 && tier !== "" && !/^free$/i.test(tier);
|
|
412
412
|
}
|
|
413
413
|
//#endregion
|
|
414
|
+
//#region src/model-order.ts
|
|
415
|
+
/**
|
|
416
|
+
* Wire name of the default SOLO chat function.
|
|
417
|
+
*
|
|
418
|
+
* Lives here rather than in `solo.ts` so this module stays importable from the
|
|
419
|
+
* client; `solo.ts` re-exports it, so there is still exactly one definition.
|
|
420
|
+
*/
|
|
421
|
+
const TRAE_SOLO_FUNCTION = "solo_work_lite";
|
|
422
|
+
/**
|
|
423
|
+
* The chat functions of one region, in PRIORITY order.
|
|
424
|
+
*
|
|
425
|
+
* Two consumers read it, and both mean "priority", which is why they can share
|
|
426
|
+
* one array: the wire asks these functions in order and the first to list a
|
|
427
|
+
* config owns it (`solo.ts`), and the card draws the groups in the same order so
|
|
428
|
+
* the row a user sees first is the one the resolver prefers.
|
|
429
|
+
*/
|
|
430
|
+
const TRAE_CHANNEL_PRIORITY = {
|
|
431
|
+
cn: [
|
|
432
|
+
"solo_work_remote",
|
|
433
|
+
TRAE_SOLO_FUNCTION,
|
|
434
|
+
"solo_agent_remote",
|
|
435
|
+
"chat_v3",
|
|
436
|
+
"solo_coder"
|
|
437
|
+
],
|
|
438
|
+
ai: [
|
|
439
|
+
"solo_work_remote",
|
|
440
|
+
TRAE_SOLO_FUNCTION,
|
|
441
|
+
"solo_agent_remote",
|
|
442
|
+
"chat_v3"
|
|
443
|
+
]
|
|
444
|
+
};
|
|
445
|
+
/**
|
|
446
|
+
* Rank of one channel inside the region's priority list.
|
|
447
|
+
*
|
|
448
|
+
* Three cases, and the order between them is deliberate:
|
|
449
|
+
*
|
|
450
|
+
* - a known chat channel: its index in the priority list;
|
|
451
|
+
* - an UNKNOWN channel name (a function the region does not call through —
|
|
452
|
+
* `withChatCallableFunctions` clears those, so this is a defensive branch):
|
|
453
|
+
* after every known channel, then alphabetically among themselves;
|
|
454
|
+
* - NO channel at all (a row demoted to the default chat function): last. Such
|
|
455
|
+
* a row is the one the resolver knows least about, so it must not push a
|
|
456
|
+
* properly attributed row down the list.
|
|
457
|
+
*/
|
|
458
|
+
function channelRank(channel, priority) {
|
|
459
|
+
if (channel === void 0 || channel === "") return priority.length + 1;
|
|
460
|
+
const known = priority.indexOf(channel);
|
|
461
|
+
return known === -1 ? priority.length : known;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Compare two display names the way a person reading a model list expects:
|
|
465
|
+
* case-insensitively, with embedded numbers compared as NUMBERS, so `GLM-5.10`
|
|
466
|
+
* sorts after `GLM-5.9`. Plain text comparison puts `5.10` first, which reads as
|
|
467
|
+
* a bug to anyone who sees the version numbers.
|
|
468
|
+
*/
|
|
469
|
+
function compareNames(left, right) {
|
|
470
|
+
const byName = left.trim().localeCompare(right.trim(), void 0, {
|
|
471
|
+
numeric: true,
|
|
472
|
+
sensitivity: "base"
|
|
473
|
+
});
|
|
474
|
+
return byName !== 0 ? byName : left.localeCompare(right);
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* The catalog in display order: channel priority first, then name.
|
|
478
|
+
*
|
|
479
|
+
* Total and stable — ties fall through to `id`, so two rows with the same name
|
|
480
|
+
* in the same channel still get one fixed order rather than the order they
|
|
481
|
+
* arrived in.
|
|
482
|
+
*/
|
|
483
|
+
function orderTraeModels(rows, region = "cn") {
|
|
484
|
+
const priority = TRAE_CHANNEL_PRIORITY[region] ?? TRAE_CHANNEL_PRIORITY.cn;
|
|
485
|
+
return [...rows].sort((left, right) => {
|
|
486
|
+
const byRank = channelRank(left.wireFunction, priority) - channelRank(right.wireFunction, priority);
|
|
487
|
+
if (byRank !== 0) return byRank;
|
|
488
|
+
if (left.wireFunction !== right.wireFunction) return (left.wireFunction ?? "").localeCompare(right.wireFunction ?? "");
|
|
489
|
+
const byName = compareNames(left.name, right.name);
|
|
490
|
+
return byName !== 0 ? byName : left.id.localeCompare(right.id);
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Split rows into channel groups, in display order.
|
|
495
|
+
*
|
|
496
|
+
* Ordering is re-applied here rather than trusted from the caller: a group list
|
|
497
|
+
* built from an unsorted input would put the groups in arrival order, which is
|
|
498
|
+
* the exact instability this module exists to remove.
|
|
499
|
+
*/
|
|
500
|
+
function groupTraeModels(rows, region = "cn") {
|
|
501
|
+
const groups = [];
|
|
502
|
+
const byChannel = /* @__PURE__ */ new Map();
|
|
503
|
+
for (const row of orderTraeModels(rows, region)) {
|
|
504
|
+
const channel = row.wireFunction === "" ? void 0 : row.wireFunction;
|
|
505
|
+
const key = channel ?? "\0default";
|
|
506
|
+
const existing = byChannel.get(key);
|
|
507
|
+
if (existing !== void 0) {
|
|
508
|
+
existing.rows.push(row);
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
const group = {
|
|
512
|
+
channel,
|
|
513
|
+
rows: [row]
|
|
514
|
+
};
|
|
515
|
+
byChannel.set(key, group);
|
|
516
|
+
groups.push(group);
|
|
517
|
+
}
|
|
518
|
+
return groups;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Does one row match a search query?
|
|
522
|
+
*
|
|
523
|
+
* Matches the display name, the display id and the WIRE config name — the last
|
|
524
|
+
* one because a user debugging a `4001 config_name rejected` arrives with the
|
|
525
|
+
* upstream name in hand, and the two differ exactly on the rows that carry a
|
|
526
|
+
* suffix (`DeepSeek-V4-Flash` vs `DeepSeek-V4-Flash-Official`).
|
|
527
|
+
*/
|
|
528
|
+
function traeModelMatchesQuery(model, query) {
|
|
529
|
+
const needle = query.trim().toLowerCase();
|
|
530
|
+
if (needle === "") return true;
|
|
531
|
+
if (model.name.toLowerCase().includes(needle)) return true;
|
|
532
|
+
if (model.id.toLowerCase().includes(needle)) return true;
|
|
533
|
+
return (model.wireConfigName ?? "").toLowerCase().includes(needle);
|
|
534
|
+
}
|
|
535
|
+
/** Filter by {@link traeModelMatchesQuery}, preserving order. */
|
|
536
|
+
function filterTraeModels(rows, query) {
|
|
537
|
+
if (query.trim() === "") return [...rows];
|
|
538
|
+
return rows.filter((row) => traeModelMatchesQuery(row, query));
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* The channels a NORMAL Trae chat session actually runs on: the `solo_work_*`
|
|
542
|
+
* family, i.e. the panel's own chat modes.
|
|
543
|
+
*
|
|
544
|
+
* Deliberately not "every channel we can call". After issue #52 P1 the catalog
|
|
545
|
+
* is 34 rows and 10 of them are reachable only through a SPECIALIST channel:
|
|
546
|
+
* `solo_coder` (7: `Doubao-Seed-2.0-Code`, `glm-5.1`, `glm-5v-turbo`,
|
|
547
|
+
* `kimi-k2.5`, `minimax-m2.7`, `qwen-3.5`, `qwen-3.6-plus`), `chat_v3` (2:
|
|
548
|
+
* `kimi-k2.8-preview`, `qwen3.8-flash`) and `solo_agent_remote` (1:
|
|
549
|
+
* `Doubao-Seed-Code`). Measured 2026-10-23 on a live CN account. Those rows work
|
|
550
|
+
* — that is why P1 kept them — but they are not what Trae's main chat offers,
|
|
551
|
+
* and mixing them into the default view is exactly the "a code model answering a
|
|
552
|
+
* solo request" shape issue #52 asked about.
|
|
553
|
+
*/
|
|
554
|
+
const TRAE_COMMON_FUNCTIONS = {
|
|
555
|
+
cn: ["solo_work_remote", TRAE_SOLO_FUNCTION],
|
|
556
|
+
ai: ["solo_work_remote", TRAE_SOLO_FUNCTION]
|
|
557
|
+
};
|
|
558
|
+
/**
|
|
559
|
+
* The function a request for this row will NAME.
|
|
560
|
+
*
|
|
561
|
+
* A row with no `wireFunction` is not "unknown to the wire": the bridge sends
|
|
562
|
+
* the region default (`TRAE_SOLO_FUNCTION`), so for every question about where
|
|
563
|
+
* the turn goes, the default is the answer. The distinction still matters for
|
|
564
|
+
* DISPLAY (`groupTraeModels` keeps these in their own group, because their
|
|
565
|
+
* provenance — which directory group advertised them — really is unknown), but
|
|
566
|
+
* not for scope: a `solo_work_lite` turn is a normal chat turn either way.
|
|
567
|
+
*/
|
|
568
|
+
function traeModelCallChannel(model) {
|
|
569
|
+
const channel = model.wireFunction;
|
|
570
|
+
return channel === void 0 || channel === "" ? TRAE_SOLO_FUNCTION : channel;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Does this row belong to the view?
|
|
574
|
+
*
|
|
575
|
+
* Judged on {@link traeModelCallChannel}, never on `wireFunction` directly:
|
|
576
|
+
* otherwise the one row without a function (`doubao_1_8`, measured 2026-10-23)
|
|
577
|
+
* would fall OUT of the common view while being called through exactly the
|
|
578
|
+
* common channel it belongs to.
|
|
579
|
+
*/
|
|
580
|
+
function traeModelInView(model, view, region = "cn") {
|
|
581
|
+
if (view === "all") return true;
|
|
582
|
+
return (TRAE_COMMON_FUNCTIONS[region] ?? TRAE_COMMON_FUNCTIONS.cn).includes(traeModelCallChannel(model));
|
|
583
|
+
}
|
|
584
|
+
/** Keep the rows a view asks for, preserving order. */
|
|
585
|
+
function scopeTraeModels(rows, view, region = "cn") {
|
|
586
|
+
if (view === "all") return [...rows];
|
|
587
|
+
return rows.filter((row) => traeModelInView(row, view, region));
|
|
588
|
+
}
|
|
589
|
+
//#endregion
|
|
414
590
|
//#region src/promo.ts
|
|
415
591
|
/** Zone used when upstream omits `off_peak.time_zone` (CN accounts). */
|
|
416
592
|
const TRAE_PROMO_DEFAULT_TIME_ZONE = "Asia/Shanghai";
|
|
@@ -490,17 +666,39 @@ window.__ModuleLoader__.load({
|
|
|
490
666
|
if (price.beforeRate <= 0 || price.rate >= price.beforeRate) return void 0;
|
|
491
667
|
return Math.round(price.rate / price.beforeRate * 100);
|
|
492
668
|
}
|
|
669
|
+
/**
|
|
670
|
+
* The member price, but only when it beats the rate the badge is already showing.
|
|
671
|
+
*
|
|
672
|
+
* Two rates on the same base are two ways to reach one price: `member` quoting
|
|
673
|
+
* 0.06 beside a live 0.06 is not "another 5 折". Comparing the post-discount
|
|
674
|
+
* numbers is the only honest test — a branch with no rate cannot be shown to be
|
|
675
|
+
* cheaper, so it is not quoted at all.
|
|
676
|
+
*/
|
|
677
|
+
function cheaperMemberOf(price, discount) {
|
|
678
|
+
const member = discount.member;
|
|
679
|
+
if (member === void 0 || member.rate === void 0) return void 0;
|
|
680
|
+
if (price.rate === void 0 || member.rate >= price.rate) return void 0;
|
|
681
|
+
const discountPercent = discountOf(member);
|
|
682
|
+
if (discountPercent === void 0) return void 0;
|
|
683
|
+
return {
|
|
684
|
+
rate: member.rate,
|
|
685
|
+
discountPercent
|
|
686
|
+
};
|
|
687
|
+
}
|
|
493
688
|
function badgeOf(kind, price, discount, window) {
|
|
494
689
|
const discountPercent = discountOf(price);
|
|
495
690
|
if (discountPercent === void 0) return void 0;
|
|
496
|
-
const
|
|
691
|
+
const member = cheaperMemberOf(price, discount);
|
|
497
692
|
return {
|
|
498
693
|
kind,
|
|
499
694
|
discountPercent,
|
|
500
695
|
percentOff: 100 - discountPercent,
|
|
501
696
|
...price.beforeRate === void 0 ? {} : { beforeRate: price.beforeRate },
|
|
502
697
|
...price.rate === void 0 ? {} : { rate: price.rate },
|
|
503
|
-
...
|
|
698
|
+
...member === void 0 ? {} : {
|
|
699
|
+
memberDiscountPercent: member.discountPercent,
|
|
700
|
+
memberRate: member.rate
|
|
701
|
+
},
|
|
504
702
|
...window === void 0 ? {} : {
|
|
505
703
|
fromClock: clockOf(window.startMinute),
|
|
506
704
|
untilClock: clockOf(window.endMinute),
|
|
@@ -520,7 +718,8 @@ window.__ModuleLoader__.load({
|
|
|
520
718
|
const current = discount.current;
|
|
521
719
|
if (current !== void 0) {
|
|
522
720
|
if ((current.discountType ?? "none") !== "none" && (current.beforeRate === void 0 || current.rate === void 0 || current.rate < current.beforeRate)) {
|
|
523
|
-
const
|
|
721
|
+
const kind = kindOf(discount);
|
|
722
|
+
const badge = badgeOf(kind, current, discount, kind === "offPeak" ? runningWindow(discount, now, defaultTimeZone) : void 0);
|
|
524
723
|
if (badge !== void 0) return badge;
|
|
525
724
|
}
|
|
526
725
|
}
|
|
@@ -814,6 +1013,64 @@ window.__ModuleLoader__.load({
|
|
|
814
1013
|
.dsm-trae-models-head{display:flex;align-items:center;justify-content:space-between;gap:12px}
|
|
815
1014
|
.dsm-trae-models-title{margin:0;color:var(--dsw-alias-label-primary,#e6e6e6);font-size:14px;font-weight:600;line-height:20px}
|
|
816
1015
|
.dsm-trae-models-summary{margin:2px 0 0;color:var(--dsw-alias-label-tertiary,#999);font-size:12px;line-height:18px}
|
|
1016
|
+
/* CATALOG SHAPE (issue #52): search + channel groups.
|
|
1017
|
+
*
|
|
1018
|
+
* The catalog is the union of every Trae mode, so a flat list reads as "a mess
|
|
1019
|
+
* of models" no matter how many rows it holds. The search box answers 「where is
|
|
1020
|
+
* X」 and the group headers answer 「why are there so many」 — the header names the
|
|
1021
|
+
* mode each row is actually called through, so the union is legible instead of
|
|
1022
|
+
* merely long. Both are presentation only: no rule here changes what is saved. */
|
|
1023
|
+
.dsm-trae-models-search{display:flex;align-items:center;gap:10px;min-width:0}
|
|
1024
|
+
/* SCOPE CHIPS (issue #52, P2).
|
|
1025
|
+
*
|
|
1026
|
+
* A segmented pair, not two more dsm-btn-outline buttons: those are 30px tall
|
|
1027
|
+
* and would compete with the section's own buttons, while this control is a
|
|
1028
|
+
* filter on the list directly below it. aria-pressed carries the state, so the
|
|
1029
|
+
* visual and the announced state cannot disagree. */
|
|
1030
|
+
.dsm-trae-models-view{display:flex;align-items:center;gap:2px;padding:2px;border-radius:8px;background:var(--dsw-alias-bg-layer-2,#232529);align-self:flex-start}
|
|
1031
|
+
.dsm-trae-models-view-opt{padding:4px 10px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:12px;line-height:17px;cursor:pointer;font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
1032
|
+
.dsm-trae-models-view-opt:hover{color:var(--dsw-alias-label-secondary,#c6c9d0)}
|
|
1033
|
+
/* The pressed chip is bg-layer-3 on a bg-layer-2 track: the verified pair that
|
|
1034
|
+
* reads as "this segment sits ON the control" in both themes. (bg-layer-1 was
|
|
1035
|
+
* the first choice and is NOT a token DSH defines — the fallback would then win
|
|
1036
|
+
* in both themes, the exact defect card-styles.spec.tsx exists to catch.) */
|
|
1037
|
+
.dsm-trae-models-view-opt[aria-pressed="true"]{background:var(--dsw-alias-bg-layer-3,#2e3136);color:var(--dsw-alias-label-primary,#e6e6e6);font-weight:600}
|
|
1038
|
+
.dsm-trae-models-view-opt:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:-2px}
|
|
1039
|
+
/* The follow-up line under a scoped list ("N enabled are outside this view") and
|
|
1040
|
+
* the "show all" affordance inside it. Same 12px tertiary treatment as the hint
|
|
1041
|
+
* line above the list, because it is the same kind of statement: a note about
|
|
1042
|
+
* the list, not part of it. */
|
|
1043
|
+
.dsm-trae-models-note{margin:0;display:flex;align-items:center;gap:8px;flex-wrap:wrap;color:var(--dsw-alias-label-tertiary,#999);font-size:12px;line-height:18px}
|
|
1044
|
+
.dsm-trae-models-widen{margin-left:2px;padding:0;border:0;background:transparent;color:var(--dsw-alias-brand-primary,#5686fe);font-size:12px;line-height:18px;cursor:pointer;text-decoration:underline;text-underline-offset:2px}
|
|
1045
|
+
.dsm-trae-models-widen:hover{color:var(--dsw-alias-brand-primary,#5686fe);opacity:.8}
|
|
1046
|
+
.dsm-trae-models-widen:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px;border-radius:3px}
|
|
1047
|
+
.dsm-trae-models-search-input{flex:1 1 auto;min-width:0;padding:6px 10px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2,#36373b);background:var(--dsw-alias-bg-layer-2,#232529);color:var(--dsw-alias-label-primary,#e6e6e6);font-size:13px;line-height:18px}
|
|
1048
|
+
.dsm-trae-models-search-input::placeholder{color:var(--dsw-alias-label-tertiary,#999)}
|
|
1049
|
+
.dsm-trae-models-search-input:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:-1px}
|
|
1050
|
+
.dsm-trae-models-search-count{flex:none;color:var(--dsw-alias-label-tertiary,#999);font-size:12px;line-height:18px;font-variant-numeric:tabular-nums}
|
|
1051
|
+
.dsm-trae-models-empty{margin:0;padding:14px 12px;color:var(--dsw-alias-label-tertiary,#999);font-size:12px;line-height:18px}
|
|
1052
|
+
.dsm-trae-model-group{display:block}
|
|
1053
|
+
.dsm-trae-model-group+.dsm-trae-model-group{border-top:1px solid var(--dsw-alias-border-l2,#36373b)}
|
|
1054
|
+
/* list-style:none + a drawn marker: the native triangle cannot be styled to
|
|
1055
|
+
sit on the header's baseline, and Safari needs the -webkit- pseudo removed
|
|
1056
|
+
separately. The marker is TEXT on purpose — an icon import would tie the card
|
|
1057
|
+
to a host package for one glyph. */
|
|
1058
|
+
/* The header tint is a NEUTRAL overlay, not a DSH token: it has to sit one step
|
|
1059
|
+
away from the row background in BOTH themes, and the only token the card may
|
|
1060
|
+
use there (bg-layer-2) is what the rows already paint. A 6% grey wash is the
|
|
1061
|
+
same "slightly recessed" relationship for a light and a dark surface. */
|
|
1062
|
+
.dsm-trae-model-group-head{display:flex;align-items:center;gap:8px;padding:7px 12px;cursor:pointer;background:rgba(127,127,127,.12);color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px;line-height:17px;font-weight:600;list-style:none}
|
|
1063
|
+
.dsm-trae-model-group-head::-webkit-details-marker{display:none}
|
|
1064
|
+
.dsm-trae-model-group-head::before{content:'▸';flex:none;width:9px;color:var(--dsw-alias-label-tertiary,#999);font-size:10px}
|
|
1065
|
+
.dsm-trae-model-group[open]>.dsm-trae-model-group-head::before{content:'▾'}
|
|
1066
|
+
.dsm-trae-model-group-head:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:-2px}
|
|
1067
|
+
.dsm-trae-model-group-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
1068
|
+
.dsm-trae-model-group-count{flex:none;color:var(--dsw-alias-label-tertiary,#999);font-weight:400;font-variant-numeric:tabular-nums}
|
|
1069
|
+
/* The header separates itself from its FIRST row; between rows the generic
|
|
1070
|
+
.dsm-trae-model+.dsm-trae-model hairline still applies, and between groups
|
|
1071
|
+
the rule above does. Without this the first row of every group looked pasted
|
|
1072
|
+
onto its header. */
|
|
1073
|
+
.dsm-trae-model-group-head+.dsm-trae-model{border-top:1px solid var(--dsw-alias-border-l2,#36373b)}
|
|
817
1074
|
/* EVERY model is on the page — no inner scroller, no max-height.
|
|
818
1075
|
*
|
|
819
1076
|
* A capped list with its own scrollbar was tried and rejected by the user: it
|
|
@@ -3588,6 +3845,26 @@ window.__ModuleLoader__.load({
|
|
|
3588
3845
|
return formatNumber(value);
|
|
3589
3846
|
}
|
|
3590
3847
|
/**
|
|
3848
|
+
* Human name for one wire channel (issue #52, P0).
|
|
3849
|
+
*
|
|
3850
|
+
* The group header answers 「why is this list so long」 — because it is the union
|
|
3851
|
+
* of several Trae modes — so it must name the mode, not print `solo_work_remote`.
|
|
3852
|
+
* An unknown function falls through to its raw name on purpose: a channel this
|
|
3853
|
+
* build does not know about is a fact worth showing rather than hiding behind
|
|
3854
|
+
* 「其他」, and it is exactly what a bug report should quote.
|
|
3855
|
+
*/
|
|
3856
|
+
function modelChannelLabel(channel, t) {
|
|
3857
|
+
if (channel === void 0) return t("row.modelsChannelDefault");
|
|
3858
|
+
const key = {
|
|
3859
|
+
solo_work_remote: "row.modelsChannelWorkRemote",
|
|
3860
|
+
solo_work_lite: "row.modelsChannelWorkLite",
|
|
3861
|
+
solo_agent_remote: "row.modelsChannelAgentRemote",
|
|
3862
|
+
chat_v3: "row.modelsChannelChatV3",
|
|
3863
|
+
solo_coder: "row.modelsChannelCoder"
|
|
3864
|
+
}[channel];
|
|
3865
|
+
return key === void 0 ? channel : t(key);
|
|
3866
|
+
}
|
|
3867
|
+
/**
|
|
3591
3868
|
* The verdict badge for one model, or null when it has not been tested.
|
|
3592
3869
|
*
|
|
3593
3870
|
* `unknown` renders as inconclusive, never as a failure: a throttle or a dropped
|
|
@@ -3660,7 +3937,8 @@ window.__ModuleLoader__.load({
|
|
|
3660
3937
|
until: badge.untilClock,
|
|
3661
3938
|
zone: badge.timeZone
|
|
3662
3939
|
}));
|
|
3663
|
-
if (badge.memberDiscountPercent !== void 0) hints.push(t("row.promoHintMember", {
|
|
3940
|
+
if (badge.memberDiscountPercent !== void 0 && badge.memberRate !== void 0) hints.push(t("row.promoHintMember", {
|
|
3941
|
+
rate: String(badge.memberRate),
|
|
3664
3942
|
zhe: promoZhe$1(badge.memberDiscountPercent),
|
|
3665
3943
|
percent: String(100 - badge.memberDiscountPercent)
|
|
3666
3944
|
}));
|
|
@@ -3734,6 +4012,37 @@ window.__ModuleLoader__.load({
|
|
|
3734
4012
|
/** Per-region unsaved model edits; a draft on one tab is never dropped by
|
|
3735
4013
|
* switching to the other tab, only by that tab's discard/save. */
|
|
3736
4014
|
const [drafts, setDrafts] = (0, react.useState)({});
|
|
4015
|
+
/**
|
|
4016
|
+
* Search text over the model catalog (issue #52, P0).
|
|
4017
|
+
*
|
|
4018
|
+
* Kept in COMPONENT state, not in the draft: filtering what you look at is not
|
|
4019
|
+
* an edit, so it must survive a save and must never make the card look dirty.
|
|
4020
|
+
*/
|
|
4021
|
+
const [modelQuery, setModelQuery] = (0, react.useState)("");
|
|
4022
|
+
/**
|
|
4023
|
+
* How much of the catalog the list shows (issue #52, P2).
|
|
4024
|
+
*
|
|
4025
|
+
* `common` is the DEFAULT because it answers the question the issue was filed
|
|
4026
|
+
* with: 24 of the 34 rows are reachable from a normal `solo_work_*` chat turn,
|
|
4027
|
+
* and the other 10 exist only on a specialist channel (`solo_coder` 7,
|
|
4028
|
+
* `chat_v3` 2, `solo_agent_remote` 1 — measured 2026-10-23). The vendor's own
|
|
4029
|
+
* picker shows 14–24 per mode, so 24 is the first list since P0 that looks
|
|
4030
|
+
* like the client the reporter compared against.
|
|
4031
|
+
*
|
|
4032
|
+
* Nothing is REMOVED by this: the view is presentation only. Every action
|
|
4033
|
+
* (「test all」, bulk context, the save) still ranges over the full catalog, and
|
|
4034
|
+
* an enabled model that the view hides is called out below the list rather
|
|
4035
|
+
* than silently dropped from the screen.
|
|
4036
|
+
*/
|
|
4037
|
+
const [modelView, setModelView] = (0, react.useState)("common");
|
|
4038
|
+
/**
|
|
4039
|
+
* Channel groups the user folded shut, keyed `panel|channel`.
|
|
4040
|
+
*
|
|
4041
|
+
* Stored as the EXCEPTION rather than the state of every group so the default
|
|
4042
|
+
* stays 「open」: the catalog is short enough to read once grouped, and a
|
|
4043
|
+
* default of "collapsed" would hide a model the user came to enable.
|
|
4044
|
+
*/
|
|
4045
|
+
const [closedModelGroups, setClosedModelGroups] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
3737
4046
|
const [saving, setSaving] = (0, react.useState)(false);
|
|
3738
4047
|
const [switchingAccount, setSwitchingAccount] = (0, react.useState)(false);
|
|
3739
4048
|
/** Region whose on/off checkbox write is in flight, so its box can't race. */
|
|
@@ -4489,6 +4798,47 @@ window.__ModuleLoader__.load({
|
|
|
4489
4798
|
const draft = drafts[activeRegion];
|
|
4490
4799
|
const visibleModels = draft?.models ?? (status.status === "signed-in" ? status.models : []);
|
|
4491
4800
|
/**
|
|
4801
|
+
* The catalog as the card draws it: one fixed order, then the search filter,
|
|
4802
|
+
* then split into the channels the rows will actually be called through
|
|
4803
|
+
* (issue #52, P0).
|
|
4804
|
+
*
|
|
4805
|
+
* The ROLL is `orderTraeModels` and it is applied here as well as at
|
|
4806
|
+
* discovery, because a catalog saved by an older build carries the arrival
|
|
4807
|
+
* order — the very thing the reporter photographed. Re-sorting costs one
|
|
4808
|
+
* comparison pass over ≤40 rows and makes the screen independent of which
|
|
4809
|
+
* version wrote `lastCatalog`.
|
|
4810
|
+
*
|
|
4811
|
+
* Every action below still ranges over `visibleModels` (the whole catalog),
|
|
4812
|
+
* never `shownModels`: 「test all」 and the bulk context buttons must act on all
|
|
4813
|
+
* rows, not on whatever the search box happens to show.
|
|
4814
|
+
*/
|
|
4815
|
+
const modelRegion = regionOfPanel(activeRegion);
|
|
4816
|
+
const orderedModels = orderTraeModels(visibleModels, modelRegion);
|
|
4817
|
+
/**
|
|
4818
|
+
* Scope, then search, then group — in that order, and each step answers a
|
|
4819
|
+
* different question (P2): the view is 「how much of the catalog」, the query is
|
|
4820
|
+
* 「which row」, the groups are 「where will the turn go」.
|
|
4821
|
+
*
|
|
4822
|
+
* SEARCH IS SCOPED TO THE VIEW. A global search would contradict the group
|
|
4823
|
+
* headers the user is looking at (results from a channel that is not on
|
|
4824
|
+
* screen), and scoping it means the empty state has to be honest about what it
|
|
4825
|
+
* did not look at — see `matchesOutsideView`.
|
|
4826
|
+
*/
|
|
4827
|
+
const commonModels = scopeTraeModels(orderedModels, "common", modelRegion);
|
|
4828
|
+
const scopedModels = modelView === "all" ? orderedModels : commonModels;
|
|
4829
|
+
const commonModelCount = commonModels.length;
|
|
4830
|
+
const shownModels = filterTraeModels(scopedModels, modelQuery);
|
|
4831
|
+
const modelGroups = groupTraeModels(shownModels, modelRegion);
|
|
4832
|
+
const modelQueryActive = modelQuery.trim() !== "";
|
|
4833
|
+
/**
|
|
4834
|
+
* Matches the query has in the FULL catalog while the view has none.
|
|
4835
|
+
*
|
|
4836
|
+
* Without this, searching `kimi` with the default view up would answer
|
|
4837
|
+
* 「没有匹配」 for `kimi-k2.5`, which is on screen in 全部 — the one wrong
|
|
4838
|
+
* answer a search box must not give. Zero means "genuinely absent".
|
|
4839
|
+
*/
|
|
4840
|
+
const matchesOutsideView = modelQueryActive && shownModels.length === 0 ? filterTraeModels(orderedModels, modelQuery).length : 0;
|
|
4841
|
+
/**
|
|
4492
4842
|
* One clock reading for the whole model list.
|
|
4493
4843
|
*
|
|
4494
4844
|
* Promotion badges are judged against it (see `promoBadge`): reading the clock
|
|
@@ -4501,6 +4851,17 @@ window.__ModuleLoader__.load({
|
|
|
4501
4851
|
const activeEnabledIds = draft?.enabledIds ?? savedEnabledIds;
|
|
4502
4852
|
const activeImageIds = draft?.imageIds ?? savedImageIds;
|
|
4503
4853
|
/**
|
|
4854
|
+
* Enabled rows this view does not draw.
|
|
4855
|
+
*
|
|
4856
|
+
* The view hides 10 rows by default; if the user had enabled some of them
|
|
4857
|
+
* before this existed, their checkbox is not on the screen, so the ONLY honest
|
|
4858
|
+
* options are "say they exist" or "let the screen imply they are off". The save
|
|
4859
|
+
* path is already safe (it writes `activeEnabledIds` from state, never from the
|
|
4860
|
+
* rendered checkboxes), so this is about not lying to the reader.
|
|
4861
|
+
*/
|
|
4862
|
+
const scopedIds = new Set(scopedModels.map((model) => model.id));
|
|
4863
|
+
const enabledOutsideView = modelView === "all" ? [] : orderedModels.filter((model) => activeEnabledIds.has(model.id) && !scopedIds.has(model.id));
|
|
4864
|
+
/**
|
|
4504
4865
|
* Effective image capability of one row, mirroring what the Host reports to
|
|
4505
4866
|
* DSH: the user's opt-in unioned with Trae's own `multimodal` flag (issue
|
|
4506
4867
|
* #16). Rendering the union is what keeps this checkbox truthful — showing the
|
|
@@ -4624,7 +4985,7 @@ window.__ModuleLoader__.load({
|
|
|
4624
4985
|
if (status.status !== "signed-in") return;
|
|
4625
4986
|
const nextSlot = {
|
|
4626
4987
|
enabled: regionOn(activeRegion),
|
|
4627
|
-
lastCatalog:
|
|
4988
|
+
lastCatalog: orderedModels.map((model) => ({
|
|
4628
4989
|
...model,
|
|
4629
4990
|
input: ["text"],
|
|
4630
4991
|
...model.multimodal === void 0 ? {} : { multimodal: model.multimodal }
|
|
@@ -5088,79 +5449,177 @@ window.__ModuleLoader__.load({
|
|
|
5088
5449
|
role: "alert",
|
|
5089
5450
|
children: testError
|
|
5090
5451
|
}),
|
|
5091
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
5452
|
+
orderedModels.length > commonModelCount ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5453
|
+
className: "dsm-trae-models-view",
|
|
5454
|
+
role: "group",
|
|
5455
|
+
"aria-label": t("row.modelsViewLabel"),
|
|
5456
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5457
|
+
type: "button",
|
|
5458
|
+
className: "dsm-trae-models-view-opt",
|
|
5459
|
+
"aria-pressed": modelView === "common",
|
|
5460
|
+
title: t("row.modelsViewCommonHint"),
|
|
5461
|
+
onClick: () => {
|
|
5462
|
+
setModelView("common");
|
|
5463
|
+
},
|
|
5464
|
+
children: t("row.modelsViewCommon", { count: commonModelCount })
|
|
5465
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5466
|
+
type: "button",
|
|
5467
|
+
className: "dsm-trae-models-view-opt",
|
|
5468
|
+
"aria-pressed": modelView === "all",
|
|
5469
|
+
title: t("row.modelsViewAllHint"),
|
|
5470
|
+
onClick: () => {
|
|
5471
|
+
setModelView("all");
|
|
5472
|
+
},
|
|
5473
|
+
children: t("row.modelsViewAll", { count: orderedModels.length })
|
|
5474
|
+
})]
|
|
5475
|
+
}) : null,
|
|
5476
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5477
|
+
className: "dsm-trae-models-search",
|
|
5478
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5479
|
+
type: "search",
|
|
5480
|
+
className: "dsm-trae-models-search-input",
|
|
5481
|
+
value: modelQuery,
|
|
5482
|
+
placeholder: t("row.modelsSearchPlaceholder"),
|
|
5483
|
+
"aria-label": t("row.modelsSearch"),
|
|
5484
|
+
onChange: (event) => {
|
|
5485
|
+
setModelQuery(event.target.value);
|
|
5486
|
+
setClosedModelGroups(/* @__PURE__ */ new Set());
|
|
5487
|
+
}
|
|
5488
|
+
}), modelQueryActive ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5489
|
+
className: "dsm-trae-models-search-count",
|
|
5490
|
+
children: t("row.modelsSearchCount", {
|
|
5491
|
+
shown: shownModels.length,
|
|
5492
|
+
total: scopedModels.length
|
|
5493
|
+
})
|
|
5494
|
+
}) : null]
|
|
5495
|
+
}),
|
|
5496
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5092
5497
|
className: "dsm-trae-model-list",
|
|
5093
|
-
children:
|
|
5094
|
-
className:
|
|
5095
|
-
children: [
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5498
|
+
children: [modelGroups.length === 0 ? matchesOutsideView > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
5499
|
+
className: "dsm-trae-models-empty",
|
|
5500
|
+
children: [t("row.modelsSearchOutsideView", {
|
|
5501
|
+
query: modelQuery.trim(),
|
|
5502
|
+
count: matchesOutsideView
|
|
5503
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5504
|
+
type: "button",
|
|
5505
|
+
className: "dsm-trae-models-widen",
|
|
5506
|
+
onClick: () => {
|
|
5507
|
+
setModelView("all");
|
|
5508
|
+
},
|
|
5509
|
+
children: t("row.modelsViewShowAll")
|
|
5510
|
+
})]
|
|
5511
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5512
|
+
className: "dsm-trae-models-empty",
|
|
5513
|
+
children: t("row.modelsSearchEmpty", { query: modelQuery.trim() })
|
|
5514
|
+
}) : null, modelGroups.map((group) => {
|
|
5515
|
+
const groupKey = `${activeRegion}|${group.channel ?? ""}`;
|
|
5516
|
+
const groupOpen = modelQueryActive || !closedModelGroups.has(groupKey);
|
|
5517
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
5518
|
+
className: "dsm-trae-model-group",
|
|
5519
|
+
open: groupOpen,
|
|
5520
|
+
onToggle: (event) => {
|
|
5521
|
+
const open = event.currentTarget.open;
|
|
5522
|
+
setClosedModelGroups((current) => {
|
|
5523
|
+
if (open === !current.has(groupKey)) return current;
|
|
5524
|
+
const next = new Set(current);
|
|
5525
|
+
if (open) next.delete(groupKey);
|
|
5526
|
+
else next.add(groupKey);
|
|
5527
|
+
return next;
|
|
5528
|
+
});
|
|
5529
|
+
},
|
|
5530
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
|
|
5531
|
+
className: "dsm-trae-model-group-head",
|
|
5532
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5533
|
+
className: "dsm-trae-model-group-name",
|
|
5534
|
+
children: modelChannelLabel(group.channel, t)
|
|
5535
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5536
|
+
className: "dsm-trae-model-group-count",
|
|
5537
|
+
children: t("row.modelsGroupCount", { count: group.rows.length })
|
|
5119
5538
|
})]
|
|
5120
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5121
|
-
className: "dsm-trae-model-
|
|
5122
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
5123
|
-
className: "dsm-trae-model-
|
|
5124
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5539
|
+
}), group.rows.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5540
|
+
className: `dsm-trae-model${activeEnabledIds.has(model.id) ? "" : " dsm-trae-model-disabled"}`,
|
|
5541
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5542
|
+
className: "dsm-trae-model-head",
|
|
5543
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5544
|
+
className: "dsm-trae-model-enabled",
|
|
5545
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5546
|
+
type: "checkbox",
|
|
5547
|
+
checked: activeEnabledIds.has(model.id),
|
|
5548
|
+
disabled: settingsScope?.getSnapshot().writable !== true || saving,
|
|
5549
|
+
onChange: () => {
|
|
5550
|
+
toggleModel(model.id);
|
|
5551
|
+
}
|
|
5552
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5553
|
+
className: "dsm-trae-model-copy",
|
|
5554
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5555
|
+
className: "dsm-trae-model-name",
|
|
5556
|
+
children: [
|
|
5557
|
+
model.name,
|
|
5558
|
+
activeRegion === "enterprise" || model.creditMultiplier === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5559
|
+
className: "dsm-trae-model-name-rate",
|
|
5560
|
+
children: ["· x", model.creditMultiplier.toFixed(2)]
|
|
5561
|
+
}),
|
|
5562
|
+
probeBadge(probeByRegion[activeRegion][model.id], t)
|
|
5563
|
+
]
|
|
5564
|
+
}), promoBadge(model, t, nowForPromo)]
|
|
5565
|
+
})]
|
|
5566
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5567
|
+
className: "dsm-trae-model-options",
|
|
5568
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5569
|
+
className: "dsm-trae-model-image",
|
|
5570
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5571
|
+
type: "checkbox",
|
|
5572
|
+
checked: rowAcceptsImage(model),
|
|
5573
|
+
disabled: rowImageIsAutomatic(model) || settingsScope?.getSnapshot().writable !== true || saving,
|
|
5574
|
+
title: rowImageIsAutomatic(model) ? t("row.modelImageAuto") : void 0,
|
|
5575
|
+
onChange: () => {
|
|
5576
|
+
toggleImage(model.id);
|
|
5577
|
+
}
|
|
5578
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelImage") })]
|
|
5579
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
|
|
5580
|
+
className: "dsm-trae-context-budget",
|
|
5581
|
+
"aria-label": t("row.contextBudget"),
|
|
5582
|
+
children: [model.maxContextWindow !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5583
|
+
type: "radio",
|
|
5584
|
+
name: `context-${model.id}`,
|
|
5585
|
+
checked: activeContextBudgets[model.id] !== model.maxContextWindow,
|
|
5586
|
+
disabled: settingsScope?.getSnapshot().writable !== true || saving,
|
|
5587
|
+
onChange: () => {
|
|
5588
|
+
setContextBudget(model.id, model.contextWindow);
|
|
5589
|
+
}
|
|
5590
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatCapacity(model.contextWindow, t("row.modelUnknown")) })] }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5591
|
+
type: "radio",
|
|
5592
|
+
name: `context-${model.id}`,
|
|
5593
|
+
checked: model.maxContextWindow === void 0 || activeContextBudgets[model.id] === model.maxContextWindow,
|
|
5594
|
+
disabled: model.maxContextWindow === void 0 || settingsScope?.getSnapshot().writable !== true || saving,
|
|
5595
|
+
onChange: () => {
|
|
5596
|
+
setContextBudget(model.id, model.maxContextWindow);
|
|
5597
|
+
}
|
|
5598
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatCapacity(model.maxContextWindow ?? model.contextWindow, t("row.modelUnknown")) })] })]
|
|
5599
|
+
})]
|
|
5600
|
+
})]
|
|
5601
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5602
|
+
className: "dsm-trae-model-meta",
|
|
5603
|
+
children: [
|
|
5604
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelContext", { context: formatCapacity(model.maxContextWindow ?? model.contextWindow, t("row.modelUnknown")) }) }),
|
|
5605
|
+
model.maxTokens === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelOutput", { output: formatCapacity(model.maxTokens, t("row.modelUnknown")) }) }),
|
|
5606
|
+
model.reasoning === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelReasoning", { efforts: model.reasoning.supported.join(" / ") }) })
|
|
5607
|
+
]
|
|
5153
5608
|
})]
|
|
5154
|
-
})]
|
|
5155
|
-
}
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5609
|
+
}, model.id))]
|
|
5610
|
+
}, groupKey);
|
|
5611
|
+
})]
|
|
5612
|
+
}),
|
|
5613
|
+
enabledOutsideView.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
5614
|
+
className: "dsm-trae-models-note",
|
|
5615
|
+
children: [t("row.modelsEnabledOutsideView", { count: enabledOutsideView.length }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5616
|
+
type: "button",
|
|
5617
|
+
className: "dsm-trae-models-widen",
|
|
5618
|
+
onClick: () => {
|
|
5619
|
+
setModelView("all");
|
|
5620
|
+
},
|
|
5621
|
+
children: t("row.modelsViewShowAll")
|
|
5622
|
+
})]
|
|
5164
5623
|
}),
|
|
5165
5624
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5166
5625
|
className: "dsm-trae-context-bulk",
|
|
@@ -6211,6 +6670,25 @@ window.__ModuleLoader__.load({
|
|
|
6211
6670
|
"row.modelsSummary": "{count} enabled",
|
|
6212
6671
|
"row.modelsRefresh": "Refresh from Trae",
|
|
6213
6672
|
"row.modelsRefreshing": "Refreshing models…",
|
|
6673
|
+
"row.modelsSearch": "Search models",
|
|
6674
|
+
"row.modelsSearchPlaceholder": "Search by name or config name",
|
|
6675
|
+
"row.modelsSearchCount": "{shown} of {total}",
|
|
6676
|
+
"row.modelsSearchEmpty": "No model matches 「{query}」.",
|
|
6677
|
+
"row.modelsGroupCount": "{count}",
|
|
6678
|
+
"row.modelsViewLabel": "Model scope",
|
|
6679
|
+
"row.modelsViewCommon": "Common channels · {count}",
|
|
6680
|
+
"row.modelsViewAll": "All · {count}",
|
|
6681
|
+
"row.modelsViewCommonHint": "Models a normal SOLO workspace chat can call (solo_work_remote / solo_work_lite).",
|
|
6682
|
+
"row.modelsViewAllHint": "Also includes models only offered by a specialist channel: solo_coder, chat_v3, solo_agent_remote.",
|
|
6683
|
+
"row.modelsViewShowAll": "Show all",
|
|
6684
|
+
"row.modelsSearchOutsideView": "No match for 「{query}」 on the common channels; the full catalog has {count}.",
|
|
6685
|
+
"row.modelsEnabledOutsideView": "{count} enabled model(s) are not in this view.",
|
|
6686
|
+
"row.modelsChannelWorkRemote": "SOLO workspace · remote",
|
|
6687
|
+
"row.modelsChannelWorkLite": "SOLO workspace · default",
|
|
6688
|
+
"row.modelsChannelAgentRemote": "SOLO agent · remote",
|
|
6689
|
+
"row.modelsChannelChatV3": "Chat · v3",
|
|
6690
|
+
"row.modelsChannelCoder": "Code · solo_coder",
|
|
6691
|
+
"row.modelsChannelDefault": "No mode announced (calls solo_work_lite)",
|
|
6214
6692
|
"row.modelsTestEnabled": "Test enabled models",
|
|
6215
6693
|
"row.modelsTestAll": "Test all models",
|
|
6216
6694
|
"row.modelsTesting": "Testing {done}/{total}…",
|
|
@@ -6273,7 +6751,7 @@ window.__ModuleLoader__.load({
|
|
|
6273
6751
|
"row.promoOffPeak": "Off-peak {percent}% off",
|
|
6274
6752
|
"row.promoHintRate": "was {before} credits/Mtoken, now {rate}",
|
|
6275
6753
|
"row.promoHintWindow": "off-peak hours {from}–{until} ({zone})",
|
|
6276
|
-
"row.promoHintMember": "subscription price is
|
|
6754
|
+
"row.promoHintMember": "subscription price is {rate} credits/Mtoken ({percent}% off)",
|
|
6277
6755
|
"row.modelEnabled": "Enabled",
|
|
6278
6756
|
"row.discard": "Discard changes",
|
|
6279
6757
|
"row.save": "Save",
|
|
@@ -6562,6 +7040,25 @@ window.__ModuleLoader__.load({
|
|
|
6562
7040
|
"row.modelsSummary": "已启用 {count} 个",
|
|
6563
7041
|
"row.modelsRefresh": "从 Trae 刷新",
|
|
6564
7042
|
"row.modelsRefreshing": "正在刷新模型…",
|
|
7043
|
+
"row.modelsSearch": "搜索模型",
|
|
7044
|
+
"row.modelsSearchPlaceholder": "按模型名或 config_name 搜索",
|
|
7045
|
+
"row.modelsSearchCount": "{shown} / {total}",
|
|
7046
|
+
"row.modelsSearchEmpty": "没有匹配「{query}」的模型。",
|
|
7047
|
+
"row.modelsGroupCount": "{count} 个",
|
|
7048
|
+
"row.modelsViewLabel": "模型范围",
|
|
7049
|
+
"row.modelsViewCommon": "常用通道 · {count}",
|
|
7050
|
+
"row.modelsViewAll": "全部 · {count}",
|
|
7051
|
+
"row.modelsViewCommonHint": "普通 SOLO 工作区对话能调到的模型(solo_work_remote / solo_work_lite)。",
|
|
7052
|
+
"row.modelsViewAllHint": "还包含只在专用通道提供的模型:solo_coder、chat_v3、solo_agent_remote。",
|
|
7053
|
+
"row.modelsViewShowAll": "查看全部",
|
|
7054
|
+
"row.modelsSearchOutsideView": "常用通道内没有匹配「{query}」的模型,全部目录里有 {count} 个。",
|
|
7055
|
+
"row.modelsEnabledOutsideView": "另有 {count} 个已启用的模型不在此视图内。",
|
|
7056
|
+
"row.modelsChannelWorkRemote": "SOLO 工作区 · 远程",
|
|
7057
|
+
"row.modelsChannelWorkLite": "SOLO 工作区 · 默认",
|
|
7058
|
+
"row.modelsChannelAgentRemote": "SOLO 智能体 · 远程",
|
|
7059
|
+
"row.modelsChannelChatV3": "对话 · v3",
|
|
7060
|
+
"row.modelsChannelCoder": "代码 · solo_coder",
|
|
7061
|
+
"row.modelsChannelDefault": "未标注模式(走 solo_work_lite)",
|
|
6565
7062
|
"row.modelsTestEnabled": "一键测试启用模型",
|
|
6566
7063
|
"row.modelsTestAll": "一键测试所有模型",
|
|
6567
7064
|
"row.modelsTesting": "正在测试 {done}/{total}…",
|
|
@@ -6624,7 +7121,7 @@ window.__ModuleLoader__.load({
|
|
|
6624
7121
|
"row.promoOffPeak": "闲时 {zhe} 折",
|
|
6625
7122
|
"row.promoHintRate": "原价 {before} 积分/百万 token,现价 {rate}",
|
|
6626
7123
|
"row.promoHintWindow": "闲时时段 {from}–{until}({zone})",
|
|
6627
|
-
"row.promoHintMember": "
|
|
7124
|
+
"row.promoHintMember": "会员价 {rate} 积分/百万 token({zhe} 折)",
|
|
6628
7125
|
"row.modelEnabled": "启用模型",
|
|
6629
7126
|
"row.discard": "放弃修改",
|
|
6630
7127
|
"row.save": "保存",
|
|
@@ -7041,7 +7538,8 @@ window.__ModuleLoader__.load({
|
|
|
7041
7538
|
until: badge.untilClock,
|
|
7042
7539
|
zone: badge.timeZone
|
|
7043
7540
|
}));
|
|
7044
|
-
if (badge.memberDiscountPercent !== void 0) hints.push(fillPlaceholders(copy["row.promoHintMember"], {
|
|
7541
|
+
if (badge.memberDiscountPercent !== void 0 && badge.memberRate !== void 0) hints.push(fillPlaceholders(copy["row.promoHintMember"], {
|
|
7542
|
+
rate: String(badge.memberRate),
|
|
7045
7543
|
zhe: promoZhe(badge.memberDiscountPercent),
|
|
7046
7544
|
percent: String(100 - badge.memberDiscountPercent)
|
|
7047
7545
|
}));
|