@zzusp/dingtalk-dsh-observer 0.5.14 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/web-client.js +557 -62
package/package.json
CHANGED
package/web-client.js
CHANGED
|
@@ -4,7 +4,7 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
const module = { exports: {} }
|
|
5
5
|
const React = require('react')
|
|
6
6
|
const { Button, IconChecklistOutline14, IconChevronDownOutline14, IconChevronUpOutline14, Menu, Pill, StateDot } = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
7
|
-
const { useCallback, useEffect, useState } = React
|
|
7
|
+
const { useCallback, useEffect, useState, useRef } = React
|
|
8
8
|
const ENDPOINT = 'http://127.0.0.1:18998'
|
|
9
9
|
const colors = {
|
|
10
10
|
surface: 'var(--dsw-alias-bg-base, #fff)',
|
|
@@ -54,7 +54,10 @@ window.__ModuleLoader__.load({
|
|
|
54
54
|
const short = (value) => value ? String(value).replace(/^session-/, '').slice(0, 14) : '—'
|
|
55
55
|
const outboxDelivery = (message) => {
|
|
56
56
|
if (message.recallStatus === 'recalled') return { id: 'recalled', label: '已撤回', state: 'neutral' }
|
|
57
|
+
if (message.recallStatus === 'failed') return { id: 'recall-failed', label: '撤回待处理', state: 'warning', detail: message.recallError === 'replacement_delivery_unknown' ? '已停止旧意图发送;历史是否送达仍未知,等待核验' : `旧消息撤回失败${message.recallRetryAt ? ',将有限重试' : ',需核验后处理'}:${message.recallError || '原因未记录'}` }
|
|
58
|
+
if (message.supersededByOutboundId || message.status === 'superseded') return { id: 'superseded', label: '已替代', state: 'neutral', detail: message.deliveredMessageId ? '已记录送达,等待替换通知送达后撤回' : '已停止旧意图发送;未声称历史消息未送达' }
|
|
57
59
|
if (message.status === 'sent') return { id: 'confirmed', label: message.deliveredMessageId ? '已回读' : '已发送', state: 'done' }
|
|
60
|
+
if (message.deliveryBlockedAt) return { id: 'failed', label: '发送待处理', state: 'error', detail: '服务端已拒绝发送,停止自动重试;需核验失败原因' }
|
|
58
61
|
const reason = message.deliveryPendingReason
|
|
59
62
|
const details = {
|
|
60
63
|
preflight_history_partial: '发送前检查:历史消息不完整,本次未发送',
|
|
@@ -71,17 +74,46 @@ window.__ModuleLoader__.load({
|
|
|
71
74
|
if (message.deliveryError || reason === 'send_failed') return { id: 'failed', label: '投递异常', state: 'error', detail }
|
|
72
75
|
return { id: 'queued', label: '待发送', state: 'ongoing', detail }
|
|
73
76
|
}
|
|
74
|
-
const
|
|
75
|
-
const
|
|
77
|
+
const ui = { textSm: 12, textMd: 14, textLg: 16, textXl: 20, space1: 4, space2: 8, space3: 12, space4: 16, space6: 24, radiusSm: 8, radiusMd: 12 }
|
|
78
|
+
const observerCss = `
|
|
79
|
+
[data-dingtalk-observer] :is(button, input, select, textarea, summary, [tabindex]):focus-visible {
|
|
80
|
+
outline: 2px solid var(--dsw-alias-brand-primary, #4d6bfe) !important;
|
|
81
|
+
outline-offset: 2px !important;
|
|
82
|
+
}
|
|
83
|
+
[data-dingtalk-observer] .observer-task-step { position: relative; display: grid; grid-template-columns: 28px minmax(0, 1fr); gap: 12px; padding-bottom: 24px; }
|
|
84
|
+
[data-dingtalk-observer] .observer-task-step:not(:last-child)::before { content: ''; position: absolute; left: 13px; top: 28px; bottom: 0; border-left: 1px solid var(--dsw-alias-border-primary, rgba(127,127,127,.25)); }
|
|
85
|
+
[data-dingtalk-observer] .observer-task-step-heading { display: flex; align-items: baseline; justify-content: space-between; gap: 8px 16px; flex-wrap: wrap; min-height: 28px; }
|
|
86
|
+
[data-dingtalk-observer] .observer-task-output-row { display: grid; grid-template-columns: 112px minmax(0, 1fr); gap: 12px; }
|
|
87
|
+
[data-dingtalk-observer] .observer-task-overview { padding: 20px 0; }
|
|
88
|
+
[data-dingtalk-observer] .observer-task-result { padding: 24px; }
|
|
89
|
+
@media (max-width: 960px) {
|
|
90
|
+
[data-dingtalk-observer] .observer-task-output-row { grid-template-columns: minmax(0, 1fr); gap: 2px; }
|
|
91
|
+
[data-dingtalk-observer] .observer-task-overview { padding: 20px 0; }
|
|
92
|
+
[data-dingtalk-observer] .observer-task-result { padding: 18px; }
|
|
93
|
+
[data-dingtalk-observer] .observer-main { padding: 16px !important; }
|
|
94
|
+
[data-dingtalk-observer] .observer-topics-layout { display: grid !important; grid-template-columns: minmax(280px, 2fr) minmax(0, 3fr); }
|
|
95
|
+
[data-dingtalk-observer] .observer-topic-list { max-width: none !important; }
|
|
96
|
+
[data-dingtalk-observer] .observer-task-board { grid-template-columns: repeat(2, minmax(280px, 1fr)) !important; }
|
|
97
|
+
}
|
|
98
|
+
@media (max-width: 720px) {
|
|
99
|
+
[data-dingtalk-observer] .observer-main { padding: 12px !important; }
|
|
100
|
+
[data-dingtalk-observer] .observer-topics-layout,
|
|
101
|
+
[data-dingtalk-observer] .observer-task-board { grid-template-columns: minmax(0, 1fr) !important; }
|
|
102
|
+
[data-dingtalk-observer] .observer-topic-detail { min-height: 320px !important; }
|
|
103
|
+
[data-dingtalk-observer] .observer-toolbar { align-items: stretch !important; }
|
|
104
|
+
}
|
|
105
|
+
`
|
|
106
|
+
const pill = (tone) => ({ display: 'inline-flex', alignItems: 'center', borderRadius: 999, padding: `${ui.space1}px ${ui.space2}px`, fontSize: ui.textSm, color: tone, background: `color-mix(in srgb, ${tone} 12%, transparent)` })
|
|
107
|
+
const card = { border: `1px solid ${colors.border}`, borderRadius: ui.radiusMd, background: colors.cardSurface, padding: ui.space4 }
|
|
76
108
|
const tableFrame = { border: `1px solid ${colors.border}`, borderRadius: 12, background: colors.cardSurface, overflow: 'hidden' }
|
|
77
|
-
const tableHeadCell = { height: 42, boxSizing: 'border-box', padding:
|
|
78
|
-
const tableBodyCell = { padding:
|
|
109
|
+
const tableHeadCell = { height: 42, boxSizing: 'border-box', padding: `0 ${ui.space4}px`, color: colors.muted, fontSize: ui.textSm, fontWeight: 600, verticalAlign: 'middle' }
|
|
110
|
+
const tableBodyCell = { padding: `${ui.space3}px ${ui.space4}px`, borderTop: `1px solid ${colors.border}`, fontSize: ui.textMd, lineHeight: 1.55, verticalAlign: 'middle' }
|
|
79
111
|
const tableBodyContent = { display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', overflowWrap: 'anywhere' }
|
|
80
112
|
const clampTableContent = (...children) => React.createElement('div', { style: tableBodyContent }, ...children)
|
|
81
113
|
const singleLineTableContent = (...children) => React.createElement('div', { style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, ...children)
|
|
82
114
|
const tableFooter = { minHeight: 48, boxSizing: 'border-box', display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: 8, padding: '8px 12px', borderTop: `1px solid ${colors.border}`, background: colors.surface2 }
|
|
83
115
|
const toolbar = { minHeight: 48, boxSizing: 'border-box', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, padding: '8px 12px', borderBottom: `1px solid ${colors.border}`, background: colors.cardSurface }
|
|
84
|
-
const navigationTabStyle = (selected, { paddingBottom =
|
|
116
|
+
const navigationTabStyle = (selected, { paddingBottom = 12 } = {}) => ({ position: 'relative', boxSizing: 'border-box', border: 0, borderBottom: selected ? `2px solid ${colors.accent}` : '2px solid transparent', borderRadius: 0, padding: `0 0 ${paddingBottom}px`, cursor: 'pointer', font: 'inherit', fontSize: ui.textMd, fontWeight: 500, lineHeight: '20px', color: selected ? colors.accent : colors.muted, background: 'transparent', whiteSpace: 'nowrap' })
|
|
85
117
|
const statusTone = {
|
|
86
118
|
done: 'var(--dsw-alias-state-success-primary, #248a3d)',
|
|
87
119
|
warning: 'var(--dsw-alias-state-warn-primary, #a56500)',
|
|
@@ -100,7 +132,11 @@ window.__ModuleLoader__.load({
|
|
|
100
132
|
const anchor = React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', 'aria-label': label, 'aria-expanded': open, onClick: () => setOpen((current) => !current), style: { minWidth: fitContent ? 0 : minWidth, width: fitContent ? 'fit-content' : undefined, maxWidth: fitContent ? 220 : undefined, justifyContent: fitContent ? 'flex-start' : 'space-between', gap: fitContent ? 8 : 16, fontWeight: 400 } }, React.createElement('span', { style: { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, selected?.label || value), React.createElement('span', { 'aria-hidden': true, style: { flex: '0 0 auto', color: colors.muted, fontSize: 9, transform: open ? 'rotate(180deg)' : 'none', transition: 'transform .15s ease' } }, '▼'))
|
|
101
133
|
return React.createElement(Menu, { open, anchor, items: options, selectedId: value, onSelect: (id) => { onChange(id); setOpen(false) }, onClose: () => setOpen(false), align: 'end', portal: true, dense: true, compact: true })
|
|
102
134
|
}
|
|
103
|
-
const emptyState = (
|
|
135
|
+
const emptyState = (title, { detail, actionLabel, onAction } = {}) => React.createElement('div', { style: { minHeight: 112, display: 'grid', placeItems: 'center', padding: ui.space4, textAlign: 'center', color: colors.muted, fontSize: ui.textSm, border: `1px dashed ${colors.border}`, borderRadius: ui.radiusSm, background: colors.surface2 } },
|
|
136
|
+
React.createElement('div', { style: { display: 'grid', justifyItems: 'center', gap: ui.space2 } },
|
|
137
|
+
React.createElement('strong', { style: { color: 'inherit', fontSize: ui.textMd, fontWeight: 600 } }, title),
|
|
138
|
+
detail ? React.createElement('span', null, detail) : null,
|
|
139
|
+
actionLabel && onAction ? React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', onClick: onAction }, actionLabel) : null))
|
|
104
140
|
async function get(path) {
|
|
105
141
|
const response = await fetch(`${ENDPOINT}${path}`, { method: 'GET', headers: { accept: 'application/json' } })
|
|
106
142
|
const value = await response.json()
|
|
@@ -135,16 +171,220 @@ window.__ModuleLoader__.load({
|
|
|
135
171
|
{ state: 'queued', label: '待执行', tone: 'var(--dsw-alias-label-secondary, #64748b)', background: 'color-mix(in srgb, var(--dsw-alias-label-secondary, #64748b) 8%, var(--dsw-alias-bg-layer-1, #fff))' },
|
|
136
172
|
{ state: 'running', label: '执行中', tone: colors.accent, background: 'color-mix(in srgb, var(--dsw-alias-brand-primary, #4d6bfe) 8%, var(--dsw-alias-bg-layer-1, #fff))' },
|
|
137
173
|
{ state: 'waiting', label: '等待中', tone: colors.warning, background: 'color-mix(in srgb, var(--dsw-alias-state-warn-primary, #a56500) 8%, var(--dsw-alias-bg-layer-1, #fff))' },
|
|
138
|
-
{ state: 'completed', label: '
|
|
174
|
+
{ state: 'completed', label: '已结束', tone: 'var(--dsw-alias-state-success-primary, #248a3d)', background: 'color-mix(in srgb, var(--dsw-alias-state-success-primary, #248a3d) 8%, var(--dsw-alias-bg-layer-1, #fff))' }
|
|
139
175
|
]
|
|
140
|
-
const pages = [{ id: 'groups', label: '
|
|
141
|
-
|
|
176
|
+
const pages = [{ id: 'groups', label: '群消息' }, { id: 'topics', label: '话题' }, { id: 'tasks', label: '任务看板' }, { id: 'authorizations', label: '人工介入' }, { id: 'archive', label: '归档任务' }, { id: 'alerts', label: '告警' }]
|
|
177
|
+
const traceStatus = (status) => ({ succeeded: '已完成', applied: '已接纳', settled: '处理已结束', processed: '处理已结束', running: '处理中', claimed: '处理中', pending: '等待处理', queued: '等待处理', waiting: '等待继续', waiting_user: '待补充', interrupted: '待恢复', cancelling: '正在停止', ready: '等待执行', needs_attention: '需要处理', failed: '失败', blocked: '已阻塞', superseded: '已被新消息替代', cancelled: '已取消', rejected: '未接纳', accepted: '已接纳' }[status] || '状态未记录')
|
|
178
|
+
function TaskTotalElapsed({ task }) {
|
|
179
|
+
const [now, setNow] = useState(Date.now)
|
|
180
|
+
const ended = task.state === 'completed'
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
if (ended || !task.createdAt) return
|
|
183
|
+
setNow(Date.now())
|
|
184
|
+
const timer = setInterval(() => setNow(Date.now()), 1000)
|
|
185
|
+
return () => clearInterval(timer)
|
|
186
|
+
}, [task.taskId, task.createdAt, ended])
|
|
187
|
+
const elapsed = traceElapsed({ startedAt: task.createdAt, completedAt: task.updatedAt,
|
|
188
|
+
status: ended ? 'succeeded' : 'running' }, now)
|
|
189
|
+
const timing = task.executionTiming
|
|
190
|
+
const executionMs = timing?.elapsedMs + (timing?.running && !ended ? Math.max(0, now - Date.parse(timing.sampledAt)) : 0)
|
|
191
|
+
const execution = Number.isFinite(executionMs) ? traceElapsed({ startedAt: new Date(0).toISOString(),
|
|
192
|
+
completedAt: new Date(executionMs).toISOString(), status: 'succeeded' }, now).replace(/^耗时 /, '') : '未记录'
|
|
193
|
+
return React.createElement('div', { style: { marginLeft: 'auto', display: 'flex', flexWrap: 'wrap', justifyContent: 'flex-end', gap: '4px 16px', color: colors.muted, fontVariantNumeric: 'tabular-nums' } },
|
|
194
|
+
React.createElement('span', { title: '累计业务步骤实际执行时间,包含重试;排除排队及等待,并行区间只计一次,不含消息判断和任务负责会话' },
|
|
195
|
+
`累计执行时长 ${execution}${timing?.complete === false ? '(记录不完整)' : ''}`),
|
|
196
|
+
React.createElement('span', {
|
|
197
|
+
title: ended ? '从任务创建至终态记录更新时间,包含排队、等待与重试' : '从任务创建至现在,包含排队、等待与重试',
|
|
198
|
+
style: { marginLeft: 'auto', textAlign: 'right', color: colors.muted, fontVariantNumeric: 'tabular-nums' },
|
|
199
|
+
}, elapsed === '耗时未记录' ? '累计总耗时未记录' : `累计总耗时 ${elapsed.replace(/^(?:已用时|耗时) /, '')}`))
|
|
200
|
+
}
|
|
201
|
+
const nodeTitle = { 'prepare-merge-main': '准备合并主分支', 'execute-merge-main': '合并主分支', 'prepare-verify-main-merge': '准备核验主分支合并', 'execute-verify-main-merge': '核验主分支合并', 'inspect-merged': '回读合并结果', 'prepare-approval-gate': '准备审批', 'execute-approval-gate': '等待真人审批', 'inspect-approved': '回读审批结果', 'prepare-tag': '准备发布标签', 'execute-tag': '创建发布标签', 'inspect-tagged': '回读标签结果', 'prepare-build': '准备构建', 'execute-build': '执行构建', 'inspect-built': '核对构建产物', 'inspect-runtime': '核对运行版本', investigate: '调查与分析', 'accept-result': '校验调查结果', execute: '执行任务', 'prepare-rebuild': '准备重新构建', 'execute-rebuild': '执行重新构建', 'prepare-workspace': '创建独立工作目录', 'read-files': '读取相关文件', 'propose-changes': '编写修改方案', 'inspect-and-propose': '编写修改方案', 'validate-proposal': '检查修改方案', 'apply-changes': '按方案修改文件', 'verify-candidate': '构建检查', 'business-acceptance': '业务验收', 'define-local-acceptance': '核对本地验收条件', 'plan-local-acceptance': '编写本地验收用例', 'prepare-local-acceptance': '准备本地验收环境', 'run-local-acceptance': '启动服务并执行本地验收', 'finalize-local-acceptance': '核对验收与清理结果', 'index-files': '索引可修改文件', 'select-files': '选择改动文件', 'validate-selection': '检查文件范围', 'prepare-commit': '检查提交条件', commit: '提交代码', 'prepare-push': '检查推送条件', push: '推送代码', 'prepare-pr': '编写合并请求', 'create-pr': '创建合并请求', 'prepare-generation': '确认项目与修改起点', prepare: '校验输入', assess: '审查材料', analyze: '分析材料', 'validate-result': '校验结果', 'inspect-preflight': '核对合并条件', 'prepare-merge': '准备合并', 'execute-merge': '执行合并', 'verify-source': '回读合并结果', 'freeze-target': '冻结目标', 'freeze-input': '冻结输入', 'propose-sql': '编写 SQL 候选', 'validate-package': '校验变更包', 'prepare-rehearsal': '核对 UAT 演练条件', 'run-rehearsal': '在 UAT 数据库演练', 'readback-rehearsal': '回读 UAT 演练', 'prepare-issue': '准备工单', 'create-issue': '提交工单', 'readback-issue': '回读工单', 'approval-gate': '等待真人审批', 'prepare-execute': '准备执行', 'execute-task': '执行受控任务', 'readback-production': '生产只读回查', finalize: '核对交付结果' }
|
|
202
|
+
const taskDisplayTitle = task => {
|
|
203
|
+
const text = String(task.title || task.objective || '未命名任务').replace(/\s+/gu, ' ').trim() || '未命名任务'
|
|
204
|
+
const first = text.split(/[。!?\n]/u)[0]
|
|
205
|
+
if (Array.from(first).length >= 8 && Array.from(first).length <= 32) return first
|
|
206
|
+
const characters = Array.from(text)
|
|
207
|
+
return characters.length <= 32 ? text : `${characters.slice(0, 31).join('')}…`
|
|
208
|
+
}
|
|
209
|
+
const taskStepStart = node => Number.isFinite(Date.parse(node.startedAt)) ? `开始 ${fmt(node.startedAt)}`
|
|
210
|
+
: !node.startedAt && ['pending', 'ready', 'blocked'].includes(node.status) ? '未开始' : '开始时间未记录'
|
|
211
|
+
const groupTaskSteps = nodes => {
|
|
212
|
+
const groups = new Map()
|
|
213
|
+
nodes.forEach((node, index) => {
|
|
214
|
+
const key = node.stageId || node.runId || 'current'
|
|
215
|
+
if (!groups.has(key)) groups.set(key, { key, title: node.stageTitle || '当前工作流', steps: [] })
|
|
216
|
+
groups.get(key).steps.push({ node, index })
|
|
217
|
+
})
|
|
218
|
+
return [...groups.values()]
|
|
219
|
+
}
|
|
220
|
+
function TaskStepElapsed({ node, fontSize = 12 }) {
|
|
221
|
+
const [now, setNow] = useState(Date.now)
|
|
222
|
+
useEffect(() => {
|
|
223
|
+
if (node.status !== 'running' || !node.startedAt) return
|
|
224
|
+
setNow(Date.now())
|
|
225
|
+
const timer = setInterval(() => setNow(Date.now()), 1000)
|
|
226
|
+
return () => clearInterval(timer)
|
|
227
|
+
}, [node.status, node.startedAt])
|
|
228
|
+
const notStarted = !node.startedAt && ['pending', 'ready', 'blocked'].includes(node.status)
|
|
229
|
+
const label = notStarted ? '尚未开始' : traceElapsed({ ...node, attempt: node.leaseEpoch }, now)
|
|
230
|
+
return React.createElement('span', { style: { color: colors.muted, fontSize, fontVariantNumeric: 'tabular-nums' } },
|
|
231
|
+
node.status === 'waiting' && node.completedAt ? `本次执行${label}` : label)
|
|
232
|
+
}
|
|
233
|
+
const traceElapsed = (item, now) => {
|
|
234
|
+
const start = Date.parse(item.startedAt), end = Date.parse(item.completedAt)
|
|
235
|
+
const running = ['running', 'claimed'].includes(item.status)
|
|
236
|
+
const duration = (running ? now : end) - start
|
|
237
|
+
if (!Number.isFinite(duration) || duration < 0) return '耗时未记录'
|
|
238
|
+
const seconds = duration / 1000
|
|
239
|
+
const value = seconds < 1 ? `${Math.round(duration)} 毫秒` : seconds < 60 ? `${seconds.toFixed(1)} 秒`
|
|
240
|
+
: seconds < 3600 ? `${Math.floor(seconds / 60)} 分 ${Math.floor(seconds % 60)} 秒`
|
|
241
|
+
: `${Math.floor(seconds / 3600)} 小时 ${Math.floor(seconds % 3600 / 60)} 分 ${Math.floor(seconds % 60)} 秒`
|
|
242
|
+
return `${running ? '已用时' : '耗时'} ${value}${item.attempt > 1 ? `(第 ${item.attempt} 次处理)` : ''}`
|
|
243
|
+
}
|
|
244
|
+
const traceReason = (reason) => {
|
|
245
|
+
if (!reason) return null
|
|
246
|
+
const text = typeof reason === 'string' ? reason : JSON.stringify(reason)
|
|
247
|
+
if (/MESSAGE_(?:CONTEXT|MATERIAL|REFERENCED_CANDIDATES)_CAPACITY|context_capacity_blocked/.test(text)) return '上下文容量受阻:必要材料未能完整提供,后续判断已停止。'
|
|
248
|
+
return ({ LOCAL_ACCEPTANCE_CONFIG_REQUIRED: '尚未配置本地验收环境与固定验收命令,请补齐后继续', LOCAL_ACCEPTANCE_CRITERIA_REQUIRED: '缺少明确的业务验收条件,请补充预期结果', LOCAL_ACCEPTANCE_PLAN_INVALID: '验收方案未覆盖任务要求或包含无效用例,请修订方案', LOCAL_ACCEPTANCE_PENDING_RECONCILIATION: '上次本地验收结果待核对,暂停重试以避免重复写入共享 UAT 数据', LOCAL_ACCEPTANCE_FAILED: '本地业务验收未通过,请查看实际结果并修复', LOCAL_ACCEPTANCE_CLEANUP_UNCONFIRMED: '测试数据清理或本地服务停止尚未确认,后续提交已停止', LOCAL_ACCEPTANCE_RECEIPT_INVALID: '验收回执与当前代码或方案不一致,后续提交已停止', ENGINEERING_UAT_ENVIRONMENT_REQUIRED: '请明确指定 uat1~uat9 中的一个环境,不能默认选择', ENGINEERING_UAT_BRANCH_REQUIRED: '开发 PR 只能提交到明确指定的 UAT 分支,main 合并需独立上线任务', ENGINEERING_UAT_BRANCH_NOT_FOUND: '指定 UAT 环境对应的分支不存在,请核对', ENGINEERING_ACCEPTANCE_REQUIRED: '缺少业务验收用例与预期结果,后续提交已停止', ENGINEERING_ACCEPTANCE_FAILED: '业务验收未通过或未取得实际结果,后续提交已停止', ENGINEERING_VERIFICATION_FAILED: '构建检查未通过,后续步骤已停止' })[text] ?? text
|
|
249
|
+
}
|
|
250
|
+
const readableValue = (value) => value === undefined || value === null ? '历史未记录' : typeof value === 'string' ? value : JSON.stringify(value, null, 2)
|
|
251
|
+
const readout = (label, value) => React.createElement('details', { style: { borderTop: `1px solid ${colors.border}`, padding: '8px 0', overflowWrap: 'anywhere' } }, React.createElement('summary', { style: { cursor: 'pointer', fontWeight: 600 } }, label), React.createElement('pre', { style: { margin: '8px 0 0', padding: 10, maxHeight: 300, overflow: 'auto', whiteSpace: 'pre-wrap', wordBreak: 'break-word', background: colors.surface2, borderRadius: ui.radiusSm, font: 'inherit', fontSize: 12 } }, readableValue(value)))
|
|
252
|
+
function WorkflowClarification({ request, onSubmitted }) {
|
|
253
|
+
const [answer, setAnswer] = useState('')
|
|
254
|
+
const [busy, setBusy] = useState(false)
|
|
255
|
+
const [submitted, setSubmitted] = useState(false)
|
|
256
|
+
const [error, setError] = useState('')
|
|
257
|
+
const field = useRef(null), pending = useRef(false), attempt = useRef(null)
|
|
258
|
+
const fieldId = `clarification-${request.requestId}`, errorId = `${fieldId}-error`
|
|
259
|
+
const submit = async event => {
|
|
260
|
+
event.preventDefault()
|
|
261
|
+
if (pending.current || submitted) return
|
|
262
|
+
const value = answer.trim()
|
|
263
|
+
if (!value) { setError('请填写需要补充的信息。'); field.current?.focus(); return }
|
|
264
|
+
if (!attempt.current || attempt.current.answer !== value) attempt.current = { answer: value, eventId: `web-answer:${crypto.randomUUID()}` }
|
|
265
|
+
pending.current = true; setBusy(true); setError('')
|
|
266
|
+
try {
|
|
267
|
+
await post(`/workflows/${encodeURIComponent(request.runId)}/requests/${encodeURIComponent(request.requestId)}/answer`, attempt.current)
|
|
268
|
+
setSubmitted(true)
|
|
269
|
+
onSubmitted?.()
|
|
270
|
+
} catch { setError('补充信息提交未确认,内容已保留。请重试或刷新核对处理状态。') }
|
|
271
|
+
finally { pending.current = false; setBusy(false) }
|
|
272
|
+
}
|
|
273
|
+
return React.createElement('section', { 'aria-label': '补充调查信息', style: { margin: '12px 0 16px', padding: 16, background: colors.surface2, borderLeft: `3px solid ${colors.warning}`, borderRadius: ui.radiusSm } },
|
|
274
|
+
React.createElement('strong', { style: { display: 'block', marginBottom: 8 } }, '需要你补充'),
|
|
275
|
+
React.createElement('p', { style: { margin: '0 0 12px', whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } }, request.question),
|
|
276
|
+
submitted ? React.createElement('p', { role: 'status', style: { margin: 0 } }, '补充已提交,后续执行情况请查看处理记录。')
|
|
277
|
+
: request.canAnswer !== true ? React.createElement('p', { style: { margin: 0, color: colors.muted } }, '请由任务发起人或负责人在原群回复补充信息。')
|
|
278
|
+
: React.createElement('form', { noValidate: true, onSubmit: submit },
|
|
279
|
+
React.createElement('label', { htmlFor: fieldId, style: { display: 'block', marginBottom: 6, fontSize: 13 } }, '补充信息'),
|
|
280
|
+
React.createElement('textarea', { id: fieldId, ref: field, value: answer, rows: 4, maxLength: 16000, disabled: busy,
|
|
281
|
+
'aria-invalid': !!error, 'aria-describedby': error ? errorId : undefined,
|
|
282
|
+
onChange: event => { setAnswer(event.target.value); setError('') },
|
|
283
|
+
style: { width: '100%', boxSizing: 'border-box', minHeight: 96, resize: 'none', padding: 10, color: 'inherit', background: colors.surface, border: `1px solid ${colors.border}`, borderRadius: ui.radiusSm, font: 'inherit', lineHeight: 1.6 } }),
|
|
284
|
+
error ? React.createElement('p', { id: errorId, role: 'alert', style: { color: colors.danger, margin: '8px 0' } }, error) : null,
|
|
285
|
+
React.createElement(Button, { variant: 'outline', size: 'sm', type: 'submit', disabled: busy, 'aria-busy': busy, style: { marginTop: 8, minWidth: 120 } }, busy ? '正在提交…' : '提交补充并继续')))
|
|
286
|
+
}
|
|
287
|
+
function TraceReadout({ runId, resourceRef, label }) {
|
|
288
|
+
const [expanded, setExpanded] = useState(false)
|
|
289
|
+
const [page, setPage] = useState()
|
|
290
|
+
const [cursor, setCursor] = useState(0)
|
|
291
|
+
const [hash, setHash] = useState('')
|
|
292
|
+
const [text, setText] = useState('')
|
|
293
|
+
const [loading, setLoading] = useState(false)
|
|
294
|
+
const [error, setError] = useState('')
|
|
295
|
+
const [retry, setRetry] = useState(0)
|
|
296
|
+
useEffect(() => {
|
|
297
|
+
if (!expanded) return
|
|
298
|
+
let active = true
|
|
299
|
+
setLoading(true); setError('')
|
|
300
|
+
get(`/state/workflows/${encodeURIComponent(runId)}/evidence/${encodeURIComponent(resourceRef)}?cursor=${cursor}${cursor && hash ? `&hash=${encodeURIComponent(hash)}` : ''}`).then(value => {
|
|
301
|
+
if (active) { setPage(value); setHash(value.hash); setText(previous => cursor ? previous + value.text : value.text) }
|
|
302
|
+
}, () => { if (active) setError('产出暂时无法读取,请重试。') }).finally(() => { if (active) setLoading(false) })
|
|
303
|
+
return () => { active = false }
|
|
304
|
+
}, [runId, resourceRef, expanded, cursor, retry])
|
|
305
|
+
return React.createElement('section', { style: { minWidth: 0 } },
|
|
306
|
+
React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', 'aria-expanded': expanded, onClick: () => { setExpanded(value => !value); setCursor(0); setHash(''); setText(''); setPage(undefined) } }, expanded ? '收起产出' : label),
|
|
307
|
+
expanded ? React.createElement('div', { style: { marginTop: 10, padding: 12, background: colors.surface2, borderRadius: ui.radiusSm, lineHeight: 1.7 } },
|
|
308
|
+
text ? React.createElement('div', { style: { whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } }, text) : null,
|
|
309
|
+
loading ? React.createElement('p', { role: 'status', style: { minHeight: 24 } }, '正在读取产出…') : null,
|
|
310
|
+
error ? React.createElement('div', { role: 'alert' }, error, React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', onClick: () => setRetry(value => value + 1) }, '重试')) : null,
|
|
311
|
+
!loading && !error && page && !text ? React.createElement('p', null, '未记录正文。') : null,
|
|
312
|
+
page?.nextCursor !== null && page?.nextCursor !== undefined ? React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: loading, onClick: () => setCursor(page.nextCursor) }, '继续阅读产出') : null,
|
|
313
|
+
page?.evidenceRefs?.length ? React.createElement('div', { style: { display: 'grid', gap: 8, marginTop: 12 } }, ...page.evidenceRefs.map((ref, index) => React.createElement(TraceReadout, { key: ref, runId, resourceRef: ref, label: `查看依据 ${index + 1}` }))) : null) : null)
|
|
314
|
+
}
|
|
315
|
+
function TraceDetail({ runId: initialRunId, onBack, onOpenSession, backLabel = '返回收信箱' }) {
|
|
316
|
+
const [runId, setRunId] = useState(initialRunId)
|
|
317
|
+
const [page, setPage] = useState()
|
|
318
|
+
const [cursor, setCursor] = useState('')
|
|
319
|
+
const [cursorHistory, setCursorHistory] = useState([])
|
|
320
|
+
const [retry, setRetry] = useState(0)
|
|
321
|
+
const [error, setError] = useState('')
|
|
322
|
+
const [loading, setLoading] = useState(true)
|
|
323
|
+
const [clock, setClock] = useState(Date.now)
|
|
324
|
+
const hasRunningStep = page?.items?.some(item => ['running', 'claimed'].includes(item.status))
|
|
325
|
+
useEffect(() => {
|
|
326
|
+
if (!hasRunningStep) return
|
|
327
|
+
const timer = setInterval(() => setClock(Date.now()), 1000)
|
|
328
|
+
return () => clearInterval(timer)
|
|
329
|
+
}, [hasRunningStep])
|
|
330
|
+
useEffect(() => {
|
|
331
|
+
let active = true
|
|
332
|
+
setLoading(true); setError('')
|
|
333
|
+
get(`/state/workflows/${encodeURIComponent(runId)}/trace${cursor ? `?cursor=${encodeURIComponent(cursor)}` : ''}`).then((value) => { if (active) setPage(value) }, (cause) => { if (active) setError(cause.message) }).finally(() => { if (active) setLoading(false) })
|
|
334
|
+
return () => { active = false }
|
|
335
|
+
}, [runId, cursor, retry])
|
|
336
|
+
const labels = { split: '拆分事项', route: '关联话题', intent: '判断下一步', command: '接纳动作', agent: '查询与答复' }
|
|
337
|
+
const ready = !loading && !error && page
|
|
338
|
+
return React.createElement('section', { 'aria-label': '消息处理过程', style: { ...card, display: 'grid', gap: 12 } },
|
|
339
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 } }, React.createElement('strong', { style: { fontSize: 16 } }, '消息处理过程'), React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', onClick: onBack }, backLabel)),
|
|
340
|
+
ready ? React.createElement('div', { style: { background: colors.surface2, padding: 16, borderRadius: ui.radiusSm, display: 'grid', gap: 10 } },
|
|
341
|
+
React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, flexWrap: 'wrap' } }, React.createElement('strong', { style: { fontSize: 16 } }, traceStatus(page.status)), React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', onClick: () => setRetry(value => value + 1) }, '刷新处理记录')),
|
|
342
|
+
React.createElement('blockquote', { style: { margin: 0, paddingLeft: 12, borderLeft: `3px solid ${colors.accent}`, fontSize: 14, lineHeight: 1.7, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } }, page.message?.text ?? '原消息未记录'),
|
|
343
|
+
page.reason ? React.createElement('p', { role: 'status', style: { color: colors.warning, margin: 0, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } }, traceReason(page.reason)) : null,
|
|
344
|
+
React.createElement('span', { style: { color: colors.muted, fontSize: 12 } }, '这里展示消息判断、查询与答复;需要持续交付的任务见任务看板,回复送达情况见发信箱。')) : null,
|
|
345
|
+
ready && page.items?.length ? React.createElement('div', { 'aria-label': '本页处理步骤', style: { display: 'flex', flexWrap: 'wrap', gap: 8 } }, ...Object.entries(labels).map(([kind, label]) => { const count = page.items.filter(item => item.kind === kind).length; return count ? React.createElement('span', { key: kind, style: pill(colors.accent) }, `${label} · ${count} 条记录`) : null })) : null,
|
|
346
|
+
loading ? React.createElement('div', { role: 'status', style: { minHeight: 160 } }, '正在读取处理记录…') : error ? React.createElement('div', { role: 'alert' }, `处理记录读取失败:${error}`, React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', onClick: () => setRetry((value) => value + 1) }, '重试')) : !page?.items?.length ? emptyState('此页没有处理记录', { detail: '历史消息可能尚未记录完整处理轨迹。' }) : React.createElement('ol', { 'aria-label': '逐步判断记录', start: Number(cursor || 0) + 1, style: { margin: 0, paddingLeft: 28, display: 'grid', gap: 16 } }, ...page.items.map((item, index) => {
|
|
347
|
+
const view = item.summary ?? { title: '处理记录', conclusion: '尚未记录判断结论', rows: [] }
|
|
348
|
+
const tone = ['failed', 'blocked', 'needs_attention'].includes(item.status) ? colors.warning : colors.accent
|
|
349
|
+
return React.createElement('li', { key: item.id || index, style: { borderLeft: `2px solid ${colors.border}`, padding: '0 0 12px 16px', minWidth: 0, fontSize: 14, overflowWrap: 'anywhere' } },
|
|
350
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' } }, React.createElement('strong', null, view.title), React.createElement('span', { style: pill(tone) }, traceStatus(item.status)), React.createElement('span', { style: { marginLeft: 'auto', color: colors.muted, fontSize: 12, textAlign: 'right', fontVariantNumeric: 'tabular-nums' } }, traceElapsed(item, clock))),
|
|
351
|
+
item.kind === 'intent' && item.sourceMessages?.length ? React.createElement('section', { 'aria-label': '本次判断的来源消息', style: { background: colors.surface2, padding: 12, borderRadius: ui.radiusSm, margin: '10px 0' } },
|
|
352
|
+
React.createElement('strong', { style: { fontSize: 13 } }, `${item.topicTitle || '当前话题'} · 本次判断覆盖 ${item.sourceMessages.length} 条消息`),
|
|
353
|
+
React.createElement('ol', { style: { margin: '12px 0 0', paddingLeft: 22, display: 'grid', gap: 12 } }, ...item.sourceMessages.map(message => React.createElement('li', { key: message.runId, style: { borderLeft: message.current ? `3px solid ${colors.accent}` : `3px solid ${colors.border}`, paddingLeft: 10 } },
|
|
354
|
+
React.createElement('div', { style: { fontSize: 12, color: colors.muted } }, `${message.senderName || '发送人未记录'} · ${fmt(message.occurredAt)} · ${message.current ? '当前查看的消息' : '共同参与判断'}`),
|
|
355
|
+
React.createElement('p', { style: { margin: '6px 0', whiteSpace: 'pre-wrap', lineHeight: 1.65 } }, message.text),
|
|
356
|
+
!message.current ? React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', onClick: () => { setPage(undefined); setCursor(''); setCursorHistory([]); setRunId(message.runId) } }, '查看这条消息的过程') : null))),
|
|
357
|
+
React.createElement('p', { style: { margin: '10px 0 0', color: colors.muted, fontSize: 12 } }, '以上消息共享下面这次判断,判断耗时只记录一次。后续重判保留为另一条判断记录。')) : null,
|
|
358
|
+
React.createElement('p', { style: { margin: '10px 0', fontWeight: 600, lineHeight: 1.6 } }, view.conclusion),
|
|
359
|
+
view.rows.length ? React.createElement('dl', { style: { margin: '0 0 12px', display: 'grid', gap: 10 } }, ...view.rows.map((row, i) => React.createElement('div', { key: i }, React.createElement('dt', { style: { color: colors.muted, fontSize: 12, marginBottom: 4 } }, row.label), React.createElement('dd', { style: { margin: 0, whiteSpace: 'pre-wrap', lineHeight: 1.65 } }, row.value)))) : null,
|
|
360
|
+
item.clarification ? React.createElement(WorkflowClarification, { key: item.clarification.requestId, request: item.clarification, onSubmitted: () => setRetry(value => value + 1) }) : null,
|
|
361
|
+
item.kind === 'agent' ? React.createElement('div', { style: { display: 'grid', gap: 8, margin: '8px 0' } },
|
|
362
|
+
item.outputRef ? React.createElement(TraceReadout, { key: item.outputRef, runId, resourceRef: item.outputRef, label: `查看答复与依据${item.evidenceCount ? `(${item.evidenceCount} 条)` : ''}` }) : null,
|
|
363
|
+
item.sessionId && onOpenSession ? React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', style: { justifySelf: 'start' }, onClick: () => onOpenSession(item.sessionId) }, '查看会话记录') : null) : null,
|
|
364
|
+
item.reason ? React.createElement('p', { style: { color: colors.warning, whiteSpace: 'pre-wrap' } }, traceReason(item.reason)) : null,
|
|
365
|
+
item.sourceRunIds?.length > 1 ? React.createElement('p', { style: { color: colors.muted, fontSize: 12 } }, `同一话题的 ${new Set(item.sourceRunIds).size} 条消息共同判断,各事项的决定分别列出。`) : null,
|
|
366
|
+
item.gaps?.length ? React.createElement('p', { style: { color: colors.warning } }, `依据缺口:${item.gaps.join(';')}`) : null)
|
|
367
|
+
})),
|
|
368
|
+
ready ? React.createElement('span', { style: { color: colors.muted, fontSize: 12 } }, `本页 ${page.items?.length ?? 0} 条${Number.isFinite(page.total) ? `,共 ${page.total} 条处理记录` : ''};按记录时间排列,多事项可能分别判断。`) : null,
|
|
369
|
+
ready ? React.createElement('p', { style: { color: colors.muted, fontSize: 12, margin: 0 } }, '耗时按本次开始到完成计算,包含工具等待;重试显示最近一次处理,不代表纯模型耗时。进行中记录可刷新查看最新状态。') : null,
|
|
370
|
+
React.createElement('div', { style: { display: 'flex', gap: 8 } }, cursorHistory.length ? React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: loading, onClick: () => { setCursor(cursorHistory.at(-1)); setCursorHistory((items) => items.slice(0, -1)) } }, '上一页') : null, page?.nextCursor ? React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: loading, onClick: () => { setCursorHistory((items) => [...items, cursor]); setCursor(page.nextCursor) } }, '下一页') : null))
|
|
371
|
+
}
|
|
372
|
+
function TopicBrowser({ groups, tasks, target, updatedAt, onOpenTask, onOpenSession }) {
|
|
142
373
|
const [groupId, setGroupId] = useState(target?.groupId || '')
|
|
143
374
|
const [offset, setOffset] = useState(0)
|
|
144
375
|
const [selection, setSelection] = useState(target)
|
|
145
376
|
const [messageOffset, setMessageOffset] = useState(0)
|
|
146
377
|
const [listing, setListing] = useState()
|
|
147
378
|
const [context, setContext] = useState()
|
|
379
|
+
const [topicContext, setTopicContext] = useState()
|
|
380
|
+
const [contextCursor, setContextCursor] = useState('')
|
|
381
|
+
const [contextCursorHistory, setContextCursorHistory] = useState([])
|
|
382
|
+
const [contextRevision, setContextRevision] = useState()
|
|
383
|
+
const [intentCursor, setIntentCursor] = useState('')
|
|
384
|
+
const [intentCursorHistory, setIntentCursorHistory] = useState([])
|
|
385
|
+
const [selectedIntentRun, setSelectedIntentRun] = useState('')
|
|
386
|
+
const [contextError, setContextError] = useState('')
|
|
387
|
+
const [contextLoading, setContextLoading] = useState(false)
|
|
148
388
|
const [listError, setListError] = useState('')
|
|
149
389
|
const [detailError, setDetailError] = useState('')
|
|
150
390
|
const [loading, setLoading] = useState(true)
|
|
@@ -152,6 +392,15 @@ window.__ModuleLoader__.load({
|
|
|
152
392
|
const [retry, setRetry] = useState(0)
|
|
153
393
|
const topicPageSize = 10
|
|
154
394
|
const messagePageSize = 25
|
|
395
|
+
useEffect(() => {
|
|
396
|
+
if (!target?.topicId) return
|
|
397
|
+
setGroupId(target.groupId || '')
|
|
398
|
+
setSelection(target)
|
|
399
|
+
setMessageOffset(0)
|
|
400
|
+
setContextCursor('')
|
|
401
|
+
setContextCursorHistory([])
|
|
402
|
+
setIntentCursor(''); setIntentCursorHistory([]); setSelectedIntentRun(''); setContextRevision(undefined)
|
|
403
|
+
}, [target?.groupId, target?.topicId, target?.revision])
|
|
155
404
|
useEffect(() => {
|
|
156
405
|
let active = true
|
|
157
406
|
setLoading(true); setListError('')
|
|
@@ -168,35 +417,112 @@ window.__ModuleLoader__.load({
|
|
|
168
417
|
get(`/state/topics/${encodeURIComponent(selection.topicId)}?${params}`).then((value) => { if (active) setContext(value) }, (error) => { if (active) setDetailError(error.message) }).finally(() => { if (active) setDetailLoading(false) })
|
|
169
418
|
return () => { active = false }
|
|
170
419
|
}, [selection, messageOffset, retry])
|
|
171
|
-
|
|
420
|
+
useEffect(() => {
|
|
421
|
+
if (!selection) { setTopicContext(undefined); return }
|
|
422
|
+
let active = true
|
|
423
|
+
setContextLoading(true); setContextError('')
|
|
424
|
+
const params = new URLSearchParams()
|
|
425
|
+
if (contextCursor) params.set('cursor', contextCursor)
|
|
426
|
+
if (intentCursor) params.set('intentCursor', intentCursor)
|
|
427
|
+
if (contextRevision !== undefined) params.set('revision', String(contextRevision))
|
|
428
|
+
get(`/state/workflows/topics/${encodeURIComponent(selection.topicId)}/context?${params}`).then((value) => { if (active) { setTopicContext(value); setContextRevision(value.revision) } }, (cause) => { if (active) setContextError(cause.message) }).finally(() => { if (active) setContextLoading(false) })
|
|
429
|
+
return () => { active = false }
|
|
430
|
+
}, [selection, contextCursor, intentCursor, retry])
|
|
431
|
+
const selectTopic = (topic) => { setSelection({ groupId: topic.groupId, topicId: topic.topicId, revision: topic.revision }); setMessageOffset(0); setContextCursor(''); setContextCursorHistory([]); setIntentCursor(''); setIntentCursorHistory([]); setSelectedIntentRun(''); setContextRevision(undefined); setTopicContext(undefined) }
|
|
432
|
+
if (selectedIntentRun) return React.createElement(TraceDetail, { key: selectedIntentRun, runId: selectedIntentRun, onOpenSession, onBack: () => setSelectedIntentRun(''), backLabel: '返回话题判断列表' })
|
|
172
433
|
const topic = context?.topic
|
|
173
434
|
const group = groups.find((item) => item.groupId === groupId)
|
|
174
|
-
const relatedTasks = tasks.filter((task) => task.
|
|
435
|
+
const relatedTasks = tasks.filter((task) => task.topicRefs?.some((ref) => (ref.groupId || task.sourceGroupId || task.groupId) === selection?.groupId && ref.topicId === selection?.topicId))
|
|
175
436
|
const failure = (message) => React.createElement('div', { role: 'alert', style: { padding: 16, color: colors.danger } }, message, ' ', React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', onClick: () => setRetry((value) => value + 1) }, '重试'))
|
|
176
437
|
const pager = (pageOffset, total, pageSize, onChange, busy) => React.createElement('div', { style: { ...tableFooter, flexWrap: 'wrap' } }, React.createElement('span', { style: { marginRight: 'auto', color: colors.muted, fontSize: 12 } }, `${total} 条 · 第 ${Math.floor(pageOffset / pageSize) + 1} 页`), React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: busy || pageOffset === 0, onClick: () => onChange(Math.max(0, pageOffset - pageSize)) }, '上一页'), React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: busy || pageOffset + pageSize >= total, onClick: () => onChange(pageOffset + pageSize) }, '下一页'))
|
|
177
|
-
return React.createElement('section', { 'aria-label': '话题与上下文', style: { display: 'grid', gap:
|
|
178
|
-
React.createElement('div', { style: { ...toolbar, flexWrap: 'wrap' } }, React.createElement(SelectMenu, { label: '筛选话题群聊', value: groupId, options: [{ id: '', label: '全部群聊' }, ...groups.map((item) => ({ id: item.groupId, label: item.name || item.groupId }))], onChange: (value) => { setGroupId(value); setOffset(0); setListing(undefined); setSelection(undefined) } }), React.createElement('span', { style: { fontSize:
|
|
179
|
-
React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'flex-start', gap:
|
|
180
|
-
React.createElement('section', { 'aria-label': '话题列表', 'aria-busy': loading, style: { ...tableFrame, flex: '1 1 340px', width: '100%', minWidth: 0, maxWidth: 420, minHeight: 200 } },
|
|
438
|
+
return React.createElement('section', { 'aria-label': '话题与上下文', style: { display: 'grid', gap: ui.space4 } },
|
|
439
|
+
React.createElement('div', { className: 'observer-toolbar', style: { ...toolbar, flexWrap: 'wrap' } }, React.createElement(SelectMenu, { label: '筛选话题群聊', value: groupId, options: [{ id: '', label: '全部群聊' }, ...groups.map((item) => ({ id: item.groupId, label: item.name || item.groupId }))], onChange: (value) => { setGroupId(value); setOffset(0); setListing(undefined); setSelection(undefined) } }), React.createElement('span', { style: { fontSize: ui.textSm, color: colors.muted } }, group?.topicProgress ? `待归类 ${group.topicProgress.unroutedMessages} 条 · 待处理 ${group.topicProgress.pending} 个话题` : '接收、话题处理与任务完成分别计量')),
|
|
440
|
+
React.createElement('div', { className: 'observer-topics-layout', style: { display: 'flex', flexWrap: 'wrap', alignItems: 'flex-start', gap: ui.space4 } },
|
|
441
|
+
React.createElement('section', { className: 'observer-topic-list', 'aria-label': '话题列表', 'aria-busy': loading, style: { ...tableFrame, flex: '1 1 340px', width: '100%', minWidth: 0, maxWidth: 420, minHeight: 200 } },
|
|
181
442
|
React.createElement('div', { role: 'status', style: { ...toolbar, minHeight: 52 } }, React.createElement('strong', { style: { fontSize: 14 } }, loading ? '正在刷新话题…' : '话题'), listing ? React.createElement('span', { style: { color: colors.muted, fontSize: 12, fontVariantNumeric: 'tabular-nums' } }, `${listing.total} 个`) : null),
|
|
182
|
-
listError ? failure(`话题加载失败:${listError}`) : !listing ? emptyState('正在读取话题…') : listing.topics.length === 0 ? emptyState('
|
|
183
|
-
React.createElement('strong', { title: item.title || item.topicId, style: { display: 'block', minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', overflowWrap: 'anywhere', fontSize:
|
|
184
|
-
React.createElement('p', { title: item.summary || '暂无摘要', style: { margin:
|
|
443
|
+
listError ? failure(`话题加载失败:${listError}`) : !listing ? emptyState('正在读取话题…') : listing.topics.length === 0 ? emptyState('暂无话题', { detail: '新消息完成归类后会显示在这里。', actionLabel: '重新读取', onAction: () => setRetry((value) => value + 1) }) : React.createElement('ul', { style: { listStyle: 'none', margin: 0, padding: 0 } }, ...listing.topics.map((item) => { const selected = selection?.topicId === item.topicId && selection?.groupId === item.groupId; return React.createElement('li', { key: `${item.groupId}:${item.topicId}`, style: { borderBottom: `1px solid ${colors.border}` } }, React.createElement('button', { type: 'button', 'aria-pressed': selected, 'aria-label': item.title || item.topicId, onClick: () => selectTopic(item), style: { position: 'relative', width: '100%', minWidth: 0, border: 0, borderLeft: `3px solid ${selected ? colors.accent : 'transparent'}`, padding: `${ui.space4}px ${ui.space4}px ${ui.space4}px ${ui.space3}px`, background: selected ? `color-mix(in srgb, ${colors.accent} 8%, ${colors.cardSurface})` : colors.cardSurface, color: 'inherit', cursor: 'pointer', textAlign: 'left', fontFamily: 'inherit', outlineOffset: -2 } },
|
|
444
|
+
React.createElement('strong', { title: item.title || item.topicId, style: { display: 'block', minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', overflowWrap: 'anywhere', fontSize: ui.textMd, fontWeight: 650, lineHeight: 1.5 } }, item.title || item.topicId),
|
|
445
|
+
React.createElement('p', { title: item.summary || '暂无摘要', style: { margin: `${ui.space1}px 0 0`, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', overflowWrap: 'anywhere', whiteSpace: 'pre-wrap', color: colors.muted, fontSize: ui.textSm, lineHeight: 1.6 } }, item.summary || '暂无摘要')
|
|
185
446
|
)) })),
|
|
186
447
|
listing ? pager(offset, listing.total, topicPageSize, setOffset, loading) : null),
|
|
187
|
-
React.createElement('section', { 'aria-label': '话题详情', 'aria-busy': detailLoading, style: { ...tableFrame, flex: '2 1 560px', width: '100%', minWidth: 0, minHeight: 200 } },
|
|
448
|
+
React.createElement('section', { className: 'observer-topic-detail', 'aria-label': '话题详情', 'aria-busy': detailLoading, style: { ...tableFrame, flex: '2 1 560px', width: '100%', minWidth: 0, minHeight: 200 } },
|
|
188
449
|
React.createElement('header', { style: { padding: '17px 18px 15px', borderBottom: `1px solid ${colors.border}`, background: colors.cardSurface } }, React.createElement('h2', { title: topic?.title, style: { minWidth: 0, margin: 0, overflowWrap: 'anywhere', fontSize: 18, fontWeight: 650, lineHeight: 1.5, letterSpacing: '-.01em' } }, topic?.title || '话题详情')),
|
|
189
450
|
detailError ? failure(`上下文加载失败:${detailError}`) : detailLoading ? React.createElement('div', { role: 'status' }, emptyState('正在读取固定版本上下文…')) : !context ? emptyState('选择一个话题查看消息和关联任务') : React.createElement('div', { role: 'region', 'aria-label': '话题详情内容', style: { minWidth: 0, maxHeight: 'calc(100vh - 260px)', padding: '0 18px 18px', display: 'grid', gap: 0, overflowY: 'auto', overflowX: 'hidden', overflowWrap: 'anywhere' } },
|
|
190
451
|
React.createElement('section', { 'aria-label': '话题摘要', style: { marginTop: 16, padding: '13px 14px', borderLeft: `3px solid ${colors.accent}`, borderRadius: '0 9px 9px 0', background: `color-mix(in srgb, ${colors.accent} 6%, ${colors.surface2})` } }, React.createElement('strong', { style: { display: 'block', fontSize: 12 } }, '话题摘要'), React.createElement('p', { style: { margin: '6px 0 0', whiteSpace: 'pre-wrap', color: topic?.summary ? 'inherit' : colors.muted, fontSize: 13, lineHeight: 1.65 } }, topic?.summary || '暂无摘要')),
|
|
452
|
+
React.createElement('section', { 'aria-label': '当前上下文与判断记录', style: { padding: '16px 0', borderBottom: `1px solid ${colors.border}`, display: 'grid', gap: 10, fontSize: 12 } },
|
|
453
|
+
React.createElement('strong', { style: { fontSize: 13 } }, '当前上下文与判断记录'),
|
|
454
|
+
contextLoading ? React.createElement('span', { role: 'status' }, '正在读取话题上下文…') : contextError ? React.createElement('div', { role: 'alert', style: { color: colors.danger } }, `上下文读取失败:${contextError}`, React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', onClick: () => { setContextCursor(''); setContextCursorHistory([]); setIntentCursor(''); setIntentCursorHistory([]); setContextRevision(undefined); setRetry((value) => value + 1) } }, '重新读取当前上下文')) : topicContext ? React.createElement(React.Fragment, null,
|
|
455
|
+
readout('当前有效状态', topicContext.current),
|
|
456
|
+
React.createElement('strong', null, `事实 · ${(topicContext.facts || []).length} 条`),
|
|
457
|
+
...(topicContext.facts || []).map((fact, index) => React.createElement('details', { key: fact.factId || index }, React.createElement('summary', { style: { cursor: 'pointer' } }, `${fact.kind || '事实'} · ${fact.text || '内容未记录'} · ${fact.status || '状态未记录'}`), readout('原文来源', fact.sourceRefs))),
|
|
458
|
+
React.createElement('strong', null, `意图判断批次 · ${(topicContext.intentRuns || []).length} 条`),
|
|
459
|
+
...(topicContext.intentRuns || []).map((item, index) => React.createElement('details', { key: item.nodeRunId || item.intentRunId || index }, React.createElement('summary', { style: { cursor: 'pointer' } }, `${fmt(item.createdAt)} · ${item.status || '状态未记录'} · ${short(item.intentRunId)}`), readout('本批来源', item.sourceRunIds), readout('判断依据', item.reason), item.carrierRunId ? React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', onClick: () => setSelectedIntentRun(item.carrierRunId) }, '查看本次判断') : React.createElement('span', { style: { color: colors.muted } }, '历史未记录判断来源'))),
|
|
460
|
+
React.createElement('nav', { 'aria-label': '判断批次分页', style: { display: 'flex', gap: 8 } }, React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: !intentCursorHistory.length, onClick: () => { setIntentCursor(intentCursorHistory.at(-1)); setIntentCursorHistory((items) => items.slice(0, -1)) } }, '上一页判断'), React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: !topicContext.intentNextCursor, onClick: () => { setIntentCursorHistory((items) => [...items, intentCursor]); setIntentCursor(topicContext.intentNextCursor) } }, '下一页判断')),
|
|
461
|
+
React.createElement('div', { style: { display: 'flex', gap: 8 } }, contextCursorHistory.length ? React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', onClick: () => { setContextCursor(contextCursorHistory.at(-1)); setContextCursorHistory((items) => items.slice(0, -1)) } }, '上一页') : null, topicContext.nextCursor ? React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', onClick: () => { setContextCursorHistory((items) => [...items, contextCursor]); setContextCursor(topicContext.nextCursor) } }, '下一页') : null)) : React.createElement('span', { style: { color: colors.muted } }, '历史未记录')),
|
|
191
462
|
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 240px), 1fr))', gap: 12, padding: '16px 0', borderBottom: `1px solid ${colors.border}` } },
|
|
192
463
|
React.createElement('section', { 'aria-label': '当前待解决问题', style: { minWidth: 0 } }, React.createElement('strong', { style: { display: 'block', marginBottom: 8, fontSize: 12 } }, `待解决问题 · ${topic?.openQuestions?.length || 0}`), topic?.openQuestions?.length ? React.createElement('ul', { style: { margin: 0, paddingLeft: 18, display: 'grid', gap: 6, fontSize: 12, lineHeight: 1.6 } }, ...topic.openQuestions.map((question, index) => React.createElement('li', { key: index }, question))) : React.createElement('span', { style: { color: colors.muted, fontSize: 12 } }, '当前没有待解决问题')),
|
|
193
|
-
React.createElement('section', { 'aria-label': '关联任务', style: { minWidth: 0 } }, React.createElement('strong', { style: { display: 'block', marginBottom: 8, fontSize: 12 } }, `关联任务 · ${relatedTasks.length}`), React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 7 } }, ...relatedTasks.map((task) => React.createElement(Button, { key: task.taskId, variant: 'outline', size: 'sm', type: 'button', disabled: !task.childSessionId || task.state === 'queued', onClick: () => onOpenTask(task), style: { maxWidth: '100%' } }, React.createElement('span', { style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, task.title || task.objective))), relatedTasks.length === 0 ? React.createElement('span', { style: { color: colors.muted, fontSize: 12 } }, '暂无关联任务') : null))),
|
|
464
|
+
React.createElement('section', { 'aria-label': '关联任务', style: { minWidth: 0 } }, React.createElement('strong', { style: { display: 'block', marginBottom: 8, fontSize: 12 } }, `关联任务 · ${relatedTasks.length}`), React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 7 } }, ...relatedTasks.map((task) => React.createElement(Button, { key: task.taskId, variant: 'outline', size: 'sm', type: 'button', disabled: task.engine !== 'workflow-v2' && (!task.childSessionId || task.state === 'queued'), onClick: () => onOpenTask(task), style: { maxWidth: '100%' } }, React.createElement('span', { style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, task.title || task.objective))), relatedTasks.length === 0 ? React.createElement('span', { style: { color: colors.muted, fontSize: 12 } }, '暂无关联任务') : null))),
|
|
194
465
|
React.createElement('section', { 'aria-label': '群聊消息', style: { marginTop: 4 } },
|
|
195
466
|
React.createElement('strong', { style: { display: 'block', padding: '14px 0', fontSize: 13, fontWeight: 600 } }, `群聊消息 · ${context.total} 条`),
|
|
196
467
|
React.createElement('div', { style: { display: 'grid' } }, ...(context.messages?.length ? context.messages.map((message, index) => React.createElement('article', { key: `${message.messageId}:${index}`, style: { position: 'relative', minWidth: 0, padding: '10px 0 15px 22px', borderLeft: `1px solid ${colors.border}` } }, React.createElement('span', { 'aria-hidden': true, style: { position: 'absolute', top: 15, left: -4, width: 7, height: 7, borderRadius: '50%', background: index === 0 ? colors.accent : colors.cardSurface, border: `1px solid ${index === 0 ? colors.accent : colors.border}` } }), React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'baseline', gap: '2px 8px', color: colors.muted, fontSize: 11 } }, React.createElement('strong', { style: { color: 'inherit', fontWeight: 600 } }, message.senderName || message.sourceKind || '未知发送人'), React.createElement('time', { dateTime: message.occurredAt || undefined, style: { fontVariantNumeric: 'tabular-nums' } }, fmt(message.occurredAt))), React.createElement('p', { style: { margin: '6px 0 0', whiteSpace: 'pre-wrap', overflowWrap: 'anywhere', fontSize: 13, lineHeight: 1.7 } }, message.text || '(空消息)'), message.quotedMessage?.content ? React.createElement('details', { style: { marginTop: 9, color: colors.muted, fontSize: 12 } }, React.createElement('summary', { style: { cursor: 'pointer', fontWeight: 600 } }, '查看引用消息'), React.createElement('blockquote', { style: { margin: '7px 0 0', padding: '8px 10px', borderLeft: `2px solid ${colors.border}`, background: colors.surface2, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere', lineHeight: 1.6 } }, message.quotedMessage.content)) : null)) : [emptyState('此版本没有消息')])),
|
|
197
468
|
pager(messageOffset, context.total, messagePageSize, setMessageOffset, detailLoading))
|
|
198
469
|
))))
|
|
199
470
|
}
|
|
471
|
+
function TaskStepOutput({ taskId, node, detailRevision }) {
|
|
472
|
+
return React.createElement(TaskStepOutputContent, { key: `${taskId}:${node.runId}:${node.nodeRunId}:${node.outputRef}`, taskId, node, detailRevision })
|
|
473
|
+
}
|
|
474
|
+
function TaskStepOutputContent({ taskId, node, detailRevision }) {
|
|
475
|
+
const [text, setText] = useState('')
|
|
476
|
+
const [overview, setOverview] = useState('')
|
|
477
|
+
const [documentName, setDocumentName] = useState('')
|
|
478
|
+
const [expanded, setExpanded] = useState(false)
|
|
479
|
+
const [cursor, setCursor] = useState(0)
|
|
480
|
+
const [nextCursor, setNextCursor] = useState(null)
|
|
481
|
+
const [loading, setLoading] = useState(true)
|
|
482
|
+
const [error, setError] = useState('')
|
|
483
|
+
const [retry, setRetry] = useState(0)
|
|
484
|
+
const loadedCursor = useRef(null)
|
|
485
|
+
useEffect(() => {
|
|
486
|
+
if (loadedCursor.current === cursor) { setLoading(false); return }
|
|
487
|
+
let active = true
|
|
488
|
+
setLoading(true); setError('')
|
|
489
|
+
get(`/state/tasks/${encodeURIComponent(taskId)}/runs/${encodeURIComponent(node.runId)}/nodes/${encodeURIComponent(node.nodeRunId)}/output?ref=${encodeURIComponent(node.outputRef)}&cursor=${cursor}&detailRevision=${encodeURIComponent(detailRevision)}`).then(value => {
|
|
490
|
+
if (active) { loadedCursor.current = cursor; setText(previous => cursor ? previous + value.text : value.text); setNextCursor(value.nextCursor); setOverview(value.overview || ''); setDocumentName(value.documentName || '') }
|
|
491
|
+
}, () => { if (active) setError('产出暂时无法读取,请重试。') }).finally(() => { if (active) setLoading(false) })
|
|
492
|
+
return () => { active = false }
|
|
493
|
+
}, [taskId, node.runId, node.nodeRunId, node.outputRef, detailRevision, cursor, retry])
|
|
494
|
+
const content = React.createElement('div', { style: { minWidth: 0, fontSize: 13, lineHeight: 1.7 } },
|
|
495
|
+
text ? React.createElement('div', { style: { display: 'grid', gap: 6, overflowWrap: 'anywhere' } }, ...text.split('\n\n').map((part, index) => {
|
|
496
|
+
const [heading, ...lines] = part.split('\n')
|
|
497
|
+
const titled = ['产出摘要', '正文', '任务要求', '发现', '限制与未确认事项', '执行范围', '相关文件', '已有文件', '新建文件', '材料正文', '已读取文件', '已修改文件', '涉及文件', '文件变更', '修改方案', '检查结果', '文件索引', '基线版本', '可修改文件', '变更文件', '提交说明', '分支', '远端', '提交版本', '执行结果', 'PR 标题', '目标仓库', '来源分支', '目标分支', 'PR 正文', 'PR 地址', 'PR 状态', '方案工件路径', '工作目录', '来源仓库', '隔离方式', '处理内容', '项目', '工作分支', '起点版本'].includes(heading)
|
|
498
|
+
return React.createElement('div', { key: index, className: titled ? 'observer-task-output-row' : undefined }, titled ? React.createElement('strong', { style: { fontSize: 12, fontWeight: 500, color: colors.muted } }, heading) : null,
|
|
499
|
+
React.createElement('div', { style: { whiteSpace: 'pre-wrap' } }, titled ? lines.join('\n') : part))
|
|
500
|
+
})) : null,
|
|
501
|
+
loading ? React.createElement('span', { role: 'status', style: { color: colors.muted } }, '正在读取产出…') : null,
|
|
502
|
+
error ? React.createElement('div', { role: 'alert', style: { color: colors.danger } }, error, React.createElement(Button, { type: 'button', variant: 'ghost', size: 'sm', onClick: () => setRetry(value => value + 1) }, '重试读取产出')) : null,
|
|
503
|
+
!loading && !error && !text ? React.createElement('span', { style: { color: colors.muted } }, '已保存节点产出,暂未提供可读展示') : null,
|
|
504
|
+
!loading && !error && nextCursor !== null ? React.createElement(Button, { type: 'button', variant: 'ghost', size: 'sm', onClick: () => setCursor(nextCursor) }, '继续阅读产出') : null)
|
|
505
|
+
const documentLink = documentName ? React.createElement('a', { href: `${ENDPOINT}/state/tasks/${encodeURIComponent(taskId)}/runs/${encodeURIComponent(node.runId)}/nodes/${encodeURIComponent(node.nodeRunId)}/document?ref=${encodeURIComponent(node.outputRef)}&detailRevision=${encodeURIComponent(detailRevision)}`, style: { display: 'inline-block', marginTop: 6, color: colors.accent, fontSize: 12 } }, `下载 ${documentName}`) : null
|
|
506
|
+
return React.createElement('div', null, overview ? React.createElement('details', { onToggle: event => setExpanded(event.currentTarget.open), style: { fontSize: 13, lineHeight: 1.7 } },
|
|
507
|
+
React.createElement('summary', { style: { cursor: 'pointer', color: colors.muted } }, overview, React.createElement('span', { style: { marginLeft: 12, color: colors.accent, fontSize: 12 } }, expanded ? '收起产出' : '查看产出')),
|
|
508
|
+
expanded ? React.createElement('div', { style: { marginTop: 8 } }, content) : null) : content, documentLink)
|
|
509
|
+
}
|
|
510
|
+
const linkedTaskText = text => {
|
|
511
|
+
const content = String(text), parts = []
|
|
512
|
+
let offset = 0
|
|
513
|
+
for (const match of content.matchAll(/\[([^\]\n]+)\]\((https?:\/\/[^\s)]+)\)|(https?:\/\/[^\s<>")。,;]+)/g)) {
|
|
514
|
+
const href = (match[2] || match[3]).replace(/[.,;!?]+$/, '')
|
|
515
|
+
parts.push(content.slice(offset, match.index), React.createElement('a', { key: match.index, href, target: '_blank', rel: 'noopener noreferrer', style: { color: colors.accent } }, match[1] || href))
|
|
516
|
+
if (!match[2]) parts.push(match[3].slice(href.length))
|
|
517
|
+
offset = match.index + match[0].length
|
|
518
|
+
}
|
|
519
|
+
parts.push(content.slice(offset))
|
|
520
|
+
return parts
|
|
521
|
+
}
|
|
522
|
+
const adjacentCurrentStep = (previousKeys, currentKeys, readingKey) => {
|
|
523
|
+
if (!readingKey || currentKeys.includes(readingKey) || !previousKeys.includes(readingKey)) return null
|
|
524
|
+
return currentKeys[Math.min(previousKeys.indexOf(readingKey), currentKeys.length - 1)] || ''
|
|
525
|
+
}
|
|
200
526
|
function ObserverContent({ openSession }) {
|
|
201
527
|
const [activePage, setActivePage] = useState('groups')
|
|
202
528
|
const [topicTarget, setTopicTarget] = useState()
|
|
@@ -213,8 +539,17 @@ window.__ModuleLoader__.load({
|
|
|
213
539
|
const [outboxStatusFilter, setOutboxStatusFilter] = useState('all')
|
|
214
540
|
const [hoveredTaskId, setHoveredTaskId] = useState('')
|
|
215
541
|
const [expandedCheckpointTaskId, setExpandedCheckpointTaskId] = useState('')
|
|
542
|
+
const [selectedWorkflowTaskId, setSelectedWorkflowTaskId] = useState('')
|
|
543
|
+
const [workflowTaskDetail, setWorkflowTaskDetail] = useState()
|
|
544
|
+
const [workflowDetailError, setWorkflowDetailError] = useState('')
|
|
545
|
+
const [workflowDetailRetry, setWorkflowDetailRetry] = useState(0)
|
|
546
|
+
const [stepReadingNotice, setStepReadingNotice] = useState('')
|
|
547
|
+
const detailFocusTarget = useRef(null)
|
|
548
|
+
const [selectedMessageRunId, setSelectedMessageRunId] = useState('')
|
|
216
549
|
const [copiedId, setCopiedId] = useState('')
|
|
217
550
|
const [navigatingSessionId, setNavigatingSessionId] = useState('')
|
|
551
|
+
const [archivingTaskId, setArchivingTaskId] = useState('')
|
|
552
|
+
const [archiveDetailTaskId, setArchiveDetailTaskId] = useState('')
|
|
218
553
|
const [alertType, setAlertType] = useState('all')
|
|
219
554
|
const [alertView, setAlertView] = useState('active')
|
|
220
555
|
const [resolvedAlertPage, setResolvedAlertPage] = useState(1)
|
|
@@ -234,6 +569,30 @@ window.__ModuleLoader__.load({
|
|
|
234
569
|
setRefreshState('done')
|
|
235
570
|
window.setTimeout(() => setRefreshState('idle'), 1200)
|
|
236
571
|
}, [refresh, refreshState])
|
|
572
|
+
useEffect(() => {
|
|
573
|
+
if (!selectedWorkflowTaskId) { setWorkflowTaskDetail(undefined); setWorkflowDetailError(''); return }
|
|
574
|
+
let active = true
|
|
575
|
+
setWorkflowDetailError('')
|
|
576
|
+
get(`/state/tasks/${encodeURIComponent(selectedWorkflowTaskId)}/detail`).then(value => {
|
|
577
|
+
if (!active) return
|
|
578
|
+
const previous = [...document.querySelectorAll('[aria-label="任务执行详情"] li.observer-task-step')]
|
|
579
|
+
const focused = document.activeElement?.closest?.('li.observer-task-step')
|
|
580
|
+
const reading = focused || previous.find(element => { const bounds = element.getBoundingClientRect(); return bounds.top < window.innerHeight && bounds.bottom > 0 })
|
|
581
|
+
const adjacentKey = adjacentCurrentStep(previous.map(element => element.dataset.stepKey), (value.executionNodes || []).map(node => node.stepKey), reading?.dataset.stepKey)
|
|
582
|
+
if (adjacentKey !== null) { setStepReadingNotice('正在阅读的步骤已从当前计划移除,已转到相邻步骤。'); detailFocusTarget.current = { taskId: selectedWorkflowTaskId, stepKey: adjacentKey } }
|
|
583
|
+
else setStepReadingNotice('')
|
|
584
|
+
setWorkflowTaskDetail(value)
|
|
585
|
+
}, cause => { if (active) setWorkflowDetailError(cause.message) })
|
|
586
|
+
return () => { active = false }
|
|
587
|
+
}, [selectedWorkflowTaskId, updatedAt, workflowDetailRetry])
|
|
588
|
+
useEffect(() => {
|
|
589
|
+
const target = detailFocusTarget.current
|
|
590
|
+
if (!target || target.taskId !== selectedWorkflowTaskId) return
|
|
591
|
+
detailFocusTarget.current = null
|
|
592
|
+
const steps = [...document.querySelectorAll('[aria-label="任务执行详情"] li.observer-task-step')]
|
|
593
|
+
const adjacent = steps.find(element => element.dataset.stepKey === target.stepKey)
|
|
594
|
+
;(adjacent || document.querySelector('[aria-label="任务执行详情"] h2'))?.focus()
|
|
595
|
+
}, [workflowTaskDetail, selectedWorkflowTaskId])
|
|
237
596
|
useEffect(() => {
|
|
238
597
|
refresh()
|
|
239
598
|
const timer = window.setInterval(refresh, 5000)
|
|
@@ -253,6 +612,10 @@ window.__ModuleLoader__.load({
|
|
|
253
612
|
catch (cause) { setNavigationError(cause instanceof Error ? cause.message : String(cause)) }
|
|
254
613
|
finally { setNavigatingSessionId('') }
|
|
255
614
|
}
|
|
615
|
+
const openTask = (task) => {
|
|
616
|
+
if (task.engine === 'workflow-v2') { setSelectedWorkflowTaskId(task.taskId); setActivePage('tasks'); return }
|
|
617
|
+
if (task.childSessionId) navigate(task.childSessionId, groupsById.get(task.groupId)?.residentSessionId)
|
|
618
|
+
}
|
|
256
619
|
const copyId = async (value) => {
|
|
257
620
|
let copied = false
|
|
258
621
|
try {
|
|
@@ -275,10 +638,12 @@ window.__ModuleLoader__.load({
|
|
|
275
638
|
}
|
|
276
639
|
const copyButton = (value, label) => React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: !value, onClick: (event) => { event.stopPropagation(); copyId(value).catch((cause) => setNavigationError(cause instanceof Error ? cause.message : String(cause))) }, style: { color: copiedId === value ? 'var(--dsw-alias-state-success-primary, #248a3d)' : undefined, cursor: value ? 'pointer' : 'default', fontSize: 10.5 } }, copiedId === value ? '已复制' : `复制${label}`)
|
|
277
640
|
const selectedGroup = groupsById.get(selectedGroupId) || (data?.groups || [])[0]
|
|
278
|
-
const selectedMessages = [...(selectedGroup?.messages || [])].filter((message) => message.sourceKind === 'dingtalk' || message.sourceKind === 'migration' || !message.sourceKind).sort((left, right) => Number(right.sequence || 0) - Number(left.sequence || 0))
|
|
641
|
+
const selectedMessages = [...(selectedGroup?.messages || [])].filter((message) => message.sourceKind === 'dingtalk' || message.sourceKind === 'migration' || message.sourceKind === 'workflow-v2' || !message.sourceKind).sort((left, right) => (new Date(right.occurredAt).getTime() || 0) - (new Date(left.occurredAt).getTime() || 0) || Number(right.sequence || 0) - Number(left.sequence || 0))
|
|
279
642
|
const messageWorkflowState = (message) => {
|
|
643
|
+
if (['routing', 'waiting_clarification', 'waiting_context', 'routing_blocked', 'waiting_routing_barrier', 'intent_judging', 'intent_rejudging', 'processed'].includes(message.workflowStatus)) return message.workflowStatus
|
|
280
644
|
if (message.routingStatus === 'failed') return 'failed'
|
|
281
645
|
if (message.routingStatus !== 'routed') return 'routing'
|
|
646
|
+
if (message.sourceKind === 'workflow-v2') return 'processed'
|
|
282
647
|
const related = (selectedGroup?.topics || []).flatMap((topic) => {
|
|
283
648
|
const entry = [...(topic.entries || [])].reverse().find((item) => item.messageId === message.messageId)
|
|
284
649
|
return entry?.action === 'add' && entry.messageVersion === message.messageVersion ? [{ topic, entry }] : []
|
|
@@ -293,6 +658,12 @@ window.__ModuleLoader__.load({
|
|
|
293
658
|
const visibleMessages = filteredMessages.slice((currentMessagePage - 1) * pageSize, currentMessagePage * pageSize)
|
|
294
659
|
const delivery = {
|
|
295
660
|
routing: { label: '待归类', state: 'ongoing' },
|
|
661
|
+
waiting_clarification: { label: '等待澄清', state: 'ongoing' },
|
|
662
|
+
waiting_context: { label: '等待补充材料', state: 'ongoing' },
|
|
663
|
+
routing_blocked: { label: '关联受阻', state: 'error' },
|
|
664
|
+
waiting_routing_barrier: { label: '已关联 · 等待其他消息', state: 'ongoing' },
|
|
665
|
+
intent_judging: { label: '话题意图判断中', state: 'ongoing' },
|
|
666
|
+
intent_rejudging: { label: '新消息加入 · 重新判断', state: 'ongoing' },
|
|
296
667
|
processing: { label: '话题处理中', state: 'ongoing' },
|
|
297
668
|
processed: { label: '已处理', state: 'done' },
|
|
298
669
|
failed: { label: '归类失败', state: 'error' },
|
|
@@ -300,11 +671,11 @@ window.__ModuleLoader__.load({
|
|
|
300
671
|
const messageRows = visibleMessages.map((message, rowIndex) => {
|
|
301
672
|
const status = delivery[messageWorkflowState(message)]
|
|
302
673
|
return React.createElement('tr', { key: message.messageId, style: { background: rowIndex % 2 ? `color-mix(in srgb, ${colors.surface2} 55%, transparent)` : colors.cardSurface } },
|
|
303
|
-
React.createElement('td', { style: { ...tableBodyCell, width: 104 }, title: message.agentDeliveryError || '' }, clampTableContent(tableStatusTag(status.label, status.state, { fontWeight: 600 }))),
|
|
674
|
+
React.createElement('td', { style: { ...tableBodyCell, width: 104 }, title: message.workflowStatusDetail || message.agentDeliveryError || '' }, clampTableContent(tableStatusTag(status.label, status.state, { fontWeight: 600 }))),
|
|
304
675
|
React.createElement('td', { style: { ...tableBodyCell, width: 160 } }, clampTableContent(React.createElement('strong', { style: { fontSize: 14, fontWeight: 600 } }, message.senderName || message.senderOpenDingTalkId || '发送人未记录'), React.createElement('div', { style: { marginTop: 3, fontSize: 11, color: colors.muted } }, fmt(message.occurredAt)))),
|
|
305
|
-
React.createElement('td', { style: { ...tableBodyCell, width: 160 } }, React.createElement('div', { style: { display: 'flex', gap: 4, minWidth: 0, overflow: 'hidden' } }, ...(message.topicRefs?.length ? message.topicRefs.map((ref) => React.createElement('button', { key: ref.topicId, type: 'button', title: ref.title, 'aria-label': `话题 ${ref.title}`, onClick: () => { setTopicTarget({ groupId: selectedGroup.groupId, topicId: ref.topicId, revision: ref.revision }); setActivePage('topics') }, style: { ...pill(colors.accent), display: 'inline-block', flex: '0 1 auto', minWidth: 0, maxWidth: 120, border: 0, padding: '2px 7px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', cursor: 'pointer', fontFamily: 'inherit' } }, ref.title)) : [React.createElement('span', { key: 'unrouted', style: { color: colors.muted, fontSize: 12 } }, '待归类')]))),
|
|
676
|
+
React.createElement('td', { style: { ...tableBodyCell, width: 160 } }, React.createElement('div', { style: { display: 'flex', gap: 4, minWidth: 0, overflow: 'hidden' } }, ...(message.topicRefs?.length ? message.topicRefs.map((ref) => React.createElement('button', { key: ref.topicId, type: 'button', title: ref.title, 'aria-label': `话题 ${ref.title}`, onClick: () => { setTopicTarget({ groupId: selectedGroup.groupId, topicId: ref.topicId, revision: ref.revision }); setActivePage('topics') }, style: { ...pill(colors.accent), display: 'inline-block', flex: '0 1 auto', minWidth: 0, maxWidth: 120, border: 0, padding: '2px 7px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', cursor: 'pointer', fontFamily: 'inherit' } }, ref.title)) : [React.createElement('span', { key: 'unrouted', style: { color: colors.muted, fontSize: 12 } }, messageWorkflowState(message) === 'processed' ? '—' : '待归类')]))),
|
|
306
677
|
React.createElement('td', { title: message.text, style: tableBodyCell }, clampTableContent(message.text || '(空消息)')),
|
|
307
|
-
React.createElement('td', { style: { ...tableBodyCell, width: 240 } }, singleLineTableContent(React.createElement('code', { title: message.messageId, style: { fontSize: 14, color: colors.muted, whiteSpace: 'nowrap' } }, `#${message.sequence ?? '—'} · ${short(message.messageId)}`))))
|
|
678
|
+
React.createElement('td', { style: { ...tableBodyCell, width: 240 } }, singleLineTableContent(React.createElement('code', { title: message.messageId, style: { fontSize: 14, color: colors.muted, whiteSpace: 'nowrap' } }, `#${message.sequence ?? '—'} · ${short(message.messageId)}`)), message.runId ? React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', onClick: () => setSelectedMessageRunId(message.runId), style: { marginTop: 5 } }, '处理过程') : React.createElement('span', { style: { color: colors.muted, fontSize: 11, display: 'block' } }, '历史未记录处理过程')))
|
|
308
679
|
})
|
|
309
680
|
const selectedOutbox = [...(selectedGroup?.outbox || [])].reverse()
|
|
310
681
|
const outboxState = (message) => outboxDelivery(message).id
|
|
@@ -318,7 +689,7 @@ window.__ModuleLoader__.load({
|
|
|
318
689
|
return React.createElement('tr', { key: message.outboundId, style: { background: rowIndex % 2 ? `color-mix(in srgb, ${colors.surface2} 55%, transparent)` : colors.cardSurface } },
|
|
319
690
|
React.createElement('td', { style: { ...tableBodyCell, width: 124 } }, clampTableContent(tableStatusTag(status.label, status.state, { fontWeight: 600 }))),
|
|
320
691
|
React.createElement('td', { title: message.text, style: tableBodyCell }, clampTableContent(message.text || '(空消息)'),
|
|
321
|
-
status.detail ? React.createElement('div', { style: { marginTop: 6, fontSize: 12, color: status.state === 'error' ? colors.danger : colors.warning, overflowWrap: 'anywhere' } }, status.detail, message.deliveryError ? `:${message.deliveryError}` : '') : null,
|
|
692
|
+
status.detail ? React.createElement('div', { style: { marginTop: 6, fontSize: 12, color: status.state === 'error' ? colors.danger : colors.warning, overflowWrap: 'anywhere' } }, status.detail, !message.supersededByOutboundId && !message.recallStatus && message.deliveryError ? `:${message.deliveryError}` : '') : null,
|
|
322
693
|
message.deliveryAttemptedAt ? React.createElement('div', { style: { marginTop: 3, fontSize: 11, color: colors.muted } }, `最近尝试 ${fmt(message.deliveryAttemptedAt)} · 共 ${message.deliveryAttemptCount || 1} 次`) : null),
|
|
323
694
|
React.createElement('td', { style: { ...tableBodyCell, width: 220 } },
|
|
324
695
|
React.createElement('div', { style: { minWidth: 0 } }, singleLineTableContent(React.createElement('code', { title: message.sourceMessageId, style: { fontSize: 14, color: colors.muted, whiteSpace: 'nowrap' } }, short(message.sourceMessageId))), message.replyToMessageId ? React.createElement('div', { style: { marginTop: 3, fontSize: 11, color: colors.muted, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } }, '回复 ', React.createElement('code', { title: message.replyToMessageId }, short(message.replyToMessageId))) : null)),
|
|
@@ -327,42 +698,95 @@ window.__ModuleLoader__.load({
|
|
|
327
698
|
})
|
|
328
699
|
const renderTaskCard = (task) => {
|
|
329
700
|
const group = groupsById.get(task.groupId)
|
|
701
|
+
const sourceGroup = groupsById.get(task.sourceGroupId || task.groupId)
|
|
702
|
+
const groupLabel = sourceGroup?.name || (task.groupId?.startsWith('web:') ? 'Web 任务' : task.groupId) || '未关联群聊'
|
|
703
|
+
const waitingNotice = task.state === 'waiting' && task.waitingKind === 'information'
|
|
704
|
+
? group?.outbox?.findLast(message => message.sourceMessageId?.startsWith(`task-result:${task.taskId}:waiting:`) && !message.sourceMessageId.includes(':followup:')
|
|
705
|
+
&& message.status !== 'superseded' && message.taskInputVersion === task.inputVersion && message.taskRunSequence === task.runSequence) : undefined
|
|
706
|
+
const waitingNoticeLabel = waitingNotice?.status === 'sent' && waitingNotice.deliveredMessageId ? `群通知已送达 · ${fmt(waitingNotice.deliveredAt)}`
|
|
707
|
+
: waitingNotice?.deliveryBlockedAt || waitingNotice?.deliveryError ? '群通知发送失败,请检查' : waitingNotice ? '群通知待确认送达' : '群通知尚未生成'
|
|
330
708
|
const queued = task.state === 'queued'
|
|
331
709
|
const checkpointEvents = task.checkpoints || []
|
|
332
710
|
const planCheckpointIndex = checkpointEvents.findLastIndex((checkpoint) => checkpoint.kind === 'plan-confirmed')
|
|
333
711
|
const planCheckpoint = checkpointEvents[planCheckpointIndex]
|
|
334
712
|
const currentCheckpointEvents = checkpointEvents.slice(Math.max(0, planCheckpointIndex))
|
|
335
|
-
const
|
|
713
|
+
const boardStages = task.workflowProgress?.stages || []
|
|
714
|
+
const checkpoints = boardStages.map(stage => stage.stageId)
|
|
715
|
+
const checkpointLabel = id => nodeTitle[id] || (boardStages.find(stage => stage.stageId === id)?.title !== id ? boardStages.find(stage => stage.stageId === id)?.title : null) || '执行步骤'
|
|
336
716
|
const displayObjective = String(task.objective || '').startsWith('[TASK_SOURCE_EVIDENCE]') ? (task.title || task.objective) : task.objective
|
|
337
|
-
const
|
|
338
|
-
const
|
|
339
|
-
const remainingItemsMatchPlan = latestRemainingItems.every((checkpoint) => checkpoints.includes(checkpoint))
|
|
340
|
-
const normalizedRemainingItems = remainingItemsMatchPlan ? latestRemainingItems : checkpoints.slice(-Math.min(checkpoints.length, latestRemainingItems.length))
|
|
341
|
-
const remainingCheckpointNames = new Set(normalizedRemainingItems)
|
|
342
|
-
const completedCheckpointNames = new Set(task.state === 'completed' ? checkpoints : checkpoints.filter((checkpoint) => !remainingCheckpointNames.has(checkpoint)))
|
|
343
|
-
const currentCheckpoint = task.state === 'running' ? checkpoints.find((checkpoint) => remainingCheckpointNames.has(checkpoint)) : undefined
|
|
717
|
+
const completedCheckpointNames = new Set(boardStages.filter(stage => stage.completed).map(stage => stage.stageId))
|
|
718
|
+
const currentCheckpoint = task.state === 'running' ? checkpoints.find((checkpoint) => !completedCheckpointNames.has(checkpoint)) : undefined
|
|
344
719
|
const completedCheckpointCount = checkpoints.filter((checkpoint) => completedCheckpointNames.has(checkpoint)).length
|
|
345
|
-
const progress = Math.round((completedCheckpointCount / checkpoints.length) * 100)
|
|
346
|
-
const showCheckpoints =
|
|
720
|
+
const progress = checkpoints.length ? Math.round((completedCheckpointCount / checkpoints.length) * 100) : 0
|
|
721
|
+
const showCheckpoints = checkpoints.length > 0 && !task.archivedAt
|
|
347
722
|
const checkpointsExpanded = expandedCheckpointTaskId === task.taskId
|
|
348
723
|
const hovered = hoveredTaskId === task.taskId
|
|
349
724
|
const navigating = navigatingSessionId === task.childSessionId
|
|
725
|
+
const worktreesByPath = new Map()
|
|
726
|
+
for (const item of Array.isArray(task.localWorktrees) ? task.localWorktrees : []) {
|
|
727
|
+
const previous = worktreesByPath.get(item.path)
|
|
728
|
+
worktreesByPath.set(item.path, previous ? { ...item, documents: [...(previous.documents || []), ...(item.documents || [])] } : item)
|
|
729
|
+
}
|
|
730
|
+
const worktrees = [...worktreesByPath.values()]
|
|
731
|
+
const ownedWorktrees = worktrees.filter(item => item.createdByTask && item.status === 'registered')
|
|
732
|
+
const borrowedWorktrees = worktrees.filter(item => !item.createdByTask)
|
|
733
|
+
const ownedDocumentCount = ownedWorktrees.reduce((count, item) => count + new Set((item.documents || []).map(document => document.source)).size, 0)
|
|
734
|
+
const notifications = (task.notificationIntents || []).filter(intent => intent.inputVersion === task.inputVersion && intent.runSequence === task.runSequence && intent.status !== 'superseded')
|
|
735
|
+
const notificationCounts = new Map()
|
|
736
|
+
for (const intent of notifications.filter(item => item.status !== 'delivered')) notificationCounts.set(intent.status, (notificationCounts.get(intent.status) || 0) + 1)
|
|
737
|
+
const retryExhausted = task.waitingReason?.startsWith('topic_request_retry_exhausted:')
|
|
738
|
+
const waitingLabel = retryExhausted ? '系统协调受阻 · 待恢复' : `等待 · ${traceReason(task.waitingReason)}`
|
|
739
|
+
const cleanup = task.archiveCleanup
|
|
740
|
+
const cleanupRunning = cleanup?.status === 'running' || archivingTaskId === task.taskId
|
|
741
|
+
const archiveDetailOpen = archiveDetailTaskId === task.taskId
|
|
742
|
+
const archiveNeedsReview = worktrees.length > 0 || cleanup?.status === 'failed'
|
|
743
|
+
const archiveTask = async () => {
|
|
744
|
+
if (cleanupRunning) return
|
|
745
|
+
setArchivingTaskId(task.taskId)
|
|
746
|
+
setNavigationError('')
|
|
747
|
+
try { await post(`/tasks/${encodeURIComponent(task.taskId)}/archive`); setArchiveDetailTaskId(''); await refresh() }
|
|
748
|
+
catch (cause) { setNavigationError(cause instanceof Error ? cause.message : String(cause)); await refresh().catch(() => {}) }
|
|
749
|
+
finally { setArchivingTaskId('') }
|
|
750
|
+
}
|
|
751
|
+
const archiveDetail = archiveDetailOpen ? React.createElement('section', { 'aria-label': task.archivedAt ? '归档记录' : '归档前核对', style: { display: 'grid', gap: 10, minWidth: 0, padding: 10, border: `1px solid ${colors.border}`, borderRadius: 8, background: colors.surface2, fontSize: 11, lineHeight: 1.5 } },
|
|
752
|
+
React.createElement('strong', { style: { fontSize: 12 } }, task.archivedAt ? '归档记录' : '归档前核对'),
|
|
753
|
+
!task.archivedAt ? React.createElement('span', null, ownedWorktrees.length ? `将迁出 ${ownedDocumentCount} 份文档并清理 ${ownedWorktrees.length} 个任务自建目录。` : '本次仅归档任务记录。', borrowedWorktrees.length ? ` ${borrowedWorktrees.length} 个借用目录及其登记文档会保留原处。` : '') : null,
|
|
754
|
+
cleanup?.error ? React.createElement('span', { role: 'alert', style: { color: colors.danger, overflowWrap: 'anywhere' } }, cleanup.error) : null,
|
|
755
|
+
...worktrees.map(worktree => {
|
|
756
|
+
const documents = [...new Map((worktree.documents || []).map(document => [document.source, document])).values()]
|
|
757
|
+
const label = !worktree.createdByTask ? '借用 · 保留原处' : worktree.status === 'cleaned' ? '已清理' : '任务自建 · 归档时清理'
|
|
758
|
+
return React.createElement('div', { key: worktree.path, style: { display: 'grid', gap: 4, minWidth: 0, paddingTop: 8, borderTop: `1px solid ${colors.border}` } },
|
|
759
|
+
React.createElement('strong', { style: { fontSize: 11, fontWeight: 600 } }, label, ` · ${documents.length} 份登记文档`),
|
|
760
|
+
worktree.branch ? React.createElement('span', { style: { color: colors.muted } }, `分支 ${worktree.branch}`) : null,
|
|
761
|
+
React.createElement('code', { style: { overflowWrap: 'anywhere' } }, worktree.path),
|
|
762
|
+
documents.length ? React.createElement('div', { style: { display: 'grid', gap: 3, maxHeight: 160, overflowY: 'auto', paddingLeft: 8, borderLeft: `2px solid ${colors.border}` } }, ...documents.map(document => React.createElement('div', { key: document.source, style: { overflowWrap: 'anywhere', color: colors.muted } }, document.source, document.archivePath ? React.createElement('div', { style: { fontSize: 10.5 } }, `归档至 ${document.archivePath}`) : null))) : null)
|
|
763
|
+
}),
|
|
764
|
+
!task.archivedAt ? React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8 } },
|
|
765
|
+
React.createElement('button', { type: 'button', onClick: () => setArchiveDetailTaskId(''), style: { border: `1px solid ${colors.border}`, borderRadius: 7, background: colors.cardSurface, color: 'inherit', padding: '5px 9px', cursor: 'pointer', fontFamily: 'inherit', fontSize: 11 } }, '取消'),
|
|
766
|
+
React.createElement('button', { type: 'button', disabled: cleanupRunning, onClick: archiveTask, style: { border: `1px solid ${colors.accent}`, borderRadius: 7, background: colors.accent, color: '#fff', padding: '5px 9px', cursor: cleanupRunning ? 'default' : 'pointer', fontFamily: 'inherit', fontSize: 11 } }, cleanupRunning ? '归档中…' : '确认归档')) : null) : null
|
|
350
767
|
return React.createElement('div', { key: task.taskId, onMouseEnter: () => setHoveredTaskId(task.taskId), onMouseLeave: () => setHoveredTaskId(''), style: { width: '100%', minWidth: 0, boxSizing: 'border-box', border: `1px solid ${colors.border}`, borderRadius: 12, background: colors.cardSurface, boxShadow: hovered ? '0 8px 20px rgba(15,23,42,.08), 0 2px 6px rgba(15,23,42,.05)' : 'var(--dsw-shadow-card, 0 1px 2px rgba(0,0,0,.08))', padding: '11px 13px', color: 'inherit', display: 'grid', gap: 6, opacity: queued ? 0.72 : 1, transition: 'box-shadow 180ms ease' } },
|
|
351
|
-
React.createElement('button', { type: 'button', disabled: queued || navigating, onClick: (event) => { if (event.target.closest('[data-task-card-action]')) return;
|
|
352
|
-
React.createElement('strong', { title: task.title || task.objective, style: { fontSize: 13, lineHeight: 1.45, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', overflowWrap: 'anywhere' } }, task
|
|
768
|
+
React.createElement('button', { type: 'button', disabled: task.engine !== 'workflow-v2' && (queued || navigating), onClick: (event) => { if (event.target.closest('[data-task-card-action]')) return; openTask(task) }, style: { width: '100%', minWidth: 0, boxSizing: 'border-box', border: 0, padding: 0, background: 'transparent', color: 'inherit', cursor: task.engine === 'workflow-v2' || !queued && !navigating ? 'pointer' : 'default', textAlign: 'left', display: 'grid', gap: 6, fontFamily: 'inherit' } },
|
|
769
|
+
React.createElement('strong', { title: task.title || task.objective, style: { fontSize: 13, lineHeight: 1.45, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', overflowWrap: 'anywhere' } }, taskDisplayTitle(task)),
|
|
353
770
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 5, minWidth: 0, overflow: 'hidden' } },
|
|
354
|
-
React.createElement('span', { title:
|
|
355
|
-
...(task.topicRefs || []).map((ref) => { const topic = topicsById.get(`${
|
|
771
|
+
React.createElement('span', { title: task.sourceChannel === 'web' ? `${groupLabel} · Web 重新执行` : groupLabel, style: { ...pill(colors.muted), flex: '0 1 auto', maxWidth: 150, padding: '2px 7px', fontSize: 10.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, groupLabel),
|
|
772
|
+
...(task.topicRefs || []).map((ref) => { const topicGroupId = ref.groupId || task.sourceGroupId || task.groupId; const topic = topicsById.get(`${topicGroupId}:${ref.topicId}`); const label = topic?.title || ref.title || `话题 ${short(ref.topicId)}`; return React.createElement('span', { key: ref.topicId, role: 'button', tabIndex: 0, title: label, 'aria-label': `话题 ${label}`, 'data-task-card-action': 'open-topic', onClick: (event) => { event.stopPropagation(); setTopicTarget({ groupId: topicGroupId, topicId: ref.topicId, revision: ref.revision }); setActivePage('topics') }, onKeyDown: (event) => { if (event.key !== 'Enter' && event.key !== ' ') return; event.preventDefault(); event.stopPropagation(); setTopicTarget({ groupId: topicGroupId, topicId: ref.topicId, revision: ref.revision }); setActivePage('topics') }, style: { ...pill(colors.accent), display: 'inline-block', flex: '0 1 auto', minWidth: 0, maxWidth: 96, padding: '2px 7px', fontSize: 10.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', cursor: 'pointer' } }, label) })),
|
|
356
773
|
React.createElement('div', { title: displayObjective, style: { minWidth: 0, display: 'block', maxHeight: '4.5em', overflow: 'hidden', overflowWrap: 'anywhere', fontSize: 11, lineHeight: 1.5 } }, React.createElement('svg', { width: 12, height: 12, viewBox: '0 0 16 16', fill: 'none', stroke: 'currentColor', strokeWidth: 1.4, strokeLinecap: 'round', strokeLinejoin: 'round', role: 'img', 'aria-label': '任务目标', style: { display: 'inline-block', verticalAlign: -2, marginRight: 5, color: colors.muted } }, React.createElement('circle', { cx: 8, cy: 8, r: 5.5 }), React.createElement('circle', { cx: 8, cy: 8, r: 2 }), React.createElement('path', { d: 'M8 1v2M15 8h-2' })), displayObjective),
|
|
357
774
|
React.createElement('div', null,
|
|
775
|
+
task.workflowProgress?.outcomeLabel || notificationCounts.size ? React.createElement('div', { role: 'status', style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 5, fontSize: 11, lineHeight: 1.5 } },
|
|
776
|
+
task.workflowProgress?.outcomeLabel ? React.createElement('strong', { style: { color: task.workflowProgress.outcome === 'succeeded' ? statusTone.done : colors.muted, fontWeight: 600 } }, task.workflowProgress.outcomeLabel) : null,
|
|
777
|
+
...[...notificationCounts].map(([status, count], index) => React.createElement('span', { key: status, style: { color: status === 'blocked' ? colors.warning : colors.muted } }, `${task.workflowProgress?.outcomeLabel || index ? '· ' : ''}${count > 1 ? `${count} 条` : ''}${({ pending: '通知待生成', enqueued: '通知待送达', delivered: '通知已送达', blocked: '通知受阻' })[status] || status}`))) : null,
|
|
358
778
|
showCheckpoints ? React.createElement('section', null,
|
|
359
779
|
React.createElement('div', { role: 'button', tabIndex: 0, 'aria-expanded': checkpointsExpanded, 'aria-label': checkpointsExpanded ? '收起检查点' : `展开全部 ${checkpoints.length} 个检查点`, 'data-task-card-action': 'toggle-checkpoints', onClick: (event) => { event.stopPropagation(); setExpandedCheckpointTaskId(checkpointsExpanded ? '' : task.taskId) }, onKeyDown: (event) => { if (event.key !== 'Enter' && event.key !== ' ') return; event.preventDefault(); event.stopPropagation(); setExpandedCheckpointTaskId(checkpointsExpanded ? '' : task.taskId) }, style: { minHeight: 24, boxSizing: 'border-box', display: 'flex', alignItems: 'center', gap: 8, fontSize: 10.5, cursor: 'pointer' } }, React.createElement('span', { style: { flex: '0 0 auto', display: 'inline-flex', alignItems: 'center', gap: 4, color: colors.muted } }, React.createElement(IconChecklistOutline14, { size: 12, 'aria-label': '任务' }), '任务'), React.createElement('div', { role: 'progressbar', 'aria-label': '检查点进度', 'aria-valuemin': 0, 'aria-valuemax': 100, 'aria-valuenow': progress, style: { flex: '1 1 auto', minWidth: 24, height: 4, borderRadius: 999, overflow: 'hidden', background: `color-mix(in srgb, ${colors.muted} 22%, transparent)` } }, React.createElement('div', { style: { width: `${progress}%`, height: '100%', borderRadius: 'inherit', background: completedCheckpointCount === checkpoints.length ? statusTone.done : colors.accent, transition: 'width 180ms ease' } })), React.createElement('span', { style: { flex: '0 0 auto', color: completedCheckpointCount === checkpoints.length ? statusTone.done : colors.muted } }, `${completedCheckpointCount} / ${checkpoints.length}`), React.createElement(checkpointsExpanded ? IconChevronUpOutline14 : IconChevronDownOutline14, { size: 14, 'aria-hidden': true, style: { flex: '0 0 auto', color: colors.muted } })),
|
|
360
|
-
checkpointsExpanded ? React.createElement('div', { style: { display: 'grid', gap: 4, padding: '2px 9px 9px' } }, ...checkpoints.map((checkpoint, index) => { const completed = completedCheckpointNames.has(checkpoint); const current = !completed && currentCheckpoint === checkpoint; const tone = completed ? statusTone.done : current ? colors.accent : colors.muted; const duration = checkpointDuration(checkpoint, currentCheckpointEvents, Date.now()); return React.createElement('div', { key: `${index}:${checkpoint}`, title: checkpoint, style: { minWidth: 0, display: 'grid', gridTemplateColumns: '12px minmax(0,1fr)
|
|
361
|
-
task.waitingReason ? React.createElement('div', { title: task.waitingReason, style: { fontSize:
|
|
780
|
+
checkpointsExpanded ? React.createElement('div', { style: { display: 'grid', gap: 4, padding: '2px 9px 9px' } }, ...checkpoints.map((checkpoint, index) => { const completed = completedCheckpointNames.has(checkpoint); const current = !completed && currentCheckpoint === checkpoint; const tone = completed ? statusTone.done : current ? colors.accent : colors.muted; const node = (task.executionNodes || []).find(node => node.nodeId === checkpoint); const duration = node ? React.createElement(TaskStepElapsed, { node, fontSize: 10.5 }) : checkpointDuration(checkpointLabel(checkpoint), currentCheckpointEvents, Date.now()); return React.createElement('div', { key: `${index}:${checkpoint}`, title: checkpointLabel(checkpoint), style: { minWidth: 0, display: 'grid', gridTemplateColumns: '12px minmax(0,1fr) max-content', gap: 5, alignItems: 'start', fontSize: 10.5, lineHeight: 1.4 } }, React.createElement('span', { 'aria-hidden': true, style: { width: 12, height: 14, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: tone } }, completed ? React.createElement(CheckpointDoneIcon, { size: 12 }) : current ? React.createElement(StateDot, { state: 'ongoing', size: 10 }) : '○'), React.createElement('span', { style: { color: 'inherit', display: '-webkit-box', WebkitLineClamp: 'unset', WebkitBoxOrient: 'vertical', overflow: 'hidden', overflowWrap: 'anywhere' } }, checkpointLabel(checkpoint)), React.createElement('span', { title: node ? '本次执行耗时' : `执行时长 ${duration}`, style: { color: colors.muted, textAlign: 'right', whiteSpace: 'nowrap', fontVariantNumeric: 'tabular-nums' } }, duration)) })) : null) : null,
|
|
781
|
+
task.waitingReason ? React.createElement('div', { title: task.waitingReason, style: { fontSize: 11, lineHeight: 1.5, color: colors.warning, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', overflowWrap: 'anywhere' } }, waitingLabel) : null,
|
|
782
|
+
task.waitingReason && task.waitingKind === 'information' ? React.createElement('div', { style: { fontSize: 10.5, color: colors.muted } }, waitingNoticeLabel, task.requesterName ? ` · 发起人 ${task.requesterName}` : '') : null),
|
|
783
|
+
),
|
|
784
|
+
cleanup?.status === 'failed' && !task.archivedAt ? React.createElement('div', { role: 'alert', style: { fontSize: 11, color: colors.danger } }, '归档失败 · 查看原因后重试') : null,
|
|
362
785
|
React.createElement('div', { style: { minWidth: 0, marginTop: 0, paddingTop: 6, borderTop: `1px solid ${colors.border}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 } },
|
|
363
786
|
React.createElement('span', { title: `最后活动 ${fmt(task.updatedAt)}`, style: { minWidth: 0, display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 10.5, color: colors.muted, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, React.createElement('svg', { width: 12, height: 12, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-label': '最后活动时间' }, React.createElement('circle', { cx: 12, cy: 12, r: 9 }), React.createElement('path', { d: 'M12 7v5l3 2' })), fmt(task.updatedAt)),
|
|
364
|
-
task.state === 'completed' && !task.archivedAt ? React.createElement('button', { type: 'button', onClick:
|
|
365
|
-
|
|
787
|
+
task.state === 'completed' && (!task.archivedAt || worktrees.length) ? React.createElement('button', { type: 'button', disabled: cleanupRunning, 'aria-busy': cleanupRunning, 'aria-expanded': archiveNeedsReview || task.archivedAt ? archiveDetailOpen : undefined, onClick: (event) => { event.stopPropagation(); archiveNeedsReview || task.archivedAt ? setArchiveDetailTaskId(archiveDetailOpen ? '' : task.taskId) : archiveTask() }, style: { flex: '0 0 auto', border: `1px solid ${colors.border}`, borderRadius: 7, background: colors.surface2, color: cleanupRunning ? colors.muted : colors.accent, padding: '3px 7px', cursor: cleanupRunning ? 'default' : 'pointer', fontFamily: 'inherit', fontSize: 10.5 } }, cleanupRunning ? '归档中…' : task.archivedAt ? archiveDetailOpen ? '收起记录' : '归档记录' : archiveDetailOpen ? '收起预览' : cleanup?.status === 'failed' ? '查看并重试' : archiveNeedsReview ? '检查并归档' : '归档') : null),
|
|
788
|
+
archiveDetail,
|
|
789
|
+
navigating && task.engine !== 'workflow-v2' ? React.createElement('div', { role: 'status', style: { fontSize: 11.5, color: colors.accent } }, '正在打开会话…') : queued && task.engine !== 'workflow-v2' ? React.createElement('div', { style: { fontSize: 11.5, color: colors.muted } }, '等待执行,尚无对话和轨迹') : null)
|
|
366
790
|
}
|
|
367
791
|
const alertCategories = [
|
|
368
792
|
{ id: 'all', label: '全部类型' },
|
|
@@ -422,10 +846,10 @@ window.__ModuleLoader__.load({
|
|
|
422
846
|
const groupTableToolbar = React.createElement('div', { style: toolbar },
|
|
423
847
|
dataViewTabs,
|
|
424
848
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 8, flexWrap: 'wrap' } },
|
|
425
|
-
(data?.groups || []).length ? React.createElement(SelectMenu, { label: '
|
|
849
|
+
(data?.groups || []).length ? React.createElement(SelectMenu, { label: '选择群聊', value: selectedGroup?.groupId || '', options: (data?.groups || []).map((group) => ({ id: group.groupId, label: group.name || group.groupId })), onChange: (value) => { setSelectedGroupId(value); setMessagePage(1); setOutboxPage(1); setSelectedMessageRunId('') }, fitContent: true }) : null,
|
|
426
850
|
groupTableView === 'messages'
|
|
427
|
-
? React.createElement(SelectMenu, { label: '筛选处理状态', value: messageDeliveryFilter, options: [{ id: 'all', label: '全部处理状态' }, { id: 'routing', label: '待归类' }, { id: 'processing', label: '话题处理中' }, { id: 'processed', label: '已处理' }, { id: 'failed', label: '归类失败' }], onChange: (value) => { setMessageDeliveryFilter(value); setMessagePage(1) } })
|
|
428
|
-
: React.createElement(SelectMenu, { label: '筛选发件状态', value: outboxStatusFilter, options: [{ id: 'all', label: '全部发件状态' }, { id: 'queued', label: '待发送' }, { id: 'failed', label: '投递异常' }, { id: 'waiting', label: '待回读' }, { id: 'confirmed', label: '已发送' }, { id: 'recalled', label: '已撤回' }], onChange: (value) => { setOutboxStatusFilter(value); setOutboxPage(1) } })))
|
|
851
|
+
? React.createElement(SelectMenu, { label: '筛选处理状态', value: messageDeliveryFilter, options: [{ id: 'all', label: '全部处理状态' }, { id: 'routing', label: '待归类' }, { id: 'waiting_routing_barrier', label: '等待其他消息关联' }, { id: 'intent_judging', label: '意图判断中' }, { id: 'intent_rejudging', label: '意图重新判断' }, { id: 'waiting_clarification', label: '等待澄清' }, { id: 'waiting_context', label: '等待补充材料' }, { id: 'routing_blocked', label: '关联受阻' }, { id: 'processing', label: '话题处理中' }, { id: 'processed', label: '已处理' }, { id: 'failed', label: '归类失败' }], onChange: (value) => { setMessageDeliveryFilter(value); setMessagePage(1) } })
|
|
852
|
+
: React.createElement(SelectMenu, { label: '筛选发件状态', value: outboxStatusFilter, options: [{ id: 'all', label: '全部发件状态' }, { id: 'queued', label: '待发送' }, { id: 'failed', label: '投递异常' }, { id: 'waiting', label: '待回读' }, { id: 'confirmed', label: '已发送' }, { id: 'superseded', label: '已替代' }, { id: 'recall-failed', label: '撤回待处理' }, { id: 'recalled', label: '已撤回' }], onChange: (value) => { setOutboxStatusFilter(value); setOutboxPage(1) } })))
|
|
429
853
|
const messagesTable = React.createElement(React.Fragment, null,
|
|
430
854
|
React.createElement('div', { style: { overflowX: 'auto' } }, React.createElement('table', { style: { width: '100%', minWidth: 910, borderCollapse: 'collapse', tableLayout: 'fixed' } },
|
|
431
855
|
React.createElement('thead', null, React.createElement('tr', { style: { background: colors.surface2, textAlign: 'left' } }, React.createElement('th', { style: { ...tableHeadCell, width: 104 } }, '话题处理'), React.createElement('th', { style: { ...tableHeadCell, width: 160 } }, '发送人 / 时间'), React.createElement('th', { style: { ...tableHeadCell, width: 160 } }, '话题'), React.createElement('th', { style: tableHeadCell }, '消息内容'), React.createElement('th', { style: { ...tableHeadCell, width: 240 } }, '消息'))),
|
|
@@ -436,14 +860,85 @@ window.__ModuleLoader__.load({
|
|
|
436
860
|
React.createElement('thead', null, React.createElement('tr', { style: { background: colors.surface2, textAlign: 'left' } }, React.createElement('th', { style: { ...tableHeadCell, width: 124 } }, '发送状态'), React.createElement('th', { style: tableHeadCell }, '消息内容'), React.createElement('th', { style: { ...tableHeadCell, width: 220 } }, '来源 / 回复目标'), React.createElement('th', { style: { ...tableHeadCell, width: 220 } }, '投递消息 ID'), React.createElement('th', { style: { ...tableHeadCell, width: 220 } }, '发信箱 ID'))),
|
|
437
861
|
React.createElement('tbody', null, ...(outboxRows.length ? outboxRows : [React.createElement('tr', { key: 'empty' }, React.createElement('td', { colSpan: 5, style: { ...tableBodyCell, padding: 36, textAlign: 'center', color: colors.muted } }, '暂无符合条件的 Agent 发件记录'))])))),
|
|
438
862
|
React.createElement('div', { style: tableFooter }, React.createElement('span', { style: { marginRight: 'auto', fontSize: 11, color: colors.muted } }, `${filteredOutbox.length} 条 · 每页 ${outboxPageSize} 条`), React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: currentOutboxPage <= 1, onClick: () => setOutboxPage((page) => Math.max(1, page - 1)) }, '上一页'), React.createElement('span', { style: { fontSize: 11, color: colors.muted } }, `${currentOutboxPage} / ${outboxPageCount}`), React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: currentOutboxPage >= outboxPageCount, onClick: () => setOutboxPage((page) => Math.min(outboxPageCount, page + 1)) }, '下一页')))
|
|
439
|
-
const groupsPage = React.createElement('section', null, React.createElement('div', { style: tableFrame }, groupTableToolbar, groupTableView === 'messages' ? messagesTable : outboxTable))
|
|
440
|
-
const
|
|
863
|
+
const groupsPage = selectedMessageRunId ? React.createElement(TraceDetail, { runId: selectedMessageRunId, onOpenSession: navigate, onBack: () => setSelectedMessageRunId('') }) : React.createElement('section', null, React.createElement('div', { style: tableFrame }, groupTableToolbar, groupTableView === 'messages' ? messagesTable : outboxTable))
|
|
864
|
+
const selectedWorkflowTask = (workflowTaskDetail?.taskId === selectedWorkflowTaskId || workflowTaskDetail?.requestedTaskId === selectedWorkflowTaskId) ? workflowTaskDetail : undefined
|
|
865
|
+
const nodeState = { succeeded: '已完成', running: '执行中', ready: '待执行', waiting: '等待处理', failed: '失败', pending: '未开始', cancelled: '已取消', skipped: '已跳过' }
|
|
866
|
+
const planStageState = { ready: '待执行', waiting_confirmation: '等待人工确认', running: '执行中', succeeded: '已完成', invalidated: '需重新执行', blocked: '受阻' }
|
|
867
|
+
|
|
868
|
+
const taskNodes = selectedWorkflowTask?.executionNodes || []
|
|
869
|
+
const completedSteps = taskNodes.filter(node => !node.definitionPending && node.status === 'succeeded').length
|
|
870
|
+
const stepsResolved = selectedWorkflowTask?.plan?.stepsResolved !== false && !taskNodes.some(node => node.definitionPending)
|
|
871
|
+
const stepProgress = taskNodes.length ? Math.min(stepsResolved ? 100 : 99, Math.round(completedSteps / taskNodes.length * 100)) : 0
|
|
872
|
+
const taskTone = selectedWorkflowTask?.outcome === 'failed' ? colors.danger : selectedWorkflowTask?.state === 'waiting' ? colors.warning : selectedWorkflowTask?.outcome === 'succeeded' ? 'var(--dsw-alias-state-success-primary, #248a3d)' : colors.accent
|
|
873
|
+
const taskStatus = selectedWorkflowTask?.workflowProgress?.outcomeLabel || ({ succeeded: '已完成', failed: '执行失败', cancelled: '已取消' }[selectedWorkflowTask?.outcome]) || ({ completed: '已结束', running: '执行中', waiting: '等待处理', queued: '待执行' }[selectedWorkflowTask?.state]) || '状态未记录'
|
|
874
|
+
const activeStep = taskNodes.find(node => ['running', 'waiting', 'failed'].includes(node.status))
|
|
875
|
+
const stepColor = (status) => status === 'succeeded' ? 'var(--dsw-alias-state-success-primary, #248a3d)' : status === 'failed' ? colors.danger : status === 'waiting' ? colors.warning : status === 'running' ? colors.accent : colors.muted
|
|
876
|
+
const workflowDetail = selectedWorkflowTask ? React.createElement('section', { 'aria-label': '任务执行详情', style: { maxWidth: 1120, margin: '0 auto', minWidth: 0, overflowWrap: 'anywhere' } },
|
|
877
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16, fontSize: 12, color: colors.muted } },
|
|
878
|
+
React.createElement('span', null, '任务看板 / 任务详情'),
|
|
879
|
+
React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', onClick: () => setSelectedWorkflowTaskId('') }, '返回看板')),
|
|
880
|
+
stepReadingNotice ? React.createElement('p', { role: 'status', 'aria-live': 'polite', style: { color: colors.muted, fontSize: 12 } }, stepReadingNotice) : null,
|
|
881
|
+
workflowDetailError ? React.createElement('div', { role: 'alert', style: { marginTop: 12, color: colors.danger, fontSize: 12 } }, `当前详情刷新失败,以下内容尚未刷新:${workflowDetailError}`, React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', onClick: () => setWorkflowDetailRetry(value => value + 1) }, '重试读取详情')) : null,
|
|
882
|
+
React.createElement('header', { className: 'observer-task-overview', style: { borderBottom: `1px solid ${colors.border}`, marginBottom: 16 } },
|
|
883
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 12, marginBottom: 12, fontSize: 12 } },
|
|
884
|
+
React.createElement('span', { style: { ...pill(taskTone), fontWeight: 600 } }, taskStatus),
|
|
885
|
+
React.createElement('span', { style: { color: colors.muted } }, `最近更新 ${fmt(selectedWorkflowTask.updatedAt)}`)),
|
|
886
|
+
React.createElement('h1', { title: selectedWorkflowTask.title || selectedWorkflowTask.objective, style: { margin: 0, fontSize: 22, fontWeight: 650, lineHeight: 1.45, letterSpacing: '-.02em', maxWidth: 900 } }, taskDisplayTitle(selectedWorkflowTask)),
|
|
887
|
+
React.createElement('div', { style: { marginTop: 16, display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: '12px 24px', fontSize: 12 } },
|
|
888
|
+
React.createElement('span', { style: { color: colors.muted, fontVariantNumeric: 'tabular-nums' } }, taskNodes.length ? `已完成 ${completedSteps} / ${taskNodes.length} 个步骤` : stepsResolved ? '暂无步骤记录' : '步骤待确定'),
|
|
889
|
+
activeStep ? React.createElement('span', { style: { color: stepColor(activeStep.status) } }, `当前 · ${activeStep.title || nodeTitle[activeStep.nodeId] || '执行步骤'}`) : null,
|
|
890
|
+
React.createElement(TaskTotalElapsed, { key: selectedWorkflowTask.taskId, task: selectedWorkflowTask }))),
|
|
891
|
+
selectedWorkflowTask.objective ? React.createElement('details', { style: { margin: '0 0 16px', color: colors.muted, fontSize: 14, lineHeight: 1.7 } },
|
|
892
|
+
React.createElement('summary', { style: { cursor: 'pointer' } }, '任务目标'),
|
|
893
|
+
React.createElement('p', { style: { margin: '8px 0 0', whiteSpace: 'pre-wrap' } }, selectedWorkflowTask.objective)) : null,
|
|
894
|
+
selectedWorkflowTask.waitingReason ? React.createElement('div', { role: 'status', style: { marginBottom: 16, padding: '14px 18px', borderLeft: `3px solid ${colors.warning}`, background: colors.surface2, fontSize: 14, lineHeight: 1.7 } }, traceReason(selectedWorkflowTask.waitingReason)) : null,
|
|
895
|
+
selectedWorkflowTask.investigationRequest ? React.createElement(WorkflowClarification, { key: selectedWorkflowTask.investigationRequest.requestId, request: selectedWorkflowTask.investigationRequest, onSubmitted: manualRefresh }) : null,
|
|
896
|
+
selectedWorkflowTask.plan?.stages?.length ? React.createElement('div', { 'aria-label': '任务阶段', style: { display: 'flex', flexWrap: 'wrap', gap: '8px 24px', marginBottom: 16, fontSize: 12, color: colors.muted } }, ...selectedWorkflowTask.plan.stages.map((stage, index) => React.createElement('span', { key: stage.stageId }, `${String(index + 1).padStart(2, '0')} · ${stage.title || '任务阶段'} · ${planStageState[stage.status] || '状态未记录'}`))) : null,
|
|
897
|
+
React.createElement('section', { 'aria-label': '执行步骤' },
|
|
898
|
+
React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 16, marginBottom: 12 } },
|
|
899
|
+
React.createElement('h2', { style: { margin: 0, fontSize: 16, fontWeight: 650 }, tabIndex: -1 }, '执行步骤'),
|
|
900
|
+
taskNodes.length || !stepsResolved ? React.createElement('span', { style: { fontSize: 12, color: colors.muted, fontVariantNumeric: 'tabular-nums' } }, stepsResolved ? `步骤完成 ${stepProgress}%` : '步骤待确定') : null),
|
|
901
|
+
taskNodes.length ? React.createElement('div', { role: 'progressbar', 'aria-label': '步骤完成情况', 'aria-valuemin': 0, 'aria-valuemax': 100, 'aria-valuenow': stepProgress, 'aria-valuetext': stepsResolved ? `步骤完成 ${stepProgress}%` : '步骤待确定', style: { height: 3, background: colors.border, marginBottom: 24 } }, React.createElement('div', { style: { width: `${stepProgress}%`, height: '100%', background: taskTone } })) : null,
|
|
902
|
+
!taskNodes.length ? React.createElement('p', { style: { padding: '20px 0', color: colors.muted, fontSize: 14 } }, stepsResolved ? '暂无执行步骤记录' : '该阶段步骤待确定') : null,
|
|
903
|
+
...groupTaskSteps(taskNodes).map((group, groupIndex) => React.createElement('section', {
|
|
904
|
+
key: group.key, className: 'observer-task-workflow', 'aria-label': `工作流 ${group.title}`,
|
|
905
|
+
style: { marginTop: 16, minWidth: 0 } },
|
|
906
|
+
React.createElement('h3', { style: { margin: '0 0 14px', padding: '10px 0', borderBottom: `1px solid ${colors.border}`, fontSize: 14, fontWeight: 600 } },
|
|
907
|
+
`工作流 ${groupIndex + 1} · ${group.title}`, React.createElement('span', { style: { marginLeft: 12, color: colors.muted, fontSize: 12, fontWeight: 400 } }, `${group.steps.length} 个步骤`)),
|
|
908
|
+
React.createElement('ol', { style: { margin: 0, padding: 0, listStyle: 'none' } }, ...group.steps.map(({ node, index }) => {
|
|
909
|
+
const tone = stepColor(node.status)
|
|
910
|
+
return React.createElement('li', { key: node.stepKey, 'data-step-key': node.stepKey, tabIndex: -1, className: 'observer-task-step', 'aria-current': node.status === 'running' ? 'step' : undefined },
|
|
911
|
+
React.createElement('span', { 'aria-label': `步骤 ${index + 1}`, style: { display: 'grid', placeItems: 'center', width: 28, height: 28, boxSizing: 'border-box', borderRadius: '50%', border: `${node.status === 'running' ? 2 : 1}px solid ${tone}`, background: colors.surface, color: tone, fontSize: 12, fontWeight: 600, fontVariantNumeric: 'tabular-nums' } }, String(index + 1).padStart(2, '0')),
|
|
912
|
+
React.createElement('div', { style: { minWidth: 0 } },
|
|
913
|
+
React.createElement('div', { className: 'observer-task-step-heading' },
|
|
914
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'baseline', flexWrap: 'wrap', gap: '4px 10px' } },
|
|
915
|
+
React.createElement('strong', { style: { fontSize: 14, lineHeight: 1.6 } }, node.title || nodeTitle[node.nodeId] || '执行步骤'),
|
|
916
|
+
React.createElement('span', { style: { color: tone, fontSize: 12 } }, nodeState[node.status] || '状态未记录')),
|
|
917
|
+
React.createElement('div', { style: { marginLeft: 'auto', textAlign: 'right', fontVariantNumeric: 'tabular-nums' } }, React.createElement(TaskStepElapsed, { node }))),
|
|
918
|
+
React.createElement('span', { style: { display: 'block', marginBottom: 6, color: colors.muted, fontSize: 12, fontVariantNumeric: 'tabular-nums' } },
|
|
919
|
+
Number.isFinite(Date.parse(node.startedAt)) ? React.createElement('time', { dateTime: node.startedAt }, taskStepStart(node)) : taskStepStart(node)),
|
|
920
|
+
node.waitReason?.reference ? React.createElement('p', { style: { margin: '0 0 6px', fontSize: 13, lineHeight: 1.7, color: colors.warning } }, traceReason(node.waitReason.reference)) : null,
|
|
921
|
+
node.outputRef ? React.createElement(TaskStepOutput, { key: `${node.nodeRunId}:${node.outputRef}`, taskId: selectedWorkflowTask.taskId, node, detailRevision: selectedWorkflowTask.detailRevision }) : React.createElement('span', { style: { color: colors.muted, fontSize: 13 } }, node.definitionPending ? '该阶段步骤待确定' : ['pending', 'ready', 'blocked'].includes(node.status) ? '执行后将在这里展示产出' : '暂未记录产出'),
|
|
922
|
+
node.sessionId ? React.createElement('button', { type: 'button', onClick: () => navigate(node.sessionId), style: { display: 'block', marginTop: 6, padding: 0, border: 0, color: colors.accent, background: 'transparent', font: 'inherit', fontSize: 12, cursor: 'pointer' } }, '查看会话记录') : null))
|
|
923
|
+
}))))),
|
|
924
|
+
React.createElement('section', { 'aria-label': '当前结果', style: { marginTop: 24, padding: '24px 0', borderTop: `1px solid ${colors.border}` } },
|
|
925
|
+
React.createElement('h2', { style: { margin: '0 0 12px', fontSize: 16, fontWeight: 650 } }, '当前结果'),
|
|
926
|
+
React.createElement('div', { style: { fontSize: 14, lineHeight: 1.8, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } }, ...linkedTaskText(selectedWorkflowTask.result || '暂未记录产出')),
|
|
927
|
+
selectedWorkflowTask.result ? React.createElement('p', { style: { margin: '16px 0 0', color: colors.muted, fontSize: 12 } }, '送达情况请查看发件记录。') : null))
|
|
928
|
+
: null
|
|
929
|
+
const workflowDetailPending = selectedWorkflowTaskId && !selectedWorkflowTask ? React.createElement('section', { 'aria-label': '任务详情读取', style: { display: 'grid', gap: 12 } }, React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', onClick: () => setSelectedWorkflowTaskId('') }, '返回看板'), workflowDetailError ? React.createElement('div', { role: 'alert', style: { color: colors.danger } }, `任务详情读取失败:${workflowDetailError}`, React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', onClick: () => setWorkflowDetailRetry(value => value + 1) }, '重试读取详情')) : React.createElement('span', { role: 'status' }, '正在读取当前任务详情…')) : null
|
|
930
|
+
const tasksPage = workflowDetailPending || workflowDetail || React.createElement('div', { className: 'observer-task-board', style: { display: 'grid', gridTemplateColumns: 'repeat(4, minmax(300px, 1fr))', gap: ui.space3, overflowX: 'auto', alignItems: 'start', paddingBottom: ui.space2 } }, ...bucketColumns)
|
|
441
931
|
const authorizationStatus = {
|
|
442
932
|
'pending-send': { label: '待发送', state: 'warning' },
|
|
443
933
|
'waiting-reply': { label: '等待处理', state: 'warning' },
|
|
444
934
|
answered: { label: '已处理', state: 'done' },
|
|
935
|
+
superseded: { label: '已失效', state: 'neutral' },
|
|
445
936
|
}
|
|
446
937
|
const authorizationDecision = { approved: { label: '已继续', state: 'done' }, rejected: { label: '不执行', state: 'error' } }
|
|
938
|
+
const isPendingAuthorization = (item) => item.status === 'pending-send' || item.status === 'waiting-reply'
|
|
939
|
+
const authorizationPresentation = (item) => item.status === 'answered'
|
|
940
|
+
? authorizationDecision[item.decision] || authorizationStatus.answered
|
|
941
|
+
: authorizationStatus[item.status] || { label: '状态异常', state: 'error' }
|
|
447
942
|
const decideAuthorization = async (requestId, decision) => {
|
|
448
943
|
try {
|
|
449
944
|
setNavigationError(undefined); setDecidingAuthorizationId(requestId)
|
|
@@ -453,21 +948,21 @@ window.__ModuleLoader__.load({
|
|
|
453
948
|
finally { setDecidingAuthorizationId('') }
|
|
454
949
|
}
|
|
455
950
|
const authorizationItems = [...(data?.authorizations || [])].sort((left, right) => {
|
|
456
|
-
const leftPending = left
|
|
951
|
+
const leftPending = isPendingAuthorization(left) ? 1 : 0; const rightPending = isPendingAuthorization(right) ? 1 : 0
|
|
457
952
|
return rightPending - leftPending || String(right.createdAt || '').localeCompare(String(left.createdAt || ''))
|
|
458
953
|
})
|
|
459
|
-
const filteredAuthorizationItems = authorizationItems.filter((item) => authorizationFilter === 'all' || (authorizationFilter === 'pending' ? item.status
|
|
954
|
+
const filteredAuthorizationItems = authorizationItems.filter((item) => authorizationFilter === 'all' || (authorizationFilter === 'pending' ? isPendingAuthorization(item) : authorizationFilter === 'superseded' ? item.status === 'superseded' : item.decision === authorizationFilter))
|
|
460
955
|
const authorizationPageSize = 10
|
|
461
956
|
const authorizationPageCount = Math.max(1, Math.ceil(filteredAuthorizationItems.length / authorizationPageSize))
|
|
462
957
|
const currentAuthorizationPage = Math.min(authorizationPage, authorizationPageCount)
|
|
463
958
|
const visibleAuthorizationItems = filteredAuthorizationItems.slice((currentAuthorizationPage - 1) * authorizationPageSize, currentAuthorizationPage * authorizationPageSize)
|
|
464
959
|
const selectedAuthorization = authorizationItems.find((item) => item.requestId === selectedAuthorizationId)
|
|
960
|
+
const selectedAuthorizationPending = selectedAuthorization ? isPendingAuthorization(selectedAuthorization) : false
|
|
465
961
|
const authorizationDetailField = (label, value) => React.createElement('div', { style: { display: 'grid', gap: 5 } },
|
|
466
962
|
React.createElement('strong', { style: { fontSize: 11, color: colors.muted } }, label),
|
|
467
963
|
React.createElement('div', { style: { fontSize: 13, lineHeight: 1.65, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } }, value || '—'))
|
|
468
964
|
const authorizationRows = visibleAuthorizationItems.map((item, rowIndex) => {
|
|
469
|
-
const
|
|
470
|
-
const status = pending ? authorizationStatus[item.status] : authorizationDecision[item.decision] || authorizationStatus.answered
|
|
965
|
+
const status = authorizationPresentation(item)
|
|
471
966
|
const group = groupsById.get(item.groupId)
|
|
472
967
|
return React.createElement('div', { key: item.requestId, style: { width: '100%', minWidth: 0, boxSizing: 'border-box', display: 'grid', gridTemplateColumns: '104px minmax(220px, 1.1fr) minmax(280px, 1.5fr) 126px 52px', alignItems: 'center', gap: 16, borderTop: `1px solid ${colors.border}`, background: rowIndex % 2 ? `color-mix(in srgb, ${colors.surface2} 55%, transparent)` : colors.cardSurface, color: 'inherit', padding: '12px 16px', fontSize: 14 } },
|
|
473
968
|
React.createElement('div', { style: { justifySelf: 'start' } }, tableStatusTag(status.label, status.state, { fontWeight: 600 })),
|
|
@@ -479,7 +974,7 @@ window.__ModuleLoader__.load({
|
|
|
479
974
|
const authorizationDetail = selectedAuthorization ? React.createElement('div', { role: 'presentation', onMouseDown: (event) => { if (event.target === event.currentTarget) setSelectedAuthorizationId('') }, style: { position: 'fixed', inset: 0, zIndex: 1100, display: 'flex', justifyContent: 'flex-end', background: 'rgba(0,0,0,.28)', backdropFilter: 'blur(2px)' } },
|
|
480
975
|
React.createElement('section', { role: 'dialog', 'aria-modal': true, 'aria-label': '人工介入事项详情', style: { width: 'min(640px, 100%)', height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden', borderLeft: `1px solid ${colors.border}`, background: colors.surface, boxShadow: '-20px 0 60px rgba(0,0,0,.2)' } },
|
|
481
976
|
React.createElement('header', { style: { flex: '0 0 auto', display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 24, padding: '24px 24px 20px', borderBottom: `1px solid ${colors.border}`, background: colors.surface } },
|
|
482
|
-
React.createElement('div', { style: { minWidth: 0 } }, React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 } }, statusTag(
|
|
977
|
+
React.createElement('div', { style: { minWidth: 0 } }, React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 } }, statusTag(authorizationPresentation(selectedAuthorization).label, authorizationPresentation(selectedAuthorization).state), React.createElement('span', { style: { color: colors.muted, fontSize: 11.5 } }, fmt(selectedAuthorization.createdAt))), React.createElement('h3', { style: { margin: 0, fontSize: 18, fontWeight: 600, lineHeight: 1.5 } }, selectedAuthorization.objective || '人工介入事项'), React.createElement('code', { style: { display: 'block', marginTop: 10, color: colors.muted, fontSize: 10.5 } }, selectedAuthorization.requestId)),
|
|
483
978
|
React.createElement(Button, { variant: 'ghost', size: 'sm', type: 'button', 'aria-label': '关闭人工介入事项', onClick: () => setSelectedAuthorizationId(''), style: { flex: '0 0 auto', minWidth: 52, whiteSpace: 'nowrap' } }, '关闭')),
|
|
484
979
|
React.createElement('div', { style: { flex: '1 1 auto', overflowY: 'auto', padding: '0 24px 24px' } },
|
|
485
980
|
React.createElement('section', { style: { padding: '24px 0', borderBottom: `1px solid ${colors.border}` } }, authorizationDetailField('需要人工处理', selectedAuthorization.requestedAction)),
|
|
@@ -489,13 +984,13 @@ window.__ModuleLoader__.load({
|
|
|
489
984
|
React.createElement('details', { style: { padding: '18px 0', borderBottom: `1px solid ${colors.border}` } }, React.createElement('summary', { style: { cursor: 'pointer', fontSize: 12, fontWeight: 600 } }, `已尝试 · ${(selectedAuthorization.attemptedActions || []).length}`), React.createElement('div', { style: { marginTop: 14 } }, authorizationDetailField('', (selectedAuthorization.attemptedActions || []).map((value, index) => `${index + 1}. ${value}`).join('\n')))),
|
|
490
985
|
React.createElement('section', { style: { padding: '20px 0', borderBottom: `1px solid ${colors.border}` } }, authorizationDetailField('关联信息', `Task ID:${selectedAuthorization.taskId || '—'}\n群聊:${groupsById.get(selectedAuthorization.groupId)?.name || selectedAuthorization.groupId || '—'}`)),
|
|
491
986
|
selectedAuthorization.status === 'answered' ? React.createElement('section', { style: { padding: '20px 0' } }, authorizationDetailField('处理结果', `${selectedAuthorization.decision === 'approved' ? '继续任务' : selectedAuthorization.decision === 'rejected' ? '不执行' : selectedAuthorization.decision || '已处理'}\n处理渠道:${selectedAuthorization.decisionSource === 'web' ? '运行看板' : selectedAuthorization.decisionSource === 'dingtalk' ? '钉钉私聊' : selectedAuthorization.decisionSource || '历史迁移'}\n处理时间:${fmt(selectedAuthorization.decidedAt)}\n处理意见:${selectedAuthorization.reply || '—'}`)) : null),
|
|
492
|
-
|
|
493
|
-
React.createElement('input', { 'aria-label':
|
|
987
|
+
selectedAuthorizationPending ? React.createElement('footer', { style: { flex: '0 0 auto', display: 'grid', gridTemplateColumns: '1fr auto auto', gap: 10, alignItems: 'center', padding: '16px 24px', borderTop: `1px solid ${colors.border}`, background: colors.surface2 } },
|
|
988
|
+
React.createElement('input', { 'aria-label': `执行限制或拒绝原因 ${selectedAuthorization.requestId}`, value: authorizationComments[selectedAuthorization.requestId] || '', onChange: (event) => setAuthorizationComments((current) => ({ ...current, [selectedAuthorization.requestId]: event.target.value })), placeholder: '执行限制或拒绝原因(可选)', style: { minWidth: 0, border: `1px solid ${colors.border}`, borderRadius: 9, background: colors.surface2, color: 'inherit', padding: '9px 11px', fontFamily: 'inherit', fontSize: 12 } }),
|
|
494
989
|
React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: decidingAuthorizationId === selectedAuthorization.requestId, onClick: () => decideAuthorization(selectedAuthorization.requestId, 'rejected'), style: { color: colors.danger } }, '不执行'),
|
|
495
|
-
React.createElement(Button, { variant: 'primary', size: 'sm', type: 'button', disabled: decidingAuthorizationId === selectedAuthorization.requestId, onClick: () => decideAuthorization(selectedAuthorization.requestId, 'approved') }, decidingAuthorizationId === selectedAuthorization.requestId ? '处理中…' : '
|
|
990
|
+
React.createElement(Button, { variant: 'primary', size: 'sm', type: 'button', disabled: decidingAuthorizationId === selectedAuthorization.requestId, onClick: () => decideAuthorization(selectedAuthorization.requestId, 'approved') }, decidingAuthorizationId === selectedAuthorization.requestId ? '处理中…' : '批准该事项并继续')) : null)) : null
|
|
496
991
|
const authorizationsPage = React.createElement(React.Fragment, null, React.createElement('section', null,
|
|
497
992
|
React.createElement('div', { style: tableFrame },
|
|
498
|
-
React.createElement('div', { style: { ...toolbar, justifyContent: 'flex-end' } }, React.createElement(SelectMenu, { label: '筛选处理状态', value: authorizationFilter, options: [{ id: 'all', label: '全部处理状态' }, { id: 'pending', label: '等待处理' }, { id: 'approved', label: '已继续' }, { id: 'rejected', label: '不执行' }], onChange: (value) => { setAuthorizationFilter(value); setAuthorizationPage(1) }, fitContent: true })),
|
|
993
|
+
React.createElement('div', { style: { ...toolbar, justifyContent: 'flex-end' } }, React.createElement(SelectMenu, { label: '筛选处理状态', value: authorizationFilter, options: [{ id: 'all', label: '全部处理状态' }, { id: 'pending', label: '等待处理' }, { id: 'approved', label: '已继续' }, { id: 'rejected', label: '不执行' }, { id: 'superseded', label: '已失效' }], onChange: (value) => { setAuthorizationFilter(value); setAuthorizationPage(1) }, fitContent: true })),
|
|
499
994
|
React.createElement('div', { style: { height: 42, boxSizing: 'border-box', display: 'grid', gridTemplateColumns: '104px minmax(220px, 1.1fr) minmax(280px, 1.5fr) 126px 52px', alignItems: 'center', gap: 16, padding: '0 16px', background: colors.surface2, color: colors.muted, fontSize: 13, fontWeight: 600 } }, React.createElement('span', null, '状态'), React.createElement('span', null, '任务'), React.createElement('span', null, '阻塞事项'), React.createElement('span', null, '提出时间'), React.createElement('span', null, '操作')),
|
|
500
995
|
React.createElement('div', null, ...(authorizationRows.length ? authorizationRows : [React.createElement('div', { key: 'empty', style: { padding: 36, textAlign: 'center', color: colors.muted, fontSize: 12 } }, '暂无人工介入事项')])),
|
|
501
996
|
React.createElement('div', { style: tableFooter },
|
|
@@ -514,15 +1009,15 @@ window.__ModuleLoader__.load({
|
|
|
514
1009
|
React.createElement('div', { style: { padding: '0 16px' } }, ...(alertView === 'active' ? (filteredActiveAlerts.length ? filteredActiveAlerts.map(renderAlert) : [React.createElement('div', { key: 'empty', style: { padding: '36px 0', textAlign: 'center', color: colors.muted, fontSize: 12 } }, '当前没有此类型异常')]) : (visibleResolvedAlerts.length ? visibleResolvedAlerts.map(renderAlert) : [React.createElement('div', { key: 'empty', style: { padding: '36px 0', textAlign: 'center', color: colors.muted, fontSize: 12 } }, '暂无此类型恢复记录')]))),
|
|
515
1010
|
alertView === 'resolved' ? React.createElement('div', { style: tableFooter }, React.createElement('span', { style: { marginRight: 'auto', fontSize: 11, color: colors.muted } }, `${filteredResolvedAlerts.length} 条 · 每页 ${resolvedAlertPageSize} 条`), React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: currentResolvedAlertPage <= 1, onClick: () => setResolvedAlertPage((page) => Math.max(1, page - 1)) }, '上一页'), React.createElement('span', { style: { fontSize: 11, color: colors.muted } }, `${currentResolvedAlertPage} / ${resolvedAlertPageCount}`), React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: currentResolvedAlertPage >= resolvedAlertPageCount, onClick: () => setResolvedAlertPage((page) => Math.min(resolvedAlertPageCount, page + 1)) }, '下一页')) : null))
|
|
516
1011
|
)
|
|
517
|
-
const topicsPage = React.createElement(TopicBrowser, { groups: data?.groups || [], tasks: data?.tasks || [], target: topicTarget, updatedAt, onOpenTask:
|
|
1012
|
+
const topicsPage = React.createElement(TopicBrowser, { groups: data?.groups || [], tasks: data?.tasks || [], target: topicTarget, updatedAt, onOpenTask: openTask, onOpenSession: navigate })
|
|
518
1013
|
const pageContent = activePage === 'topics' ? topicsPage : activePage === 'tasks' ? tasksPage : activePage === 'authorizations' ? authorizationsPage : activePage === 'archive' ? archivePage : activePage === 'alerts' ? alertsPage : groupsPage
|
|
519
1014
|
const pageViewport = React.createElement('div', { style: { width: '100%', maxWidth: 1320, minHeight: 'calc(100dvh - 138px)', boxSizing: 'border-box', margin: '0 auto' } }, pageContent)
|
|
520
|
-
const main = React.createElement('main', { style: { width: '100%', minHeight: 'calc(100dvh - 90px)', boxSizing: 'border-box', padding: activePage === 'tasks' ?
|
|
1015
|
+
const main = React.createElement('main', { className: 'observer-main', style: { width: '100%', minHeight: 'calc(100dvh - 90px)', boxSizing: 'border-box', padding: activePage === 'tasks' ? `${ui.space6}px ${ui.space6}px 0` : `${ui.space6}px ${ui.space6}px ${ui.space6 + ui.space2}px`, display: 'grid', alignContent: 'start', gap: ui.space6, pointerEvents: 'auto' } },
|
|
521
1016
|
error ? React.createElement('div', { style: { ...card, borderColor: colors.danger, color: colors.danger } }, `无法连接 resident 插件:${error}`) : null,
|
|
522
|
-
navigationError ? React.createElement('div', { style: { ...card, borderColor: colors.danger, color: colors.danger } },
|
|
1017
|
+
navigationError ? React.createElement('div', { role: 'alert', style: { ...card, borderColor: colors.danger, color: colors.danger } }, `操作失败:${navigationError}`) : null,
|
|
523
1018
|
pageViewport
|
|
524
1019
|
)
|
|
525
|
-
return React.createElement('div', { style: { width: '100%', height: '100%', minWidth: 0, minHeight: 0, background: colors.surface, color: 'inherit', overflow: 'auto' } }, header, main)
|
|
1020
|
+
return React.createElement('div', { 'data-dingtalk-observer': '', style: { width: '100%', height: '100%', minWidth: 0, minHeight: 0, background: colors.surface, color: 'inherit', overflow: 'auto' } }, React.createElement('style', null, observerCss), header, main)
|
|
526
1021
|
}
|
|
527
1022
|
function apply(ctx) {
|
|
528
1023
|
ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({ name: 'sidebar.footer.action', id: 'dingtalk-dsh-observer-entry', order: 5, inject: () => ({}) }, SidebarAction))
|