dsh-connect-workbuddy 2.0.6 → 2.0.10
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 +140 -0
- package/README.en.md +6 -1
- package/README.md +6 -1
- package/lib/bin.js +15 -2
- package/lib/client.js +416 -66
- package/lib/{host-heartbeat--iKDuwr5.js → host-heartbeat-DPbL9oi2.js} +539 -31
- package/lib/index.d.ts +438 -10
- package/lib/index.js +262 -27
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -41,6 +41,69 @@ window.__ModuleLoader__.load({
|
|
|
41
41
|
return `${path}?${WORKBUDDY_REGION_PARAM}=${region}`;
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
|
+
* Narrow a settings value to the `regions` map. Accepts EITHER the whole
|
|
45
|
+
* settings section (the Host's resolved `Config`) OR the `regions` map itself,
|
|
46
|
+
* and unwraps the former. This tolerance is deliberate: passing the whole
|
|
47
|
+
* section where the map was expected was a real shipped bug in the sibling
|
|
48
|
+
* project — the lookup then read `section['cn']` (absent), so the card's
|
|
49
|
+
* checkbox reported `true` forever and clicking it appeared to do nothing even
|
|
50
|
+
* though the write succeeded.
|
|
51
|
+
*/
|
|
52
|
+
function regionsMapOf(value) {
|
|
53
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return {};
|
|
54
|
+
const record = value;
|
|
55
|
+
const nested = record["regions"];
|
|
56
|
+
if (typeof nested === "object" && nested !== null && !Array.isArray(nested)) return nested;
|
|
57
|
+
return record;
|
|
58
|
+
}
|
|
59
|
+
/** One region's stored slot as a plain object; any other shape reads as empty. */
|
|
60
|
+
function regionSlotOf(value, region) {
|
|
61
|
+
const slot = regionsMapOf(value)[region];
|
|
62
|
+
return typeof slot === "object" && slot !== null && !Array.isArray(slot) ? slot : {};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Whether one region's provider is switched on. Opt-out semantics: only an
|
|
66
|
+
* explicit `false` disables it, so a config written before this switch existed
|
|
67
|
+
* (and the pre-region-split flat fields, which never carry `enabled`) keep both
|
|
68
|
+
* providers running exactly as before. The Host reads the same rule through
|
|
69
|
+
* `regionStateOf`, so card and Host can never disagree about a region's state.
|
|
70
|
+
*
|
|
71
|
+
* `value` may be the whole settings section or the `regions` map (see
|
|
72
|
+
* {@link regionsMapOf}).
|
|
73
|
+
*/
|
|
74
|
+
function regionEnabledOf(value, region) {
|
|
75
|
+
return regionSlotOf(value, region)["enabled"] !== false;
|
|
76
|
+
}
|
|
77
|
+
/** Build the next `regions` settings value for a signed-in tab's save. */
|
|
78
|
+
function nextRegionSlots(regions, region, slot) {
|
|
79
|
+
return {
|
|
80
|
+
...typeof regions === "object" && regions !== null && !Array.isArray(regions) ? regions : {},
|
|
81
|
+
[region]: slot
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Build the next `regions` settings value for a provider on/off toggle. ONLY
|
|
86
|
+
* the target region's `enabled` flag changes: every other field of that slot
|
|
87
|
+
* (its directory, selection, image opt-ins, context budgets) and every other
|
|
88
|
+
* region's slot are carried over verbatim, so switching a provider off never
|
|
89
|
+
* discards the user's model picks and switching it back on restores them.
|
|
90
|
+
*
|
|
91
|
+
* This is deliberately separate from {@link nextRegionSlots}: that helper
|
|
92
|
+
* writes a whole slot from a signed-in tab's draft, while this one must work
|
|
93
|
+
* for a region that is signed OUT — which is precisely the region a user wants
|
|
94
|
+
* to switch off (no international install, no international account).
|
|
95
|
+
*
|
|
96
|
+
* `value` may be the whole settings section or the `regions` map; the RETURN
|
|
97
|
+
* value is always the `regions` map, i.e. exactly what `settingsScope.set(
|
|
98
|
+
* 'regions', ...)` needs.
|
|
99
|
+
*/
|
|
100
|
+
function nextRegionEnabled(value, region, enabled) {
|
|
101
|
+
return nextRegionSlots(regionsMapOf(value), region, {
|
|
102
|
+
...regionSlotOf(value, region),
|
|
103
|
+
enabled
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
44
107
|
* Project one card row into its persisted `lastCatalog` shape: the native
|
|
45
108
|
* context window becomes the stored `contextWindow`, and the card-only
|
|
46
109
|
* presentation fields (`nativeContextWindow`, `multimodal`) are removed BY
|
|
@@ -199,6 +262,12 @@ window.__ModuleLoader__.load({
|
|
|
199
262
|
.dsm-workbuddy-tab:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
200
263
|
.dsm-workbuddy-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)}
|
|
201
264
|
.dsm-workbuddy-tab-dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:6px;vertical-align:baseline}
|
|
265
|
+
.dsm-workbuddy-tab-cell{display:flex;align-items:center;gap:2px;flex:1;min-width:0}
|
|
266
|
+
.dsm-workbuddy-tab-switch{display:inline-flex;align-items:center;flex:none;padding:0 8px 0 2px;cursor:pointer}
|
|
267
|
+
.dsm-workbuddy-tab-switch input{margin:0;cursor:pointer;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
|
|
268
|
+
.dsm-workbuddy-tab-switch input:disabled{opacity:.4;cursor:default}
|
|
269
|
+
.dsm-workbuddy-tab-off{opacity:.55}
|
|
270
|
+
.dsm-workbuddy-tab-off-notice{color:var(--dsw-alias-label-tertiary,#999);font-size:12px;line-height:18px;margin:0 0 4px;padding:8px 10px;background:var(--dsw-alias-bg-layer-3,#2a2c33);border:1px solid var(--dsw-alias-border-l2,#36373b);border-radius:8px}
|
|
202
271
|
.dsm-workbuddy-usage-account{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:14px;background:var(--dsw-alias-bg-layer-2,#24262c)}
|
|
203
272
|
.dsm-workbuddy-usage-account-copy{display:flex;flex-direction:column;gap:3px;min-width:0}
|
|
204
273
|
.dsm-workbuddy-usage-expiry{padding-left:19px;color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:12px;line-height:18px}
|
|
@@ -213,6 +282,18 @@ window.__ModuleLoader__.load({
|
|
|
213
282
|
.dsm-workbuddy-usage-select:disabled{opacity:.6;cursor:default}
|
|
214
283
|
.dsm-workbuddy-usage-select-wrap::after{content:"";position:absolute;top:50%;right:12px;width:7px;height:7px;transform:translateY(-65%) rotate(45deg);border-right:1.6px solid var(--dsw-alias-label-secondary,#c6c9d0);border-bottom:1.6px solid var(--dsw-alias-label-secondary,#c6c9d0);pointer-events:none}
|
|
215
284
|
.dsm-workbuddy-usage-text{margin:0;font-size:14px;line-height:22px;color:var(--dsw-alias-label-secondary,#b8b8b8)}
|
|
285
|
+
.dsm-workbuddy-searched{margin-top:8px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,#999)}
|
|
286
|
+
.dsm-workbuddy-searched>summary{cursor:pointer;color:var(--dsw-alias-label-secondary,#b8b8b8);user-select:none}
|
|
287
|
+
.dsm-workbuddy-searched-hint{margin:6px 0 4px;color:var(--dsw-alias-label-tertiary,#999)}
|
|
288
|
+
.dsm-workbuddy-searched-list{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:4px}
|
|
289
|
+
.dsm-workbuddy-searched-list li{display:flex;flex-direction:column;gap:1px;min-width:0}
|
|
290
|
+
.dsm-workbuddy-searched-list code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary,#b8b8b8);word-break:break-all}
|
|
291
|
+
.dsm-workbuddy-searched-reason{color:var(--dsw-alias-label-tertiary,#999);font-size:11px}
|
|
292
|
+
.dsm-workbuddy-searched-reason-encrypted{color:var(--dsw-alias-state-warning-primary,#e0a13a)}
|
|
293
|
+
.dsm-workbuddy-searched-reason-wrong-region{color:var(--dsw-alias-brand-primary,#5686fe)}
|
|
294
|
+
.dsm-workbuddy-searched-notice{margin:6px 0 0;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#36373b);border-radius:8px;background:var(--dsw-alias-bg-layer-3,#2a2c33);color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px;line-height:18px}
|
|
295
|
+
.dsm-workbuddy-searched-more{margin-top:6px;padding:0;border:0;background:none;color:var(--dsw-alias-brand-primary,#5686fe);font:inherit;font-size:12px;line-height:18px;cursor:pointer;text-align:left}
|
|
296
|
+
.dsm-workbuddy-searched-more:hover{text-decoration:underline}
|
|
216
297
|
.dsm-workbuddy-usage-error{margin:0;font-size:14px;line-height:22px;color:var(--dsw-alias-state-error-primary,#ef4444)}
|
|
217
298
|
.dsm-workbuddy-usage-dot{width:9px;height:9px;border-radius:50%;flex:0 0 auto}
|
|
218
299
|
.dsm-workbuddy-usage-status{display:flex;align-items:center;gap:10px;font-size:15px;font-weight:500;color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
@@ -272,6 +353,114 @@ window.__ModuleLoader__.load({
|
|
|
272
353
|
.dsm-workbuddy-usage-cheer:hover{color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
273
354
|
.dsm-workbuddy-usage-cheer:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:2px}
|
|
274
355
|
`;
|
|
356
|
+
//#endregion
|
|
357
|
+
//#region src/client/searched-paths.ts
|
|
358
|
+
/**
|
|
359
|
+
* Reasons that explain a failure, as opposed to merely recording an absence.
|
|
360
|
+
*
|
|
361
|
+
* `missing` is the normal state of most candidates on any machine — the app
|
|
362
|
+
* writes one file while the platform offers several possible directories — so
|
|
363
|
+
* it is the one reason that gets hidden by default. Everything else is a real
|
|
364
|
+
* finding: the file is there and unreadable, present but not a credential, or
|
|
365
|
+
* present and encrypted without a key.
|
|
366
|
+
*
|
|
367
|
+
* `encrypted` is WorkBuddy-specific and the most important of the three: the
|
|
368
|
+
* user is very likely signed in already, and the plugin simply cannot open the
|
|
369
|
+
* token fields without the desktop app present. "Sign in again" is the one
|
|
370
|
+
* action that cannot help, so this reason must never be hidden behind a toggle.
|
|
371
|
+
*
|
|
372
|
+
* `wrong-region` is equally a finding and equally actionable: the sign-in is
|
|
373
|
+
* real and readable, it just belongs to the other tab. That is why it must be
|
|
374
|
+
* shown up front — hidden among the absences it would look like "nothing here",
|
|
375
|
+
* when it is in fact the whole explanation, and the cheapest possible fix.
|
|
376
|
+
*/
|
|
377
|
+
const INTERESTING_REASONS = [
|
|
378
|
+
"encrypted",
|
|
379
|
+
"invalid",
|
|
380
|
+
"unreadable",
|
|
381
|
+
"wrong-region"
|
|
382
|
+
];
|
|
383
|
+
/**
|
|
384
|
+
* Partition one region's probe failures for display.
|
|
385
|
+
*
|
|
386
|
+
* Order within each group is preserved from the Host: the store reports
|
|
387
|
+
* candidates in probe order, which is meaningful (the live file before its
|
|
388
|
+
* timestamped backups).
|
|
389
|
+
*/
|
|
390
|
+
function searchedView(items) {
|
|
391
|
+
const interesting = items.filter((item) => INTERESTING_REASONS.includes(item.reason));
|
|
392
|
+
return {
|
|
393
|
+
interesting,
|
|
394
|
+
missing: items.filter((item) => !INTERESTING_REASONS.includes(item.reason)),
|
|
395
|
+
missingOpen: interesting.length === 0,
|
|
396
|
+
encrypted: items.some((item) => item.reason === "encrypted"),
|
|
397
|
+
total: items.length
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
/** Every reason's locale key, so no reason can silently fall through to a wrong one. */
|
|
401
|
+
const REASON_KEYS = {
|
|
402
|
+
missing: "row.reasonMissing",
|
|
403
|
+
unreadable: "row.reasonUnreadable",
|
|
404
|
+
invalid: "row.reasonInvalid",
|
|
405
|
+
encrypted: "row.reasonEncrypted",
|
|
406
|
+
"wrong-region": "row.reasonWrongRegion"
|
|
407
|
+
};
|
|
408
|
+
/** The reason's short label; `missing`/`unreadable`/`invalid`/`encrypted`/`wrong-region`. */
|
|
409
|
+
function searchReasonKey(reason) {
|
|
410
|
+
return REASON_KEYS[reason] ?? "row.reasonInvalid";
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* One entry's cause line: which store the path belongs to, and why it failed.
|
|
414
|
+
*
|
|
415
|
+
* The source matters because the two are fixed differently — a desktop-app path
|
|
416
|
+
* is about the app's installation and sign-in, a plugin-owned copy is about the
|
|
417
|
+
* plugin's own storage.
|
|
418
|
+
*/
|
|
419
|
+
function searchReasonLabel(item, t) {
|
|
420
|
+
return `${t(item.source === "desktop" ? "row.sourceDesktop" : "row.sourceDsh")} · ${t(searchReasonKey(item.reason))}`;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Choose the signed-out paragraph's copy.
|
|
424
|
+
*
|
|
425
|
+
* This exists because the card was saying the same thing twice. `resolve()`
|
|
426
|
+
* refuses with a message that already ENUMERATES every path it tried
|
|
427
|
+
* (`expected <Local>\workbuddy-desktop.info or <Roaming>\... or
|
|
428
|
+
* WORKBUDDY_AUTH_FILE`), and the probed-path `<details>` right below it listed
|
|
429
|
+
* those same paths with a per-entry reason. Both hint keys also opened with a
|
|
430
|
+
* variant of "no sign-in was found", so one screen carried four statements of
|
|
431
|
+
* one fact and the user's eye had nowhere to land.
|
|
432
|
+
*
|
|
433
|
+
* The rule: the paragraph states the situation, the list supplies the detail.
|
|
434
|
+
* Whenever a list is rendered, the enumeration in `message` is redundant by
|
|
435
|
+
* construction — the list is the same paths, with strictly better reasons — so
|
|
436
|
+
* the paragraph falls back to the concise hint. Measured against the real
|
|
437
|
+
* `resolve()` branches, the only content this drops is "or refresh an existing
|
|
438
|
+
* session", which is vacuous exactly here: `searched` is attached only when
|
|
439
|
+
* there are zero accounts, so there is no session to refresh.
|
|
440
|
+
*
|
|
441
|
+
* `selectionLost` still wins outright: there the tokens are healthy and the
|
|
442
|
+
* advice is to re-pick an account, which no path list can replace.
|
|
443
|
+
*/
|
|
444
|
+
function signedOutNotice(input) {
|
|
445
|
+
if (input.selectionLost) return { key: "row.selectionLostMessage" };
|
|
446
|
+
if (input.searched.some((item) => item.reason === "wrong-region")) return { key: "row.signedOutWrongRegion" };
|
|
447
|
+
if (input.searched.length > 0) return { key: "row.signedOutHint" };
|
|
448
|
+
return input.message === void 0 ? { key: "row.signedOutHint" } : {
|
|
449
|
+
key: "row.signedOutHint",
|
|
450
|
+
fallback: input.message
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* The paragraph's final text: the localized copy, plus the Host message only
|
|
455
|
+
* when {@link signedOutNotice} decided nothing supersedes it.
|
|
456
|
+
*
|
|
457
|
+
* Kept here rather than inline in the JSX so the anti-duplication guarantee can
|
|
458
|
+
* be asserted on the string that is actually rendered, not on its inputs.
|
|
459
|
+
*/
|
|
460
|
+
function signedOutText(notice, t) {
|
|
461
|
+
const hint = t(notice.key);
|
|
462
|
+
return notice.fallback === void 0 ? hint : `${hint} (${notice.fallback})`;
|
|
463
|
+
}
|
|
275
464
|
//#endregion
|
|
276
465
|
//#region src/client/WorkBuddyCard.tsx
|
|
277
466
|
/**
|
|
@@ -311,6 +500,68 @@ window.__ModuleLoader__.load({
|
|
|
311
500
|
document.head.appendChild(styleTag);
|
|
312
501
|
}
|
|
313
502
|
}
|
|
503
|
+
/**
|
|
504
|
+
* One probed path, with its cause. The presentation rules — which entries are
|
|
505
|
+
* worth showing up front, and how a reason is labelled — live in
|
|
506
|
+
* `./searched-paths.ts` so they are unit-testable without a DOM.
|
|
507
|
+
*/
|
|
508
|
+
function renderSearchedItem(item, t) {
|
|
509
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: item.path }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
510
|
+
className: `dsm-workbuddy-searched-reason${item.reason === "encrypted" ? " dsm-workbuddy-searched-reason-encrypted" : item.reason === "wrong-region" ? " dsm-workbuddy-searched-reason-wrong-region" : ""}`,
|
|
511
|
+
children: [searchReasonLabel(item, t), item.message === void 0 ? null : ` · ${item.message}`]
|
|
512
|
+
})] }, `${item.source}:${item.path}`);
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* The probed-path list behind a signed-out card.
|
|
516
|
+
*
|
|
517
|
+
* Collapsed by default because it is a diagnostic, not a headline. The
|
|
518
|
+
* interesting failures (encrypted / invalid / unreadable) are listed up front;
|
|
519
|
+
* the merely-absent candidates — most of them, on any normal machine — sit
|
|
520
|
+
* behind a second toggle, so the one entry that explains the failure is not
|
|
521
|
+
* buried under a dozen "not found" lines. When nothing interesting was found
|
|
522
|
+
* the absent list IS the explanation, so it opens directly.
|
|
523
|
+
*
|
|
524
|
+
* An `encrypted` failure additionally raises a notice: it is the one cause
|
|
525
|
+
* where the user is very likely already signed in, and telling them to sign in
|
|
526
|
+
* again sends them to an action that cannot work.
|
|
527
|
+
*/
|
|
528
|
+
function SearchedPaths({ items, t }) {
|
|
529
|
+
const view = searchedView(items);
|
|
530
|
+
const [explicitOpen, setExplicitOpen] = (0, react.useState)(void 0);
|
|
531
|
+
const showMissing = explicitOpen ?? view.missingOpen;
|
|
532
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
533
|
+
className: "dsm-workbuddy-searched",
|
|
534
|
+
children: [
|
|
535
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", { children: [
|
|
536
|
+
t("row.searchedTitle"),
|
|
537
|
+
" (",
|
|
538
|
+
view.total,
|
|
539
|
+
")"
|
|
540
|
+
] }),
|
|
541
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
542
|
+
className: "dsm-workbuddy-searched-hint",
|
|
543
|
+
children: t("row.searchedHint")
|
|
544
|
+
}),
|
|
545
|
+
view.encrypted ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
546
|
+
className: "dsm-workbuddy-searched-notice",
|
|
547
|
+
role: "status",
|
|
548
|
+
children: t("row.searchedEncryptedNotice")
|
|
549
|
+
}) : null,
|
|
550
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
|
|
551
|
+
className: "dsm-workbuddy-searched-list",
|
|
552
|
+
children: [view.interesting.map((item) => renderSearchedItem(item, t)), showMissing ? view.missing.map((item) => renderSearchedItem(item, t)) : null]
|
|
553
|
+
}),
|
|
554
|
+
showMissing || view.missing.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
555
|
+
type: "button",
|
|
556
|
+
className: "dsm-workbuddy-searched-more",
|
|
557
|
+
onClick: () => {
|
|
558
|
+
setExplicitOpen(true);
|
|
559
|
+
},
|
|
560
|
+
children: t("row.searchedMore", { count: view.missing.length })
|
|
561
|
+
})
|
|
562
|
+
]
|
|
563
|
+
});
|
|
564
|
+
}
|
|
314
565
|
function formatNumber(value) {
|
|
315
566
|
return new Intl.NumberFormat(void 0, {
|
|
316
567
|
minimumFractionDigits: 0,
|
|
@@ -379,12 +630,12 @@ window.__ModuleLoader__.load({
|
|
|
379
630
|
/** Save failure surfaced next to the buttons; cleared by the next attempt. */
|
|
380
631
|
const [saveError, setSaveError] = (0, react.useState)(void 0);
|
|
381
632
|
const [switchingAccount, setSwitchingAccount] = (0, react.useState)(false);
|
|
382
|
-
/** Set right after a CONFIRMED Clear so the dropped choice is stated. */
|
|
383
|
-
const [accountNote, setAccountNote] = (0, react.useState)(void 0);
|
|
384
633
|
/** A refused account write (silently unpersisted settings on a locked file). */
|
|
385
634
|
const [accountError, setAccountError] = (0, react.useState)(void 0);
|
|
386
635
|
const [checkingIn, setCheckingIn] = (0, react.useState)(false);
|
|
387
636
|
const [checkinActionError, setCheckinActionError] = (0, react.useState)(void 0);
|
|
637
|
+
/** Region whose on/off checkbox write is in flight, so its box can't race. */
|
|
638
|
+
const [togglingRegion, setTogglingRegion] = (0, react.useState)(void 0);
|
|
388
639
|
const mounted = (0, react.useRef)(true);
|
|
389
640
|
(0, react.useEffect)(() => {
|
|
390
641
|
mounted.current = true;
|
|
@@ -485,7 +736,6 @@ window.__ModuleLoader__.load({
|
|
|
485
736
|
const switchAccount = async (accountId) => {
|
|
486
737
|
if (settingsScope === void 0) return;
|
|
487
738
|
setSwitchingAccount(true);
|
|
488
|
-
setAccountNote(void 0);
|
|
489
739
|
setAccountError(void 0);
|
|
490
740
|
try {
|
|
491
741
|
await writeAccountSlot(settingsScope, activeRegion, accountId);
|
|
@@ -497,36 +747,40 @@ window.__ModuleLoader__.load({
|
|
|
497
747
|
}
|
|
498
748
|
};
|
|
499
749
|
/**
|
|
500
|
-
*
|
|
501
|
-
*
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
*
|
|
505
|
-
* written here.
|
|
750
|
+
* Whether one region's provider is switched on, read off the SAME committed
|
|
751
|
+
* settings document the Host reads (`regionEnabledOf` mirrors the Host's
|
|
752
|
+
* `regionStateOf` opt-out rule: only an explicit `false` disables). Reading
|
|
753
|
+
* the stored value rather than echoing local state means a rejected write,
|
|
754
|
+
* another window's change, or a restart all converge on the truth.
|
|
506
755
|
*
|
|
507
|
-
* The
|
|
508
|
-
*
|
|
509
|
-
*
|
|
510
|
-
*
|
|
511
|
-
*
|
|
512
|
-
* would be a lie that the old selection silently contradicts.
|
|
756
|
+
* The whole settings section is passed deliberately: `regionEnabledOf`
|
|
757
|
+
* accepts either it or the bare `regions` map, because passing the section
|
|
758
|
+
* where the map was expected was a shipped bug (the lookup read
|
|
759
|
+
* `section['cn']`, found nothing, and reported `true` forever — the checkbox
|
|
760
|
+
* stayed checked and clicking it appeared dead while the write succeeded).
|
|
513
761
|
*/
|
|
514
|
-
const
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
762
|
+
const regionOn = (item) => regionEnabledOf(settingsScope?.getSnapshot().value, item);
|
|
763
|
+
const activeRegionOn = regionOn(activeRegion);
|
|
764
|
+
/**
|
|
765
|
+
* Switch one region's provider off or on. The write carries the region's
|
|
766
|
+
* whole slot through untouched — only `enabled` changes — so the user's
|
|
767
|
+
* directory, model picks, image opt-ins and budgets survive a round trip.
|
|
768
|
+
* The Host withdraws or restores the provider route on the next `onChange`,
|
|
769
|
+
* which is what actually removes it from DSH's model picker.
|
|
770
|
+
*/
|
|
771
|
+
const toggleRegion = async (item, enabled) => {
|
|
772
|
+
if (settingsScope === void 0 || settingsScope.getSnapshot().writable !== true) return;
|
|
773
|
+
setTogglingRegion(item);
|
|
518
774
|
try {
|
|
519
|
-
await
|
|
520
|
-
await refreshUsage(activeRegion);
|
|
521
|
-
if (mounted.current) setAccountNote("cleared");
|
|
522
|
-
} catch (error) {
|
|
523
|
-
if (!mounted.current) return;
|
|
524
|
-
setAccountNote(void 0);
|
|
525
|
-
setAccountError(error instanceof Error ? error.message : t("row.requestFailed"));
|
|
775
|
+
await settingsScope.set("regions", nextRegionEnabled(settingsScope.getSnapshot().value, item, enabled));
|
|
526
776
|
} finally {
|
|
527
|
-
if (mounted.current)
|
|
777
|
+
if (mounted.current) setTogglingRegion(void 0);
|
|
528
778
|
}
|
|
529
779
|
};
|
|
780
|
+
/**
|
|
781
|
+
* Claim the daily check-in reward for the active region. The action endpoint
|
|
782
|
+
* is region-scoped; the response only refreshes this tab's check-in state.
|
|
783
|
+
*/
|
|
530
784
|
const claimDailyCheckin = async () => {
|
|
531
785
|
setCheckingIn(true);
|
|
532
786
|
setCheckinActionError(void 0);
|
|
@@ -652,6 +906,7 @@ window.__ModuleLoader__.load({
|
|
|
652
906
|
setSaveError(void 0);
|
|
653
907
|
try {
|
|
654
908
|
await writeRegionModels(settingsScope, status.region, {
|
|
909
|
+
enabled: activeRegionOn,
|
|
655
910
|
lastCatalog: visibleModels.map(toPersistedWorkBuddyModel),
|
|
656
911
|
enabledModelIds: [...activeEnabledIds],
|
|
657
912
|
imageModelIds: [...activeImageIds],
|
|
@@ -666,10 +921,35 @@ window.__ModuleLoader__.load({
|
|
|
666
921
|
}
|
|
667
922
|
};
|
|
668
923
|
const title = t("row.title");
|
|
669
|
-
|
|
924
|
+
/**
|
|
925
|
+
* Show a name, or a placeholder when the desktop app recorded none.
|
|
926
|
+
*
|
|
927
|
+
* The Host sends `''` rather than an identifier, because a `uin`/`uid` shown
|
|
928
|
+
* where a name belongs reads as "the plugin does not know who this is" — the
|
|
929
|
+
* placeholder says the honest thing instead.
|
|
930
|
+
*/
|
|
931
|
+
const nameOf = (value) => value === "" ? t("row.accountUnnamed") : value;
|
|
932
|
+
const label = status.status === "signed-in" ? t("row.signedIn", { accountName: nameOf(status.accountName) }) : status.status === "error" ? t("row.requestFailed") : t("row.signedOut");
|
|
670
933
|
/** The saved choice no longer matches a local sign-in (tokens are fine). */
|
|
671
934
|
const selectionLost = status.status === "signed-out" && status.selectionLost === true;
|
|
672
935
|
/**
|
|
936
|
+
* The paths the Host probed, on the branch where nothing was found at all.
|
|
937
|
+
* Absent on the legacy card payload (an older Host), so it defaults empty
|
|
938
|
+
* rather than rendering an empty diagnostic.
|
|
939
|
+
*/
|
|
940
|
+
const searched = status.status === "signed-out" ? status.searched ?? [] : [];
|
|
941
|
+
/**
|
|
942
|
+
* What the signed-out paragraph says, and whether to append the Host's raw
|
|
943
|
+
* error. The rule lives in `./searched-paths.ts` because it is the fix for a
|
|
944
|
+
* real duplication: `resolve()`'s message already enumerated every path, and
|
|
945
|
+
* the list below enumerates them again with better reasons.
|
|
946
|
+
*/
|
|
947
|
+
const notice = signedOutNotice({
|
|
948
|
+
selectionLost,
|
|
949
|
+
message: status.status === "signed-out" ? status.message : void 0,
|
|
950
|
+
searched
|
|
951
|
+
});
|
|
952
|
+
/**
|
|
673
953
|
* Whether this region runs a saved choice, per the Host. `undefined` only on
|
|
674
954
|
* the error branch, which renders no picker and therefore no state line.
|
|
675
955
|
*/
|
|
@@ -718,21 +998,36 @@ window.__ModuleLoader__.load({
|
|
|
718
998
|
"aria-label": title,
|
|
719
999
|
children: WORKBUDDY_REGIONS.map((region) => {
|
|
720
1000
|
const regionStatus = statusByRegion[region];
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
1001
|
+
const regionOnState = regionOn(region);
|
|
1002
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1003
|
+
className: "dsm-workbuddy-tab-cell",
|
|
1004
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1005
|
+
type: "button",
|
|
1006
|
+
role: "tab",
|
|
1007
|
+
"aria-selected": region === activeRegion,
|
|
1008
|
+
className: `dsm-workbuddy-tab${region === activeRegion ? " dsm-workbuddy-tab-active" : ""}${regionOnState ? "" : " dsm-workbuddy-tab-off"}`,
|
|
1009
|
+
onClick: () => {
|
|
1010
|
+
setActiveRegion(region);
|
|
1011
|
+
setAccountError(void 0);
|
|
1012
|
+
},
|
|
1013
|
+
children: [regionStatus === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1014
|
+
"aria-hidden": "true",
|
|
1015
|
+
className: "dsm-workbuddy-tab-dot",
|
|
1016
|
+
style: dotStyle(regionStatus.status)
|
|
1017
|
+
}), region === "cn" ? t("row.tabCn") : t("row.tabGlobal")]
|
|
1018
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1019
|
+
className: "dsm-workbuddy-tab-switch",
|
|
1020
|
+
title: t("row.tabSwitchHint"),
|
|
1021
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1022
|
+
type: "checkbox",
|
|
1023
|
+
checked: regionOnState,
|
|
1024
|
+
disabled: togglingRegion === region || settingsScope?.getSnapshot().writable !== true,
|
|
1025
|
+
"aria-label": t("row.tabSwitchAria", { region: region === "cn" ? t("row.tabCn") : t("row.tabGlobal") }),
|
|
1026
|
+
onChange: (event) => {
|
|
1027
|
+
toggleRegion(region, event.target.checked);
|
|
1028
|
+
}
|
|
1029
|
+
})
|
|
1030
|
+
})]
|
|
736
1031
|
}, region);
|
|
737
1032
|
})
|
|
738
1033
|
}),
|
|
@@ -740,6 +1035,10 @@ window.__ModuleLoader__.load({
|
|
|
740
1035
|
className: "dsm-workbuddy-models-summary",
|
|
741
1036
|
children: t("row.tabHint")
|
|
742
1037
|
}),
|
|
1038
|
+
!activeRegionOn ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1039
|
+
className: "dsm-workbuddy-tab-off-notice",
|
|
1040
|
+
children: t("row.tabOffNotice")
|
|
1041
|
+
}) : null,
|
|
743
1042
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
744
1043
|
className: "dsm-workbuddy-usage-account",
|
|
745
1044
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -758,7 +1057,7 @@ window.__ModuleLoader__.load({
|
|
|
758
1057
|
className: "dsm-workbuddy-usage-expiry",
|
|
759
1058
|
children: t("row.tokenExpiry", { expiresAt: formatDateTime(status.tokenExpiresAtMs) })
|
|
760
1059
|
}) : null,
|
|
761
|
-
status.status === "error" || status.status === "signed-in" && status.creditsError !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1060
|
+
status.status === "error" || status.status === "signed-in" && status.creditsError !== void 0 ? status.status === "signed-in" && status.credentialRejected === true ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
762
1061
|
className: "dsm-workbuddy-usage-hint",
|
|
763
1062
|
children: t("row.reloginHint")
|
|
764
1063
|
}) : null,
|
|
@@ -796,23 +1095,14 @@ window.__ModuleLoader__.load({
|
|
|
796
1095
|
children: t("row.accountNoneInEffect")
|
|
797
1096
|
}), status.accounts.map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
798
1097
|
value: account.id,
|
|
799
|
-
children: [account.accountName, account.domain === "" ? "" : ` · ${account.domain}`]
|
|
1098
|
+
children: [nameOf(account.accountName), account.domain === "" ? "" : ` · ${account.domain}`]
|
|
800
1099
|
}, account.id))]
|
|
801
1100
|
})
|
|
802
1101
|
}),
|
|
803
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
804
|
-
type: "button",
|
|
805
|
-
className: "dsm-btn dsm-btn-outline",
|
|
806
|
-
disabled: switchingAccount || settingsScope?.getSnapshot().writable !== true,
|
|
807
|
-
onClick: () => {
|
|
808
|
-
clearAccount();
|
|
809
|
-
},
|
|
810
|
-
children: t("row.accountsFollowApp")
|
|
811
|
-
}),
|
|
812
1102
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
813
1103
|
className: "dsm-workbuddy-account-state",
|
|
814
1104
|
role: "status",
|
|
815
|
-
children:
|
|
1105
|
+
children: selectionExplicit === true ? t("row.accountsSavedChoice") : ""
|
|
816
1106
|
}),
|
|
817
1107
|
accountError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
818
1108
|
className: "dsm-workbuddy-account-error",
|
|
@@ -894,6 +1184,25 @@ window.__ModuleLoader__.load({
|
|
|
894
1184
|
})]
|
|
895
1185
|
});
|
|
896
1186
|
})(),
|
|
1187
|
+
status.credentialRejected === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1188
|
+
className: "dsm-workbuddy-usage-error",
|
|
1189
|
+
role: "alert",
|
|
1190
|
+
children: [
|
|
1191
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("row.credentialRejectedTitle") }),
|
|
1192
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("row.credentialRejectedIntro", { accountName: nameOf(status.accountName) }) }),
|
|
1193
|
+
status.recovery?.usableAccount !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("row.credentialRejectedSwitch", { accountName: nameOf(status.recovery.usableAccount.accountName) }) }) : status.recovery?.reloginRequired === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("row.credentialRejectedRelogin") }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("row.credentialRejectedChoose") }),
|
|
1194
|
+
status.recovery?.usableAccount === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1195
|
+
type: "button",
|
|
1196
|
+
className: "dsm-btn dsm-btn-outline",
|
|
1197
|
+
disabled: switchingAccount || settingsScope?.getSnapshot().writable !== true,
|
|
1198
|
+
onClick: () => {
|
|
1199
|
+
const target = status.recovery?.usableAccount;
|
|
1200
|
+
if (target !== void 0) switchAccount(target.accountId);
|
|
1201
|
+
},
|
|
1202
|
+
children: switchingAccount ? t("row.accountsScanning") : t("row.credentialRejectedSwitchAction", { accountName: nameOf(status.recovery.usableAccount.accountName) })
|
|
1203
|
+
})
|
|
1204
|
+
]
|
|
1205
|
+
}) : null,
|
|
897
1206
|
status.creditsError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
898
1207
|
className: "dsm-workbuddy-usage-error",
|
|
899
1208
|
children: t("row.creditsError", { message: status.creditsError })
|
|
@@ -1043,10 +1352,13 @@ window.__ModuleLoader__.load({
|
|
|
1043
1352
|
]
|
|
1044
1353
|
})
|
|
1045
1354
|
] }) : null,
|
|
1046
|
-
status.status === "signed-out" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1355
|
+
status.status === "signed-out" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1047
1356
|
className: "dsm-workbuddy-usage-text",
|
|
1048
|
-
children:
|
|
1049
|
-
})
|
|
1357
|
+
children: signedOutText(notice, t)
|
|
1358
|
+
}), searched.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchedPaths, {
|
|
1359
|
+
items: searched,
|
|
1360
|
+
t
|
|
1361
|
+
}) : null] }) : null,
|
|
1050
1362
|
status.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1051
1363
|
className: "dsm-workbuddy-usage-error",
|
|
1052
1364
|
children: status.message
|
|
@@ -1077,11 +1389,32 @@ window.__ModuleLoader__.load({
|
|
|
1077
1389
|
"row.tabCn": "Domestic",
|
|
1078
1390
|
"row.tabGlobal": "Global",
|
|
1079
1391
|
"row.tabHint": "Each tab is a separate provider (workbuddy / workbuddy-global) with its own account, credits, and models. Both sides are live at once: different sessions can pick from either side, and changes on one tab never touch the other.",
|
|
1392
|
+
"row.tabSwitchHint": "Uncheck to switch this provider off. Its models disappear from the DSH model picker; your account, credits, and model settings are kept and return when you check it again.",
|
|
1393
|
+
"row.tabSwitchAria": "Offer the {region} provider to DSH",
|
|
1394
|
+
"row.tabOffNotice": "This provider is switched off: none of its models are offered to DSH. Your account, credits, and model settings are kept — check the box above the tab to bring it back.",
|
|
1080
1395
|
"row.signedOut": "Not signed in",
|
|
1081
1396
|
"row.signedOutHint": "Sign in once in the WorkBuddy desktop app; this plugin follows that sign-in automatically.",
|
|
1397
|
+
"row.signedOutWrongRegion": "Your WorkBuddy sign-in was found, but it belongs to the other region tab. Switch tabs above to use it — there is nothing to sign in again.",
|
|
1398
|
+
"row.searchedTitle": "Paths checked",
|
|
1399
|
+
"row.searchedHint": "If your WorkBuddy keeps its login somewhere else, point the plugin at it with the WORKBUDDY_AUTH_FILE environment variable.",
|
|
1400
|
+
"row.reasonMissing": "not found",
|
|
1401
|
+
"row.reasonUnreadable": "unreadable",
|
|
1402
|
+
"row.reasonInvalid": "no usable token",
|
|
1403
|
+
"row.reasonEncrypted": "encrypted — needs the desktop app",
|
|
1404
|
+
"row.reasonWrongRegion": "belongs to the other region",
|
|
1405
|
+
"row.sourceDesktop": "desktop app",
|
|
1406
|
+
"row.sourceDsh": "plugin copy",
|
|
1407
|
+
"row.searchedEncryptedNotice": "The credential file is there, but its token fields are encrypted and the key could not be read. Install the WorkBuddy desktop app, or set WORKBUDDY_APP_EXECUTABLE to its executable — signing in again on its own will not help.",
|
|
1408
|
+
"row.searchedMore": "Show the {count} path(s) that were not found",
|
|
1082
1409
|
"row.signedIn": "Signed in: {accountName}",
|
|
1083
1410
|
"row.tokenExpiry": "Access token expires {expiresAt} (auto-renewal)",
|
|
1084
1411
|
"row.reloginHint": "If errors occur, sign back in to the WorkBuddy app.",
|
|
1412
|
+
"row.credentialRejectedTitle": "WorkBuddy refused this account's token",
|
|
1413
|
+
"row.credentialRejectedIntro": "The token for \"{accountName}\" is no longer accepted by the upstream, so credits and check-in are unavailable. Signing in again only helps if the problem is the token itself — here is what actually applies:",
|
|
1414
|
+
"row.credentialRejectedSwitch": "Another sign-in on this machine answered the upstream: \"{accountName}\". Switching to it fixes this — no need to sign in again.",
|
|
1415
|
+
"row.credentialRejectedSwitchAction": "Switch to \"{accountName}\"",
|
|
1416
|
+
"row.credentialRejectedRelogin": "No other local sign-in exists to switch to. Sign in again in the WorkBuddy desktop app.",
|
|
1417
|
+
"row.credentialRejectedChoose": "Other local sign-ins exist but none of them answered the upstream just now. Try switching to one above; if none works, sign in again in the desktop app.",
|
|
1085
1418
|
"row.requestFailed": "Request failed",
|
|
1086
1419
|
"row.creditsTotalLabel": "Total remaining credits",
|
|
1087
1420
|
"row.creditsMonthlyRemain": "Remaining {remain} / {size} · Refreshes {at}",
|
|
@@ -1095,17 +1428,15 @@ window.__ModuleLoader__.load({
|
|
|
1095
1428
|
"row.refresh": "Refresh",
|
|
1096
1429
|
"row.refreshing": "Refreshing…",
|
|
1097
1430
|
"row.accountsTitle": "WorkBuddy account",
|
|
1431
|
+
"row.accountUnnamed": "Unnamed account",
|
|
1098
1432
|
"row.accountsHint": "Choose from locally detected sign-ins. Tokens are never shown or saved in DSH settings.",
|
|
1099
1433
|
"row.accountsRescan": "Detect accounts again",
|
|
1100
1434
|
"row.accountsScanning": "Detecting…",
|
|
1101
|
-
"row.accountsFollowApp": "Follow the app's sign-in",
|
|
1102
1435
|
"row.accountNoneInEffect": "No account in effect",
|
|
1103
1436
|
"row.accountsSavedChoice": "Using the account saved for this region.",
|
|
1104
|
-
"row.accountsFollowingApp": "Following the app's current sign-in — no account is saved for this region.",
|
|
1105
|
-
"row.accountsCleared": "Saved choice cleared. This region follows the app's current sign-in again.",
|
|
1106
1437
|
"row.accountsWriteFailed": "The account change was not saved: {message} — the settings file could not be updated (on Windows it may be locked by an antivirus scanner or a sync client). Your previous choice is still in effect; close whatever holds settings.yaml and try again.",
|
|
1107
1438
|
"row.selectionLostHint": "The saved account no longer exists locally.",
|
|
1108
|
-
"row.selectionLostMessage": "The account saved for this region is no longer among the local sign-ins (WorkBuddy replaced its login or cleaned up backups). Your other sign-ins are fine — pick
|
|
1439
|
+
"row.selectionLostMessage": "The account saved for this region is no longer among the local sign-ins (WorkBuddy replaced its login or cleaned up backups). Your other sign-ins are fine — pick a current one from the list above. Signing in again in the desktop app will not fix this.",
|
|
1109
1440
|
"row.modelsTitle": "Models",
|
|
1110
1441
|
"row.modelsSummary": "{count} enabled",
|
|
1111
1442
|
"row.modelsRefresh": "Refresh from WorkBuddy",
|
|
@@ -1133,11 +1464,32 @@ window.__ModuleLoader__.load({
|
|
|
1133
1464
|
"row.tabCn": "国内版",
|
|
1134
1465
|
"row.tabGlobal": "国际版",
|
|
1135
1466
|
"row.tabHint": "每个 tab 是一个独立供应商(workbuddy / workbuddy-global),各有自己的账号、积分与模型。两边同时生效:不同会话可各选一边,一侧的改动不影响另一侧。",
|
|
1467
|
+
"row.tabSwitchHint": "取消勾选即关闭该供应商:它的模型不再出现在 DSH 模型选择器里;账号、积分与模型设置都会保留,重新勾选即恢复。",
|
|
1468
|
+
"row.tabSwitchAria": "是否向 DSH 提供{region}供应商",
|
|
1469
|
+
"row.tabOffNotice": "该供应商已关闭:它的模型不会提供给 DSH。账号、积分与模型设置均已保留——勾选上方复选框即可恢复。",
|
|
1136
1470
|
"row.signedOut": "未登录",
|
|
1137
1471
|
"row.signedOutHint": "在 WorkBuddy 桌面 App 里登录一次即可,插件会自动跟随当前登录的账号。",
|
|
1472
|
+
"row.signedOutWrongRegion": "找到了你的 WorkBuddy 登录信息,但它属于另一个地区的标签页。切换到上方对应标签页即可使用——不需要重新登录。",
|
|
1473
|
+
"row.searchedTitle": "已检查的路径",
|
|
1474
|
+
"row.searchedHint": "如果你的 WorkBuddy 把登录信息存在别处,可以用环境变量 WORKBUDDY_AUTH_FILE 指定。",
|
|
1475
|
+
"row.reasonMissing": "不存在",
|
|
1476
|
+
"row.reasonUnreadable": "无法读取",
|
|
1477
|
+
"row.reasonInvalid": "没有可用的 token",
|
|
1478
|
+
"row.reasonEncrypted": "已加密——需要桌面 App",
|
|
1479
|
+
"row.reasonWrongRegion": "属于另一个地区",
|
|
1480
|
+
"row.sourceDesktop": "桌面 App",
|
|
1481
|
+
"row.sourceDsh": "插件副本",
|
|
1482
|
+
"row.searchedEncryptedNotice": "凭据文件在,但其中的 token 字段是加密的,插件拿不到密钥。请安装 WorkBuddy 桌面 App,或用环境变量 WORKBUDDY_APP_EXECUTABLE 指定它的可执行文件——单纯重新登录解决不了这个问题。",
|
|
1483
|
+
"row.searchedMore": "显示另外 {count} 条「不存在」的路径",
|
|
1138
1484
|
"row.signedIn": "已登录:{accountName}",
|
|
1139
1485
|
"row.tokenExpiry": "访问令牌 {expiresAt} 过期(自动续期)",
|
|
1140
1486
|
"row.reloginHint": "出现错误,重新登录 WorkBuddy APP 即可。",
|
|
1487
|
+
"row.credentialRejectedTitle": "WorkBuddy 拒绝了这个账号的 token",
|
|
1488
|
+
"row.credentialRejectedIntro": "「{accountName}」的 token 已不被上游接受,所以积分与签到都用不了。重新登录只在「确实是 token 的问题」时才有用——当前实际适用的是下面这一条:",
|
|
1489
|
+
"row.credentialRejectedSwitch": "本机另一个账号「{accountName}」刚刚实测可用,切换到它即可恢复,不必重新登录。",
|
|
1490
|
+
"row.credentialRejectedSwitchAction": "切换到「{accountName}」",
|
|
1491
|
+
"row.credentialRejectedRelogin": "本机没有其他账号可切换。请在 WorkBuddy 桌面端重新登录。",
|
|
1492
|
+
"row.credentialRejectedChoose": "本机还有其他账号,但刚才都没能通过上游校验。可以在上方切换一个试试;如果都不行,请在桌面端重新登录。",
|
|
1141
1493
|
"row.requestFailed": "请求失败",
|
|
1142
1494
|
"row.creditsTotalLabel": "总剩余积分",
|
|
1143
1495
|
"row.creditsMonthlyRemain": "剩余 {remain} / {size} · {at} 刷新",
|
|
@@ -1151,17 +1503,15 @@ window.__ModuleLoader__.load({
|
|
|
1151
1503
|
"row.refresh": "刷新",
|
|
1152
1504
|
"row.refreshing": "正在刷新…",
|
|
1153
1505
|
"row.accountsTitle": "WorkBuddy 账号",
|
|
1506
|
+
"row.accountUnnamed": "未命名账号",
|
|
1154
1507
|
"row.accountsHint": "选择本机检测到的登录账号;Token 不会显示,也不会保存到 DSH 设置。",
|
|
1155
1508
|
"row.accountsRescan": "重新检测账号",
|
|
1156
1509
|
"row.accountsScanning": "正在检测…",
|
|
1157
|
-
"row.accountsFollowApp": "跟随 App 当前登录",
|
|
1158
1510
|
"row.accountNoneInEffect": "当前没有生效的账号",
|
|
1159
1511
|
"row.accountsSavedChoice": "正在使用该区域保存的账号。",
|
|
1160
|
-
"row.accountsFollowingApp": "正在跟随 App 当前登录——该区域没有保存账号。",
|
|
1161
|
-
"row.accountsCleared": "已清除保存的选择,该区域重新跟随 App 当前登录。",
|
|
1162
1512
|
"row.accountsWriteFailed": "账号改动未保存:{message} —— 设置文件写入失败(Windows 上可能被杀毒软件或同步盘占用)。当前仍是原来保存的选择,请先关闭占用 settings.yaml 的程序再重试。",
|
|
1163
1513
|
"row.selectionLostHint": "已保存的账号在本机已不存在。",
|
|
1164
|
-
"row.selectionLostMessage": "该区域保存的账号已不在本机登录列表中(WorkBuddy
|
|
1514
|
+
"row.selectionLostMessage": "该区域保存的账号已不在本机登录列表中(WorkBuddy 更换了登录或清理了备份文件)。本机其他登录仍然可用——请在上方列表中重新选择一个当前账号。重新登录桌面端 App 无法修复此问题。",
|
|
1165
1515
|
"row.modelsTitle": "模型",
|
|
1166
1516
|
"row.modelsSummary": "已启用 {count} 个",
|
|
1167
1517
|
"row.modelsRefresh": "从 WorkBuddy 刷新",
|