@sparkelf/dsh-dataops-managed 0.3.19 → 0.3.21

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
@@ -4,6 +4,26 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ //#region \0rolldown/runtime.js
8
+ var __defProp = Object.defineProperty;
9
+ var __esmMin = (fn, res, err) => () => {
10
+ if (err) throw err[0];
11
+ try {
12
+ return fn && (res = fn(fn = 0)), res;
13
+ } catch (e) {
14
+ throw err = [e], e;
15
+ }
16
+ };
17
+ var __exportAll = (all, no_symbols) => {
18
+ let target = {};
19
+ for (var name in all) __defProp(target, name, {
20
+ get: all[name],
21
+ enumerable: true
22
+ });
23
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
24
+ return target;
25
+ };
26
+ //#endregion
7
27
  let react = require("react");
8
28
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
29
  let react_jsx_runtime = require("react/jsx-runtime");
@@ -20,14 +40,14 @@ window.__ModuleLoader__.load({
20
40
  }
21
41
  }
22
42
  let preference = initialPreference();
23
- const listeners = /* @__PURE__ */ new Set();
43
+ const listeners$1 = /* @__PURE__ */ new Set();
24
44
  /** Observable preference compatible with React's useSyncExternalStore. */
25
45
  const wanxiangBrandPreference = {
26
46
  getSnapshot: () => preference,
27
47
  subscribe: (listener) => {
28
- listeners.add(listener);
48
+ listeners$1.add(listener);
29
49
  return () => {
30
- listeners.delete(listener);
50
+ listeners$1.delete(listener);
31
51
  };
32
52
  }
33
53
  };
@@ -38,7 +58,7 @@ window.__ModuleLoader__.load({
38
58
  if (typeof localStorage !== "undefined") try {
39
59
  localStorage.setItem(WANXIANG_BRAND_PREFERENCE_KEY, JSON.stringify(preference));
40
60
  } catch {}
41
- for (const listener of listeners) listener();
61
+ for (const listener of listeners$1) listener();
42
62
  }
43
63
  //#endregion
44
64
  //#region src/client/model-sync-client.ts
@@ -46,7 +66,7 @@ window.__ModuleLoader__.load({
46
66
  /** Route the plugin's Host half serves for model sharing. */
47
67
  const MODEL_SYNC_PATH = "/integrations/dataops/model-sync";
48
68
  /** Narrow a response body to the status shape, rejecting anything else. */
49
- function asStatus$1(value) {
69
+ function asStatus$2(value) {
50
70
  if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
51
71
  const record = value;
52
72
  if (record.followState !== "following" && record.followState !== "detached") return null;
@@ -86,7 +106,7 @@ window.__ModuleLoader__.load({
86
106
  const response = await fetch(MODEL_SYNC_PATH, { headers: { accept: "application/json" } });
87
107
  if (response.status === 503) return null;
88
108
  if (!response.ok) throw new Error("model sync status failed with HTTP " + String(response.status));
89
- return asStatus$1(await response.json());
109
+ return asStatus$2(await response.json());
90
110
  }
91
111
  /**
92
112
  * Change what this workspace shares.
@@ -104,7 +124,7 @@ window.__ModuleLoader__.load({
104
124
  });
105
125
  if (response.status === 503) return null;
106
126
  if (!response.ok) throw new Error("model sync action failed with HTTP " + String(response.status));
107
- return asStatus$1(await response.json());
127
+ return asStatus$2(await response.json());
108
128
  }
109
129
  //#endregion
110
130
  //#region src/client/workspace-limits-client.ts
@@ -112,7 +132,7 @@ window.__ModuleLoader__.load({
112
132
  /** Route the plugin's Host half serves for workspace limits. */
113
133
  const WORKSPACE_LIMITS_PATH = "/integrations/dataops/workspace-limits";
114
134
  /** Narrow a response body to the status, rejecting anything else. */
115
- function asStatus(value) {
135
+ function asStatus$1(value) {
116
136
  if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
117
137
  const record = value;
118
138
  const limits = record.limits;
@@ -129,7 +149,7 @@ window.__ModuleLoader__.load({
129
149
  readOnlyReason: typeof record.readOnlyReason === "string" ? record.readOnlyReason : null
130
150
  };
131
151
  }
132
- function readJson(value) {
152
+ function readJson$1(value) {
133
153
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
134
154
  }
135
155
  /**
@@ -147,7 +167,7 @@ window.__ModuleLoader__.load({
147
167
  /** Read the reason from a refused response, falling back to the status line. */
148
168
  async function refusalReason$1(response, fallback) {
149
169
  try {
150
- const error = readJson(await response.json())?.error;
170
+ const error = readJson$1(await response.json())?.error;
151
171
  if (typeof error === "string" && error.trim() !== "") return error;
152
172
  } catch {}
153
173
  return `${fallback} with HTTP ${String(response.status)}`;
@@ -160,7 +180,7 @@ window.__ModuleLoader__.load({
160
180
  const response = await fetch(WORKSPACE_LIMITS_PATH, { credentials: "same-origin" });
161
181
  if (response.status === 503) return null;
162
182
  if (!response.ok) throw new Error(`Workspace limits read failed with HTTP ${String(response.status)}`);
163
- return asStatus(await response.json());
183
+ return asStatus$1(await response.json());
164
184
  }
165
185
  /**
166
186
  * Store new workspace limits.
@@ -177,8 +197,8 @@ window.__ModuleLoader__.load({
177
197
  if (response.status === 503) return null;
178
198
  if (response.status === 400) throw new WorkspaceLimitsRejection(await refusalReason$1(response, "Workspace limits write refused"));
179
199
  if (!response.ok) throw new Error(await refusalReason$1(response, "Workspace limits write failed"));
180
- const body = readJson(await response.json());
181
- return body === null ? null : asStatus(body);
200
+ const body = readJson$1(await response.json());
201
+ return body === null ? null : asStatus$1(body);
182
202
  }
183
203
  //#endregion
184
204
  //#region src/client/tool-timeout-client.ts
@@ -230,31 +250,643 @@ window.__ModuleLoader__.load({
230
250
  return asTimeout(await response.json());
231
251
  }
232
252
  //#endregion
233
- //#region \0dsh-css:/tmp/wt-rebase/packages/dataops-managed/src/client/ManagedDataOpsSection.module.css.mjs
234
- const css$2 = ".dcEWUa_section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}.dcEWUa_title{margin:0;font-size:18px;font-weight:600;line-height:28px}.dcEWUa_status{color:var(--dsw-alias-label-secondary);align-items:center;gap:8px;font-size:13px;line-height:22px;display:flex}.dcEWUa_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}.dcEWUa_heading{margin:0;font-size:13px;font-weight:600;line-height:22px}.dcEWUa_row{justify-content:space-between;align-items:center;gap:16px;display:flex}.dcEWUa_copy{flex-direction:column;min-width:0;display:flex}.dcEWUa_label{font-size:13px;line-height:22px}.dcEWUa_meta{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.dcEWUa_fields{grid-template-columns:repeat(auto-fit,minmax(min(200px,100%),1fr));gap:0 16px;display:grid}.dcEWUa_fields>div{min-width:0}.dcEWUa_fields input{font-variant-numeric:tabular-nums;min-width:0}.dcEWUa_actions{justify-content:flex-end;align-items:center;gap:12px;display:flex}.dcEWUa_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}";
235
- const tagId$2 = "@sparkelf/dsh-dataops-managed/ManagedDataOpsSection.module.css";
236
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
253
+ //#region src/client/review-demo.ts
254
+ var review_demo_exports = /* @__PURE__ */ __exportAll({
255
+ demoPlazaSkills: () => demoPlazaSkills,
256
+ demoRbacOverview: () => demoRbacOverview
257
+ });
258
+ /** A pending skill with an AI report, an approved global skill, and a rejected private one. */
259
+ function demoPlazaSkills() {
260
+ return [
261
+ {
262
+ id: 9101,
263
+ name: "SQL 取数助手",
264
+ description: "把自然语言问题改写成只读 SQL,并解释每个过滤条件。",
265
+ instruction: "你是数据分析助手。只生成只读 SELECT,不要写 UPDATE/DELETE。",
266
+ tags: ["数据", "SQL"],
267
+ sharedByMe: false,
268
+ updatedAt: "2026-10-23T04:40:00.000Z",
269
+ reviewStatus: "pending",
270
+ visibility: "department",
271
+ departmentCode: "dept-rd",
272
+ departmentName: "研发部",
273
+ departmentPath: [{
274
+ code: "dept-root",
275
+ name: "万相"
276
+ }, {
277
+ code: "dept-rd",
278
+ name: "研发部"
279
+ }],
280
+ ownerName: "林工",
281
+ riskReport: {
282
+ score: 62,
283
+ severity: "medium",
284
+ summary: "指令包含数据库读取,需确认连接凭据的最小权限。",
285
+ findings: [{
286
+ category: "credential_access",
287
+ severity: "medium",
288
+ evidence: "提示词要求读取 DATABASE_URL 以连接数据库。",
289
+ recommendation: "改为由平台注入只读连接,不要在提示词中出现凭据。"
290
+ }, {
291
+ category: "destructive_action",
292
+ severity: "low",
293
+ evidence: "明确禁止写操作,但未限制 DDL。",
294
+ recommendation: "补充「禁止 CREATE/ALTER/DROP」。"
295
+ }],
296
+ scannedAt: "2026-10-23T04:41:00.000Z"
297
+ },
298
+ reviewNote: null,
299
+ submittedAt: "2026-10-23T04:40:00.000Z"
300
+ },
301
+ {
302
+ id: 9102,
303
+ name: "经营日报生成",
304
+ description: "按日汇总营收、订单与退款,输出 Markdown 日报。",
305
+ instruction: "读取昨日指标,生成经营日报。",
306
+ tags: ["经营", "日报"],
307
+ sharedByMe: true,
308
+ updatedAt: "2026-10-21T02:00:00.000Z",
309
+ reviewStatus: "approved",
310
+ visibility: "global",
311
+ departmentCode: null,
312
+ ownerName: "admin",
313
+ riskReport: null
314
+ },
315
+ {
316
+ id: 9103,
317
+ name: "私域名单清洗",
318
+ description: "按内部规则去重并标记高价值客户。",
319
+ instruction: "清洗名单并输出 CSV。",
320
+ tags: ["客户"],
321
+ sharedByMe: false,
322
+ updatedAt: "2026-10-20T06:00:00.000Z",
323
+ reviewStatus: "rejected",
324
+ visibility: "private",
325
+ departmentCode: "dept-rd",
326
+ departmentName: "研发部",
327
+ ownerName: "周工",
328
+ reviewNote: "包含真实手机号样例,请脱敏后重新提交。"
329
+ }
330
+ ];
331
+ }
332
+ /** Four roles, the permission catalog, three members, two departments, and three resources. */
333
+ function demoRbacOverview() {
334
+ return {
335
+ matrix: {
336
+ permissions: [
337
+ {
338
+ key: "skill.review",
339
+ name: "审核技能",
340
+ group: "技能广场"
341
+ },
342
+ {
343
+ key: "workflow.review",
344
+ name: "审核工作流",
345
+ group: "工作流广场"
346
+ },
347
+ {
348
+ key: "rbac.manage",
349
+ name: "管理权限",
350
+ group: "系统"
351
+ },
352
+ {
353
+ key: "kb.write",
354
+ name: "写入知识库",
355
+ group: "知识库"
356
+ }
357
+ ],
358
+ roles: [
359
+ {
360
+ code: "SUPER_ADMIN",
361
+ name: "超级管理员",
362
+ systemKey: "SUPER_ADMIN",
363
+ dataScope: "ALL",
364
+ permissions: [
365
+ "skill.review",
366
+ "workflow.review",
367
+ "rbac.manage",
368
+ "kb.write"
369
+ ]
370
+ },
371
+ {
372
+ code: "ADMIN",
373
+ name: "管理员",
374
+ systemKey: "ADMIN",
375
+ dataScope: "ALL",
376
+ permissions: [
377
+ "skill.review",
378
+ "workflow.review",
379
+ "kb.write"
380
+ ]
381
+ },
382
+ {
383
+ code: "DEPT_ADMIN",
384
+ name: "部门管理员",
385
+ systemKey: "DEPT_ADMIN",
386
+ dataScope: "DEPARTMENT_TREE",
387
+ permissions: ["skill.review", "workflow.review"]
388
+ },
389
+ {
390
+ code: "APP_USER",
391
+ name: "应用人员",
392
+ systemKey: "APP_USER",
393
+ dataScope: "SELF",
394
+ permissions: []
395
+ }
396
+ ]
397
+ },
398
+ members: [
399
+ {
400
+ userId: "u1",
401
+ username: "admin",
402
+ displayName: "超级管理员",
403
+ roleCodes: ["SUPER_ADMIN"],
404
+ departmentCode: "dept-root",
405
+ departmentPath: [{
406
+ code: "dept-root",
407
+ name: "万相"
408
+ }]
409
+ },
410
+ {
411
+ userId: "u2",
412
+ username: "lin",
413
+ displayName: "林工",
414
+ roleCodes: ["DEPT_ADMIN"],
415
+ departmentCode: "dept-rd",
416
+ departmentPath: [{
417
+ code: "dept-root",
418
+ name: "万相"
419
+ }, {
420
+ code: "dept-rd",
421
+ name: "研发部"
422
+ }]
423
+ },
424
+ {
425
+ userId: "u3",
426
+ username: "zhou",
427
+ displayName: "周工",
428
+ roleCodes: ["APP_USER"],
429
+ departmentCode: "dept-rd",
430
+ departmentPath: [{
431
+ code: "dept-root",
432
+ name: "万相"
433
+ }, {
434
+ code: "dept-rd",
435
+ name: "研发部"
436
+ }]
437
+ }
438
+ ],
439
+ departments: [{
440
+ code: "dept-root",
441
+ name: "万相",
442
+ path: [{
443
+ code: "dept-root",
444
+ name: "万相"
445
+ }],
446
+ memberCount: 3,
447
+ deptAdmins: []
448
+ }, {
449
+ code: "dept-rd",
450
+ name: "研发部",
451
+ parentCode: "dept-root",
452
+ path: [{
453
+ code: "dept-root",
454
+ name: "万相"
455
+ }, {
456
+ code: "dept-rd",
457
+ name: "研发部"
458
+ }],
459
+ memberCount: 2,
460
+ deptAdmins: [{
461
+ userId: "u2",
462
+ username: "lin",
463
+ displayName: "林工"
464
+ }]
465
+ }],
466
+ resources: [
467
+ {
468
+ kind: "skill",
469
+ id: 9101,
470
+ name: "SQL 取数助手",
471
+ ownerName: "林工",
472
+ departmentCode: "dept-rd",
473
+ visibility: "department",
474
+ reviewStatus: "pending"
475
+ },
476
+ {
477
+ kind: "skill",
478
+ id: 9102,
479
+ name: "经营日报生成",
480
+ ownerName: "admin",
481
+ departmentCode: null,
482
+ visibility: "global",
483
+ reviewStatus: "approved"
484
+ },
485
+ {
486
+ kind: "workflow",
487
+ id: "wf-1",
488
+ name: "每日经营日报",
489
+ ownerName: "admin",
490
+ departmentCode: null,
491
+ visibility: "global",
492
+ reviewStatus: "approved"
493
+ }
494
+ ]
495
+ };
496
+ }
497
+ var init_review_demo = __esmMin((() => {}));
498
+ //#endregion
499
+ //#region src/client/review-client.ts
500
+ /** Caller identity and review capability. */
501
+ const REVIEW_CONTEXT_PATH = "/integrations/dataops/review-context";
502
+ /** Notification list and read receipts. */
503
+ const NOTIFICATIONS_PATH = "/integrations/dataops/review-notifications";
504
+ /** AI scan and the human decision. */
505
+ const REVIEW_ACTION_PATH = "/integrations/dataops/review";
506
+ /** Role matrix, members, departments, and resource scopes. */
507
+ const RBAC_PATH = "/integrations/dataops/rbac";
508
+ /** The three plaza-review switches in the DataOps settings panel. */
509
+ const REVIEW_SETTINGS_PATH = "/integrations/dataops/review-settings";
510
+ /** Department CRUD bridge (task-67). */
511
+ const DEPARTMENTS_PATH = "/integrations/dataops/departments";
512
+ /** The product defaults, used until DataOps returns its own value. */
513
+ const DEFAULT_REVIEW_SETTINGS = {
514
+ skillPlaza: true,
515
+ workflowPlaza: true,
516
+ knowledgeBase: true
517
+ };
518
+ /**
519
+ * Whether the page was opened with the demo fixture.
520
+ *
521
+ * The fixture exists so the review dialog and the permission matrix can be reviewed before the
522
+ * backend contract is deployed; every fixture row is labelled and the mode is never the default.
523
+ */
524
+ function reviewDemoMode() {
525
+ if (typeof window === "undefined") return false;
526
+ try {
527
+ return new URLSearchParams(window.location.search).get("dataopsReviewDemo") === "1" || window.localStorage.getItem("dataopsReviewDemo") === "1";
528
+ } catch {
529
+ return false;
530
+ }
531
+ }
532
+ /** Read JSON, mapping "not active" and "backend not ready" answers to null. */
533
+ async function readJson(path, init = {}) {
534
+ const response = await fetch(path, {
535
+ ...init,
536
+ headers: {
537
+ accept: "application/json",
538
+ ...init.body === void 0 ? {} : { "content-type": "application/json" },
539
+ ...init.headers
540
+ }
541
+ });
542
+ if (response.status === 503 || response.status === 404 || response.status === 501) return null;
543
+ if (!response.ok) throw new Error("DataOps review request failed with HTTP " + String(response.status));
544
+ return await response.json();
545
+ }
546
+ /** Read the caller's role and review capability. */
547
+ async function fetchReviewContext() {
548
+ if (reviewDemoMode()) return {
549
+ role: "ADMIN",
550
+ departmentCode: "dept-rd",
551
+ departmentPath: ["万相", "研发部"],
552
+ canReview: true,
553
+ canManagePermissions: true
554
+ };
555
+ const body = await readJson(REVIEW_CONTEXT_PATH);
556
+ if (body === null) return null;
557
+ const codes = Array.isArray(body.roleCodes) ? body.roleCodes : [];
558
+ const primaryKey = body.roles?.find((role) => role.code === body.primaryRole)?.systemKey ?? body.roles?.find((role) => role.code !== void 0 && codes.includes(role.code))?.systemKey;
559
+ const role = primaryKey === "SUPER_ADMIN" || codes.includes("超级管理员") ? "SUPER_ADMIN" : primaryKey === "ADMIN" || codes.includes("管理员") ? "ADMIN" : primaryKey === "DEPT_ADMIN" || codes.includes("部门管理员") ? "DEPT_ADMIN" : "APP_USER";
560
+ return {
561
+ userId: body.userId,
562
+ role,
563
+ departmentCode: body.departmentCode ?? void 0,
564
+ departmentPath: body.departmentPath?.map((entry) => entry.name),
565
+ canReview: body.review?.canReview === true || role === "SUPER_ADMIN" || role === "ADMIN" || role === "DEPT_ADMIN",
566
+ canManagePermissions: body.review?.canManagePermissions === true || role === "SUPER_ADMIN" || role === "ADMIN"
567
+ };
568
+ }
569
+ /** Read the current user's notifications. */
570
+ async function fetchNotifications() {
571
+ const body = await readJson(NOTIFICATIONS_PATH);
572
+ if (body === null || !Array.isArray(body.items)) return null;
573
+ return {
574
+ items: body.items,
575
+ unread: typeof body.unread === "number" ? body.unread : body.items.filter((item) => !item.read).length
576
+ };
577
+ }
578
+ /** Mark some notifications, or every one, as read. */
579
+ async function markNotificationsRead(ids) {
580
+ const response = await fetch(NOTIFICATIONS_PATH, {
581
+ method: "POST",
582
+ headers: {
583
+ "content-type": "application/json",
584
+ accept: "application/json"
585
+ },
586
+ body: JSON.stringify(ids === "all" ? { all: true } : { ids })
587
+ });
588
+ return response.ok && response.status !== 503;
589
+ }
590
+ /** Run the AI review for one or more resources. */
591
+ async function scanReview(kind, ids) {
592
+ const report = (await readJson(REVIEW_ACTION_PATH, {
593
+ method: "POST",
594
+ body: JSON.stringify({
595
+ action: "scan",
596
+ skills: kind === "skill" ? ids : [],
597
+ workflows: kind === "workflow" ? ids : []
598
+ })
599
+ }))?.reports?.[0];
600
+ return report !== void 0 && Array.isArray(report.findings) ? report : null;
601
+ }
602
+ /** Record the administrator's decision. The backend enforces the authorization flag too. */
603
+ async function decideReview(kind, ids, decision, note) {
604
+ const response = await fetch(REVIEW_ACTION_PATH, {
605
+ method: "POST",
606
+ headers: {
607
+ "content-type": "application/json",
608
+ accept: "application/json"
609
+ },
610
+ body: JSON.stringify({
611
+ action: "decide",
612
+ skills: kind === "skill" ? ids : [],
613
+ workflows: kind === "workflow" ? ids : [],
614
+ decision,
615
+ note,
616
+ authorization: {
617
+ confirmed: true,
618
+ reason: "permission-center"
619
+ }
620
+ })
621
+ });
622
+ return response.ok && response.status !== 503;
623
+ }
624
+ /** Read the permission centre. */
625
+ async function fetchRbacOverview() {
626
+ if (reviewDemoMode()) {
627
+ const { demoRbacOverview } = await Promise.resolve().then(() => (init_review_demo(), review_demo_exports));
628
+ return demoRbacOverview();
629
+ }
630
+ const [matrix, members, departments, resources] = await Promise.all([
631
+ readJson(RBAC_PATH + "?section=matrix&scope=dsh"),
632
+ readJson(RBAC_PATH + "?section=members"),
633
+ readJson(RBAC_PATH + "?section=departments"),
634
+ readJson(RBAC_PATH + "?section=resources")
635
+ ]);
636
+ if (matrix === null || members === null || departments === null || resources === null) return null;
637
+ return {
638
+ matrix: {
639
+ roles: matrix.roles ?? [],
640
+ permissions: matrix.permissions ?? []
641
+ },
642
+ members: members.items ?? [],
643
+ departments: departments.items ?? [],
644
+ resources: resources.items ?? []
645
+ };
646
+ }
647
+ /** Persist one permission-centre section. */
648
+ async function saveRbacSection(section, value) {
649
+ if (reviewDemoMode()) return { ok: true };
650
+ const response = await fetch(RBAC_PATH, {
651
+ method: "PUT",
652
+ headers: {
653
+ "content-type": "application/json",
654
+ accept: "application/json"
655
+ },
656
+ body: JSON.stringify({
657
+ section,
658
+ value
659
+ })
660
+ });
661
+ if (response.ok && response.status !== 503) return { ok: true };
662
+ try {
663
+ const body = await response.json();
664
+ const reason = body.details?.reason ?? body.message;
665
+ return {
666
+ ok: false,
667
+ ...typeof reason === "string" ? { reason } : {}
668
+ };
669
+ } catch {
670
+ return { ok: false };
671
+ }
672
+ }
673
+ /** Read the three plaza-review switches. */
674
+ async function fetchReviewSettings() {
675
+ if (reviewDemoMode()) return DEFAULT_REVIEW_SETTINGS;
676
+ return await readJson(REVIEW_SETTINGS_PATH);
677
+ }
678
+ /** Persist the three plaza-review switches. */
679
+ async function saveReviewSettings(settings) {
680
+ if (reviewDemoMode()) return true;
681
+ const response = await fetch(REVIEW_SETTINGS_PATH, {
682
+ method: "PUT",
683
+ headers: {
684
+ "content-type": "application/json",
685
+ accept: "application/json"
686
+ },
687
+ body: JSON.stringify(settings)
688
+ });
689
+ return response.ok && response.status !== 503;
690
+ }
691
+ /** Create one department through the same-origin bridge. */
692
+ async function createDepartment(input) {
693
+ return await resultOf(await fetch(DEPARTMENTS_PATH, {
694
+ method: "POST",
695
+ headers: {
696
+ "content-type": "application/json",
697
+ accept: "application/json"
698
+ },
699
+ body: JSON.stringify(input)
700
+ }));
701
+ }
702
+ /** Update one department; the Host fills `updatedAt` from the current row when omitted. */
703
+ async function updateDepartment(input) {
704
+ return await resultOf(await fetch(DEPARTMENTS_PATH, {
705
+ method: "PATCH",
706
+ headers: {
707
+ "content-type": "application/json",
708
+ accept: "application/json"
709
+ },
710
+ body: JSON.stringify(input)
711
+ }));
712
+ }
713
+ /** Delete one department after the confirmation dialog. */
714
+ async function deleteDepartment(code) {
715
+ return await resultOf(await fetch(DEPARTMENTS_PATH, {
716
+ method: "DELETE",
717
+ headers: {
718
+ "content-type": "application/json",
719
+ accept: "application/json"
720
+ },
721
+ body: JSON.stringify({ code })
722
+ }));
723
+ }
724
+ /** Map one bridge response to `{ok, reason}` so callers can show the backend text. */
725
+ async function resultOf(response) {
726
+ if (response.ok && response.status !== 503) return { ok: true };
727
+ try {
728
+ const body = await response.json();
729
+ const reason = body.details?.reason ?? body.message;
730
+ return {
731
+ ok: false,
732
+ ...typeof reason === "string" ? { reason } : {}
733
+ };
734
+ } catch {
735
+ return { ok: false };
736
+ }
737
+ }
738
+ //#endregion
739
+ //#region \0dsh-css:/root/projects/dsh-plugins-plus/packages/dataops-managed/src/client/ManagedDataOpsSection.module.css.mjs
740
+ const css$5 = "._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}._2dqzjG_reviewHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:17px}._2dqzjG_reviewRows{flex-direction:column;gap:2px;display:flex}._2dqzjG_reviewRow{justify-content:space-between;align-items:center;gap:12px;min-height:30px;display:flex}._2dqzjG_reviewActions{justify-content:flex-end;display:flex}";
741
+ const tagId$5 = "@sparkelf/dsh-dataops-managed/ManagedDataOpsSection.module.css";
742
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$5) + "]") === null) {
237
743
  const tag = document.createElement("style");
238
744
  tag.dataset.plugin = "@sparkelf/dsh-dataops-managed";
239
- tag.dataset.pluginCss = tagId$2;
240
- tag.textContent = css$2;
745
+ tag.dataset.pluginCss = tagId$5;
746
+ tag.textContent = css$5;
241
747
  document.head.appendChild(tag);
242
748
  }
243
749
  var ManagedDataOpsSection_module_css_default = {
244
- "status": "dcEWUa_status",
245
- "card": "dcEWUa_card",
246
- "copy": "dcEWUa_copy",
247
- "actions": "dcEWUa_actions",
248
- "refusal": "dcEWUa_refusal",
249
- "title": "dcEWUa_title",
250
- "row": "dcEWUa_row",
251
- "heading": "dcEWUa_heading",
252
- "label": "dcEWUa_label",
253
- "meta": "dcEWUa_meta",
254
- "section": "dcEWUa_section",
255
- "fields": "dcEWUa_fields"
750
+ "refusal": "_2dqzjG_refusal",
751
+ "heading": "_2dqzjG_heading",
752
+ "section": "_2dqzjG_section",
753
+ "status": "_2dqzjG_status",
754
+ "card": "_2dqzjG_card",
755
+ "meta": "_2dqzjG_meta",
756
+ "title": "_2dqzjG_title",
757
+ "reviewActions": "_2dqzjG_reviewActions",
758
+ "reviewRows": "_2dqzjG_reviewRows",
759
+ "reviewHint": "_2dqzjG_reviewHint",
760
+ "row": "_2dqzjG_row",
761
+ "label": "_2dqzjG_label",
762
+ "fields": "_2dqzjG_fields",
763
+ "actions": "_2dqzjG_actions",
764
+ "reviewRow": "_2dqzjG_reviewRow",
765
+ "copy": "_2dqzjG_copy"
256
766
  };
257
767
  //#endregion
768
+ //#region src/client/ReviewSettingsCard.tsx
769
+ /**
770
+ * The three plaza-review switches.
771
+ *
772
+ * The card is independent of the review queue so the settings page owns the switches while the
773
+ * plaza owns the rows. Defaults are on; a 503 keeps the switches usable and says the API is
774
+ * waiting rather than pretending the save happened.
775
+ */
776
+ function ReviewSettingsCard({ t }) {
777
+ const [settings, setSettings] = (0, react.useState)(DEFAULT_REVIEW_SETTINGS);
778
+ const [saved, setSaved] = (0, react.useState)(DEFAULT_REVIEW_SETTINGS);
779
+ const [loading, setLoading] = (0, react.useState)(true);
780
+ const [unavailable, setUnavailable] = (0, react.useState)(false);
781
+ const [busy, setBusy] = (0, react.useState)(false);
782
+ const [message, setMessage] = (0, react.useState)(null);
783
+ (0, react.useEffect)(() => {
784
+ let live = true;
785
+ fetchReviewSettings().then((value) => {
786
+ if (!live) return;
787
+ if (value === null) setUnavailable(true);
788
+ else {
789
+ setSettings(value);
790
+ setSaved(value);
791
+ }
792
+ }).catch(() => {
793
+ if (live) setUnavailable(true);
794
+ }).finally(() => {
795
+ if (live) setLoading(false);
796
+ });
797
+ return () => {
798
+ live = false;
799
+ };
800
+ }, []);
801
+ const changed = settings.skillPlaza !== saved.skillPlaza || settings.workflowPlaza !== saved.workflowPlaza || settings.knowledgeBase !== saved.knowledgeBase;
802
+ const save = async () => {
803
+ setBusy(true);
804
+ setMessage(null);
805
+ try {
806
+ if (await saveReviewSettings(settings)) {
807
+ setSaved(settings);
808
+ setMessage(t("reviewSettingsSaved"));
809
+ } else setMessage(t("reviewSettingsWaiting"));
810
+ } catch {
811
+ setMessage(t("reviewSettingsFailed"));
812
+ } finally {
813
+ setBusy(false);
814
+ }
815
+ };
816
+ const rows = [
817
+ {
818
+ key: "skillPlaza",
819
+ label: t("reviewSettingsSkill")
820
+ },
821
+ {
822
+ key: "workflowPlaza",
823
+ label: t("reviewSettingsWorkflow")
824
+ },
825
+ {
826
+ key: "knowledgeBase",
827
+ label: t("reviewSettingsKb")
828
+ }
829
+ ];
830
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
831
+ className: ManagedDataOpsSection_module_css_default.card,
832
+ "data-review-settings-card": "",
833
+ children: [
834
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
835
+ className: ManagedDataOpsSection_module_css_default.heading,
836
+ children: t("reviewSettingsTitle")
837
+ }),
838
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
839
+ className: ManagedDataOpsSection_module_css_default.reviewHint,
840
+ children: t("reviewSettingsHint")
841
+ }),
842
+ loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
843
+ className: ManagedDataOpsSection_module_css_default.status,
844
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "…" })
845
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
846
+ className: ManagedDataOpsSection_module_css_default.reviewRows,
847
+ children: rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
848
+ className: ManagedDataOpsSection_module_css_default.reviewRow,
849
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
850
+ className: ManagedDataOpsSection_module_css_default.label,
851
+ children: row.label
852
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
853
+ checked: settings[row.key],
854
+ label: row.label,
855
+ onChange: (next) => {
856
+ setSettings((current) => ({
857
+ ...current,
858
+ [row.key]: next
859
+ }));
860
+ }
861
+ })]
862
+ }, row.key))
863
+ }),
864
+ unavailable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
865
+ className: ManagedDataOpsSection_module_css_default.reviewHint,
866
+ role: "status",
867
+ children: t("reviewSettingsWaiting")
868
+ }) : null,
869
+ message === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
870
+ className: ManagedDataOpsSection_module_css_default.reviewHint,
871
+ role: "status",
872
+ children: message
873
+ }),
874
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
875
+ className: ManagedDataOpsSection_module_css_default.reviewActions,
876
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
877
+ variant: "primary",
878
+ size: "sm",
879
+ disabled: !changed || busy || loading,
880
+ onClick: () => {
881
+ save();
882
+ },
883
+ children: busy ? t("limitsSaving") : t("reviewSettingsSave")
884
+ })
885
+ })
886
+ ]
887
+ });
888
+ }
889
+ //#endregion
258
890
  //#region src/client/ManagedDataOpsSection.tsx
259
891
  /** The panel's unit, so a stored byte count survives a round trip through the input. */
260
892
  const BYTES_PER_MIB = 1048576;
@@ -302,6 +934,7 @@ window.__ModuleLoader__.load({
302
934
  }),
303
935
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LimitsCard, { t }),
304
936
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TimeoutCard, { t }),
937
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewSettingsCard, { t }),
305
938
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultsCard, { t })
306
939
  ]
307
940
  });
@@ -667,8 +1300,102 @@ window.__ModuleLoader__.load({
667
1300
  });
668
1301
  }
669
1302
  //#endregion
1303
+ //#region src/client/review-types.ts
1304
+ /** Treat a missing status as approved: rows written before the review state existed were live. */
1305
+ function normalizeReviewStatus(value) {
1306
+ return value === "pending" || value === "rejected" || value === "approved" ? value : "approved";
1307
+ }
1308
+ /** Treat a missing visibility as global: a shared row with no scope was visible to everyone. */
1309
+ function normalizeVisibility(value) {
1310
+ return value === "department" || value === "private" ? value : "global";
1311
+ }
1312
+ /** Whether the caller may see and act on the review queue. */
1313
+ function canReview(context) {
1314
+ if (context === null || context === void 0) return false;
1315
+ return context.canReview || context.role === "SUPER_ADMIN" || context.role === "ADMIN" || context.role === "DEPT_ADMIN";
1316
+ }
1317
+ /** Whether the caller may open the permission centre's write controls. */
1318
+ function canManagePermissions(context) {
1319
+ if (context === null || context === void 0) return false;
1320
+ return context.canManagePermissions || context.role === "SUPER_ADMIN" || context.role === "ADMIN";
1321
+ }
1322
+ /**
1323
+ * The tabs this caller may see.
1324
+ *
1325
+ * A department administrator sees the queue too, but the Host narrows it to their subtree;
1326
+ * an ordinary user gets only the approved list, and no tab strip at all when there is one tab.
1327
+ */
1328
+ function reviewTabsFor(context) {
1329
+ if (!canReview(context)) return ["approved"];
1330
+ return [
1331
+ "approved",
1332
+ "pending",
1333
+ "all"
1334
+ ];
1335
+ }
1336
+ /** Filter rows by the selected review tab. */
1337
+ function filterByReview(rows, tab) {
1338
+ if (tab === "all") return rows;
1339
+ return rows.filter((row) => normalizeReviewStatus(row.reviewStatus) === tab);
1340
+ }
1341
+ /** Counts for the tab labels. */
1342
+ function reviewCounts(rows) {
1343
+ const counts = {
1344
+ approved: 0,
1345
+ pending: 0,
1346
+ all: rows.length
1347
+ };
1348
+ for (const row of rows) {
1349
+ const status = normalizeReviewStatus(row.reviewStatus);
1350
+ if (status === "pending") counts.pending += 1;
1351
+ else if (status === "rejected") counts.all += 0;
1352
+ else counts.approved += 1;
1353
+ }
1354
+ return counts;
1355
+ }
1356
+ /**
1357
+ * The visibility tag in a card's top-right corner.
1358
+ *
1359
+ * A department tag names the department and keeps the full path in the tooltip; a global tag
1360
+ * is one word because it is the default and should not compete with the skill name.
1361
+ */
1362
+ /** Read a department path from either the legacy string form or the backend object form. */
1363
+ function departmentPathNames(path) {
1364
+ if (path === void 0) return [];
1365
+ return path.map((entry) => typeof entry === "string" ? entry : entry.name);
1366
+ }
1367
+ function visibilityTag(row) {
1368
+ const visibility = normalizeVisibility(row.visibility);
1369
+ if (visibility === "private") return {
1370
+ label: "私有",
1371
+ detail: "仅自己可见"
1372
+ };
1373
+ if (visibility === "global") return {
1374
+ label: "全局",
1375
+ detail: "所有已登录用户可见"
1376
+ };
1377
+ const path = departmentPathNames(row.departmentPath);
1378
+ const name = row.departmentName ?? path.at(-1) ?? row.departmentCode ?? "部门";
1379
+ return {
1380
+ label: name + "部门专属",
1381
+ detail: path.length === 0 ? name : path.join(" / ")
1382
+ };
1383
+ }
1384
+ /** Unread count for a bell badge. */
1385
+ function unreadCount(items) {
1386
+ return items.reduce((total, item) => total + (item.read ? 0 : 1), 0);
1387
+ }
1388
+ /** Newest first. Read state never reorders rows, so marking one read cannot move it to another page. */
1389
+ function sortNotifications(items) {
1390
+ return [...items].sort((left, right) => right.createdAt.localeCompare(left.createdAt));
1391
+ }
1392
+ /** The tab a notification should open, when its resource has one. */
1393
+ function notificationTab(item) {
1394
+ if (item.type === "submitted" || item.type === "review_pending" || item.type === "ai_report_ready" || item.type === "ai_report") return "pending";
1395
+ if (item.type === "approved" || item.type === "published" || item.type === "shared") return "approved";
1396
+ }
1397
+ //#endregion
670
1398
  //#region src/client/skill-plaza-client.ts
671
- /** Same-origin skill plaza calls from the DataOps skill center section. */
672
1399
  /** Route the plugin's Host half serves for the plaza. */
673
1400
  const SKILL_PLAZA_PATH = "/integrations/dataops/skill-plaza";
674
1401
  /** Narrow an unknown value to a plaza entry. */
@@ -683,16 +1410,37 @@ window.__ModuleLoader__.load({
683
1410
  instruction: typeof row.instruction === "string" ? row.instruction : "",
684
1411
  tags: Array.isArray(row.tags) ? row.tags.filter((tag) => typeof tag === "string") : [],
685
1412
  sharedByMe: row.sharedByMe === true,
686
- updatedAt: typeof row.updatedAt === "string" ? row.updatedAt : ""
1413
+ updatedAt: typeof row.updatedAt === "string" ? row.updatedAt : "",
1414
+ reviewStatus: normalizeReviewStatus(row.reviewStatus),
1415
+ visibility: normalizeVisibility(row.visibility),
1416
+ departmentCode: typeof row.departmentCode === "string" ? row.departmentCode : null,
1417
+ departmentName: typeof row.departmentName === "string" ? row.departmentName : void 0,
1418
+ departmentPath: Array.isArray(row.departmentPath) ? row.departmentPath.flatMap((entry) => typeof entry === "object" && entry !== null && typeof entry.name === "string" ? [{
1419
+ code: String(entry.code ?? ""),
1420
+ name: String(entry.name)
1421
+ }] : []) : void 0,
1422
+ ownerName: typeof row.ownerName === "string" ? row.ownerName : void 0,
1423
+ riskReport: typeof row.riskReport === "object" && row.riskReport !== null ? row.riskReport : null,
1424
+ reviewNote: typeof row.reviewNote === "string" ? row.reviewNote : null,
1425
+ submittedAt: typeof row.submittedAt === "string" ? row.submittedAt : void 0
687
1426
  };
688
1427
  }
689
1428
  /** Narrow an unknown value to the listing. */
690
1429
  function asListing(value) {
691
1430
  if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
692
1431
  const row = value;
1432
+ const skills = (Array.isArray(row.skills) ? row.skills : Array.isArray(row.items) ? row.items : []).flatMap((entry) => asSkill(entry) ?? []);
1433
+ const tags = Array.isArray(row.tags) ? row.tags.filter((tag) => typeof tag === "string") : [];
1434
+ const rawCounts = typeof row.counts === "object" && row.counts !== null ? row.counts : {};
693
1435
  return {
694
- skills: Array.isArray(row.skills) ? row.skills.flatMap((entry) => asSkill(entry) ?? []) : [],
695
- tags: Array.isArray(row.tags) ? row.tags.filter((tag) => typeof tag === "string") : []
1436
+ skills,
1437
+ tags,
1438
+ counts: Object.keys(rawCounts).length === 0 ? void 0 : {
1439
+ pending: Number(rawCounts.pending ?? 0),
1440
+ approved: Number(rawCounts.approved ?? 0),
1441
+ rejected: Number(rawCounts.rejected ?? 0),
1442
+ total: Number(rawCounts.total ?? skills.length)
1443
+ }
696
1444
  };
697
1445
  }
698
1446
  /**
@@ -704,6 +1452,8 @@ window.__ModuleLoader__.load({
704
1452
  const params = new URLSearchParams();
705
1453
  if (filter.tag !== void 0 && filter.tag !== "") params.set("tag", filter.tag);
706
1454
  if (filter.query !== void 0 && filter.query !== "") params.set("q", filter.query);
1455
+ if (filter.status !== void 0) params.set("status", filter.status);
1456
+ if (filter.scope !== void 0) params.set("scope", filter.scope);
707
1457
  const suffix = params.size === 0 ? "" : "?" + params.toString();
708
1458
  const response = await fetch(SKILL_PLAZA_PATH + suffix, { headers: { accept: "application/json" } });
709
1459
  if (response.status === 503) return null;
@@ -729,95 +1479,587 @@ window.__ModuleLoader__.load({
729
1479
  return await response.json();
730
1480
  }
731
1481
  //#endregion
732
- //#region \0dsh-css:/tmp/wt-rebase/packages/dataops-managed/src/client/SkillPlazaSection.module.css.mjs
733
- const css$1 = ".pN-avG_section{box-sizing:border-box;flex-direction:column;width:100%;max-width:960px;margin:0 auto;display:flex}.pN-avG_head{flex-direction:column;margin-bottom:8px;display:flex}.pN-avG_title{color:var(--dsw-alias-label-primary);align-items:baseline;gap:8px;margin:0;font-size:14px;font-weight:500;line-height:20px;display:flex}.pN-avG_titleCount{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400}.pN-avG_status{text-align:center;color:var(--dsw-alias-label-tertiary);margin:0;padding:20px 0;font-size:13px;line-height:20px}.pN-avG_tagBar{flex-wrap:wrap;gap:6px;margin-bottom:8px;display:flex}.pN-avG_chip{background:var(--dsw-alias-bg-module-platform);height:24px;color:var(--dsw-alias-label-secondary);cursor:pointer;border:0;border-radius:12px;align-items:center;padding:0 10px;font-size:12px;line-height:18px;transition:background .12s;display:inline-flex}.pN-avG_chip:hover{background:var(--dsw-alias-interactive-bg-hover)}.pN-avG_chip:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.pN-avG_chipOn{color:var(--dsw-alias-state-business-primary);background:#ecf1fc;font-weight:500}.pN-avG_notice{color:var(--dsw-alias-state-business-primary);margin:4px 0 0;font-size:12px;line-height:18px}.pN-avG_cards{grid-template-columns:repeat(auto-fill,minmax(272px,1fr));align-items:start;gap:12px;margin:0;padding:0;list-style:none;display:grid}.pN-avG_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:10px;flex-direction:column;align-self:start;gap:10px;min-width:0;padding:12px;display:flex}.pN-avG_cardTop{justify-content:space-between;align-items:flex-start;gap:10px;display:flex}.pN-avG_cardIcon{background:var(--dsw-alias-bg-layer-2);width:28px;height:28px;color:var(--dsw-alias-label-secondary);border-radius:50%;flex:none;place-items:center;display:grid}.pN-avG_cardTitle{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;font-size:14px;font-weight:600;line-height:20px;overflow:hidden}.pN-avG_mine{flex:none}.pN-avG_cardDesc{color:var(--dsw-alias-label-secondary);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:12px;line-height:18px;display:-webkit-box;overflow:hidden}.pN-avG_cardMarks{flex-wrap:wrap;gap:6px;display:flex}.pN-avG_mark{color:var(--dsw-alias-state-business-primary);white-space:nowrap;background:#ecf1fc;border:0;border-radius:4px;padding:2px 7px;font-size:11px;font-weight:500;line-height:16px}.pN-avG_cardEnd{flex:none;align-items:center;gap:4px;margin-top:auto;padding-top:4px;display:flex}.pN-avG_detailButton{margin-left:auto}.pN-avG_detail{flex-direction:column;gap:12px;margin:0;display:flex}.pN-avG_dialog{width:min(560px,100%)}.pN-avG_detailRow{flex-direction:column;gap:2px;display:flex}.pN-avG_detailLabel{color:var(--dsw-alias-label-caption);font-size:11px;line-height:16px}.pN-avG_detailValue{max-height:160px;color:var(--dsw-alias-label-secondary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:12px;line-height:18px;overflow:auto}.pN-avG_empty{text-align:center;flex-direction:column;align-items:center;gap:10px;padding:28px 16px 32px;display:flex}.pN-avG_emptyIcon{border:.5px solid var(--dsw-alias-border-l3);width:56px;height:56px;color:var(--dsw-alias-label-tertiary);border-radius:14px;justify-content:center;align-items:center;display:inline-flex}.pN-avG_emptyText{color:var(--dsw-alias-label-tertiary);margin:0;font-size:13px;line-height:20px}";
734
- const tagId$1 = "@sparkelf/dsh-dataops-managed/SkillPlazaSection.module.css";
735
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
1482
+ //#region src/client/review-notification-store.ts
1483
+ init_review_demo();
1484
+ /**
1485
+ * One process-wide notification store shared by every bell.
1486
+ *
1487
+ * The skill plaza and the workflow plaza are separate React trees, but they show the same
1488
+ * inbox; keeping the read state in one store is what stops a badge in one place from staying
1489
+ * unread after the other opened the row.
1490
+ */
1491
+ const DEMO_ITEMS = [
1492
+ {
1493
+ id: "demo-1",
1494
+ type: "review_pending",
1495
+ title: "技能「SQL 取数助手」等待审核",
1496
+ body: "来自研发部 · 提交于 10 分钟前",
1497
+ resourceKind: "skill",
1498
+ resourceId: 101,
1499
+ read: false,
1500
+ createdAt: "2026-10-23T04:50:00.000Z"
1501
+ },
1502
+ {
1503
+ id: "demo-2",
1504
+ type: "ai_report",
1505
+ title: "AI 风险报告已生成",
1506
+ body: "风险等级:中 · 2 条建议",
1507
+ resourceKind: "skill",
1508
+ resourceId: 101,
1509
+ read: false,
1510
+ createdAt: "2026-10-23T04:49:00.000Z"
1511
+ },
1512
+ {
1513
+ id: "demo-3",
1514
+ type: "approved",
1515
+ title: "工作流「每日经营日报」已通过",
1516
+ body: "审核人:admin",
1517
+ resourceKind: "workflow",
1518
+ resourceId: "wf-1",
1519
+ read: true,
1520
+ createdAt: "2026-10-22T09:10:00.000Z"
1521
+ }
1522
+ ];
1523
+ let state = {
1524
+ status: "idle",
1525
+ items: [],
1526
+ unread: 0
1527
+ };
1528
+ const listeners = /* @__PURE__ */ new Set();
1529
+ function publish(next) {
1530
+ state = next;
1531
+ for (const listener of listeners) listener();
1532
+ }
1533
+ /** Current snapshot for `useSyncExternalStore`. */
1534
+ function notificationSnapshot() {
1535
+ return state;
1536
+ }
1537
+ /** Subscribe to store changes. */
1538
+ function subscribeNotifications(listener) {
1539
+ listeners.add(listener);
1540
+ return () => {
1541
+ listeners.delete(listener);
1542
+ };
1543
+ }
1544
+ /** Load the inbox once; a second caller joins the first request. */
1545
+ let inflight;
1546
+ function loadNotifications() {
1547
+ if (inflight !== void 0) return inflight;
1548
+ publish({
1549
+ ...state,
1550
+ status: "loading"
1551
+ });
1552
+ inflight = (async () => {
1553
+ try {
1554
+ if (reviewDemoMode()) {
1555
+ publish({
1556
+ status: "ready",
1557
+ items: sortNotifications(DEMO_ITEMS),
1558
+ unread: unreadCount(DEMO_ITEMS)
1559
+ });
1560
+ return;
1561
+ }
1562
+ const result = await fetchNotifications();
1563
+ if (result === null) {
1564
+ publish({
1565
+ status: "unavailable",
1566
+ items: [],
1567
+ unread: 0
1568
+ });
1569
+ return;
1570
+ }
1571
+ publish({
1572
+ status: "ready",
1573
+ items: sortNotifications(result.items),
1574
+ unread: unreadCount(result.items)
1575
+ });
1576
+ } catch {
1577
+ publish({
1578
+ status: "unavailable",
1579
+ items: [],
1580
+ unread: 0
1581
+ });
1582
+ } finally {
1583
+ inflight = void 0;
1584
+ }
1585
+ })();
1586
+ return inflight;
1587
+ }
1588
+ /** Mark one row or the whole inbox read, optimistically. */
1589
+ async function markRead(ids) {
1590
+ const items = ids === "all" ? state.items.map((item) => ({
1591
+ ...item,
1592
+ read: true
1593
+ })) : state.items.map((item) => ids.includes(item.id) ? {
1594
+ ...item,
1595
+ read: true
1596
+ } : item);
1597
+ publish({
1598
+ ...state,
1599
+ items,
1600
+ unread: unreadCount(items)
1601
+ });
1602
+ try {
1603
+ if (!await markNotificationsRead(ids)) await loadNotifications();
1604
+ } catch {
1605
+ await loadNotifications();
1606
+ }
1607
+ }
1608
+ //#endregion
1609
+ //#region \0dsh-css:/root/projects/dsh-plugins-plus/packages/dataops-managed/src/client/NotificationBell.module.css.mjs
1610
+ const css$4 = ".tE0trG_bell{width:26px;height:26px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:6px;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex;position:relative}.tE0trG_bell:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.tE0trG_bell:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.tE0trG_badge{background:var(--dsw-alias-state-error-primary);color:#fff;text-align:center;border-radius:7px;min-width:14px;height:14px;padding:0 3px;font-size:9px;font-weight:600;line-height:14px;position:absolute;top:0;right:-2px}.tE0trG_dialog{flex-direction:column;width:min(520px,100vw - 32px);height:70vh;max-height:70vh;display:flex;overflow:hidden}.tE0trG_demo{background:var(--dsw-alias-state-warn-secondary);color:var(--dsw-alias-state-warn-primary);border-radius:4px;margin:0 0 8px;padding:5px 8px;font-size:11px}.tE0trG_empty{color:var(--dsw-alias-label-tertiary);text-align:center;margin:0;padding:18px 0;font-size:12px;line-height:18px}.tE0trG_list{flex-direction:column;flex:none;gap:6px;max-height:calc(70vh - 210px);margin:0;padding:0;list-style:none;display:flex;overflow-y:auto}.tE0trG_row{width:100%;color:var(--dsw-alias-label-secondary);text-align:left;cursor:pointer;background:0 0;border:0;border-radius:0;align-items:flex-start;gap:9px;padding:4px 2px;display:flex}.tE0trG_row:hover .tE0trG_rowTitle{color:var(--dsw-alias-state-business-primary)}.tE0trG_rowIcon{width:18px;height:18px;color:var(--dsw-alias-label-secondary);flex:none;justify-content:center;align-items:center;margin-top:1px;display:inline-flex}.tE0trG_rowIcon[data-tone=success]{color:var(--dsw-alias-state-success-primary)}.tE0trG_rowIcon[data-tone=danger]{color:var(--dsw-alias-state-error-primary)}.tE0trG_rowIcon[data-tone=warn]{color:var(--dsw-alias-state-warn-primary)}.tE0trG_rowIcon[data-tone=info]{color:var(--dsw-alias-state-business-primary)}.tE0trG_dot{background:var(--dsw-alias-state-business-primary);border-radius:50%;flex:none;width:6px;height:6px}.tE0trG_rowBody{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.tE0trG_rowTitle{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:500;line-height:18px}.tE0trG_rowText{color:var(--dsw-alias-label-secondary);font-size:11px;line-height:16px}.tE0trG_rowMeta{flex:none;align-items:center;gap:6px;margin-top:2px;display:flex}.tE0trG_rowTime{color:var(--dsw-alias-label-tertiary);flex:none;font-size:10px;line-height:16px}.tE0trG_actions{justify-content:flex-end;gap:6px;display:flex}.tE0trG_button,.tE0trG_linkButton{cursor:pointer;border-radius:5px;height:28px;padding:0 10px;font-size:12px}.tE0trG_button{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:0 0}.tE0trG_linkButton{color:var(--dsw-alias-state-business-primary);background:0 0;border:0}.tE0trG_pager{color:var(--dsw-alias-label-secondary);justify-content:flex-end;align-items:center;gap:8px;padding-top:8px;font-size:11px;display:flex}.tE0trG_pager button{border:1px solid var(--dsw-alias-border-l2);height:24px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border-radius:4px;padding:0 8px;font-size:11px}.tE0trG_pager button:disabled{opacity:.4;cursor:default}";
1611
+ const tagId$4 = "@sparkelf/dsh-dataops-managed/NotificationBell.module.css";
1612
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
736
1613
  const tag = document.createElement("style");
737
1614
  tag.dataset.plugin = "@sparkelf/dsh-dataops-managed";
738
- tag.dataset.pluginCss = tagId$1;
739
- tag.textContent = css$1;
1615
+ tag.dataset.pluginCss = tagId$4;
1616
+ tag.textContent = css$4;
740
1617
  document.head.appendChild(tag);
741
1618
  }
742
- var SkillPlazaSection_module_css_default = {
743
- "mark": "pN-avG_mark",
744
- "titleCount": "pN-avG_titleCount",
745
- "detailButton": "pN-avG_detailButton",
746
- "cardIcon": "pN-avG_cardIcon",
747
- "mine": "pN-avG_mine",
748
- "status": "pN-avG_status",
749
- "head": "pN-avG_head",
750
- "cardTop": "pN-avG_cardTop",
751
- "cardTitle": "pN-avG_cardTitle",
752
- "emptyIcon": "pN-avG_emptyIcon",
753
- "detailValue": "pN-avG_detailValue",
754
- "cards": "pN-avG_cards",
755
- "notice": "pN-avG_notice",
756
- "dialog": "pN-avG_dialog",
757
- "tagBar": "pN-avG_tagBar",
758
- "cardEnd": "pN-avG_cardEnd",
759
- "detail": "pN-avG_detail",
760
- "card": "pN-avG_card",
761
- "detailRow": "pN-avG_detailRow",
762
- "chip": "pN-avG_chip",
763
- "section": "pN-avG_section",
764
- "cardMarks": "pN-avG_cardMarks",
765
- "detailLabel": "pN-avG_detailLabel",
766
- "chipOn": "pN-avG_chipOn",
767
- "cardDesc": "pN-avG_cardDesc",
768
- "empty": "pN-avG_empty",
769
- "title": "pN-avG_title",
770
- "emptyText": "pN-avG_emptyText"
1619
+ var NotificationBell_module_css_default = {
1620
+ "empty": "tE0trG_empty",
1621
+ "button": "tE0trG_button",
1622
+ "rowTime": "tE0trG_rowTime",
1623
+ "pager": "tE0trG_pager",
1624
+ "rowText": "tE0trG_rowText",
1625
+ "demo": "tE0trG_demo",
1626
+ "row": "tE0trG_row",
1627
+ "rowTitle": "tE0trG_rowTitle",
1628
+ "actions": "tE0trG_actions",
1629
+ "dialog": "tE0trG_dialog",
1630
+ "rowMeta": "tE0trG_rowMeta",
1631
+ "dot": "tE0trG_dot",
1632
+ "rowIcon": "tE0trG_rowIcon",
1633
+ "list": "tE0trG_list",
1634
+ "badge": "tE0trG_badge",
1635
+ "bell": "tE0trG_bell",
1636
+ "rowBody": "tE0trG_rowBody",
1637
+ "linkButton": "tE0trG_linkButton"
771
1638
  };
772
1639
  //#endregion
773
- //#region src/client/SkillPlazaSection.tsx
774
- /**
775
- * One skill as a card in the plaza grid.
776
- *
777
- * A plaza entry carries a name, a description, tags, and its own actions; a full-width row left
778
- * most of that width empty and forced every entry onto its own line. The card stacks the same
779
- * parts vertically, so a grid column count can change without the content reflowing into a
780
- * different shape.
781
- *
782
- * @param props - the card's skill, its actions, and the dictionary resolver.
783
- * @returns the rendered card.
784
- */
785
- function PlazaCard({ skill, busy, onInstall, onUnshare, t }) {
786
- const [open, setOpen] = (0, react.useState)(false);
787
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
788
- className: SkillPlazaSection_module_css_default.card,
1640
+ //#region src/client/NotificationBell.tsx
1641
+ /** Colour tone and glyph for one notification type, matching the automation-task rows. */
1642
+ function notificationTone(type) {
1643
+ if (type === "approved" || type === "published" || type === "success") return "success";
1644
+ if (type === "rejected") return "danger";
1645
+ if (type === "unpublished") return "warn";
1646
+ return "info";
1647
+ }
1648
+ function NotificationIcon({ tone }) {
1649
+ if (tone === "success") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1650
+ width: "14",
1651
+ height: "14",
1652
+ viewBox: "0 0 16 16",
1653
+ fill: "none",
1654
+ "aria-hidden": "true",
1655
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
1656
+ cx: "8",
1657
+ cy: "8",
1658
+ r: "6.2",
1659
+ stroke: "currentColor",
1660
+ strokeWidth: "1.3"
1661
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1662
+ d: "m5.3 8.1 1.8 1.8 3.6-3.8",
1663
+ stroke: "currentColor",
1664
+ strokeWidth: "1.3",
1665
+ strokeLinecap: "round",
1666
+ strokeLinejoin: "round"
1667
+ })]
1668
+ });
1669
+ if (tone === "danger") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1670
+ width: "14",
1671
+ height: "14",
1672
+ viewBox: "0 0 16 16",
1673
+ fill: "none",
1674
+ "aria-hidden": "true",
789
1675
  children: [
790
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
791
- className: SkillPlazaSection_module_css_default.cardTop,
792
- children: [
793
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
794
- className: SkillPlazaSection_module_css_default.cardIcon,
795
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSkillOutlineRegular, { size: 16 })
796
- }),
797
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
798
- className: SkillPlazaSection_module_css_default.cardTitle,
799
- children: skill.name
800
- }),
801
- skill.sharedByMe ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1676
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1677
+ d: "M8 2.3 14 13H2L8 2.3Z",
1678
+ stroke: "currentColor",
1679
+ strokeWidth: "1.3",
1680
+ strokeLinejoin: "round"
1681
+ }),
1682
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1683
+ d: "M8 6v3.1",
1684
+ stroke: "currentColor",
1685
+ strokeWidth: "1.3",
1686
+ strokeLinecap: "round"
1687
+ }),
1688
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
1689
+ cx: "8",
1690
+ cy: "11.2",
1691
+ r: ".7",
1692
+ fill: "currentColor"
1693
+ })
1694
+ ]
1695
+ });
1696
+ if (tone === "warn") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1697
+ width: "14",
1698
+ height: "14",
1699
+ viewBox: "0 0 16 16",
1700
+ fill: "none",
1701
+ "aria-hidden": "true",
1702
+ children: [
1703
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
1704
+ cx: "8",
1705
+ cy: "8",
1706
+ r: "6.2",
1707
+ stroke: "currentColor",
1708
+ strokeWidth: "1.3"
1709
+ }),
1710
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1711
+ d: "M8 4.8v3.6",
1712
+ stroke: "currentColor",
1713
+ strokeWidth: "1.3",
1714
+ strokeLinecap: "round"
1715
+ }),
1716
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
1717
+ cx: "8",
1718
+ cy: "11.1",
1719
+ r: ".7",
1720
+ fill: "currentColor"
1721
+ })
1722
+ ]
1723
+ });
1724
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1725
+ width: "14",
1726
+ height: "14",
1727
+ viewBox: "0 0 16 16",
1728
+ fill: "none",
1729
+ "aria-hidden": "true",
1730
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1731
+ d: "M4 2.5h5l3 3v8H4v-11Z",
1732
+ stroke: "currentColor",
1733
+ strokeWidth: "1.2",
1734
+ strokeLinejoin: "round"
1735
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1736
+ d: "M9 2.5v3h3M6 8h4M6 10.5h3",
1737
+ stroke: "currentColor",
1738
+ strokeWidth: "1.2",
1739
+ strokeLinecap: "round"
1740
+ })]
1741
+ });
1742
+ }
1743
+ /**
1744
+ * A bell with an unread badge and the shared inbox.
1745
+ *
1746
+ * The bell is a button rather than a menu so keyboard focus and Escape follow the dialog
1747
+ * conventions the rest of the page already uses. Rows mark themselves read when opened;
1748
+ * "全部已读" is the one bulk action.
1749
+ */
1750
+ function NotificationBell({ label, onOpen }) {
1751
+ const state = (0, react.useSyncExternalStore)(subscribeNotifications, notificationSnapshot, notificationSnapshot);
1752
+ const [open, setOpen] = (0, react.useState)(false);
1753
+ const [page, setPage] = (0, react.useState)(0);
1754
+ const items = sortNotifications(state.items);
1755
+ const unread = unreadCount(items);
1756
+ const pages = Math.max(1, Math.ceil(items.length / 20));
1757
+ const visible = items.slice(page * 20, page * 20 + 20);
1758
+ (0, react.useEffect)(() => {
1759
+ if (page >= pages) setPage(pages - 1);
1760
+ }, [page, pages]);
1761
+ (0, react.useEffect)(() => {
1762
+ loadNotifications();
1763
+ }, []);
1764
+ const openOne = (item) => {
1765
+ markRead([item.id]);
1766
+ setOpen(false);
1767
+ onOpen?.(item);
1768
+ };
1769
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1770
+ type: "button",
1771
+ className: NotificationBell_module_css_default.bell,
1772
+ "aria-label": unread === 0 ? label : label + "," + String(unread) + " 条未读",
1773
+ title: label,
1774
+ "data-notification-bell": "",
1775
+ "data-unread": unread,
1776
+ onClick: () => {
1777
+ setOpen(true);
1778
+ setPage(0);
1779
+ loadNotifications();
1780
+ },
1781
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1782
+ width: "16",
1783
+ height: "16",
1784
+ viewBox: "0 0 16 16",
1785
+ fill: "none",
1786
+ "aria-hidden": "true",
1787
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1788
+ d: "M8 2.2a4.1 4.1 0 0 0-4.1 4.1v2.3L2.7 10.8a.6.6 0 0 0 .5.9h9.6a.6.6 0 0 0 .5-.9L12.1 8.6V6.3A4.1 4.1 0 0 0 8 2.2Z",
1789
+ stroke: "currentColor",
1790
+ strokeWidth: "1.2",
1791
+ strokeLinejoin: "round"
1792
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1793
+ d: "M6.4 13.2a1.7 1.7 0 0 0 3.2 0",
1794
+ stroke: "currentColor",
1795
+ strokeWidth: "1.2",
1796
+ strokeLinecap: "round"
1797
+ })]
1798
+ }), unread === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1799
+ className: NotificationBell_module_css_default.badge,
1800
+ children: unread > 99 ? "99+" : unread
1801
+ })]
1802
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1803
+ open,
1804
+ onClose: () => {
1805
+ setOpen(false);
1806
+ },
1807
+ title: label,
1808
+ closeLabel: "关闭",
1809
+ className: NotificationBell_module_css_default.dialog,
1810
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1811
+ className: NotificationBell_module_css_default.actions,
1812
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1813
+ type: "button",
1814
+ className: NotificationBell_module_css_default.linkButton,
1815
+ onClick: () => {
1816
+ markRead("all");
1817
+ },
1818
+ children: "全部已读"
1819
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1820
+ type: "button",
1821
+ className: NotificationBell_module_css_default.button,
1822
+ onClick: () => {
1823
+ setOpen(false);
1824
+ },
1825
+ children: "关闭"
1826
+ })]
1827
+ }),
1828
+ children: [
1829
+ reviewDemoMode() ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1830
+ className: NotificationBell_module_css_default.demo,
1831
+ children: "演示数据 · 后端契约到达后自动切换"
1832
+ }) : null,
1833
+ state.status === "unavailable" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1834
+ className: NotificationBell_module_css_default.empty,
1835
+ children: "通知服务待后端联调(task-35);上线后这里会显示审核、上架与 AI 报告事件。"
1836
+ }) : null,
1837
+ state.status !== "unavailable" && items.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1838
+ className: NotificationBell_module_css_default.empty,
1839
+ children: "暂无通知。"
1840
+ }) : null,
1841
+ items.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1842
+ className: NotificationBell_module_css_default.list,
1843
+ children: visible.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1844
+ type: "button",
1845
+ className: NotificationBell_module_css_default.row,
1846
+ onClick: () => {
1847
+ openOne(item);
1848
+ },
1849
+ children: [
1850
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1851
+ className: NotificationBell_module_css_default.rowIcon,
1852
+ "data-tone": notificationTone(item.type),
1853
+ "aria-hidden": "true",
1854
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NotificationIcon, { tone: notificationTone(item.type) })
1855
+ }),
1856
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1857
+ className: NotificationBell_module_css_default.rowBody,
1858
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1859
+ className: NotificationBell_module_css_default.rowTitle,
1860
+ children: item.title
1861
+ }), item.body === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1862
+ className: NotificationBell_module_css_default.rowText,
1863
+ children: item.body
1864
+ })]
1865
+ }),
1866
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1867
+ className: NotificationBell_module_css_default.rowMeta,
1868
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1869
+ className: NotificationBell_module_css_default.rowTime,
1870
+ children: item.createdAt.slice(0, 16).replace("T", " ")
1871
+ }), item.read ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1872
+ className: NotificationBell_module_css_default.dot,
1873
+ "aria-hidden": "true"
1874
+ })]
1875
+ })
1876
+ ]
1877
+ }) }, item.id))
1878
+ }),
1879
+ items.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1880
+ className: NotificationBell_module_css_default.pager,
1881
+ children: [
1882
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1883
+ type: "button",
1884
+ disabled: page === 0,
1885
+ onClick: () => {
1886
+ setPage((current) => Math.max(0, current - 1));
1887
+ },
1888
+ children: "上一页"
1889
+ }),
1890
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
1891
+ "第 ",
1892
+ page + 1,
1893
+ " / ",
1894
+ pages,
1895
+ " 页 · 共 ",
1896
+ items.length,
1897
+ " 条"
1898
+ ] }),
1899
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1900
+ type: "button",
1901
+ disabled: page >= pages - 1,
1902
+ onClick: () => {
1903
+ setPage((current) => Math.min(pages - 1, current + 1));
1904
+ },
1905
+ children: "下一页"
1906
+ })
1907
+ ]
1908
+ })
1909
+ ]
1910
+ })] });
1911
+ }
1912
+ /** The automation page's seat: the same inbox under the workflow plaza's heading. */
1913
+ function WorkflowPlazaBell() {
1914
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NotificationBell, { label: "自动化任务通知" });
1915
+ }
1916
+ //#endregion
1917
+ //#region \0dsh-css:/root/projects/dsh-plugins-plus/packages/dataops-managed/src/client/SkillPlazaSection.module.css.mjs
1918
+ const css$3 = "._9FdZvq_section{box-sizing:border-box;flex-direction:column;width:100%;max-width:960px;margin:0 auto;display:flex}._9FdZvq_head{flex-direction:column;margin-bottom:8px;display:flex}._9FdZvq_title{color:var(--dsw-alias-label-primary);align-items:baseline;gap:8px;margin:0;font-size:14px;font-weight:500;line-height:20px;display:flex}._9FdZvq_titleCount{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400}._9FdZvq_status{text-align:center;color:var(--dsw-alias-label-tertiary);margin:0;padding:20px 0;font-size:13px;line-height:20px}._9FdZvq_tagBar{flex-wrap:wrap;gap:6px;margin-bottom:8px;display:flex}._9FdZvq_chip{background:var(--dsw-alias-bg-module-platform);height:24px;color:var(--dsw-alias-label-secondary);cursor:pointer;border:0;border-radius:12px;align-items:center;padding:0 10px;font-size:12px;line-height:18px;transition:background .12s;display:inline-flex}._9FdZvq_chip:hover{background:var(--dsw-alias-interactive-bg-hover)}._9FdZvq_chip:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}._9FdZvq_chipOn{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 14%, var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-state-business-primary);font-weight:500}._9FdZvq_notice{color:var(--dsw-alias-state-business-primary);margin:4px 0 0;font-size:12px;line-height:18px}._9FdZvq_cards{grid-template-columns:repeat(auto-fill,minmax(272px,1fr));grid-auto-rows:max-content;align-content:start;align-items:stretch;gap:12px;margin:0;padding:0;list-style:none;display:grid}._9FdZvq_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:10px;flex-direction:column;align-self:stretch;gap:10px;min-width:0;height:auto;padding:12px;display:flex}._9FdZvq_cardTop{justify-content:space-between;align-items:center;gap:10px;min-height:28px;display:flex}._9FdZvq_cardIcon{background:var(--dsw-alias-bg-layer-2);width:28px;height:28px;color:var(--dsw-alias-label-secondary);border-radius:50%;flex:none;place-items:center;display:grid}._9FdZvq_cardTitle{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;font-size:14px;font-weight:600;line-height:20px;overflow:hidden}._9FdZvq_mine{flex:none}._9FdZvq_cardDesc{min-height:36px;color:var(--dsw-alias-label-secondary);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:12px;line-height:18px;display:-webkit-box;overflow:hidden}._9FdZvq_cardMarks{flex-wrap:wrap;align-content:flex-start;gap:6px;min-height:20px;display:flex}._9FdZvq_mark{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 14%, var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-state-business-primary);white-space:nowrap;border:0;border-radius:4px;padding:2px 7px;font-size:11px;font-weight:500;line-height:16px}._9FdZvq_cardEnd{flex:none;align-items:center;gap:4px;margin-top:auto;padding-top:4px;display:flex}._9FdZvq_detailButton{margin-left:auto}._9FdZvq_detail{flex-direction:column;gap:12px;margin:0;display:flex}._9FdZvq_dialog{width:min(560px,100%)}._9FdZvq_detailRow{flex-direction:column;gap:2px;display:flex}._9FdZvq_detailLabel{color:var(--dsw-alias-label-caption);font-size:11px;line-height:16px}._9FdZvq_detailValue{max-height:160px;color:var(--dsw-alias-label-secondary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:12px;line-height:18px;overflow:auto}._9FdZvq_empty{text-align:center;flex-direction:column;align-items:center;gap:10px;padding:28px 16px 32px;display:flex}._9FdZvq_emptyIcon{border:.5px solid var(--dsw-alias-border-l3);width:56px;height:56px;color:var(--dsw-alias-label-tertiary);border-radius:14px;justify-content:center;align-items:center;display:inline-flex}._9FdZvq_emptyText{color:var(--dsw-alias-label-tertiary);margin:0;font-size:13px;line-height:20px}._9FdZvq_headRow{align-items:center;gap:6px;display:flex}._9FdZvq_headRow ._9FdZvq_title{flex:none}._9FdZvq_demoTag{background:var(--dsw-alias-state-warn-secondary);color:var(--dsw-alias-state-warn-primary);border-radius:4px;padding:1px 6px;font-size:10px;line-height:16px}._9FdZvq_tabs{border-bottom:1px solid var(--dsw-alias-border-l1);align-items:center;gap:2px;margin-bottom:8px;display:flex}._9FdZvq_tab{height:30px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-bottom:2px solid #0000;align-items:center;gap:5px;padding:0 9px;font-size:12px;display:inline-flex}._9FdZvq_tab:hover{color:var(--dsw-alias-label-primary)}._9FdZvq_tabActive{color:var(--dsw-alias-state-business-primary);border-bottom-color:var(--dsw-alias-state-business-primary);font-weight:500}._9FdZvq_tabCount{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-tertiary);border-radius:7px;padding:0 4px;font-size:10px;line-height:14px}._9FdZvq_tagTop{text-overflow:ellipsis;white-space:nowrap;border:1px solid var(--dsw-alias-border-l2);max-width:40%;color:var(--dsw-alias-label-secondary);border-radius:4px;flex:none;padding:1px 6px;font-size:10px;line-height:16px;overflow:hidden}._9FdZvq_sharedTag{color:#fff;background:#0e6b3a;border-radius:4px;padding:2px 7px;font-size:11px;font-weight:600;line-height:16px}._9FdZvq_pendingTag{color:#fff;background:#7a4a00;border-radius:4px;padding:2px 7px;font-size:11px;font-weight:600;line-height:16px}._9FdZvq_rejectedTag{color:#fff;background:#a8231b;border-radius:4px;padding:2px 7px;font-size:11px;font-weight:600;line-height:16px}._9FdZvq_reviewButton{margin-left:auto}._9FdZvq_reviewNote{border-left:3px solid var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-label-primary);margin:0;padding:6px 8px;font-size:11px;line-height:16px}._9FdZvq_auditDialog{width:min(640px,100vw - 32px)}._9FdZvq_audit{flex-direction:column;gap:14px;display:flex}._9FdZvq_auditFooter{justify-content:flex-end;gap:6px;display:flex}._9FdZvq_risk{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:6px;flex-direction:column;gap:8px;padding:10px;display:flex}._9FdZvq_riskHead{justify-content:space-between;align-items:center;gap:8px;font-size:12px;display:flex}._9FdZvq_riskEmpty,._9FdZvq_riskFailed{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:16px}._9FdZvq_riskFailed{color:var(--dsw-alias-state-error-primary)}._9FdZvq_riskSummary{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;align-items:center;gap:6px;font-size:12px;line-height:18px;display:flex}._9FdZvq_riskScore{color:var(--dsw-alias-label-primary);font-weight:600}._9FdZvq_severity{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);text-transform:uppercase;border-radius:3px;padding:0 5px;font-size:10px;line-height:16px}._9FdZvq_severity[data-severity=high],._9FdZvq_severity[data-severity=critical]{background:var(--dsw-alias-state-error-secondary);color:var(--dsw-alias-state-error-primary)}._9FdZvq_severity[data-severity=medium]{background:var(--dsw-alias-state-warn-secondary);color:var(--dsw-alias-state-warn-primary)}._9FdZvq_findings{flex-direction:column;gap:6px;margin:0;padding:0;list-style:none;display:flex}._9FdZvq_finding{flex-wrap:wrap;align-items:baseline;gap:5px;font-size:11px;line-height:16px;display:flex}._9FdZvq_findingText{color:var(--dsw-alias-label-secondary);flex-basis:100%}._9FdZvq_findingHint{color:var(--dsw-alias-label-tertiary);flex-basis:100%}._9FdZvq_noteField{color:var(--dsw-alias-label-secondary);flex-direction:column;gap:5px;font-size:12px;display:flex}._9FdZvq_noteField textarea{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);min-height:64px;color:var(--dsw-alias-label-primary);font:inherit;resize:vertical;border-radius:5px;padding:7px 9px}._9FdZvq_reviewError{color:var(--dsw-alias-state-error-primary);margin:0;font-size:11px}";
1919
+ const tagId$3 = "@sparkelf/dsh-dataops-managed/SkillPlazaSection.module.css";
1920
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
1921
+ const tag = document.createElement("style");
1922
+ tag.dataset.plugin = "@sparkelf/dsh-dataops-managed";
1923
+ tag.dataset.pluginCss = tagId$3;
1924
+ tag.textContent = css$3;
1925
+ document.head.appendChild(tag);
1926
+ }
1927
+ var SkillPlazaSection_module_css_default = {
1928
+ "reviewError": "_9FdZvq_reviewError",
1929
+ "chipOn": "_9FdZvq_chipOn",
1930
+ "tabs": "_9FdZvq_tabs",
1931
+ "sharedTag": "_9FdZvq_sharedTag",
1932
+ "noteField": "_9FdZvq_noteField",
1933
+ "mark": "_9FdZvq_mark",
1934
+ "severity": "_9FdZvq_severity",
1935
+ "findingText": "_9FdZvq_findingText",
1936
+ "section": "_9FdZvq_section",
1937
+ "head": "_9FdZvq_head",
1938
+ "cardMarks": "_9FdZvq_cardMarks",
1939
+ "cardTop": "_9FdZvq_cardTop",
1940
+ "cardTitle": "_9FdZvq_cardTitle",
1941
+ "detailLabel": "_9FdZvq_detailLabel",
1942
+ "riskHead": "_9FdZvq_riskHead",
1943
+ "riskSummary": "_9FdZvq_riskSummary",
1944
+ "tab": "_9FdZvq_tab",
1945
+ "reviewButton": "_9FdZvq_reviewButton",
1946
+ "headRow": "_9FdZvq_headRow",
1947
+ "risk": "_9FdZvq_risk",
1948
+ "tagBar": "_9FdZvq_tagBar",
1949
+ "riskEmpty": "_9FdZvq_riskEmpty",
1950
+ "emptyText": "_9FdZvq_emptyText",
1951
+ "cards": "_9FdZvq_cards",
1952
+ "cardEnd": "_9FdZvq_cardEnd",
1953
+ "chip": "_9FdZvq_chip",
1954
+ "notice": "_9FdZvq_notice",
1955
+ "cardIcon": "_9FdZvq_cardIcon",
1956
+ "detailButton": "_9FdZvq_detailButton",
1957
+ "status": "_9FdZvq_status",
1958
+ "tagTop": "_9FdZvq_tagTop",
1959
+ "title": "_9FdZvq_title",
1960
+ "detailRow": "_9FdZvq_detailRow",
1961
+ "detailValue": "_9FdZvq_detailValue",
1962
+ "card": "_9FdZvq_card",
1963
+ "detail": "_9FdZvq_detail",
1964
+ "tabActive": "_9FdZvq_tabActive",
1965
+ "tabCount": "_9FdZvq_tabCount",
1966
+ "pendingTag": "_9FdZvq_pendingTag",
1967
+ "rejectedTag": "_9FdZvq_rejectedTag",
1968
+ "demoTag": "_9FdZvq_demoTag",
1969
+ "empty": "_9FdZvq_empty",
1970
+ "auditDialog": "_9FdZvq_auditDialog",
1971
+ "audit": "_9FdZvq_audit",
1972
+ "auditFooter": "_9FdZvq_auditFooter",
1973
+ "riskScore": "_9FdZvq_riskScore",
1974
+ "finding": "_9FdZvq_finding",
1975
+ "findingHint": "_9FdZvq_findingHint",
1976
+ "riskFailed": "_9FdZvq_riskFailed",
1977
+ "emptyIcon": "_9FdZvq_emptyIcon",
1978
+ "reviewNote": "_9FdZvq_reviewNote",
1979
+ "cardDesc": "_9FdZvq_cardDesc",
1980
+ "dialog": "_9FdZvq_dialog",
1981
+ "findings": "_9FdZvq_findings",
1982
+ "titleCount": "_9FdZvq_titleCount",
1983
+ "mine": "_9FdZvq_mine"
1984
+ };
1985
+ //#endregion
1986
+ //#region src/client/SkillPlazaSection.tsx
1987
+ /** Tab counts from the same visible rows the cards render. */
1988
+ function countsOf(skills) {
1989
+ const counted = reviewCounts(skills);
1990
+ return {
1991
+ approved: counted.approved,
1992
+ pending: counted.pending,
1993
+ rejected: skills.filter((row) => row.reviewStatus === "rejected").length,
1994
+ total: counted.all
1995
+ };
1996
+ }
1997
+ /** One skill as a card in the plaza grid. */
1998
+ function PlazaCard({ skill, busy, canAudit, onInstall, onUnshare, onReview, t }) {
1999
+ const [open, setOpen] = (0, react.useState)(false);
2000
+ const status = normalizeReviewStatus(skill.reviewStatus);
2001
+ const tag = visibilityTag(skill);
2002
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
2003
+ className: SkillPlazaSection_module_css_default.card,
2004
+ "data-review-status": status,
2005
+ children: [
2006
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2007
+ className: SkillPlazaSection_module_css_default.cardTop,
2008
+ children: [
2009
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2010
+ className: SkillPlazaSection_module_css_default.cardIcon,
2011
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSkillOutlineRegular, { size: 16 })
2012
+ }),
2013
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2014
+ className: SkillPlazaSection_module_css_default.cardTitle,
2015
+ children: skill.name
2016
+ }),
2017
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2018
+ className: SkillPlazaSection_module_css_default.tagTop,
2019
+ title: tag.detail,
2020
+ children: tag.label
2021
+ })
2022
+ ]
2023
+ }),
2024
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2025
+ className: SkillPlazaSection_module_css_default.cardMarks,
2026
+ children: [
2027
+ skill.sharedByMe ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2028
+ className: SkillPlazaSection_module_css_default.sharedTag,
2029
+ children: t("plazaSharedTag")
2030
+ }) : null,
2031
+ status === "pending" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2032
+ className: SkillPlazaSection_module_css_default.pendingTag,
2033
+ children: t("plazaTabPending")
2034
+ }) : null,
2035
+ status === "rejected" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2036
+ className: SkillPlazaSection_module_css_default.rejectedTag,
2037
+ children: t("plazaReject")
2038
+ }) : null,
2039
+ skill.tags.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
802
2040
  className: SkillPlazaSection_module_css_default.mark,
803
- children: t("plazaMine")
804
- }) : null
2041
+ children: item
2042
+ }, item))
805
2043
  ]
806
2044
  }),
807
- skill.description === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2045
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
808
2046
  className: SkillPlazaSection_module_css_default.cardDesc,
809
- children: skill.description
810
- }),
811
- skill.tags.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
812
- className: SkillPlazaSection_module_css_default.cardMarks,
813
- children: skill.tags.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
814
- className: SkillPlazaSection_module_css_default.mark,
815
- children: item
816
- }, item))
2047
+ children: skill.description ?? ""
817
2048
  }),
2049
+ status === "rejected" && skill.reviewNote !== null && skill.reviewNote !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2050
+ className: SkillPlazaSection_module_css_default.reviewNote,
2051
+ children: skill.reviewNote
2052
+ }) : null,
818
2053
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
819
2054
  className: SkillPlazaSection_module_css_default.cardEnd,
820
2055
  children: [
2056
+ canAudit && status === "pending" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2057
+ variant: "outline",
2058
+ size: "sm",
2059
+ className: SkillPlazaSection_module_css_default.reviewButton,
2060
+ onClick: onReview,
2061
+ children: t("plazaReview")
2062
+ }) : null,
821
2063
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
822
2064
  variant: "outline",
823
2065
  size: "sm",
@@ -886,43 +2128,171 @@ window.__ModuleLoader__.load({
886
2128
  ]
887
2129
  });
888
2130
  }
2131
+ /** The AI report block inside the audit dialog. */
2132
+ function RiskReportView({ report, busy, failed, onScan, t }) {
2133
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2134
+ className: SkillPlazaSection_module_css_default.risk,
2135
+ children: [
2136
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2137
+ className: SkillPlazaSection_module_css_default.riskHead,
2138
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("plazaRiskTitle") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2139
+ variant: "outline",
2140
+ size: "sm",
2141
+ disabled: busy,
2142
+ onClick: onScan,
2143
+ children: busy ? t("plazaRiskScanning") : t("plazaRiskScan")
2144
+ })]
2145
+ }),
2146
+ failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2147
+ className: SkillPlazaSection_module_css_default.riskFailed,
2148
+ children: t("plazaRiskFailed")
2149
+ }) : null,
2150
+ report === null ? busy ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2151
+ className: SkillPlazaSection_module_css_default.riskEmpty,
2152
+ children: t("plazaRiskNone")
2153
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2154
+ className: SkillPlazaSection_module_css_default.riskSummary,
2155
+ children: [
2156
+ report.score === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2157
+ className: SkillPlazaSection_module_css_default.riskScore,
2158
+ children: [
2159
+ t("plazaRiskScore"),
2160
+ " ",
2161
+ report.score
2162
+ ]
2163
+ }),
2164
+ report.severity === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2165
+ className: SkillPlazaSection_module_css_default.severity,
2166
+ "data-severity": report.severity,
2167
+ children: report.severity
2168
+ }),
2169
+ report.summary === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: report.summary })
2170
+ ]
2171
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
2172
+ className: SkillPlazaSection_module_css_default.findings,
2173
+ children: report.findings.map((finding, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
2174
+ className: SkillPlazaSection_module_css_default.finding,
2175
+ children: [
2176
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2177
+ className: SkillPlazaSection_module_css_default.severity,
2178
+ "data-severity": finding.severity,
2179
+ children: finding.severity
2180
+ }),
2181
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: finding.category }),
2182
+ finding.evidence === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2183
+ className: SkillPlazaSection_module_css_default.findingText,
2184
+ children: finding.evidence
2185
+ }),
2186
+ finding.recommendation === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2187
+ className: SkillPlazaSection_module_css_default.findingHint,
2188
+ children: finding.recommendation
2189
+ })
2190
+ ]
2191
+ }, String(index)))
2192
+ })] })
2193
+ ]
2194
+ });
2195
+ }
889
2196
  /**
890
2197
  * Browse, share, and install skills other users published.
891
2198
  *
892
- * The section appears only where this plugin is installed: the skill center renders the slot,
893
- * and an empty slot renders nothing. Every action goes through this plugin's Host route, so a
894
- * workspace without DataOps never reaches these calls.
895
- * @param props - Skill center injection values.
896
- * @returns The plaza section, or nothing before injection.
2199
+ * Review is a permissioned overlay on the same plaza: the tab strip and the audit dialog only
2200
+ * appear when the Host says the caller may review, and the Host additionally narrows the pending
2201
+ * list to the caller's department subtree.
897
2202
  */
898
2203
  function SkillPlazaSection(props) {
899
2204
  const { t } = props;
900
2205
  const [state, setState] = (0, react.useState)({ kind: "loading" });
2206
+ const [context, setContext] = (0, react.useState)(void 0);
2207
+ const [tab, setTab] = (0, react.useState)("approved");
901
2208
  const [tag, setTag] = (0, react.useState)("");
902
2209
  const [busy, setBusy] = (0, react.useState)(false);
903
- const query = props.query ?? "";
904
2210
  const [notice, setNotice] = (0, react.useState)(null);
2211
+ const [audit, setAudit] = (0, react.useState)(null);
2212
+ const [risk, setRisk] = (0, react.useState)(null);
2213
+ const [riskBusy, setRiskBusy] = (0, react.useState)(false);
2214
+ const [riskFailed, setRiskFailed] = (0, react.useState)(false);
2215
+ const [reviewNote, setReviewNote] = (0, react.useState)("");
2216
+ const [reviewBusy, setReviewBusy] = (0, react.useState)(false);
2217
+ const [reviewError, setReviewError] = (0, react.useState)(null);
2218
+ const query = props.query ?? "";
2219
+ const allowed = canReview(context);
2220
+ (0, react.useEffect)(() => {
2221
+ let live = true;
2222
+ fetchReviewContext().then((next) => {
2223
+ if (live) setContext(next);
2224
+ }).catch(() => {
2225
+ if (live) setContext(null);
2226
+ });
2227
+ return () => {
2228
+ live = false;
2229
+ };
2230
+ }, []);
905
2231
  const load = (0, react.useCallback)(async (filter) => {
906
2232
  try {
907
- const listing = await fetchPlaza(filter);
908
- setState(listing === null ? { kind: "unavailable" } : {
2233
+ if (reviewDemoMode()) {
2234
+ const demo = demoPlazaSkills();
2235
+ setState({
2236
+ kind: "ready",
2237
+ listing: {
2238
+ skills: [...filterByReview(demo, filter.tab)],
2239
+ tags: [
2240
+ "性能",
2241
+ "数据",
2242
+ "数据库",
2243
+ "质量",
2244
+ "经营",
2245
+ "日报",
2246
+ "客户"
2247
+ ],
2248
+ counts: countsOf(demo)
2249
+ }
2250
+ });
2251
+ return;
2252
+ }
2253
+ const listing = await fetchPlaza({
2254
+ tag: filter.tag,
2255
+ query: filter.query,
2256
+ status: allowed ? "all" : "approved"
2257
+ });
2258
+ if (listing === null) {
2259
+ setState({ kind: "unavailable" });
2260
+ return;
2261
+ }
2262
+ const visible = allowed ? listing.skills : listing.skills.filter((row) => normalizeReviewStatus(row.reviewStatus) === "approved");
2263
+ setState({
909
2264
  kind: "ready",
910
- listing
2265
+ listing: {
2266
+ ...listing,
2267
+ skills: [...filterByReview(visible, filter.tab)],
2268
+ counts: countsOf(visible)
2269
+ }
911
2270
  });
912
2271
  } catch {
913
2272
  setState({ kind: "failed" });
914
2273
  }
915
- }, []);
2274
+ }, [allowed]);
916
2275
  (0, react.useEffect)(() => {
917
- load({ query });
918
- }, [load, query]);
919
- const refresh = (0, react.useCallback)(async (nextTag = tag, nextQuery = query) => {
2276
+ load({
2277
+ tab,
2278
+ tag,
2279
+ query
2280
+ });
2281
+ }, [
2282
+ load,
2283
+ tab,
2284
+ tag,
2285
+ query
2286
+ ]);
2287
+ const refresh = (0, react.useCallback)(async (nextTab = tab, nextTag = tag) => {
920
2288
  await load({
2289
+ tab: nextTab,
921
2290
  tag: nextTag,
922
- query: nextQuery
2291
+ query
923
2292
  });
924
2293
  }, [
925
2294
  load,
2295
+ tab,
926
2296
  tag,
927
2297
  query
928
2298
  ]);
@@ -939,16 +2309,75 @@ window.__ModuleLoader__.load({
939
2309
  setBusy(false);
940
2310
  }
941
2311
  }, [refresh, t]);
2312
+ const runScan = async (skill) => {
2313
+ setRiskBusy(true);
2314
+ setRiskFailed(false);
2315
+ try {
2316
+ const report = await scanReview("skill", [skill.id]);
2317
+ if (report === null) setRiskFailed(true);
2318
+ else setRisk(report);
2319
+ } catch {
2320
+ setRiskFailed(true);
2321
+ } finally {
2322
+ setRiskBusy(false);
2323
+ }
2324
+ };
2325
+ const openAudit = (skill) => {
2326
+ setAudit(skill);
2327
+ setRisk(skill.riskReport ?? null);
2328
+ setRiskFailed(false);
2329
+ setReviewNote("");
2330
+ setReviewError(null);
2331
+ if (skill.riskReport === null || skill.riskReport === void 0) runScan(skill);
2332
+ };
2333
+ const submitReview = async (decision) => {
2334
+ if (audit === null || reviewBusy) return;
2335
+ if (decision === "reject" && reviewNote.trim() === "") {
2336
+ setReviewError(t === void 0 ? null : t("plazaReviewNote"));
2337
+ return;
2338
+ }
2339
+ setReviewBusy(true);
2340
+ setReviewError(null);
2341
+ try {
2342
+ if (!await decideReview("skill", [audit.id], decision, reviewNote.trim())) {
2343
+ setReviewError(t === void 0 ? null : t("plazaReviewFailed"));
2344
+ return;
2345
+ }
2346
+ setAudit(null);
2347
+ setNotice(t === void 0 ? null : t("plazaReviewDone"));
2348
+ await refresh("pending");
2349
+ } catch {
2350
+ setReviewError(t === void 0 ? null : t("plazaReviewFailed"));
2351
+ } finally {
2352
+ setReviewBusy(false);
2353
+ }
2354
+ };
942
2355
  if (t === void 0) return null;
943
2356
  if (state.kind === "loading") return null;
944
2357
  const head = (count) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
945
2358
  className: SkillPlazaSection_module_css_default.head,
946
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h2", {
947
- className: SkillPlazaSection_module_css_default.title,
948
- children: [t("plazaTitle"), count === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
949
- className: SkillPlazaSection_module_css_default.titleCount,
950
- children: count
951
- })]
2359
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2360
+ className: SkillPlazaSection_module_css_default.headRow,
2361
+ children: [
2362
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h2", {
2363
+ className: SkillPlazaSection_module_css_default.title,
2364
+ children: [t("plazaTitle"), count === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2365
+ className: SkillPlazaSection_module_css_default.titleCount,
2366
+ children: count
2367
+ })]
2368
+ }),
2369
+ reviewDemoMode() ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2370
+ className: SkillPlazaSection_module_css_default.demoTag,
2371
+ children: t("plazaDemo")
2372
+ }) : null,
2373
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NotificationBell, {
2374
+ label: t("plazaTitle") + "通知",
2375
+ onOpen: (item) => {
2376
+ const next = notificationTab(item);
2377
+ if (next !== void 0) setTab(next);
2378
+ }
2379
+ })
2380
+ ]
952
2381
  })
953
2382
  });
954
2383
  if (state.kind === "unavailable") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
@@ -985,10 +2414,40 @@ window.__ModuleLoader__.load({
985
2414
  })]
986
2415
  });
987
2416
  const { listing } = state;
2417
+ const counts = listing.counts === void 0 ? reviewCounts(listing.skills) : {
2418
+ approved: listing.counts.approved,
2419
+ pending: listing.counts.pending,
2420
+ all: listing.counts.total
2421
+ };
2422
+ const tabs = reviewTabsFor(context);
2423
+ const tabLabel = (item) => item === "approved" ? t("plazaTabApproved") : item === "pending" ? t("plazaTabPending") : t("plazaTabAll");
988
2424
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
989
2425
  className: SkillPlazaSection_module_css_default.section,
990
2426
  children: [
991
- head(listing.skills.length),
2427
+ head(counts[tab]),
2428
+ tabs.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2429
+ className: SkillPlazaSection_module_css_default.tabs,
2430
+ role: "tablist",
2431
+ "aria-label": t("plazaTitle"),
2432
+ children: tabs.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2433
+ type: "button",
2434
+ role: "tab",
2435
+ "aria-selected": tab === item,
2436
+ className: tab === item ? SkillPlazaSection_module_css_default.tab + " " + SkillPlazaSection_module_css_default.tabActive : SkillPlazaSection_module_css_default.tab,
2437
+ onClick: () => {
2438
+ setTab(item);
2439
+ load({
2440
+ tab: item,
2441
+ tag,
2442
+ query
2443
+ });
2444
+ },
2445
+ children: [tabLabel(item), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2446
+ className: SkillPlazaSection_module_css_default.tabCount,
2447
+ children: counts[item]
2448
+ })]
2449
+ }, item))
2450
+ }) : null,
992
2451
  listing.tags.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
993
2452
  className: SkillPlazaSection_module_css_default.tagBar,
994
2453
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -996,7 +2455,7 @@ window.__ModuleLoader__.load({
996
2455
  className: tag === "" ? SkillPlazaSection_module_css_default.chip + " " + SkillPlazaSection_module_css_default.chipOn : SkillPlazaSection_module_css_default.chip,
997
2456
  onClick: () => {
998
2457
  setTag("");
999
- refresh("", query);
2458
+ refresh(tab, "");
1000
2459
  },
1001
2460
  children: t("plazaAllTags")
1002
2461
  }), listing.tags.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -1004,7 +2463,7 @@ window.__ModuleLoader__.load({
1004
2463
  className: tag === item ? SkillPlazaSection_module_css_default.chip + " " + SkillPlazaSection_module_css_default.chipOn : SkillPlazaSection_module_css_default.chip,
1005
2464
  onClick: () => {
1006
2465
  setTag(item);
1007
- refresh(item, query);
2466
+ refresh(tab, item);
1008
2467
  },
1009
2468
  children: item
1010
2469
  }, item))]
@@ -1020,7 +2479,7 @@ window.__ModuleLoader__.load({
1020
2479
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconShareOutlineRegular, { size: 28 })
1021
2480
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1022
2481
  className: SkillPlazaSection_module_css_default.emptyText,
1023
- children: t("plazaEmpty")
2482
+ children: tab === "pending" ? t("plazaPendingEmpty") : t("plazaEmpty")
1024
2483
  })]
1025
2484
  }) : null,
1026
2485
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
@@ -1028,6 +2487,7 @@ window.__ModuleLoader__.load({
1028
2487
  children: listing.skills.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlazaCard, {
1029
2488
  skill,
1030
2489
  busy,
2490
+ canAudit: allowed,
1031
2491
  t,
1032
2492
  onInstall: () => {
1033
2493
  act({
@@ -1040,67 +2500,151 @@ window.__ModuleLoader__.load({
1040
2500
  action: "unshare",
1041
2501
  id: skill.id
1042
2502
  }, t("plazaUnshared"));
2503
+ },
2504
+ onReview: () => {
2505
+ openAudit(skill);
1043
2506
  }
1044
2507
  }, skill.id))
2508
+ }),
2509
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
2510
+ open: audit !== null,
2511
+ onClose: () => {
2512
+ if (!reviewBusy) setAudit(null);
2513
+ },
2514
+ title: t("plazaAuditTitle"),
2515
+ closeLabel: t("plazaClose"),
2516
+ className: SkillPlazaSection_module_css_default.auditDialog,
2517
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2518
+ className: SkillPlazaSection_module_css_default.auditFooter,
2519
+ children: [
2520
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2521
+ variant: "outline",
2522
+ disabled: reviewBusy,
2523
+ onClick: () => {
2524
+ setAudit(null);
2525
+ },
2526
+ children: t("plazaClose")
2527
+ }),
2528
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2529
+ variant: "outline",
2530
+ disabled: reviewBusy,
2531
+ onClick: () => {
2532
+ submitReview("reject");
2533
+ },
2534
+ children: t("plazaReject")
2535
+ }),
2536
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2537
+ variant: "primary",
2538
+ disabled: reviewBusy,
2539
+ onClick: () => {
2540
+ submitReview("approve");
2541
+ },
2542
+ children: t("plazaApprove")
2543
+ })
2544
+ ]
2545
+ }),
2546
+ children: audit === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2547
+ className: SkillPlazaSection_module_css_default.audit,
2548
+ children: [
2549
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
2550
+ className: SkillPlazaSection_module_css_default.detail,
2551
+ children: [
2552
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2553
+ className: SkillPlazaSection_module_css_default.detailRow,
2554
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
2555
+ className: SkillPlazaSection_module_css_default.detailLabel,
2556
+ children: t("plazaAuditSource")
2557
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dd", {
2558
+ className: SkillPlazaSection_module_css_default.detailValue,
2559
+ children: [
2560
+ audit.ownerName ?? "—",
2561
+ " · ",
2562
+ visibilityTag(audit).detail
2563
+ ]
2564
+ })]
2565
+ }),
2566
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2567
+ className: SkillPlazaSection_module_css_default.detailRow,
2568
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
2569
+ className: SkillPlazaSection_module_css_default.detailLabel,
2570
+ children: t("plazaAuditVisibility")
2571
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
2572
+ className: SkillPlazaSection_module_css_default.detailValue,
2573
+ children: visibilityTag(audit).label
2574
+ })]
2575
+ }),
2576
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2577
+ className: SkillPlazaSection_module_css_default.detailRow,
2578
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
2579
+ className: SkillPlazaSection_module_css_default.detailLabel,
2580
+ children: t("plazaShowDetail")
2581
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
2582
+ className: SkillPlazaSection_module_css_default.detailValue,
2583
+ children: audit.description ?? "—"
2584
+ })]
2585
+ })
2586
+ ]
2587
+ }),
2588
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RiskReportView, {
2589
+ report: risk,
2590
+ busy: riskBusy,
2591
+ failed: riskFailed,
2592
+ onScan: () => {
2593
+ runScan(audit);
2594
+ },
2595
+ t
2596
+ }),
2597
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2598
+ className: SkillPlazaSection_module_css_default.noteField,
2599
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("plazaReviewNote") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
2600
+ value: reviewNote,
2601
+ placeholder: t("plazaReviewNotePlaceholder"),
2602
+ onChange: (event) => {
2603
+ setReviewNote(event.target.value);
2604
+ }
2605
+ })]
2606
+ }),
2607
+ reviewError === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2608
+ className: SkillPlazaSection_module_css_default.reviewError,
2609
+ role: "alert",
2610
+ children: reviewError
2611
+ })
2612
+ ]
2613
+ })
1045
2614
  })
1046
2615
  ]
1047
2616
  });
1048
2617
  }
1049
2618
  //#endregion
1050
- //#region \0dsh-css:/tmp/wt-rebase/packages/dataops-managed/src/client/WanxiangBrand.module.css.mjs
1051
- const css = ".kO6bEq_mark{color:inherit;flex:none;display:block;overflow:visible}.kO6bEq_animatedMark{cursor:pointer}.kO6bEq_mark g{transform-box:fill-box;transform-origin:50%}.kO6bEq_sidebarName{min-width:0;height:24px;color:inherit;align-items:center;gap:6px;display:inline-flex}.kO6bEq_sidebarName>svg:first-child{flex:0 auto;min-width:0;max-width:102px}.kO6bEq_harness{flex:none;display:block;overflow:visible}@keyframes kO6bEq_unit-top{0%,to{transform:translate(0)}50%{transform:translateY(-2px)}}@keyframes kO6bEq_unit-left{0%,to{transform:translate(0)}50%{transform:translate(-2px,1px)}}@keyframes kO6bEq_unit-right{0%,to{transform:translate(0)}50%{transform:translate(2px,1px)}}@keyframes kO6bEq_unit-core{0%,to{transform:translate(0)}50%{transform:translateY(2px)}}@media (hover:hover) and (prefers-reduced-motion:no-preference){.kO6bEq_animatedMark:hover .kO6bEq_unitTop{animation:1.6s ease-in-out infinite kO6bEq_unit-top}.kO6bEq_animatedMark:hover .kO6bEq_unitLeft{animation:1.6s ease-in-out infinite kO6bEq_unit-left}.kO6bEq_animatedMark:hover .kO6bEq_unitRight{animation:1.6s ease-in-out infinite kO6bEq_unit-right}.kO6bEq_animatedMark:hover .kO6bEq_unitCore{animation:1.6s ease-in-out infinite kO6bEq_unit-core}}@media (prefers-reduced-motion:reduce){.kO6bEq_animatedMark,.kO6bEq_animatedMark g{animation:none!important;transform:none!important}}";
1052
- const tagId = "@sparkelf/dsh-dataops-managed/WanxiangBrand.module.css";
1053
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
2619
+ //#region \0dsh-css:/root/projects/dsh-plugins-plus/packages/dataops-managed/src/client/WanxiangBrand.module.css.mjs
2620
+ const css$2 = ".s0HPxG_mark{color:inherit;flex:none;display:block;overflow:visible}.s0HPxG_animatedMark{cursor:pointer}.s0HPxG_mark g{transform-box:fill-box;transform-origin:50%}.s0HPxG_sidebarName{min-width:0;height:24px;color:inherit;align-items:center;gap:6px;display:inline-flex}.s0HPxG_sidebarName>:first-child{flex:0 auto;min-width:0;max-width:102px}.s0HPxG_wordmark{letter-spacing:.01em;white-space:nowrap;text-overflow:ellipsis;font-family:PingFang SC,HarmonyOS Sans SC,Microsoft YaHei,Noto Sans CJK SC,Source Han Sans SC,sans-serif;font-weight:600;line-height:1;display:block;overflow:hidden}.s0HPxG_harness{flex:none;display:block;overflow:visible}@keyframes s0HPxG_unit-top{0%,to{transform:translate(0)}50%{transform:translateY(-2px)}}@keyframes s0HPxG_unit-left{0%,to{transform:translate(0)}50%{transform:translate(-2px,1px)}}@keyframes s0HPxG_unit-right{0%,to{transform:translate(0)}50%{transform:translate(2px,1px)}}@keyframes s0HPxG_unit-core{0%,to{transform:translate(0)}50%{transform:translateY(2px)}}@media (hover:hover) and (prefers-reduced-motion:no-preference){.s0HPxG_animatedMark:hover .s0HPxG_unitTop{animation:1.6s ease-in-out infinite s0HPxG_unit-top}.s0HPxG_animatedMark:hover .s0HPxG_unitLeft{animation:1.6s ease-in-out infinite s0HPxG_unit-left}.s0HPxG_animatedMark:hover .s0HPxG_unitRight{animation:1.6s ease-in-out infinite s0HPxG_unit-right}.s0HPxG_animatedMark:hover .s0HPxG_unitCore{animation:1.6s ease-in-out infinite s0HPxG_unit-core}}@media (prefers-reduced-motion:reduce){.s0HPxG_animatedMark,.s0HPxG_animatedMark g{animation:none!important;transform:none!important}}";
2621
+ const tagId$2 = "@sparkelf/dsh-dataops-managed/WanxiangBrand.module.css";
2622
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
1054
2623
  const tag = document.createElement("style");
1055
2624
  tag.dataset.plugin = "@sparkelf/dsh-dataops-managed";
1056
- tag.dataset.pluginCss = tagId;
1057
- tag.textContent = css;
2625
+ tag.dataset.pluginCss = tagId$2;
2626
+ tag.textContent = css$2;
1058
2627
  document.head.appendChild(tag);
1059
2628
  }
1060
2629
  var WanxiangBrand_module_css_default = {
1061
- "sidebarName": "kO6bEq_sidebarName",
1062
- "mark": "kO6bEq_mark",
1063
- "unit-right": "kO6bEq_unit-right",
1064
- "harness": "kO6bEq_harness",
1065
- "unit-top": "kO6bEq_unit-top",
1066
- "unit-core": "kO6bEq_unit-core",
1067
- "unitLeft": "kO6bEq_unitLeft",
1068
- "unit-left": "kO6bEq_unit-left",
1069
- "unitTop": "kO6bEq_unitTop",
1070
- "unitCore": "kO6bEq_unitCore",
1071
- "animatedMark": "kO6bEq_animatedMark",
1072
- "unitRight": "kO6bEq_unitRight"
2630
+ "unitRight": "s0HPxG_unitRight",
2631
+ "unitCore": "s0HPxG_unitCore",
2632
+ "animatedMark": "s0HPxG_animatedMark",
2633
+ "wordmark": "s0HPxG_wordmark",
2634
+ "unit-left": "s0HPxG_unit-left",
2635
+ "unitLeft": "s0HPxG_unitLeft",
2636
+ "unit-top": "s0HPxG_unit-top",
2637
+ "unit-core": "s0HPxG_unit-core",
2638
+ "sidebarName": "s0HPxG_sidebarName",
2639
+ "mark": "s0HPxG_mark",
2640
+ "unitTop": "s0HPxG_unitTop",
2641
+ "harness": "s0HPxG_harness",
2642
+ "unit-right": "s0HPxG_unit-right"
1073
2643
  };
1074
2644
  //#endregion
1075
2645
  //#region src/client/WanxiangBrand.tsx
1076
- const WORDMARK_VIEWBOX = "1.64 115.64 5927.36 933.38";
1077
- const WORDMARK_RATIO = 5927.36 / 933.38;
1078
- const WORDMARK_PATHS = [
1079
- {
1080
- "d": "M62 765V691H333C326 434 312 123 34 -24C53 -38 77 -62 89 -82C287 28 361 217 390 414H767C752 147 735 37 705 9C693 -2 681 -4 657 -3C631 -3 558 -3 483 4C498 -17 508 -48 509 -70C578 -74 648 -75 686 -72C724 -70 749 -62 772 -36C811 5 829 126 846 450C847 460 847 487 847 487H399C406 556 409 625 411 691H939V765Z",
1081
- "transform": "translate(0.00 940.00) scale(0.96000 -0.94000)"
1082
- },
1083
- {
1084
- "d": "M546 474H850V300H546ZM546 542V710H850V542ZM546 231H850V57H546ZM473 781V-73H546V-12H850V-70H926V781ZM214 840V626H52V554H205C170 416 99 258 29 175C41 157 60 127 68 107C122 176 175 287 214 402V-79H287V378C325 329 370 267 389 234L435 295C413 322 322 429 287 464V554H430V626H287V840Z",
1085
- "transform": "translate(1002.00 940.00) scale(0.96000 -0.94000)"
1086
- },
1087
- {
1088
- "d": "M443 821C425 782 393 723 368 688L417 664C443 697 477 747 506 793ZM88 793C114 751 141 696 150 661L207 686C198 722 171 776 143 815ZM410 260C387 208 355 164 317 126C279 145 240 164 203 180C217 204 233 231 247 260ZM110 153C159 134 214 109 264 83C200 37 123 5 41 -14C54 -28 70 -54 77 -72C169 -47 254 -8 326 50C359 30 389 11 412 -6L460 43C437 59 408 77 375 95C428 152 470 222 495 309L454 326L442 323H278L300 375L233 387C226 367 216 345 206 323H70V260H175C154 220 131 183 110 153ZM257 841V654H50V592H234C186 527 109 465 39 435C54 421 71 395 80 378C141 411 207 467 257 526V404H327V540C375 505 436 458 461 435L503 489C479 506 391 562 342 592H531V654H327V841ZM629 832C604 656 559 488 481 383C497 373 526 349 538 337C564 374 586 418 606 467C628 369 657 278 694 199C638 104 560 31 451 -22C465 -37 486 -67 493 -83C595 -28 672 41 731 129C781 44 843 -24 921 -71C933 -52 955 -26 972 -12C888 33 822 106 771 198C824 301 858 426 880 576H948V646H663C677 702 689 761 698 821ZM809 576C793 461 769 361 733 276C695 366 667 468 648 576Z",
1089
- "transform": "translate(2004.00 940.00) scale(0.96000 -0.94000)"
1090
- },
1091
- {
1092
- "d": "M484 238V-81H550V-40H858V-77H927V238H734V362H958V427H734V537H923V796H395V494C395 335 386 117 282 -37C299 -45 330 -67 344 -79C427 43 455 213 464 362H663V238ZM468 731H851V603H468ZM468 537H663V427H467L468 494ZM550 22V174H858V22ZM167 839V638H42V568H167V349C115 333 67 319 29 309L49 235L167 273V14C167 0 162 -4 150 -4C138 -5 99 -5 56 -4C65 -24 75 -55 77 -73C140 -74 179 -71 203 -59C228 -48 237 -27 237 14V296L352 334L341 403L237 370V568H350V638H237V839Z",
1093
- "transform": "translate(3006.00 940.00) scale(0.96000 -0.94000)"
1094
- },
1095
- {
1096
- "d": "M174 630C213 556 252 459 266 399L337 424C323 482 282 578 242 650ZM755 655C730 582 684 480 646 417L711 396C750 456 797 552 834 633ZM52 348V273H459V-79H537V273H949V348H537V698H893V773H105V698H459V348Z",
1097
- "transform": "translate(4008.00 940.00) scale(0.96000 -0.94000)"
1098
- },
1099
- {
1100
- "d": "M179 342V-79H255V-25H741V-77H821V342ZM255 48V270H741V48ZM126 426C165 441 224 443 800 474C825 443 846 414 861 388L925 434C873 518 756 641 658 727L599 687C647 644 699 591 745 540L231 516C320 598 410 701 490 811L415 844C336 720 219 593 183 559C149 526 124 505 101 500C110 480 122 442 126 426Z",
1101
- "transform": "translate(5010.00 940.00) scale(0.96000 -0.94000)"
1102
- }
1103
- ];
2646
+ /** User-visible platform name shared by the header wordmark and its accessible name. */
2647
+ const BRAND_NAME = "万相AI中台";
1104
2648
  const HARNESS_PATHS = [
1105
2649
  {
1106
2650
  "d": "M132.848 8.93205H134.08V16.137H132.848V8.93205ZM136.5 8.93205H137.732V16.137H136.5V8.93205ZM133.365 13.024V11.99H137.193V13.024H133.365Z",
@@ -1189,30 +2733,22 @@ window.__ModuleLoader__.load({
1189
2733
  ]
1190
2734
  });
1191
2735
  }
1192
- /** Approved rounded-Heiti artwork, stored as paths with no runtime font dependency. */
2736
+ /**
2737
+ * Platform wordmark rendered as text.
2738
+ *
2739
+ * It used to be glyph artwork for the previous platform name, which cannot express the new
2740
+ * name without redrawing every path. Text keeps the visible header, the accessible name, and
2741
+ * the browser title on one string, and inherits the host's CJK font stack through `font-family`.
2742
+ */
1193
2743
  function WanxiangWordmark({ height, accessible = false, className }) {
1194
- const accessibility = accessible ? {
1195
- role: "img",
1196
- "aria-label": "万相数据平台"
1197
- } : { "aria-hidden": true };
1198
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1199
- width: height * WORDMARK_RATIO,
1200
- height,
1201
- className,
1202
- viewBox: WORDMARK_VIEWBOX,
1203
- ...accessibility,
1204
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", {
1205
- fill: "currentColor",
1206
- stroke: "currentColor",
1207
- strokeWidth: "42",
1208
- strokeLinejoin: "round",
1209
- strokeLinecap: "round",
1210
- paintOrder: "stroke fill",
1211
- children: WORDMARK_PATHS.map((path) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1212
- d: path.d,
1213
- transform: path.transform
1214
- }, path.transform))
1215
- })
2744
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2745
+ className: classes(WanxiangBrand_module_css_default.wordmark, className),
2746
+ style: { fontSize: height },
2747
+ ...accessible ? {
2748
+ role: "img",
2749
+ "aria-label": BRAND_NAME
2750
+ } : {},
2751
+ children: BRAND_NAME
1216
2752
  });
1217
2753
  }
1218
2754
  /** Exact HARNESS badge artwork extracted from the current DSH BrandWordmark. */
@@ -1254,7 +2790,7 @@ window.__ModuleLoader__.load({
1254
2790
  }
1255
2791
  //#endregion
1256
2792
  //#region src/client/install-branding.tsx
1257
- const BRAND_TITLE = "万相数据平台 Harness";
2793
+ const BRAND_TITLE = "万相AI中台 Harness";
1258
2794
  const SLOT_PRIORITY = -100;
1259
2795
  const FAVICON_ID = "dataops-wanxiang-favicon";
1260
2796
  const FAVICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 58"><style>:root{color:#1b1b1d}@media(prefers-color-scheme:dark){:root{color:#f7f7f8}}</style><g transform="translate(15 0)" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"><path d="M12 0 24 7 12 14 0 7Z" fill="none"/><path d="M0 7 12 14v16L0 23Z" fill="currentColor"/><path d="M12 14 24 7v16L12 30Z" fill="currentColor" fill-opacity=".18"/></g><g transform="translate(0 16)" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"><path d="M12 0 24 7 12 14 0 7Z" fill="none"/><path d="M0 7 12 14v16L0 23Z" fill="currentColor"/><path d="M12 14 24 7v16L12 30Z" fill="currentColor" fill-opacity=".18"/></g><g transform="translate(30 16)" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"><path d="M12 0 24 7 12 14 0 7Z" fill="none"/><path d="M0 7 12 14v16L0 23Z" fill="currentColor"/><path d="M12 14 24 7v16L12 30Z" fill="currentColor" fill-opacity=".18"/></g><g transform="translate(15 27)" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"><path d="M12 0 24 7 12 14 0 7Z" fill="none"/><path d="M0 7 12 14v16L0 23Z" fill="currentColor"/><path d="M12 14 24 7v16L12 30Z" fill="currentColor" fill-opacity=".18"/></g></svg>`;
@@ -1340,50 +2876,2004 @@ window.__ModuleLoader__.load({
1340
2876
  }, "dataops-managed: Wanxiang shell branding");
1341
2877
  }
1342
2878
  //#endregion
1343
- //#region src/client/locales.ts
1344
- /** English managed DataOps Settings copy. */
1345
- const en = {
1346
- nav: "DataOps",
1347
- title: "DataOps",
1348
- managed: "Connected to DataOps",
1349
- unavailable: "DataOps is not connected.",
1350
- loadFailed: "Loading failed. Try again.",
1351
- brandingTitle: "Wanxiang branding",
1352
- brandingToggle: "Wanxiang branding",
1353
- limitsTitle: "File size limits",
1354
- limitsFileReadLabel: "Largest file to read (MiB)",
1355
- limitsUploadLabel: "Largest file to upload (MiB)",
1356
- limitsInvalid: "Enter a size in MiB.",
1357
- limitsSave: "Save",
1358
- limitsSaving: "Saving…",
1359
- timeoutTitle: "DataOps query timeout",
1360
- timeoutLabel: "Longest single query (seconds)",
1361
- timeoutInvalid: "Enter 1 to 3600 seconds.",
1362
- defaultsTitle: "Default configuration",
1363
- defaultsPublish: "Publish",
1364
- defaultsPublishing: "Publishing…",
1365
- defaultsPublishedAt: "Last published",
1366
- defaultsNeverPublished: "Not published yet",
1367
- defaultsNotAdmin: "Administrators only",
1368
- plazaTitle: "Skill plaza",
1369
- plazaAllTags: "All tags",
1370
- plazaMine: "Shared by me",
1371
- plazaShowDetail: "Details",
1372
- plazaClose: "Close",
1373
- plazaDetailUpdated: "Updated",
1374
- plazaDetailBody: "Skill body",
1375
- plazaInstall: "Install",
1376
- plazaInstalled: "Installed. It is now in your skill list.",
1377
- plazaUnshare: "Stop sharing",
1378
- plazaUnshared: "Removed from the plaza. Users who installed it keep their copy.",
1379
- plazaEmpty: "No skills have been shared yet.",
1380
- plazaUnavailable: "The skill plaza is available once this workspace is connected to DataOps.",
1381
- plazaLoadFailed: "The skill plaza could not be loaded. Try again.",
1382
- plazaActionFailed: "The action could not be completed. Try again.",
1383
- plazaRetry: "Try again"
2879
+ //#region src/client/ReloginPrompt.tsx
2880
+ /**
2881
+ * The "sign in again" prompt shown when this workspace's DataOps session has ended.
2882
+ *
2883
+ * The failure it reports used to be invisible. Every DataOps call handles a 401 by degrading to
2884
+ * an empty result, which is right for one panel but wrong for the operator: settings, shared
2885
+ * models and the skill plaza all render as "nothing here" while the real cause is that nobody is
2886
+ * signed in any more. This prompt names that state once, with the one action that clears it.
2887
+ *
2888
+ * It reports and nothing more. It does not retry, expire, or re-authenticate — the portal owns
2889
+ * the login form and is the only thing that can issue a session. It also cannot navigate the
2890
+ * operator there: the portal is a different origin, so this frame cannot read or drive its
2891
+ * window, and the gateway does not forward the browser's origin to the workspace. Guessing a
2892
+ * URL from inside here would open the wrong page on some deployments, so the dialog tells the
2893
+ * operator which window to switch to instead of pretending it can take them.
2894
+ *
2895
+ * @module ReloginPrompt
2896
+ */
2897
+ /** How often to re-read the observed auth state. */
2898
+ const POLL_MS$1 = 15e3;
2899
+ /**
2900
+ * Read the observed credential state from the Host.
2901
+ *
2902
+ * A failed read says nothing about the credential: this route is served by the same process that
2903
+ * serves the page, so an unreachable route means the Web server is busy or restarting, not that
2904
+ * anyone was signed out. Returning `undefined` keeps a transient error from raising a sign-in
2905
+ * prompt, which would be a lie the operator cannot act on.
2906
+ *
2907
+ * @returns the reported state, or `undefined` when it could not be read.
2908
+ */
2909
+ const readAuthState = async () => {
2910
+ try {
2911
+ const response = await fetch(new URL("/integrations/dataops/auth-state", window.location.origin), { credentials: "same-origin" });
2912
+ if (!response.ok) return void 0;
2913
+ const state = (await response.json()).state;
2914
+ return state === "expired" || state === "valid" || state === "unknown" ? state : void 0;
2915
+ } catch {
2916
+ return;
2917
+ }
1384
2918
  };
1385
- /** Chinese managed DataOps Settings copy. */
1386
- const zh = {
2919
+ /**
2920
+ * Watch for an ended DataOps session and say so.
2921
+ *
2922
+ * @param props.t - localized copy resolver.
2923
+ * @returns the dialog while the session is known to have ended, otherwise `null`.
2924
+ */
2925
+ function ReloginPrompt(props) {
2926
+ const { t } = props;
2927
+ const [expired, setExpired] = (0, react.useState)(false);
2928
+ const [dismissed, setDismissed] = (0, react.useState)(false);
2929
+ (0, react.useEffect)(() => {
2930
+ let cancelled = false;
2931
+ const tick = async () => {
2932
+ const state = await readAuthState();
2933
+ if (cancelled || state === void 0) return;
2934
+ setExpired(state === "expired");
2935
+ if (state !== "expired") setDismissed(false);
2936
+ };
2937
+ tick();
2938
+ const timer = setInterval(() => {
2939
+ tick();
2940
+ }, POLL_MS$1);
2941
+ return () => {
2942
+ cancelled = true;
2943
+ clearInterval(timer);
2944
+ };
2945
+ }, []);
2946
+ const dismiss = (0, react.useCallback)(() => {
2947
+ setDismissed(true);
2948
+ }, []);
2949
+ if (!expired || dismissed) return null;
2950
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
2951
+ open: true,
2952
+ onClose: dismiss,
2953
+ title: t("reloginTitle"),
2954
+ closeLabel: t("reloginDismiss"),
2955
+ description: t("reloginBody"),
2956
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2957
+ onClick: dismiss,
2958
+ children: t("reloginDismiss")
2959
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2960
+ variant: "primary",
2961
+ onClick: () => {
2962
+ window.location.reload();
2963
+ },
2964
+ children: t("reloginAction")
2965
+ })] })
2966
+ });
2967
+ }
2968
+ //#endregion
2969
+ //#region src/client/settings-gate.ts
2970
+ /**
2971
+ * Whether the managed DataOps settings section may be registered at all.
2972
+ *
2973
+ * Registration is the gate, not a CSS class: an ordinary user's profile should not contain a
2974
+ * section whose controls they may not use. The identity record is presentation-only and arrives
2975
+ * from the portal after the iframe loads, so the registration follows it instead of reading it
2976
+ * once at startup. Authorization still lives in DataOps; this only keeps a forbidden panel out of
2977
+ * the interface.
2978
+ */
2979
+ /** The two DataOps role labels allowed to see the managed settings panel. */
2980
+ const MANAGED_SETTINGS_ROLES = /* @__PURE__ */ new Set(["超级管理员", "管理员"]);
2981
+ /** The account launcher's visible label, which is painted before the window record settles. */
2982
+ function domAccountName() {
2983
+ if (typeof document === "undefined") return null;
2984
+ return [...document.querySelectorAll("[data-dataops-account-name]")].map((element) => element.textContent?.trim() ?? "").filter((text) => text !== "" && text.length <= 30).sort((left, right) => left.length - right.length)[0] ?? null;
2985
+ }
2986
+ /** Read the current role label without trusting any other field. */
2987
+ function currentManagedSettingsRole() {
2988
+ if (typeof window === "undefined") return null;
2989
+ const domName = domAccountName();
2990
+ if (domName !== null) return domName;
2991
+ const name = window.__DATAOPS_USER__?.name;
2992
+ return typeof name === "string" && name.trim() !== "" ? name.trim() : null;
2993
+ }
2994
+ /** Whether the current identity may see the managed DataOps settings section. */
2995
+ function canSeeManagedSettings() {
2996
+ const role = currentManagedSettingsRole();
2997
+ return role !== null && MANAGED_SETTINGS_ROLES.has(role);
2998
+ }
2999
+ /**
3000
+ * Observe identity changes.
3001
+ *
3002
+ * The account plugin keeps one store on `window`; subscribing to its listener set catches the
3003
+ * in-page update even before the portal's DOM event. The event is kept as a second signal because
3004
+ * a future portal may publish the event without touching the store this plugin reads.
3005
+ */
3006
+ function subscribeManagedSettingsVisibility(listener) {
3007
+ if (typeof window === "undefined") return () => {};
3008
+ const store = window.__DATAOPS_ACCOUNT_STORE__;
3009
+ store?.listeners?.add(listener);
3010
+ window.addEventListener("dataops-account:changed", listener);
3011
+ /**
3012
+ * The first identity can arrive before this module subscribes (or replace the account store
3013
+ * object), which is why the sidebar used to show only the sections whose gate happened to run
3014
+ * later. The launcher's label is a DOM fact either way, so watch it as the fallback signal.
3015
+ */
3016
+ let observed = domAccountName();
3017
+ const observer = typeof MutationObserver === "undefined" || typeof document === "undefined" ? void 0 : new MutationObserver(() => {
3018
+ const next = domAccountName();
3019
+ if (next === observed) return;
3020
+ observed = next;
3021
+ listener();
3022
+ });
3023
+ observer?.observe(document.documentElement, {
3024
+ subtree: true,
3025
+ childList: true,
3026
+ characterData: true,
3027
+ attributes: true,
3028
+ attributeFilter: ["data-dataops-account-name"]
3029
+ });
3030
+ if (observed !== null) queueMicrotask(listener);
3031
+ return () => {
3032
+ store?.listeners?.delete(listener);
3033
+ window.removeEventListener("dataops-account:changed", listener);
3034
+ observer?.disconnect();
3035
+ };
3036
+ }
3037
+ //#endregion
3038
+ //#region \0dsh-css:/root/projects/dsh-plugins-plus/packages/dataops-managed/src/client/PermissionCenterSection.module.css.mjs
3039
+ const css$1 = ".PNbOqW_page{box-sizing:border-box;flex-direction:column;gap:14px;width:100%;max-width:clamp(1000px,60vw,1200px);min-height:0;margin:0 auto;padding:24px;display:flex}.PNbOqW_head{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.PNbOqW_title{color:var(--dsw-alias-label-primary);margin:0;font-size:18px;font-weight:600;line-height:26px}.PNbOqW_subtitle{color:var(--dsw-alias-label-tertiary);align-items:center;gap:6px;margin:3px 0 0;font-size:12px;display:flex}.PNbOqW_demo{color:#9a5b00;background:#fff3e0;border-radius:4px;padding:1px 6px;font-size:10px;line-height:16px}.PNbOqW_tabs{border-bottom:1px solid var(--dsw-alias-border-l1);gap:2px;display:flex}.PNbOqW_tab{height:32px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-bottom:2px solid #0000;padding:0 12px;font-size:13px}.PNbOqW_tabActive{color:var(--dsw-alias-state-business-primary);border-bottom-color:var(--dsw-alias-state-business-primary);font-weight:500}.PNbOqW_notice,.PNbOqW_waiting{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);border-radius:5px;margin:0;padding:8px 10px;font-size:12px}.PNbOqW_waiting{border:1px dashed var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary)}.PNbOqW_panel{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:8px;flex-direction:column;gap:10px;min-height:0;padding:14px;display:flex}.PNbOqW_sectionTitle{margin:0;font-size:13px;font-weight:600}.PNbOqW_matrixScroll,.PNbOqW_tableScroll{overscroll-behavior:contain;scrollbar-gutter:stable;max-height:clamp(280px,100dvh - 340px,520px);overflow:auto}.PNbOqW_matrix,.PNbOqW_table{border-collapse:collapse;width:100%;font-size:12px}.PNbOqW_matrix th,.PNbOqW_matrix td,.PNbOqW_table th,.PNbOqW_table td{border-bottom:1px solid var(--dsw-alias-border-l1);text-align:left;vertical-align:top;padding:7px 9px}.PNbOqW_matrix th,.PNbOqW_table th{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);white-space:nowrap;font-weight:600}.PNbOqW_permission{white-space:nowrap;color:var(--dsw-alias-label-secondary);align-items:center;gap:5px;font-size:11px;line-height:20px;display:flex}.PNbOqW_panelFoot{flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:6px;display:flex}.PNbOqW_readOnly{color:var(--dsw-alias-label-tertiary);margin-right:auto;font-size:11px}.PNbOqW_muted{color:var(--dsw-alias-label-tertiary);font-size:11px}.PNbOqW_departments{flex-direction:column;gap:4px;margin:0;padding:0;list-style:none;display:flex}.PNbOqW_department{border-bottom:1px solid var(--dsw-alias-border-l1);justify-content:space-between;align-items:center;gap:12px;padding:9px 8px;font-size:12px;display:flex}.PNbOqW_deptAdmins{align-items:center;gap:5px;display:flex}.PNbOqW_adminTag{color:var(--dsw-alias-state-business-primary);background:#ecf1fc;border-radius:4px;padding:1px 6px;font-size:11px}.PNbOqW_empty{text-align:center;color:var(--dsw-alias-label-tertiary);margin:0;padding:40px 0}.PNbOqW_matrixScroll{overscroll-behavior:contain;scrollbar-gutter:stable;max-height:clamp(280px,100dvh - 340px,520px);overflow:auto}.PNbOqW_matrix thead th{z-index:2;position:sticky;top:0}.PNbOqW_matrix thead th:first-child,.PNbOqW_matrix tbody th{background:var(--dsw-alias-bg-layer-2);min-width:132px;position:sticky;left:0}.PNbOqW_matrix thead th:first-child{z-index:3}.PNbOqW_matrix tbody th{z-index:1;background:var(--dsw-alias-bg-layer-1)}.PNbOqW_protected{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-tertiary);white-space:nowrap;border-radius:3px;margin-left:5px;padding:0 5px;font-size:10px;font-weight:500;line-height:16px;display:inline-block}tr[data-locked=true] td{opacity:.62}.PNbOqW_pager{justify-content:flex-end;align-items:center;gap:8px;padding-top:8px;display:flex}.PNbOqW_dialog{width:min(560px,100vw - 32px)}.PNbOqW_dialogBody{flex-direction:column;gap:10px;display:flex}.PNbOqW_dialogHint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:18px}.PNbOqW_search{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);width:100%;height:30px;color:var(--dsw-alias-label-primary);font:var(--dsw-font-xs-13);border-radius:5px;padding:0 9px}.PNbOqW_candidates{flex-direction:column;gap:2px;max-height:260px;margin:0;padding:0;list-style:none;display:flex;overflow:auto}.PNbOqW_candidate{cursor:pointer;border-radius:5px;align-items:center;gap:7px;padding:6px 7px;font-size:12px;display:flex}.PNbOqW_candidate:hover{background:var(--dsw-alias-interactive-bg-hover)}.PNbOqW_error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:11px;line-height:16px}.PNbOqW_dialogFooter{justify-content:flex-end;gap:6px;display:flex}.PNbOqW_matrixNote{color:var(--dsw-alias-label-tertiary);margin:6px 0 0;font-size:11px;line-height:16px}.PNbOqW_panelHead{justify-content:space-between;align-items:center;gap:10px;display:flex}.PNbOqW_treeRoot,.PNbOqW_treeChildren{margin:0;padding:0;list-style:none}.PNbOqW_treeChildren{border-left:1px solid var(--dsw-alias-border-l1);margin-left:18px}.PNbOqW_treeRow{border-bottom:1px solid var(--dsw-alias-border-l1);align-items:center;gap:7px;min-height:34px;font-size:12px;display:flex}.PNbOqW_treeToggle,.PNbOqW_treeSpacer{flex:none;justify-content:center;align-items:center;width:18px;height:18px;display:inline-flex}.PNbOqW_treeToggle{color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:3px;font-size:11px}.PNbOqW_treeToggle:hover{background:var(--dsw-alias-interactive-bg-hover)}.PNbOqW_treeName{color:var(--dsw-alias-label-primary);white-space:nowrap;font-weight:600}.PNbOqW_treeActions{align-items:center;gap:2px;margin-left:auto;display:flex}.PNbOqW_field{color:var(--dsw-alias-label-secondary);flex-direction:column;gap:5px;font-size:12px;display:flex}.PNbOqW_field input,.PNbOqW_field select{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);width:100%;height:30px;color:var(--dsw-alias-label-primary);font:var(--dsw-font-xs-13);border-radius:5px;padding:0 9px}.PNbOqW_field input:disabled{opacity:.65}.PNbOqW_matrixModule{vertical-align:top;background:var(--dsw-alias-bg-layer-2);min-width:96px;color:var(--dsw-alias-label-secondary);text-align:left;border-right:1px solid var(--dsw-alias-border-l1);padding:10px 9px;font-size:12px;font-weight:600}.PNbOqW_matrixGroup th{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);text-align:left;padding:7px 9px;font-size:12px;font-weight:600}.PNbOqW_matrixPermission{white-space:nowrap;min-width:230px}.PNbOqW_scopeBadge{border-radius:3px;margin-left:6px;padding:0 5px;font-size:10px;font-weight:500;line-height:16px;display:inline-block}.PNbOqW_scopeBadge[data-scope=department]{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 14%, var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-state-business-primary)}.PNbOqW_scopeBadge[data-scope=global]{background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 16%, var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-state-warn-primary)}.PNbOqW_onlyAdmin{color:var(--dsw-alias-label-tertiary);white-space:nowrap;margin-left:4px;font-size:10px}.PNbOqW_permCopy{max-width:100%;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;align-items:center;gap:2px;padding:0;display:inline-flex}.PNbOqW_permCopy:hover span:first-child{color:var(--dsw-alias-state-business-primary);text-decoration:underline dotted}.PNbOqW_permDescription{color:var(--dsw-alias-label-tertiary);white-space:normal;margin-top:2px;font-size:10px;font-weight:400;line-height:15px;display:block}.PNbOqW_copied{color:var(--dsw-alias-state-success-primary);margin-left:4px;font-size:10px}.PNbOqW_combobox{min-width:132px;position:relative}.PNbOqW_comboboxTrigger{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);width:100%;height:28px;color:var(--dsw-alias-label-primary);font:var(--dsw-font-xs-13);text-align:left;cursor:pointer;border-radius:5px;justify-content:space-between;align-items:center;gap:8px;padding:0 8px;display:flex}.PNbOqW_comboboxTrigger:hover:not(:disabled){border-color:var(--dsw-alias-state-business-primary)}.PNbOqW_comboboxTrigger:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}.PNbOqW_comboboxTrigger:disabled{opacity:.55;cursor:default}.PNbOqW_comboboxPlaceholder{color:var(--dsw-alias-label-tertiary)}.PNbOqW_comboboxMenu{z-index:30;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:7px;width:max(100%,220px);padding:5px;position:absolute;top:calc(100% + 4px);left:0;box-shadow:0 8px 24px #00000038}.PNbOqW_comboboxSearch{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);width:100%;height:28px;color:var(--dsw-alias-label-primary);font:var(--dsw-font-xs-13);border-radius:4px;padding:0 7px}.PNbOqW_comboboxList{max-height:210px;margin:4px 0 0;padding:0;list-style:none;overflow:auto}.PNbOqW_comboboxOption{width:100%;color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer;background:0 0;border:0;border-radius:4px;align-items:center;gap:5px;padding:6px;font-size:12px;line-height:18px;display:flex}.PNbOqW_comboboxOptionActive,.PNbOqW_comboboxOption:hover{background:var(--dsw-alias-interactive-bg-hover)}.PNbOqW_comboboxCheck{width:13px;color:var(--dsw-alias-state-business-primary);flex:none;font-size:11px}.PNbOqW_comboboxHint{color:var(--dsw-alias-label-tertiary);margin-left:auto;font-size:10px}.PNbOqW_comboboxEmpty{color:var(--dsw-alias-label-tertiary);padding:9px 7px;font-size:11px}.PNbOqW_rowActions{white-space:nowrap;align-items:center;gap:4px;display:flex}.PNbOqW_detailGrid{grid-template-columns:92px minmax(0,1fr);gap:8px 12px;margin:0;font-size:12px;line-height:18px;display:grid}.PNbOqW_detailGrid dt{color:var(--dsw-alias-label-tertiary)}.PNbOqW_detailGrid dd{color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;margin:0}";
3040
+ const tagId$1 = "@sparkelf/dsh-dataops-managed/PermissionCenterSection.module.css";
3041
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
3042
+ const tag = document.createElement("style");
3043
+ tag.dataset.plugin = "@sparkelf/dsh-dataops-managed";
3044
+ tag.dataset.pluginCss = tagId$1;
3045
+ tag.textContent = css$1;
3046
+ document.head.appendChild(tag);
3047
+ }
3048
+ var PermissionCenterSection_module_css_default = {
3049
+ "panelFoot": "PNbOqW_panelFoot",
3050
+ "comboboxHint": "PNbOqW_comboboxHint",
3051
+ "matrixGroup": "PNbOqW_matrixGroup",
3052
+ "permDescription": "PNbOqW_permDescription",
3053
+ "protected": "PNbOqW_protected",
3054
+ "matrixNote": "PNbOqW_matrixNote",
3055
+ "dialogHint": "PNbOqW_dialogHint",
3056
+ "pager": "PNbOqW_pager",
3057
+ "tabs": "PNbOqW_tabs",
3058
+ "permCopy": "PNbOqW_permCopy",
3059
+ "comboboxSearch": "PNbOqW_comboboxSearch",
3060
+ "error": "PNbOqW_error",
3061
+ "dialog": "PNbOqW_dialog",
3062
+ "treeSpacer": "PNbOqW_treeSpacer",
3063
+ "matrixPermission": "PNbOqW_matrixPermission",
3064
+ "treeActions": "PNbOqW_treeActions",
3065
+ "panel": "PNbOqW_panel",
3066
+ "tab": "PNbOqW_tab",
3067
+ "adminTag": "PNbOqW_adminTag",
3068
+ "candidates": "PNbOqW_candidates",
3069
+ "department": "PNbOqW_department",
3070
+ "copied": "PNbOqW_copied",
3071
+ "tabActive": "PNbOqW_tabActive",
3072
+ "treeRoot": "PNbOqW_treeRoot",
3073
+ "comboboxOption": "PNbOqW_comboboxOption",
3074
+ "table": "PNbOqW_table",
3075
+ "onlyAdmin": "PNbOqW_onlyAdmin",
3076
+ "permission": "PNbOqW_permission",
3077
+ "combobox": "PNbOqW_combobox",
3078
+ "treeToggle": "PNbOqW_treeToggle",
3079
+ "dialogFooter": "PNbOqW_dialogFooter",
3080
+ "candidate": "PNbOqW_candidate",
3081
+ "empty": "PNbOqW_empty",
3082
+ "comboboxList": "PNbOqW_comboboxList",
3083
+ "sectionTitle": "PNbOqW_sectionTitle",
3084
+ "readOnly": "PNbOqW_readOnly",
3085
+ "treeRow": "PNbOqW_treeRow",
3086
+ "detailGrid": "PNbOqW_detailGrid",
3087
+ "matrix": "PNbOqW_matrix",
3088
+ "field": "PNbOqW_field",
3089
+ "subtitle": "PNbOqW_subtitle",
3090
+ "demo": "PNbOqW_demo",
3091
+ "title": "PNbOqW_title",
3092
+ "treeChildren": "PNbOqW_treeChildren",
3093
+ "matrixModule": "PNbOqW_matrixModule",
3094
+ "comboboxPlaceholder": "PNbOqW_comboboxPlaceholder",
3095
+ "comboboxMenu": "PNbOqW_comboboxMenu",
3096
+ "comboboxCheck": "PNbOqW_comboboxCheck",
3097
+ "scopeBadge": "PNbOqW_scopeBadge",
3098
+ "comboboxEmpty": "PNbOqW_comboboxEmpty",
3099
+ "treeName": "PNbOqW_treeName",
3100
+ "panelHead": "PNbOqW_panelHead",
3101
+ "rowActions": "PNbOqW_rowActions",
3102
+ "deptAdmins": "PNbOqW_deptAdmins",
3103
+ "comboboxTrigger": "PNbOqW_comboboxTrigger",
3104
+ "waiting": "PNbOqW_waiting",
3105
+ "tableScroll": "PNbOqW_tableScroll",
3106
+ "muted": "PNbOqW_muted",
3107
+ "search": "PNbOqW_search",
3108
+ "page": "PNbOqW_page",
3109
+ "departments": "PNbOqW_departments",
3110
+ "notice": "PNbOqW_notice",
3111
+ "dialogBody": "PNbOqW_dialogBody",
3112
+ "head": "PNbOqW_head",
3113
+ "matrixScroll": "PNbOqW_matrixScroll",
3114
+ "comboboxOptionActive": "PNbOqW_comboboxOptionActive"
3115
+ };
3116
+ //#endregion
3117
+ //#region src/client/combobox.ts
3118
+ /**
3119
+ * Filter options by display label or code hint, case-insensitively.
3120
+ * @param options - all options.
3121
+ * @param query - typed search text.
3122
+ */
3123
+ function filterComboboxOptions(options, query) {
3124
+ const needle = query.trim().toLowerCase();
3125
+ if (needle === "") return [...options];
3126
+ return options.filter((option) => option.label.toLowerCase().includes(needle) || (option.hint ?? "").toLowerCase().includes(needle));
3127
+ }
3128
+ /** Keep an active index inside a possibly empty result list. */
3129
+ function clampActiveIndex(index, length) {
3130
+ if (length <= 0) return 0;
3131
+ return Math.max(0, Math.min(length - 1, index));
3132
+ }
3133
+ /** Owner cell face: a real display name, or a dash; never the raw id. */
3134
+ function ownerDisplay(ownerName) {
3135
+ const name = ownerName?.trim();
3136
+ return name === void 0 || name === "" ? "—" : name;
3137
+ }
3138
+ //#endregion
3139
+ //#region src/client/PermissionCenterSection.tsx
3140
+ init_review_demo();
3141
+ /** How many rows one page of the members/resources tables shows. */
3142
+ const PAGE_SIZE = 10;
3143
+ /**
3144
+ * The permission centre.
3145
+ *
3146
+ * Every write goes through `saveRbacSection`; the backend enforces the same role/scope rules
3147
+ * the disabled controls represent. The page keeps target-aware state explicitly (actor role,
3148
+ * actor department) so an ADMIN cannot edit a SUPER_ADMIN row and a DEPT_ADMIN cannot reach
3149
+ * another department, even if a list payload is wider than expected.
3150
+ */
3151
+ function PermissionCenterSection({ t }) {
3152
+ const [context, setContext] = (0, react.useState)(void 0);
3153
+ const [overview, setOverview] = (0, react.useState)(void 0);
3154
+ const [tab, setTab] = (0, react.useState)("roles");
3155
+ const [notice, setNotice] = (0, react.useState)(null);
3156
+ const [busy, setBusy] = (0, react.useState)(false);
3157
+ const [deptDialog, setDeptDialog] = (0, react.useState)(null);
3158
+ const [deptSearch, setDeptSearch] = (0, react.useState)("");
3159
+ const [deptSelected, setDeptSelected] = (0, react.useState)([]);
3160
+ const [deptSaving, setDeptSaving] = (0, react.useState)(false);
3161
+ const [deptError, setDeptError] = (0, react.useState)(null);
3162
+ const [deptForm, setDeptForm] = (0, react.useState)(null);
3163
+ const demo = reviewDemoMode();
3164
+ const load = (0, react.useCallback)(async () => {
3165
+ const nextContext = await fetchReviewContext();
3166
+ setContext(nextContext);
3167
+ if (nextContext === null && !demo) {
3168
+ setOverview(null);
3169
+ return;
3170
+ }
3171
+ try {
3172
+ setOverview(demo ? demoRbacOverview() : await fetchRbacOverview());
3173
+ } catch {
3174
+ setOverview(null);
3175
+ }
3176
+ }, [demo]);
3177
+ (0, react.useEffect)(() => {
3178
+ load();
3179
+ }, [load]);
3180
+ if (t === void 0) return null;
3181
+ if (context === void 0 || overview === void 0) return null;
3182
+ const knownAdmin = canSeeManagedSettings();
3183
+ if (context !== null ? context.role !== "SUPER_ADMIN" && context.role !== "ADMIN" && context.role !== "DEPT_ADMIN" && !knownAdmin : !knownAdmin) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
3184
+ className: PermissionCenterSection_module_css_default.page,
3185
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3186
+ className: PermissionCenterSection_module_css_default.empty,
3187
+ children: t("permNoAccess")
3188
+ })
3189
+ });
3190
+ const roleName = context === null ? currentManagedSettingsRole() ?? "—" : roleLabel(context.role);
3191
+ const departmentName = context?.departmentPath?.join(" / ") ?? "—";
3192
+ const actorRole = context?.role ?? (knownAdmin ? "ADMIN" : "APP_USER");
3193
+ const writable = context === null ? knownAdmin : canManagePermissions(context) || demo;
3194
+ const superAdmin = context === null ? knownAdmin : context.role === "SUPER_ADMIN" || demo;
3195
+ const canAssignDepartment = writable && (superAdmin || actorRole === "ADMIN");
3196
+ const labels = [
3197
+ {
3198
+ key: "roles",
3199
+ label: t("permTabRoles")
3200
+ },
3201
+ {
3202
+ key: "members",
3203
+ label: t("permTabMembers")
3204
+ },
3205
+ {
3206
+ key: "departments",
3207
+ label: t("permTabDepartments")
3208
+ },
3209
+ {
3210
+ key: "resources",
3211
+ label: t("permTabResources")
3212
+ }
3213
+ ];
3214
+ const save = async (section, value) => {
3215
+ setBusy(true);
3216
+ setNotice(null);
3217
+ try {
3218
+ const result = await saveRbacSection(section, value);
3219
+ setNotice(result.ok ? t("permSaved") : result.reason ?? t("permWaiting"));
3220
+ if (result.ok) await load();
3221
+ return result;
3222
+ } catch {
3223
+ setNotice(t("permSaveFailed"));
3224
+ return {
3225
+ ok: false,
3226
+ reason: t("permSaveFailed")
3227
+ };
3228
+ } finally {
3229
+ setBusy(false);
3230
+ }
3231
+ };
3232
+ const saveRoleChanges = async (changes) => {
3233
+ setBusy(true);
3234
+ setNotice(null);
3235
+ try {
3236
+ for (const change of changes) {
3237
+ const result = await saveRbacSection("roles", {
3238
+ roleCode: change.roleCode,
3239
+ permissionKeys: change.permissionKeys
3240
+ });
3241
+ if (!result.ok) {
3242
+ setNotice(result.reason ?? t("permSaveFailed"));
3243
+ await load();
3244
+ return result;
3245
+ }
3246
+ }
3247
+ setNotice(t("permSaved"));
3248
+ await load();
3249
+ return { ok: true };
3250
+ } catch {
3251
+ setNotice(t("permSaveFailed"));
3252
+ return {
3253
+ ok: false,
3254
+ reason: t("permSaveFailed")
3255
+ };
3256
+ } finally {
3257
+ setBusy(false);
3258
+ }
3259
+ };
3260
+ const openDepartment = (department) => {
3261
+ setDeptDialog(department);
3262
+ setDeptSelected(department.deptAdmins?.map((admin) => admin.userId) ?? []);
3263
+ setDeptSearch("");
3264
+ setDeptError(null);
3265
+ };
3266
+ const assignDepartment = async (userIds) => {
3267
+ if (deptDialog === null || deptSaving) return;
3268
+ setDeptSaving(true);
3269
+ setDeptError(null);
3270
+ try {
3271
+ const result = await saveRbacSection("departments", {
3272
+ code: deptDialog.code,
3273
+ userIds
3274
+ });
3275
+ if (!result.ok) {
3276
+ setDeptError(result.reason ?? "保存失败:后端拒绝或无权限(403)。");
3277
+ return;
3278
+ }
3279
+ setDeptDialog(null);
3280
+ setNotice("部门管理员已更新。");
3281
+ await load();
3282
+ } catch {
3283
+ setDeptError("保存失败:后端拒绝或无权限(403)。");
3284
+ } finally {
3285
+ setDeptSaving(false);
3286
+ }
3287
+ };
3288
+ const removeDepartment = async (department) => {
3289
+ const children = overview?.departments.filter((row) => row.parentCode === department.code).length ?? 0;
3290
+ const members = department.memberCount ?? 0;
3291
+ if (!window.confirm("确认删除部门「" + department.name + "」?\n成员 " + String(members) + " 人,子部门 " + String(children) + " 个;删除后不可恢复。")) return;
3292
+ const result = await deleteDepartment(department.code);
3293
+ if (!result.ok) {
3294
+ setNotice(result.reason ?? "删除失败:后端拒绝或无权限(403)。");
3295
+ return;
3296
+ }
3297
+ setNotice("部门已删除。");
3298
+ await load();
3299
+ };
3300
+ const candidates = deptDialog === null || overview === null ? [] : overview.members.filter((member) => {
3301
+ const pathCodes = member.departmentPath?.map((entry) => entry.code) ?? [];
3302
+ return member.departmentCode === deptDialog.code || pathCodes.includes(deptDialog.code);
3303
+ }).filter((member) => {
3304
+ const needle = deptSearch.trim().toLowerCase();
3305
+ return needle === "" || member.displayName.toLowerCase().includes(needle) || member.username.toLowerCase().includes(needle);
3306
+ });
3307
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3308
+ className: PermissionCenterSection_module_css_default.page,
3309
+ "data-permission-center": "",
3310
+ children: [
3311
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
3312
+ className: PermissionCenterSection_module_css_default.head,
3313
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
3314
+ className: PermissionCenterSection_module_css_default.title,
3315
+ children: t("permTitle")
3316
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
3317
+ className: PermissionCenterSection_module_css_default.subtitle,
3318
+ children: [
3319
+ roleName,
3320
+ " · ",
3321
+ departmentName,
3322
+ demo ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3323
+ className: PermissionCenterSection_module_css_default.demo,
3324
+ children: t("permDemo")
3325
+ }) : null
3326
+ ]
3327
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3328
+ variant: "outline",
3329
+ size: "sm",
3330
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineRegular, { size: 16 }),
3331
+ onClick: () => {
3332
+ load();
3333
+ },
3334
+ children: "刷新"
3335
+ })]
3336
+ }),
3337
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3338
+ className: PermissionCenterSection_module_css_default.tabs,
3339
+ role: "tablist",
3340
+ "aria-label": t("permTitle"),
3341
+ children: labels.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3342
+ type: "button",
3343
+ role: "tab",
3344
+ "aria-selected": tab === item.key,
3345
+ className: tab === item.key ? PermissionCenterSection_module_css_default.tab + " " + PermissionCenterSection_module_css_default.tabActive : PermissionCenterSection_module_css_default.tab,
3346
+ onClick: () => {
3347
+ setTab(item.key);
3348
+ },
3349
+ children: item.label
3350
+ }, item.key))
3351
+ }),
3352
+ notice === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3353
+ className: PermissionCenterSection_module_css_default.notice,
3354
+ children: notice
3355
+ }),
3356
+ overview === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3357
+ className: PermissionCenterSection_module_css_default.waiting,
3358
+ children: t("permWaiting")
3359
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3360
+ tab === "roles" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoleMatrix, {
3361
+ roles: overview.matrix.roles,
3362
+ permissions: overview.matrix.permissions,
3363
+ writable,
3364
+ actorRole,
3365
+ busy,
3366
+ t,
3367
+ onSaveAll: saveRoleChanges
3368
+ }) : null,
3369
+ tab === "members" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Members, {
3370
+ members: overview.members,
3371
+ writable,
3372
+ actorRole,
3373
+ actorUserId: context?.userId,
3374
+ actorDepartmentCode: context?.departmentCode,
3375
+ busy,
3376
+ t,
3377
+ onSave: (userId, roleCodes) => {
3378
+ save("members", {
3379
+ userId,
3380
+ roleCodes
3381
+ });
3382
+ }
3383
+ }) : null,
3384
+ tab === "departments" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Departments, {
3385
+ departments: overview.departments,
3386
+ writable,
3387
+ canAssign: canAssignDepartment,
3388
+ canWrite: writable && (superAdmin || actorRole === "ADMIN"),
3389
+ t,
3390
+ onAssign: openDepartment,
3391
+ onAdd: (parentCode) => {
3392
+ setDeptForm({
3393
+ mode: "create",
3394
+ parentCode
3395
+ });
3396
+ },
3397
+ onEdit: (department) => {
3398
+ setDeptForm({
3399
+ mode: "edit",
3400
+ department
3401
+ });
3402
+ },
3403
+ onDelete: (department) => {
3404
+ removeDepartment(department);
3405
+ }
3406
+ }) : null,
3407
+ tab === "resources" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Resources, {
3408
+ resources: overview.resources,
3409
+ departments: overview.departments,
3410
+ writable,
3411
+ actorRole,
3412
+ actorDepartmentCode: context?.departmentCode,
3413
+ busy,
3414
+ t,
3415
+ onSave: (row, visibility, departmentCode) => save("resources", {
3416
+ kind: row.kind,
3417
+ id: row.id,
3418
+ visibility,
3419
+ departmentCode
3420
+ })
3421
+ }) : null
3422
+ ] }),
3423
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
3424
+ open: deptDialog !== null,
3425
+ onClose: () => {
3426
+ if (!deptSaving) setDeptDialog(null);
3427
+ },
3428
+ title: deptDialog === null ? "分配部门管理员" : "分配管理员 · " + deptDialog.name,
3429
+ closeLabel: "取消",
3430
+ className: PermissionCenterSection_module_css_default.dialog,
3431
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3432
+ className: PermissionCenterSection_module_css_default.dialogFooter,
3433
+ children: [
3434
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3435
+ variant: "outline",
3436
+ disabled: deptSaving,
3437
+ onClick: () => {
3438
+ if (deptDialog !== null) assignDepartment([]);
3439
+ },
3440
+ children: "清空"
3441
+ }),
3442
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3443
+ variant: "outline",
3444
+ disabled: deptSaving,
3445
+ onClick: () => {
3446
+ setDeptDialog(null);
3447
+ },
3448
+ children: "取消"
3449
+ }),
3450
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3451
+ variant: "primary",
3452
+ disabled: deptSaving,
3453
+ onClick: () => {
3454
+ assignDepartment(deptSelected);
3455
+ },
3456
+ children: "保存"
3457
+ })
3458
+ ]
3459
+ }),
3460
+ children: deptDialog === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3461
+ className: PermissionCenterSection_module_css_default.dialogBody,
3462
+ children: [
3463
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
3464
+ className: PermissionCenterSection_module_css_default.dialogHint,
3465
+ children: ["当前管理员:", deptDialog.deptAdmins?.length === 0 || deptDialog.deptAdmins === void 0 ? "未设置" : deptDialog.deptAdmins.map((admin) => admin.displayName).join("、")]
3466
+ }),
3467
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3468
+ className: PermissionCenterSection_module_css_default.search,
3469
+ value: deptSearch,
3470
+ placeholder: "搜索本部门成员…",
3471
+ onChange: (event) => {
3472
+ setDeptSearch(event.target.value);
3473
+ }
3474
+ }),
3475
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
3476
+ className: PermissionCenterSection_module_css_default.candidates,
3477
+ children: candidates.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
3478
+ className: PermissionCenterSection_module_css_default.muted,
3479
+ children: "没有可选成员(后端可能只允许本部门/未分配用户)。"
3480
+ }) : candidates.map((member) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3481
+ className: PermissionCenterSection_module_css_default.candidate,
3482
+ children: [
3483
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3484
+ type: "checkbox",
3485
+ checked: deptSelected.includes(member.userId),
3486
+ onChange: (event) => {
3487
+ setDeptSelected((current) => event.target.checked ? [...current, member.userId] : current.filter((id) => id !== member.userId));
3488
+ }
3489
+ }),
3490
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: member.displayName }),
3491
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3492
+ className: PermissionCenterSection_module_css_default.muted,
3493
+ children: [
3494
+ "@",
3495
+ member.username,
3496
+ " · ",
3497
+ member.departmentPath?.map((entry) => entry.name).join(" / ") ?? "—"
3498
+ ]
3499
+ })
3500
+ ]
3501
+ }) }, member.userId))
3502
+ }),
3503
+ deptError === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3504
+ className: PermissionCenterSection_module_css_default.error,
3505
+ role: "alert",
3506
+ children: deptError
3507
+ })
3508
+ ]
3509
+ })
3510
+ }),
3511
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DepartmentFormDialog, {
3512
+ state: deptForm,
3513
+ departments: overview?.departments ?? [],
3514
+ t,
3515
+ onClose: () => {
3516
+ setDeptForm(null);
3517
+ },
3518
+ onSaved: async () => {
3519
+ setDeptForm(null);
3520
+ setNotice("部门已保存。");
3521
+ await load();
3522
+ }
3523
+ })
3524
+ ]
3525
+ });
3526
+ }
3527
+ function RoleMatrix({ roles, permissions, writable, actorRole, busy, t, onSaveAll }) {
3528
+ const visibleRoles = (0, react.useMemo)(() => roles.filter((role) => role.systemKey === "SUPER_ADMIN" || role.systemKey === "ADMIN" || role.systemKey === "DEPT_ADMIN" || role.systemKey === "APP_USER" || role.code === "超级管理员" || role.code === "管理员" || role.code === "部门管理员" || role.code === "应用人员" || role.code === "普通用户").map((role) => role.systemKey === "APP_USER" || role.code === "应用人员" || role.code === "普通用户" ? {
3529
+ ...role,
3530
+ name: "普通用户"
3531
+ } : role), [roles]);
3532
+ const groups = (0, react.useMemo)(() => {
3533
+ const hasSkill = permissions.some((point) => point.group === "skill" || point.group === "技能中心");
3534
+ const keyOf = (group) => {
3535
+ const raw = group.trim().toLowerCase();
3536
+ if (raw === "kb" || raw === "knowledge" || group === "知识库") return "kb";
3537
+ if (raw === "skill" || group === "技能中心") return "skill";
3538
+ if (raw === "workflow" || group === "工作流广场") return "workflow";
3539
+ if (raw === "automation" || group === "自动化任务") return "automation";
3540
+ if (raw === "ai" && !hasSkill) return "skill";
3541
+ return null;
3542
+ };
3543
+ return [
3544
+ {
3545
+ key: "kb",
3546
+ label: "知识库"
3547
+ },
3548
+ {
3549
+ key: "skill",
3550
+ label: "技能中心"
3551
+ },
3552
+ {
3553
+ key: "workflow",
3554
+ label: "工作流广场"
3555
+ },
3556
+ {
3557
+ key: "automation",
3558
+ label: "自动化任务"
3559
+ }
3560
+ ].map((def) => ({
3561
+ ...def,
3562
+ points: permissions.filter((point) => keyOf(point.group) === def.key)
3563
+ })).filter((group) => group.points.length > 0);
3564
+ }, [permissions]);
3565
+ (0, react.useMemo)(() => groups.flatMap((group) => group.points), [groups]);
3566
+ const [draft, setDraft] = (0, react.useState)(() => Object.fromEntries(visibleRoles.map((role) => [role.code, new Set(role.permissions)])));
3567
+ const [dirty, setDirty] = (0, react.useState)(() => /* @__PURE__ */ new Set());
3568
+ const [saving, setSaving] = (0, react.useState)(false);
3569
+ const [error, setError] = (0, react.useState)(null);
3570
+ const [copiedKey, setCopiedKey] = (0, react.useState)(null);
3571
+ const copyKey = async (key) => {
3572
+ try {
3573
+ await navigator.clipboard.writeText(key);
3574
+ } catch {}
3575
+ setCopiedKey(key);
3576
+ window.setTimeout(() => {
3577
+ setCopiedKey((current) => current === key ? null : current);
3578
+ }, 1600);
3579
+ };
3580
+ (0, react.useEffect)(() => {
3581
+ setDraft(Object.fromEntries(visibleRoles.map((role) => [role.code, new Set(role.permissions)])));
3582
+ setDirty(/* @__PURE__ */ new Set());
3583
+ }, [visibleRoles]);
3584
+ const isDepartmentRole = (code) => code === "DEPT_ADMIN" || code === "部门管理员";
3585
+ const isAppRole = (code) => code === "APP_USER" || code === "应用人员" || code === "普通用户";
3586
+ const globalScope = (point) => point.scope === "global" || point.scope === "GLOBAL" || point.key === "skill:review:global" || point.key === "workflow:review:global";
3587
+ const locked = (roleCode, point) => {
3588
+ if (!writable || actorRole !== "SUPER_ADMIN") return true;
3589
+ return globalScope(point) && (isDepartmentRole(roleCode) || isAppRole(roleCode));
3590
+ };
3591
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3592
+ className: PermissionCenterSection_module_css_default.panel,
3593
+ children: [
3594
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
3595
+ className: PermissionCenterSection_module_css_default.sectionTitle,
3596
+ children: t("permRoleMatrix")
3597
+ }),
3598
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3599
+ className: PermissionCenterSection_module_css_default.matrixScroll,
3600
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
3601
+ className: PermissionCenterSection_module_css_default.matrix,
3602
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
3603
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permPermissionModule") }),
3604
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permPermissionPoint") }),
3605
+ visibleRoles.map((role) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: role.name }, role.code))
3606
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: groups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: group.points.map((point, pointIndex) => {
3607
+ const global = globalScope(point);
3608
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
3609
+ pointIndex === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
3610
+ scope: "rowgroup",
3611
+ rowSpan: group.points.length,
3612
+ className: PermissionCenterSection_module_css_default.matrixModule,
3613
+ children: group.label
3614
+ }) : null,
3615
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("th", {
3616
+ scope: "row",
3617
+ className: PermissionCenterSection_module_css_default.matrixPermission,
3618
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3619
+ type: "button",
3620
+ className: PermissionCenterSection_module_css_default.permCopy,
3621
+ title: point.key,
3622
+ onClick: () => {
3623
+ copyKey(point.key);
3624
+ },
3625
+ children: [
3626
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: point.name }),
3627
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3628
+ className: PermissionCenterSection_module_css_default.scopeBadge,
3629
+ "data-scope": global ? "global" : "department",
3630
+ children: global ? "全局" : "部门级"
3631
+ }),
3632
+ copiedKey === point.key ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3633
+ className: PermissionCenterSection_module_css_default.copied,
3634
+ children: "已复制"
3635
+ }) : null
3636
+ ]
3637
+ }), point.description === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3638
+ className: PermissionCenterSection_module_css_default.permDescription,
3639
+ children: point.description
3640
+ })]
3641
+ }),
3642
+ visibleRoles.map((role) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3643
+ className: PermissionCenterSection_module_css_default.permission,
3644
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3645
+ type: "checkbox",
3646
+ checked: draft[role.code]?.has(point.key) === true,
3647
+ disabled: locked(role.code, point) || busy,
3648
+ onChange: (event) => {
3649
+ setDraft((current) => {
3650
+ const next = new Set(current[role.code] ?? []);
3651
+ if (event.target.checked) next.add(point.key);
3652
+ else next.delete(point.key);
3653
+ return {
3654
+ ...current,
3655
+ [role.code]: next
3656
+ };
3657
+ });
3658
+ setDirty((current) => new Set(current).add(role.code));
3659
+ }
3660
+ }), global && (isDepartmentRole(role.code) || isAppRole(role.code)) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3661
+ className: PermissionCenterSection_module_css_default.onlyAdmin,
3662
+ children: "仅管理员"
3663
+ }) : null]
3664
+ }) }, role.code))
3665
+ ] }, point.key);
3666
+ }) }, group.key)) })]
3667
+ })
3668
+ }),
3669
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3670
+ className: PermissionCenterSection_module_css_default.panelFoot,
3671
+ children: [
3672
+ error === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3673
+ className: PermissionCenterSection_module_css_default.error,
3674
+ children: error
3675
+ }),
3676
+ actorRole !== "SUPER_ADMIN" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3677
+ className: PermissionCenterSection_module_css_default.readOnly,
3678
+ children: t("permSuperAdminOnly")
3679
+ }) : null,
3680
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3681
+ variant: "primary",
3682
+ size: "sm",
3683
+ disabled: dirty.size === 0 || busy || saving,
3684
+ onClick: () => {
3685
+ setSaving(true);
3686
+ setError(null);
3687
+ onSaveAll(visibleRoles.filter((role) => dirty.has(role.code)).map((role) => ({
3688
+ roleCode: role.code,
3689
+ permissionKeys: [...draft[role.code] ?? []]
3690
+ }))).then((result) => {
3691
+ if (result.ok) {
3692
+ setDirty(/* @__PURE__ */ new Set());
3693
+ setError(null);
3694
+ } else setError(result.reason ?? t("permSaveFailed"));
3695
+ }).finally(() => {
3696
+ setSaving(false);
3697
+ });
3698
+ },
3699
+ children: [t("permSave"), dirty.size === 0 ? "" : " (" + String(dirty.size) + ")"]
3700
+ })
3701
+ ]
3702
+ })
3703
+ ]
3704
+ });
3705
+ }
3706
+ function Members({ members, writable, actorRole, actorUserId, actorDepartmentCode, busy, t, onSave }) {
3707
+ const [page, setPage] = (0, react.useState)(0);
3708
+ const protectedMember = (member) => member.roleCodes.some((code) => {
3709
+ if (code === "SUPER_ADMIN" || code === "超级管理员" || code === "ADMIN" || code === "管理员") return true;
3710
+ return actorRole === "DEPT_ADMIN" && (code === "DEPT_ADMIN" || code === "部门管理员");
3711
+ });
3712
+ const superCount = members.filter((member) => member.roleCodes.some((code) => code === "SUPER_ADMIN" || code === "超级管理员")).length;
3713
+ const inScope = (member) => actorRole === "SUPER_ADMIN" || actorRole === "ADMIN" || member.departmentCode === actorDepartmentCode || member.departmentPath?.some((entry) => entry.code === actorDepartmentCode) === true;
3714
+ const pages = Math.max(1, Math.ceil(members.length / PAGE_SIZE));
3715
+ const visible = members.slice(page * PAGE_SIZE, page * PAGE_SIZE + PAGE_SIZE);
3716
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3717
+ className: PermissionCenterSection_module_css_default.panel,
3718
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3719
+ className: PermissionCenterSection_module_css_default.tableScroll,
3720
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
3721
+ className: PermissionCenterSection_module_css_default.table,
3722
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
3723
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permMember") }),
3724
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permDepartment") }),
3725
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permRole") })
3726
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: visible.map((member) => {
3727
+ const isProtected = protectedMember(member);
3728
+ const scoped = inScope(member);
3729
+ const isSelf = actorUserId !== void 0 && member.userId === actorUserId;
3730
+ const lastSuper = superCount <= 1 && member.roleCodes.some((code) => code === "SUPER_ADMIN" || code === "超级管理员");
3731
+ const locked = !writable || !scoped || isSelf || lastSuper || actorRole !== "SUPER_ADMIN" && isProtected;
3732
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
3733
+ "data-locked": locked,
3734
+ children: [
3735
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", { children: [
3736
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: member.displayName }),
3737
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3738
+ className: PermissionCenterSection_module_css_default.muted,
3739
+ children: [" @", member.username]
3740
+ }),
3741
+ isProtected && actorRole !== "SUPER_ADMIN" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3742
+ className: PermissionCenterSection_module_css_default.protected,
3743
+ children: "受保护"
3744
+ }) : null,
3745
+ isSelf ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3746
+ className: PermissionCenterSection_module_css_default.protected,
3747
+ children: "本人"
3748
+ }) : null,
3749
+ lastSuper ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3750
+ className: PermissionCenterSection_module_css_default.protected,
3751
+ children: "最后一个超管"
3752
+ }) : null,
3753
+ !scoped ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3754
+ className: PermissionCenterSection_module_css_default.protected,
3755
+ children: "越界"
3756
+ }) : null
3757
+ ] }),
3758
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: member.departmentPath?.map((entry) => entry.name).join(" / ") ?? "—" }),
3759
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
3760
+ value: member.roleCodes[0] ?? "APP_USER",
3761
+ disabled: locked || busy,
3762
+ onChange: (event) => {
3763
+ if (window.confirm("确认修改该成员的角色?")) onSave(member.userId, [event.target.value]);
3764
+ },
3765
+ children: (actorRole === "SUPER_ADMIN" ? [
3766
+ "超级管理员",
3767
+ "管理员",
3768
+ "部门管理员",
3769
+ "运营人员",
3770
+ "应用人员"
3771
+ ] : [
3772
+ "部门管理员",
3773
+ "运营人员",
3774
+ "应用人员"
3775
+ ]).map((role) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3776
+ value: role,
3777
+ children: role
3778
+ }, role))
3779
+ }) })
3780
+ ]
3781
+ }, member.userId);
3782
+ }) })]
3783
+ })
3784
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pager, {
3785
+ page,
3786
+ pages,
3787
+ onChange: setPage
3788
+ })]
3789
+ });
3790
+ }
3791
+ function Departments({ departments, writable, canAssign, canWrite, t, onAssign, onAdd, onEdit, onDelete }) {
3792
+ const [expanded, setExpanded] = (0, react.useState)(() => new Set(departments.map((department) => department.code)));
3793
+ const byParent = (0, react.useMemo)(() => {
3794
+ const map = /* @__PURE__ */ new Map();
3795
+ const codes = new Set(departments.map((department) => department.code));
3796
+ for (const department of departments) {
3797
+ const parent = department.parentCode !== void 0 && department.parentCode !== null && codes.has(department.parentCode) ? department.parentCode : null;
3798
+ map.set(parent, [...map.get(parent) ?? [], department]);
3799
+ }
3800
+ for (const list of map.values()) list.sort((left, right) => left.name.localeCompare(right.name, "zh-CN") || left.code.localeCompare(right.code));
3801
+ return map;
3802
+ }, [departments]);
3803
+ const node = (department, depth) => {
3804
+ const children = byParent.get(department.code) ?? [];
3805
+ const open = expanded.has(department.code);
3806
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
3807
+ className: PermissionCenterSection_module_css_default.treeNode,
3808
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3809
+ className: PermissionCenterSection_module_css_default.treeRow,
3810
+ style: { paddingLeft: String(8 + depth * 18) + "px" },
3811
+ children: [
3812
+ children.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: PermissionCenterSection_module_css_default.treeSpacer }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3813
+ type: "button",
3814
+ className: PermissionCenterSection_module_css_default.treeToggle,
3815
+ "aria-label": open ? "折叠" : "展开",
3816
+ onClick: () => {
3817
+ setExpanded((current) => {
3818
+ const next = new Set(current);
3819
+ if (next.has(department.code)) next.delete(department.code);
3820
+ else next.add(department.code);
3821
+ return next;
3822
+ });
3823
+ },
3824
+ children: open ? "▾" : "▸"
3825
+ }),
3826
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
3827
+ className: PermissionCenterSection_module_css_default.treeName,
3828
+ children: department.name
3829
+ }),
3830
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3831
+ className: PermissionCenterSection_module_css_default.muted,
3832
+ children: [
3833
+ department.code,
3834
+ " · ",
3835
+ department.memberCount ?? 0,
3836
+ " 人"
3837
+ ]
3838
+ }),
3839
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3840
+ className: PermissionCenterSection_module_css_default.deptAdmins,
3841
+ children: department.deptAdmins?.length === 0 || department.deptAdmins === void 0 ? null : department.deptAdmins.map((admin) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3842
+ className: PermissionCenterSection_module_css_default.adminTag,
3843
+ children: admin.displayName
3844
+ }, admin.userId))
3845
+ }),
3846
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3847
+ className: PermissionCenterSection_module_css_default.treeActions,
3848
+ children: [
3849
+ canAssign ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3850
+ variant: "ghost",
3851
+ size: "sm",
3852
+ disabled: !writable,
3853
+ onClick: () => {
3854
+ onAssign(department);
3855
+ },
3856
+ children: "分配管理员"
3857
+ }) : null,
3858
+ canWrite ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3859
+ variant: "ghost",
3860
+ size: "sm",
3861
+ onClick: () => {
3862
+ onAdd(department.code);
3863
+ },
3864
+ children: "+ 子部门"
3865
+ }) : null,
3866
+ canWrite ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3867
+ variant: "ghost",
3868
+ size: "sm",
3869
+ onClick: () => {
3870
+ onEdit(department);
3871
+ },
3872
+ children: "编辑"
3873
+ }) : null,
3874
+ canWrite ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3875
+ variant: "ghost",
3876
+ size: "sm",
3877
+ onClick: () => {
3878
+ onDelete(department);
3879
+ },
3880
+ children: "删除"
3881
+ }) : null
3882
+ ]
3883
+ })
3884
+ ]
3885
+ }), children.length > 0 && open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
3886
+ className: PermissionCenterSection_module_css_default.treeChildren,
3887
+ children: children.map((child) => node(child, depth + 1))
3888
+ }) : null]
3889
+ }, department.code);
3890
+ };
3891
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3892
+ className: PermissionCenterSection_module_css_default.panel,
3893
+ children: [
3894
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3895
+ className: PermissionCenterSection_module_css_default.panelHead,
3896
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
3897
+ className: PermissionCenterSection_module_css_default.sectionTitle,
3898
+ children: t("permTabDepartments")
3899
+ }), canWrite ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3900
+ variant: "outline",
3901
+ size: "sm",
3902
+ onClick: () => {
3903
+ onAdd(null);
3904
+ },
3905
+ children: "新增部门"
3906
+ }) : null]
3907
+ }),
3908
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
3909
+ className: PermissionCenterSection_module_css_default.treeRoot,
3910
+ children: (byParent.get(null) ?? []).map((department) => node(department, 0))
3911
+ }),
3912
+ departments.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3913
+ className: PermissionCenterSection_module_css_default.muted,
3914
+ children: "暂无部门。"
3915
+ }) : null
3916
+ ]
3917
+ });
3918
+ }
3919
+ /** Create/edit dialog for one department. */
3920
+ function DepartmentFormDialog({ state, departments, t, onClose, onSaved }) {
3921
+ const [code, setCode] = (0, react.useState)("");
3922
+ const [name, setName] = (0, react.useState)("");
3923
+ const [parentCode, setParentCode] = (0, react.useState)("");
3924
+ const [description, setDescription] = (0, react.useState)("");
3925
+ const [owner, setOwner] = (0, react.useState)("");
3926
+ const [enabled, setEnabled] = (0, react.useState)(true);
3927
+ const [saving, setSaving] = (0, react.useState)(false);
3928
+ const [error, setError] = (0, react.useState)(null);
3929
+ (0, react.useEffect)(() => {
3930
+ if (state === null) return;
3931
+ setCode(state.department?.code ?? "");
3932
+ setName(state.department?.name ?? "");
3933
+ setParentCode(state.parentCode ?? state.department?.parentCode ?? "");
3934
+ setDescription(state.department?.description ?? "");
3935
+ setOwner(state.department?.owner ?? "");
3936
+ setEnabled(state.department?.enabled !== false);
3937
+ setError(null);
3938
+ }, [state]);
3939
+ if (state === null) return null;
3940
+ const editing = state.mode === "edit";
3941
+ const blocked = /* @__PURE__ */ new Set();
3942
+ if (editing && state.department !== void 0) {
3943
+ blocked.add(state.department.code);
3944
+ let changed = true;
3945
+ while (changed) {
3946
+ changed = false;
3947
+ for (const department of departments) if (department.parentCode !== void 0 && department.parentCode !== null && blocked.has(department.parentCode) && !blocked.has(department.code)) {
3948
+ blocked.add(department.code);
3949
+ changed = true;
3950
+ }
3951
+ }
3952
+ }
3953
+ const submit = async () => {
3954
+ if (saving) return;
3955
+ setSaving(true);
3956
+ setError(null);
3957
+ try {
3958
+ const result = editing && state.department !== void 0 ? await updateDepartment({
3959
+ code: state.department.code,
3960
+ name,
3961
+ parentCode: parentCode === "" ? null : parentCode,
3962
+ description,
3963
+ owner,
3964
+ enabled,
3965
+ updatedAt: state.department.updatedAt
3966
+ }) : await createDepartment({
3967
+ code: code.trim(),
3968
+ name: name.trim(),
3969
+ parentCode: parentCode === "" ? null : parentCode,
3970
+ description,
3971
+ owner,
3972
+ enabled
3973
+ });
3974
+ if (!result.ok) {
3975
+ setError(result.reason ?? "保存失败:后端拒绝或无权限(403)。");
3976
+ return;
3977
+ }
3978
+ await onSaved();
3979
+ } catch {
3980
+ setError("保存失败:网络或后端异常。");
3981
+ } finally {
3982
+ setSaving(false);
3983
+ }
3984
+ };
3985
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
3986
+ open: true,
3987
+ onClose: () => {
3988
+ if (!saving) onClose();
3989
+ },
3990
+ title: editing ? "编辑部门" : "新增部门",
3991
+ closeLabel: "取消",
3992
+ className: PermissionCenterSection_module_css_default.dialog,
3993
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3994
+ className: PermissionCenterSection_module_css_default.dialogFooter,
3995
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3996
+ variant: "outline",
3997
+ disabled: saving,
3998
+ onClick: onClose,
3999
+ children: "取消"
4000
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4001
+ variant: "primary",
4002
+ disabled: saving,
4003
+ onClick: () => {
4004
+ submit();
4005
+ },
4006
+ children: "保存"
4007
+ })]
4008
+ }),
4009
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4010
+ className: PermissionCenterSection_module_css_default.dialogBody,
4011
+ children: [
4012
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4013
+ className: PermissionCenterSection_module_css_default.field,
4014
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "部门编码" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4015
+ value: code,
4016
+ disabled: editing,
4017
+ placeholder: "例如 marketing",
4018
+ onChange: (event) => {
4019
+ setCode(event.target.value);
4020
+ }
4021
+ })]
4022
+ }),
4023
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4024
+ className: PermissionCenterSection_module_css_default.field,
4025
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "部门名称" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4026
+ value: name,
4027
+ placeholder: "例如 市场部",
4028
+ onChange: (event) => {
4029
+ setName(event.target.value);
4030
+ }
4031
+ })]
4032
+ }),
4033
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4034
+ className: PermissionCenterSection_module_css_default.field,
4035
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "上级部门" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchCombobox, {
4036
+ t,
4037
+ value: parentCode,
4038
+ ariaLabel: "上级部门",
4039
+ placeholder: t("permNone"),
4040
+ options: [{
4041
+ value: "",
4042
+ label: t("permNone")
4043
+ }, ...departments.filter((department) => !blocked.has(department.code)).map((department) => ({
4044
+ value: department.code,
4045
+ label: department.name,
4046
+ hint: department.code
4047
+ }))],
4048
+ onChange: setParentCode
4049
+ })]
4050
+ }),
4051
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4052
+ className: PermissionCenterSection_module_css_default.field,
4053
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "描述" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4054
+ value: description,
4055
+ onChange: (event) => {
4056
+ setDescription(event.target.value);
4057
+ }
4058
+ })]
4059
+ }),
4060
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4061
+ className: PermissionCenterSection_module_css_default.field,
4062
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "负责人" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4063
+ value: owner,
4064
+ onChange: (event) => {
4065
+ setOwner(event.target.value);
4066
+ }
4067
+ })]
4068
+ }),
4069
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4070
+ className: PermissionCenterSection_module_css_default.candidate,
4071
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4072
+ type: "checkbox",
4073
+ checked: enabled,
4074
+ onChange: (event) => {
4075
+ setEnabled(event.target.checked);
4076
+ }
4077
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "启用" })]
4078
+ }),
4079
+ error === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4080
+ className: PermissionCenterSection_module_css_default.error,
4081
+ role: "alert",
4082
+ children: error
4083
+ })
4084
+ ]
4085
+ })
4086
+ });
4087
+ }
4088
+ function Resources({ resources, departments, writable, actorRole, actorDepartmentCode, busy, t, onSave }) {
4089
+ const [page, setPage] = (0, react.useState)(0);
4090
+ const [draft, setDraft] = (0, react.useState)({});
4091
+ const [error, setError] = (0, react.useState)(null);
4092
+ const [detail, setDetail] = (0, react.useState)(null);
4093
+ const pages = Math.max(1, Math.ceil(resources.length / PAGE_SIZE));
4094
+ const visible = resources.slice(page * PAGE_SIZE, page * PAGE_SIZE + PAGE_SIZE);
4095
+ const departmentOptions = actorRole === "DEPT_ADMIN" ? departments.filter((department) => department.code === actorDepartmentCode || department.path?.some((entry) => entry.code === actorDepartmentCode) === true) : departments;
4096
+ const keyOf = (row) => row.kind + ":" + String(row.id);
4097
+ const valueOf = (row) => draft[keyOf(row)] ?? {
4098
+ visibility: row.visibility,
4099
+ departmentCode: row.departmentCode ?? null
4100
+ };
4101
+ const visibilityLabel = (visibility) => visibility === "global" ? t("permGlobal") : visibility === "department" ? t("permDepartmentScope") : t("permPrivate");
4102
+ const statusLabel = (status) => status === "pending" ? t("permStatusPending") : status === "approved" ? t("permStatusApproved") : status === "rejected" ? t("permStatusRejected") : "—";
4103
+ const departmentName = (code) => code === null || code === void 0 ? "—" : departments.find((department) => department.code === code)?.name ?? code;
4104
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4105
+ className: PermissionCenterSection_module_css_default.panel,
4106
+ children: [
4107
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4108
+ className: PermissionCenterSection_module_css_default.tableScroll,
4109
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
4110
+ className: PermissionCenterSection_module_css_default.table,
4111
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
4112
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permType") }),
4113
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permName") }),
4114
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permOwner") }),
4115
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permScope") }),
4116
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permDepartment") }),
4117
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permOperations") })
4118
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: visible.map((row) => {
4119
+ const key = keyOf(row);
4120
+ const value = valueOf(row);
4121
+ const visibilityOptions = actorRole === "DEPT_ADMIN" ? ["department", "private"] : [
4122
+ "global",
4123
+ "department",
4124
+ "private"
4125
+ ];
4126
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
4127
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.kind === "skill" ? t("permResourceSkill") : t("permResourceWorkflow") }),
4128
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: row.name }) }),
4129
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: ownerDisplay(row.ownerName) }),
4130
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchCombobox, {
4131
+ t,
4132
+ value: value.visibility,
4133
+ ariaLabel: t("permScope"),
4134
+ placeholder: t("permSelect"),
4135
+ disabled: !writable || busy,
4136
+ options: visibilityOptions.map((option) => ({
4137
+ value: option,
4138
+ label: visibilityLabel(option)
4139
+ })),
4140
+ onChange: (next) => {
4141
+ const visibility = next;
4142
+ setDraft((current) => ({
4143
+ ...current,
4144
+ [key]: {
4145
+ visibility,
4146
+ departmentCode: visibility === "department" ? current[key]?.departmentCode ?? row.departmentCode ?? actorDepartmentCode ?? null : null
4147
+ }
4148
+ }));
4149
+ }
4150
+ }) }),
4151
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchCombobox, {
4152
+ t,
4153
+ value: value.departmentCode ?? "",
4154
+ ariaLabel: t("permDepartment"),
4155
+ placeholder: t("permNone"),
4156
+ disabled: !writable || busy || value.visibility !== "department" || actorRole === "DEPT_ADMIN",
4157
+ options: [{
4158
+ value: "",
4159
+ label: t("permNone")
4160
+ }, ...departmentOptions.map((department) => ({
4161
+ value: department.code,
4162
+ label: department.name,
4163
+ hint: department.code
4164
+ }))],
4165
+ onChange: (next) => {
4166
+ setDraft((current) => ({
4167
+ ...current,
4168
+ [key]: {
4169
+ ...valueOf(row),
4170
+ departmentCode: next === "" ? null : next
4171
+ }
4172
+ }));
4173
+ }
4174
+ }) }),
4175
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4176
+ className: PermissionCenterSection_module_css_default.rowActions,
4177
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4178
+ variant: "ghost",
4179
+ size: "sm",
4180
+ onClick: () => {
4181
+ setDetail(row);
4182
+ },
4183
+ children: t("permViewDetails")
4184
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4185
+ variant: "outline",
4186
+ size: "sm",
4187
+ disabled: !writable || busy,
4188
+ onClick: () => {
4189
+ setError(null);
4190
+ if (window.confirm(t("permSave") + "?")) onSave(row, value.visibility, value.departmentCode).then((result) => {
4191
+ if (!result.ok) setError(result.reason ?? t("permSaveFailed"));
4192
+ });
4193
+ },
4194
+ children: t("permSave")
4195
+ })]
4196
+ }) })
4197
+ ] }, key);
4198
+ }) })]
4199
+ })
4200
+ }),
4201
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pager, {
4202
+ page,
4203
+ pages,
4204
+ onChange: setPage
4205
+ }),
4206
+ error === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4207
+ className: PermissionCenterSection_module_css_default.error,
4208
+ role: "alert",
4209
+ children: error
4210
+ }),
4211
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
4212
+ open: detail !== null,
4213
+ onClose: () => {
4214
+ setDetail(null);
4215
+ },
4216
+ title: t("permDetailTitle"),
4217
+ closeLabel: t("permClose"),
4218
+ className: PermissionCenterSection_module_css_default.dialog,
4219
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4220
+ className: PermissionCenterSection_module_css_default.dialogFooter,
4221
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4222
+ variant: "outline",
4223
+ onClick: () => {
4224
+ setDetail(null);
4225
+ },
4226
+ children: t("permClose")
4227
+ })
4228
+ }),
4229
+ children: detail === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
4230
+ className: PermissionCenterSection_module_css_default.detailGrid,
4231
+ children: [
4232
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("permDetailType") }),
4233
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: detail.kind === "skill" ? t("permResourceSkill") : t("permResourceWorkflow") }),
4234
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("permDetailName") }),
4235
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: detail.name }),
4236
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("permDetailOwner") }),
4237
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: ownerDisplay(detail.ownerName) }),
4238
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("permDetailScope") }),
4239
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: visibilityLabel(detail.visibility) }),
4240
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("permDetailDepartment") }),
4241
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: departmentName(detail.departmentCode) }),
4242
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("permDetailStatus") }),
4243
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: statusLabel(detail.reviewStatus) }),
4244
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("permDetailUpdated") }),
4245
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: detail.updatedAt === void 0 ? "—" : new Date(detail.updatedAt).toLocaleString() })
4246
+ ]
4247
+ })
4248
+ })
4249
+ ]
4250
+ });
4251
+ }
4252
+ /** Accessible searchable combobox used for visibility and department pickers. */
4253
+ function SearchCombobox({ t, value, options, onChange, placeholder, ariaLabel, disabled = false }) {
4254
+ const [open, setOpen] = (0, react.useState)(false);
4255
+ const [query, setQuery] = (0, react.useState)("");
4256
+ const [active, setActive] = (0, react.useState)(0);
4257
+ const rootRef = (0, react.useRef)(null);
4258
+ const inputRef = (0, react.useRef)(null);
4259
+ const selected = options.find((option) => option.value === value);
4260
+ const filtered = (0, react.useMemo)(() => filterComboboxOptions(options, query), [options, query]);
4261
+ (0, react.useEffect)(() => {
4262
+ if (!open) return;
4263
+ const close = (event) => {
4264
+ if (rootRef.current !== null && !rootRef.current.contains(event.target)) setOpen(false);
4265
+ };
4266
+ document.addEventListener("mousedown", close);
4267
+ inputRef.current?.focus();
4268
+ return () => {
4269
+ document.removeEventListener("mousedown", close);
4270
+ };
4271
+ }, [open]);
4272
+ (0, react.useEffect)(() => {
4273
+ if (!open) return;
4274
+ const index = filtered.findIndex((option) => option.value === value);
4275
+ setActive(index >= 0 ? index : 0);
4276
+ }, [
4277
+ open,
4278
+ filtered,
4279
+ value
4280
+ ]);
4281
+ const choose = (next) => {
4282
+ onChange(next);
4283
+ setOpen(false);
4284
+ setQuery("");
4285
+ };
4286
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4287
+ className: PermissionCenterSection_module_css_default.combobox,
4288
+ ref: rootRef,
4289
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4290
+ type: "button",
4291
+ className: PermissionCenterSection_module_css_default.comboboxTrigger,
4292
+ "aria-label": ariaLabel,
4293
+ "aria-haspopup": "listbox",
4294
+ "aria-expanded": open,
4295
+ disabled,
4296
+ onClick: () => {
4297
+ setOpen((current) => !current);
4298
+ },
4299
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4300
+ className: selected === void 0 ? PermissionCenterSection_module_css_default.comboboxPlaceholder : void 0,
4301
+ children: selected?.label ?? placeholder
4302
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4303
+ "aria-hidden": "true",
4304
+ children: "⌄"
4305
+ })]
4306
+ }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4307
+ className: PermissionCenterSection_module_css_default.comboboxMenu,
4308
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4309
+ ref: inputRef,
4310
+ className: PermissionCenterSection_module_css_default.comboboxSearch,
4311
+ value: query,
4312
+ placeholder: t("permSearch"),
4313
+ "aria-label": t("permSearch"),
4314
+ onChange: (event) => {
4315
+ setQuery(event.target.value);
4316
+ setActive(0);
4317
+ },
4318
+ onKeyDown: (event) => {
4319
+ if (event.key === "Escape") {
4320
+ setOpen(false);
4321
+ return;
4322
+ }
4323
+ if (event.key === "ArrowDown") {
4324
+ event.preventDefault();
4325
+ setActive((current) => clampActiveIndex(current + 1, filtered.length));
4326
+ }
4327
+ if (event.key === "ArrowUp") {
4328
+ event.preventDefault();
4329
+ setActive((current) => clampActiveIndex(current - 1, filtered.length));
4330
+ }
4331
+ if (event.key === "Enter") {
4332
+ event.preventDefault();
4333
+ const option = filtered[active];
4334
+ if (option !== void 0) choose(option.value);
4335
+ }
4336
+ }
4337
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
4338
+ className: PermissionCenterSection_module_css_default.comboboxList,
4339
+ role: "listbox",
4340
+ "aria-label": ariaLabel,
4341
+ children: filtered.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
4342
+ className: PermissionCenterSection_module_css_default.comboboxEmpty,
4343
+ children: t("permNoMatches")
4344
+ }) : filtered.map((option, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
4345
+ role: "option",
4346
+ "aria-selected": option.value === value,
4347
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4348
+ type: "button",
4349
+ className: index === active ? PermissionCenterSection_module_css_default.comboboxOption + " " + PermissionCenterSection_module_css_default.comboboxOptionActive : PermissionCenterSection_module_css_default.comboboxOption,
4350
+ onMouseEnter: () => {
4351
+ setActive(index);
4352
+ },
4353
+ onClick: () => {
4354
+ choose(option.value);
4355
+ },
4356
+ children: [
4357
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4358
+ className: PermissionCenterSection_module_css_default.comboboxCheck,
4359
+ children: option.value === value ? "✓" : ""
4360
+ }),
4361
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: option.label }),
4362
+ option.hint === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4363
+ className: PermissionCenterSection_module_css_default.comboboxHint,
4364
+ children: option.hint
4365
+ })
4366
+ ]
4367
+ })
4368
+ }, option.value))
4369
+ })]
4370
+ }) : null]
4371
+ });
4372
+ }
4373
+ function Pager({ page, pages, onChange }) {
4374
+ if (pages <= 1) return null;
4375
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4376
+ className: PermissionCenterSection_module_css_default.pager,
4377
+ children: [
4378
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4379
+ variant: "outline",
4380
+ size: "sm",
4381
+ disabled: page === 0,
4382
+ onClick: () => {
4383
+ onChange(page - 1);
4384
+ },
4385
+ children: "上一页"
4386
+ }),
4387
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4388
+ className: PermissionCenterSection_module_css_default.muted,
4389
+ children: [
4390
+ page + 1,
4391
+ " / ",
4392
+ pages
4393
+ ]
4394
+ }),
4395
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4396
+ variant: "outline",
4397
+ size: "sm",
4398
+ disabled: page >= pages - 1,
4399
+ onClick: () => {
4400
+ onChange(page + 1);
4401
+ },
4402
+ children: "下一页"
4403
+ })
4404
+ ]
4405
+ });
4406
+ }
4407
+ /** The four role labels, so the header does not show a backend code. */
4408
+ function roleLabel(role) {
4409
+ switch (role) {
4410
+ case "SUPER_ADMIN": return "超级管理员";
4411
+ case "ADMIN": return "管理员";
4412
+ case "DEPT_ADMIN": return "部门管理员";
4413
+ default: return "应用人员";
4414
+ }
4415
+ }
4416
+ //#endregion
4417
+ //#region src/client/PermissionCenterIcon.tsx
4418
+ /**
4419
+ * The sidebar glyph for the permission centre.
4420
+ *
4421
+ * The entry is hidden for a caller the Host reports as an ordinary user. This is a navigation
4422
+ * affordance, not the authorization decision: every permission-centre endpoint re-checks the
4423
+ * caller, and the page itself refuses to render without a management role.
4424
+ */
4425
+ function PermissionCenterIcon() {
4426
+ const [allowed, setAllowed] = (0, react.useState)(() => canSeeManagedSettings() ? true : void 0);
4427
+ (0, react.useEffect)(() => {
4428
+ let live = true;
4429
+ if (reviewDemoMode() || canSeeManagedSettings()) {
4430
+ setAllowed(true);
4431
+ return () => {
4432
+ live = false;
4433
+ };
4434
+ }
4435
+ fetchReviewContext().then((context) => {
4436
+ if (!live) return;
4437
+ setAllowed(context !== null && context.role !== "APP_USER");
4438
+ }).catch(() => {
4439
+ if (live) setAllowed(false);
4440
+ });
4441
+ return () => {
4442
+ live = false;
4443
+ };
4444
+ }, []);
4445
+ if (allowed !== true) return null;
4446
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
4447
+ width: "18",
4448
+ height: "18",
4449
+ viewBox: "0 0 18 18",
4450
+ fill: "none",
4451
+ "aria-hidden": "true",
4452
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
4453
+ d: "M9 2.2 14.5 4v4.4c0 3.1-2.1 5.6-5.5 7.4-3.4-1.8-5.5-4.3-5.5-7.4V4L9 2.2Z",
4454
+ stroke: "currentColor",
4455
+ strokeWidth: "1.2",
4456
+ strokeLinejoin: "round"
4457
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
4458
+ d: "m6.7 8.8 1.6 1.6 3-3.1",
4459
+ stroke: "currentColor",
4460
+ strokeWidth: "1.2",
4461
+ strokeLinecap: "round",
4462
+ strokeLinejoin: "round"
4463
+ })]
4464
+ });
4465
+ }
4466
+ //#endregion
4467
+ //#region src/client/workspace-upgrade-client.ts
4468
+ /** Same-origin calls for the workspace image upgrade banner. */
4469
+ /** Route the plugin's Host half serves for upgrade detection and start. */
4470
+ const WORKSPACE_UPGRADE_PATH = "/integrations/dataops/workspace-upgrade";
4471
+ /** Narrow an unknown response into the status contract. */
4472
+ function asStatus(value) {
4473
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
4474
+ const record = value;
4475
+ const imageStatus = record.imageStatus;
4476
+ if (imageStatus !== "CURRENT" && imageStatus !== "UPDATE_AVAILABLE" && imageStatus !== "NOT_RUNNING" && imageStatus !== "UNKNOWN") return null;
4477
+ const sessions = Array.isArray(record.activeSessions) ? record.activeSessions.flatMap((entry) => {
4478
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return [];
4479
+ const session = entry;
4480
+ if (typeof session.sessionId !== "string" || session.sessionId.trim() === "") return [];
4481
+ return [{
4482
+ sessionId: session.sessionId.trim(),
4483
+ ...typeof session.title === "string" && session.title.trim() !== "" ? { title: session.title.trim() } : {},
4484
+ ...typeof session.cwd === "string" && session.cwd.trim() !== "" ? { cwd: session.cwd.trim() } : {}
4485
+ }];
4486
+ }) : [];
4487
+ return {
4488
+ imageStatus,
4489
+ configuredImage: typeof record.configuredImage === "string" ? record.configuredImage : "",
4490
+ activeSessions: sessions
4491
+ };
4492
+ }
4493
+ /**
4494
+ * Read the workspace upgrade preview.
4495
+ * @returns the status, or null when the Host route is not ready or not connected.
4496
+ */
4497
+ async function fetchWorkspaceUpgrade() {
4498
+ const response = await fetch(WORKSPACE_UPGRADE_PATH, {
4499
+ credentials: "same-origin",
4500
+ headers: { accept: "application/json" }
4501
+ });
4502
+ if (response.status === 503 || response.status === 401) return null;
4503
+ if (!response.ok) throw new Error(`workspace upgrade preview failed with HTTP ${String(response.status)}`);
4504
+ return asStatus(await response.json());
4505
+ }
4506
+ /**
4507
+ * Ask the Host to snapshot sessions and recreate the workspace container.
4508
+ * @returns the accepted hand-off summary.
4509
+ */
4510
+ async function requestWorkspaceUpgrade() {
4511
+ const response = await fetch(WORKSPACE_UPGRADE_PATH, {
4512
+ method: "POST",
4513
+ credentials: "same-origin",
4514
+ headers: {
4515
+ "content-type": "application/json",
4516
+ accept: "application/json"
4517
+ },
4518
+ body: JSON.stringify({ force: true })
4519
+ });
4520
+ const body = await response.json().catch(() => null);
4521
+ if (!response.ok) {
4522
+ const message = typeof body?.error === "string" ? body.error : `workspace upgrade failed with HTTP ${String(response.status)}`;
4523
+ throw new Error(message);
4524
+ }
4525
+ return {
4526
+ accepted: body?.accepted === true,
4527
+ upgradeId: typeof body?.upgradeId === "string" ? body.upgradeId : "",
4528
+ sessions: typeof body?.sessions === "number" ? body.sessions : 0
4529
+ };
4530
+ }
4531
+ //#endregion
4532
+ //#region \0dsh-css:/root/projects/dsh-plugins-plus/packages/dataops-managed/src/client/WorkspaceUpgradePrompt.module.css.mjs
4533
+ const css = ".razUfG_banner{z-index:40;border:.5px solid var(--dsw-alias-settings-card-stroke);border-radius:var(--dsw-radius-xl);background:var(--dsw-alias-settings-card-fill);width:min(680px,100vw - 32px);color:var(--dsw-alias-label-primary);justify-content:space-between;align-items:center;gap:20px;padding:10px 12px 10px 16px;display:flex;position:fixed;top:16px;left:50%;transform:translate(-50%);box-shadow:0 8px 28px #0000001f}.razUfG_copy{flex-direction:column;gap:2px;min-width:0;display:flex}.razUfG_title{font-size:13px;font-weight:600;line-height:22px}.razUfG_body{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.razUfG_actions{flex:none;align-items:center;gap:4px;display:flex}.razUfG_dialogBody{flex-direction:column;gap:12px;min-width:min(420px,70vw);display:flex}.razUfG_dialogText{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px;line-height:22px}.razUfG_sessionList{border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-bg-layer-1);flex-direction:column;gap:4px;max-height:220px;margin:0;padding:8px 10px;list-style:none;display:flex;overflow:auto}.razUfG_sessionRow{flex-direction:column;gap:1px;min-width:0;display:flex}.razUfG_sessionTitle{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:18px;overflow:hidden}.razUfG_sessionPath{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:11px;line-height:16px;overflow:hidden}.razUfG_error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:18px}";
4534
+ const tagId = "@sparkelf/dsh-dataops-managed/WorkspaceUpgradePrompt.module.css";
4535
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
4536
+ const tag = document.createElement("style");
4537
+ tag.dataset.plugin = "@sparkelf/dsh-dataops-managed";
4538
+ tag.dataset.pluginCss = tagId;
4539
+ tag.textContent = css;
4540
+ document.head.appendChild(tag);
4541
+ }
4542
+ var WorkspaceUpgradePrompt_module_css_default = {
4543
+ "sessionPath": "razUfG_sessionPath",
4544
+ "banner": "razUfG_banner",
4545
+ "copy": "razUfG_copy",
4546
+ "actions": "razUfG_actions",
4547
+ "sessionRow": "razUfG_sessionRow",
4548
+ "dialogText": "razUfG_dialogText",
4549
+ "sessionTitle": "razUfG_sessionTitle",
4550
+ "title": "razUfG_title",
4551
+ "sessionList": "razUfG_sessionList",
4552
+ "error": "razUfG_error",
4553
+ "dialogBody": "razUfG_dialogBody",
4554
+ "body": "razUfG_body"
4555
+ };
4556
+ //#endregion
4557
+ //#region src/client/WorkspaceUpgradePrompt.tsx
4558
+ /**
4559
+ * Workspace image upgrade banner and confirmation dialog.
4560
+ *
4561
+ * The banner is deliberately quiet: it tells the user a newer workspace image exists and
4562
+ * leaves the current session usable. Only the explicit upgrade action opens the dialog
4563
+ * that can interrupt running sessions.
4564
+ *
4565
+ * @module WorkspaceUpgradePrompt
4566
+ */
4567
+ /** How often to re-read the image status while the page is open. */
4568
+ const POLL_MS = 3e4;
4569
+ /** Wait before a reload so the restarted workspace has a chance to come back. */
4570
+ const RELOAD_DELAY_MS = 12e3;
4571
+ /**
4572
+ * Render the banner and its upgrade dialog.
4573
+ * @param props.t - localized copy resolver.
4574
+ * @returns the banner while an image update is available, otherwise null.
4575
+ */
4576
+ function WorkspaceUpgradePrompt(props) {
4577
+ const { t } = props;
4578
+ const [status, setStatus] = (0, react.useState)(null);
4579
+ const [dismissed, setDismissed] = (0, react.useState)(false);
4580
+ const [dialogOpen, setDialogOpen] = (0, react.useState)(false);
4581
+ const [busy, setBusy] = (0, react.useState)(false);
4582
+ const [notice, setNotice] = (0, react.useState)("");
4583
+ const [error, setError] = (0, react.useState)("");
4584
+ const refresh = (0, react.useCallback)(async () => {
4585
+ try {
4586
+ const next = await fetchWorkspaceUpgrade();
4587
+ if (next !== null) setStatus(next);
4588
+ } catch {}
4589
+ }, []);
4590
+ (0, react.useEffect)(() => {
4591
+ let cancelled = false;
4592
+ const tick = async () => {
4593
+ if (cancelled) return;
4594
+ await refresh();
4595
+ };
4596
+ tick();
4597
+ const timer = setInterval(() => {
4598
+ tick();
4599
+ }, POLL_MS);
4600
+ return () => {
4601
+ cancelled = true;
4602
+ clearInterval(timer);
4603
+ };
4604
+ }, [refresh]);
4605
+ const available = status?.imageStatus === "UPDATE_AVAILABLE";
4606
+ const sessionCount = status?.activeSessions.length ?? 0;
4607
+ const warning = (0, react.useMemo)(() => t("upgradeSessionsWarning").replace("{count}", String(sessionCount)), [sessionCount, t]);
4608
+ const openDialog = (0, react.useCallback)(() => {
4609
+ setError("");
4610
+ setDialogOpen(true);
4611
+ }, []);
4612
+ const closeDialog = (0, react.useCallback)(() => {
4613
+ if (busy) return;
4614
+ setDialogOpen(false);
4615
+ }, [busy]);
4616
+ const upgrade = (0, react.useCallback)(async () => {
4617
+ setBusy(true);
4618
+ setError("");
4619
+ try {
4620
+ await requestWorkspaceUpgrade();
4621
+ setDialogOpen(false);
4622
+ setNotice(t("upgradeStarted"));
4623
+ setDismissed(true);
4624
+ window.setTimeout(() => {
4625
+ window.location.reload();
4626
+ }, RELOAD_DELAY_MS);
4627
+ } catch (cause) {
4628
+ if (cause instanceof TypeError) {
4629
+ setDialogOpen(false);
4630
+ setNotice(t("upgradeStarted"));
4631
+ setDismissed(true);
4632
+ window.setTimeout(() => {
4633
+ window.location.reload();
4634
+ }, RELOAD_DELAY_MS);
4635
+ return;
4636
+ }
4637
+ setError(cause instanceof Error ? cause.message : t("upgradeFailed"));
4638
+ } finally {
4639
+ setBusy(false);
4640
+ }
4641
+ }, [t]);
4642
+ if (notice !== "") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4643
+ className: WorkspaceUpgradePrompt_module_css_default.banner,
4644
+ role: "status",
4645
+ "aria-live": "polite",
4646
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4647
+ className: WorkspaceUpgradePrompt_module_css_default.title,
4648
+ children: notice
4649
+ })
4650
+ });
4651
+ if (!available || dismissed) return null;
4652
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4653
+ className: WorkspaceUpgradePrompt_module_css_default.banner,
4654
+ role: "status",
4655
+ "aria-live": "polite",
4656
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4657
+ className: WorkspaceUpgradePrompt_module_css_default.copy,
4658
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
4659
+ className: WorkspaceUpgradePrompt_module_css_default.title,
4660
+ children: t("upgradeAvailableTitle")
4661
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4662
+ className: WorkspaceUpgradePrompt_module_css_default.body,
4663
+ children: t("upgradeAvailableBody")
4664
+ })]
4665
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4666
+ className: WorkspaceUpgradePrompt_module_css_default.actions,
4667
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4668
+ variant: "primary",
4669
+ size: "sm",
4670
+ onClick: openDialog,
4671
+ children: t("upgradeAction")
4672
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4673
+ variant: "ghost",
4674
+ size: "sm",
4675
+ onClick: () => {
4676
+ setDismissed(true);
4677
+ },
4678
+ children: t("upgradeLater")
4679
+ })]
4680
+ })]
4681
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
4682
+ open: dialogOpen,
4683
+ onClose: closeDialog,
4684
+ title: t("upgradeDialogTitle"),
4685
+ description: t("upgradeDialogBody"),
4686
+ closeLabel: t("upgradeCancel"),
4687
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4688
+ variant: "ghost",
4689
+ onClick: closeDialog,
4690
+ disabled: busy,
4691
+ children: t("upgradeCancel")
4692
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4693
+ variant: "primary",
4694
+ onClick: () => {
4695
+ upgrade();
4696
+ },
4697
+ disabled: busy,
4698
+ children: busy ? t("upgradeBusy") : sessionCount === 0 ? t("upgradeAction") : t("upgradeConfirm")
4699
+ })] }),
4700
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4701
+ className: WorkspaceUpgradePrompt_module_css_default.dialogBody,
4702
+ children: [
4703
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4704
+ className: WorkspaceUpgradePrompt_module_css_default.dialogText,
4705
+ children: sessionCount === 0 ? t("upgradeNoSessions") : warning
4706
+ }),
4707
+ sessionCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
4708
+ className: WorkspaceUpgradePrompt_module_css_default.sessionList,
4709
+ children: status?.activeSessions.map((session) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
4710
+ className: WorkspaceUpgradePrompt_module_css_default.sessionRow,
4711
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4712
+ className: WorkspaceUpgradePrompt_module_css_default.sessionTitle,
4713
+ children: session.title ?? session.sessionId
4714
+ }), session.cwd ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4715
+ className: WorkspaceUpgradePrompt_module_css_default.sessionPath,
4716
+ children: session.cwd
4717
+ }) : null]
4718
+ }, session.sessionId))
4719
+ }) : null,
4720
+ error !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4721
+ className: WorkspaceUpgradePrompt_module_css_default.error,
4722
+ role: "alert",
4723
+ children: error
4724
+ }) : null
4725
+ ]
4726
+ })
4727
+ })] });
4728
+ }
4729
+ //#endregion
4730
+ //#region src/client/locales.ts
4731
+ /** English managed DataOps Settings copy. */
4732
+ const en = {
4733
+ nav: "DataOps",
4734
+ title: "DataOps",
4735
+ managed: "Connected to DataOps",
4736
+ unavailable: "DataOps is not connected.",
4737
+ loadFailed: "Loading failed. Try again.",
4738
+ brandingTitle: "Wanxiang branding",
4739
+ brandingToggle: "Wanxiang branding",
4740
+ limitsTitle: "File size limits",
4741
+ limitsFileReadLabel: "Largest file to read (MiB)",
4742
+ limitsUploadLabel: "Largest file to upload (MiB)",
4743
+ limitsInvalid: "Enter a size in MiB.",
4744
+ limitsSave: "Save",
4745
+ limitsSaving: "Saving…",
4746
+ timeoutTitle: "DataOps query timeout",
4747
+ timeoutLabel: "Longest single query (seconds)",
4748
+ timeoutInvalid: "Enter 1 to 3600 seconds.",
4749
+ defaultsTitle: "Default configuration",
4750
+ defaultsPublish: "Publish",
4751
+ defaultsPublishing: "Publishing…",
4752
+ defaultsPublishedAt: "Last published",
4753
+ defaultsNeverPublished: "Not published yet",
4754
+ defaultsNotAdmin: "Administrators only",
4755
+ plazaTitle: "Skill plaza",
4756
+ plazaAllTags: "All tags",
4757
+ plazaMine: "Shared by me",
4758
+ plazaShowDetail: "Details",
4759
+ plazaClose: "Close",
4760
+ plazaDetailUpdated: "Updated",
4761
+ plazaDetailBody: "Skill body",
4762
+ plazaInstall: "Install",
4763
+ plazaInstalled: "Installed. It is now in your skill list.",
4764
+ plazaUnshare: "Stop sharing",
4765
+ plazaUnshared: "Removed from the plaza. Users who installed it keep their copy.",
4766
+ plazaEmpty: "No skills have been shared yet.",
4767
+ plazaUnavailable: "The skill plaza is available once this workspace is connected to DataOps.",
4768
+ plazaLoadFailed: "The skill plaza could not be loaded. Try again.",
4769
+ plazaActionFailed: "The action could not be completed. Try again.",
4770
+ plazaRetry: "Try again",
4771
+ plazaRefresh: "Refresh",
4772
+ plazaTabApproved: "Approved",
4773
+ plazaTabPending: "Pending",
4774
+ plazaTabAll: "All",
4775
+ plazaSharedTag: "Shared",
4776
+ plazaReview: "Review",
4777
+ plazaPendingEmpty: "No skills are waiting for review.",
4778
+ plazaAuditTitle: "Review skill",
4779
+ plazaAuditSource: "Source",
4780
+ plazaAuditVisibility: "Visibility",
4781
+ plazaRiskTitle: "AI risk report",
4782
+ plazaRiskScan: "Run AI scan",
4783
+ plazaRiskScanning: "Scanning…",
4784
+ plazaRiskNone: "No AI risk report yet.",
4785
+ plazaRiskScore: "Risk score",
4786
+ plazaRiskFindings: "Findings",
4787
+ plazaRiskFailed: "The AI scan failed; review manually.",
4788
+ plazaApprove: "Approve",
4789
+ plazaReject: "Reject",
4790
+ plazaReviewNote: "Review note (required to reject)",
4791
+ plazaReviewNotePlaceholder: "Explain the reason or what to change…",
4792
+ plazaReviewDone: "Review saved.",
4793
+ plazaReviewFailed: "The review could not be saved. Try again.",
4794
+ plazaDemo: "Demo data",
4795
+ plazaGlobal: "Global",
4796
+ plazaPrivate: "Private",
4797
+ plazaDeptSuffix: " department",
4798
+ reviewSettingsTitle: "Review settings",
4799
+ reviewSettingsSkill: "Skill plaza review",
4800
+ reviewSettingsWorkflow: "Workflow plaza review",
4801
+ reviewSettingsKb: "Knowledge base review",
4802
+ reviewSettingsHint: "When on, submitted content stays private until an administrator approves it.",
4803
+ reviewSettingsSave: "Save",
4804
+ reviewSettingsSaved: "Review settings saved.",
4805
+ reviewSettingsFailed: "Could not save. Try again.",
4806
+ reviewSettingsWaiting: "Review settings API is waiting for the backend (task-39).",
4807
+ permNav: "Permissions",
4808
+ permTitle: "Permission centre",
4809
+ permTabRoles: "Role permissions",
4810
+ permTabMembers: "Member roles",
4811
+ permTabDepartments: "Departments",
4812
+ permTabResources: "Resource scope",
4813
+ permRoleMatrix: "Role permission matrix",
4814
+ permPermissionModule: "Module",
4815
+ permPermissionPoint: "Permission",
4816
+ permReadOnly: "Read only",
4817
+ permSave: "Save",
4818
+ permSaved: "Saved.",
4819
+ permSaveFailed: "Could not save. Try again.",
4820
+ permSuperAdminOnly: "Only a super administrator can change this.",
4821
+ permMember: "Member",
4822
+ permDepartment: "Department",
4823
+ permRole: "Role",
4824
+ permDeptAdmin: "Department administrator",
4825
+ permScope: "Visibility",
4826
+ permGlobal: "Global",
4827
+ permDepartmentScope: "Department",
4828
+ permPrivate: "Private",
4829
+ permNoAccess: "You do not have access to the permission centre.",
4830
+ permWaiting: "The permission-centre API is waiting for the backend (task-37).",
4831
+ permResourceSkill: "Skill",
4832
+ permResourceWorkflow: "Workflow",
4833
+ permResourceKb: "Knowledge base",
4834
+ permType: "Type",
4835
+ permName: "Name",
4836
+ permOwner: "Owner",
4837
+ permOperations: "Actions",
4838
+ permViewDetails: "View details",
4839
+ permDetailTitle: "Resource details",
4840
+ permDetailType: "Type",
4841
+ permDetailName: "Name",
4842
+ permDetailOwner: "Owner",
4843
+ permDetailScope: "Visibility",
4844
+ permDetailDepartment: "Department",
4845
+ permDetailStatus: "Review status",
4846
+ permDetailUpdated: "Updated",
4847
+ permNone: "None",
4848
+ permSearch: "Search…",
4849
+ permNoMatches: "No matches",
4850
+ permClose: "Close",
4851
+ permSelect: "Select…",
4852
+ permStatusPending: "Pending review",
4853
+ permStatusApproved: "Approved",
4854
+ permStatusRejected: "Rejected",
4855
+ permDemo: "Demo data",
4856
+ reloginTitle: "Sign in to DataOps again",
4857
+ reloginBody: "This workspace can no longer reach DataOps: the sign-in for this session has ended, so settings, shared models and the skill plaza cannot load. Sign in again in the DataOps window, then reload this page.",
4858
+ reloginAction: "Reload",
4859
+ reloginDismiss: "Later",
4860
+ upgradeAvailableTitle: "A new workspace version is available",
4861
+ upgradeAvailableBody: "Restart the workspace to apply it.",
4862
+ upgradeAction: "Upgrade",
4863
+ upgradeLater: "Later",
4864
+ upgradeDialogTitle: "Upgrade workspace",
4865
+ upgradeDialogBody: "The workspace will restart and briefly disconnect. Your files and sessions are kept.",
4866
+ upgradeNoSessions: "No sessions are running. The workspace will restart now.",
4867
+ upgradeSessionsWarning: "{count} running session(s) will be interrupted. After the upgrade, each one is resumed and sent “继续”.",
4868
+ upgradeCancel: "Cancel",
4869
+ upgradeConfirm: "Stop tasks and upgrade",
4870
+ upgradeBusy: "Upgrading workspace…",
4871
+ upgradeStarted: "Upgrade started. Reload this page when the workspace reconnects.",
4872
+ upgradeFailed: "The workspace upgrade could not be started. Try again.",
4873
+ upgradeRetry: "Retry"
4874
+ };
4875
+ /** Chinese managed DataOps Settings copy. */
4876
+ const zh = {
1387
4877
  nav: "DataOps",
1388
4878
  title: "DataOps",
1389
4879
  managed: "已连接 DataOps",
@@ -1421,18 +4911,142 @@ window.__ModuleLoader__.load({
1421
4911
  plazaUnavailable: "工作区接入 DataOps 后即可使用技能广场。",
1422
4912
  plazaLoadFailed: "技能广场加载失败,请重试。",
1423
4913
  plazaActionFailed: "操作未完成,请重试。",
1424
- plazaRetry: "重试"
4914
+ plazaRetry: "重试",
4915
+ plazaRefresh: "刷新",
4916
+ plazaTabApproved: "已审核",
4917
+ plazaTabPending: "未审核",
4918
+ plazaTabAll: "全部",
4919
+ plazaSharedTag: "已共享",
4920
+ plazaReview: "审核",
4921
+ plazaPendingEmpty: "没有待审核的技能。",
4922
+ plazaAuditTitle: "审核技能",
4923
+ plazaAuditSource: "来源",
4924
+ plazaAuditVisibility: "可见范围",
4925
+ plazaRiskTitle: "AI 风险报告",
4926
+ plazaRiskScan: "生成风险报告",
4927
+ plazaRiskScanning: "分析中…",
4928
+ plazaRiskNone: "尚未生成 AI 风险报告。",
4929
+ plazaRiskScore: "风险分",
4930
+ plazaRiskFindings: "发现",
4931
+ plazaRiskFailed: "风险报告生成失败,可人工审核。",
4932
+ plazaApprove: "通过",
4933
+ plazaReject: "驳回",
4934
+ plazaReviewNote: "审核意见(驳回必填)",
4935
+ plazaReviewNotePlaceholder: "说明原因或修改建议…",
4936
+ plazaReviewDone: "审核结果已保存。",
4937
+ plazaReviewFailed: "审核未完成,请重试。",
4938
+ plazaDemo: "演示数据",
4939
+ plazaGlobal: "全局",
4940
+ plazaPrivate: "私有",
4941
+ plazaDeptSuffix: "部门专属",
4942
+ reviewSettingsTitle: "审核设置",
4943
+ reviewSettingsSkill: "技能广场审核",
4944
+ reviewSettingsWorkflow: "工作流广场审核",
4945
+ reviewSettingsKb: "知识库审核",
4946
+ reviewSettingsHint: "开启后,提交内容需管理员审核通过才公开展示。",
4947
+ reviewSettingsSave: "保存",
4948
+ reviewSettingsSaved: "审核设置已保存。",
4949
+ reviewSettingsFailed: "保存失败,请重试。",
4950
+ reviewSettingsWaiting: "审核设置 API 待后端联调(task-39)。",
4951
+ permNav: "权限",
4952
+ permTitle: "权限中心",
4953
+ permTabRoles: "角色权限",
4954
+ permTabMembers: "成员角色",
4955
+ permTabDepartments: "部门",
4956
+ permTabResources: "资源范围",
4957
+ permRoleMatrix: "角色权限矩阵",
4958
+ permPermissionModule: "模块",
4959
+ permPermissionPoint: "权限点",
4960
+ permReadOnly: "只读",
4961
+ permSave: "保存",
4962
+ permSaved: "已保存。",
4963
+ permSaveFailed: "保存失败,请重试。",
4964
+ permSuperAdminOnly: "仅超级管理员可修改。",
4965
+ permMember: "成员",
4966
+ permDepartment: "部门",
4967
+ permRole: "角色",
4968
+ permDeptAdmin: "部门管理员",
4969
+ permScope: "可见范围",
4970
+ permGlobal: "全局",
4971
+ permDepartmentScope: "部门专属",
4972
+ permPrivate: "私有",
4973
+ permNoAccess: "无权限访问权限中心。",
4974
+ permWaiting: "权限中心 API 待后端联调(task-37)。",
4975
+ permResourceSkill: "技能",
4976
+ permResourceWorkflow: "工作流",
4977
+ permResourceKb: "知识库",
4978
+ permType: "类型",
4979
+ permName: "名称",
4980
+ permOwner: "所有者",
4981
+ permOperations: "操作",
4982
+ permViewDetails: "查看详情",
4983
+ permDetailTitle: "资源详情",
4984
+ permDetailType: "类型",
4985
+ permDetailName: "名称",
4986
+ permDetailOwner: "所有者",
4987
+ permDetailScope: "可见范围",
4988
+ permDetailDepartment: "部门",
4989
+ permDetailStatus: "审核状态",
4990
+ permDetailUpdated: "更新时间",
4991
+ permNone: "无",
4992
+ permSearch: "搜索…",
4993
+ permNoMatches: "无匹配项",
4994
+ permClose: "关闭",
4995
+ permSelect: "请选择…",
4996
+ permStatusPending: "待审核",
4997
+ permStatusApproved: "已审核",
4998
+ permStatusRejected: "已拒绝",
4999
+ permDemo: "演示数据",
5000
+ reloginTitle: "请重新登录 DataOps",
5001
+ reloginBody: "该工作区已无法访问 DataOps:本次登录会话已过期,因此设置、共享模型与技能广场都无法加载。请在 DataOps 页面重新登录,然后重新加载本页。",
5002
+ reloginAction: "重新加载",
5003
+ reloginDismiss: "稍后再说",
5004
+ upgradeAvailableTitle: "工作区有新版本可用",
5005
+ upgradeAvailableBody: "重启工作区后生效。",
5006
+ upgradeAction: "立即升级",
5007
+ upgradeLater: "稍后",
5008
+ upgradeDialogTitle: "升级工作区",
5009
+ upgradeDialogBody: "工作区会重启并短暂断开。文件和历史会话会保留。",
5010
+ upgradeNoSessions: "当前没有运行中的会话,工作区将立即重启。",
5011
+ upgradeSessionsWarning: "有 {count} 个正在运行的会话会被中断。升级完成后会自动恢复这些会话并发送“继续”。",
5012
+ upgradeCancel: "取消",
5013
+ upgradeConfirm: "停止任务并升级",
5014
+ upgradeBusy: "正在升级工作区…",
5015
+ upgradeStarted: "升级已开始。工作区重新连接后请刷新页面。",
5016
+ upgradeFailed: "工作区升级启动失败,请重试。",
5017
+ upgradeRetry: "重试"
1425
5018
  };
1426
5019
  //#endregion
1427
5020
  //#region src/client/index.ts
1428
5021
  const NS = "settings.dataops-managed";
1429
5022
  const AUTH_UPDATED_MESSAGE = "dataops-auth-updated";
1430
5023
  const MANAGED_AUTH_PATH = "/integrations/dataops/managed-auth";
5024
+ /** Sidebar entry and main page id for the permission centre. */
5025
+ const PERMISSION_PANEL_ID = "dataops-permissions";
1431
5026
  /** Required client services for the managed DataOps Settings section. */
1432
5027
  const inject = ["slots", "locale"];
1433
5028
  /**
1434
- * Register localized managed DataOps Settings navigation and content.
1435
- * @param ctx - DSH client context with locale and settings slots.
5029
+ * Bind the four strings the sign-in prompt needs.
5030
+ *
5031
+ * Narrowed to exactly those keys rather than reusing the settings-section resolver: the prompt
5032
+ * asks its own small copy, and a wider type would let a typo pass as a valid key.
5033
+ *
5034
+ * @param ctx - client context carrying the registered namespace.
5035
+ * @returns a resolver for the prompt's own keys.
5036
+ */
5037
+ const bindReloginCopy = (ctx) => {
5038
+ const translate = ctx.locale.bind(NS);
5039
+ return (key) => translate(key);
5040
+ };
5041
+ /**
5042
+ * Register the managed DataOps surfaces and the sign-in prompt.
5043
+ *
5044
+ * The prompt rides a sidebar seat because the client needs *some* always-mounted surface for a
5045
+ * dialog that is about the whole workspace rather than one panel. The seat is a container: the
5046
+ * dialog itself portals to `document.body`, so this contributes no visible element to the
5047
+ * sidebar at rest and cannot disturb the regions that already live there.
5048
+ *
5049
+ * @param ctx - DSH client context with locale, slots and the sidebar foot.
1436
5050
  */
1437
5051
  function apply(ctx) {
1438
5052
  ctx.effect(() => ctx.locale.register(NS, {
@@ -1441,13 +5055,113 @@ window.__ModuleLoader__.load({
1441
5055
  }), "dataops-managed: settings copy");
1442
5056
  installWanxiangBranding(ctx);
1443
5057
  const t = ctx.locale.bind(NS);
1444
- ctx.slots.inject("settings.section", () => ctx.slots.register({
1445
- name: "settings.section",
1446
- id: "dataops",
1447
- order: 25,
1448
- label: () => t("nav"),
5058
+ /**
5059
+ * Register the settings section only for the two administrator role labels.
5060
+ *
5061
+ * The identity record arrives after the iframe loads and can change when the portal account is
5062
+ * switched, so the registration is reconciled whenever the shared account store publishes. An
5063
+ * ordinary user's profile therefore has no section, no configuration form, and no publish
5064
+ * control at all — not a hidden one.
5065
+ */
5066
+ ctx.effect(() => {
5067
+ let disposeSection;
5068
+ const reconcile = () => {
5069
+ const allowed = canSeeManagedSettings();
5070
+ if (allowed && disposeSection === void 0) disposeSection = ctx.slots.inject("settings.section", () => ctx.slots.register({
5071
+ name: "settings.section",
5072
+ id: "dataops",
5073
+ order: 25,
5074
+ label: () => t("nav"),
5075
+ inject: () => ({ t })
5076
+ }, ManagedDataOpsSection));
5077
+ else if (!allowed && disposeSection !== void 0) {
5078
+ disposeSection();
5079
+ disposeSection = void 0;
5080
+ }
5081
+ };
5082
+ reconcile();
5083
+ const unsubscribe = subscribeManagedSettingsVisibility(reconcile);
5084
+ return () => {
5085
+ unsubscribe();
5086
+ disposeSection?.();
5087
+ disposeSection = void 0;
5088
+ };
5089
+ }, "dataops-managed: settings section role gate");
5090
+ /**
5091
+ * The re-authentication prompt, mounted where it is always alive.
5092
+ *
5093
+ * A dialog needs a component that stays mounted whether or not the operator has the settings
5094
+ * panel open, and the sidebar foot is the one seat this plugin already owns. The component
5095
+ * renders nothing until DataOps reports the session has ended, so at rest it adds no element.
5096
+ */
5097
+ ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
5098
+ name: "sidebar.footer.action",
5099
+ id: "dataops-relogin",
5100
+ order: 90,
5101
+ inject: () => ({ t: bindReloginCopy(ctx) })
5102
+ }, ReloginPrompt));
5103
+ /**
5104
+ * The workspace image upgrade notice. It lives in the shell overlay so it can sit
5105
+ * above the page without taking a sidebar or conversation seat; the component renders
5106
+ * nothing until DataOps reports a newer image.
5107
+ */
5108
+ ctx.slots.inject("shell.overlay", () => ctx.slots.register({
5109
+ name: "shell.overlay",
5110
+ id: "dataops-workspace-upgrade",
5111
+ order: 20,
1449
5112
  inject: () => ({ t })
1450
- }, ManagedDataOpsSection));
5113
+ }, WorkspaceUpgradePrompt));
5114
+ /**
5115
+ * The permission centre, a sidebar entry plus the page it opens.
5116
+ *
5117
+ * The entry's glyph hides itself for an ordinary user; the page and every backend endpoint
5118
+ * re-check the caller, so hiding is navigation, not authorization.
5119
+ */
5120
+ ctx.effect(() => ctx.slots.inject("main", () => ctx.slots.register({
5121
+ name: "main",
5122
+ key: PERMISSION_PANEL_ID
5123
+ }, (() => PermissionCenterSection({ t })))), "dataops-managed: permission centre page");
5124
+ ctx.effect(() => {
5125
+ let disposeEntry;
5126
+ let live = true;
5127
+ const allowed = async () => {
5128
+ if (typeof window !== "undefined" && reviewDemoMode()) return true;
5129
+ if (canSeeManagedSettings()) return true;
5130
+ try {
5131
+ const response = await fetch(REVIEW_CONTEXT_PATH);
5132
+ if (!response.ok) return false;
5133
+ const body = await response.json();
5134
+ return body.primaryRole === "DEPT_ADMIN" || body.review?.canReview === true;
5135
+ } catch {
5136
+ return false;
5137
+ }
5138
+ };
5139
+ const sync = async () => {
5140
+ const next = await allowed();
5141
+ if (!live) return;
5142
+ if (next && disposeEntry === void 0) disposeEntry = ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({
5143
+ name: "sidebar.panellist",
5144
+ id: PERMISSION_PANEL_ID,
5145
+ order: 20,
5146
+ label: () => t("permNav"),
5147
+ locale: NS
5148
+ }, PermissionCenterIcon));
5149
+ else if (!next && disposeEntry !== void 0) {
5150
+ disposeEntry();
5151
+ disposeEntry = void 0;
5152
+ }
5153
+ };
5154
+ sync();
5155
+ const off = subscribeManagedSettingsVisibility(() => {
5156
+ sync();
5157
+ });
5158
+ return () => {
5159
+ live = false;
5160
+ off();
5161
+ disposeEntry?.();
5162
+ disposeEntry = void 0;
5163
+ };
5164
+ }, "dataops-managed: permission centre entry gate");
1451
5165
  /**
1452
5166
  * The skill plaza, contributed to the skill center page.
1453
5167
  *
@@ -1461,6 +5175,11 @@ window.__ModuleLoader__.load({
1461
5175
  order: 10,
1462
5176
  inject: () => ({ t })
1463
5177
  }, SkillPlazaSection));
5178
+ /**
5179
+ * The workflow plaza's header seat, declared by the knowledge-base plugin. It renders a slot
5180
+ * rather than importing this plugin; the bell therefore joins only when both are installed.
5181
+ */
5182
+ ctx.slots.inject("workflow-plaza.actions", () => ctx.slots.register({ name: "workflow-plaza.actions" }, WorkflowPlazaBell));
1464
5183
  const synchronizeJwt = async () => {
1465
5184
  const response = await fetch(new URL(MANAGED_AUTH_PATH, window.location.origin), {
1466
5185
  method: "POST",