dsh-connect-workbuddy 4.14.1 → 5.0.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/lib/client.js CHANGED
@@ -264,6 +264,20 @@ window.__ModuleLoader__.load({
264
264
  function regionCreditsShownOf(value, region) {
265
265
  return regionSlotOf(value, region)["showCreditsInMainUi"] !== false;
266
266
  }
267
+ /**
268
+ * Whether the model may call the image-generation tool in one region.
269
+ *
270
+ * OPT-IN, unlike every other switch in this file: absent reads as `false`. The
271
+ * asymmetry is deliberate. The credit line and the provider switch are things
272
+ * whose absence should behave like a fresh install (on), because a hidden line
273
+ * or a withdrawn provider is recoverable by clicking. This one authorizes the
274
+ * MODEL to spend credits on its own — about 5.71 per image, with no per-call
275
+ * approval available in the host — so an upgrade or an unreadable setting must
276
+ * leave it OFF rather than silently grant it.
277
+ */
278
+ function regionImageToolEnabledOf(value, region) {
279
+ return regionSlotOf(value, region)["imageToolEnabled"] === true;
280
+ }
267
281
  /** The seat a region's readout uses, defaulting to the composer row. */
268
282
  function regionCreditPositionOf(value, region) {
269
283
  return regionSlotOf(value, region)["creditPosition"] === "stats" ? "stats" : "composer";
@@ -295,6 +309,18 @@ window.__ModuleLoader__.load({
295
309
  });
296
310
  }
297
311
  /**
312
+ * Build the next `regions` settings value for the image-tool opt-in.
313
+ *
314
+ * Same merge rule: ONLY the target region's flag changes, so the directory,
315
+ * the model selection and the sibling region survive the write.
316
+ */
317
+ function nextRegionImageToolEnabled(value, region, enabled) {
318
+ return nextRegionSlots(regionsMapOf(value), region, {
319
+ ...regionSlotOf(value, region),
320
+ imageToolEnabled: enabled
321
+ });
322
+ }
323
+ /**
298
324
  * Project one card row into its persisted `lastCatalog` shape: the native
299
325
  * context window becomes the stored `contextWindow`, and the card-only
300
326
  * presentation fields (`nativeContextWindow`, `multimodal`) are removed BY
@@ -673,6 +699,12 @@ window.__ModuleLoader__.load({
673
699
  * @param slot - the region's COMPLETE next slot (caller owns the merge).
674
700
  * @throws {WorkBuddySettingsWriteError} when neither writer persists the value.
675
701
  */
702
+ async function writeRegionImageToolEnabled(scope, region, enabled, slot) {
703
+ await writeField(scope, "regions", region, slot, (readBack) => {
704
+ const stored = readBack;
705
+ return stored !== null && stored !== void 0 && stored["imageToolEnabled"] === enabled;
706
+ });
707
+ }
676
708
  async function writeRegionCreditsShown(scope, region, shown, slot) {
677
709
  await writeField(scope, "regions", region, slot, (readBack) => {
678
710
  const stored = readBack;
@@ -4038,6 +4070,37 @@ window.__ModuleLoader__.load({
4038
4070
  .dsm-workbuddy-tab-switch{display:inline-flex;align-items:center;flex:none;margin-left:auto;padding-left:10px;border-left:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.24));cursor:pointer}
4039
4071
  .dsm-workbuddy-tab-switch input{margin:0;cursor:pointer;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
4040
4072
  .dsm-workbuddy-tab-switch input:disabled{cursor:default}
4073
+ /* SECTION TABS: the card's own top-level split (accounts / models / diagnostics).
4074
+ *
4075
+ * Deliberately NOT the boxed cells above. Those are the REGION scope — a
4076
+ * two-way either/or, each side carrying its own on/off switch — while these are
4077
+ * panels of ONE document, the shape a reader already knows from the app's own
4078
+ * settings window. Two rows of boxes would read as one four-way choice.
4079
+ *
4080
+ * The hidden attribute on each panel is what does the work: an inactive panel
4081
+ * stays MOUNTED, so switching back does not refetch, a half-filled catalog draft
4082
+ * survives the trip, and the account pool keeps its log. It also takes the
4083
+ * inactive panel out of the accessibility tree, which is exactly what
4084
+ * role="tabpanel" wants — unlike display:none reached by a CSS class, it
4085
+ * cannot be overridden by a later rule. */
4086
+ .dsm-workbuddy-panels{display:flex;align-items:stretch;gap:2px;margin:16px 0 0;border-bottom:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.24))}
4087
+ .dsm-workbuddy-panel-tab{appearance:none;font:inherit;cursor:pointer;border:0;background:transparent;padding:7px 11px;margin-bottom:-1px;border-bottom:2px solid transparent;color:var(--dsw-alias-label-tertiary,#999);font-size:13px;font-weight:600;line-height:20px;white-space:nowrap;transition:color .15s,border-color .15s}
4088
+ .dsm-workbuddy-panel-tab:hover:not(.dsm-workbuddy-panel-tab-active){color:var(--dsw-alias-label-primary,#e6e6e6)}
4089
+ .dsm-workbuddy-panel-tab:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
4090
+ .dsm-workbuddy-panel-tab-active{color:var(--dsw-alias-label-primary,#e6e6e6);border-bottom-color:var(--dsw-alias-brand-primary,#5686fe)}
4091
+ .dsm-workbuddy-panel{margin-top:12px}
4092
+ /* DIAGNOSTICS: a two-column fact table rather than prose. Each row is one thing
4093
+ * the card actually READ, so a bug report can quote a line instead of
4094
+ * paraphrasing the UI. No control lives here on purpose — a diagnostics panel
4095
+ * that can change settings is a settings panel with a worse name. */
4096
+ .dsm-workbuddy-diag{display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l2,#36373b);border-radius:10px;overflow:hidden;background:var(--dsw-alias-bg-layer-2,#232529)}
4097
+ .dsm-workbuddy-diag-row{display:flex;align-items:baseline;justify-content:space-between;gap:16px;padding:8px 12px}
4098
+ .dsm-workbuddy-diag-row+.dsm-workbuddy-diag-row{border-top:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.24))}
4099
+ .dsm-workbuddy-diag-label{flex:none;color:var(--dsw-alias-label-tertiary,#999);font-size:12px;line-height:18px}
4100
+ .dsm-workbuddy-diag-value{min-width:0;color:var(--dsw-alias-label-primary,#e6e6e6);font-size:12px;line-height:18px;text-align:right;overflow-wrap:anywhere}
4101
+ .dsm-workbuddy-diag-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
4102
+ .dsm-workbuddy-diag-bad{color:var(--dsw-alias-label-caution,#e8b34b)}
4103
+ .dsm-workbuddy-diag-hint{margin:10px 0 0;color:var(--dsw-alias-label-tertiary,#999);font-size:12px;line-height:18px}
4041
4104
  /* PREFERENCE LINES: a checkbox on a line that commits on the click, packed from
4042
4105
  the LEFT — the shape the reference card uses for this exact block.
4043
4106
  *
@@ -4067,7 +4130,7 @@ window.__ModuleLoader__.load({
4067
4130
  CONTRAST colour, so a ticked box painted itself as a heavy black pill — the
4068
4131
  loudest object in a section made of plain 13px text. */
4069
4132
  .dsm-workbuddy-pref input{flex:none;cursor:pointer;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
4070
- .dsm-workbuddy-pref-check{inline-size:15px;block-size:15px;margin:3px 0 0}
4133
+ .dsm-workbuddy-pref-check,.dsm-workbuddy-image-tool-check{inline-size:15px;block-size:15px;margin:3px 0 0}
4071
4134
  .dsm-workbuddy-pref input:disabled{cursor:default}
4072
4135
  .dsm-workbuddy-pref:has(input:disabled){opacity:.6;cursor:default}
4073
4136
  /* The NAME and what it does are TWO lines, not one wrapped blob: first what the
@@ -4168,7 +4231,14 @@ window.__ModuleLoader__.load({
4168
4231
  an error banner, because the fact it states is normal product behaviour
4169
4232
  rather than a failure. Weight is the only signal it needs. */
4170
4233
  .dsm-workbuddy-free-quota-warn{font-weight:600}
4171
- .dsm-workbuddy-model-list{display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l2,#36373b);border-radius:10px;overflow:hidden}
4234
+ /* The rows scroll INSIDE the box once the list outgrows it: the section owns a
4235
+ fixed share of the card instead of setting its height from the roster's
4236
+ length. Without this the list was the card's height all by itself — 20-odd
4237
+ models, each carrying a budget field, a reasoning picker and an image toggle.
4238
+ The rounded corners survive because the scroll container is the bordered box
4239
+ itself, so the first and last row are clipped by the same radius the border
4240
+ already draws. */
4241
+ .dsm-workbuddy-model-list{display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l2,#36373b);border-radius:10px;overflow:hidden;max-height:520px;overflow-y:auto}
4172
4242
  .dsm-workbuddy-model{display:grid;grid-template-columns:minmax(0,1fr);gap:7px;padding:10px 12px;background:var(--dsw-alias-bg-layer-2,#232529);transition:opacity .16s}
4173
4243
  .dsm-workbuddy-model-disabled{opacity:.55}
4174
4244
  .dsm-workbuddy-model+.dsm-workbuddy-model{border-top:1px solid var(--dsw-alias-border-l2,#36373b)}
@@ -5190,6 +5260,21 @@ window.__ModuleLoader__.load({
5190
5260
  * appear before anything billing-side has answered.
5191
5261
  */
5192
5262
  const [statusByRegion, setStatusByRegion] = (0, react.useState)({});
5263
+ /**
5264
+ * Which of the card's three sections is on screen.
5265
+ *
5266
+ * The section tabs exist because the card grew into one 3,500 px column that
5267
+ * carried two unrelated jobs: operating an account (who serves, how much is
5268
+ * left, which order) and editing the catalog (which models, what context
5269
+ * budget, which reasoning tiers). They even commit differently — an account
5270
+ * change lands on the click, a catalog change is a draft until saved — so a
5271
+ * single column put the save button three screens away from what it saves.
5272
+ *
5273
+ * Only the ACTIVE section is visible; the others stay MOUNTED (the `hidden`
5274
+ * attribute), so switching back never refetches, a half-filled draft
5275
+ * survives, and the pool keeps its log.
5276
+ */
5277
+ const [panel, setPanel] = (0, react.useState)("accounts");
5193
5278
  const [busy, setBusy] = (0, react.useState)(false);
5194
5279
  const [settingsRevision, setSettingsRevision] = (0, react.useState)(0);
5195
5280
  /** Per-region unsaved model edits; a draft on one tab is never dropped by
@@ -5409,6 +5494,14 @@ window.__ModuleLoader__.load({
5409
5494
  selectionExplicit: false
5410
5495
  };
5411
5496
  /**
5497
+ * Whether the ACTIVE region has a usable sign-in.
5498
+ *
5499
+ * The section tabs render only here: a signed-out card is one screen of
5500
+ * instructions, and splitting that into panels would hide the only thing it
5501
+ * has to say behind a tab.
5502
+ */
5503
+ const signedIn = status.status === "signed-in";
5504
+ /**
5412
5505
  * Whether the ACTIVE region knows of any account at all.
5413
5506
  *
5414
5507
  * This is the poll's precondition in place of `status === 'signed-in'`. The
@@ -5520,6 +5613,19 @@ window.__ModuleLoader__.load({
5520
5613
  return regionCreditsShownOf(settingsScope?.getSnapshot().value, item);
5521
5614
  };
5522
5615
  /**
5616
+ * Whether the model may call the image tool in this region.
5617
+ *
5618
+ * Read off the Host's answer first, like {@link creditsShownOn} and for the
5619
+ * same measured reason — the browser mirror is stale for this namespace on the
5620
+ * affected 0.1.7 deployments, and here a stale `true` would be worse than a
5621
+ * dead checkbox: it would claim the spending capability is on.
5622
+ */
5623
+ const imageToolOn = (item) => {
5624
+ const usage = statusByRegion[item];
5625
+ if (typeof usage?.imageToolEnabled === "boolean") return usage.imageToolEnabled;
5626
+ return regionImageToolEnabledOf(settingsScope?.getSnapshot().value, item);
5627
+ };
5628
+ /**
5523
5629
  * Switch one region's provider off or on. The write carries the region's
5524
5630
  * whole slot through untouched — only `enabled` changes — so the user's
5525
5631
  * directory, model picks, image opt-ins and budgets survive a round trip.
@@ -5605,6 +5711,19 @@ window.__ModuleLoader__.load({
5605
5711
  if (mounted.current) setTogglingCredits(void 0);
5606
5712
  }
5607
5713
  };
5714
+ const toggleImageTool = async (item, enabled) => {
5715
+ if (settingsScope === void 0) return;
5716
+ setTogglingCredits(item);
5717
+ try {
5718
+ const slot = nextRegionImageToolEnabled(settingsScope.getSnapshot().value, item, enabled)[item];
5719
+ await writeRegionImageToolEnabled(settingsScope, item, enabled, typeof slot === "object" && slot !== null ? slot : {});
5720
+ await refreshUsage(item);
5721
+ } catch (error) {
5722
+ if (mounted.current) setAccountError(error instanceof Error ? error.message : t("row.requestFailed"));
5723
+ } finally {
5724
+ if (mounted.current) setTogglingCredits(void 0);
5725
+ }
5726
+ };
5608
5727
  const refreshModels = async () => {
5609
5728
  setBusy(true);
5610
5729
  try {
@@ -5990,46 +6109,82 @@ window.__ModuleLoader__.load({
5990
6109
  });
5991
6110
  const creditPositionCaption = t("row.creditPositionHintInline");
5992
6111
  const creditPositionDisabledCaption = t("row.creditPositionDisabledHint");
6112
+ /**
6113
+ * The image tool opt-in, rendered above the credit preferences.
6114
+ *
6115
+ * It sits first because it is the only preference here that authorizes SPEND
6116
+ * rather than display: the credit line is a readout, this one lets the model
6117
+ * buy an image (about 5.71 credits) without asking again. CN-only, because the
6118
+ * upstream publishes a `text-to-image` model on the CN gateway and none at all
6119
+ * internationally — showing the box on the global tab would offer a control
6120
+ * that can never do anything.
6121
+ */
6122
+ const imageToolPreference = activeRegion === "cn" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6123
+ className: "dsm-workbuddy-pref",
6124
+ title: t("row.imageToolHint"),
6125
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6126
+ type: "checkbox",
6127
+ className: "dsm-workbuddy-image-tool-check",
6128
+ checked: activeRegionOn ? imageToolOn(activeRegion) : false,
6129
+ disabled: !activeRegionOn || togglingCredits === activeRegion || !canWrite || saving || poolBusy,
6130
+ onChange: (event) => {
6131
+ toggleImageTool(activeRegion, event.target.checked);
6132
+ }
6133
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6134
+ className: "dsm-workbuddy-pref-copy",
6135
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6136
+ className: "dsm-workbuddy-pref-label",
6137
+ children: t("row.imageToolLabel")
6138
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6139
+ className: "dsm-workbuddy-pref-hint",
6140
+ children: t("row.imageToolHint")
6141
+ })]
6142
+ })]
6143
+ }) : null;
5993
6144
  const creditPreferences = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5994
6145
  className: "dsm-workbuddy-prefs",
5995
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5996
- className: "dsm-workbuddy-pref",
5997
- title: activeRegion === "cn" ? t("row.showCreditsCnHint") : t("row.showCreditsGlobalHint"),
5998
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5999
- type: "checkbox",
6000
- className: "dsm-workbuddy-pref-check",
6001
- checked: activeRegionOn ? creditsShownOn(activeRegion) : false,
6002
- disabled: !activeRegionOn || togglingCredits === activeRegion || !canWrite || saving || poolBusy,
6003
- onChange: (event) => {
6004
- toggleCredits(activeRegion, event.target.checked);
6005
- }
6006
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6007
- className: "dsm-workbuddy-pref-copy",
6008
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6009
- className: "dsm-workbuddy-pref-label",
6010
- children: activeRegion === "cn" ? t("row.showCreditsCn") : t("row.showCreditsGlobal")
6011
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6012
- className: "dsm-workbuddy-pref-hint",
6013
- children: t("row.showCreditsHintInline")
6014
- })]
6015
- })]
6016
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
6017
- className: "dsm-workbuddy-pref dsm-workbuddy-pref-indent",
6018
- title: t("row.creditPositionHint"),
6019
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6020
- className: "dsm-workbuddy-pref-copy",
6021
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6022
- className: "dsm-workbuddy-pref-lead",
6146
+ children: [
6147
+ imageToolPreference,
6148
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6149
+ className: "dsm-workbuddy-pref",
6150
+ title: activeRegion === "cn" ? t("row.showCreditsCnHint") : t("row.showCreditsGlobalHint"),
6151
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6152
+ type: "checkbox",
6153
+ className: "dsm-workbuddy-pref-check",
6154
+ checked: activeRegionOn ? creditsShownOn(activeRegion) : false,
6155
+ disabled: !activeRegionOn || togglingCredits === activeRegion || !canWrite || saving || poolBusy,
6156
+ onChange: (event) => {
6157
+ toggleCredits(activeRegion, event.target.checked);
6158
+ }
6159
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6160
+ className: "dsm-workbuddy-pref-copy",
6023
6161
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6024
6162
  className: "dsm-workbuddy-pref-label",
6025
- children: t("row.creditPositionLabel")
6026
- }), creditPositionPicker]
6027
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6028
- className: "dsm-workbuddy-pref-hint",
6029
- children: activeRegionOn ? creditPositionCaption : creditPositionDisabledCaption
6163
+ children: activeRegion === "cn" ? t("row.showCreditsCn") : t("row.showCreditsGlobal")
6164
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6165
+ className: "dsm-workbuddy-pref-hint",
6166
+ children: t("row.showCreditsHintInline")
6167
+ })]
6030
6168
  })]
6169
+ }),
6170
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
6171
+ className: "dsm-workbuddy-pref dsm-workbuddy-pref-indent",
6172
+ title: t("row.creditPositionHint"),
6173
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6174
+ className: "dsm-workbuddy-pref-copy",
6175
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6176
+ className: "dsm-workbuddy-pref-lead",
6177
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6178
+ className: "dsm-workbuddy-pref-label",
6179
+ children: t("row.creditPositionLabel")
6180
+ }), creditPositionPicker]
6181
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6182
+ className: "dsm-workbuddy-pref-hint",
6183
+ children: activeRegionOn ? creditPositionCaption : creditPositionDisabledCaption
6184
+ })]
6185
+ })
6031
6186
  })
6032
- })]
6187
+ ]
6033
6188
  });
6034
6189
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
6035
6190
  className: `dsm-plugin-card${open ? " dsm-plugin-card-open" : ""}`,
@@ -6107,81 +6262,140 @@ window.__ModuleLoader__.load({
6107
6262
  }, region);
6108
6263
  })
6109
6264
  }),
6110
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6111
- className: "dsm-workbuddy-models-summary",
6112
- children: t("row.tabHint")
6113
- }),
6114
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6115
- className: "dsm-workbuddy-models-summary dsm-workbuddy-free-quota-warn",
6116
- children: t("row.tabFreeQuotaWarn")
6117
- }),
6118
- !activeRegionOn ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6119
- className: "dsm-workbuddy-tab-off-notice",
6120
- children: t("row.tabOffNotice")
6121
- }) : null,
6122
- creditPreferences,
6123
- 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)("p", {
6124
- className: "dsm-workbuddy-usage-hint",
6125
- children: t("row.reloginHint")
6126
- }) : null,
6127
- accountError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6128
- className: "dsm-workbuddy-account-error",
6129
- role: "alert",
6130
- children: accountError
6131
- }),
6132
- selectionLost ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6133
- className: "dsm-workbuddy-usage-hint",
6134
- children: t("row.selectionLostHint")
6265
+ signedIn ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6266
+ className: "dsm-workbuddy-panels",
6267
+ role: "tablist",
6268
+ "aria-label": t("row.panelAria"),
6269
+ children: [
6270
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6271
+ type: "button",
6272
+ role: "tab",
6273
+ id: "dsm-workbuddy-tab-accounts",
6274
+ "aria-controls": "dsm-workbuddy-panel-accounts",
6275
+ "aria-selected": panel === "accounts",
6276
+ className: `dsm-workbuddy-panel-tab${panel === "accounts" ? " dsm-workbuddy-panel-tab-active" : ""}`,
6277
+ onClick: () => {
6278
+ setPanel("accounts");
6279
+ },
6280
+ children: t("row.panelAccounts")
6281
+ }),
6282
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6283
+ type: "button",
6284
+ role: "tab",
6285
+ id: "dsm-workbuddy-tab-models",
6286
+ "aria-controls": "dsm-workbuddy-panel-models",
6287
+ "aria-selected": panel === "models",
6288
+ className: `dsm-workbuddy-panel-tab${panel === "models" ? " dsm-workbuddy-panel-tab-active" : ""}`,
6289
+ onClick: () => {
6290
+ setPanel("models");
6291
+ },
6292
+ children: t("row.panelModels")
6293
+ }),
6294
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6295
+ type: "button",
6296
+ role: "tab",
6297
+ id: "dsm-workbuddy-tab-diag",
6298
+ "aria-controls": "dsm-workbuddy-panel-diag",
6299
+ "aria-selected": panel === "diag",
6300
+ className: `dsm-workbuddy-panel-tab${panel === "diag" ? " dsm-workbuddy-panel-tab-active" : ""}`,
6301
+ onClick: () => {
6302
+ setPanel("diag");
6303
+ },
6304
+ children: t("row.panelDiag")
6305
+ })
6306
+ ]
6135
6307
  }) : null,
6136
- status.status === "signed-in" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
6137
- status.credentialRejected === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
6138
- className: "dsm-workbuddy-usage-error",
6139
- role: "alert",
6140
- children: [
6141
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("row.credentialRejectedTitle") }),
6142
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("row.credentialRejectedIntro", { accountName: nameOf(status.accountName) }) }),
6143
- 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") }),
6144
- status.recovery?.usableAccount === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6145
- type: "button",
6146
- className: "dsm-btn dsm-btn-outline",
6147
- disabled: switchingAccount || !canWrite,
6148
- onClick: () => {
6149
- const target = status.recovery?.usableAccount;
6150
- if (target !== void 0) switchAccount(target.accountId);
6308
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6309
+ className: "dsm-workbuddy-panel",
6310
+ role: "tabpanel",
6311
+ id: "dsm-workbuddy-panel-accounts",
6312
+ "aria-labelledby": "dsm-workbuddy-tab-accounts",
6313
+ hidden: signedIn && panel !== "accounts",
6314
+ children: [
6315
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6316
+ className: "dsm-workbuddy-models-summary",
6317
+ children: t("row.tabHint")
6318
+ }),
6319
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6320
+ className: "dsm-workbuddy-models-summary dsm-workbuddy-free-quota-warn",
6321
+ children: t("row.tabFreeQuotaWarn")
6322
+ }),
6323
+ !activeRegionOn ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6324
+ className: "dsm-workbuddy-tab-off-notice",
6325
+ children: t("row.tabOffNotice")
6326
+ }) : null,
6327
+ creditPreferences,
6328
+ 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)("p", {
6329
+ className: "dsm-workbuddy-usage-hint",
6330
+ children: t("row.reloginHint")
6331
+ }) : null,
6332
+ accountError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6333
+ className: "dsm-workbuddy-account-error",
6334
+ role: "alert",
6335
+ children: accountError
6336
+ }),
6337
+ selectionLost ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6338
+ className: "dsm-workbuddy-usage-hint",
6339
+ children: t("row.selectionLostHint")
6340
+ }) : null,
6341
+ status.status === "signed-in" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
6342
+ status.credentialRejected === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
6343
+ className: "dsm-workbuddy-usage-error",
6344
+ role: "alert",
6345
+ children: [
6346
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("row.credentialRejectedTitle") }),
6347
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("row.credentialRejectedIntro", { accountName: nameOf(status.accountName) }) }),
6348
+ 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") }),
6349
+ status.recovery?.usableAccount === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6350
+ type: "button",
6351
+ className: "dsm-btn dsm-btn-outline",
6352
+ disabled: switchingAccount || !canWrite,
6353
+ onClick: () => {
6354
+ const target = status.recovery?.usableAccount;
6355
+ if (target !== void 0) switchAccount(target.accountId);
6356
+ },
6357
+ children: switchingAccount ? t("row.accountsScanning") : t("row.credentialRejectedSwitchAction", { accountName: nameOf(status.recovery.usableAccount.accountName) })
6358
+ })
6359
+ ]
6360
+ }) : null,
6361
+ status.creditsError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6362
+ className: "dsm-workbuddy-usage-error",
6363
+ children: t("row.creditsError", { message: status.creditsError })
6364
+ }),
6365
+ status.refreshError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6366
+ className: "dsm-workbuddy-usage-error",
6367
+ role: "alert",
6368
+ children: t("row.requestFailedHint", { message: status.refreshError })
6369
+ }),
6370
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountPool, {
6371
+ t,
6372
+ region: activeRegion,
6373
+ ...status.pool === void 0 ? {} : { pool: status.pool },
6374
+ ...settingsScope === void 0 ? {} : { settingsScope },
6375
+ onRescan: () => {
6376
+ rescanAccounts();
6377
+ },
6378
+ rescanning: busy,
6379
+ onSelectAccount: (accountId) => {
6380
+ switchAccount(accountId);
6151
6381
  },
6152
- children: switchingAccount ? t("row.accountsScanning") : t("row.credentialRejectedSwitchAction", { accountName: nameOf(status.recovery.usableAccount.accountName) })
6382
+ selectingAccount: switchingAccount,
6383
+ accountTools,
6384
+ siblingBusy: saving || togglingRegion !== void 0,
6385
+ onBusyChange: setPoolBusy,
6386
+ onSaved: async () => await refreshUsage(activeRegion) !== void 0,
6387
+ onRefresh: () => refreshUsage(activeRegion)
6153
6388
  })
6154
- ]
6155
- }) : null,
6156
- status.creditsError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6157
- className: "dsm-workbuddy-usage-error",
6158
- children: t("row.creditsError", { message: status.creditsError })
6159
- }),
6160
- status.refreshError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6161
- className: "dsm-workbuddy-usage-error",
6162
- role: "alert",
6163
- children: t("row.requestFailedHint", { message: status.refreshError })
6164
- }),
6165
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountPool, {
6166
- t,
6167
- region: activeRegion,
6168
- ...status.pool === void 0 ? {} : { pool: status.pool },
6169
- ...settingsScope === void 0 ? {} : { settingsScope },
6170
- onRescan: () => {
6171
- rescanAccounts();
6172
- },
6173
- rescanning: busy,
6174
- onSelectAccount: (accountId) => {
6175
- switchAccount(accountId);
6176
- },
6177
- selectingAccount: switchingAccount,
6178
- accountTools,
6179
- siblingBusy: saving || togglingRegion !== void 0,
6180
- onBusyChange: setPoolBusy,
6181
- onSaved: async () => await refreshUsage(activeRegion) !== void 0,
6182
- onRefresh: () => refreshUsage(activeRegion)
6183
- }),
6184
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
6389
+ ] }) : null
6390
+ ]
6391
+ }),
6392
+ signedIn ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6393
+ className: "dsm-workbuddy-panel",
6394
+ role: "tabpanel",
6395
+ id: "dsm-workbuddy-panel-models",
6396
+ "aria-labelledby": "dsm-workbuddy-tab-models",
6397
+ hidden: panel !== "models",
6398
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
6185
6399
  className: "dsm-workbuddy-models",
6186
6400
  "aria-label": t("row.modelsTitle"),
6187
6401
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -6430,7 +6644,111 @@ window.__ModuleLoader__.load({
6430
6644
  ]
6431
6645
  })
6432
6646
  })
6433
- ] }) : null,
6647
+ }) : null,
6648
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6649
+ className: "dsm-workbuddy-panel",
6650
+ role: "tabpanel",
6651
+ id: "dsm-workbuddy-panel-diag",
6652
+ "aria-labelledby": "dsm-workbuddy-tab-diag",
6653
+ hidden: !signedIn || panel !== "diag",
6654
+ children: status.status !== "signed-in" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6655
+ className: "dsm-workbuddy-diag",
6656
+ children: [
6657
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6658
+ className: "dsm-workbuddy-diag-row",
6659
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6660
+ className: "dsm-workbuddy-diag-label",
6661
+ children: t("row.diagRegion")
6662
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6663
+ className: "dsm-workbuddy-diag-value",
6664
+ children: status.region === "cn" ? t("row.tabCn") : t("row.tabGlobal")
6665
+ })]
6666
+ }),
6667
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6668
+ className: "dsm-workbuddy-diag-row",
6669
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6670
+ className: "dsm-workbuddy-diag-label",
6671
+ children: t("row.diagDomain")
6672
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6673
+ className: "dsm-workbuddy-diag-value dsm-workbuddy-diag-mono",
6674
+ children: status.domain === void 0 || status.domain === "" ? t("row.modelUnknown") : status.domain
6675
+ })]
6676
+ }),
6677
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6678
+ className: "dsm-workbuddy-diag-row",
6679
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6680
+ className: "dsm-workbuddy-diag-label",
6681
+ children: t("row.diagAccount")
6682
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6683
+ className: "dsm-workbuddy-diag-value",
6684
+ children: nameOf(status.accountName)
6685
+ })]
6686
+ }),
6687
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6688
+ className: "dsm-workbuddy-diag-row",
6689
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6690
+ className: "dsm-workbuddy-diag-label",
6691
+ children: t("row.diagToken")
6692
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6693
+ className: "dsm-workbuddy-diag-value dsm-workbuddy-diag-mono",
6694
+ children: formatDate(status.tokenExpiresAtMs)
6695
+ })]
6696
+ }),
6697
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6698
+ className: "dsm-workbuddy-diag-row",
6699
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6700
+ className: "dsm-workbuddy-diag-label",
6701
+ children: t("row.diagCatalog")
6702
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6703
+ className: "dsm-workbuddy-diag-value",
6704
+ children: formatNumber(status.models.length)
6705
+ })]
6706
+ }),
6707
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6708
+ className: "dsm-workbuddy-diag-row",
6709
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6710
+ className: "dsm-workbuddy-diag-label",
6711
+ children: t("row.diagEnabled")
6712
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6713
+ className: "dsm-workbuddy-diag-value",
6714
+ children: formatNumber(status.enabledModelIds.length)
6715
+ })]
6716
+ }),
6717
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6718
+ className: "dsm-workbuddy-diag-row",
6719
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6720
+ className: "dsm-workbuddy-diag-label",
6721
+ children: t("row.diagPool")
6722
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6723
+ className: "dsm-workbuddy-diag-value",
6724
+ children: status.pool?.enabled === true ? t("row.diagPoolOn", { count: status.pool.accounts.length }) : t("row.diagPoolOff")
6725
+ })]
6726
+ }),
6727
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6728
+ className: "dsm-workbuddy-diag-row",
6729
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6730
+ className: "dsm-workbuddy-diag-label",
6731
+ children: t("row.diagFailures")
6732
+ }), (() => {
6733
+ const failures = [];
6734
+ if (status.credentialRejected === true) failures.push(t("row.credentialRejectedTitle"));
6735
+ if (status.refreshError !== void 0) failures.push(status.refreshError);
6736
+ if (status.creditsError !== void 0) failures.push(status.creditsError);
6737
+ return failures.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6738
+ className: "dsm-workbuddy-diag-value",
6739
+ children: t("row.diagNoFailures")
6740
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6741
+ className: "dsm-workbuddy-diag-value dsm-workbuddy-diag-bad dsm-workbuddy-diag-mono",
6742
+ children: failures.join(" · ")
6743
+ });
6744
+ })()]
6745
+ })
6746
+ ]
6747
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6748
+ className: "dsm-workbuddy-diag-hint",
6749
+ children: t("row.diagHint")
6750
+ })] })
6751
+ }),
6434
6752
  status.status === "signed-out" && loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6435
6753
  className: "dsm-workbuddy-usage-text",
6436
6754
  role: "status",
@@ -7500,6 +7818,22 @@ window.__ModuleLoader__.load({
7500
7818
  "row.accountsWriteFailed": "The account change was not saved: {message} — the active profile's configuration 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 that file and try again (see docs/WINDOWS.md in the plugin repo for the exact path).",
7501
7819
  "row.selectionLostHint": "The saved account no longer exists locally.",
7502
7820
  "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.",
7821
+ "row.panelAccounts": "Accounts",
7822
+ "row.panelModels": "Models",
7823
+ "row.panelDiag": "Diagnostics",
7824
+ "row.panelAria": "Card sections",
7825
+ "row.diagHint": "What this card actually read, so a report can quote it. Nothing here changes a setting.",
7826
+ "row.diagRegion": "Region",
7827
+ "row.diagDomain": "Upstream domain",
7828
+ "row.diagAccount": "Serving account",
7829
+ "row.diagToken": "Sign-in expires",
7830
+ "row.diagCatalog": "Models in the catalog",
7831
+ "row.diagEnabled": "Enabled models",
7832
+ "row.diagPool": "Account pool",
7833
+ "row.diagPoolOn": "on · {count} accounts",
7834
+ "row.diagPoolOff": "off",
7835
+ "row.diagFailures": "Last failure",
7836
+ "row.diagNoFailures": "Nothing reported.",
7503
7837
  "row.modelsTitle": "Models",
7504
7838
  "row.modelsSummary": "{count} enabled",
7505
7839
  "row.modelsDirty": "unsaved changes",
@@ -7732,6 +8066,8 @@ window.__ModuleLoader__.load({
7732
8066
  "row.poolRowRemoved": "{accountName}: hidden from the list. 「Detect accounts again」 re-adds it; the desktop app stays signed in.",
7733
8067
  "row.poolRowRemoving": "Removing the account…",
7734
8068
  "row.poolRowRemoveDesktopOnly": "{accountName}: hidden from the list — this account comes from the desktop app's own sign-in, which is left untouched. 「Detect accounts again」 re-adds it.",
8069
+ "row.imageToolLabel": "Let the model draw images",
8070
+ "row.imageToolHint": "Adds the workbuddy_draw_image tool, which the MODEL may call on its own — about 5.71 credits per image, with no per-call confirmation. Off means the tool is not registered at all.",
7735
8071
  "row.showCreditsCn": "Show credits in the composer toolbar",
7736
8072
  "row.showCreditsGlobal": "Show credits in the composer toolbar",
7737
8073
  "row.showCreditsCnHint": "Show the domestic (WB CN) credit balance in the composer toolbar, but only while this session uses a WB CN model. Click it for per-account balances and a manual refresh (refreshes by itself every 5 minutes).",
@@ -7826,6 +8162,22 @@ window.__ModuleLoader__.load({
7826
8162
  "row.accountsWriteFailed": "账号改动未保存:{message} —— 当前 profile 的配置文件写入失败(Windows 上可能被杀毒软件或同步盘占用)。当前仍是原来保存的选择,请先关闭占用该文件的程序再重试(文件的确切路径见插件仓库的 docs/WINDOWS.md)。",
7827
8163
  "row.selectionLostHint": "已保存的账号在本机已不存在。",
7828
8164
  "row.selectionLostMessage": "该区域保存的账号已不在本机登录列表中(WorkBuddy 更换了登录或清理了备份文件)。本机其他登录仍然可用——请在上方列表中重新选择一个当前账号。重新登录桌面端 App 无法修复此问题。",
8165
+ "row.panelAccounts": "账号",
8166
+ "row.panelModels": "模型",
8167
+ "row.panelDiag": "诊断",
8168
+ "row.panelAria": "卡片分区",
8169
+ "row.diagHint": "这张卡片实际读到了什么,方便贴进问题反馈。这里没有任何会改动设置的控件。",
8170
+ "row.diagRegion": "区域",
8171
+ "row.diagDomain": "上游域名",
8172
+ "row.diagAccount": "当前服务账号",
8173
+ "row.diagToken": "登录有效期至",
8174
+ "row.diagCatalog": "目录中的模型",
8175
+ "row.diagEnabled": "已启用的模型",
8176
+ "row.diagPool": "账号池",
8177
+ "row.diagPoolOn": "开启 · {count} 个账号",
8178
+ "row.diagPoolOff": "关闭",
8179
+ "row.diagFailures": "最近一次失败",
8180
+ "row.diagNoFailures": "没有报错。",
7829
8181
  "row.modelsTitle": "模型",
7830
8182
  "row.modelsSummary": "已启用 {count} 个",
7831
8183
  "row.modelsDirty": "有未保存的修改",
@@ -8058,6 +8410,8 @@ window.__ModuleLoader__.load({
8058
8410
  "row.poolRowRemoved": "{accountName}:已从列表中隐藏。点「重新检测账号」会重新加入,桌面 App 的登录不受影响。",
8059
8411
  "row.poolRowRemoving": "正在删除账号…",
8060
8412
  "row.poolRowRemoveDesktopOnly": "{accountName}:已从列表中隐藏——这个账号来自桌面 App 自己的登录,该登录未被改动。点「重新检测账号」会重新加入。",
8413
+ "row.imageToolLabel": "允许模型自主生图",
8414
+ "row.imageToolHint": "开启后模型可以自己调用 workbuddy_draw_image 画图——约 5.71 积分/张,没有逐次确认。关闭时该工具根本不会注册。",
8061
8415
  "row.showCreditsCn": "在输入框工具栏显示积分读数",
8062
8416
  "row.showCreditsGlobal": "在输入框工具栏显示积分读数",
8063
8417
  "row.showCreditsCnHint": "在对话输入框工具栏显示国内版(WB CN)剩余积分,仅当本会话使用 WB CN 模型时出现。点击可看各账号余额并手动刷新(每 5 分钟也会自动刷新)。",