@sparkelf/dsh-task-bus 0.0.0-stage → 0.1.1

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 ADDED
@@ -0,0 +1,2332 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@sparkelf/dsh-task-bus",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
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
502
+ //#region src/client/index.tsx
503
+ /**
504
+ * The Task Center surface in the frame-wide overlay.
505
+ *
506
+ * The button is global because a task can arrive while the reader is in any
507
+ * session. The panel only renders cards returned by the Host bridge; it never
508
+ * talks to DataOps directly and never derives a second approval status.
509
+ */
510
+ const inject = [
511
+ "slots",
512
+ "locale",
513
+ "uiWorkspace",
514
+ "sessions",
515
+ "sidebarRight",
516
+ "layout"
517
+ ];
518
+ const ROUTE = "/dsh-task-bus/api";
519
+ const TASK_BUS_MODAL_CLASS = "task-bus-center-modal";
520
+ const TASK_BUS_HISTORY_CLASS = "task-bus-history-modal";
521
+ const TASK_BUS_STYLE_ID = "task-bus-center-modal-style";
522
+ const TASK_BUS_MODAL_CSS = `
523
+ .${TASK_BUS_MODAL_CLASS} {
524
+ width: min(960px, calc(100vw - 64px)) !important;
525
+ height: min(760px, calc(100vh - 64px)) !important;
526
+ gap: 0 !important;
527
+ padding: 0 !important;
528
+ }
529
+ @media (max-width: 700px) {
530
+ .${TASK_BUS_MODAL_CLASS} {
531
+ width: calc(100vw - 24px) !important;
532
+ height: calc(100vh - 24px) !important;
533
+ }
534
+ }
535
+ .${TASK_BUS_HISTORY_CLASS} {
536
+ width: min(960px, 92vw) !important;
537
+ max-width: calc(100vw - 32px) !important;
538
+ height: auto !important;
539
+ max-height: min(780px, calc(100vh - 48px)) !important;
540
+ }
541
+ @media (max-width: 700px) {
542
+ .${TASK_BUS_HISTORY_CLASS} {
543
+ width: calc(100vw - 24px) !important;
544
+ max-width: calc(100vw - 24px) !important;
545
+ max-height: calc(100vh - 24px) !important;
546
+ }
547
+ }
548
+ `;
549
+ const HISTORY_NS = "task-bus.history";
550
+ const HISTORY_COPY = {
551
+ zh: {
552
+ title: "消息记录",
553
+ open: "消息记录",
554
+ close: "关闭",
555
+ searchPlaceholder: "搜索消息或发送人…",
556
+ searchLabel: "搜索消息记录",
557
+ clear: "清空搜索",
558
+ initial: "输入关键词搜索消息或发送人",
559
+ initialHint: "可按消息正文或发送人筛选,点结果跳到原消息。",
560
+ loading: "正在加载消息记录…",
561
+ error: "消息记录加载失败",
562
+ empty: "暂无消息记录",
563
+ noResults: "没有匹配的消息",
564
+ jump: "跳到该消息",
565
+ me: "我",
566
+ assistant: "助手",
567
+ dateLocale: "zh-CN"
568
+ },
569
+ en: {
570
+ title: "Message history",
571
+ open: "Message history",
572
+ close: "Close",
573
+ searchPlaceholder: "Search messages or senders…",
574
+ searchLabel: "Search message history",
575
+ clear: "Clear search",
576
+ initial: "Search messages or senders to begin",
577
+ initialHint: "Filter by message text or sender, then click a result to jump to it.",
578
+ loading: "Loading message history…",
579
+ error: "Could not load message history",
580
+ empty: "No message history",
581
+ noResults: "No matching messages",
582
+ jump: "Jump to message",
583
+ me: "Me",
584
+ assistant: "Assistant",
585
+ dateLocale: "en-US"
586
+ }
587
+ };
588
+ const buttonStyle = {
589
+ border: "1px solid " + colors.line,
590
+ background: colors.panel,
591
+ color: colors.text,
592
+ borderRadius: 8,
593
+ padding: "7px 11px",
594
+ fontSize: 12,
595
+ cursor: "pointer"
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
+ });
608
+ const statusTagTone = (status) => {
609
+ if (status === "completed") return "success";
610
+ if (status === "cancelled") return "neutral";
611
+ if (status === "blocked") return "danger";
612
+ if (status === "pending_confirm" || status === "returned" || status === "pending_accept") return "warning";
613
+ return "info";
614
+ };
615
+ const relativeTime = (value) => {
616
+ const time = Date.parse(value);
617
+ if (!Number.isFinite(time)) return "";
618
+ const minutes = Math.max(0, Math.round((Date.now() - time) / 6e4));
619
+ if (minutes < 1) return "刚刚";
620
+ if (minutes < 60) return String(minutes) + " 分钟前";
621
+ const hours = Math.round(minutes / 60);
622
+ if (hours < 24) return String(hours) + " 小时前";
623
+ return String(Math.round(hours / 24)) + " 天前";
624
+ };
625
+ const fetchJson = async (path) => {
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
+ }
638
+ if (!response.ok) {
639
+ const message = payload !== null && typeof payload === "object" && "error" in payload ? String(payload.error) : "任务中心请求失败(" + response.status + ")";
640
+ throw new Error(message);
641
+ }
642
+ return payload;
643
+ };
644
+ const approvalHref = (raw) => {
645
+ if (raw === null) return null;
646
+ try {
647
+ const target = new URL(raw);
648
+ const referrer = document.referrer ? new URL(document.referrer) : null;
649
+ if (referrer !== null && referrer.origin !== target.origin) return referrer.origin + target.pathname + target.search + target.hash;
650
+ return target.href;
651
+ } catch {
652
+ return raw;
653
+ }
654
+ };
655
+ const TaskCard = ({ task, busy, aiBusy, onTransition, onAiAccept, onOpen }) => {
656
+ const isApproval = task.sourceType === "dataops_approval";
657
+ const deepLink = approvalHref(task.source.deepLink ?? null);
658
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
659
+ style: {
660
+ border: "1px solid " + colors.line,
661
+ borderRadius: 10,
662
+ background: colors.panel,
663
+ padding: "12px 13px",
664
+ display: "grid",
665
+ gap: 8
666
+ },
667
+ children: [
668
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
669
+ style: {
670
+ display: "flex",
671
+ alignItems: "flex-start",
672
+ gap: 10
673
+ },
674
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
675
+ style: {
676
+ flex: 1,
677
+ minWidth: 0
678
+ },
679
+ children: [
680
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
681
+ style: {
682
+ display: "flex",
683
+ alignItems: "center",
684
+ gap: 7,
685
+ flexWrap: "wrap",
686
+ marginBottom: 4
687
+ },
688
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
689
+ tone: isApproval ? "info" : "neutral",
690
+ children: isApproval ? task.source.label ?? "DataOps 审批" : "任务总线"
691
+ }), task.tags?.map((tag) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
692
+ tone: "quiet",
693
+ children: tag
694
+ }, tag))]
695
+ }),
696
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
697
+ style: {
698
+ fontSize: 13,
699
+ fontWeight: 650,
700
+ color: colors.text,
701
+ lineHeight: 1.45
702
+ },
703
+ children: task.title
704
+ }),
705
+ task.description ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
706
+ style: {
707
+ fontSize: 11,
708
+ color: colors.muted,
709
+ marginTop: 4,
710
+ lineHeight: 1.5
711
+ },
712
+ children: task.description
713
+ }) : null
714
+ ]
715
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
716
+ style: { flex: "none" },
717
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
718
+ tone: statusTagTone(task.status),
719
+ children: task.statusLabel
720
+ })
721
+ })]
722
+ }),
723
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
724
+ style: {
725
+ display: "flex",
726
+ alignItems: "center",
727
+ gap: 8,
728
+ flexWrap: "wrap",
729
+ fontSize: 10,
730
+ color: colors.muted
731
+ },
732
+ children: [
733
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: isApproval ? "审批任务" : "任务" }),
734
+ task.assignee?.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["受理人:", task.assignee.id] }) : null,
735
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
736
+ style: { marginLeft: "auto" },
737
+ children: relativeTime(task.updatedAt)
738
+ })
739
+ ]
740
+ }),
741
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
742
+ style: {
743
+ display: "flex",
744
+ gap: 6,
745
+ flexWrap: "wrap"
746
+ },
747
+ children: [
748
+ isApproval && deepLink ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
749
+ size: "sm",
750
+ variant: "primary",
751
+ onClick: () => window.open(deepLink, "_blank", "noopener,noreferrer"),
752
+ children: "办理"
753
+ }) : null,
754
+ task.status !== "completed" && task.status !== "cancelled" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
755
+ size: "sm",
756
+ variant: "outline",
757
+ disabled: aiBusy || busy,
758
+ onClick: () => onAiAccept(task),
759
+ children: aiBusy ? "受理中…" : "AI受理"
760
+ }) : null,
761
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
762
+ size: "sm",
763
+ variant: "ghost",
764
+ onClick: () => onOpen(task),
765
+ children: "详情"
766
+ }),
767
+ !isApproval && task.status === "pending_accept" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
768
+ size: "sm",
769
+ variant: "primary",
770
+ disabled: busy,
771
+ onClick: () => onTransition(task, "accept"),
772
+ children: "受理"
773
+ }) : null,
774
+ !isApproval && task.status === "in_progress" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
775
+ size: "sm",
776
+ variant: "outline",
777
+ disabled: busy,
778
+ onClick: () => onTransition(task, "submit"),
779
+ children: "提交确认"
780
+ }) : null,
781
+ !isApproval && task.status === "pending_confirm" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
782
+ size: "sm",
783
+ variant: "primary",
784
+ disabled: busy,
785
+ onClick: () => onTransition(task, "complete"),
786
+ children: "完成"
787
+ }) : null,
788
+ !isApproval && task.status !== "completed" && task.status !== "cancelled" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
789
+ size: "sm",
790
+ variant: "ghost",
791
+ disabled: busy,
792
+ onClick: () => onTransition(task, "cancel"),
793
+ children: "取消"
794
+ }) : null
795
+ ]
796
+ })
797
+ ]
798
+ });
799
+ };
800
+ const FORM_LABELS = {
801
+ applicationMode: "申请方式",
802
+ requestKind: "申请类型",
803
+ requestedPermissions: "申请权限",
804
+ requesterDepartment: "申请人部门",
805
+ requesterPhone: "申请人电话",
806
+ validFrom: "授权开始时间",
807
+ expiresAt: "授权结束时间",
808
+ selectionSummary: "资源摘要",
809
+ code: "应用编码",
810
+ name: "应用名称",
811
+ reason: "申请理由",
812
+ description: "应用说明",
813
+ departmentName: "归属部门",
814
+ departmentCode: "归属部门编码",
815
+ ownerName: "归属负责人",
816
+ ownerPhone: "归属负责人电话",
817
+ constructionUnit: "建设单位",
818
+ constructionOwner: "建设单位负责人",
819
+ constructionOwnerPhone: "建设单位负责人电话",
820
+ operationsUnit: "运维单位",
821
+ operationsOwner: "运维单位负责人",
822
+ operationsOwnerPhone: "运维单位负责人电话",
823
+ requestTime: "申请时间",
824
+ resourceName: "资源名称",
825
+ resourceType: "资源类型",
826
+ accessType: "访问方式",
827
+ accessScope: "访问范围",
828
+ applicationName: "所属应用",
829
+ applicationCode: "应用编码",
830
+ environment: "环境",
831
+ accountCount: "账号数量"
832
+ };
833
+ const SKIP_FORM_KEYS = /* @__PURE__ */ new Set([
834
+ "ownerUserId",
835
+ "requesterUserId",
836
+ "requesterDept",
837
+ "existingApplicationId",
838
+ "applicationId",
839
+ "taskType"
840
+ ]);
841
+ const formatBytes = (bytes) => {
842
+ if (!Number.isFinite(bytes) || bytes < 0) return "";
843
+ if (bytes < 1024) return bytes + " B";
844
+ if (bytes < 1048576) return (bytes / 1024).toFixed(1) + " KB";
845
+ return (bytes / 1048576).toFixed(1) + " MB";
846
+ };
847
+ const humanizeField = (key) => key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
848
+ const formatFormValue = (key, value) => {
849
+ if (key === "applicationMode") {
850
+ if (value === "EXISTING") return "使用已有应用";
851
+ if (value === "NEW") return "新建应用";
852
+ }
853
+ if (typeof value === "boolean") return value ? "是" : "否";
854
+ if (Array.isArray(value)) return value.map((item) => String(item)).join("、");
855
+ if (value !== null && typeof value === "object") {
856
+ const record = value;
857
+ if (typeof record.name === "string") return record.name;
858
+ return JSON.stringify(value);
859
+ }
860
+ return String(value);
861
+ };
862
+ const toFormField = (key, value) => ({
863
+ key,
864
+ label: FORM_LABELS[key] ?? humanizeField(key),
865
+ value,
866
+ ...key === "reason" || key === "description" || key === "accessScope" ? {
867
+ type: "multiline",
868
+ span: 2
869
+ } : {}
870
+ });
871
+ const formFields = (form) => form === void 0 ? [] : Object.entries(form).filter(([key, value]) => !SKIP_FORM_KEYS.has(key) && value !== null && value !== void 0 && value !== "").map(([key, value]) => toFormField(key, value));
872
+ const approvalFormSections = (detail) => {
873
+ if (detail === null) return [];
874
+ const sections = [];
875
+ const applicationFields = formFields(detail.payload?.form);
876
+ if (applicationFields.length > 0) sections.push({
877
+ title: "申请信息",
878
+ fields: applicationFields
879
+ });
880
+ const resourceFields = formFields(detail.payload?.resourceAccessRequest?.form);
881
+ if (resourceFields.length > 0) sections.push({
882
+ title: "资源申请",
883
+ fields: resourceFields
884
+ });
885
+ const resources = [...detail.payload?.selectedItems ?? [], ...detail.payload?.resourceAccessRequest?.selectedItems ?? []];
886
+ if (resources.length > 0) sections.push({
887
+ title: "申请资源",
888
+ fields: [{
889
+ key: "resources",
890
+ label: "资源",
891
+ value: resources,
892
+ type: "resources",
893
+ span: 2
894
+ }]
895
+ });
896
+ return sections;
897
+ };
898
+ const ResourceValue = ({ items }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
899
+ style: {
900
+ display: "grid",
901
+ gap: 5
902
+ },
903
+ children: items.map((item, index) => {
904
+ const label = typeof item.title === "string" ? item.title : typeof item.name === "string" ? item.name : `资源 ${index + 1}`;
905
+ const detail = [
906
+ item.scope,
907
+ item.urn,
908
+ item.id
909
+ ].filter((value) => typeof value === "string" || typeof value === "number").join(" · ");
910
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
911
+ style: {
912
+ display: "flex",
913
+ alignItems: "baseline",
914
+ gap: 8,
915
+ minWidth: 0
916
+ },
917
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
918
+ style: {
919
+ fontSize: 12,
920
+ color: colors.text
921
+ },
922
+ children: label
923
+ }), detail ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
924
+ style: {
925
+ fontSize: 10,
926
+ color: colors.muted,
927
+ overflow: "hidden",
928
+ textOverflow: "ellipsis",
929
+ whiteSpace: "nowrap"
930
+ },
931
+ children: detail
932
+ }) : null]
933
+ }, String(item.urn ?? item.id ?? index));
934
+ })
935
+ });
936
+ const FormDescriptions = ({ sections }) => {
937
+ if (sections.length === 0) return null;
938
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
939
+ style: {
940
+ display: "grid",
941
+ gap: 10
942
+ },
943
+ children: sections.map((section, sectionIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
944
+ style: {
945
+ border: "1px solid " + colors.line,
946
+ borderRadius: 10,
947
+ background: colors.panel,
948
+ padding: 14
949
+ },
950
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
951
+ style: {
952
+ fontSize: 12,
953
+ fontWeight: 650,
954
+ color: colors.text,
955
+ marginBottom: 9
956
+ },
957
+ children: section.title
958
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dl", {
959
+ style: {
960
+ margin: 0,
961
+ display: "grid",
962
+ gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
963
+ gap: "9px 24px"
964
+ },
965
+ children: section.fields.map((field) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
966
+ style: {
967
+ display: "grid",
968
+ gridTemplateColumns: "94px minmax(0, 1fr)",
969
+ alignItems: "start",
970
+ gap: 8,
971
+ minWidth: 0,
972
+ gridColumn: field.span === 2 ? "1 / -1" : void 0
973
+ },
974
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
975
+ style: {
976
+ fontSize: 10,
977
+ lineHeight: 1.6,
978
+ color: colors.muted
979
+ },
980
+ children: field.label
981
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
982
+ style: {
983
+ margin: 0,
984
+ fontSize: 12,
985
+ lineHeight: 1.6,
986
+ color: colors.text,
987
+ whiteSpace: "pre-wrap",
988
+ overflowWrap: "anywhere"
989
+ },
990
+ children: field.type === "resources" && Array.isArray(field.value) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResourceValue, { items: field.value }) : field.value === null || field.value === void 0 || field.value === "" ? "—" : formatFormValue(field.key, field.value)
991
+ })]
992
+ }, field.key))
993
+ })]
994
+ }, section.title + sectionIndex))
995
+ });
996
+ };
997
+ const TaskDetail = ({ task, onBack }) => {
998
+ const isApproval = task.sourceType === "dataops_approval";
999
+ const [detail, setDetail] = (0, react.useState)(null);
1000
+ const [detailLoading, setDetailLoading] = (0, react.useState)(true);
1001
+ const [detailError, setDetailError] = (0, react.useState)(null);
1002
+ (0, react.useEffect)(() => {
1003
+ let alive = true;
1004
+ setDetailLoading(true);
1005
+ setDetailError(null);
1006
+ fetchJson("/items/" + encodeURIComponent(task.id)).then((payload) => {
1007
+ if (alive) setDetail(payload.detail ?? null);
1008
+ }).catch((cause) => {
1009
+ if (alive) setDetailError(cause instanceof Error ? cause.message : String(cause));
1010
+ }).finally(() => {
1011
+ if (alive) setDetailLoading(false);
1012
+ });
1013
+ return () => {
1014
+ alive = false;
1015
+ };
1016
+ }, [task.id]);
1017
+ const sections = isApproval ? approvalFormSections(detail) : detail?.form?.sections ?? [];
1018
+ const attachments = detail?.attachments ?? [];
1019
+ const approvalTaskId = isApproval && typeof detail?.id === "number" ? detail.id : null;
1020
+ const attachmentHref = (attachment) => approvalTaskId === null ? null : ROUTE + "/attachment?taskId=" + encodeURIComponent(String(approvalTaskId)) + "&attachmentId=" + encodeURIComponent(attachment.id);
1021
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1022
+ style: {
1023
+ display: "flex",
1024
+ flexDirection: "column",
1025
+ gap: 10,
1026
+ flex: 1,
1027
+ minHeight: "100%"
1028
+ },
1029
+ children: [
1030
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1031
+ style: {
1032
+ display: "flex",
1033
+ alignItems: "flex-start",
1034
+ gap: 10
1035
+ },
1036
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1037
+ style: {
1038
+ flex: 1,
1039
+ minWidth: 0
1040
+ },
1041
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1042
+ style: {
1043
+ fontSize: 14,
1044
+ fontWeight: 700,
1045
+ color: colors.text,
1046
+ lineHeight: 1.45
1047
+ },
1048
+ children: task.title
1049
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1050
+ style: {
1051
+ marginTop: 3,
1052
+ fontSize: 10,
1053
+ color: colors.muted
1054
+ },
1055
+ children: [
1056
+ task.source.label ?? (isApproval ? "DataOps 审批" : "任务总线"),
1057
+ " · ",
1058
+ relativeTime(task.updatedAt)
1059
+ ]
1060
+ })]
1061
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
1062
+ tone: statusTagTone(task.status),
1063
+ children: task.statusLabel
1064
+ })]
1065
+ }),
1066
+ detailLoading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1067
+ style: {
1068
+ padding: 18,
1069
+ textAlign: "center",
1070
+ color: colors.muted,
1071
+ fontSize: 12
1072
+ },
1073
+ children: "正在加载表单…"
1074
+ }) : null,
1075
+ detailError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1076
+ style: {
1077
+ padding: "8px 10px",
1078
+ borderRadius: 8,
1079
+ background: "#fef2f2",
1080
+ color: colors.danger,
1081
+ fontSize: 11
1082
+ },
1083
+ children: detailError
1084
+ }) : null,
1085
+ !detailLoading && detailError === null && sections.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormDescriptions, { sections }) : null,
1086
+ !detailLoading && detailError === null && sections.length === 0 && !isApproval && task.description ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1087
+ style: {
1088
+ border: "1px solid " + colors.line,
1089
+ borderRadius: 10,
1090
+ background: colors.panel,
1091
+ padding: 14,
1092
+ fontSize: 12,
1093
+ lineHeight: 1.6,
1094
+ color: colors.text,
1095
+ whiteSpace: "pre-wrap"
1096
+ },
1097
+ children: task.description
1098
+ }) : null,
1099
+ !detailLoading && detailError === null && sections.length === 0 && (isApproval || !task.description) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1100
+ style: {
1101
+ padding: 18,
1102
+ textAlign: "center",
1103
+ color: colors.muted,
1104
+ fontSize: 12
1105
+ },
1106
+ children: "暂无表单信息"
1107
+ }) : null,
1108
+ isApproval || attachments.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1109
+ style: {
1110
+ border: "1px solid " + colors.line,
1111
+ borderRadius: 10,
1112
+ background: colors.panel,
1113
+ padding: 14
1114
+ },
1115
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1116
+ style: {
1117
+ fontSize: 12,
1118
+ fontWeight: 650,
1119
+ color: colors.text,
1120
+ marginBottom: 8
1121
+ },
1122
+ children: "附件"
1123
+ }), attachments.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1124
+ style: {
1125
+ fontSize: 11,
1126
+ color: colors.muted
1127
+ },
1128
+ children: "暂无附件"
1129
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1130
+ style: {
1131
+ display: "grid",
1132
+ gap: 6
1133
+ },
1134
+ children: attachments.map((attachment) => {
1135
+ const href = attachmentHref(attachment);
1136
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1137
+ style: {
1138
+ display: "flex",
1139
+ alignItems: "center",
1140
+ gap: 8,
1141
+ minWidth: 0
1142
+ },
1143
+ children: [
1144
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1145
+ style: {
1146
+ flex: 1,
1147
+ minWidth: 0,
1148
+ fontSize: 11,
1149
+ color: colors.text,
1150
+ overflow: "hidden",
1151
+ textOverflow: "ellipsis",
1152
+ whiteSpace: "nowrap"
1153
+ },
1154
+ children: attachment.originalName
1155
+ }),
1156
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1157
+ style: {
1158
+ flex: "none",
1159
+ fontSize: 10,
1160
+ color: colors.muted
1161
+ },
1162
+ children: formatBytes(attachment.sizeBytes)
1163
+ }),
1164
+ href !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1165
+ size: "sm",
1166
+ variant: "ghost",
1167
+ onClick: () => window.open(href, "_blank", "noopener,noreferrer"),
1168
+ children: "下载"
1169
+ }) : null
1170
+ ]
1171
+ }, attachment.id);
1172
+ })
1173
+ })]
1174
+ }) : null,
1175
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1176
+ style: {
1177
+ display: "flex",
1178
+ justifyContent: "flex-end",
1179
+ gap: 8,
1180
+ flexWrap: "wrap",
1181
+ marginTop: "auto",
1182
+ paddingTop: 2
1183
+ },
1184
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1185
+ size: "sm",
1186
+ variant: "ghost",
1187
+ onClick: onBack,
1188
+ children: "← 返回列表"
1189
+ }), isApproval && task.source.deepLink ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1190
+ size: "sm",
1191
+ variant: "outline",
1192
+ onClick: () => window.open(approvalHref(task.source.deepLink ?? null) ?? "#", "_blank", "noopener,noreferrer"),
1193
+ children: "打开 DataOps 审批详情"
1194
+ }) : null]
1195
+ })
1196
+ ]
1197
+ });
1198
+ };
1199
+ const TaskCenterButton = ({ openSession, openSkillCenter }) => {
1200
+ const [open, setOpen] = (0, react.useState)(false);
1201
+ const [tasks, setTasks] = (0, react.useState)([]);
1202
+ const [loading, setLoading] = (0, react.useState)(false);
1203
+ const [error, setError] = (0, react.useState)(null);
1204
+ const [tab, setTab] = (0, react.useState)("all");
1205
+ const [busyId, setBusyId] = (0, react.useState)(null);
1206
+ const [aiBusyId, setAiBusyId] = (0, react.useState)(null);
1207
+ const [creating, setCreating] = (0, react.useState)(false);
1208
+ const [draftTitle, setDraftTitle] = (0, react.useState)("");
1209
+ const [draftSource, setDraftSource] = (0, react.useState)("manual");
1210
+ const [createBusy, setCreateBusy] = (0, react.useState)(false);
1211
+ const [selected, setSelected] = (0, react.useState)(null);
1212
+ const [anchor, setAnchor] = (0, react.useState)(null);
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);
1219
+ const [pendingCount, setPendingCount] = (0, react.useState)(0);
1220
+ const connectorEnabled = connector?.enabled === true;
1221
+ (0, react.useEffect)(() => {
1222
+ if (document.getElementById(TASK_BUS_STYLE_ID) !== null) return;
1223
+ const style = document.createElement("style");
1224
+ style.id = TASK_BUS_STYLE_ID;
1225
+ style.textContent = TASK_BUS_MODAL_CSS;
1226
+ document.head.append(style);
1227
+ return () => {
1228
+ style.remove();
1229
+ };
1230
+ }, []);
1231
+ (0, react.useEffect)(() => {
1232
+ const update = () => {
1233
+ if (document.querySelector("[data-sidebar-collapsed=\"true\"]") !== null) {
1234
+ setAnchor((current) => current === null ? current : null);
1235
+ return;
1236
+ }
1237
+ const toggle = Array.from(document.querySelectorAll("button")).find((button) => /收起侧边栏|Collapse sidebar/i.test((button.getAttribute("aria-label") ?? "").trim()));
1238
+ if (!toggle) {
1239
+ setAnchor((current) => current === null ? current : null);
1240
+ return;
1241
+ }
1242
+ const label = (toggle.getAttribute("aria-label") ?? "").trim();
1243
+ if (/展开侧边栏|Expand sidebar/i.test(label)) {
1244
+ setAnchor((current) => current === null ? current : null);
1245
+ return;
1246
+ }
1247
+ const rect = toggle.getBoundingClientRect();
1248
+ if (rect.width === 0 || rect.height === 0) return;
1249
+ const next = {
1250
+ left: Math.max(8, rect.left - 36),
1251
+ top: rect.top + (rect.height - 28) / 2
1252
+ };
1253
+ setAnchor((current) => current !== null && Math.abs(current.left - next.left) < .5 && Math.abs(current.top - next.top) < .5 ? current : next);
1254
+ };
1255
+ update();
1256
+ const timer = window.setInterval(update, 400);
1257
+ window.addEventListener("resize", update);
1258
+ return () => {
1259
+ window.clearInterval(timer);
1260
+ window.removeEventListener("resize", update);
1261
+ };
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
+ }, []);
1289
+ const load = async (silent = false) => {
1290
+ if (!connectorEnabled) {
1291
+ setTasks([]);
1292
+ setLoading(false);
1293
+ setError(null);
1294
+ return;
1295
+ }
1296
+ if (!silent) setLoading(true);
1297
+ setError(null);
1298
+ try {
1299
+ const overview = await fetchJson("/overview");
1300
+ setTasks([...overview.items].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)));
1301
+ } catch (cause) {
1302
+ setError(cause instanceof Error ? cause.message : String(cause));
1303
+ } finally {
1304
+ if (!silent) setLoading(false);
1305
+ }
1306
+ };
1307
+ const setConnectorEnabled = async (enabled) => {
1308
+ setConnectorBusy(true);
1309
+ setConnectorError(null);
1310
+ try {
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("技能中心未安装,无法打开连接器页面");
1344
+ };
1345
+ (0, react.useEffect)(() => {
1346
+ if (!open) return;
1347
+ if (!connectorEnabled) {
1348
+ setTab("connectors");
1349
+ setTasks([]);
1350
+ setError(null);
1351
+ setLoading(false);
1352
+ return;
1353
+ }
1354
+ load();
1355
+ const timer = window.setInterval(() => {
1356
+ load(true);
1357
+ }, 6e4);
1358
+ return () => {
1359
+ window.clearInterval(timer);
1360
+ };
1361
+ }, [open, connectorEnabled]);
1362
+ (0, react.useEffect)(() => {
1363
+ if (!connectorEnabled) {
1364
+ setPendingCount(0);
1365
+ return;
1366
+ }
1367
+ let alive = true;
1368
+ const refresh = async () => {
1369
+ try {
1370
+ const result = await fetchJson("/count");
1371
+ if (alive) setPendingCount(Math.max(0, Math.trunc(Number(result.count) || 0)));
1372
+ } catch {}
1373
+ };
1374
+ refresh();
1375
+ const timer = window.setInterval(() => {
1376
+ refresh();
1377
+ }, 3e5);
1378
+ return () => {
1379
+ alive = false;
1380
+ window.clearInterval(timer);
1381
+ };
1382
+ }, [connectorEnabled]);
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
+ ]);
1395
+ const actionable = tasks.filter((task) => task.status !== "completed" && task.status !== "cancelled").length;
1396
+ const tabItems = [
1397
+ {
1398
+ value: "all",
1399
+ id: "task-bus-tab-all",
1400
+ panelId: "task-bus-panel-all",
1401
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["全部 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1402
+ style: { opacity: .55 },
1403
+ children: tasks.length
1404
+ })] })
1405
+ },
1406
+ {
1407
+ value: "mine",
1408
+ id: "task-bus-tab-mine",
1409
+ panelId: "task-bus-panel-mine",
1410
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["待我处理 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1411
+ style: { opacity: .55 },
1412
+ children: actionable
1413
+ })] })
1414
+ },
1415
+ {
1416
+ value: "personal",
1417
+ id: "task-bus-tab-personal",
1418
+ panelId: "task-bus-panel-personal",
1419
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["个人 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1420
+ style: { opacity: .55 },
1421
+ children: tasks.filter((task) => task.sourceType === "task_bus" && !task.source.type.includes("group")).length
1422
+ })] })
1423
+ },
1424
+ {
1425
+ value: "group",
1426
+ id: "task-bus-tab-group",
1427
+ panelId: "task-bus-panel-group",
1428
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["群聊 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1429
+ style: { opacity: .55 },
1430
+ children: tasks.filter((task) => task.sourceType === "task_bus" && task.source.type.includes("group")).length
1431
+ })] })
1432
+ },
1433
+ {
1434
+ value: "connectors",
1435
+ id: "task-bus-tab-connectors",
1436
+ panelId: "task-bus-panel-connectors",
1437
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["连接器 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1438
+ style: { opacity: .55 },
1439
+ children: connector === null ? 0 : 1
1440
+ })] })
1441
+ }
1442
+ ];
1443
+ const transition = async (task, action) => {
1444
+ setBusyId(task.id);
1445
+ setError(null);
1446
+ try {
1447
+ await fetch(ROUTE + "/generic/" + encodeURIComponent(task.id.slice(5)) + "/transition", {
1448
+ method: "POST",
1449
+ headers: { "content-type": "application/json" },
1450
+ body: JSON.stringify({ action })
1451
+ }).then(async (response) => {
1452
+ if (!response.ok) {
1453
+ const payload = await response.json().catch(() => null);
1454
+ throw new Error(payload?.error ?? "任务操作失败");
1455
+ }
1456
+ });
1457
+ await load();
1458
+ } catch (cause) {
1459
+ setError(cause instanceof Error ? cause.message : String(cause));
1460
+ } finally {
1461
+ setBusyId(null);
1462
+ }
1463
+ };
1464
+ const createTask = async () => {
1465
+ const title = draftTitle.trim();
1466
+ if (!title) return;
1467
+ setCreateBusy(true);
1468
+ setError(null);
1469
+ try {
1470
+ const response = await fetch(ROUTE + "/generic", {
1471
+ method: "POST",
1472
+ headers: { "content-type": "application/json" },
1473
+ body: JSON.stringify({
1474
+ title,
1475
+ sourceType: draftSource
1476
+ })
1477
+ });
1478
+ const payload = await response.json();
1479
+ if (!response.ok) throw new Error(payload.error ?? "创建任务失败");
1480
+ setDraftTitle("");
1481
+ setCreating(false);
1482
+ await load();
1483
+ } catch (cause) {
1484
+ setError(cause instanceof Error ? cause.message : String(cause));
1485
+ } finally {
1486
+ setCreateBusy(false);
1487
+ }
1488
+ };
1489
+ const aiAccept = async (task) => {
1490
+ setAiBusyId(task.id);
1491
+ setError(null);
1492
+ try {
1493
+ const response = await fetch(ROUTE + "/ai-accept", {
1494
+ method: "POST",
1495
+ headers: { "content-type": "application/json" },
1496
+ body: JSON.stringify({ id: task.id })
1497
+ });
1498
+ const payload = await response.json();
1499
+ if (!response.ok || typeof payload.sessionId !== "string") throw new Error(payload.error ?? "AI 受理会话创建失败");
1500
+ openSession(payload.sessionId);
1501
+ setOpen(false);
1502
+ } catch (cause) {
1503
+ setError(cause instanceof Error ? cause.message : String(cause));
1504
+ } finally {
1505
+ setAiBusyId(null);
1506
+ }
1507
+ };
1508
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [anchor !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1509
+ type: "button",
1510
+ "aria-label": "任务中心",
1511
+ title: "任务中心",
1512
+ onClick: () => setOpen((value) => !value),
1513
+ style: {
1514
+ position: "fixed",
1515
+ left: anchor.left,
1516
+ top: anchor.top,
1517
+ zIndex: 45,
1518
+ width: 28,
1519
+ height: 28,
1520
+ padding: 0,
1521
+ border: "none",
1522
+ borderRadius: 8,
1523
+ background: "transparent",
1524
+ color: actionable > 0 ? colors.accent : colors.muted,
1525
+ cursor: "pointer",
1526
+ pointerEvents: "auto",
1527
+ display: "inline-flex",
1528
+ alignItems: "center",
1529
+ justifyContent: "center"
1530
+ },
1531
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1532
+ width: "17",
1533
+ height: "17",
1534
+ viewBox: "0 0 24 24",
1535
+ fill: "none",
1536
+ stroke: "currentColor",
1537
+ strokeWidth: "1.8",
1538
+ strokeLinecap: "round",
1539
+ strokeLinejoin: "round",
1540
+ "aria-hidden": "true",
1541
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M18 8a6 6 0 0 0-12 0c0 7-3 7-3 9h18c0-2-3-2-3-9" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M13.7 21a2 2 0 0 1-3.4 0" })]
1542
+ }), pendingCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1543
+ style: {
1544
+ position: "absolute",
1545
+ bottom: -2,
1546
+ right: -2,
1547
+ minWidth: 14,
1548
+ height: 14,
1549
+ padding: "0 3px",
1550
+ boxSizing: "border-box",
1551
+ borderRadius: 999,
1552
+ background: "#2563eb",
1553
+ color: "#fff",
1554
+ fontSize: 9,
1555
+ lineHeight: "14px",
1556
+ textAlign: "center"
1557
+ },
1558
+ children: pendingCount > 99 ? "99+" : pendingCount
1559
+ }) : null]
1560
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1561
+ open,
1562
+ onClose: () => setOpen(false),
1563
+ title: "任务中心",
1564
+ headless: true,
1565
+ className: TASK_BUS_MODAL_CLASS,
1566
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1567
+ style: {
1568
+ display: "flex",
1569
+ flexDirection: "column",
1570
+ width: "100%",
1571
+ height: "100%",
1572
+ minHeight: 0
1573
+ },
1574
+ children: [
1575
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
1576
+ style: {
1577
+ display: "flex",
1578
+ alignItems: "center",
1579
+ gap: 8,
1580
+ padding: "22px 24px 12px"
1581
+ },
1582
+ children: [
1583
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1584
+ style: {
1585
+ flex: 1,
1586
+ minWidth: 0
1587
+ },
1588
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1589
+ style: {
1590
+ margin: 0,
1591
+ fontSize: 16,
1592
+ lineHeight: "24px",
1593
+ fontWeight: 500,
1594
+ color: colors.text
1595
+ },
1596
+ children: "任务中心"
1597
+ })
1598
+ }),
1599
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1600
+ size: "sm",
1601
+ variant: "outline",
1602
+ disabled: !connectorEnabled,
1603
+ onClick: () => setCreating((value) => !value),
1604
+ children: "新建"
1605
+ }),
1606
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1607
+ size: "sm",
1608
+ variant: "ghost",
1609
+ onClick: () => void load(),
1610
+ children: "刷新"
1611
+ }),
1612
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1613
+ size: "sm",
1614
+ variant: "ghost",
1615
+ "aria-label": "关闭",
1616
+ title: "关闭",
1617
+ onClick: () => setOpen(false),
1618
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular, { size: 14 }),
1619
+ style: {
1620
+ width: 28,
1621
+ padding: 0
1622
+ }
1623
+ })
1624
+ ]
1625
+ }),
1626
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1627
+ style: {
1628
+ padding: "0 24px 14px",
1629
+ borderBottom: "1px solid " + colors.line
1630
+ },
1631
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedTabs, {
1632
+ items: tabItems,
1633
+ value: tab,
1634
+ onChange: setTab,
1635
+ label: "任务中心分类"
1636
+ })
1637
+ }),
1638
+ creating ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1639
+ style: {
1640
+ margin: "0 24px 12px",
1641
+ padding: 12,
1642
+ border: "1px solid " + colors.line,
1643
+ borderRadius: 9,
1644
+ display: "grid",
1645
+ gap: 7
1646
+ },
1647
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1648
+ value: draftTitle,
1649
+ onChange: (event) => setDraftTitle(event.target.value),
1650
+ placeholder: "任务标题,例如:整理演示材料",
1651
+ autoFocus: true,
1652
+ style: {
1653
+ ...buttonStyle,
1654
+ cursor: "text",
1655
+ padding: "7px 9px"
1656
+ }
1657
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1658
+ style: {
1659
+ display: "flex",
1660
+ gap: 6
1661
+ },
1662
+ children: [
1663
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1664
+ value: draftSource,
1665
+ onChange: (event) => setDraftSource(event.target.value),
1666
+ style: {
1667
+ ...buttonStyle,
1668
+ padding: "6px 8px"
1669
+ },
1670
+ children: [
1671
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1672
+ value: "manual",
1673
+ children: "手工任务"
1674
+ }),
1675
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1676
+ value: "session",
1677
+ children: "会话任务"
1678
+ }),
1679
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1680
+ value: "group",
1681
+ children: "群聊任务"
1682
+ })
1683
+ ]
1684
+ }),
1685
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1686
+ size: "sm",
1687
+ variant: "primary",
1688
+ disabled: createBusy || draftTitle.trim() === "",
1689
+ onClick: () => void createTask(),
1690
+ children: createBusy ? "创建中…" : "创建"
1691
+ }),
1692
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1693
+ size: "sm",
1694
+ variant: "ghost",
1695
+ onClick: () => setCreating(false),
1696
+ children: "取消"
1697
+ })
1698
+ ]
1699
+ })]
1700
+ }) : null,
1701
+ error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1702
+ style: {
1703
+ padding: "8px 10px",
1704
+ borderRadius: 8,
1705
+ background: "#fef2f2",
1706
+ color: colors.danger,
1707
+ fontSize: 11,
1708
+ margin: "0 24px 12px"
1709
+ },
1710
+ children: error
1711
+ }) : null,
1712
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1713
+ id: "task-bus-panel-" + tab,
1714
+ role: "tabpanel",
1715
+ "aria-labelledby": "task-bus-tab-" + tab,
1716
+ style: {
1717
+ padding: "16px 24px 24px",
1718
+ display: "flex",
1719
+ flexDirection: "column",
1720
+ gap: 10,
1721
+ overflowY: "auto",
1722
+ flex: 1
1723
+ },
1724
+ children: selected !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskDetail, {
1725
+ task: selected,
1726
+ onBack: () => setSelected(null)
1727
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1728
+ tab === "connectors" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1729
+ style: {
1730
+ display: "grid",
1731
+ gridTemplateColumns: "repeat(auto-fill, minmax(320px, 1fr))",
1732
+ gap: 10
1733
+ },
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
+ }
1749
+ })
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,
1781
+ loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1782
+ style: {
1783
+ padding: 18,
1784
+ textAlign: "center",
1785
+ color: colors.muted,
1786
+ fontSize: 12
1787
+ },
1788
+ children: "正在加载…"
1789
+ }) : null,
1790
+ !loading && tab !== "connectors" && filtered.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1791
+ style: {
1792
+ padding: 18,
1793
+ textAlign: "center",
1794
+ color: colors.muted,
1795
+ fontSize: 12
1796
+ },
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" ? "暂无来自该连接器的任务" : "该能力下暂无任务"
1807
+ }) : null,
1808
+ !loading && tab === "connectors" && filtered.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1809
+ style: {
1810
+ fontSize: 10,
1811
+ color: colors.muted
1812
+ },
1813
+ children: "来自该连接器的任务"
1814
+ }) : null,
1815
+ !loading && filtered.map((task) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskCard, {
1816
+ task,
1817
+ busy: busyId === task.id,
1818
+ aiBusy: aiBusyId === task.id,
1819
+ onTransition: (item, action) => void transition(item, action),
1820
+ onAiAccept: (item) => void aiAccept(item),
1821
+ onOpen: setSelected
1822
+ }, task.id))
1823
+ ] })
1824
+ })
1825
+ ]
1826
+ })
1827
+ })] });
1828
+ };
1829
+ const HISTORY_CONTENT_TYPES = /* @__PURE__ */ new Set(["user/message", "assistant/message"]);
1830
+ const historyText = (content) => {
1831
+ if (!Array.isArray(content)) return "";
1832
+ return content.map((block) => {
1833
+ if (block === null || typeof block !== "object") return "";
1834
+ const record = block;
1835
+ return record.type === "text" && typeof record.text === "string" ? record.text : "";
1836
+ }).filter(Boolean).join("\n").trim();
1837
+ };
1838
+ const historyRole = (event) => {
1839
+ return event.data?.role === "user" || event.type === "user/message" ? "user" : "assistant";
1840
+ };
1841
+ const historyAuthor = (event, role, t) => {
1842
+ const source = event.data?.source;
1843
+ const memberName = typeof source?.memberName === "string" ? source.memberName.trim() : "";
1844
+ if (memberName !== "") return memberName;
1845
+ return role === "user" ? t("me") : t("assistant");
1846
+ };
1847
+ const groupHistory = async (roomId, t) => {
1848
+ const entries = [];
1849
+ let cursor = null;
1850
+ for (let page = 0; page < 20; page += 1) {
1851
+ const suffix = cursor === null ? "" : "?cursor=" + encodeURIComponent(cursor);
1852
+ const response = await fetch("/dsh-group-chat/api/rooms/" + encodeURIComponent(roomId) + "/events" + suffix);
1853
+ if (!response.ok) throw new Error(t("error"));
1854
+ const payload = await response.json();
1855
+ for (const record of payload.events ?? []) {
1856
+ const event = record.event;
1857
+ if (event === void 0 || typeof event.type !== "string" || !HISTORY_CONTENT_TYPES.has(event.type)) continue;
1858
+ const data = event.data;
1859
+ const role = historyRole(event);
1860
+ const text = historyText(data?.content);
1861
+ if (text === "") continue;
1862
+ entries.push({
1863
+ key: record.id ?? String(event.seq ?? entries.length),
1864
+ role,
1865
+ author: historyAuthor(event, role, t),
1866
+ time: typeof event.time === "number" ? event.time : void 0,
1867
+ text,
1868
+ jumpId: record.id
1869
+ });
1870
+ }
1871
+ if (payload.hasMore !== true || payload.nextCursor === null || payload.nextCursor === void 0) break;
1872
+ cursor = payload.nextCursor;
1873
+ }
1874
+ return entries.sort((left, right) => (right.time ?? 0) - (left.time ?? 0));
1875
+ };
1876
+ const sessionHistory = async (sessionId, sessions, t) => {
1877
+ const service = sessions;
1878
+ if (service.retain === void 0) return [];
1879
+ const reference = service.retain(sessionId, { source: "gateway" });
1880
+ try {
1881
+ await reference.ready;
1882
+ const binding = reference.binding;
1883
+ if (binding === void 0) return [];
1884
+ await binding.session?.loadOlder?.().catch(() => void 0);
1885
+ const entries = binding.eventSource?.getSnapshot().entries ?? [];
1886
+ return entries.filter((entry) => entry.type === "event" && entry.event !== void 0).filter((entry) => typeof entry.event.type === "string" && HISTORY_CONTENT_TYPES.has(entry.event.type)).map((entry) => {
1887
+ const event = entry.event;
1888
+ const data = event.data;
1889
+ const role = historyRole(event);
1890
+ return {
1891
+ key: String(event.seq ?? entries.indexOf(entry)),
1892
+ role,
1893
+ author: historyAuthor(event, role, t),
1894
+ time: typeof event.time === "number" ? event.time : void 0,
1895
+ text: historyText(data?.content)
1896
+ };
1897
+ }).filter((entry) => entry.text !== "").sort((left, right) => (right.time ?? 0) - (left.time ?? 0));
1898
+ } finally {
1899
+ reference.release?.();
1900
+ }
1901
+ };
1902
+ const HistoryGlyph = () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1903
+ width: "16",
1904
+ height: "16",
1905
+ viewBox: "0 0 16 16",
1906
+ fill: "none",
1907
+ xmlns: "http://www.w3.org/2000/svg",
1908
+ "aria-hidden": "true",
1909
+ strokeWidth: "1.5",
1910
+ children: [
1911
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1912
+ d: "M2.4 6.8A5.6 5.6 0 1 1 2.4 9.2",
1913
+ stroke: "currentColor",
1914
+ strokeLinecap: "round"
1915
+ }),
1916
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1917
+ d: "M2.2 3.4v3.4h3.4",
1918
+ stroke: "currentColor",
1919
+ strokeLinecap: "round",
1920
+ strokeLinejoin: "round"
1921
+ }),
1922
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1923
+ d: "M8 5.4V8l1.9 1.2",
1924
+ stroke: "currentColor",
1925
+ strokeLinecap: "round",
1926
+ strokeLinejoin: "round"
1927
+ })
1928
+ ]
1929
+ });
1930
+ const fileAttachmentIn = (content, name) => {
1931
+ if (!Array.isArray(content)) return void 0;
1932
+ for (const block of content) {
1933
+ if (block === null || typeof block !== "object") continue;
1934
+ const record = block;
1935
+ if (record.type !== "file") continue;
1936
+ const attachment = record.attachment;
1937
+ if (attachment?.name === name && typeof attachment.attachmentId === "string" && typeof attachment.bytes === "number") return {
1938
+ attachmentId: attachment.attachmentId,
1939
+ name,
1940
+ bytes: attachment.bytes
1941
+ };
1942
+ }
1943
+ };
1944
+ const sessionFileAttachment = async (sessionId, sessions, name) => {
1945
+ const service = sessions;
1946
+ if (service.retain === void 0) return void 0;
1947
+ const reference = service.retain(sessionId, { source: "gateway" });
1948
+ try {
1949
+ await reference.ready;
1950
+ const binding = reference.binding;
1951
+ if (binding === void 0) return void 0;
1952
+ const find = () => {
1953
+ const entries = binding.eventSource?.getSnapshot().entries ?? [];
1954
+ for (const entry of entries) {
1955
+ if (entry.type !== "event" || entry.event === void 0) continue;
1956
+ if (entry.event.type !== "user/message") continue;
1957
+ const data = entry.event.data;
1958
+ const found = fileAttachmentIn(data?.content, name);
1959
+ if (found !== void 0) return found;
1960
+ }
1961
+ };
1962
+ let found = find();
1963
+ for (let page = 0; page < 5 && found === void 0; page += 1) {
1964
+ await binding.session?.loadOlder?.().catch(() => void 0);
1965
+ found = find();
1966
+ }
1967
+ return found;
1968
+ } finally {
1969
+ reference.release?.();
1970
+ }
1971
+ };
1972
+ const HistoryButton = ({ sessionId, sessions, t }) => {
1973
+ const [open, setOpen] = (0, react.useState)(false);
1974
+ const [entries, setEntries] = (0, react.useState)([]);
1975
+ const [loading, setLoading] = (0, react.useState)(false);
1976
+ const [error, setError] = (0, react.useState)("");
1977
+ const [query, setQuery] = (0, react.useState)("");
1978
+ const isGroup = sessionId.startsWith("group-room-");
1979
+ (0, react.useEffect)(() => {
1980
+ if (!open) return;
1981
+ let alive = true;
1982
+ setLoading(true);
1983
+ setError("");
1984
+ setQuery("");
1985
+ setEntries([]);
1986
+ (isGroup ? groupHistory(sessionId.slice(11), t) : sessionHistory(sessionId, sessions, t)).then((items) => {
1987
+ if (alive) setEntries(items);
1988
+ }).catch((cause) => {
1989
+ if (alive) setError(cause instanceof Error ? cause.message : String(cause));
1990
+ }).finally(() => {
1991
+ if (alive) setLoading(false);
1992
+ });
1993
+ return () => {
1994
+ alive = false;
1995
+ };
1996
+ }, [
1997
+ open,
1998
+ isGroup,
1999
+ sessionId,
2000
+ sessions,
2001
+ t
2002
+ ]);
2003
+ const needle = query.trim().toLocaleLowerCase();
2004
+ const results = (0, react.useMemo)(() => {
2005
+ if (needle === "") return [];
2006
+ return entries.filter((entry) => entry.text.toLocaleLowerCase().includes(needle) || entry.author.toLocaleLowerCase().includes(needle));
2007
+ }, [entries, needle]);
2008
+ const jump = (entry) => {
2009
+ setOpen(false);
2010
+ window.setTimeout(() => {
2011
+ let target = entry.jumpId === void 0 ? null : document.querySelector("[data-group-chat-message=\"" + CSS.escape(entry.jumpId) + "\"]");
2012
+ if (target === null) {
2013
+ const text = entry.text.slice(0, 80);
2014
+ target = [...document.querySelectorAll("[data-group-chat-message], [data-slot=\"conversation.user.text.node\"]")].find((element) => (element.innerText ?? "").includes(text)) ?? null;
2015
+ }
2016
+ if (target === null && window.find(entry.text.slice(0, 80), false, false, true, false, false, false)) {
2017
+ const node = window.getSelection()?.anchorNode;
2018
+ target = node instanceof HTMLElement ? node : node?.parentElement ?? null;
2019
+ }
2020
+ target?.scrollIntoView({
2021
+ behavior: "smooth",
2022
+ block: "center"
2023
+ });
2024
+ if (target !== null) {
2025
+ target.style.outline = "2px solid #2563eb";
2026
+ target.style.outlineOffset = "4px";
2027
+ window.setTimeout(() => {
2028
+ target.style.outline = "";
2029
+ target.style.outlineOffset = "";
2030
+ }, 1800);
2031
+ }
2032
+ }, 80);
2033
+ };
2034
+ const searchInputStyle = {
2035
+ width: "100%",
2036
+ boxSizing: "border-box",
2037
+ height: 36,
2038
+ padding: "0 34px 0 12px",
2039
+ borderRadius: 8,
2040
+ border: "1px solid " + colors.line,
2041
+ background: colors.panel,
2042
+ color: colors.text,
2043
+ fontSize: 13,
2044
+ outline: "none"
2045
+ };
2046
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2047
+ type: "button",
2048
+ "aria-label": t("open"),
2049
+ title: t("open"),
2050
+ "data-session-id": sessionId,
2051
+ onClick: () => setOpen(true),
2052
+ style: {
2053
+ display: "inline-flex",
2054
+ alignItems: "center",
2055
+ justifyContent: "center",
2056
+ width: 28,
2057
+ height: 28,
2058
+ border: "none",
2059
+ borderRadius: 6,
2060
+ background: "transparent",
2061
+ color: "inherit",
2062
+ cursor: "pointer"
2063
+ },
2064
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HistoryGlyph, {})
2065
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
2066
+ open,
2067
+ onClose: () => setOpen(false),
2068
+ title: t("title"),
2069
+ closeLabel: t("close"),
2070
+ className: TASK_BUS_HISTORY_CLASS,
2071
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2072
+ style: {
2073
+ width: "100%",
2074
+ maxHeight: "min(68vh, 620px)",
2075
+ overflowY: "auto",
2076
+ display: "grid",
2077
+ gap: 10,
2078
+ padding: "4px 0 8px"
2079
+ },
2080
+ children: [
2081
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2082
+ style: {
2083
+ position: "relative",
2084
+ width: "100%"
2085
+ },
2086
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2087
+ type: "text",
2088
+ value: query,
2089
+ autoFocus: true,
2090
+ spellCheck: false,
2091
+ placeholder: t("searchPlaceholder"),
2092
+ "aria-label": t("searchLabel"),
2093
+ onChange: (event) => {
2094
+ setQuery(event.target.value);
2095
+ },
2096
+ style: searchInputStyle
2097
+ }), query !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2098
+ type: "button",
2099
+ "aria-label": t("clear"),
2100
+ title: t("clear"),
2101
+ onClick: () => {
2102
+ setQuery("");
2103
+ },
2104
+ style: {
2105
+ position: "absolute",
2106
+ right: 6,
2107
+ top: "50%",
2108
+ transform: "translateY(-50%)",
2109
+ display: "inline-flex",
2110
+ alignItems: "center",
2111
+ justifyContent: "center",
2112
+ width: 24,
2113
+ height: 24,
2114
+ border: "none",
2115
+ borderRadius: 5,
2116
+ background: "transparent",
2117
+ color: colors.muted,
2118
+ cursor: "pointer"
2119
+ },
2120
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular, { size: 14 })
2121
+ }) : null]
2122
+ }),
2123
+ loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2124
+ style: {
2125
+ padding: 28,
2126
+ textAlign: "center",
2127
+ color: colors.muted,
2128
+ fontSize: 12
2129
+ },
2130
+ children: t("loading")
2131
+ }) : null,
2132
+ !loading && error !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2133
+ role: "alert",
2134
+ style: {
2135
+ padding: 12,
2136
+ color: colors.danger,
2137
+ fontSize: 12
2138
+ },
2139
+ children: error
2140
+ }) : null,
2141
+ !loading && error === "" && needle === "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2142
+ style: {
2143
+ display: "grid",
2144
+ justifyItems: "center",
2145
+ gap: 8,
2146
+ padding: "36px 20px 40px",
2147
+ color: colors.muted,
2148
+ textAlign: "center"
2149
+ },
2150
+ children: [
2151
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2152
+ style: {
2153
+ display: "inline-flex",
2154
+ width: 36,
2155
+ height: 36,
2156
+ alignItems: "center",
2157
+ justifyContent: "center",
2158
+ borderRadius: 18,
2159
+ background: colors.panelAlt,
2160
+ color: colors.muted
2161
+ },
2162
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HistoryGlyph, {})
2163
+ }),
2164
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2165
+ style: {
2166
+ fontSize: 13,
2167
+ fontWeight: 600,
2168
+ color: colors.text
2169
+ },
2170
+ children: t("initial")
2171
+ }),
2172
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2173
+ style: {
2174
+ fontSize: 12,
2175
+ lineHeight: 1.6
2176
+ },
2177
+ children: t("initialHint")
2178
+ })
2179
+ ]
2180
+ }) : null,
2181
+ !loading && error === "" && needle !== "" && results.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2182
+ style: {
2183
+ padding: 28,
2184
+ textAlign: "center",
2185
+ color: colors.muted,
2186
+ fontSize: 12
2187
+ },
2188
+ children: entries.length === 0 ? t("empty") : t("noResults")
2189
+ }) : null,
2190
+ !loading && error === "" && needle !== "" && results.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2191
+ type: "button",
2192
+ onClick: () => jump(entry),
2193
+ style: {
2194
+ display: "grid",
2195
+ gap: 6,
2196
+ width: "100%",
2197
+ minWidth: 0,
2198
+ textAlign: "left",
2199
+ padding: "10px 12px",
2200
+ borderRadius: 8,
2201
+ border: "1px solid " + colors.line,
2202
+ background: colors.panelAlt,
2203
+ color: colors.text,
2204
+ cursor: "pointer"
2205
+ },
2206
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2207
+ style: {
2208
+ display: "flex",
2209
+ alignItems: "center",
2210
+ gap: 8,
2211
+ minWidth: 0,
2212
+ flexWrap: "wrap",
2213
+ fontSize: 10,
2214
+ color: colors.muted
2215
+ },
2216
+ children: [
2217
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2218
+ style: {
2219
+ fontWeight: 650,
2220
+ color: entry.role === "user" ? "#2563eb" : colors.text
2221
+ },
2222
+ children: entry.author
2223
+ }),
2224
+ entry.time === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: new Date(entry.time).toLocaleString(t("dateLocale")) }),
2225
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2226
+ style: { marginLeft: "auto" },
2227
+ children: t("jump")
2228
+ })
2229
+ ]
2230
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2231
+ style: {
2232
+ display: "block",
2233
+ minWidth: 0,
2234
+ fontSize: 13,
2235
+ lineHeight: 1.65,
2236
+ whiteSpace: "pre-wrap",
2237
+ overflowWrap: "anywhere",
2238
+ wordBreak: "break-word"
2239
+ },
2240
+ children: entry.text
2241
+ })]
2242
+ }, entry.key))
2243
+ ]
2244
+ })
2245
+ })] });
2246
+ };
2247
+ const apply = (ctx) => {
2248
+ const locale = ctx.locale;
2249
+ ctx.effect(() => locale.register(HISTORY_NS, {
2250
+ zh: HISTORY_COPY.zh,
2251
+ en: HISTORY_COPY.en
2252
+ }), "task-bus: history copy");
2253
+ const t = locale.bind(HISTORY_NS);
2254
+ const surface = ctx;
2255
+ const surfaceWithServices = surface;
2256
+ surfaceWithServices.sidebarRight ?? surfaceWithServices.get?.("sidebarRight");
2257
+ ctx.effect(() => {
2258
+ const onFileClick = (event) => {
2259
+ const target = event.target;
2260
+ if (!(target instanceof Element)) return;
2261
+ const card = target.closest("[data-message-attachments] > *");
2262
+ if (!(card instanceof HTMLElement)) return;
2263
+ const sessionId = document.querySelector("button[aria-label=\"消息记录\"][data-session-id]")?.getAttribute("data-session-id") ?? "";
2264
+ if (sessionId === "" || sessionId.startsWith("group-room-")) return;
2265
+ const name = (card.getAttribute("title") ?? card.innerText.split("\n")[0] ?? "").trim();
2266
+ if (name === "") return;
2267
+ (async () => {
2268
+ const attachment = await sessionFileAttachment(sessionId, ctx.sessions, name);
2269
+ if (attachment === void 0) return;
2270
+ const cwd = (ctx.sessions?.list?.getSnapshot())?.byId?.[sessionId]?.cwd ?? "";
2271
+ const query = "attachmentId=" + encodeURIComponent(attachment.attachmentId) + "&name=" + encodeURIComponent(attachment.name) + "&bytes=" + encodeURIComponent(String(attachment.bytes)) + (cwd === "" ? "" : "&cwd=" + encodeURIComponent(cwd));
2272
+ const response = await fetch(ROUTE + "/attachment-materialize?" + query);
2273
+ if (!response.ok) throw new Error("附件预览准备失败");
2274
+ const materialized = await response.json();
2275
+ if (typeof materialized.path !== "string") throw new Error("附件预览路径缺失");
2276
+ const address = "dsh-resource://file/session/" + encodeURIComponent(sessionId) + "/" + materialized.path.split("/").map((part) => encodeURIComponent(part)).join("/");
2277
+ const right = ctx.sidebarRight;
2278
+ if (right?.openResource !== void 0) right.openResource(address);
2279
+ else window.open(new URL("/dsh-task-bus/api/attachment-materialize?" + query, window.location.origin).toString(), "_blank", "noopener,noreferrer");
2280
+ })().catch((cause) => {
2281
+ console.error("[task-bus] file preview failed", cause);
2282
+ });
2283
+ };
2284
+ document.addEventListener("click", onFileClick, true);
2285
+ return () => {
2286
+ document.removeEventListener("click", onFileClick, true);
2287
+ };
2288
+ }, "task-bus: native file card preview");
2289
+ const openSkillCenter = () => {
2290
+ try {
2291
+ surface.layout.selectPanel("skill-center");
2292
+ return true;
2293
+ } catch {
2294
+ return false;
2295
+ }
2296
+ };
2297
+ const Surface = () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskCenterButton, {
2298
+ openSession: (sessionId) => surface.uiWorkspace.openSession(sessionId),
2299
+ openSkillCenter
2300
+ });
2301
+ ctx.effect(() => surface.slots.inject("shell.overlay", () => surface.slots.register({
2302
+ name: "shell.overlay",
2303
+ id: "task-bus.center"
2304
+ }, Surface)), "task-bus: Task Center overlay");
2305
+ ctx.effect(() => {
2306
+ const style = document.createElement("style");
2307
+ style.id = "task-bus-history-replaces-file-manager";
2308
+ style.textContent = "button[aria-label=\"用 文件管理器 打开\"] { display: none !important; }\n[data-message-attachments] > * { cursor: pointer; }";
2309
+ document.head.appendChild(style);
2310
+ return () => {
2311
+ style.remove();
2312
+ };
2313
+ }, "task-bus: replace file manager header button");
2314
+ ctx.effect(() => surface.slots.inject("conversation.session.header.utilities", () => surface.slots.register({
2315
+ name: "conversation.session.header.utilities",
2316
+ id: "task-bus.history",
2317
+ order: -4,
2318
+ inject: ((sessionId) => ({
2319
+ sessionId,
2320
+ sessions: ctx.sessions,
2321
+ t
2322
+ }))
2323
+ }, HistoryButton)), "task-bus: message history");
2324
+ };
2325
+ //#endregion
2326
+ exports.apply = apply;
2327
+ exports.inject = inject;
2328
+ return module.exports;
2329
+ }
2330
+ });
2331
+
2332
+ //# sourceMappingURL=client.js.map