@sparkelf/dsh-dataops-managed 0.3.14 → 0.3.15

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
@@ -133,6 +133,26 @@ window.__ModuleLoader__.load({
133
133
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
134
134
  }
135
135
  /**
136
+ * A write DataOps refused, carrying the reason it gave.
137
+ *
138
+ * The message is written for the person who set the value, so the panel shows it as-is rather
139
+ * than replacing it with a generic failure notice.
140
+ */
141
+ var WorkspaceLimitsRejection = class extends Error {
142
+ constructor(message) {
143
+ super(message);
144
+ this.name = "WorkspaceLimitsRejection";
145
+ }
146
+ };
147
+ /** Read the reason from a refused response, falling back to the status line. */
148
+ async function refusalReason(response, fallback) {
149
+ try {
150
+ const error = readJson(await response.json())?.error;
151
+ if (typeof error === "string" && error.trim() !== "") return error;
152
+ } catch {}
153
+ return `${fallback} with HTTP ${String(response.status)}`;
154
+ }
155
+ /**
136
156
  * Read the workspace limits the panel shows.
137
157
  * @returns The status, or null when the workspace is not connected to DataOps.
138
158
  */
@@ -155,13 +175,14 @@ window.__ModuleLoader__.load({
155
175
  body: JSON.stringify(limits)
156
176
  });
157
177
  if (response.status === 503) return null;
158
- if (!response.ok) throw new Error(`Workspace limits write failed with HTTP ${String(response.status)}`);
178
+ if (response.status === 400) throw new WorkspaceLimitsRejection(await refusalReason(response, "Workspace limits write refused"));
179
+ if (!response.ok) throw new Error(await refusalReason(response, "Workspace limits write failed"));
159
180
  const body = readJson(await response.json());
160
181
  return body === null ? null : asStatus(body);
161
182
  }
162
183
  //#endregion
163
184
  //#region \0dsh-css:/root/projects/dsh-plugins-plus/packages/dataops-managed/src/client/ManagedDataOpsSection.module.css.mjs
164
- const css$2 = "._2dqzjG_section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:14px;display:flex}._2dqzjG_title{margin:0;font-size:16px;font-weight:500;line-height:24px}._2dqzjG_statusRow{align-items:center;gap:8px;min-height:24px;font-size:14px;line-height:22px;display:flex}._2dqzjG_description{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}._2dqzjG_details{margin:2px 0 0}._2dqzjG_detailRow{grid-template-columns:minmax(140px,2fr) minmax(0,3fr);gap:20px;padding:12px 0;display:grid}._2dqzjG_detailRow+._2dqzjG_detailRow{border-top:1px solid var(--dsw-alias-border-l2)}._2dqzjG_detailRow dt,._2dqzjG_detailRow dd{margin:0;font-size:14px;line-height:22px}._2dqzjG_detailRow dt{color:var(--dsw-alias-label-secondary)}._2dqzjG_detailRow dd{color:var(--dsw-alias-label-primary)}._2dqzjG_brandingRow{border-top:1px solid var(--dsw-alias-border-l2);align-items:center;gap:20px;padding:14px 0 12px;display:flex}._2dqzjG_brandingCopy{flex-direction:column;flex:1;gap:3px;min-width:0;display:flex}._2dqzjG_brandingCopy strong{font-size:14px;font-weight:500;line-height:22px}._2dqzjG_brandingCopy span{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}._2dqzjG_switch{background:var(--dsw-alias-border-l1);cursor:pointer;border:0;border-radius:10px;flex:none;width:34px;height:20px;padding:0;transition:background-color .12s;position:relative}._2dqzjG_switch>span{background:var(--dsw-alias-bg-base);border-radius:50%;width:14px;height:14px;transition:transform .12s;position:absolute;top:3px;left:3px;box-shadow:0 1px 2px #0003}._2dqzjG_switch[data-checked=true]{background:var(--dsw-alias-state-business-primary)}._2dqzjG_switch[data-checked=true]>span{transform:translate(14px)}._2dqzjG_switch:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}@media (prefers-reduced-motion:reduce){._2dqzjG_switch,._2dqzjG_switch>span{transition:none}}._2dqzjG_modelPanel{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:10px;padding:14px 0 12px;display:flex}._2dqzjG_modelTitle{margin:0;font-size:14px;font-weight:500;line-height:22px}._2dqzjG_modelDescription{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}._2dqzjG_modelList{flex-direction:column;gap:8px;padding-top:4px;display:flex}._2dqzjG_modelListTitle{font-size:13px;font-weight:500;line-height:20px}._2dqzjG_modelListDescription{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}._2dqzjG_modelRow{align-items:center;gap:16px;min-height:28px;display:flex}._2dqzjG_modelId{min-width:0;color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;flex:1;font-size:13px;line-height:20px;overflow:hidden}._2dqzjG_modelAction{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:6px;flex:none;padding:5px 12px;font-size:13px;line-height:20px}._2dqzjG_modelAction:hover:not(:disabled){background:var(--dsw-alias-bg-hover)}._2dqzjG_modelAction:disabled,._2dqzjG_switch:disabled{cursor:default;opacity:.5}._2dqzjG_limitsInput{border:1px solid var(--dsw-alias-border-l2);width:6rem;color:var(--dsw-alias-label-primary);background:0 0;border-radius:6px;padding:4px 8px;font-size:13px;line-height:20px}";
185
+ const css$2 = "._2dqzjG_section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}._2dqzjG_title{margin:0;font-size:18px;font-weight:600;line-height:28px}._2dqzjG_status{color:var(--dsw-alias-label-secondary);align-items:center;gap:8px;font-size:13px;line-height:22px;display:flex}._2dqzjG_card{border:.5px solid var(--dsw-alias-settings-card-stroke);border-radius:var(--dsw-radius-xl);background:var(--dsw-alias-settings-card-fill);flex-direction:column;gap:12px;padding:12px 16px;display:flex}._2dqzjG_heading{margin:0;font-size:13px;font-weight:600;line-height:22px}._2dqzjG_row{justify-content:space-between;align-items:center;gap:16px;display:flex}._2dqzjG_copy{flex-direction:column;min-width:0;display:flex}._2dqzjG_label{font-size:13px;line-height:22px}._2dqzjG_meta{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}._2dqzjG_fields{grid-template-columns:repeat(auto-fit,minmax(min(200px,100%),1fr));gap:0 16px;display:grid}._2dqzjG_fields>div{min-width:0}._2dqzjG_fields input{font-variant-numeric:tabular-nums;min-width:0}._2dqzjG_actions{justify-content:flex-end;align-items:center;gap:12px;display:flex}._2dqzjG_refusal{color:var(--dsw-alias-state-error-primary);align-items:flex-start;gap:6px;margin-right:auto;font-size:12px;line-height:18px;display:flex}";
165
186
  const tagId$2 = "@sparkelf/dsh-dataops-managed/ManagedDataOpsSection.module.css";
166
187
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
167
188
  const tag = document.createElement("style");
@@ -171,30 +192,43 @@ window.__ModuleLoader__.load({
171
192
  document.head.appendChild(tag);
172
193
  }
173
194
  var ManagedDataOpsSection_module_css_default = {
174
- "brandingCopy": "_2dqzjG_brandingCopy",
175
- "modelTitle": "_2dqzjG_modelTitle",
176
- "details": "_2dqzjG_details",
177
- "limitsInput": "_2dqzjG_limitsInput",
178
- "modelPanel": "_2dqzjG_modelPanel",
195
+ "refusal": "_2dqzjG_refusal",
196
+ "row": "_2dqzjG_row",
179
197
  "section": "_2dqzjG_section",
180
- "statusRow": "_2dqzjG_statusRow",
181
- "switch": "_2dqzjG_switch",
182
- "description": "_2dqzjG_description",
183
- "modelListDescription": "_2dqzjG_modelListDescription",
184
- "modelRow": "_2dqzjG_modelRow",
198
+ "actions": "_2dqzjG_actions",
199
+ "card": "_2dqzjG_card",
200
+ "heading": "_2dqzjG_heading",
185
201
  "title": "_2dqzjG_title",
186
- "modelList": "_2dqzjG_modelList",
187
- "brandingRow": "_2dqzjG_brandingRow",
188
- "detailRow": "_2dqzjG_detailRow",
189
- "modelAction": "_2dqzjG_modelAction",
190
- "modelId": "_2dqzjG_modelId",
191
- "modelListTitle": "_2dqzjG_modelListTitle",
192
- "modelDescription": "_2dqzjG_modelDescription"
202
+ "copy": "_2dqzjG_copy",
203
+ "label": "_2dqzjG_label",
204
+ "status": "_2dqzjG_status",
205
+ "meta": "_2dqzjG_meta",
206
+ "fields": "_2dqzjG_fields"
193
207
  };
194
208
  //#endregion
195
209
  //#region src/client/ManagedDataOpsSection.tsx
210
+ /** The panel's unit, so a stored byte count survives a round trip through the input. */
211
+ const BYTES_PER_MIB = 1048576;
212
+ /** Render a byte ceiling as the whole MiB the input shows. */
213
+ function toMib(bytes) {
214
+ return String(Math.round(bytes / BYTES_PER_MIB * 100) / 100);
215
+ }
216
+ /** Parse a MiB draft into a byte ceiling, or null when it is not a positive count. */
217
+ function toBytes(text) {
218
+ if (text.trim() === "") return null;
219
+ const mib = Number(text);
220
+ if (!Number.isFinite(mib) || mib <= 0) return null;
221
+ const bytes = Math.round(mib * BYTES_PER_MIB);
222
+ return Number.isSafeInteger(bytes) && bytes > 0 ? bytes : null;
223
+ }
224
+ /** Format a stored publication time for the row, or nothing when it never published. */
225
+ function publishedText(publishedAt) {
226
+ const at = new Date(publishedAt);
227
+ return Number.isNaN(at.getTime()) ? null : at.toLocaleString();
228
+ }
196
229
  /**
197
- * Explain the DataOps-managed JWT and permission owner, and share model configuration.
230
+ * The DataOps settings this workspace owns: its connection state, its branding, its file ceilings,
231
+ * and the default configuration it publishes to other workspaces.
198
232
  * @param props - Settings slot injection values.
199
233
  * @returns The localized managed DataOps section, or nothing before injection.
200
234
  */
@@ -210,165 +244,81 @@ window.__ModuleLoader__.load({
210
244
  children: t("title")
211
245
  }),
212
246
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
213
- className: ManagedDataOpsSection_module_css_default.statusRow,
214
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "done" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("managed") })]
247
+ className: ManagedDataOpsSection_module_css_default.status,
248
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "done" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("managed") })]
215
249
  }),
216
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
217
- className: ManagedDataOpsSection_module_css_default.description,
218
- children: t("description")
250
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BrandingRow, {
251
+ t,
252
+ enabled: branding.enabled
219
253
  }),
220
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
221
- className: ManagedDataOpsSection_module_css_default.brandingRow,
222
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
223
- className: ManagedDataOpsSection_module_css_default.brandingCopy,
224
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("brandingTitle") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("brandingDescription") })]
225
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
226
- type: "button",
227
- className: ManagedDataOpsSection_module_css_default.switch,
228
- role: "switch",
229
- "aria-label": t("brandingToggle"),
230
- "aria-checked": branding.enabled,
231
- "data-checked": branding.enabled,
232
- onClick: () => {
233
- setWanxiangBrandEnabled(!branding.enabled);
234
- },
235
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {})
236
- })]
237
- }),
238
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PublishDefaults, { t }),
239
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceLimitsPanel, { t }),
240
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
241
- className: ManagedDataOpsSection_module_css_default.details,
242
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
243
- className: ManagedDataOpsSection_module_css_default.detailRow,
244
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("identityLabel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: t("identityValue") })]
245
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
246
- className: ManagedDataOpsSection_module_css_default.detailRow,
247
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("toolsLabel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: t("toolsValue") })]
248
- })]
249
- })
254
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LimitsCard, { t }),
255
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultsCard, { t })
250
256
  ]
251
257
  });
252
258
  }
253
- /**
254
- * Publish this workspace's configuration as the default other users start from.
255
- *
256
- * One action rather than a set of controls: the configuration is whatever this workspace already
257
- * holds -- the models, the permission default, the general settings -- and the button copies it to
258
- * DataOps. Every other user's workspace takes that copy as its starting values and may then change
259
- * anything, at which point their own version wins and this button never reaches it again.
260
- * @param props - Translate function from the section.
261
- * @returns The publish row.
262
- */
263
- function PublishDefaults({ t }) {
264
- const [state, setState] = (0, react.useState)({ kind: "loading" });
265
- const [busy, setBusy] = (0, react.useState)(false);
266
- const refresh = (0, react.useCallback)(async () => {
267
- try {
268
- const status = await fetchModelSyncStatus();
269
- setState(status === null ? { kind: "unavailable" } : {
270
- kind: "ready",
271
- status
272
- });
273
- } catch {
274
- setState({ kind: "failed" });
275
- }
276
- }, []);
277
- (0, react.useEffect)(() => {
278
- refresh();
279
- }, [refresh]);
280
- const publish = (0, react.useCallback)(async () => {
281
- setBusy(true);
282
- try {
283
- const status = await postModelSyncAction({ action: "publish-defaults" });
284
- if (status !== null) setState({
285
- kind: "ready",
286
- status
287
- });
288
- } catch {
289
- setState({ kind: "failed" });
290
- } finally {
291
- setBusy(false);
292
- }
293
- }, []);
294
- if (state.kind === "loading") return null;
295
- if (state.kind === "unavailable" || state.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
296
- className: ManagedDataOpsSection_module_css_default.modelPanel,
297
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
298
- className: ManagedDataOpsSection_module_css_default.modelTitle,
299
- children: t("defaultsTitle")
300
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
301
- className: ManagedDataOpsSection_module_css_default.modelDescription,
302
- role: state.kind === "failed" ? "alert" : void 0,
303
- children: state.kind === "failed" ? t("loadFailed") : t("modelsUnavailable")
304
- })]
259
+ /** The branding toggle as one settings row. */
260
+ function BrandingRow({ t, enabled }) {
261
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
262
+ className: ManagedDataOpsSection_module_css_default.card,
263
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
264
+ className: ManagedDataOpsSection_module_css_default.row,
265
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
266
+ className: ManagedDataOpsSection_module_css_default.label,
267
+ children: t("brandingTitle")
268
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
269
+ checked: enabled,
270
+ label: t("brandingToggle"),
271
+ onChange: (next) => {
272
+ setWanxiangBrandEnabled(next);
273
+ }
274
+ })]
275
+ })
305
276
  });
306
- const { status } = state;
307
- if (!status.canPublish) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
308
- className: ManagedDataOpsSection_module_css_default.modelPanel,
277
+ }
278
+ /** The one row a card renders while it cannot offer its controls. */
279
+ function UnavailableCard({ title, t, state }) {
280
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
281
+ className: ManagedDataOpsSection_module_css_default.card,
309
282
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
310
- className: ManagedDataOpsSection_module_css_default.modelTitle,
311
- children: t("defaultsTitle")
312
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
313
- className: ManagedDataOpsSection_module_css_default.modelDescription,
314
- children: t("defaultsNotAdmin")
283
+ className: ManagedDataOpsSection_module_css_default.heading,
284
+ children: title
285
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
286
+ className: ManagedDataOpsSection_module_css_default.status,
287
+ role: state === "failed" ? "alert" : void 0,
288
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: state === "failed" ? "error" : "idle" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: state === "failed" ? t("loadFailed") : t("unavailable") })]
315
289
  })]
316
290
  });
317
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
318
- className: ManagedDataOpsSection_module_css_default.modelPanel,
319
- children: [
320
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
321
- className: ManagedDataOpsSection_module_css_default.modelTitle,
322
- children: t("defaultsTitle")
323
- }),
324
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
325
- className: ManagedDataOpsSection_module_css_default.modelDescription,
326
- children: t("defaultsDescription")
327
- }),
328
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
329
- className: ManagedDataOpsSection_module_css_default.brandingRow,
330
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
331
- className: ManagedDataOpsSection_module_css_default.brandingCopy,
332
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("defaultsActionTitle") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: status.publishedAt === null ? t("defaultsNeverPublished") : t("defaultsPublishedAt") + " " + status.publishedAt })]
333
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
334
- type: "button",
335
- className: ManagedDataOpsSection_module_css_default.modelAction,
336
- disabled: busy,
337
- onClick: () => {
338
- publish();
339
- },
340
- children: busy ? t("defaultsPublishing") : t("defaultsPublish")
341
- })]
342
- })
343
- ]
344
- });
345
- }
346
- /** The panel's unit, so a stored byte count survives a round trip through the input. */
347
- const BYTES_PER_MIB = 1048576;
348
- /** Render a byte ceiling as the whole MiB the input shows. */
349
- function toMib(bytes) {
350
- return String(Math.round(bytes / BYTES_PER_MIB * 100) / 100);
351
291
  }
352
292
  /**
353
- * Read and change the file-size ceilings DataOps enforces on this workspace.
293
+ * The file ceilings DataOps enforces on this workspace.
354
294
  *
355
295
  * The values live in DataOps and are read through the plugin, which holds the JWT; the panel only
356
296
  * ever holds what the administrator is allowed to see.
357
297
  * @param props - Translate function from the section.
358
- * @returns The limits panel.
298
+ * @returns The limits card.
359
299
  */
360
- function WorkspaceLimitsPanel({ t }) {
300
+ function LimitsCard({ t }) {
361
301
  const [state, setState] = (0, react.useState)({ kind: "loading" });
362
302
  const [fileReadMib, setFileReadMib] = (0, react.useState)("");
363
303
  const [uploadMib, setUploadMib] = (0, react.useState)("");
304
+ /** The ceilings the draft started from, so Save stays off until one of them changes. */
305
+ const [saved, setSaved] = (0, react.useState)("");
306
+ /** Why DataOps refused the last save, kept beside the draft it refused. */
307
+ const [refusal, setRefusal] = (0, react.useState)(null);
364
308
  const [busy, setBusy] = (0, react.useState)(false);
309
+ const fileReadId = (0, react.useId)();
310
+ const uploadId = (0, react.useId)();
365
311
  const apply = (0, react.useCallback)((status) => {
366
312
  setState({
367
313
  kind: "ready",
368
314
  status
369
315
  });
370
- setFileReadMib(toMib(status.limits.fileReadMaxBytes));
371
- setUploadMib(toMib(status.limits.uploadMaxBytes));
316
+ const file = toMib(status.limits.fileReadMaxBytes);
317
+ const upload = toMib(status.limits.uploadMaxBytes);
318
+ setFileReadMib(file);
319
+ setUploadMib(upload);
320
+ setSaved(file + "|" + upload);
321
+ setRefusal(null);
372
322
  }, []);
373
323
  const refresh = (0, react.useCallback)(async () => {
374
324
  try {
@@ -382,93 +332,92 @@ window.__ModuleLoader__.load({
382
332
  (0, react.useEffect)(() => {
383
333
  refresh();
384
334
  }, [refresh]);
335
+ const fileReadBytes = toBytes(fileReadMib);
336
+ const uploadBytes = toBytes(uploadMib);
337
+ const writable = state.kind === "ready" && state.status.canWrite;
338
+ const dirty = fileReadMib + "|" + uploadMib !== saved;
339
+ const ready = fileReadBytes !== null && uploadBytes !== null && dirty;
340
+ /** Edit one ceiling, dropping a refusal that no longer describes what is in the field. */
341
+ const edit = (0, react.useCallback)((set) => (text) => {
342
+ set(text);
343
+ setRefusal(null);
344
+ }, []);
385
345
  const submit = (0, react.useCallback)(async () => {
386
- const fileReadMaxBytes = Math.round(Number(fileReadMib) * BYTES_PER_MIB);
387
- const uploadMaxBytes = Math.round(Number(uploadMib) * BYTES_PER_MIB);
388
- if (!Number.isSafeInteger(fileReadMaxBytes) || fileReadMaxBytes <= 0) return;
389
- if (!Number.isSafeInteger(uploadMaxBytes) || uploadMaxBytes <= 0) return;
346
+ if (fileReadBytes === null || uploadBytes === null) return;
390
347
  setBusy(true);
348
+ setRefusal(null);
391
349
  try {
392
350
  const status = await saveWorkspaceLimits({
393
- fileReadMaxBytes,
394
- uploadMaxBytes
351
+ fileReadMaxBytes: fileReadBytes,
352
+ uploadMaxBytes: uploadBytes
395
353
  });
396
354
  if (status === null) setState({ kind: "unavailable" });
397
355
  else apply(status);
398
- } catch {
399
- setState({ kind: "failed" });
356
+ } catch (error) {
357
+ if (error instanceof WorkspaceLimitsRejection) setRefusal(error.message);
358
+ else setState({ kind: "failed" });
400
359
  } finally {
401
360
  setBusy(false);
402
361
  }
403
362
  }, [
404
- fileReadMib,
405
- uploadMib,
363
+ fileReadBytes,
364
+ uploadBytes,
406
365
  apply
407
366
  ]);
408
367
  if (state.kind === "loading") return null;
409
- if (state.kind !== "ready") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
410
- className: ManagedDataOpsSection_module_css_default.modelPanel,
411
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
412
- className: ManagedDataOpsSection_module_css_default.modelTitle,
413
- children: t("limitsTitle")
414
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
415
- className: ManagedDataOpsSection_module_css_default.modelDescription,
416
- role: state.kind === "failed" ? "alert" : void 0,
417
- children: state.kind === "failed" ? t("loadFailed") : t("limitsUnavailable")
418
- })]
368
+ if (state.kind !== "ready") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UnavailableCard, {
369
+ title: t("limitsTitle"),
370
+ t,
371
+ state: state.kind
419
372
  });
420
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
421
- className: ManagedDataOpsSection_module_css_default.modelPanel,
373
+ const disabled = busy || !writable;
374
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
375
+ className: ManagedDataOpsSection_module_css_default.card,
422
376
  children: [
423
377
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
424
- className: ManagedDataOpsSection_module_css_default.modelTitle,
378
+ className: ManagedDataOpsSection_module_css_default.heading,
425
379
  children: t("limitsTitle")
426
380
  }),
427
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
428
- className: ManagedDataOpsSection_module_css_default.modelDescription,
429
- children: t("limitsDescription")
430
- }),
431
381
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
432
- className: ManagedDataOpsSection_module_css_default.details,
433
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
434
- className: ManagedDataOpsSection_module_css_default.detailRow,
435
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("limitsFileReadLabel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
436
- className: ManagedDataOpsSection_module_css_default.limitsInput,
437
- type: "number",
438
- min: "1",
439
- step: "1",
440
- "aria-label": t("limitsFileReadLabel"),
441
- value: fileReadMib,
442
- disabled: busy || !state.status.canWrite,
443
- onChange: (event) => {
444
- setFileReadMib(event.target.value);
445
- }
446
- })]
447
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
448
- className: ManagedDataOpsSection_module_css_default.detailRow,
449
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("limitsUploadLabel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
450
- className: ManagedDataOpsSection_module_css_default.limitsInput,
451
- type: "number",
452
- min: "1",
453
- step: "1",
454
- "aria-label": t("limitsUploadLabel"),
455
- value: uploadMib,
456
- disabled: busy || !state.status.canWrite,
457
- onChange: (event) => {
458
- setUploadMib(event.target.value);
459
- }
460
- })]
461
- })]
382
+ className: ManagedDataOpsSection_module_css_default.fields,
383
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SettingsValueField, {
384
+ id: fileReadId,
385
+ label: t("limitsFileReadLabel"),
386
+ text: fileReadMib,
387
+ numeric: true,
388
+ invalid: fileReadBytes === null,
389
+ overridden: false,
390
+ overriddenLabel: "",
391
+ resetLabel: "",
392
+ invalidLabel: t("limitsInvalid"),
393
+ disabled,
394
+ onEdit: edit(setFileReadMib),
395
+ onReset: () => void 0
396
+ }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SettingsValueField, {
397
+ id: uploadId,
398
+ label: t("limitsUploadLabel"),
399
+ text: uploadMib,
400
+ numeric: true,
401
+ invalid: uploadBytes === null,
402
+ overridden: false,
403
+ overriddenLabel: "",
404
+ resetLabel: "",
405
+ invalidLabel: t("limitsInvalid"),
406
+ disabled,
407
+ onEdit: edit(setUploadMib),
408
+ onReset: () => void 0
409
+ }) })]
462
410
  }),
463
411
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
464
- className: ManagedDataOpsSection_module_css_default.brandingRow,
465
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
466
- className: ManagedDataOpsSection_module_css_default.modelDescription,
467
- children: t("limitsUnit")
468
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
469
- type: "button",
470
- className: ManagedDataOpsSection_module_css_default.modelAction,
471
- disabled: busy || !state.status.canWrite,
412
+ className: ManagedDataOpsSection_module_css_default.actions,
413
+ children: [refusal === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
414
+ className: ManagedDataOpsSection_module_css_default.refusal,
415
+ role: "alert",
416
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "error" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: refusal })]
417
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
418
+ variant: "primary",
419
+ size: "sm",
420
+ disabled: disabled || !ready,
472
421
  onClick: () => {
473
422
  submit();
474
423
  },
@@ -478,6 +427,81 @@ window.__ModuleLoader__.load({
478
427
  ]
479
428
  });
480
429
  }
430
+ /**
431
+ * Publish this workspace's configuration as the default other users start from.
432
+ *
433
+ * One action rather than a set of controls: the configuration is whatever this workspace already
434
+ * holds, and the button copies it to DataOps.
435
+ * @param props - Translate function from the section.
436
+ * @returns The default-configuration card.
437
+ */
438
+ function DefaultsCard({ t }) {
439
+ const [state, setState] = (0, react.useState)({ kind: "loading" });
440
+ const [busy, setBusy] = (0, react.useState)(false);
441
+ const refresh = (0, react.useCallback)(async () => {
442
+ try {
443
+ const status = await fetchModelSyncStatus();
444
+ setState(status === null ? { kind: "unavailable" } : {
445
+ kind: "ready",
446
+ status
447
+ });
448
+ } catch {
449
+ setState({ kind: "failed" });
450
+ }
451
+ }, []);
452
+ (0, react.useEffect)(() => {
453
+ refresh();
454
+ }, [refresh]);
455
+ const publish = (0, react.useCallback)(async () => {
456
+ setBusy(true);
457
+ try {
458
+ const status = await postModelSyncAction({ action: "publish-defaults" });
459
+ if (status === null) setState({ kind: "unavailable" });
460
+ else setState({
461
+ kind: "ready",
462
+ status
463
+ });
464
+ } catch {
465
+ setState({ kind: "failed" });
466
+ } finally {
467
+ setBusy(false);
468
+ }
469
+ }, []);
470
+ if (state.kind === "loading") return null;
471
+ if (state.kind !== "ready") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UnavailableCard, {
472
+ title: t("defaultsTitle"),
473
+ t,
474
+ state: state.kind
475
+ });
476
+ const published = state.status.publishedAt === null ? null : publishedText(state.status.publishedAt);
477
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
478
+ className: ManagedDataOpsSection_module_css_default.card,
479
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
480
+ className: ManagedDataOpsSection_module_css_default.row,
481
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
482
+ className: ManagedDataOpsSection_module_css_default.copy,
483
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
484
+ className: ManagedDataOpsSection_module_css_default.label,
485
+ children: t("defaultsTitle")
486
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
487
+ className: ManagedDataOpsSection_module_css_default.meta,
488
+ children: published === null ? t("defaultsNeverPublished") : t("defaultsPublishedAt") + " · " + published
489
+ })]
490
+ }), state.status.canPublish ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
491
+ variant: "outline",
492
+ size: "sm",
493
+ disabled: busy,
494
+ onClick: () => {
495
+ publish();
496
+ },
497
+ children: busy ? t("defaultsPublishing") : t("defaultsPublish")
498
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
499
+ className: ManagedDataOpsSection_module_css_default.meta,
500
+ children: t("defaultsNotAdmin")
501
+ })]
502
+ })
503
+ });
504
+ }
481
505
  //#endregion
482
506
  //#region src/client/skill-plaza-client.ts
483
507
  /** Same-origin skill plaza calls from the DataOps skill center section. */
@@ -552,28 +576,28 @@ window.__ModuleLoader__.load({
552
576
  document.head.appendChild(tag);
553
577
  }
554
578
  var SkillPlazaSection_module_css_default = {
555
- "search": "_9FdZvq_search",
556
- "titleIcon": "_9FdZvq_titleIcon",
557
- "cardMarks": "_9FdZvq_cardMarks",
558
- "emptyText": "_9FdZvq_emptyText",
559
- "cardHead": "_9FdZvq_cardHead",
560
- "emptyIcon": "_9FdZvq_emptyIcon",
561
579
  "toolbar": "_9FdZvq_toolbar",
562
- "head": "_9FdZvq_head",
563
- "intro": "_9FdZvq_intro",
564
- "section": "_9FdZvq_section",
565
- "title": "_9FdZvq_title",
566
580
  "card": "_9FdZvq_card",
581
+ "mine": "_9FdZvq_mine",
582
+ "cardMarks": "_9FdZvq_cardMarks",
583
+ "emptyText": "_9FdZvq_emptyText",
567
584
  "tagBar": "_9FdZvq_tagBar",
568
- "cards": "_9FdZvq_cards",
569
- "cardMain": "_9FdZvq_cardMain",
585
+ "intro": "_9FdZvq_intro",
570
586
  "cardTitle": "_9FdZvq_cardTitle",
571
- "mine": "_9FdZvq_mine",
587
+ "emptyIcon": "_9FdZvq_emptyIcon",
588
+ "search": "_9FdZvq_search",
572
589
  "notice": "_9FdZvq_notice",
573
- "empty": "_9FdZvq_empty",
590
+ "cardMain": "_9FdZvq_cardMain",
574
591
  "cardEnd": "_9FdZvq_cardEnd",
592
+ "title": "_9FdZvq_title",
575
593
  "cardIcon": "_9FdZvq_cardIcon",
576
- "cardDesc": "_9FdZvq_cardDesc"
594
+ "cardHead": "_9FdZvq_cardHead",
595
+ "cards": "_9FdZvq_cards",
596
+ "cardDesc": "_9FdZvq_cardDesc",
597
+ "titleIcon": "_9FdZvq_titleIcon",
598
+ "head": "_9FdZvq_head",
599
+ "empty": "_9FdZvq_empty",
600
+ "section": "_9FdZvq_section"
577
601
  };
578
602
  //#endregion
579
603
  //#region src/client/SkillPlazaSection.tsx
@@ -825,18 +849,18 @@ window.__ModuleLoader__.load({
825
849
  document.head.appendChild(tag);
826
850
  }
827
851
  var WanxiangBrand_module_css_default = {
828
- "animatedMark": "s0HPxG_animatedMark",
829
- "unitRight": "s0HPxG_unitRight",
830
- "sidebarName": "s0HPxG_sidebarName",
831
- "unit-core": "s0HPxG_unit-core",
832
- "unitCore": "s0HPxG_unitCore",
833
- "mark": "s0HPxG_mark",
834
- "harness": "s0HPxG_harness",
852
+ "unit-left": "s0HPxG_unit-left",
835
853
  "unit-top": "s0HPxG_unit-top",
854
+ "mark": "s0HPxG_mark",
855
+ "unit-core": "s0HPxG_unit-core",
856
+ "sidebarName": "s0HPxG_sidebarName",
836
857
  "unit-right": "s0HPxG_unit-right",
858
+ "animatedMark": "s0HPxG_animatedMark",
837
859
  "unitLeft": "s0HPxG_unitLeft",
860
+ "unitCore": "s0HPxG_unitCore",
861
+ "unitRight": "s0HPxG_unitRight",
838
862
  "unitTop": "s0HPxG_unitTop",
839
- "unit-left": "s0HPxG_unit-left"
863
+ "harness": "s0HPxG_harness"
840
864
  };
841
865
  //#endregion
842
866
  //#region src/client/WanxiangBrand.tsx
@@ -1112,27 +1136,23 @@ window.__ModuleLoader__.load({
1112
1136
  const en = {
1113
1137
  nav: "DataOps",
1114
1138
  title: "DataOps",
1115
- managed: "Managed by this DataOps workspace",
1116
- description: "DSH uses your current DataOps session JWT. DataOps applies its role and resource permissions to every tool request.",
1117
- identityLabel: "Identity and permissions",
1118
- identityValue: "Current DataOps JWT",
1119
- toolsLabel: "DataOps tools",
1120
- toolsValue: "Authorized by DataOps on every request",
1121
- brandingTitle: "Apply Wanxiang branding",
1122
- brandingDescription: "Use the Wanxiang Data Platform identity in navigation, the New Session welcome view, browser title, and favicon.",
1123
- brandingToggle: "Apply Wanxiang branding",
1124
- modelsTitle: "Model configuration",
1125
- modelsDescription: "Share the models configured here with other users, and choose which of them they may use.",
1126
- modelsUnavailable: "Model configuration is unavailable until this workspace is connected to DataOps.",
1139
+ managed: "Connected to DataOps",
1140
+ unavailable: "DataOps is not connected.",
1141
+ loadFailed: "Loading failed. Try again.",
1142
+ brandingTitle: "Wanxiang branding",
1143
+ brandingToggle: "Wanxiang branding",
1144
+ limitsTitle: "File size limits",
1145
+ limitsFileReadLabel: "Largest file to read (MiB)",
1146
+ limitsUploadLabel: "Largest file to upload (MiB)",
1147
+ limitsInvalid: "Enter a size in MiB.",
1148
+ limitsSave: "Save",
1149
+ limitsSaving: "Saving…",
1127
1150
  defaultsTitle: "Default configuration",
1128
- defaultsDescription: "Publish the configuration this workspace holds — models, permissions, and general settings — as the values other users start from. Their workspace takes that copy once and may then change anything, and a change they make is never overwritten.",
1129
- defaultsActionTitle: "Publish as the default",
1130
- defaultsNotAdmin: "Only an administrator may publish the default configuration.",
1131
- defaultsNeverPublished: "Not published yet.",
1132
- defaultsPublishedAt: "Last published:",
1133
- defaultsPublish: "Publish default configuration",
1151
+ defaultsPublish: "Publish",
1134
1152
  defaultsPublishing: "Publishing…",
1135
- loadFailed: "The model configuration could not be loaded. Try again.",
1153
+ defaultsPublishedAt: "Last published",
1154
+ defaultsNeverPublished: "Not published yet",
1155
+ defaultsNotAdmin: "Administrators only",
1136
1156
  plazaTitle: "Skill plaza",
1137
1157
  plazaIntro: "Skills other users shared. Install one to use it in this workspace, or share a skill of your own.",
1138
1158
  plazaSearch: "Search skills",
@@ -1148,41 +1168,29 @@ window.__ModuleLoader__.load({
1148
1168
  plazaLoadFailed: "The skill plaza could not be loaded. Try again.",
1149
1169
  plazaActionFailed: "The action could not be completed. Try again.",
1150
1170
  plazaRetry: "Try again",
1151
- plazaRefresh: "Refresh",
1152
- limitsTitle: "Workspace limits",
1153
- limitsDescription: "The largest files this workspace may read and upload. Set by an administrator in DataOps.",
1154
- limitsUnavailable: "Workspace limits are available once this workspace is connected to DataOps.",
1155
- limitsFileReadLabel: "Largest file to read",
1156
- limitsUploadLabel: "Largest file to upload",
1157
- limitsUnit: "Units are MiB.",
1158
- limitsSave: "Save limits",
1159
- limitsSaving: "Saving…"
1171
+ plazaRefresh: "Refresh"
1160
1172
  };
1161
1173
  /** Chinese managed DataOps Settings copy. */
1162
1174
  const zh = {
1163
1175
  nav: "DataOps",
1164
1176
  title: "DataOps",
1165
- managed: "由当前 DataOps 工作区托管",
1166
- description: "DSH 使用当前 DataOps 会话 JWT;DataOps 对每次工具请求应用角色与资源权限。",
1167
- identityLabel: "身份与权限",
1168
- identityValue: "当前 DataOps JWT",
1169
- toolsLabel: "DataOps 工具",
1170
- toolsValue: "每次请求均由 DataOps 授权",
1177
+ managed: "已连接 DataOps",
1178
+ unavailable: "未连接 DataOps。",
1179
+ loadFailed: "加载失败,请重试。",
1171
1180
  brandingTitle: "应用万相品牌",
1172
- brandingDescription: "在导航、新会话欢迎页、浏览器标题和图标中使用万相数据平台品牌。",
1173
1181
  brandingToggle: "应用万相品牌",
1174
- modelsTitle: "模型配置共享",
1175
- modelsDescription: "把这里配置的模型共享给其他用户,并决定他们可以使用其中哪些。",
1176
- modelsUnavailable: "工作区接入 DataOps 后即可使用模型配置共享。",
1182
+ limitsTitle: "文件大小限制",
1183
+ limitsFileReadLabel: "最大读取文件(MiB)",
1184
+ limitsUploadLabel: "最大上传文件(MiB)",
1185
+ limitsInvalid: "请输入以 MiB 为单位的大小。",
1186
+ limitsSave: "保存",
1187
+ limitsSaving: "保存中…",
1177
1188
  defaultsTitle: "默认配置",
1178
- defaultsDescription: "把本工作区当前的配置(模型、权限、通用设置)发布为其他用户的初始值。对方工作区只取用一次,之后可以随意修改,改过的内容不会被覆盖。",
1179
- defaultsActionTitle: "发布为默认值",
1180
- defaultsNotAdmin: "只有管理员可以发布默认配置。",
1181
- defaultsNeverPublished: "尚未发布。",
1182
- defaultsPublishedAt: "上次发布:",
1183
- defaultsPublish: "发布默认配置",
1189
+ defaultsPublish: "发布",
1184
1190
  defaultsPublishing: "发布中…",
1185
- loadFailed: "模型配置加载失败,请重试。",
1191
+ defaultsPublishedAt: "上次发布",
1192
+ defaultsNeverPublished: "尚未发布",
1193
+ defaultsNotAdmin: "仅管理员可发布",
1186
1194
  plazaTitle: "技能广场",
1187
1195
  plazaIntro: "其他用户共享的技能。装载后即可在本工作区使用,也可以把自己的技能共享出去。",
1188
1196
  plazaSearch: "搜索技能",
@@ -1198,15 +1206,7 @@ window.__ModuleLoader__.load({
1198
1206
  plazaLoadFailed: "技能广场加载失败,请重试。",
1199
1207
  plazaActionFailed: "操作未完成,请重试。",
1200
1208
  plazaRetry: "重试",
1201
- plazaRefresh: "刷新",
1202
- limitsTitle: "工作区限制",
1203
- limitsDescription: "本工作区可读取与上传的最大文件。由 DataOps 管理员设置。",
1204
- limitsUnavailable: "工作区连接到 DataOps 后可用。",
1205
- limitsFileReadLabel: "最大读取文件",
1206
- limitsUploadLabel: "最大上传文件",
1207
- limitsUnit: "单位:MiB。",
1208
- limitsSave: "保存限制",
1209
- limitsSaving: "保存中…"
1209
+ plazaRefresh: "刷新"
1210
1210
  };
1211
1211
  //#endregion
1212
1212
  //#region src/client/index.ts