@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/README.md +32 -2
- package/cordis.patch.yml +3 -0
- package/lib/client.js +2332 -0
- package/lib/client.js.map +1 -0
- package/lib/index.d.ts +19 -0
- package/lib/index.js +485 -0
- package/package.json +87 -4
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
|