@sparkelf/dsh-task-bus 0.1.0 → 0.1.2

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
@@ -7,6 +7,498 @@ window.__ModuleLoader__.load({
7
7
  let react = require("react");
8
8
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
9
  let react_jsx_runtime = require("react/jsx-runtime");
10
+ //#region src/client/theme.ts
11
+ /** Palette aliases the Task Center draws with; the shell owns the values. */
12
+ const colors = {
13
+ panel: "var(--dsw-alias-bg-layer-1, #ffffff)",
14
+ panelAlt: "var(--dsw-alias-bg-layer-2, #f7f8fa)",
15
+ text: "var(--dsw-alias-label-primary, #111827)",
16
+ muted: "var(--dsw-alias-label-secondary, #6b7280)",
17
+ line: "var(--dsw-alias-border-l1, #e5e7eb)",
18
+ accent: "var(--dsw-alias-brand-primary, #2563eb)",
19
+ accentSoft: "var(--dsw-alias-brand-primary-alpha, #eff6ff)",
20
+ warning: "var(--dsw-alias-state-warning, #b45309)",
21
+ success: "var(--dsw-alias-state-success, #047857)",
22
+ danger: "var(--dsw-alias-state-danger, #b91c1c)"
23
+ };
24
+ //#endregion
25
+ //#region src/client/connector-state.ts
26
+ /**
27
+ * Client state adapter for the Task Center's one connector.
28
+ *
29
+ * The Host owns the authoritative enabled/capability state and the tool
30
+ * disclosure that follows it. This module only reads that state, sends the two
31
+ * writes the panel offers, and projects the task rows a capability filter
32
+ * selects. It never decides permissions: a capability absent from the Host's
33
+ * answer is one this account may not use, so it is never rendered.
34
+ *
35
+ * @module @sparkelf/dsh-task-bus/client/connector-state
36
+ */
37
+ /** Route the Host serves the connector state from. */
38
+ const CONNECTOR_ROUTE = "/dsh-task-bus/api/connector";
39
+ /** Connector id carried by DataOps approval tasks. */
40
+ const CONNECTOR_ID = "wanxiang-ai";
41
+ /** Display name shared by the card and its empty state. */
42
+ const CONNECTOR_NAME = "万相AI中台连接器";
43
+ /** Copy for the capability ids the Host currently ships. */
44
+ const CAPABILITY_COPY = {
45
+ "approval.push": {
46
+ label: "接收审批任务",
47
+ description: "从万相AI中台接收待办理的审批任务"
48
+ },
49
+ "approval.writeback": {
50
+ label: "回写审批结果",
51
+ description: "把办理结果回写到万相AI中台"
52
+ },
53
+ "task.push": {
54
+ label: "接收通用任务",
55
+ description: "接收万相AI中台下发的通用任务"
56
+ },
57
+ "task.writeback": {
58
+ label: "回写任务状态",
59
+ description: "把任务处理状态回写到万相AI中台"
60
+ },
61
+ "ai.accept": {
62
+ label: "AI 受理",
63
+ description: "由助手创建受理会话处理任务"
64
+ }
65
+ };
66
+ /** Humanize an id the client has no display copy for. */
67
+ function humanizeCapability(id) {
68
+ return id.replace(/[._-]+/gu, " ").trim();
69
+ }
70
+ /** Parse one unknown wire value into connector state. */
71
+ function parseConnectorState(value) {
72
+ if (typeof value !== "object" || value === null) return {
73
+ enabled: false,
74
+ capabilities: {}
75
+ };
76
+ const row = value;
77
+ const capabilities = {};
78
+ const reasons = {};
79
+ const raw = typeof row.capabilities === "object" && row.capabilities !== null ? row.capabilities : {};
80
+ for (const [id, entry] of Object.entries(raw)) {
81
+ if (typeof entry === "boolean") {
82
+ capabilities[id] = entry;
83
+ continue;
84
+ }
85
+ if (typeof entry !== "object" || entry === null) continue;
86
+ const detail = entry;
87
+ if (detail.permitted === false) continue;
88
+ capabilities[id] = detail.enabled === true;
89
+ if (typeof detail.reason === "string" && detail.reason !== "") reasons[id] = detail.reason;
90
+ }
91
+ const rawReasons = typeof row.reasons === "object" && row.reasons !== null ? row.reasons : {};
92
+ for (const [id, reason] of Object.entries(rawReasons)) if (typeof reason === "string" && reason !== "") reasons[id] = reason;
93
+ return {
94
+ enabled: row.enabled === true,
95
+ capabilities,
96
+ ...Object.keys(reasons).length === 0 ? {} : { reasons }
97
+ };
98
+ }
99
+ /**
100
+ * Read the Host's connector state.
101
+ * @param route - connector route; overridable for tests.
102
+ * @returns the parsed state.
103
+ * @throws when the Host rejects the request.
104
+ */
105
+ async function fetchConnectorState(route = CONNECTOR_ROUTE) {
106
+ const response = await fetch(route, { headers: { accept: "application/json" } });
107
+ if (!response.ok) throw new Error("连接器状态读取失败(" + String(response.status) + ")");
108
+ return parseConnectorState(await response.json());
109
+ }
110
+ /**
111
+ * Write connector state through the Host.
112
+ * @param patch - the enabled flag and optional capability changes to merge.
113
+ * @param route - connector route; overridable for tests.
114
+ * @returns the state the Host persisted.
115
+ * @throws when the Host rejects the request.
116
+ */
117
+ async function saveConnectorState(patch, route = CONNECTOR_ROUTE) {
118
+ const response = await fetch(route, {
119
+ method: "POST",
120
+ headers: {
121
+ accept: "application/json",
122
+ "content-type": "application/json"
123
+ },
124
+ body: JSON.stringify(patch)
125
+ });
126
+ if (!response.ok) throw new Error("连接器状态保存失败(" + String(response.status) + ")");
127
+ return parseConnectorState(await response.json());
128
+ }
129
+ /**
130
+ * Project the capability rows the card renders.
131
+ * @param state - Host state.
132
+ * @returns one row per capability the account may use, in the Host's order.
133
+ */
134
+ function visibleCapabilities(state) {
135
+ if (state === null) return [];
136
+ return Object.entries(state.capabilities).map(([id, enabled]) => {
137
+ const copy = CAPABILITY_COPY[id];
138
+ return {
139
+ id,
140
+ label: copy?.label ?? humanizeCapability(id),
141
+ ...copy === void 0 ? {} : { description: copy.description },
142
+ enabled,
143
+ ...state.reasons?.[id] === void 0 ? {} : { unavailableReason: state.reasons[id] }
144
+ };
145
+ });
146
+ }
147
+ /**
148
+ * The connector that produced a task row. MVP DataOps approval rows predate
149
+ * explicit ids, so their source type still identifies the built-in connector.
150
+ * @param task - one task row.
151
+ * @returns the connector id, or undefined for a generic Task Bus row.
152
+ */
153
+ function taskConnectorId(task) {
154
+ if (task.connectorId !== void 0 && task.connectorId !== "") return task.connectorId;
155
+ return task.sourceType === "dataops_approval" ? CONNECTOR_ID : void 0;
156
+ }
157
+ /**
158
+ * The capability that produced a task row; an id-less DataOps approval row
159
+ * belongs to `approval.push`.
160
+ * @param task - one task row.
161
+ * @returns the capability id, or undefined for a generic Task Bus row.
162
+ */
163
+ function taskCapabilityId(task) {
164
+ if (task.capabilityId !== void 0 && task.capabilityId !== "") return task.capabilityId;
165
+ return task.sourceType === "dataops_approval" ? "approval.push" : void 0;
166
+ }
167
+ /**
168
+ * Filter rows for the connector tab.
169
+ * @param tasks - every loaded row.
170
+ * @param capabilityId - `all`, or the capability whose rows should remain.
171
+ * @returns rows produced by the built-in connector, narrowed to the capability.
172
+ */
173
+ function filterConnectorTasks(tasks, capabilityId) {
174
+ return tasks.filter((task) => taskConnectorId(task) === "wanxiang-ai" && (capabilityId === "all" || taskCapabilityId(task) === capabilityId));
175
+ }
176
+ //#endregion
177
+ //#region src/client/ConnectorCard.tsx
178
+ /** Status label and tone for the card header. */
179
+ function statusOf(state, loading, error) {
180
+ if (loading && state === null) return {
181
+ label: "同步中",
182
+ tone: "neutral"
183
+ };
184
+ if (error !== null) return {
185
+ label: "连接异常",
186
+ tone: "danger"
187
+ };
188
+ if (state?.enabled !== true) return {
189
+ label: "未启用",
190
+ tone: "neutral"
191
+ };
192
+ return {
193
+ label: "已连接",
194
+ tone: "success"
195
+ };
196
+ }
197
+ /** Draw the connector glyph the card header carries. */
198
+ function ConnectorGlyph() {
199
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
200
+ width: "18",
201
+ height: "18",
202
+ viewBox: "0 0 24 24",
203
+ fill: "none",
204
+ stroke: "currentColor",
205
+ strokeWidth: "1.8",
206
+ strokeLinecap: "round",
207
+ strokeLinejoin: "round",
208
+ "aria-hidden": "true",
209
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M10 13a5 5 0 0 0 7.54.54l2-2a5 5 0 0 0-7.07-7.07l-1.15 1.15" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-2 2a5 5 0 0 0 7.07 7.07l1.15-1.15" })]
210
+ });
211
+ }
212
+ /**
213
+ * Render the connector card.
214
+ * @param props - server state, write handlers, and the current task count.
215
+ * @returns the card.
216
+ */
217
+ function ConnectorCard({ state, loading, error, busy, taskCount, onSetEnabled, onToggleCapability, onReload }) {
218
+ const status = statusOf(state, loading, error);
219
+ const enabled = state?.enabled === true;
220
+ const capabilities = visibleCapabilities(state);
221
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
222
+ "data-connector-card": CONNECTOR_NAME,
223
+ "data-connector-enabled": enabled,
224
+ style: {
225
+ width: "100%",
226
+ minWidth: 0,
227
+ border: "1px solid " + colors.line,
228
+ borderRadius: 12,
229
+ background: colors.panelAlt,
230
+ padding: 14,
231
+ display: "flex",
232
+ flexDirection: "column",
233
+ gap: 10
234
+ },
235
+ children: [
236
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
237
+ style: {
238
+ display: "flex",
239
+ alignItems: "center",
240
+ gap: 9
241
+ },
242
+ children: [
243
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
244
+ "aria-hidden": "true",
245
+ style: {
246
+ width: 32,
247
+ height: 32,
248
+ flex: "none",
249
+ borderRadius: 9,
250
+ background: "#2563eb",
251
+ color: "#fff",
252
+ display: "inline-flex",
253
+ alignItems: "center",
254
+ justifyContent: "center"
255
+ },
256
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConnectorGlyph, {})
257
+ }),
258
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
259
+ style: {
260
+ flex: 1,
261
+ minWidth: 0
262
+ },
263
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
264
+ style: {
265
+ fontSize: 13,
266
+ fontWeight: 700,
267
+ color: colors.text
268
+ },
269
+ children: CONNECTOR_NAME
270
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
271
+ style: {
272
+ marginTop: 2,
273
+ fontSize: 10,
274
+ color: colors.muted
275
+ },
276
+ children: "DataOps"
277
+ })]
278
+ }),
279
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
280
+ tone: status.tone,
281
+ children: status.label
282
+ })
283
+ ]
284
+ }),
285
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
286
+ style: {
287
+ fontSize: 11,
288
+ lineHeight: 1.55,
289
+ color: colors.muted
290
+ },
291
+ children: "接收万相AI中台的审批任务"
292
+ }),
293
+ loading && state === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
294
+ style: {
295
+ fontSize: 11,
296
+ color: colors.muted
297
+ },
298
+ children: "正在读取连接器状态…"
299
+ }) : null,
300
+ error === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
301
+ role: "alert",
302
+ style: {
303
+ display: "flex",
304
+ alignItems: "center",
305
+ gap: 8,
306
+ fontSize: 11,
307
+ color: colors.danger
308
+ },
309
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
310
+ style: {
311
+ flex: 1,
312
+ minWidth: 0
313
+ },
314
+ children: error
315
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
316
+ size: "sm",
317
+ variant: "ghost",
318
+ disabled: loading,
319
+ onClick: onReload,
320
+ children: "重试"
321
+ })]
322
+ }),
323
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
324
+ style: {
325
+ display: "flex",
326
+ alignItems: "center",
327
+ justifyContent: "space-between",
328
+ gap: 10,
329
+ minHeight: 28
330
+ },
331
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
332
+ style: {
333
+ fontSize: 12,
334
+ color: colors.muted
335
+ },
336
+ children: enabled ? "已启用,任务中心只读取该连接器的任务" : "未启用,任务中心不读取外部任务"
337
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
338
+ checked: enabled,
339
+ disabled: busy || loading,
340
+ label: (enabled ? "停用 " : "启用 ") + CONNECTOR_NAME,
341
+ onChange: (next) => {
342
+ onSetEnabled(next);
343
+ }
344
+ })]
345
+ }),
346
+ enabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
347
+ "data-connector-capabilities": true,
348
+ style: {
349
+ borderTop: "1px solid " + colors.line,
350
+ paddingTop: 8,
351
+ display: "grid",
352
+ gap: 2
353
+ },
354
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
355
+ style: {
356
+ fontSize: 10,
357
+ color: colors.muted
358
+ },
359
+ children: "能力"
360
+ }), capabilities.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
361
+ style: {
362
+ fontSize: 11,
363
+ color: colors.muted,
364
+ padding: "6px 0"
365
+ },
366
+ children: "当前账号没有可用的连接器能力,请联系管理员开通"
367
+ }) : capabilities.map((capability) => {
368
+ const unavailable = capability.unavailableReason !== void 0;
369
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
370
+ "data-capability-id": capability.id,
371
+ "data-capability-enabled": capability.enabled,
372
+ style: {
373
+ display: "flex",
374
+ alignItems: "center",
375
+ gap: 10,
376
+ minHeight: 40,
377
+ padding: "4px 0",
378
+ borderTop: "1px solid " + colors.line
379
+ },
380
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
381
+ style: {
382
+ flex: 1,
383
+ minWidth: 0
384
+ },
385
+ children: [
386
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
387
+ style: {
388
+ fontSize: 12,
389
+ color: colors.text
390
+ },
391
+ children: capability.label
392
+ }),
393
+ capability.description === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
394
+ style: {
395
+ marginTop: 1,
396
+ fontSize: 10,
397
+ color: colors.muted
398
+ },
399
+ children: capability.description
400
+ }),
401
+ capability.unavailableReason === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
402
+ style: {
403
+ marginTop: 2,
404
+ fontSize: 10,
405
+ color: colors.warning
406
+ },
407
+ children: ["暂不可用 · ", capability.unavailableReason]
408
+ })
409
+ ]
410
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
411
+ checked: capability.enabled,
412
+ disabled: busy || unavailable,
413
+ label: (capability.enabled ? "停用 " : "启用 ") + capability.label,
414
+ onChange: (next) => {
415
+ onToggleCapability(capability.id, next);
416
+ }
417
+ })]
418
+ }, capability.id);
419
+ })]
420
+ }) : null,
421
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
422
+ style: {
423
+ display: "flex",
424
+ alignItems: "center",
425
+ justifyContent: "space-between",
426
+ gap: 8,
427
+ marginTop: "auto",
428
+ fontSize: 10,
429
+ color: colors.muted
430
+ },
431
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: enabled ? "该连接器任务 " + String(taskCount) + " 条" : "启用后在此查看连接器任务" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
432
+ size: "sm",
433
+ variant: "ghost",
434
+ disabled: loading,
435
+ onClick: onReload,
436
+ children: "刷新状态"
437
+ })]
438
+ })
439
+ ]
440
+ });
441
+ }
442
+ /**
443
+ * The disabled connector's empty state, with the one path that can enable it.
444
+ * @param props.onOpenSkillCenter - select the skill center's connector tab.
445
+ * @returns the empty state.
446
+ */
447
+ function ConnectorDisabledState({ onOpenSkillCenter }) {
448
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
449
+ "data-connector-disabled-state": true,
450
+ style: {
451
+ display: "grid",
452
+ justifyItems: "center",
453
+ gap: 8,
454
+ padding: "36px 20px 40px",
455
+ color: colors.muted,
456
+ textAlign: "center"
457
+ },
458
+ children: [
459
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
460
+ style: {
461
+ display: "inline-flex",
462
+ width: 36,
463
+ height: 36,
464
+ alignItems: "center",
465
+ justifyContent: "center",
466
+ borderRadius: 18,
467
+ background: colors.panelAlt,
468
+ color: colors.muted
469
+ },
470
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConnectorGlyph, {})
471
+ }),
472
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
473
+ style: {
474
+ fontSize: 13,
475
+ fontWeight: 600,
476
+ color: colors.text
477
+ },
478
+ children: "连接器未启用"
479
+ }),
480
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
481
+ style: {
482
+ maxWidth: 380,
483
+ fontSize: 12,
484
+ lineHeight: 1.6
485
+ },
486
+ children: [
487
+ "在技能中心的连接器页面启用「",
488
+ CONNECTOR_NAME,
489
+ "」后,这里会显示它接收的审批任务。"
490
+ ]
491
+ }),
492
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
493
+ size: "sm",
494
+ variant: "outline",
495
+ onClick: onOpenSkillCenter,
496
+ children: "前往技能中心启用"
497
+ })
498
+ ]
499
+ });
500
+ }
501
+ //#endregion
10
502
  //#region src/client/index.tsx
11
503
  /**
12
504
  * The Task Center surface in the frame-wide overlay.
@@ -20,21 +512,10 @@ window.__ModuleLoader__.load({
20
512
  "locale",
21
513
  "uiWorkspace",
22
514
  "sessions",
23
- "sidebarRight"
515
+ "sidebarRight",
516
+ "layout"
24
517
  ];
25
518
  const ROUTE = "/dsh-task-bus/api";
26
- const colors = {
27
- panel: "var(--dsw-alias-bg-layer-1, #ffffff)",
28
- panelAlt: "var(--dsw-alias-bg-layer-2, #f7f8fa)",
29
- text: "var(--dsw-alias-label-primary, #111827)",
30
- muted: "var(--dsw-alias-label-secondary, #6b7280)",
31
- line: "var(--dsw-alias-border-l1, #e5e7eb)",
32
- accent: "var(--dsw-alias-brand-primary, #2563eb)",
33
- accentSoft: "var(--dsw-alias-brand-primary-alpha, #eff6ff)",
34
- warning: "var(--dsw-alias-state-warning, #b45309)",
35
- success: "var(--dsw-alias-state-success, #047857)",
36
- danger: "var(--dsw-alias-state-danger, #b91c1c)"
37
- };
38
519
  const TASK_BUS_MODAL_CLASS = "task-bus-center-modal";
39
520
  const TASK_BUS_HISTORY_CLASS = "task-bus-history-modal";
40
521
  const TASK_BUS_STYLE_ID = "task-bus-center-modal-style";
@@ -113,6 +594,17 @@ window.__ModuleLoader__.load({
113
594
  fontSize: 12,
114
595
  cursor: "pointer"
115
596
  };
597
+ /** Capability filter chip; the active chip keeps the accent fill and stroke. */
598
+ const filterChipStyle = (active) => ({
599
+ padding: "4px 10px",
600
+ borderRadius: 999,
601
+ border: "1px solid " + (active ? colors.accent : colors.line),
602
+ background: active ? colors.accentSoft : "transparent",
603
+ color: active ? colors.accent : colors.muted,
604
+ fontSize: 11,
605
+ lineHeight: 1.6,
606
+ cursor: "pointer"
607
+ });
116
608
  const statusTagTone = (status) => {
117
609
  if (status === "completed") return "success";
118
610
  if (status === "cancelled") return "neutral";
@@ -131,8 +623,18 @@ window.__ModuleLoader__.load({
131
623
  return String(Math.round(hours / 24)) + " 天前";
132
624
  };
133
625
  const fetchJson = async (path) => {
134
- const response = await fetch(ROUTE + path, { headers: { accept: "application/json" } });
135
- const payload = await response.json();
626
+ const attempt = async () => {
627
+ const response = await fetch(ROUTE + path, { headers: { accept: "application/json" } });
628
+ return {
629
+ response,
630
+ payload: await response.json()
631
+ };
632
+ };
633
+ let { response, payload } = await attempt();
634
+ if (response.status === 401) {
635
+ await new Promise((resolve) => setTimeout(resolve, 900));
636
+ ({response, payload} = await attempt());
637
+ }
136
638
  if (!response.ok) {
137
639
  const message = payload !== null && typeof payload === "object" && "error" in payload ? String(payload.error) : "任务中心请求失败(" + response.status + ")";
138
640
  throw new Error(message);
@@ -694,169 +1196,7 @@ window.__ModuleLoader__.load({
694
1196
  ]
695
1197
  });
696
1198
  };
697
- const CONNECTOR_STORAGE_KEY = "dsh.task-bus.dataops-connector.enabled";
698
- const DataOpsConnectorCard = ({ approvalCount, connected, enabled, onEnable }) => {
699
- const [detailOpen, setDetailOpen] = (0, react.useState)(false);
700
- const status = !enabled ? "未启用" : connected ? "已连接" : "连接异常";
701
- const tone = !enabled ? "neutral" : connected ? "success" : "danger";
702
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
703
- style: {
704
- width: "100%",
705
- minWidth: 0,
706
- border: "1px solid " + colors.line,
707
- borderRadius: 12,
708
- background: colors.panelAlt,
709
- padding: 14,
710
- display: "flex",
711
- flexDirection: "column",
712
- gap: 10
713
- },
714
- children: [
715
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
716
- style: {
717
- display: "flex",
718
- alignItems: "center",
719
- gap: 9
720
- },
721
- children: [
722
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
723
- "aria-hidden": "true",
724
- style: {
725
- width: 32,
726
- height: 32,
727
- flex: "none",
728
- borderRadius: 9,
729
- background: "#2563eb",
730
- color: "#fff",
731
- display: "inline-flex",
732
- alignItems: "center",
733
- justifyContent: "center"
734
- },
735
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
736
- width: "18",
737
- height: "18",
738
- viewBox: "0 0 24 24",
739
- fill: "none",
740
- stroke: "currentColor",
741
- strokeWidth: "1.8",
742
- strokeLinecap: "round",
743
- strokeLinejoin: "round",
744
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M10 13a5 5 0 0 0 7.54.54l2-2a5 5 0 0 0-7.07-7.07l-1.15 1.15" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-2 2a5 5 0 0 0 7.07 7.07l1.15-1.15" })]
745
- })
746
- }),
747
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
748
- style: {
749
- flex: 1,
750
- minWidth: 0
751
- },
752
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
753
- style: {
754
- fontSize: 13,
755
- fontWeight: 700,
756
- color: colors.text
757
- },
758
- children: "万相 AI 中台"
759
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
760
- style: {
761
- marginTop: 2,
762
- fontSize: 10,
763
- color: colors.muted
764
- },
765
- children: "DataOps 连接器"
766
- })]
767
- }),
768
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
769
- tone,
770
- children: status
771
- })
772
- ]
773
- }),
774
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
775
- style: {
776
- fontSize: 11,
777
- lineHeight: 1.55,
778
- color: colors.muted
779
- },
780
- children: "接收万相 AI 中台的审批任务"
781
- }),
782
- detailOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
783
- style: {
784
- borderTop: "1px solid " + colors.line,
785
- paddingTop: 8,
786
- display: "grid",
787
- gap: 6
788
- },
789
- children: [
790
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
791
- style: {
792
- display: "grid",
793
- gridTemplateColumns: "68px minmax(0,1fr)",
794
- gap: 8,
795
- fontSize: 10
796
- },
797
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
798
- style: { color: colors.muted },
799
- children: "协议"
800
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
801
- style: { color: colors.text },
802
- children: "DataOps Task Center API"
803
- })]
804
- }),
805
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
806
- style: {
807
- display: "grid",
808
- gridTemplateColumns: "68px minmax(0,1fr)",
809
- gap: 8,
810
- fontSize: 10
811
- },
812
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
813
- style: { color: colors.muted },
814
- children: "审批任务"
815
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
816
- style: { color: colors.text },
817
- children: approvalCount
818
- })]
819
- }),
820
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
821
- style: {
822
- display: "grid",
823
- gridTemplateColumns: "68px minmax(0,1fr)",
824
- gap: 8,
825
- fontSize: 10
826
- },
827
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
828
- style: { color: colors.muted },
829
- children: "能力"
830
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
831
- style: { color: colors.text },
832
- children: "审批 / 通用任务 / AI 受理"
833
- })]
834
- })
835
- ]
836
- }) : null,
837
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
838
- style: {
839
- display: "flex",
840
- gap: 6,
841
- marginTop: "auto"
842
- },
843
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
844
- size: "sm",
845
- variant: "ghost",
846
- onClick: () => setDetailOpen((value) => !value),
847
- children: detailOpen ? "收起" : "详情"
848
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
849
- size: "sm",
850
- variant: enabled ? "outline" : "primary",
851
- disabled: enabled,
852
- onClick: onEnable,
853
- children: enabled ? "已启用" : "启用"
854
- })]
855
- })
856
- ]
857
- });
858
- };
859
- const TaskCenterButton = ({ openSession }) => {
1199
+ const TaskCenterButton = ({ openSession, openSkillCenter }) => {
860
1200
  const [open, setOpen] = (0, react.useState)(false);
861
1201
  const [tasks, setTasks] = (0, react.useState)([]);
862
1202
  const [loading, setLoading] = (0, react.useState)(false);
@@ -870,14 +1210,14 @@ window.__ModuleLoader__.load({
870
1210
  const [createBusy, setCreateBusy] = (0, react.useState)(false);
871
1211
  const [selected, setSelected] = (0, react.useState)(null);
872
1212
  const [anchor, setAnchor] = (0, react.useState)(null);
873
- const [connectorEnabled, setConnectorEnabled] = (0, react.useState)(() => {
874
- try {
875
- return window.localStorage.getItem(CONNECTOR_STORAGE_KEY) === "true";
876
- } catch {
877
- return false;
878
- }
879
- });
1213
+ const [connector, setConnector] = (0, react.useState)(null);
1214
+ const [connectorLoading, setConnectorLoading] = (0, react.useState)(true);
1215
+ const [connectorError, setConnectorError] = (0, react.useState)(null);
1216
+ const [connectorBusy, setConnectorBusy] = (0, react.useState)(false);
1217
+ const [capabilityFilter, setCapabilityFilter] = (0, react.useState)("all");
1218
+ const [skillCenterError, setSkillCenterError] = (0, react.useState)(null);
880
1219
  const [pendingCount, setPendingCount] = (0, react.useState)(0);
1220
+ const connectorEnabled = connector?.enabled === true;
881
1221
  (0, react.useEffect)(() => {
882
1222
  if (document.getElementById(TASK_BUS_STYLE_ID) !== null) return;
883
1223
  const style = document.createElement("style");
@@ -920,6 +1260,32 @@ window.__ModuleLoader__.load({
920
1260
  window.removeEventListener("resize", update);
921
1261
  };
922
1262
  }, []);
1263
+ const readConnector = async () => {
1264
+ setConnectorLoading(true);
1265
+ setConnectorError(null);
1266
+ try {
1267
+ setConnector(await fetchConnectorState());
1268
+ } catch (cause) {
1269
+ setConnectorError(cause instanceof Error ? cause.message : String(cause));
1270
+ } finally {
1271
+ setConnectorLoading(false);
1272
+ }
1273
+ };
1274
+ (0, react.useEffect)(() => {
1275
+ let alive = true;
1276
+ setConnectorLoading(true);
1277
+ setConnectorError(null);
1278
+ fetchConnectorState().then((next) => {
1279
+ if (alive) setConnector(next);
1280
+ }).catch((cause) => {
1281
+ if (alive) setConnectorError(cause instanceof Error ? cause.message : String(cause));
1282
+ }).finally(() => {
1283
+ if (alive) setConnectorLoading(false);
1284
+ });
1285
+ return () => {
1286
+ alive = false;
1287
+ };
1288
+ }, []);
923
1289
  const load = async (silent = false) => {
924
1290
  if (!connectorEnabled) {
925
1291
  setTasks([]);
@@ -938,11 +1304,43 @@ window.__ModuleLoader__.load({
938
1304
  if (!silent) setLoading(false);
939
1305
  }
940
1306
  };
941
- const enableConnector = () => {
942
- setConnectorEnabled(true);
1307
+ const setConnectorEnabled = async (enabled) => {
1308
+ setConnectorBusy(true);
1309
+ setConnectorError(null);
943
1310
  try {
944
- window.localStorage.setItem(CONNECTOR_STORAGE_KEY, "true");
945
- } catch {}
1311
+ setConnector(await saveConnectorState({ enabled }));
1312
+ if (!enabled) {
1313
+ setTasks([]);
1314
+ setCapabilityFilter("all");
1315
+ }
1316
+ } catch (cause) {
1317
+ setConnectorError(cause instanceof Error ? cause.message : String(cause));
1318
+ } finally {
1319
+ setConnectorBusy(false);
1320
+ }
1321
+ };
1322
+ const toggleCapability = async (capabilityId, enabled) => {
1323
+ if (connector === null) return;
1324
+ setConnectorBusy(true);
1325
+ setConnectorError(null);
1326
+ try {
1327
+ setConnector(await saveConnectorState({
1328
+ enabled: connector.enabled,
1329
+ capabilities: { [capabilityId]: enabled }
1330
+ }));
1331
+ } catch (cause) {
1332
+ setConnectorError(cause instanceof Error ? cause.message : String(cause));
1333
+ } finally {
1334
+ setConnectorBusy(false);
1335
+ }
1336
+ };
1337
+ const openSkillCenterFromPanel = () => {
1338
+ if (openSkillCenter()) {
1339
+ setSkillCenterError(null);
1340
+ setOpen(false);
1341
+ return;
1342
+ }
1343
+ setSkillCenterError("技能中心未安装,无法打开连接器页面");
946
1344
  };
947
1345
  (0, react.useEffect)(() => {
948
1346
  if (!open) return;
@@ -982,13 +1380,18 @@ window.__ModuleLoader__.load({
982
1380
  window.clearInterval(timer);
983
1381
  };
984
1382
  }, [connectorEnabled]);
985
- const filtered = (0, react.useMemo)(() => tasks.filter((task) => {
986
- if (tab === "connectors") return task.sourceType === "dataops_approval";
987
- if (tab === "group") return task.sourceType === "task_bus" && task.source.type.includes("group");
988
- if (tab === "mine") return task.status !== "completed" && task.status !== "cancelled";
989
- if (tab === "personal") return task.sourceType === "task_bus" && !task.source.type.includes("group");
990
- return true;
991
- }), [tasks, tab]);
1383
+ const filtered = (0, react.useMemo)(() => {
1384
+ if (tab === "connectors") return connectorEnabled ? filterConnectorTasks(tasks, capabilityFilter) : [];
1385
+ if (tab === "group") return tasks.filter((task) => task.sourceType === "task_bus" && task.source.type.includes("group"));
1386
+ if (tab === "mine") return tasks.filter((task) => task.status !== "completed" && task.status !== "cancelled");
1387
+ if (tab === "personal") return tasks.filter((task) => task.sourceType === "task_bus" && !task.source.type.includes("group"));
1388
+ return tasks;
1389
+ }, [
1390
+ tasks,
1391
+ tab,
1392
+ connectorEnabled,
1393
+ capabilityFilter
1394
+ ]);
992
1395
  const actionable = tasks.filter((task) => task.status !== "completed" && task.status !== "cancelled").length;
993
1396
  const tabItems = [
994
1397
  {
@@ -1033,7 +1436,7 @@ window.__ModuleLoader__.load({
1033
1436
  panelId: "task-bus-panel-connectors",
1034
1437
  label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["连接器 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1035
1438
  style: { opacity: .55 },
1036
- children: "1"
1439
+ children: connector === null ? 0 : 1
1037
1440
  })] })
1038
1441
  }
1039
1442
  ];
@@ -1322,19 +1725,59 @@ window.__ModuleLoader__.load({
1322
1725
  task: selected,
1323
1726
  onBack: () => setSelected(null)
1324
1727
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1325
- tab === "connectors" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1728
+ tab === "connectors" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1326
1729
  style: {
1327
1730
  display: "grid",
1328
- gridTemplateColumns: "repeat(auto-fill, minmax(280px, 320px))",
1731
+ gridTemplateColumns: "repeat(auto-fill, minmax(320px, 1fr))",
1329
1732
  gap: 10
1330
1733
  },
1331
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataOpsConnectorCard, {
1332
- approvalCount: tasks.filter((task) => task.sourceType === "dataops_approval").length,
1333
- connected: error === null,
1334
- enabled: connectorEnabled,
1335
- onEnable: enableConnector
1734
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConnectorCard, {
1735
+ state: connector,
1736
+ loading: connectorLoading,
1737
+ error: connectorError,
1738
+ busy: connectorBusy,
1739
+ taskCount: filtered.length,
1740
+ onSetEnabled: (enabled) => {
1741
+ setConnectorEnabled(enabled);
1742
+ },
1743
+ onToggleCapability: (capabilityId, enabled) => {
1744
+ toggleCapability(capabilityId, enabled);
1745
+ },
1746
+ onReload: () => {
1747
+ readConnector();
1748
+ }
1336
1749
  })
1337
- }) : null,
1750
+ }), connectorEnabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1751
+ role: "group",
1752
+ "aria-label": "连接器能力筛选",
1753
+ style: {
1754
+ display: "flex",
1755
+ flexWrap: "wrap",
1756
+ gap: 6
1757
+ },
1758
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1759
+ type: "button",
1760
+ "aria-pressed": capabilityFilter === "all",
1761
+ onClick: () => setCapabilityFilter("all"),
1762
+ style: filterChipStyle(capabilityFilter === "all"),
1763
+ children: "全部"
1764
+ }), visibleCapabilities(connector).map((capability) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1765
+ type: "button",
1766
+ "data-capability-filter": capability.id,
1767
+ "aria-pressed": capabilityFilter === capability.id,
1768
+ onClick: () => setCapabilityFilter(capability.id),
1769
+ style: filterChipStyle(capabilityFilter === capability.id),
1770
+ children: capability.label
1771
+ }, capability.id))]
1772
+ }) : connectorLoading ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConnectorDisabledState, { onOpenSkillCenter: openSkillCenterFromPanel }), skillCenterError === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1773
+ role: "alert",
1774
+ style: {
1775
+ textAlign: "center",
1776
+ color: colors.danger,
1777
+ fontSize: 11
1778
+ },
1779
+ children: skillCenterError
1780
+ })] })] }) : null,
1338
1781
  loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1339
1782
  style: {
1340
1783
  padding: 18,
@@ -1344,14 +1787,23 @@ window.__ModuleLoader__.load({
1344
1787
  },
1345
1788
  children: "正在加载…"
1346
1789
  }) : null,
1347
- !loading && filtered.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1790
+ !loading && tab !== "connectors" && filtered.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1348
1791
  style: {
1349
1792
  padding: 18,
1350
1793
  textAlign: "center",
1351
1794
  color: colors.muted,
1352
1795
  fontSize: 12
1353
1796
  },
1354
- children: tab === "connectors" ? connectorEnabled ? "暂无来自该连接器的任务" : "启用连接器后在此显示任务" : "暂无任务"
1797
+ children: "暂无任务"
1798
+ }) : null,
1799
+ !loading && tab === "connectors" && connectorEnabled && filtered.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1800
+ style: {
1801
+ padding: 18,
1802
+ textAlign: "center",
1803
+ color: colors.muted,
1804
+ fontSize: 12
1805
+ },
1806
+ children: capabilityFilter === "all" ? "暂无来自该连接器的任务" : "该能力下暂无任务"
1355
1807
  }) : null,
1356
1808
  !loading && tab === "connectors" && filtered.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1357
1809
  style: {
@@ -1447,34 +1899,6 @@ window.__ModuleLoader__.load({
1447
1899
  reference.release?.();
1448
1900
  }
1449
1901
  };
1450
- const HistoryGlyph = () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1451
- width: "16",
1452
- height: "16",
1453
- viewBox: "0 0 16 16",
1454
- fill: "none",
1455
- xmlns: "http://www.w3.org/2000/svg",
1456
- "aria-hidden": "true",
1457
- strokeWidth: "1.5",
1458
- children: [
1459
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1460
- d: "M2.4 6.8A5.6 5.6 0 1 1 2.4 9.2",
1461
- stroke: "currentColor",
1462
- strokeLinecap: "round"
1463
- }),
1464
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1465
- d: "M2.2 3.4v3.4h3.4",
1466
- stroke: "currentColor",
1467
- strokeLinecap: "round",
1468
- strokeLinejoin: "round"
1469
- }),
1470
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1471
- d: "M8 5.4V8l1.9 1.2",
1472
- stroke: "currentColor",
1473
- strokeLinecap: "round",
1474
- strokeLinejoin: "round"
1475
- })
1476
- ]
1477
- });
1478
1902
  const fileAttachmentIn = (content, name) => {
1479
1903
  if (!Array.isArray(content)) return void 0;
1480
1904
  for (const block of content) {
@@ -1609,7 +2033,7 @@ window.__ModuleLoader__.load({
1609
2033
  color: "inherit",
1610
2034
  cursor: "pointer"
1611
2035
  },
1612
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HistoryGlyph, {})
2036
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconClockOutline16, { size: 16 })
1613
2037
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1614
2038
  open,
1615
2039
  onClose: () => setOpen(false),
@@ -1707,7 +2131,7 @@ window.__ModuleLoader__.load({
1707
2131
  background: colors.panelAlt,
1708
2132
  color: colors.muted
1709
2133
  },
1710
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HistoryGlyph, {})
2134
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconClockOutline16, { size: 16 })
1711
2135
  }),
1712
2136
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1713
2137
  style: {
@@ -1834,7 +2258,18 @@ window.__ModuleLoader__.load({
1834
2258
  document.removeEventListener("click", onFileClick, true);
1835
2259
  };
1836
2260
  }, "task-bus: native file card preview");
1837
- const Surface = () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskCenterButton, { openSession: (sessionId) => surface.uiWorkspace.openSession(sessionId) });
2261
+ const openSkillCenter = () => {
2262
+ try {
2263
+ surface.layout.selectPanel("skill-center");
2264
+ return true;
2265
+ } catch {
2266
+ return false;
2267
+ }
2268
+ };
2269
+ const Surface = () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskCenterButton, {
2270
+ openSession: (sessionId) => surface.uiWorkspace.openSession(sessionId),
2271
+ openSkillCenter
2272
+ });
1838
2273
  ctx.effect(() => surface.slots.inject("shell.overlay", () => surface.slots.register({
1839
2274
  name: "shell.overlay",
1840
2275
  id: "task-bus.center"