@zzusp/dingtalk-dsh-observer 0.5.1 → 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 +101 -107
package/package.json
CHANGED
package/web-client.js
CHANGED
|
@@ -3,8 +3,8 @@ 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')
|
|
7
|
-
const { useCallback, useEffect,
|
|
6
|
+
const { Button, IconChecklistOutline14, IconChevronDownOutline14, IconChevronUpOutline14, IconGoalOutline16, Menu, Pill, StateDot } = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
7
|
+
const { useCallback, useEffect, useState } = React
|
|
8
8
|
const ENDPOINT = 'http://127.0.0.1:18998'
|
|
9
9
|
const colors = {
|
|
10
10
|
surface: 'var(--dsw-alias-bg-base, #fff)',
|
|
@@ -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: 5, minWidth: 58, minHeight: 22, boxSizing: 'border-box', border: `1px solid color-mix(in srgb, ${tone} 18%, transparent)`, borderRadius: 6, background: `color-mix(in srgb, ${tone} 7%, transparent)`, color: tone, padding: '2px 7px', fontSize: 11, fontWeight: 500, lineHeight: 1.35, whiteSpace: 'nowrap' } }, React.createElement(StateDot, { state, size: 6 }), 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,61 +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
|
-
const overlayRef = useRef()
|
|
148
144
|
const refresh = useCallback(async () => {
|
|
149
145
|
try { setData(await load()); setUpdatedAt(new Date()); setError(undefined) }
|
|
150
146
|
catch (cause) { setError(cause instanceof Error ? cause.message : String(cause)) }
|
|
151
147
|
}, [])
|
|
152
|
-
|
|
153
|
-
if (
|
|
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])
|
|
155
|
+
useEffect(() => {
|
|
154
156
|
refresh()
|
|
155
157
|
const timer = window.setInterval(refresh, 5000)
|
|
156
158
|
return () => window.clearInterval(timer)
|
|
157
|
-
}, [
|
|
158
|
-
useEffect(() => {
|
|
159
|
-
if (!isOpen) return undefined
|
|
160
|
-
const entry = document.querySelector('[aria-label="钉钉群聊运行看板"]')
|
|
161
|
-
let sidebar = entry?.parentElement
|
|
162
|
-
while (sidebar?.parentElement) {
|
|
163
|
-
const rect = sidebar.getBoundingClientRect()
|
|
164
|
-
if (rect.left === 0 && rect.width >= 40 && rect.width <= 400 && rect.height >= window.innerHeight * .8) break
|
|
165
|
-
sidebar = sidebar.parentElement
|
|
166
|
-
}
|
|
167
|
-
const update = () => {
|
|
168
|
-
const right = sidebar?.getBoundingClientRect().right
|
|
169
|
-
if (Number.isFinite(right) && right >= 40 && right <= 400) {
|
|
170
|
-
const roundedRight = Math.round(right)
|
|
171
|
-
if (overlayRef.current) overlayRef.current.style.paddingLeft = `${roundedRight}px`
|
|
172
|
-
setSidebarWidth((current) => current === roundedRight ? current : roundedRight)
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
let animationFrame
|
|
176
|
-
let trackingUntil = 0
|
|
177
|
-
const trackTransition = () => {
|
|
178
|
-
trackingUntil = performance.now() + 700
|
|
179
|
-
if (animationFrame) return
|
|
180
|
-
const track = () => {
|
|
181
|
-
update()
|
|
182
|
-
if (performance.now() < trackingUntil) animationFrame = window.requestAnimationFrame(track)
|
|
183
|
-
else animationFrame = undefined
|
|
184
|
-
}
|
|
185
|
-
animationFrame = window.requestAnimationFrame(track)
|
|
186
|
-
}
|
|
187
|
-
update()
|
|
188
|
-
const observer = sidebar && typeof ResizeObserver === 'function' ? new ResizeObserver(update) : undefined
|
|
189
|
-
if (sidebar && observer) observer.observe(sidebar)
|
|
190
|
-
sidebar?.addEventListener('transitionrun', trackTransition)
|
|
191
|
-
document.addEventListener('click', trackTransition, true)
|
|
192
|
-
window.addEventListener('resize', update)
|
|
193
|
-
return () => {
|
|
194
|
-
observer?.disconnect()
|
|
195
|
-
sidebar?.removeEventListener('transitionrun', trackTransition)
|
|
196
|
-
document.removeEventListener('click', trackTransition, true)
|
|
197
|
-
window.removeEventListener('resize', update)
|
|
198
|
-
if (animationFrame) window.cancelAnimationFrame(animationFrame)
|
|
199
|
-
}
|
|
200
|
-
}, [isOpen])
|
|
159
|
+
}, [refresh])
|
|
201
160
|
useEffect(() => {
|
|
202
161
|
const closeForSession = (event) => {
|
|
203
162
|
if (event.target instanceof Element && event.target.closest('[role="treeitem"]')) setOpen(false)
|
|
@@ -205,8 +164,6 @@ window.__ModuleLoader__.load({
|
|
|
205
164
|
document.addEventListener('click', closeForSession, true)
|
|
206
165
|
return () => document.removeEventListener('click', closeForSession, true)
|
|
207
166
|
}, [])
|
|
208
|
-
if (!isOpen) return null
|
|
209
|
-
const activities = new Map((data?.activities || []).map((item) => [item.taskId, item]))
|
|
210
167
|
const groupsById = new Map((data?.groups || []).map((group) => [group.groupId, group]))
|
|
211
168
|
const navigate = async (sessionId, parentSessionId) => {
|
|
212
169
|
try { setNavigationError(undefined); setNavigatingSessionId(sessionId); await openSession(sessionId, parentSessionId); setOpen(false) }
|
|
@@ -236,7 +193,7 @@ window.__ModuleLoader__.load({
|
|
|
236
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}`)
|
|
237
194
|
const selectedGroup = groupsById.get(selectedGroupId) || (data?.groups || [])[0]
|
|
238
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 } },
|
|
239
|
-
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 || '尚未建立'))),
|
|
240
197
|
copyButton(selectedGroup.residentSessionId, '会话 ID')) : emptyState('暂无常驻群')
|
|
241
198
|
const selectedMessages = [...(selectedGroup?.messages || [])].sort((left, right) => Number(right.sequence || 0) - Number(left.sequence || 0))
|
|
242
199
|
const filteredMessages = selectedMessages.filter((message) => messageDeliveryFilter === 'all' || (message.agentDeliveryStatus || 'unknown') === messageDeliveryFilter)
|
|
@@ -260,17 +217,19 @@ window.__ModuleLoader__.load({
|
|
|
260
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)}`)))
|
|
261
218
|
})
|
|
262
219
|
const selectedOutbox = [...(selectedGroup?.outbox || [])].reverse()
|
|
263
|
-
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)
|
|
264
222
|
const outboxPageSize = 10
|
|
265
223
|
const outboxPageCount = Math.max(1, Math.ceil(filteredOutbox.length / outboxPageSize))
|
|
266
224
|
const currentOutboxPage = Math.min(outboxPage, outboxPageCount)
|
|
267
225
|
const visibleOutbox = filteredOutbox.slice((currentOutboxPage - 1) * outboxPageSize, currentOutboxPage * outboxPageSize)
|
|
268
226
|
const outboundStatus = {
|
|
269
|
-
sent: { label: '
|
|
270
|
-
pending: { label: '
|
|
227
|
+
sent: { label: '已回读', state: 'done' },
|
|
228
|
+
pending: { label: '已回读', state: 'done' },
|
|
229
|
+
recalled: { label: '已撤回', state: 'neutral' },
|
|
271
230
|
}
|
|
272
231
|
const outboxRows = visibleOutbox.map((message) => {
|
|
273
|
-
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
|
|
274
233
|
return React.createElement('tr', { key: message.outboundId },
|
|
275
234
|
React.createElement('td', { style: { ...tableBodyCell, width: 130 } }, statusTag(status.label, status.state)),
|
|
276
235
|
React.createElement('td', { title: message.text, style: { ...tableBodyCell, overflowWrap: 'anywhere' } }, message.text || '(空消息)'),
|
|
@@ -282,19 +241,37 @@ window.__ModuleLoader__.load({
|
|
|
282
241
|
})
|
|
283
242
|
const renderTaskCard = (task) => {
|
|
284
243
|
const group = groupsById.get(task.groupId)
|
|
285
|
-
const activity = activities.get(task.taskId)
|
|
286
244
|
const queued = task.state === 'queued'
|
|
287
|
-
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)
|
|
288
260
|
const hovered = hoveredTaskId === task.taskId
|
|
289
261
|
const navigating = navigatingSessionId === task.childSessionId
|
|
290
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' } },
|
|
291
|
-
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' } },
|
|
292
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),
|
|
293
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)),
|
|
294
|
-
|
|
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),
|
|
295
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 } },
|
|
296
|
-
React.createElement('span', { title: task.updatedAt
|
|
297
|
-
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)),
|
|
298
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),
|
|
299
276
|
null)
|
|
300
277
|
}
|
|
@@ -336,29 +313,30 @@ window.__ModuleLoader__.load({
|
|
|
336
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)),
|
|
337
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))
|
|
338
315
|
})
|
|
339
|
-
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' } },
|
|
340
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)))
|
|
341
|
-
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 }, React.createElement('path', { d: 'M20 11a8 8 0 1 0-2.34 5.66' }), React.createElement('path', { d: 'M20 4v7h-7' }))
|
|
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' }))
|
|
342
319
|
const header = React.createElement('header', { style: { position: 'sticky', top: 0, zIndex: 2, borderBottom: `1px solid ${colors.border}`, background: colors.surface, pointerEvents: 'auto' } },
|
|
343
|
-
React.createElement('div', { style: { height:
|
|
344
|
-
React.createElement('div', { style: { minWidth: 0, display: 'flex', alignItems: 'center', gap:
|
|
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' } },
|
|
345
322
|
React.createElement('span', { style: { fontSize: 15, fontWeight: 400, whiteSpace: 'nowrap' } }, '运行看板'),
|
|
346
|
-
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 } },
|
|
347
325
|
React.createElement(StateDot, { state: updatedAt ? 'done' : 'ongoing', size: 7 }),
|
|
348
|
-
React.createElement('span', { style: { color: updatedAt ? statusTone.done : colors.muted } }, updatedAt ? '运行正常' : '正在连接'),
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
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)),
|
|
352
330
|
menu)
|
|
353
331
|
const dataViewTabs = React.createElement('div', { role: 'tablist', 'aria-label': '群聊数据视图', style: { display: 'flex', alignSelf: 'stretch', gap: 20 } },
|
|
354
|
-
...[{ 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)))
|
|
355
333
|
const groupTableToolbar = React.createElement('div', { style: toolbar },
|
|
356
334
|
dataViewTabs,
|
|
357
335
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 8, flexWrap: 'wrap' } },
|
|
358
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,
|
|
359
337
|
groupTableView === 'messages'
|
|
360
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) } })
|
|
361
|
-
: 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) } })))
|
|
362
340
|
const messagesTable = React.createElement(React.Fragment, null,
|
|
363
341
|
React.createElement('div', { style: { overflowX: 'auto' } }, React.createElement('table', { style: { width: '100%', minWidth: 820, borderCollapse: 'collapse', tableLayout: 'fixed' } },
|
|
364
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 } }, '消息'))),
|
|
@@ -366,13 +344,13 @@ window.__ModuleLoader__.load({
|
|
|
366
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)) }, '下一页')))
|
|
367
345
|
const outboxTable = React.createElement(React.Fragment, null,
|
|
368
346
|
React.createElement('div', { style: { overflowX: 'auto' } }, React.createElement('table', { style: { width: '100%', minWidth: 900, borderCollapse: 'collapse', tableLayout: 'fixed' } },
|
|
369
|
-
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'))),
|
|
370
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 发件记录'))])))),
|
|
371
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)) }, '下一页')))
|
|
372
350
|
const groupsPage = React.createElement(React.Fragment, null,
|
|
373
|
-
pageHeader('群聊会话', '
|
|
374
|
-
React.createElement('section', null, sectionHeader('消息记录', '
|
|
375
|
-
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))
|
|
376
354
|
const authorizationStatus = {
|
|
377
355
|
'pending-send': { label: '待发送', state: 'warning' },
|
|
378
356
|
'waiting-reply': { label: '待批复', state: 'warning' },
|
|
@@ -404,8 +382,8 @@ window.__ModuleLoader__.load({
|
|
|
404
382
|
const pending = item.status !== 'answered'
|
|
405
383
|
const status = pending ? authorizationStatus[item.status] : authorizationDecision[item.decision] || authorizationStatus.answered
|
|
406
384
|
const group = groupsById.get(item.groupId)
|
|
407
|
-
return React.createElement('div', { key: item.requestId, style: { width: '100%', minWidth: 0, boxSizing: 'border-box', display: 'grid', gridTemplateColumns: '
|
|
408
|
-
|
|
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)),
|
|
409
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)),
|
|
410
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),
|
|
411
389
|
React.createElement('span', { style: { color: colors.muted, fontSize: 10.5, lineHeight: 1.5 } }, fmt(item.createdAt)),
|
|
@@ -414,7 +392,7 @@ window.__ModuleLoader__.load({
|
|
|
414
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)' } },
|
|
415
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)' } },
|
|
416
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 } },
|
|
417
|
-
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)),
|
|
418
396
|
React.createElement(Button, { variant: 'ghost', size: 'sm', type: 'button', 'aria-label': '关闭申请单', onClick: () => setSelectedAuthorizationId(''), style: { flex: '0 0 auto', minWidth: 52, whiteSpace: 'nowrap' } }, '关闭')),
|
|
419
397
|
React.createElement('div', { style: { flex: '1 1 auto', overflowY: 'auto', padding: '0 24px 24px' } },
|
|
420
398
|
React.createElement('section', { style: { padding: '24px 0', borderBottom: `1px solid ${colors.border}` } }, authorizationDetailField('申请范围', selectedAuthorization.requestedAction)),
|
|
@@ -428,7 +406,7 @@ window.__ModuleLoader__.load({
|
|
|
428
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 } }),
|
|
429
407
|
React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: decidingAuthorizationId === selectedAuthorization.requestId, onClick: () => decideAuthorization(selectedAuthorization.requestId, 'rejected'), style: { color: colors.danger } }, '拒绝'),
|
|
430
408
|
React.createElement(Button, { variant: 'primary', size: 'sm', type: 'button', disabled: decidingAuthorizationId === selectedAuthorization.requestId, onClick: () => decideAuthorization(selectedAuthorization.requestId, 'approved') }, decidingAuthorizationId === selectedAuthorization.requestId ? '处理中…' : '批准')) : null)) : null
|
|
431
|
-
const authorizationsPage = React.createElement(React.Fragment, null, pageHeader('授权审批', '处理需要人工确认的操作范围,并保留风险、证据和批复记录。'
|
|
409
|
+
const authorizationsPage = React.createElement(React.Fragment, null, pageHeader('授权审批', '处理需要人工确认的操作范围,并保留风险、证据和批复记录。'), React.createElement('section', null,
|
|
432
410
|
sectionHeader('审批记录', `${authorizationItems.length} 条 · 每页 ${authorizationPageSize} 条`),
|
|
433
411
|
React.createElement('div', { style: tableFrame },
|
|
434
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) } })),
|
|
@@ -441,11 +419,11 @@ window.__ModuleLoader__.load({
|
|
|
441
419
|
), authorizationDetail)
|
|
442
420
|
const archivedTasks = (data?.tasks || []).filter((task) => task.state === 'completed' && task.archivedAt).sort((left, right) => String(right.archivedAt).localeCompare(String(left.archivedAt)))
|
|
443
421
|
const archivePage = React.createElement('section', null,
|
|
444
|
-
pageHeader('归档任务', '归档只影响看板展示,Task、Session、Goal 与历史证据仍完整保留。'
|
|
422
|
+
pageHeader('归档任务', '归档只影响看板展示,Task、Session、Goal 与历史证据仍完整保留。'),
|
|
445
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('暂无归档任务'))]))
|
|
446
424
|
)
|
|
447
425
|
const alertsPage = React.createElement(React.Fragment, null,
|
|
448
|
-
pageHeader('告警', '集中查看消息通道、叶子会话、任务目标和 Runtime 的当前异常与恢复历史。'
|
|
426
|
+
pageHeader('告警', '集中查看消息通道、叶子会话、任务目标和 Runtime 的当前异常与恢复历史。'),
|
|
449
427
|
React.createElement('section', null, sectionHeader('告警记录', '按状态和类型筛选'), React.createElement('div', { style: tableFrame },
|
|
450
428
|
React.createElement('div', { style: toolbar },
|
|
451
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))),
|
|
@@ -454,25 +432,41 @@ window.__ModuleLoader__.load({
|
|
|
454
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))
|
|
455
433
|
)
|
|
456
434
|
const pageContent = activePage === 'tasks' ? tasksPage : activePage === 'authorizations' ? authorizationsPage : activePage === 'archive' ? archivePage : activePage === 'alerts' ? alertsPage : groupsPage
|
|
457
|
-
const pageViewport = React.createElement('div', { style: { minHeight: 'calc(100dvh - 138px)', boxSizing: 'border-box' } }, pageContent)
|
|
458
|
-
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' } },
|
|
459
437
|
error ? React.createElement('div', { style: { ...card, borderColor: colors.danger, color: colors.danger } }, `无法连接 resident 插件:${error}`) : null,
|
|
460
438
|
navigationError ? React.createElement('div', { style: { ...card, borderColor: colors.danger, color: colors.danger } }, `无法打开 DSH Session:${navigationError}`) : null,
|
|
461
439
|
pageViewport
|
|
462
440
|
)
|
|
463
|
-
return React.createElement('div', {
|
|
441
|
+
return React.createElement('div', { style: { width: '100%', height: '100%', minWidth: 0, minHeight: 0, background: colors.surface, color: 'inherit', overflow: 'auto' } }, header, main)
|
|
464
442
|
}
|
|
465
443
|
function apply(ctx) {
|
|
466
444
|
ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({ name: 'sidebar.footer.action', id: 'dingtalk-dsh-observer-entry', order: 5, inject: () => ({}) }, SidebarAction))
|
|
467
|
-
ctx.slots.inject('
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
if (!
|
|
471
|
-
|
|
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
|
+
}
|
|
462
|
+
}
|
|
463
|
+
openListeners.add(syncContent)
|
|
464
|
+
syncContent(open)
|
|
465
|
+
return () => {
|
|
466
|
+
openListeners.delete(syncContent)
|
|
467
|
+
disposeContent?.()
|
|
472
468
|
}
|
|
473
|
-
|
|
474
|
-
ctx.sessions.open(sessionId)
|
|
475
|
-
} }) }, ObserverOverlay))
|
|
469
|
+
})
|
|
476
470
|
}
|
|
477
471
|
module.exports = { name: 'dingtalk-dsh-observer-client', inject: ['slots', 'sessions'], apply }
|
|
478
472
|
return module.exports
|