@zzusp/dingtalk-dsh-observer 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/web-client.js +100 -80
package/package.json
CHANGED
package/web-client.js
CHANGED
|
@@ -3,7 +3,7 @@ window.__ModuleLoader__.load({
|
|
|
3
3
|
factory: (require) => {
|
|
4
4
|
const module = { exports: {} }
|
|
5
5
|
const React = require('react')
|
|
6
|
-
const { Button, Menu, Pill, StateDot } = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
6
|
+
const { Button, IconChecklistOutline14, IconChevronDownOutline14, IconChevronUpOutline14, IconGoalOutline16, Menu, Pill, StateDot } = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
7
7
|
const { useCallback, useEffect, useState } = React
|
|
8
8
|
const ENDPOINT = 'http://127.0.0.1:18998'
|
|
9
9
|
const colors = {
|
|
@@ -49,6 +49,8 @@ window.__ModuleLoader__.load({
|
|
|
49
49
|
}
|
|
50
50
|
const fmt = (value) => value ? new Date(value).toLocaleString('zh-CN', { hour12: false }) : '—'
|
|
51
51
|
const fmtTime = (value) => value ? new Date(value).toLocaleTimeString('zh-CN', { hour12: false }) : '—'
|
|
52
|
+
const fmtDuration = (value) => { const seconds = Math.max(0, Math.floor(value / 1000)); if (seconds < 60) return `${seconds}秒`; const minutes = Math.floor(seconds / 60); if (minutes < 60) return `${minutes}分${seconds % 60}秒`; const hours = Math.floor(minutes / 60); return `${hours}小时${minutes % 60}分` }
|
|
53
|
+
const checkpointDuration = (checkpoint, events, now) => { let startedAt; for (const event of events) { const submittedAt = Date.parse(event.submittedAt); const remainingItems = event.remainingItems || []; if (startedAt === undefined && remainingItems[0] === checkpoint && Number.isFinite(submittedAt)) startedAt = submittedAt; if (startedAt !== undefined && !remainingItems.includes(checkpoint) && Number.isFinite(submittedAt)) return fmtDuration(submittedAt - startedAt) } return startedAt === undefined ? '—' : fmtDuration(now - startedAt) }
|
|
52
54
|
const short = (value) => value ? String(value).replace(/^session-/, '').slice(0, 14) : '—'
|
|
53
55
|
const pill = (tone) => ({ display: 'inline-flex', alignItems: 'center', borderRadius: 999, padding: '3px 8px', fontSize: 12, color: tone, background: `color-mix(in srgb, ${tone} 12%, transparent)` })
|
|
54
56
|
const card = { border: `1px solid ${colors.border}`, borderRadius: 12, background: colors.cardSurface, padding: 16, boxShadow: '0 4px 16px rgba(15,23,42,.07)' }
|
|
@@ -67,19 +69,14 @@ window.__ModuleLoader__.load({
|
|
|
67
69
|
const tone = statusTone[state] || colors.muted
|
|
68
70
|
return React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 6, minHeight: 24, boxSizing: 'border-box', border: `1px solid color-mix(in srgb, ${tone} 28%, transparent)`, borderRadius: 999, background: `color-mix(in srgb, ${tone} 10%, transparent)`, color: tone, padding: '2px 8px', fontSize: 11, fontWeight: 500, lineHeight: 1.4, whiteSpace: 'nowrap' } }, React.createElement(StateDot, { state, size: 7 }), label)
|
|
69
71
|
}
|
|
70
|
-
const authorizationTag = (label, state) => {
|
|
71
|
-
const tone = statusTone[state] || colors.muted
|
|
72
|
-
return React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 6, minWidth: 68, minHeight: 26, boxSizing: 'border-box', border: `1px solid color-mix(in srgb, ${tone} 24%, transparent)`, borderRadius: 999, background: `color-mix(in srgb, ${tone} 9%, transparent)`, color: tone, padding: '3px 9px', fontSize: 11, fontWeight: 550, lineHeight: 1.4, whiteSpace: 'nowrap' } }, React.createElement(StateDot, { state, size: 7 }), label)
|
|
73
|
-
}
|
|
74
72
|
function SelectMenu({ label, value, options, onChange, minWidth = 150 }) {
|
|
75
73
|
const [open, setOpen] = useState(false)
|
|
76
74
|
const selected = options.find((item) => item.id === value)
|
|
77
75
|
const anchor = React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', 'aria-label': label, 'aria-expanded': open, onClick: () => setOpen((current) => !current), style: { minWidth, justifyContent: 'space-between', gap: 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: { color: colors.muted, fontSize: 9, transform: open ? 'rotate(180deg)' : 'none', transition: 'transform .15s ease' } }, '▼'))
|
|
78
76
|
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 })
|
|
79
77
|
}
|
|
80
|
-
const pageHeader = (title, description
|
|
81
|
-
React.createElement('
|
|
82
|
-
count === undefined ? null : React.createElement(Pill, null, String(count)))
|
|
78
|
+
const pageHeader = (title, description) => React.createElement('div', { style: { minHeight: 44, marginBottom: 16 } },
|
|
79
|
+
React.createElement('h1', { style: { margin: 0, fontSize: 18, lineHeight: 1.45, fontWeight: 600 } }, title), React.createElement('p', { style: { margin: '4px 0 0', color: colors.muted, fontSize: 12, lineHeight: 1.5 } }, description))
|
|
83
80
|
const sectionHeader = (title, meta) => React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16, marginBottom: 12 } }, React.createElement('h2', { style: { margin: 0, fontSize: 15, lineHeight: 1.45, fontWeight: 600 } }, title), meta ? React.createElement('span', { style: { color: colors.muted, fontSize: 11 } }, meta) : null)
|
|
84
81
|
const emptyState = (text) => React.createElement('div', { style: { minHeight: 112, display: 'grid', placeItems: 'center', color: colors.muted, fontSize: 12, border: `1px dashed ${colors.border}`, borderRadius: 10, background: colors.surface2 } }, text)
|
|
85
82
|
async function get(path) {
|
|
@@ -95,10 +92,10 @@ window.__ModuleLoader__.load({
|
|
|
95
92
|
return value
|
|
96
93
|
}
|
|
97
94
|
async function load() {
|
|
98
|
-
const [health, groups, tasks,
|
|
99
|
-
get('/health'), get('/state/groups'), get('/state/tasks'), get('/state/
|
|
95
|
+
const [health, groups, tasks, alerts, authorizations] = await Promise.all([
|
|
96
|
+
get('/health'), get('/state/groups'), get('/state/tasks'), get('/state/supervisor/alerts'), get('/state/authorizations')
|
|
100
97
|
])
|
|
101
|
-
return { health, groups, tasks,
|
|
98
|
+
return { health, groups, tasks, alerts, authorizations }
|
|
102
99
|
}
|
|
103
100
|
function SidebarAction({ wide }) {
|
|
104
101
|
const isOpen = useOpen()
|
|
@@ -119,13 +116,13 @@ window.__ModuleLoader__.load({
|
|
|
119
116
|
{ 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))' }
|
|
120
117
|
]
|
|
121
118
|
const pages = [{ id: 'groups', label: '群聊会话' }, { id: 'tasks', label: '任务看板' }, { id: 'authorizations', label: '授权审批' }, { id: 'archive', label: '归档任务' }, { id: 'alerts', label: '告警' }]
|
|
122
|
-
function
|
|
123
|
-
const isOpen = useOpen()
|
|
119
|
+
function ObserverContent({ openSession }) {
|
|
124
120
|
const [activePage, setActivePage] = useState('groups')
|
|
125
121
|
const [data, setData] = useState()
|
|
126
122
|
const [error, setError] = useState()
|
|
127
123
|
const [navigationError, setNavigationError] = useState()
|
|
128
124
|
const [updatedAt, setUpdatedAt] = useState()
|
|
125
|
+
const [refreshState, setRefreshState] = useState('idle')
|
|
129
126
|
const [selectedGroupId, setSelectedGroupId] = useState('')
|
|
130
127
|
const [messagePage, setMessagePage] = useState(1)
|
|
131
128
|
const [outboxPage, setOutboxPage] = useState(1)
|
|
@@ -133,6 +130,7 @@ window.__ModuleLoader__.load({
|
|
|
133
130
|
const [messageDeliveryFilter, setMessageDeliveryFilter] = useState('all')
|
|
134
131
|
const [outboxStatusFilter, setOutboxStatusFilter] = useState('all')
|
|
135
132
|
const [hoveredTaskId, setHoveredTaskId] = useState('')
|
|
133
|
+
const [expandedCheckpointTaskId, setExpandedCheckpointTaskId] = useState('')
|
|
136
134
|
const [copiedId, setCopiedId] = useState('')
|
|
137
135
|
const [navigatingSessionId, setNavigatingSessionId] = useState('')
|
|
138
136
|
const [alertType, setAlertType] = useState('all')
|
|
@@ -143,36 +141,22 @@ window.__ModuleLoader__.load({
|
|
|
143
141
|
const [authorizationPage, setAuthorizationPage] = useState(1)
|
|
144
142
|
const [authorizationFilter, setAuthorizationFilter] = useState('all')
|
|
145
143
|
const [selectedAuthorizationId, setSelectedAuthorizationId] = useState('')
|
|
146
|
-
const [sidebarWidth, setSidebarWidth] = useState(280)
|
|
147
144
|
const refresh = useCallback(async () => {
|
|
148
145
|
try { setData(await load()); setUpdatedAt(new Date()); setError(undefined) }
|
|
149
146
|
catch (cause) { setError(cause instanceof Error ? cause.message : String(cause)) }
|
|
150
147
|
}, [])
|
|
148
|
+
const manualRefresh = useCallback(async () => {
|
|
149
|
+
if (refreshState === 'refreshing') return
|
|
150
|
+
setRefreshState('refreshing')
|
|
151
|
+
await refresh()
|
|
152
|
+
setRefreshState('done')
|
|
153
|
+
window.setTimeout(() => setRefreshState('idle'), 1200)
|
|
154
|
+
}, [refresh, refreshState])
|
|
151
155
|
useEffect(() => {
|
|
152
|
-
if (!isOpen) return undefined
|
|
153
156
|
refresh()
|
|
154
157
|
const timer = window.setInterval(refresh, 5000)
|
|
155
158
|
return () => window.clearInterval(timer)
|
|
156
|
-
}, [
|
|
157
|
-
useEffect(() => {
|
|
158
|
-
if (!isOpen) return undefined
|
|
159
|
-
const entry = document.querySelector('[aria-label="钉钉群聊运行看板"]')
|
|
160
|
-
let sidebar = entry?.parentElement
|
|
161
|
-
while (sidebar?.parentElement) {
|
|
162
|
-
const rect = sidebar.getBoundingClientRect()
|
|
163
|
-
if (rect.left === 0 && rect.width >= 40 && rect.width <= 400 && rect.height >= window.innerHeight * .8) break
|
|
164
|
-
sidebar = sidebar.parentElement
|
|
165
|
-
}
|
|
166
|
-
const update = () => {
|
|
167
|
-
const width = sidebar?.getBoundingClientRect().width
|
|
168
|
-
if (Number.isFinite(width) && width >= 40 && width <= 400) setSidebarWidth(Math.round(width))
|
|
169
|
-
}
|
|
170
|
-
update()
|
|
171
|
-
const observer = sidebar && typeof ResizeObserver === 'function' ? new ResizeObserver(update) : undefined
|
|
172
|
-
if (sidebar && observer) observer.observe(sidebar)
|
|
173
|
-
window.addEventListener('resize', update)
|
|
174
|
-
return () => { observer?.disconnect(); window.removeEventListener('resize', update) }
|
|
175
|
-
}, [isOpen])
|
|
159
|
+
}, [refresh])
|
|
176
160
|
useEffect(() => {
|
|
177
161
|
const closeForSession = (event) => {
|
|
178
162
|
if (event.target instanceof Element && event.target.closest('[role="treeitem"]')) setOpen(false)
|
|
@@ -180,8 +164,6 @@ window.__ModuleLoader__.load({
|
|
|
180
164
|
document.addEventListener('click', closeForSession, true)
|
|
181
165
|
return () => document.removeEventListener('click', closeForSession, true)
|
|
182
166
|
}, [])
|
|
183
|
-
if (!isOpen) return null
|
|
184
|
-
const activities = new Map((data?.activities || []).map((item) => [item.taskId, item]))
|
|
185
167
|
const groupsById = new Map((data?.groups || []).map((group) => [group.groupId, group]))
|
|
186
168
|
const navigate = async (sessionId, parentSessionId) => {
|
|
187
169
|
try { setNavigationError(undefined); setNavigatingSessionId(sessionId); await openSession(sessionId, parentSessionId); setOpen(false) }
|
|
@@ -211,7 +193,7 @@ window.__ModuleLoader__.load({
|
|
|
211
193
|
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}`)
|
|
212
194
|
const selectedGroup = groupsById.get(selectedGroupId) || (data?.groups || [])[0]
|
|
213
195
|
const selectedGroupSummary = selectedGroup ? React.createElement('div', { style: { minHeight: 64, boxSizing: 'border-box', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 20, padding: '10px 12px', borderBottom: `1px solid ${colors.border}`, background: colors.surface2 } },
|
|
214
|
-
React.createElement('div', { style: { minWidth: 0, display: 'grid', gap: 5 } }, React.createElement('
|
|
196
|
+
React.createElement('div', { style: { minWidth: 0, display: 'grid', gap: 5 } }, React.createElement('button', { type: 'button', onClick: () => navigate(selectedGroup.residentSessionId), style: { minWidth: 0, border: 0, padding: 0, background: 'transparent', color: 'inherit', cursor: 'pointer', textAlign: 'left', fontFamily: 'inherit', fontSize: 13, fontWeight: 600 } }, selectedGroup.name || '未命名群'), React.createElement('div', { style: { minWidth: 0, display: 'flex', alignItems: 'center', gap: 8, color: colors.muted, fontSize: 10.5 } }, React.createElement('span', null, '常驻 Session'), React.createElement('code', { title: selectedGroup.residentSessionId || '', style: { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', color: 'inherit', fontSize: 10.5 } }, selectedGroup.residentSessionId || '尚未建立'))),
|
|
215
197
|
copyButton(selectedGroup.residentSessionId, '会话 ID')) : emptyState('暂无常驻群')
|
|
216
198
|
const selectedMessages = [...(selectedGroup?.messages || [])].sort((left, right) => Number(right.sequence || 0) - Number(left.sequence || 0))
|
|
217
199
|
const filteredMessages = selectedMessages.filter((message) => messageDeliveryFilter === 'all' || (message.agentDeliveryStatus || 'unknown') === messageDeliveryFilter)
|
|
@@ -235,17 +217,19 @@ window.__ModuleLoader__.load({
|
|
|
235
217
|
React.createElement('td', { style: { ...tableBodyCell, width: 150 } }, React.createElement('code', { title: message.messageId, style: { fontSize: 10, color: colors.muted } }, `#${message.sequence ?? '—'} · ${short(message.messageId)}`)))
|
|
236
218
|
})
|
|
237
219
|
const selectedOutbox = [...(selectedGroup?.outbox || [])].reverse()
|
|
238
|
-
const
|
|
220
|
+
const outboxState = (message) => message.recallStatus === 'recalled' ? 'recalled' : message.status === 'pending' && message.readbackRequired === true ? 'waiting' : 'confirmed'
|
|
221
|
+
const filteredOutbox = selectedOutbox.filter((message) => outboxStatusFilter === 'all' || outboxState(message) === outboxStatusFilter)
|
|
239
222
|
const outboxPageSize = 10
|
|
240
223
|
const outboxPageCount = Math.max(1, Math.ceil(filteredOutbox.length / outboxPageSize))
|
|
241
224
|
const currentOutboxPage = Math.min(outboxPage, outboxPageCount)
|
|
242
225
|
const visibleOutbox = filteredOutbox.slice((currentOutboxPage - 1) * outboxPageSize, currentOutboxPage * outboxPageSize)
|
|
243
226
|
const outboundStatus = {
|
|
244
|
-
sent: { label: '
|
|
245
|
-
pending: { label: '
|
|
227
|
+
sent: { label: '已回读', state: 'done' },
|
|
228
|
+
pending: { label: '已回读', state: 'done' },
|
|
229
|
+
recalled: { label: '已撤回', state: 'neutral' },
|
|
246
230
|
}
|
|
247
231
|
const outboxRows = visibleOutbox.map((message) => {
|
|
248
|
-
const status = message.status === 'pending' && message.readbackRequired === true ? { label: '
|
|
232
|
+
const status = message.recallStatus === 'recalled' ? outboundStatus.recalled : message.status === 'pending' && message.readbackRequired === true ? { label: '待回读', state: 'warning' } : outboundStatus[message.status] || outboundStatus.sent
|
|
249
233
|
return React.createElement('tr', { key: message.outboundId },
|
|
250
234
|
React.createElement('td', { style: { ...tableBodyCell, width: 130 } }, statusTag(status.label, status.state)),
|
|
251
235
|
React.createElement('td', { title: message.text, style: { ...tableBodyCell, overflowWrap: 'anywhere' } }, message.text || '(空消息)'),
|
|
@@ -257,19 +241,37 @@ window.__ModuleLoader__.load({
|
|
|
257
241
|
})
|
|
258
242
|
const renderTaskCard = (task) => {
|
|
259
243
|
const group = groupsById.get(task.groupId)
|
|
260
|
-
const activity = activities.get(task.taskId)
|
|
261
244
|
const queued = task.state === 'queued'
|
|
262
|
-
const
|
|
245
|
+
const checkpointEvents = task.checkpoints || []
|
|
246
|
+
const planCheckpointIndex = checkpointEvents.findLastIndex((checkpoint) => checkpoint.kind === 'plan-confirmed')
|
|
247
|
+
const planCheckpoint = checkpointEvents[planCheckpointIndex]
|
|
248
|
+
const currentCheckpointEvents = checkpointEvents.slice(Math.max(0, planCheckpointIndex))
|
|
249
|
+
const checkpoints = planCheckpoint?.remainingItems?.length ? planCheckpoint.remainingItems : (task.stageTasks?.length ? task.stageTasks : [task.objective])
|
|
250
|
+
const displayObjective = String(task.objective || '').startsWith('[TASK_SOURCE_EVIDENCE]') ? (task.title || task.objective) : task.objective
|
|
251
|
+
const latestCheckpoint = checkpointEvents.at(-1)
|
|
252
|
+
const remainingCheckpointNames = new Set(latestCheckpoint?.remainingItems || checkpoints)
|
|
253
|
+
const completedCheckpointNames = new Set(task.state === 'completed' ? checkpoints : checkpoints.filter((checkpoint) => !remainingCheckpointNames.has(checkpoint)))
|
|
254
|
+
const currentCheckpoint = task.state === 'running' ? checkpoints.find((checkpoint) => remainingCheckpointNames.has(checkpoint)) : undefined
|
|
255
|
+
const completedCheckpointCount = checkpoints.filter((checkpoint) => completedCheckpointNames.has(checkpoint)).length
|
|
256
|
+
const progress = Math.round((completedCheckpointCount / checkpoints.length) * 100)
|
|
257
|
+
const showCheckpoints = task.state !== 'completed' && !task.archivedAt
|
|
258
|
+
const checkpointsExpanded = expandedCheckpointTaskId === task.taskId
|
|
259
|
+
const visibleCheckpoints = checkpointsExpanded ? checkpoints : checkpoints.slice(0, 4)
|
|
263
260
|
const hovered = hoveredTaskId === task.taskId
|
|
264
261
|
const navigating = navigatingSessionId === task.childSessionId
|
|
265
262
|
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' } },
|
|
266
|
-
React.createElement('button', { type: 'button', disabled: queued || navigating, onClick: () => navigate(task.childSessionId, group?.residentSessionId), style: { width: '100%', minWidth: 0, boxSizing: 'border-box', border: 0, padding: 0, background: 'transparent', color: 'inherit', cursor: queued || navigating ? 'default' : 'pointer', textAlign: 'left', display: 'grid', gap: 6, fontFamily: 'inherit' } },
|
|
263
|
+
React.createElement('button', { type: 'button', disabled: queued || navigating, onClick: (event) => { if (event.target.closest('[data-task-card-action]')) return; navigate(task.childSessionId, group?.residentSessionId) }, style: { width: '100%', minWidth: 0, boxSizing: 'border-box', border: 0, padding: 0, background: 'transparent', color: 'inherit', cursor: queued || navigating ? 'default' : 'pointer', textAlign: 'left', display: 'grid', gap: 6, fontFamily: 'inherit' } },
|
|
267
264
|
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.title || task.objective),
|
|
268
265
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', minWidth: 0 } }, React.createElement('span', { title: group?.name || task.groupId, style: { ...pill(colors.muted), maxWidth: '100%', padding: '2px 7px', fontSize: 10.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, group?.name || task.groupId)),
|
|
269
|
-
|
|
266
|
+
React.createElement('div', { style: { display: 'grid', gap: 3 } }, React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 10.5, color: colors.muted } }, React.createElement(IconGoalOutline16, { size: 12, 'aria-label': '任务目标' }), '任务目标'), React.createElement('div', { title: displayObjective, style: { fontSize: 11, lineHeight: 1.5, display: '-webkit-box', WebkitLineClamp: 3, WebkitBoxOrient: 'vertical', overflow: 'hidden', overflowWrap: 'anywhere' } }, displayObjective)),
|
|
267
|
+
React.createElement('div', { style: { display: 'grid', gap: 6, paddingTop: showCheckpoints ? 3 : 0 } },
|
|
268
|
+
showCheckpoints ? React.createElement(React.Fragment, null,
|
|
269
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, fontSize: 10.5 } }, 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}`)),
|
|
270
|
+
React.createElement('div', { style: { display: 'grid', gap: 4 } }, ...visibleCheckpoints.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) 64px', 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 ? '✓' : current ? React.createElement(StateDot, { state: 'ongoing', size: 10 }) : '○'), React.createElement('span', { style: { color: 'inherit', display: '-webkit-box', WebkitLineClamp: checkpointsExpanded ? 'unset' : 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', overflowWrap: 'anywhere' } }, checkpoint), React.createElement('span', { title: `执行时长 ${duration}`, style: { width: 64, color: colors.muted, textAlign: 'right', whiteSpace: 'nowrap', fontVariantNumeric: 'tabular-nums' } }, duration)) }), checkpoints.length > 4 ? React.createElement('span', { role: 'button', tabIndex: 0, 'aria-label': checkpointsExpanded ? '收起检查点' : `展开全部 ${checkpoints.length} 个检查点`, title: 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: { justifySelf: 'center', width: 28, height: 18, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: colors.muted, cursor: 'pointer' } }, React.createElement(checkpointsExpanded ? IconChevronUpOutline14 : IconChevronDownOutline14, { size: 14 })) : null)) : null,
|
|
271
|
+
task.waitingReason ? React.createElement('div', { title: task.waitingReason, style: { fontSize: 10.5, lineHeight: 1.45, color: colors.warning } }, `等待:${task.waitingReason}`) : null),
|
|
270
272
|
React.createElement('div', { style: { minWidth: 0, marginTop: 3, paddingTop: 9, borderTop: `1px solid ${colors.border}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 } },
|
|
271
|
-
React.createElement('span', { title: task.updatedAt
|
|
272
|
-
React.createElement('div', { style: { flex: '0 0 auto', display: 'flex', alignItems: 'center', gap: 5 } }, copyButton(task.taskId, '
|
|
273
|
+
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)),
|
|
274
|
+
React.createElement('div', { style: { flex: '0 0 auto', display: 'flex', alignItems: 'center', gap: 5 } }, copyButton(task.taskId, ' Id'), task.state === 'completed' && !task.archivedAt ? React.createElement('button', { type: 'button', onClick: async (event) => { event.stopPropagation(); try { await post(`/tasks/${encodeURIComponent(task.taskId)}/archive`); await refresh() } catch (cause) { setNavigationError(cause instanceof Error ? cause.message : String(cause)) } }, style: { flex: '0 0 auto', border: `1px solid ${colors.border}`, borderRadius: 7, background: colors.surface2, color: colors.muted, padding: '3px 7px', cursor: 'pointer', fontFamily: 'inherit', fontSize: 10.5 } }, '归档') : null)),
|
|
273
275
|
navigating ? React.createElement('div', { role: 'status', style: { fontSize: 11.5, color: colors.accent } }, '正在打开会话…') : queued ? React.createElement('div', { style: { fontSize: 11.5, color: colors.muted } }, '等待执行,尚无对话和轨迹') : null),
|
|
274
276
|
null)
|
|
275
277
|
}
|
|
@@ -311,28 +313,30 @@ window.__ModuleLoader__.load({
|
|
|
311
313
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, padding: '4px 6px 10px' } }, React.createElement('strong', { style: { display: 'inline-flex', alignItems: 'center', borderRadius: 999, padding: '3px 9px', fontSize: 11.5, fontWeight: 650, lineHeight: 1.4, color: 'var(--dsw-alias-label-primary-inverted, #fff)', background: bucket.tone } }, bucket.label), React.createElement('span', { style: { borderRadius: 999, background: colors.cardSurface, padding: '2px 7px', fontSize: 10.5, color: colors.muted } }, tasks.length)),
|
|
312
314
|
React.createElement('div', { style: { flex: 1, minHeight: 0, padding: '8px 12px 16px 8px', display: 'flex', flexDirection: 'column', gap: 10, overflowY: 'scroll', scrollbarGutter: 'stable' } }, ...taskCards))
|
|
313
315
|
})
|
|
314
|
-
const menu = React.createElement('nav', { 'aria-label': '运行看板视图', style: { minWidth: 0, height:
|
|
316
|
+
const menu = React.createElement('nav', { 'aria-label': '运行看板视图', style: { minWidth: 0, height: 31, display: 'flex', alignItems: 'stretch', gap: 28, overflowX: 'auto', padding: '0 24px' } },
|
|
315
317
|
...pages.map((page) => React.createElement('button', { key: page.id, type: 'button', onClick: () => setActivePage(page.id), 'aria-current': activePage === page.id ? 'page' : undefined, style: { position: 'relative', border: 0, borderBottom: activePage === page.id ? '2px solid #4d6bfe' : '2px solid transparent', borderRadius: 0, padding: '0 0 8px', cursor: 'pointer', font: 'inherit', fontSize: 13, fontWeight: 400, color: activePage === page.id ? '#4d6bfe' : colors.muted, background: 'transparent', outline: 'none', whiteSpace: 'nowrap' } }, page.label)))
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
318
|
+
const refreshIcon = React.createElement('svg', { width: 14, height: 14, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true, style: refreshState === 'refreshing' ? { animation: 'spin 800ms linear infinite' } : undefined }, React.createElement('path', { d: 'M20 11a8 8 0 1 0-2.34 5.66' }), React.createElement('path', { d: 'M20 4v7h-7' }))
|
|
319
|
+
const header = React.createElement('header', { style: { position: 'sticky', top: 0, zIndex: 2, borderBottom: `1px solid ${colors.border}`, background: colors.surface, pointerEvents: 'auto' } },
|
|
320
|
+
React.createElement('div', { style: { height: 44, boxSizing: 'border-box', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 24, padding: '0 24px' } },
|
|
321
|
+
React.createElement('div', { 'aria-label': updatedAt ? `运行正常,最近更新 ${fmt(updatedAt)}` : '正在连接运行时', style: { minWidth: 0, display: 'flex', alignItems: 'center', gap: 12, color: colors.muted, fontSize: 11.5, whiteSpace: 'nowrap' } },
|
|
319
322
|
React.createElement('span', { style: { fontSize: 15, fontWeight: 400, whiteSpace: 'nowrap' } }, '运行看板'),
|
|
320
|
-
React.createElement('
|
|
323
|
+
React.createElement('span', { 'aria-hidden': true, style: { width: 1, height: 12, background: colors.border } }),
|
|
324
|
+
React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 7 } },
|
|
321
325
|
React.createElement(StateDot, { state: updatedAt ? 'done' : 'ongoing', size: 7 }),
|
|
322
|
-
React.createElement('span', { style: { color: updatedAt ? statusTone.done : colors.muted } }, updatedAt ? '运行正常' : '正在连接'),
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', onClick:
|
|
326
|
+
React.createElement('span', { style: { color: updatedAt ? statusTone.done : colors.muted } }, updatedAt ? '运行正常' : '正在连接')),
|
|
327
|
+
updatedAt ? React.createElement('span', { 'aria-hidden': true, style: { width: 1, height: 12, background: colors.border } }) : null,
|
|
328
|
+
updatedAt ? React.createElement('span', { title: fmt(updatedAt) }, `更新于 ${fmtTime(updatedAt)}`) : null),
|
|
329
|
+
React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: refreshState === 'refreshing', onClick: manualRefresh, 'aria-live': 'polite', style: { gap: 6, minWidth: 78 } }, refreshState === 'refreshing' ? '刷新中' : refreshState === 'done' ? '已刷新' : '刷新', refreshIcon)),
|
|
326
330
|
menu)
|
|
327
331
|
const dataViewTabs = React.createElement('div', { role: 'tablist', 'aria-label': '群聊数据视图', style: { display: 'flex', alignSelf: 'stretch', gap: 20 } },
|
|
328
|
-
...[{ id: 'messages', label:
|
|
332
|
+
...[{ id: 'messages', label: `收信箱 ${selectedMessages.length}` }, { id: 'outbox', label: `发信箱 ${selectedOutbox.length}` }].map((item) => React.createElement('button', { key: item.id, role: 'tab', 'aria-selected': groupTableView === item.id, type: 'button', onClick: () => setGroupTableView(item.id), style: { border: 0, borderBottom: groupTableView === item.id ? `2px solid ${colors.accent}` : '2px solid transparent', background: 'transparent', color: groupTableView === item.id ? colors.accent : colors.muted, padding: '0 2px', fontFamily: 'inherit', fontSize: 12, cursor: 'pointer' } }, item.label)))
|
|
329
333
|
const groupTableToolbar = React.createElement('div', { style: toolbar },
|
|
330
334
|
dataViewTabs,
|
|
331
335
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 8, flexWrap: 'wrap' } },
|
|
332
336
|
(data?.groups || []).length > 1 ? 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) }, minWidth: 220 }) : null,
|
|
333
337
|
groupTableView === 'messages'
|
|
334
338
|
? React.createElement(SelectMenu, { label: '筛选投递状态', value: messageDeliveryFilter, options: [{ id: 'all', label: '全部投递状态' }, { id: 'delivered', label: '已投递' }, { id: 'failed', label: '投递失败' }, { id: 'pending', label: '投递中' }, { id: 'skipped', label: '历史未投递' }, { id: 'unknown', label: '状态未知' }], onChange: (value) => { setMessageDeliveryFilter(value); setMessagePage(1) } })
|
|
335
|
-
: React.createElement(SelectMenu, { label: '筛选发件状态', value: outboxStatusFilter, options: [{ id: 'all', label: '全部发件状态' }, { id: 'confirmed', label: '
|
|
339
|
+
: React.createElement(SelectMenu, { label: '筛选发件状态', value: outboxStatusFilter, options: [{ id: 'all', label: '全部发件状态' }, { id: 'confirmed', label: '已回读' }, { id: 'waiting', label: '待回读' }, { id: 'recalled', label: '已撤回' }], onChange: (value) => { setOutboxStatusFilter(value); setOutboxPage(1) } })))
|
|
336
340
|
const messagesTable = React.createElement(React.Fragment, null,
|
|
337
341
|
React.createElement('div', { style: { overflowX: 'auto' } }, React.createElement('table', { style: { width: '100%', minWidth: 820, borderCollapse: 'collapse', tableLayout: 'fixed' } },
|
|
338
342
|
React.createElement('thead', null, React.createElement('tr', { style: { background: colors.surface2, textAlign: 'left' } }, React.createElement('th', { style: { ...tableHeadCell, width: 150 } }, '发送人 / 时间'), React.createElement('th', { style: tableHeadCell }, '消息内容'), React.createElement('th', { style: { ...tableHeadCell, width: 150 } }, 'Agent 投递'), React.createElement('th', { style: { ...tableHeadCell, width: 150 } }, '消息'))),
|
|
@@ -340,13 +344,13 @@ window.__ModuleLoader__.load({
|
|
|
340
344
|
React.createElement('div', { style: tableFooter }, React.createElement('span', { style: { marginRight: 'auto', fontSize: 11, color: colors.muted } }, `${filteredMessages.length} 条 · 每页 ${pageSize} 条`), React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: currentMessagePage <= 1, onClick: () => setMessagePage((page) => Math.max(1, page - 1)) }, '上一页'), React.createElement('span', { style: { fontSize: 11, color: colors.muted } }, `${currentMessagePage} / ${pageCount}`), React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: currentMessagePage >= pageCount, onClick: () => setMessagePage((page) => Math.min(pageCount, page + 1)) }, '下一页')))
|
|
341
345
|
const outboxTable = React.createElement(React.Fragment, null,
|
|
342
346
|
React.createElement('div', { style: { overflowX: 'auto' } }, React.createElement('table', { style: { width: '100%', minWidth: 900, borderCollapse: 'collapse', tableLayout: 'fixed' } },
|
|
343
|
-
React.createElement('thead', null, React.createElement('tr', { style: { background: colors.surface2, textAlign: 'left' } }, React.createElement('th', { style: { ...tableHeadCell, width: 130 } }, '发送状态'), React.createElement('th', { style: tableHeadCell }, '消息内容'), React.createElement('th', { style: { ...tableHeadCell, width: 180 } }, '来源 / 回复目标'), React.createElement('th', { style: { ...tableHeadCell, width: 170 } }, '投递消息 ID'), React.createElement('th', { style: { ...tableHeadCell, width: 150 } }, '
|
|
347
|
+
React.createElement('thead', null, React.createElement('tr', { style: { background: colors.surface2, textAlign: 'left' } }, React.createElement('th', { style: { ...tableHeadCell, width: 130 } }, '发送状态'), React.createElement('th', { style: tableHeadCell }, '消息内容'), React.createElement('th', { style: { ...tableHeadCell, width: 180 } }, '来源 / 回复目标'), React.createElement('th', { style: { ...tableHeadCell, width: 170 } }, '投递消息 ID'), React.createElement('th', { style: { ...tableHeadCell, width: 150 } }, '发信箱 ID'))),
|
|
344
348
|
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 发件记录'))])))),
|
|
345
349
|
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)) }, '下一页')))
|
|
346
350
|
const groupsPage = React.createElement(React.Fragment, null,
|
|
347
|
-
pageHeader('群聊会话', '
|
|
348
|
-
React.createElement('section', null, sectionHeader('消息记录', '
|
|
349
|
-
const tasksPage = React.createElement(React.Fragment, null, pageHeader('任务看板', '按执行阶段查看任务、叶子会话和目标进展。'
|
|
351
|
+
pageHeader('群聊会话', '查看常驻会话、收信箱状态与发信箱记录。'),
|
|
352
|
+
React.createElement('section', null, sectionHeader('消息记录', '收信箱与发信箱记录'), React.createElement('div', { style: tableFrame }, selectedGroupSummary, groupTableToolbar, groupTableView === 'messages' ? messagesTable : outboxTable)))
|
|
353
|
+
const tasksPage = React.createElement(React.Fragment, null, pageHeader('任务看板', '按执行阶段查看任务、叶子会话和目标进展。'), React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(4, minmax(300px, 1fr))', gap: 16, overflowX: 'auto', alignItems: 'start', paddingBottom: 8 } }, ...bucketColumns))
|
|
350
354
|
const authorizationStatus = {
|
|
351
355
|
'pending-send': { label: '待发送', state: 'warning' },
|
|
352
356
|
'waiting-reply': { label: '待批复', state: 'warning' },
|
|
@@ -378,8 +382,8 @@ window.__ModuleLoader__.load({
|
|
|
378
382
|
const pending = item.status !== 'answered'
|
|
379
383
|
const status = pending ? authorizationStatus[item.status] : authorizationDecision[item.decision] || authorizationStatus.answered
|
|
380
384
|
const group = groupsById.get(item.groupId)
|
|
381
|
-
return React.createElement('div', { key: item.requestId, style: { width: '100%', minWidth: 0, boxSizing: 'border-box', display: 'grid', gridTemplateColumns: '
|
|
382
|
-
|
|
385
|
+
return React.createElement('div', { key: item.requestId, style: { width: '100%', minWidth: 0, boxSizing: 'border-box', display: 'grid', gridTemplateColumns: 'max-content minmax(260px, 1.1fr) minmax(300px, 1.5fr) 130px 44px', alignItems: 'center', gap: 18, borderTop: `1px solid ${colors.border}`, background: 'transparent', color: 'inherit', padding: '15px 18px' } },
|
|
386
|
+
React.createElement('div', { style: { justifySelf: 'start' } }, statusTag(status.label, status.state)),
|
|
383
387
|
React.createElement('span', { style: { minWidth: 0 } }, React.createElement('strong', { title: item.objective, style: { display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', fontSize: 12.5, lineHeight: 1.5 } }, item.objective), React.createElement('span', { style: { display: 'block', marginTop: 5, color: colors.muted, fontSize: 10.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, group?.name || item.groupId)),
|
|
384
388
|
React.createElement('span', { style: { minWidth: 0 } }, React.createElement('span', { title: item.requestedAction, style: { display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', fontSize: 11.5, lineHeight: 1.55, color: colors.muted } }, item.requestedAction), item.risk && item.risk !== '未单独说明' ? React.createElement('span', { style: { display: 'block', marginTop: 6, color: colors.danger, fontSize: 10.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, `风险 · ${item.risk}`) : null),
|
|
385
389
|
React.createElement('span', { style: { color: colors.muted, fontSize: 10.5, lineHeight: 1.5 } }, fmt(item.createdAt)),
|
|
@@ -388,7 +392,7 @@ window.__ModuleLoader__.load({
|
|
|
388
392
|
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)' } },
|
|
389
393
|
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)' } },
|
|
390
394
|
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 } },
|
|
391
|
-
React.createElement('div', { style: { minWidth: 0 } }, React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 } },
|
|
395
|
+
React.createElement('div', { style: { minWidth: 0 } }, React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 } }, statusTag(selectedAuthorization.status === 'answered' ? (authorizationDecision[selectedAuthorization.decision]?.label || '已处理') : (authorizationStatus[selectedAuthorization.status]?.label || '待批复'), selectedAuthorization.status === 'answered' ? (authorizationDecision[selectedAuthorization.decision]?.state || 'done') : 'warning'), 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)),
|
|
392
396
|
React.createElement(Button, { variant: 'ghost', size: 'sm', type: 'button', 'aria-label': '关闭申请单', onClick: () => setSelectedAuthorizationId(''), style: { flex: '0 0 auto', minWidth: 52, whiteSpace: 'nowrap' } }, '关闭')),
|
|
393
397
|
React.createElement('div', { style: { flex: '1 1 auto', overflowY: 'auto', padding: '0 24px 24px' } },
|
|
394
398
|
React.createElement('section', { style: { padding: '24px 0', borderBottom: `1px solid ${colors.border}` } }, authorizationDetailField('申请范围', selectedAuthorization.requestedAction)),
|
|
@@ -402,7 +406,7 @@ window.__ModuleLoader__.load({
|
|
|
402
406
|
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 } }),
|
|
403
407
|
React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: decidingAuthorizationId === selectedAuthorization.requestId, onClick: () => decideAuthorization(selectedAuthorization.requestId, 'rejected'), style: { color: colors.danger } }, '拒绝'),
|
|
404
408
|
React.createElement(Button, { variant: 'primary', size: 'sm', type: 'button', disabled: decidingAuthorizationId === selectedAuthorization.requestId, onClick: () => decideAuthorization(selectedAuthorization.requestId, 'approved') }, decidingAuthorizationId === selectedAuthorization.requestId ? '处理中…' : '批准')) : null)) : null
|
|
405
|
-
const authorizationsPage = React.createElement(React.Fragment, null, pageHeader('授权审批', '处理需要人工确认的操作范围,并保留风险、证据和批复记录。'
|
|
409
|
+
const authorizationsPage = React.createElement(React.Fragment, null, pageHeader('授权审批', '处理需要人工确认的操作范围,并保留风险、证据和批复记录。'), React.createElement('section', null,
|
|
406
410
|
sectionHeader('审批记录', `${authorizationItems.length} 条 · 每页 ${authorizationPageSize} 条`),
|
|
407
411
|
React.createElement('div', { style: tableFrame },
|
|
408
412
|
React.createElement('div', { style: toolbar }, React.createElement('span', { style: { fontSize: 12, color: colors.muted } }, `当前显示 ${filteredAuthorizationItems.length} 条`), 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) } })),
|
|
@@ -415,11 +419,11 @@ window.__ModuleLoader__.load({
|
|
|
415
419
|
), authorizationDetail)
|
|
416
420
|
const archivedTasks = (data?.tasks || []).filter((task) => task.state === 'completed' && task.archivedAt).sort((left, right) => String(right.archivedAt).localeCompare(String(left.archivedAt)))
|
|
417
421
|
const archivePage = React.createElement('section', null,
|
|
418
|
-
pageHeader('归档任务', '归档只影响看板展示,Task、Session、Goal 与历史证据仍完整保留。'
|
|
422
|
+
pageHeader('归档任务', '归档只影响看板展示,Task、Session、Goal 与历史证据仍完整保留。'),
|
|
419
423
|
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: 12 } }, ...(archivedTasks.length ? archivedTasks.map(renderTaskCard) : [React.createElement(React.Fragment, { key: 'empty' }, emptyState('暂无归档任务'))]))
|
|
420
424
|
)
|
|
421
425
|
const alertsPage = React.createElement(React.Fragment, null,
|
|
422
|
-
pageHeader('告警', '集中查看消息通道、叶子会话、任务目标和 Runtime 的当前异常与恢复历史。'
|
|
426
|
+
pageHeader('告警', '集中查看消息通道、叶子会话、任务目标和 Runtime 的当前异常与恢复历史。'),
|
|
423
427
|
React.createElement('section', null, sectionHeader('告警记录', '按状态和类型筛选'), React.createElement('div', { style: tableFrame },
|
|
424
428
|
React.createElement('div', { style: toolbar },
|
|
425
429
|
React.createElement('div', { role: 'tablist', 'aria-label': '告警状态视图', style: { display: 'flex', alignSelf: 'stretch', gap: 20 } }, ...[{ id: 'active', label: `当前异常 ${filteredActiveAlerts.length}` }, { id: 'resolved', label: `恢复历史 ${filteredResolvedAlerts.length}` }].map((item) => React.createElement('button', { key: item.id, role: 'tab', 'aria-selected': alertView === item.id, type: 'button', onClick: () => setAlertView(item.id), style: { border: 0, borderBottom: alertView === item.id ? `2px solid ${colors.accent}` : '2px solid transparent', background: 'transparent', color: alertView === item.id ? colors.accent : colors.muted, padding: '0 2px', fontFamily: 'inherit', fontSize: 12, cursor: 'pointer' } }, item.label))),
|
|
@@ -428,25 +432,41 @@ window.__ModuleLoader__.load({
|
|
|
428
432
|
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))
|
|
429
433
|
)
|
|
430
434
|
const pageContent = activePage === 'tasks' ? tasksPage : activePage === 'authorizations' ? authorizationsPage : activePage === 'archive' ? archivePage : activePage === 'alerts' ? alertsPage : groupsPage
|
|
431
|
-
const pageViewport = React.createElement('div', { style: { minHeight: 'calc(100dvh - 138px)', boxSizing: 'border-box' } }, pageContent)
|
|
432
|
-
const main = React.createElement('main', { style: {
|
|
435
|
+
const pageViewport = React.createElement('div', { style: { width: '100%', maxWidth: 1320, minHeight: 'calc(100dvh - 138px)', boxSizing: 'border-box', margin: '0 auto' } }, pageContent)
|
|
436
|
+
const main = React.createElement('main', { style: { width: '100%', minHeight: 'calc(100dvh - 90px)', boxSizing: 'border-box', padding: '24px 24px 32px', display: 'grid', alignContent: 'start', gap: 20, pointerEvents: 'auto' } },
|
|
433
437
|
error ? React.createElement('div', { style: { ...card, borderColor: colors.danger, color: colors.danger } }, `无法连接 resident 插件:${error}`) : null,
|
|
434
438
|
navigationError ? React.createElement('div', { style: { ...card, borderColor: colors.danger, color: colors.danger } }, `无法打开 DSH Session:${navigationError}`) : null,
|
|
435
439
|
pageViewport
|
|
436
440
|
)
|
|
437
|
-
return React.createElement('div', { style: {
|
|
441
|
+
return React.createElement('div', { style: { width: '100%', height: '100%', minWidth: 0, minHeight: 0, background: colors.surface, color: 'inherit', overflow: 'auto' } }, header, main)
|
|
438
442
|
}
|
|
439
443
|
function apply(ctx) {
|
|
440
444
|
ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({ name: 'sidebar.footer.action', id: 'dingtalk-dsh-observer-entry', order: 5, inject: () => ({}) }, SidebarAction))
|
|
441
|
-
ctx.slots.inject('
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
if (!
|
|
445
|
-
|
|
445
|
+
ctx.slots.inject('conversation', () => {
|
|
446
|
+
let disposeContent
|
|
447
|
+
const syncContent = (visible) => {
|
|
448
|
+
if (visible && !disposeContent) {
|
|
449
|
+
disposeContent = ctx.slots.register({ name: 'conversation', id: 'dingtalk-dsh-observer', priority: -10, inject: () => ({ openSession: async (sessionId, parentSessionId) => {
|
|
450
|
+
if (parentSessionId) {
|
|
451
|
+
let address = ctx.sessions.subagentAddress(sessionId)
|
|
452
|
+
if (!address) { await ctx.sessions.refreshSubagents(parentSessionId); address = ctx.sessions.subagentAddress(sessionId) }
|
|
453
|
+
if (address) { ctx.sessions.openSubagent(address); return }
|
|
454
|
+
}
|
|
455
|
+
await ctx.sessions.refresh()
|
|
456
|
+
ctx.sessions.open(sessionId)
|
|
457
|
+
} }) }, ObserverContent)
|
|
458
|
+
} else if (!visible && disposeContent) {
|
|
459
|
+
disposeContent()
|
|
460
|
+
disposeContent = undefined
|
|
461
|
+
}
|
|
446
462
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
463
|
+
openListeners.add(syncContent)
|
|
464
|
+
syncContent(open)
|
|
465
|
+
return () => {
|
|
466
|
+
openListeners.delete(syncContent)
|
|
467
|
+
disposeContent?.()
|
|
468
|
+
}
|
|
469
|
+
})
|
|
450
470
|
}
|
|
451
471
|
module.exports = { name: 'dingtalk-dsh-observer-client', inject: ['slots', 'sessions'], apply }
|
|
452
472
|
return module.exports
|