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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js ADDED
@@ -0,0 +1,1869 @@
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/index.tsx
11
+ /**
12
+ * The Task Center surface in the frame-wide overlay.
13
+ *
14
+ * The button is global because a task can arrive while the reader is in any
15
+ * session. The panel only renders cards returned by the Host bridge; it never
16
+ * talks to DataOps directly and never derives a second approval status.
17
+ */
18
+ const inject = [
19
+ "slots",
20
+ "locale",
21
+ "uiWorkspace",
22
+ "sessions",
23
+ "sidebarRight"
24
+ ];
25
+ 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
+ const TASK_BUS_MODAL_CLASS = "task-bus-center-modal";
39
+ const TASK_BUS_HISTORY_CLASS = "task-bus-history-modal";
40
+ const TASK_BUS_STYLE_ID = "task-bus-center-modal-style";
41
+ const TASK_BUS_MODAL_CSS = `
42
+ .${TASK_BUS_MODAL_CLASS} {
43
+ width: min(960px, calc(100vw - 64px)) !important;
44
+ height: min(760px, calc(100vh - 64px)) !important;
45
+ gap: 0 !important;
46
+ padding: 0 !important;
47
+ }
48
+ @media (max-width: 700px) {
49
+ .${TASK_BUS_MODAL_CLASS} {
50
+ width: calc(100vw - 24px) !important;
51
+ height: calc(100vh - 24px) !important;
52
+ }
53
+ }
54
+ .${TASK_BUS_HISTORY_CLASS} {
55
+ width: min(960px, 92vw) !important;
56
+ max-width: calc(100vw - 32px) !important;
57
+ height: auto !important;
58
+ max-height: min(780px, calc(100vh - 48px)) !important;
59
+ }
60
+ @media (max-width: 700px) {
61
+ .${TASK_BUS_HISTORY_CLASS} {
62
+ width: calc(100vw - 24px) !important;
63
+ max-width: calc(100vw - 24px) !important;
64
+ max-height: calc(100vh - 24px) !important;
65
+ }
66
+ }
67
+ `;
68
+ const HISTORY_NS = "task-bus.history";
69
+ const HISTORY_COPY = {
70
+ zh: {
71
+ title: "消息记录",
72
+ open: "消息记录",
73
+ close: "关闭",
74
+ searchPlaceholder: "搜索消息或发送人…",
75
+ searchLabel: "搜索消息记录",
76
+ clear: "清空搜索",
77
+ initial: "输入关键词搜索消息或发送人",
78
+ initialHint: "可按消息正文或发送人筛选,点结果跳到原消息。",
79
+ loading: "正在加载消息记录…",
80
+ error: "消息记录加载失败",
81
+ empty: "暂无消息记录",
82
+ noResults: "没有匹配的消息",
83
+ jump: "跳到该消息",
84
+ me: "我",
85
+ assistant: "助手",
86
+ dateLocale: "zh-CN"
87
+ },
88
+ en: {
89
+ title: "Message history",
90
+ open: "Message history",
91
+ close: "Close",
92
+ searchPlaceholder: "Search messages or senders…",
93
+ searchLabel: "Search message history",
94
+ clear: "Clear search",
95
+ initial: "Search messages or senders to begin",
96
+ initialHint: "Filter by message text or sender, then click a result to jump to it.",
97
+ loading: "Loading message history…",
98
+ error: "Could not load message history",
99
+ empty: "No message history",
100
+ noResults: "No matching messages",
101
+ jump: "Jump to message",
102
+ me: "Me",
103
+ assistant: "Assistant",
104
+ dateLocale: "en-US"
105
+ }
106
+ };
107
+ const buttonStyle = {
108
+ border: "1px solid " + colors.line,
109
+ background: colors.panel,
110
+ color: colors.text,
111
+ borderRadius: 8,
112
+ padding: "7px 11px",
113
+ fontSize: 12,
114
+ cursor: "pointer"
115
+ };
116
+ const statusTagTone = (status) => {
117
+ if (status === "completed") return "success";
118
+ if (status === "cancelled") return "neutral";
119
+ if (status === "blocked") return "danger";
120
+ if (status === "pending_confirm" || status === "returned" || status === "pending_accept") return "warning";
121
+ return "info";
122
+ };
123
+ const relativeTime = (value) => {
124
+ const time = Date.parse(value);
125
+ if (!Number.isFinite(time)) return "";
126
+ const minutes = Math.max(0, Math.round((Date.now() - time) / 6e4));
127
+ if (minutes < 1) return "刚刚";
128
+ if (minutes < 60) return String(minutes) + " 分钟前";
129
+ const hours = Math.round(minutes / 60);
130
+ if (hours < 24) return String(hours) + " 小时前";
131
+ return String(Math.round(hours / 24)) + " 天前";
132
+ };
133
+ const fetchJson = async (path) => {
134
+ const response = await fetch(ROUTE + path, { headers: { accept: "application/json" } });
135
+ const payload = await response.json();
136
+ if (!response.ok) {
137
+ const message = payload !== null && typeof payload === "object" && "error" in payload ? String(payload.error) : "任务中心请求失败(" + response.status + ")";
138
+ throw new Error(message);
139
+ }
140
+ return payload;
141
+ };
142
+ const approvalHref = (raw) => {
143
+ if (raw === null) return null;
144
+ try {
145
+ const target = new URL(raw);
146
+ const referrer = document.referrer ? new URL(document.referrer) : null;
147
+ if (referrer !== null && referrer.origin !== target.origin) return referrer.origin + target.pathname + target.search + target.hash;
148
+ return target.href;
149
+ } catch {
150
+ return raw;
151
+ }
152
+ };
153
+ const TaskCard = ({ task, busy, aiBusy, onTransition, onAiAccept, onOpen }) => {
154
+ const isApproval = task.sourceType === "dataops_approval";
155
+ const deepLink = approvalHref(task.source.deepLink ?? null);
156
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
157
+ style: {
158
+ border: "1px solid " + colors.line,
159
+ borderRadius: 10,
160
+ background: colors.panel,
161
+ padding: "12px 13px",
162
+ display: "grid",
163
+ gap: 8
164
+ },
165
+ children: [
166
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
167
+ style: {
168
+ display: "flex",
169
+ alignItems: "flex-start",
170
+ gap: 10
171
+ },
172
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
173
+ style: {
174
+ flex: 1,
175
+ minWidth: 0
176
+ },
177
+ children: [
178
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
179
+ style: {
180
+ display: "flex",
181
+ alignItems: "center",
182
+ gap: 7,
183
+ flexWrap: "wrap",
184
+ marginBottom: 4
185
+ },
186
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
187
+ tone: isApproval ? "info" : "neutral",
188
+ children: isApproval ? task.source.label ?? "DataOps 审批" : "任务总线"
189
+ }), task.tags?.map((tag) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
190
+ tone: "quiet",
191
+ children: tag
192
+ }, tag))]
193
+ }),
194
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
195
+ style: {
196
+ fontSize: 13,
197
+ fontWeight: 650,
198
+ color: colors.text,
199
+ lineHeight: 1.45
200
+ },
201
+ children: task.title
202
+ }),
203
+ task.description ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
204
+ style: {
205
+ fontSize: 11,
206
+ color: colors.muted,
207
+ marginTop: 4,
208
+ lineHeight: 1.5
209
+ },
210
+ children: task.description
211
+ }) : null
212
+ ]
213
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
214
+ style: { flex: "none" },
215
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
216
+ tone: statusTagTone(task.status),
217
+ children: task.statusLabel
218
+ })
219
+ })]
220
+ }),
221
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
222
+ style: {
223
+ display: "flex",
224
+ alignItems: "center",
225
+ gap: 8,
226
+ flexWrap: "wrap",
227
+ fontSize: 10,
228
+ color: colors.muted
229
+ },
230
+ children: [
231
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: isApproval ? "审批任务" : "任务" }),
232
+ task.assignee?.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["受理人:", task.assignee.id] }) : null,
233
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
234
+ style: { marginLeft: "auto" },
235
+ children: relativeTime(task.updatedAt)
236
+ })
237
+ ]
238
+ }),
239
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
240
+ style: {
241
+ display: "flex",
242
+ gap: 6,
243
+ flexWrap: "wrap"
244
+ },
245
+ children: [
246
+ isApproval && deepLink ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
247
+ size: "sm",
248
+ variant: "primary",
249
+ onClick: () => window.open(deepLink, "_blank", "noopener,noreferrer"),
250
+ children: "办理"
251
+ }) : null,
252
+ task.status !== "completed" && task.status !== "cancelled" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
253
+ size: "sm",
254
+ variant: "outline",
255
+ disabled: aiBusy || busy,
256
+ onClick: () => onAiAccept(task),
257
+ children: aiBusy ? "受理中…" : "AI受理"
258
+ }) : null,
259
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
260
+ size: "sm",
261
+ variant: "ghost",
262
+ onClick: () => onOpen(task),
263
+ children: "详情"
264
+ }),
265
+ !isApproval && task.status === "pending_accept" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
266
+ size: "sm",
267
+ variant: "primary",
268
+ disabled: busy,
269
+ onClick: () => onTransition(task, "accept"),
270
+ children: "受理"
271
+ }) : null,
272
+ !isApproval && task.status === "in_progress" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
273
+ size: "sm",
274
+ variant: "outline",
275
+ disabled: busy,
276
+ onClick: () => onTransition(task, "submit"),
277
+ children: "提交确认"
278
+ }) : null,
279
+ !isApproval && task.status === "pending_confirm" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
280
+ size: "sm",
281
+ variant: "primary",
282
+ disabled: busy,
283
+ onClick: () => onTransition(task, "complete"),
284
+ children: "完成"
285
+ }) : null,
286
+ !isApproval && task.status !== "completed" && task.status !== "cancelled" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
287
+ size: "sm",
288
+ variant: "ghost",
289
+ disabled: busy,
290
+ onClick: () => onTransition(task, "cancel"),
291
+ children: "取消"
292
+ }) : null
293
+ ]
294
+ })
295
+ ]
296
+ });
297
+ };
298
+ const FORM_LABELS = {
299
+ applicationMode: "申请方式",
300
+ requestKind: "申请类型",
301
+ requestedPermissions: "申请权限",
302
+ requesterDepartment: "申请人部门",
303
+ requesterPhone: "申请人电话",
304
+ validFrom: "授权开始时间",
305
+ expiresAt: "授权结束时间",
306
+ selectionSummary: "资源摘要",
307
+ code: "应用编码",
308
+ name: "应用名称",
309
+ reason: "申请理由",
310
+ description: "应用说明",
311
+ departmentName: "归属部门",
312
+ departmentCode: "归属部门编码",
313
+ ownerName: "归属负责人",
314
+ ownerPhone: "归属负责人电话",
315
+ constructionUnit: "建设单位",
316
+ constructionOwner: "建设单位负责人",
317
+ constructionOwnerPhone: "建设单位负责人电话",
318
+ operationsUnit: "运维单位",
319
+ operationsOwner: "运维单位负责人",
320
+ operationsOwnerPhone: "运维单位负责人电话",
321
+ requestTime: "申请时间",
322
+ resourceName: "资源名称",
323
+ resourceType: "资源类型",
324
+ accessType: "访问方式",
325
+ accessScope: "访问范围",
326
+ applicationName: "所属应用",
327
+ applicationCode: "应用编码",
328
+ environment: "环境",
329
+ accountCount: "账号数量"
330
+ };
331
+ const SKIP_FORM_KEYS = /* @__PURE__ */ new Set([
332
+ "ownerUserId",
333
+ "requesterUserId",
334
+ "requesterDept",
335
+ "existingApplicationId",
336
+ "applicationId",
337
+ "taskType"
338
+ ]);
339
+ const formatBytes = (bytes) => {
340
+ if (!Number.isFinite(bytes) || bytes < 0) return "";
341
+ if (bytes < 1024) return bytes + " B";
342
+ if (bytes < 1048576) return (bytes / 1024).toFixed(1) + " KB";
343
+ return (bytes / 1048576).toFixed(1) + " MB";
344
+ };
345
+ const humanizeField = (key) => key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
346
+ const formatFormValue = (key, value) => {
347
+ if (key === "applicationMode") {
348
+ if (value === "EXISTING") return "使用已有应用";
349
+ if (value === "NEW") return "新建应用";
350
+ }
351
+ if (typeof value === "boolean") return value ? "是" : "否";
352
+ if (Array.isArray(value)) return value.map((item) => String(item)).join("、");
353
+ if (value !== null && typeof value === "object") {
354
+ const record = value;
355
+ if (typeof record.name === "string") return record.name;
356
+ return JSON.stringify(value);
357
+ }
358
+ return String(value);
359
+ };
360
+ const toFormField = (key, value) => ({
361
+ key,
362
+ label: FORM_LABELS[key] ?? humanizeField(key),
363
+ value,
364
+ ...key === "reason" || key === "description" || key === "accessScope" ? {
365
+ type: "multiline",
366
+ span: 2
367
+ } : {}
368
+ });
369
+ 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));
370
+ const approvalFormSections = (detail) => {
371
+ if (detail === null) return [];
372
+ const sections = [];
373
+ const applicationFields = formFields(detail.payload?.form);
374
+ if (applicationFields.length > 0) sections.push({
375
+ title: "申请信息",
376
+ fields: applicationFields
377
+ });
378
+ const resourceFields = formFields(detail.payload?.resourceAccessRequest?.form);
379
+ if (resourceFields.length > 0) sections.push({
380
+ title: "资源申请",
381
+ fields: resourceFields
382
+ });
383
+ const resources = [...detail.payload?.selectedItems ?? [], ...detail.payload?.resourceAccessRequest?.selectedItems ?? []];
384
+ if (resources.length > 0) sections.push({
385
+ title: "申请资源",
386
+ fields: [{
387
+ key: "resources",
388
+ label: "资源",
389
+ value: resources,
390
+ type: "resources",
391
+ span: 2
392
+ }]
393
+ });
394
+ return sections;
395
+ };
396
+ const ResourceValue = ({ items }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
397
+ style: {
398
+ display: "grid",
399
+ gap: 5
400
+ },
401
+ children: items.map((item, index) => {
402
+ const label = typeof item.title === "string" ? item.title : typeof item.name === "string" ? item.name : `资源 ${index + 1}`;
403
+ const detail = [
404
+ item.scope,
405
+ item.urn,
406
+ item.id
407
+ ].filter((value) => typeof value === "string" || typeof value === "number").join(" · ");
408
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
409
+ style: {
410
+ display: "flex",
411
+ alignItems: "baseline",
412
+ gap: 8,
413
+ minWidth: 0
414
+ },
415
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
416
+ style: {
417
+ fontSize: 12,
418
+ color: colors.text
419
+ },
420
+ children: label
421
+ }), detail ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
422
+ style: {
423
+ fontSize: 10,
424
+ color: colors.muted,
425
+ overflow: "hidden",
426
+ textOverflow: "ellipsis",
427
+ whiteSpace: "nowrap"
428
+ },
429
+ children: detail
430
+ }) : null]
431
+ }, String(item.urn ?? item.id ?? index));
432
+ })
433
+ });
434
+ const FormDescriptions = ({ sections }) => {
435
+ if (sections.length === 0) return null;
436
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
437
+ style: {
438
+ display: "grid",
439
+ gap: 10
440
+ },
441
+ children: sections.map((section, sectionIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
442
+ style: {
443
+ border: "1px solid " + colors.line,
444
+ borderRadius: 10,
445
+ background: colors.panel,
446
+ padding: 14
447
+ },
448
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
449
+ style: {
450
+ fontSize: 12,
451
+ fontWeight: 650,
452
+ color: colors.text,
453
+ marginBottom: 9
454
+ },
455
+ children: section.title
456
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dl", {
457
+ style: {
458
+ margin: 0,
459
+ display: "grid",
460
+ gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
461
+ gap: "9px 24px"
462
+ },
463
+ children: section.fields.map((field) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
464
+ style: {
465
+ display: "grid",
466
+ gridTemplateColumns: "94px minmax(0, 1fr)",
467
+ alignItems: "start",
468
+ gap: 8,
469
+ minWidth: 0,
470
+ gridColumn: field.span === 2 ? "1 / -1" : void 0
471
+ },
472
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
473
+ style: {
474
+ fontSize: 10,
475
+ lineHeight: 1.6,
476
+ color: colors.muted
477
+ },
478
+ children: field.label
479
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
480
+ style: {
481
+ margin: 0,
482
+ fontSize: 12,
483
+ lineHeight: 1.6,
484
+ color: colors.text,
485
+ whiteSpace: "pre-wrap",
486
+ overflowWrap: "anywhere"
487
+ },
488
+ 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)
489
+ })]
490
+ }, field.key))
491
+ })]
492
+ }, section.title + sectionIndex))
493
+ });
494
+ };
495
+ const TaskDetail = ({ task, onBack }) => {
496
+ const isApproval = task.sourceType === "dataops_approval";
497
+ const [detail, setDetail] = (0, react.useState)(null);
498
+ const [detailLoading, setDetailLoading] = (0, react.useState)(true);
499
+ const [detailError, setDetailError] = (0, react.useState)(null);
500
+ (0, react.useEffect)(() => {
501
+ let alive = true;
502
+ setDetailLoading(true);
503
+ setDetailError(null);
504
+ fetchJson("/items/" + encodeURIComponent(task.id)).then((payload) => {
505
+ if (alive) setDetail(payload.detail ?? null);
506
+ }).catch((cause) => {
507
+ if (alive) setDetailError(cause instanceof Error ? cause.message : String(cause));
508
+ }).finally(() => {
509
+ if (alive) setDetailLoading(false);
510
+ });
511
+ return () => {
512
+ alive = false;
513
+ };
514
+ }, [task.id]);
515
+ const sections = isApproval ? approvalFormSections(detail) : detail?.form?.sections ?? [];
516
+ const attachments = detail?.attachments ?? [];
517
+ const approvalTaskId = isApproval && typeof detail?.id === "number" ? detail.id : null;
518
+ const attachmentHref = (attachment) => approvalTaskId === null ? null : ROUTE + "/attachment?taskId=" + encodeURIComponent(String(approvalTaskId)) + "&attachmentId=" + encodeURIComponent(attachment.id);
519
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
520
+ style: {
521
+ display: "flex",
522
+ flexDirection: "column",
523
+ gap: 10,
524
+ flex: 1,
525
+ minHeight: "100%"
526
+ },
527
+ children: [
528
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
529
+ style: {
530
+ display: "flex",
531
+ alignItems: "flex-start",
532
+ gap: 10
533
+ },
534
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
535
+ style: {
536
+ flex: 1,
537
+ minWidth: 0
538
+ },
539
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
540
+ style: {
541
+ fontSize: 14,
542
+ fontWeight: 700,
543
+ color: colors.text,
544
+ lineHeight: 1.45
545
+ },
546
+ children: task.title
547
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
548
+ style: {
549
+ marginTop: 3,
550
+ fontSize: 10,
551
+ color: colors.muted
552
+ },
553
+ children: [
554
+ task.source.label ?? (isApproval ? "DataOps 审批" : "任务总线"),
555
+ " · ",
556
+ relativeTime(task.updatedAt)
557
+ ]
558
+ })]
559
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
560
+ tone: statusTagTone(task.status),
561
+ children: task.statusLabel
562
+ })]
563
+ }),
564
+ detailLoading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
565
+ style: {
566
+ padding: 18,
567
+ textAlign: "center",
568
+ color: colors.muted,
569
+ fontSize: 12
570
+ },
571
+ children: "正在加载表单…"
572
+ }) : null,
573
+ detailError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
574
+ style: {
575
+ padding: "8px 10px",
576
+ borderRadius: 8,
577
+ background: "#fef2f2",
578
+ color: colors.danger,
579
+ fontSize: 11
580
+ },
581
+ children: detailError
582
+ }) : null,
583
+ !detailLoading && detailError === null && sections.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormDescriptions, { sections }) : null,
584
+ !detailLoading && detailError === null && sections.length === 0 && !isApproval && task.description ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
585
+ style: {
586
+ border: "1px solid " + colors.line,
587
+ borderRadius: 10,
588
+ background: colors.panel,
589
+ padding: 14,
590
+ fontSize: 12,
591
+ lineHeight: 1.6,
592
+ color: colors.text,
593
+ whiteSpace: "pre-wrap"
594
+ },
595
+ children: task.description
596
+ }) : null,
597
+ !detailLoading && detailError === null && sections.length === 0 && (isApproval || !task.description) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
598
+ style: {
599
+ padding: 18,
600
+ textAlign: "center",
601
+ color: colors.muted,
602
+ fontSize: 12
603
+ },
604
+ children: "暂无表单信息"
605
+ }) : null,
606
+ isApproval || attachments.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
607
+ style: {
608
+ border: "1px solid " + colors.line,
609
+ borderRadius: 10,
610
+ background: colors.panel,
611
+ padding: 14
612
+ },
613
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
614
+ style: {
615
+ fontSize: 12,
616
+ fontWeight: 650,
617
+ color: colors.text,
618
+ marginBottom: 8
619
+ },
620
+ children: "附件"
621
+ }), attachments.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
622
+ style: {
623
+ fontSize: 11,
624
+ color: colors.muted
625
+ },
626
+ children: "暂无附件"
627
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
628
+ style: {
629
+ display: "grid",
630
+ gap: 6
631
+ },
632
+ children: attachments.map((attachment) => {
633
+ const href = attachmentHref(attachment);
634
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
635
+ style: {
636
+ display: "flex",
637
+ alignItems: "center",
638
+ gap: 8,
639
+ minWidth: 0
640
+ },
641
+ children: [
642
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
643
+ style: {
644
+ flex: 1,
645
+ minWidth: 0,
646
+ fontSize: 11,
647
+ color: colors.text,
648
+ overflow: "hidden",
649
+ textOverflow: "ellipsis",
650
+ whiteSpace: "nowrap"
651
+ },
652
+ children: attachment.originalName
653
+ }),
654
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
655
+ style: {
656
+ flex: "none",
657
+ fontSize: 10,
658
+ color: colors.muted
659
+ },
660
+ children: formatBytes(attachment.sizeBytes)
661
+ }),
662
+ href !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
663
+ size: "sm",
664
+ variant: "ghost",
665
+ onClick: () => window.open(href, "_blank", "noopener,noreferrer"),
666
+ children: "下载"
667
+ }) : null
668
+ ]
669
+ }, attachment.id);
670
+ })
671
+ })]
672
+ }) : null,
673
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
674
+ style: {
675
+ display: "flex",
676
+ justifyContent: "flex-end",
677
+ gap: 8,
678
+ flexWrap: "wrap",
679
+ marginTop: "auto",
680
+ paddingTop: 2
681
+ },
682
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
683
+ size: "sm",
684
+ variant: "ghost",
685
+ onClick: onBack,
686
+ children: "← 返回列表"
687
+ }), isApproval && task.source.deepLink ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
688
+ size: "sm",
689
+ variant: "outline",
690
+ onClick: () => window.open(approvalHref(task.source.deepLink ?? null) ?? "#", "_blank", "noopener,noreferrer"),
691
+ children: "打开 DataOps 审批详情"
692
+ }) : null]
693
+ })
694
+ ]
695
+ });
696
+ };
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 }) => {
860
+ const [open, setOpen] = (0, react.useState)(false);
861
+ const [tasks, setTasks] = (0, react.useState)([]);
862
+ const [loading, setLoading] = (0, react.useState)(false);
863
+ const [error, setError] = (0, react.useState)(null);
864
+ const [tab, setTab] = (0, react.useState)("all");
865
+ const [busyId, setBusyId] = (0, react.useState)(null);
866
+ const [aiBusyId, setAiBusyId] = (0, react.useState)(null);
867
+ const [creating, setCreating] = (0, react.useState)(false);
868
+ const [draftTitle, setDraftTitle] = (0, react.useState)("");
869
+ const [draftSource, setDraftSource] = (0, react.useState)("manual");
870
+ const [createBusy, setCreateBusy] = (0, react.useState)(false);
871
+ const [selected, setSelected] = (0, react.useState)(null);
872
+ 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
+ });
880
+ const [pendingCount, setPendingCount] = (0, react.useState)(0);
881
+ (0, react.useEffect)(() => {
882
+ if (document.getElementById(TASK_BUS_STYLE_ID) !== null) return;
883
+ const style = document.createElement("style");
884
+ style.id = TASK_BUS_STYLE_ID;
885
+ style.textContent = TASK_BUS_MODAL_CSS;
886
+ document.head.append(style);
887
+ return () => {
888
+ style.remove();
889
+ };
890
+ }, []);
891
+ (0, react.useEffect)(() => {
892
+ const update = () => {
893
+ if (document.querySelector("[data-sidebar-collapsed=\"true\"]") !== null) {
894
+ setAnchor((current) => current === null ? current : null);
895
+ return;
896
+ }
897
+ const toggle = Array.from(document.querySelectorAll("button")).find((button) => /收起侧边栏|Collapse sidebar/i.test((button.getAttribute("aria-label") ?? "").trim()));
898
+ if (!toggle) {
899
+ setAnchor((current) => current === null ? current : null);
900
+ return;
901
+ }
902
+ const label = (toggle.getAttribute("aria-label") ?? "").trim();
903
+ if (/展开侧边栏|Expand sidebar/i.test(label)) {
904
+ setAnchor((current) => current === null ? current : null);
905
+ return;
906
+ }
907
+ const rect = toggle.getBoundingClientRect();
908
+ if (rect.width === 0 || rect.height === 0) return;
909
+ const next = {
910
+ left: Math.max(8, rect.left - 36),
911
+ top: rect.top + (rect.height - 28) / 2
912
+ };
913
+ setAnchor((current) => current !== null && Math.abs(current.left - next.left) < .5 && Math.abs(current.top - next.top) < .5 ? current : next);
914
+ };
915
+ update();
916
+ const timer = window.setInterval(update, 400);
917
+ window.addEventListener("resize", update);
918
+ return () => {
919
+ window.clearInterval(timer);
920
+ window.removeEventListener("resize", update);
921
+ };
922
+ }, []);
923
+ const load = async (silent = false) => {
924
+ if (!connectorEnabled) {
925
+ setTasks([]);
926
+ setLoading(false);
927
+ setError(null);
928
+ return;
929
+ }
930
+ if (!silent) setLoading(true);
931
+ setError(null);
932
+ try {
933
+ const overview = await fetchJson("/overview");
934
+ setTasks([...overview.items].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)));
935
+ } catch (cause) {
936
+ setError(cause instanceof Error ? cause.message : String(cause));
937
+ } finally {
938
+ if (!silent) setLoading(false);
939
+ }
940
+ };
941
+ const enableConnector = () => {
942
+ setConnectorEnabled(true);
943
+ try {
944
+ window.localStorage.setItem(CONNECTOR_STORAGE_KEY, "true");
945
+ } catch {}
946
+ };
947
+ (0, react.useEffect)(() => {
948
+ if (!open) return;
949
+ if (!connectorEnabled) {
950
+ setTab("connectors");
951
+ setTasks([]);
952
+ setError(null);
953
+ setLoading(false);
954
+ return;
955
+ }
956
+ load();
957
+ const timer = window.setInterval(() => {
958
+ load(true);
959
+ }, 6e4);
960
+ return () => {
961
+ window.clearInterval(timer);
962
+ };
963
+ }, [open, connectorEnabled]);
964
+ (0, react.useEffect)(() => {
965
+ if (!connectorEnabled) {
966
+ setPendingCount(0);
967
+ return;
968
+ }
969
+ let alive = true;
970
+ const refresh = async () => {
971
+ try {
972
+ const result = await fetchJson("/count");
973
+ if (alive) setPendingCount(Math.max(0, Math.trunc(Number(result.count) || 0)));
974
+ } catch {}
975
+ };
976
+ refresh();
977
+ const timer = window.setInterval(() => {
978
+ refresh();
979
+ }, 3e5);
980
+ return () => {
981
+ alive = false;
982
+ window.clearInterval(timer);
983
+ };
984
+ }, [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]);
992
+ const actionable = tasks.filter((task) => task.status !== "completed" && task.status !== "cancelled").length;
993
+ const tabItems = [
994
+ {
995
+ value: "all",
996
+ id: "task-bus-tab-all",
997
+ panelId: "task-bus-panel-all",
998
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["全部 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
999
+ style: { opacity: .55 },
1000
+ children: tasks.length
1001
+ })] })
1002
+ },
1003
+ {
1004
+ value: "mine",
1005
+ id: "task-bus-tab-mine",
1006
+ panelId: "task-bus-panel-mine",
1007
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["待我处理 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1008
+ style: { opacity: .55 },
1009
+ children: actionable
1010
+ })] })
1011
+ },
1012
+ {
1013
+ value: "personal",
1014
+ id: "task-bus-tab-personal",
1015
+ panelId: "task-bus-panel-personal",
1016
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["个人 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1017
+ style: { opacity: .55 },
1018
+ children: tasks.filter((task) => task.sourceType === "task_bus" && !task.source.type.includes("group")).length
1019
+ })] })
1020
+ },
1021
+ {
1022
+ value: "group",
1023
+ id: "task-bus-tab-group",
1024
+ panelId: "task-bus-panel-group",
1025
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["群聊 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1026
+ style: { opacity: .55 },
1027
+ children: tasks.filter((task) => task.sourceType === "task_bus" && task.source.type.includes("group")).length
1028
+ })] })
1029
+ },
1030
+ {
1031
+ value: "connectors",
1032
+ id: "task-bus-tab-connectors",
1033
+ panelId: "task-bus-panel-connectors",
1034
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["连接器 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1035
+ style: { opacity: .55 },
1036
+ children: "1"
1037
+ })] })
1038
+ }
1039
+ ];
1040
+ const transition = async (task, action) => {
1041
+ setBusyId(task.id);
1042
+ setError(null);
1043
+ try {
1044
+ await fetch(ROUTE + "/generic/" + encodeURIComponent(task.id.slice(5)) + "/transition", {
1045
+ method: "POST",
1046
+ headers: { "content-type": "application/json" },
1047
+ body: JSON.stringify({ action })
1048
+ }).then(async (response) => {
1049
+ if (!response.ok) {
1050
+ const payload = await response.json().catch(() => null);
1051
+ throw new Error(payload?.error ?? "任务操作失败");
1052
+ }
1053
+ });
1054
+ await load();
1055
+ } catch (cause) {
1056
+ setError(cause instanceof Error ? cause.message : String(cause));
1057
+ } finally {
1058
+ setBusyId(null);
1059
+ }
1060
+ };
1061
+ const createTask = async () => {
1062
+ const title = draftTitle.trim();
1063
+ if (!title) return;
1064
+ setCreateBusy(true);
1065
+ setError(null);
1066
+ try {
1067
+ const response = await fetch(ROUTE + "/generic", {
1068
+ method: "POST",
1069
+ headers: { "content-type": "application/json" },
1070
+ body: JSON.stringify({
1071
+ title,
1072
+ sourceType: draftSource
1073
+ })
1074
+ });
1075
+ const payload = await response.json();
1076
+ if (!response.ok) throw new Error(payload.error ?? "创建任务失败");
1077
+ setDraftTitle("");
1078
+ setCreating(false);
1079
+ await load();
1080
+ } catch (cause) {
1081
+ setError(cause instanceof Error ? cause.message : String(cause));
1082
+ } finally {
1083
+ setCreateBusy(false);
1084
+ }
1085
+ };
1086
+ const aiAccept = async (task) => {
1087
+ setAiBusyId(task.id);
1088
+ setError(null);
1089
+ try {
1090
+ const response = await fetch(ROUTE + "/ai-accept", {
1091
+ method: "POST",
1092
+ headers: { "content-type": "application/json" },
1093
+ body: JSON.stringify({ id: task.id })
1094
+ });
1095
+ const payload = await response.json();
1096
+ if (!response.ok || typeof payload.sessionId !== "string") throw new Error(payload.error ?? "AI 受理会话创建失败");
1097
+ openSession(payload.sessionId);
1098
+ setOpen(false);
1099
+ } catch (cause) {
1100
+ setError(cause instanceof Error ? cause.message : String(cause));
1101
+ } finally {
1102
+ setAiBusyId(null);
1103
+ }
1104
+ };
1105
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [anchor !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1106
+ type: "button",
1107
+ "aria-label": "任务中心",
1108
+ title: "任务中心",
1109
+ onClick: () => setOpen((value) => !value),
1110
+ style: {
1111
+ position: "fixed",
1112
+ left: anchor.left,
1113
+ top: anchor.top,
1114
+ zIndex: 45,
1115
+ width: 28,
1116
+ height: 28,
1117
+ padding: 0,
1118
+ border: "none",
1119
+ borderRadius: 8,
1120
+ background: "transparent",
1121
+ color: actionable > 0 ? colors.accent : colors.muted,
1122
+ cursor: "pointer",
1123
+ pointerEvents: "auto",
1124
+ display: "inline-flex",
1125
+ alignItems: "center",
1126
+ justifyContent: "center"
1127
+ },
1128
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1129
+ width: "17",
1130
+ height: "17",
1131
+ viewBox: "0 0 24 24",
1132
+ fill: "none",
1133
+ stroke: "currentColor",
1134
+ strokeWidth: "1.8",
1135
+ strokeLinecap: "round",
1136
+ strokeLinejoin: "round",
1137
+ "aria-hidden": "true",
1138
+ 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" })]
1139
+ }), pendingCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1140
+ style: {
1141
+ position: "absolute",
1142
+ bottom: -2,
1143
+ right: -2,
1144
+ minWidth: 14,
1145
+ height: 14,
1146
+ padding: "0 3px",
1147
+ boxSizing: "border-box",
1148
+ borderRadius: 999,
1149
+ background: "#2563eb",
1150
+ color: "#fff",
1151
+ fontSize: 9,
1152
+ lineHeight: "14px",
1153
+ textAlign: "center"
1154
+ },
1155
+ children: pendingCount > 99 ? "99+" : pendingCount
1156
+ }) : null]
1157
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1158
+ open,
1159
+ onClose: () => setOpen(false),
1160
+ title: "任务中心",
1161
+ headless: true,
1162
+ className: TASK_BUS_MODAL_CLASS,
1163
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1164
+ style: {
1165
+ display: "flex",
1166
+ flexDirection: "column",
1167
+ width: "100%",
1168
+ height: "100%",
1169
+ minHeight: 0
1170
+ },
1171
+ children: [
1172
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
1173
+ style: {
1174
+ display: "flex",
1175
+ alignItems: "center",
1176
+ gap: 8,
1177
+ padding: "22px 24px 12px"
1178
+ },
1179
+ children: [
1180
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1181
+ style: {
1182
+ flex: 1,
1183
+ minWidth: 0
1184
+ },
1185
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1186
+ style: {
1187
+ margin: 0,
1188
+ fontSize: 16,
1189
+ lineHeight: "24px",
1190
+ fontWeight: 500,
1191
+ color: colors.text
1192
+ },
1193
+ children: "任务中心"
1194
+ })
1195
+ }),
1196
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1197
+ size: "sm",
1198
+ variant: "outline",
1199
+ disabled: !connectorEnabled,
1200
+ onClick: () => setCreating((value) => !value),
1201
+ children: "新建"
1202
+ }),
1203
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1204
+ size: "sm",
1205
+ variant: "ghost",
1206
+ onClick: () => void load(),
1207
+ children: "刷新"
1208
+ }),
1209
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1210
+ size: "sm",
1211
+ variant: "ghost",
1212
+ "aria-label": "关闭",
1213
+ title: "关闭",
1214
+ onClick: () => setOpen(false),
1215
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular, { size: 14 }),
1216
+ style: {
1217
+ width: 28,
1218
+ padding: 0
1219
+ }
1220
+ })
1221
+ ]
1222
+ }),
1223
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1224
+ style: {
1225
+ padding: "0 24px 14px",
1226
+ borderBottom: "1px solid " + colors.line
1227
+ },
1228
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedTabs, {
1229
+ items: tabItems,
1230
+ value: tab,
1231
+ onChange: setTab,
1232
+ label: "任务中心分类"
1233
+ })
1234
+ }),
1235
+ creating ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1236
+ style: {
1237
+ margin: "0 24px 12px",
1238
+ padding: 12,
1239
+ border: "1px solid " + colors.line,
1240
+ borderRadius: 9,
1241
+ display: "grid",
1242
+ gap: 7
1243
+ },
1244
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1245
+ value: draftTitle,
1246
+ onChange: (event) => setDraftTitle(event.target.value),
1247
+ placeholder: "任务标题,例如:整理演示材料",
1248
+ autoFocus: true,
1249
+ style: {
1250
+ ...buttonStyle,
1251
+ cursor: "text",
1252
+ padding: "7px 9px"
1253
+ }
1254
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1255
+ style: {
1256
+ display: "flex",
1257
+ gap: 6
1258
+ },
1259
+ children: [
1260
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1261
+ value: draftSource,
1262
+ onChange: (event) => setDraftSource(event.target.value),
1263
+ style: {
1264
+ ...buttonStyle,
1265
+ padding: "6px 8px"
1266
+ },
1267
+ children: [
1268
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1269
+ value: "manual",
1270
+ children: "手工任务"
1271
+ }),
1272
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1273
+ value: "session",
1274
+ children: "会话任务"
1275
+ }),
1276
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1277
+ value: "group",
1278
+ children: "群聊任务"
1279
+ })
1280
+ ]
1281
+ }),
1282
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1283
+ size: "sm",
1284
+ variant: "primary",
1285
+ disabled: createBusy || draftTitle.trim() === "",
1286
+ onClick: () => void createTask(),
1287
+ children: createBusy ? "创建中…" : "创建"
1288
+ }),
1289
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1290
+ size: "sm",
1291
+ variant: "ghost",
1292
+ onClick: () => setCreating(false),
1293
+ children: "取消"
1294
+ })
1295
+ ]
1296
+ })]
1297
+ }) : null,
1298
+ error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1299
+ style: {
1300
+ padding: "8px 10px",
1301
+ borderRadius: 8,
1302
+ background: "#fef2f2",
1303
+ color: colors.danger,
1304
+ fontSize: 11,
1305
+ margin: "0 24px 12px"
1306
+ },
1307
+ children: error
1308
+ }) : null,
1309
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1310
+ id: "task-bus-panel-" + tab,
1311
+ role: "tabpanel",
1312
+ "aria-labelledby": "task-bus-tab-" + tab,
1313
+ style: {
1314
+ padding: "16px 24px 24px",
1315
+ display: "flex",
1316
+ flexDirection: "column",
1317
+ gap: 10,
1318
+ overflowY: "auto",
1319
+ flex: 1
1320
+ },
1321
+ children: selected !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskDetail, {
1322
+ task: selected,
1323
+ onBack: () => setSelected(null)
1324
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1325
+ tab === "connectors" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1326
+ style: {
1327
+ display: "grid",
1328
+ gridTemplateColumns: "repeat(auto-fill, minmax(280px, 320px))",
1329
+ gap: 10
1330
+ },
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
1336
+ })
1337
+ }) : null,
1338
+ loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1339
+ style: {
1340
+ padding: 18,
1341
+ textAlign: "center",
1342
+ color: colors.muted,
1343
+ fontSize: 12
1344
+ },
1345
+ children: "正在加载…"
1346
+ }) : null,
1347
+ !loading && filtered.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1348
+ style: {
1349
+ padding: 18,
1350
+ textAlign: "center",
1351
+ color: colors.muted,
1352
+ fontSize: 12
1353
+ },
1354
+ children: tab === "connectors" ? connectorEnabled ? "暂无来自该连接器的任务" : "启用连接器后在此显示任务" : "暂无任务"
1355
+ }) : null,
1356
+ !loading && tab === "connectors" && filtered.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1357
+ style: {
1358
+ fontSize: 10,
1359
+ color: colors.muted
1360
+ },
1361
+ children: "来自该连接器的任务"
1362
+ }) : null,
1363
+ !loading && filtered.map((task) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskCard, {
1364
+ task,
1365
+ busy: busyId === task.id,
1366
+ aiBusy: aiBusyId === task.id,
1367
+ onTransition: (item, action) => void transition(item, action),
1368
+ onAiAccept: (item) => void aiAccept(item),
1369
+ onOpen: setSelected
1370
+ }, task.id))
1371
+ ] })
1372
+ })
1373
+ ]
1374
+ })
1375
+ })] });
1376
+ };
1377
+ const HISTORY_CONTENT_TYPES = /* @__PURE__ */ new Set(["user/message", "assistant/message"]);
1378
+ const historyText = (content) => {
1379
+ if (!Array.isArray(content)) return "";
1380
+ return content.map((block) => {
1381
+ if (block === null || typeof block !== "object") return "";
1382
+ const record = block;
1383
+ return record.type === "text" && typeof record.text === "string" ? record.text : "";
1384
+ }).filter(Boolean).join("\n").trim();
1385
+ };
1386
+ const historyRole = (event) => {
1387
+ return event.data?.role === "user" || event.type === "user/message" ? "user" : "assistant";
1388
+ };
1389
+ const historyAuthor = (event, role, t) => {
1390
+ const source = event.data?.source;
1391
+ const memberName = typeof source?.memberName === "string" ? source.memberName.trim() : "";
1392
+ if (memberName !== "") return memberName;
1393
+ return role === "user" ? t("me") : t("assistant");
1394
+ };
1395
+ const groupHistory = async (roomId, t) => {
1396
+ const entries = [];
1397
+ let cursor = null;
1398
+ for (let page = 0; page < 20; page += 1) {
1399
+ const suffix = cursor === null ? "" : "?cursor=" + encodeURIComponent(cursor);
1400
+ const response = await fetch("/dsh-group-chat/api/rooms/" + encodeURIComponent(roomId) + "/events" + suffix);
1401
+ if (!response.ok) throw new Error(t("error"));
1402
+ const payload = await response.json();
1403
+ for (const record of payload.events ?? []) {
1404
+ const event = record.event;
1405
+ if (event === void 0 || typeof event.type !== "string" || !HISTORY_CONTENT_TYPES.has(event.type)) continue;
1406
+ const data = event.data;
1407
+ const role = historyRole(event);
1408
+ const text = historyText(data?.content);
1409
+ if (text === "") continue;
1410
+ entries.push({
1411
+ key: record.id ?? String(event.seq ?? entries.length),
1412
+ role,
1413
+ author: historyAuthor(event, role, t),
1414
+ time: typeof event.time === "number" ? event.time : void 0,
1415
+ text,
1416
+ jumpId: record.id
1417
+ });
1418
+ }
1419
+ if (payload.hasMore !== true || payload.nextCursor === null || payload.nextCursor === void 0) break;
1420
+ cursor = payload.nextCursor;
1421
+ }
1422
+ return entries.sort((left, right) => (right.time ?? 0) - (left.time ?? 0));
1423
+ };
1424
+ const sessionHistory = async (sessionId, sessions, t) => {
1425
+ const service = sessions;
1426
+ if (service.retain === void 0) return [];
1427
+ const reference = service.retain(sessionId, { source: "gateway" });
1428
+ try {
1429
+ await reference.ready;
1430
+ const binding = reference.binding;
1431
+ if (binding === void 0) return [];
1432
+ await binding.session?.loadOlder?.().catch(() => void 0);
1433
+ const entries = binding.eventSource?.getSnapshot().entries ?? [];
1434
+ 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) => {
1435
+ const event = entry.event;
1436
+ const data = event.data;
1437
+ const role = historyRole(event);
1438
+ return {
1439
+ key: String(event.seq ?? entries.indexOf(entry)),
1440
+ role,
1441
+ author: historyAuthor(event, role, t),
1442
+ time: typeof event.time === "number" ? event.time : void 0,
1443
+ text: historyText(data?.content)
1444
+ };
1445
+ }).filter((entry) => entry.text !== "").sort((left, right) => (right.time ?? 0) - (left.time ?? 0));
1446
+ } finally {
1447
+ reference.release?.();
1448
+ }
1449
+ };
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
+ const fileAttachmentIn = (content, name) => {
1479
+ if (!Array.isArray(content)) return void 0;
1480
+ for (const block of content) {
1481
+ if (block === null || typeof block !== "object") continue;
1482
+ const record = block;
1483
+ if (record.type !== "file") continue;
1484
+ const attachment = record.attachment;
1485
+ if (attachment?.name === name && typeof attachment.attachmentId === "string" && typeof attachment.bytes === "number") return {
1486
+ attachmentId: attachment.attachmentId,
1487
+ name,
1488
+ bytes: attachment.bytes
1489
+ };
1490
+ }
1491
+ };
1492
+ const sessionFileAttachment = async (sessionId, sessions, name) => {
1493
+ const service = sessions;
1494
+ if (service.retain === void 0) return void 0;
1495
+ const reference = service.retain(sessionId, { source: "gateway" });
1496
+ try {
1497
+ await reference.ready;
1498
+ const binding = reference.binding;
1499
+ if (binding === void 0) return void 0;
1500
+ const find = () => {
1501
+ const entries = binding.eventSource?.getSnapshot().entries ?? [];
1502
+ for (const entry of entries) {
1503
+ if (entry.type !== "event" || entry.event === void 0) continue;
1504
+ if (entry.event.type !== "user/message") continue;
1505
+ const data = entry.event.data;
1506
+ const found = fileAttachmentIn(data?.content, name);
1507
+ if (found !== void 0) return found;
1508
+ }
1509
+ };
1510
+ let found = find();
1511
+ for (let page = 0; page < 5 && found === void 0; page += 1) {
1512
+ await binding.session?.loadOlder?.().catch(() => void 0);
1513
+ found = find();
1514
+ }
1515
+ return found;
1516
+ } finally {
1517
+ reference.release?.();
1518
+ }
1519
+ };
1520
+ const HistoryButton = ({ sessionId, sessions, t }) => {
1521
+ const [open, setOpen] = (0, react.useState)(false);
1522
+ const [entries, setEntries] = (0, react.useState)([]);
1523
+ const [loading, setLoading] = (0, react.useState)(false);
1524
+ const [error, setError] = (0, react.useState)("");
1525
+ const [query, setQuery] = (0, react.useState)("");
1526
+ const isGroup = sessionId.startsWith("group-room-");
1527
+ (0, react.useEffect)(() => {
1528
+ if (!open) return;
1529
+ let alive = true;
1530
+ setLoading(true);
1531
+ setError("");
1532
+ setQuery("");
1533
+ setEntries([]);
1534
+ (isGroup ? groupHistory(sessionId.slice(11), t) : sessionHistory(sessionId, sessions, t)).then((items) => {
1535
+ if (alive) setEntries(items);
1536
+ }).catch((cause) => {
1537
+ if (alive) setError(cause instanceof Error ? cause.message : String(cause));
1538
+ }).finally(() => {
1539
+ if (alive) setLoading(false);
1540
+ });
1541
+ return () => {
1542
+ alive = false;
1543
+ };
1544
+ }, [
1545
+ open,
1546
+ isGroup,
1547
+ sessionId,
1548
+ sessions,
1549
+ t
1550
+ ]);
1551
+ const needle = query.trim().toLocaleLowerCase();
1552
+ const results = (0, react.useMemo)(() => {
1553
+ if (needle === "") return [];
1554
+ return entries.filter((entry) => entry.text.toLocaleLowerCase().includes(needle) || entry.author.toLocaleLowerCase().includes(needle));
1555
+ }, [entries, needle]);
1556
+ const jump = (entry) => {
1557
+ setOpen(false);
1558
+ window.setTimeout(() => {
1559
+ let target = entry.jumpId === void 0 ? null : document.querySelector("[data-group-chat-message=\"" + CSS.escape(entry.jumpId) + "\"]");
1560
+ if (target === null) {
1561
+ const text = entry.text.slice(0, 80);
1562
+ target = [...document.querySelectorAll("[data-group-chat-message], [data-slot=\"conversation.user.text.node\"]")].find((element) => (element.innerText ?? "").includes(text)) ?? null;
1563
+ }
1564
+ if (target === null && window.find(entry.text.slice(0, 80), false, false, true, false, false, false)) {
1565
+ const node = window.getSelection()?.anchorNode;
1566
+ target = node instanceof HTMLElement ? node : node?.parentElement ?? null;
1567
+ }
1568
+ target?.scrollIntoView({
1569
+ behavior: "smooth",
1570
+ block: "center"
1571
+ });
1572
+ if (target !== null) {
1573
+ target.style.outline = "2px solid #2563eb";
1574
+ target.style.outlineOffset = "4px";
1575
+ window.setTimeout(() => {
1576
+ target.style.outline = "";
1577
+ target.style.outlineOffset = "";
1578
+ }, 1800);
1579
+ }
1580
+ }, 80);
1581
+ };
1582
+ const searchInputStyle = {
1583
+ width: "100%",
1584
+ boxSizing: "border-box",
1585
+ height: 36,
1586
+ padding: "0 34px 0 12px",
1587
+ borderRadius: 8,
1588
+ border: "1px solid " + colors.line,
1589
+ background: colors.panel,
1590
+ color: colors.text,
1591
+ fontSize: 13,
1592
+ outline: "none"
1593
+ };
1594
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1595
+ type: "button",
1596
+ "aria-label": t("open"),
1597
+ title: t("open"),
1598
+ "data-session-id": sessionId,
1599
+ onClick: () => setOpen(true),
1600
+ style: {
1601
+ display: "inline-flex",
1602
+ alignItems: "center",
1603
+ justifyContent: "center",
1604
+ width: 28,
1605
+ height: 28,
1606
+ border: "none",
1607
+ borderRadius: 6,
1608
+ background: "transparent",
1609
+ color: "inherit",
1610
+ cursor: "pointer"
1611
+ },
1612
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HistoryGlyph, {})
1613
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1614
+ open,
1615
+ onClose: () => setOpen(false),
1616
+ title: t("title"),
1617
+ closeLabel: t("close"),
1618
+ className: TASK_BUS_HISTORY_CLASS,
1619
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1620
+ style: {
1621
+ width: "100%",
1622
+ maxHeight: "min(68vh, 620px)",
1623
+ overflowY: "auto",
1624
+ display: "grid",
1625
+ gap: 10,
1626
+ padding: "4px 0 8px"
1627
+ },
1628
+ children: [
1629
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1630
+ style: {
1631
+ position: "relative",
1632
+ width: "100%"
1633
+ },
1634
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1635
+ type: "text",
1636
+ value: query,
1637
+ autoFocus: true,
1638
+ spellCheck: false,
1639
+ placeholder: t("searchPlaceholder"),
1640
+ "aria-label": t("searchLabel"),
1641
+ onChange: (event) => {
1642
+ setQuery(event.target.value);
1643
+ },
1644
+ style: searchInputStyle
1645
+ }), query !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1646
+ type: "button",
1647
+ "aria-label": t("clear"),
1648
+ title: t("clear"),
1649
+ onClick: () => {
1650
+ setQuery("");
1651
+ },
1652
+ style: {
1653
+ position: "absolute",
1654
+ right: 6,
1655
+ top: "50%",
1656
+ transform: "translateY(-50%)",
1657
+ display: "inline-flex",
1658
+ alignItems: "center",
1659
+ justifyContent: "center",
1660
+ width: 24,
1661
+ height: 24,
1662
+ border: "none",
1663
+ borderRadius: 5,
1664
+ background: "transparent",
1665
+ color: colors.muted,
1666
+ cursor: "pointer"
1667
+ },
1668
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular, { size: 14 })
1669
+ }) : null]
1670
+ }),
1671
+ loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1672
+ style: {
1673
+ padding: 28,
1674
+ textAlign: "center",
1675
+ color: colors.muted,
1676
+ fontSize: 12
1677
+ },
1678
+ children: t("loading")
1679
+ }) : null,
1680
+ !loading && error !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1681
+ role: "alert",
1682
+ style: {
1683
+ padding: 12,
1684
+ color: colors.danger,
1685
+ fontSize: 12
1686
+ },
1687
+ children: error
1688
+ }) : null,
1689
+ !loading && error === "" && needle === "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1690
+ style: {
1691
+ display: "grid",
1692
+ justifyItems: "center",
1693
+ gap: 8,
1694
+ padding: "36px 20px 40px",
1695
+ color: colors.muted,
1696
+ textAlign: "center"
1697
+ },
1698
+ children: [
1699
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1700
+ style: {
1701
+ display: "inline-flex",
1702
+ width: 36,
1703
+ height: 36,
1704
+ alignItems: "center",
1705
+ justifyContent: "center",
1706
+ borderRadius: 18,
1707
+ background: colors.panelAlt,
1708
+ color: colors.muted
1709
+ },
1710
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HistoryGlyph, {})
1711
+ }),
1712
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1713
+ style: {
1714
+ fontSize: 13,
1715
+ fontWeight: 600,
1716
+ color: colors.text
1717
+ },
1718
+ children: t("initial")
1719
+ }),
1720
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1721
+ style: {
1722
+ fontSize: 12,
1723
+ lineHeight: 1.6
1724
+ },
1725
+ children: t("initialHint")
1726
+ })
1727
+ ]
1728
+ }) : null,
1729
+ !loading && error === "" && needle !== "" && results.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1730
+ style: {
1731
+ padding: 28,
1732
+ textAlign: "center",
1733
+ color: colors.muted,
1734
+ fontSize: 12
1735
+ },
1736
+ children: entries.length === 0 ? t("empty") : t("noResults")
1737
+ }) : null,
1738
+ !loading && error === "" && needle !== "" && results.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1739
+ type: "button",
1740
+ onClick: () => jump(entry),
1741
+ style: {
1742
+ display: "grid",
1743
+ gap: 6,
1744
+ width: "100%",
1745
+ minWidth: 0,
1746
+ textAlign: "left",
1747
+ padding: "10px 12px",
1748
+ borderRadius: 8,
1749
+ border: "1px solid " + colors.line,
1750
+ background: colors.panelAlt,
1751
+ color: colors.text,
1752
+ cursor: "pointer"
1753
+ },
1754
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1755
+ style: {
1756
+ display: "flex",
1757
+ alignItems: "center",
1758
+ gap: 8,
1759
+ minWidth: 0,
1760
+ flexWrap: "wrap",
1761
+ fontSize: 10,
1762
+ color: colors.muted
1763
+ },
1764
+ children: [
1765
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1766
+ style: {
1767
+ fontWeight: 650,
1768
+ color: entry.role === "user" ? "#2563eb" : colors.text
1769
+ },
1770
+ children: entry.author
1771
+ }),
1772
+ entry.time === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: new Date(entry.time).toLocaleString(t("dateLocale")) }),
1773
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1774
+ style: { marginLeft: "auto" },
1775
+ children: t("jump")
1776
+ })
1777
+ ]
1778
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1779
+ style: {
1780
+ display: "block",
1781
+ minWidth: 0,
1782
+ fontSize: 13,
1783
+ lineHeight: 1.65,
1784
+ whiteSpace: "pre-wrap",
1785
+ overflowWrap: "anywhere",
1786
+ wordBreak: "break-word"
1787
+ },
1788
+ children: entry.text
1789
+ })]
1790
+ }, entry.key))
1791
+ ]
1792
+ })
1793
+ })] });
1794
+ };
1795
+ const apply = (ctx) => {
1796
+ const locale = ctx.locale;
1797
+ ctx.effect(() => locale.register(HISTORY_NS, {
1798
+ zh: HISTORY_COPY.zh,
1799
+ en: HISTORY_COPY.en
1800
+ }), "task-bus: history copy");
1801
+ const t = locale.bind(HISTORY_NS);
1802
+ const surface = ctx;
1803
+ const surfaceWithServices = surface;
1804
+ surfaceWithServices.sidebarRight ?? surfaceWithServices.get?.("sidebarRight");
1805
+ ctx.effect(() => {
1806
+ const onFileClick = (event) => {
1807
+ const target = event.target;
1808
+ if (!(target instanceof Element)) return;
1809
+ const card = target.closest("[data-message-attachments] > *");
1810
+ if (!(card instanceof HTMLElement)) return;
1811
+ const sessionId = document.querySelector("button[aria-label=\"消息记录\"][data-session-id]")?.getAttribute("data-session-id") ?? "";
1812
+ if (sessionId === "" || sessionId.startsWith("group-room-")) return;
1813
+ const name = (card.getAttribute("title") ?? card.innerText.split("\n")[0] ?? "").trim();
1814
+ if (name === "") return;
1815
+ (async () => {
1816
+ const attachment = await sessionFileAttachment(sessionId, ctx.sessions, name);
1817
+ if (attachment === void 0) return;
1818
+ const cwd = (ctx.sessions?.list?.getSnapshot())?.byId?.[sessionId]?.cwd ?? "";
1819
+ const query = "attachmentId=" + encodeURIComponent(attachment.attachmentId) + "&name=" + encodeURIComponent(attachment.name) + "&bytes=" + encodeURIComponent(String(attachment.bytes)) + (cwd === "" ? "" : "&cwd=" + encodeURIComponent(cwd));
1820
+ const response = await fetch(ROUTE + "/attachment-materialize?" + query);
1821
+ if (!response.ok) throw new Error("附件预览准备失败");
1822
+ const materialized = await response.json();
1823
+ if (typeof materialized.path !== "string") throw new Error("附件预览路径缺失");
1824
+ const address = "dsh-resource://file/session/" + encodeURIComponent(sessionId) + "/" + materialized.path.split("/").map((part) => encodeURIComponent(part)).join("/");
1825
+ const right = ctx.sidebarRight;
1826
+ if (right?.openResource !== void 0) right.openResource(address);
1827
+ else window.open(new URL("/dsh-task-bus/api/attachment-materialize?" + query, window.location.origin).toString(), "_blank", "noopener,noreferrer");
1828
+ })().catch((cause) => {
1829
+ console.error("[task-bus] file preview failed", cause);
1830
+ });
1831
+ };
1832
+ document.addEventListener("click", onFileClick, true);
1833
+ return () => {
1834
+ document.removeEventListener("click", onFileClick, true);
1835
+ };
1836
+ }, "task-bus: native file card preview");
1837
+ const Surface = () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskCenterButton, { openSession: (sessionId) => surface.uiWorkspace.openSession(sessionId) });
1838
+ ctx.effect(() => surface.slots.inject("shell.overlay", () => surface.slots.register({
1839
+ name: "shell.overlay",
1840
+ id: "task-bus.center"
1841
+ }, Surface)), "task-bus: Task Center overlay");
1842
+ ctx.effect(() => {
1843
+ const style = document.createElement("style");
1844
+ style.id = "task-bus-history-replaces-file-manager";
1845
+ style.textContent = "button[aria-label=\"用 文件管理器 打开\"] { display: none !important; }\n[data-message-attachments] > * { cursor: pointer; }";
1846
+ document.head.appendChild(style);
1847
+ return () => {
1848
+ style.remove();
1849
+ };
1850
+ }, "task-bus: replace file manager header button");
1851
+ ctx.effect(() => surface.slots.inject("conversation.session.header.utilities", () => surface.slots.register({
1852
+ name: "conversation.session.header.utilities",
1853
+ id: "task-bus.history",
1854
+ order: -4,
1855
+ inject: ((sessionId) => ({
1856
+ sessionId,
1857
+ sessions: ctx.sessions,
1858
+ t
1859
+ }))
1860
+ }, HistoryButton)), "task-bus: message history");
1861
+ };
1862
+ //#endregion
1863
+ exports.apply = apply;
1864
+ exports.inject = inject;
1865
+ return module.exports;
1866
+ }
1867
+ });
1868
+
1869
+ //# sourceMappingURL=client.js.map