@zzusp/dingtalk-dsh-observer 0.5.12 → 0.5.14
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 +2 -2
- package/web-client.js +120 -35
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zzusp/dingtalk-dsh-observer",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.14",
|
|
4
4
|
"description": "DingTalk task board client for DeepSeek Harness",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.1.
|
|
38
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.2-rc.1",
|
|
39
39
|
"react": "^18.2.0"
|
|
40
40
|
}
|
|
41
41
|
}
|
package/web-client.js
CHANGED
|
@@ -52,6 +52,25 @@ window.__ModuleLoader__.load({
|
|
|
52
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
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) }
|
|
54
54
|
const short = (value) => value ? String(value).replace(/^session-/, '').slice(0, 14) : '—'
|
|
55
|
+
const outboxDelivery = (message) => {
|
|
56
|
+
if (message.recallStatus === 'recalled') return { id: 'recalled', label: '已撤回', state: 'neutral' }
|
|
57
|
+
if (message.status === 'sent') return { id: 'confirmed', label: message.deliveredMessageId ? '已回读' : '已发送', state: 'done' }
|
|
58
|
+
const reason = message.deliveryPendingReason
|
|
59
|
+
const details = {
|
|
60
|
+
preflight_history_partial: '发送前检查:历史消息不完整,本次未发送',
|
|
61
|
+
preflight_failed: '发送前检查失败,本次未发送',
|
|
62
|
+
send_failed: '发送调用失败,尚未确认送达',
|
|
63
|
+
delivery_unknown: '发送结果未知,等待确认',
|
|
64
|
+
postflight_history_partial: '发送后回读:历史消息不完整',
|
|
65
|
+
postflight_failed: '发送后回读失败',
|
|
66
|
+
message_not_observed: '已调用发送,回读尚未找到对应消息',
|
|
67
|
+
}
|
|
68
|
+
const detail = details[reason] || reason || (message.deliveryError ? '投递异常' : '')
|
|
69
|
+
if (['delivery_unknown', 'postflight_history_partial', 'postflight_failed', 'message_not_observed'].includes(reason)) return { id: 'waiting', label: '待回读', state: 'warning', detail }
|
|
70
|
+
if (reason === 'preflight_failed' || reason === 'preflight_history_partial') return { id: 'failed', label: '发送受阻', state: 'error', detail }
|
|
71
|
+
if (message.deliveryError || reason === 'send_failed') return { id: 'failed', label: '投递异常', state: 'error', detail }
|
|
72
|
+
return { id: 'queued', label: '待发送', state: 'ongoing', detail }
|
|
73
|
+
}
|
|
55
74
|
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)` })
|
|
56
75
|
const card = { border: `1px solid ${colors.border}`, borderRadius: 12, background: colors.cardSurface, padding: 16, boxShadow: '0 4px 16px rgba(15,23,42,.07)' }
|
|
57
76
|
const tableFrame = { border: `1px solid ${colors.border}`, borderRadius: 12, background: colors.cardSurface, overflow: 'hidden' }
|
|
@@ -95,10 +114,10 @@ window.__ModuleLoader__.load({
|
|
|
95
114
|
return value
|
|
96
115
|
}
|
|
97
116
|
async function load() {
|
|
98
|
-
const [health, groups, tasks, alerts, authorizations] = await Promise.all([
|
|
99
|
-
get('/health'), get('/state/groups'), get('/state/tasks'), get('/state/supervisor/alerts'), get('/state/authorizations')
|
|
117
|
+
const [health, groups, tasks, alerts, authorizations, topicPage] = await Promise.all([
|
|
118
|
+
get('/health'), get('/state/groups'), get('/state/tasks'), get('/state/supervisor/alerts'), get('/state/authorizations'), get('/state/topics?offset=0&limit=100')
|
|
100
119
|
])
|
|
101
|
-
return { health, groups, tasks, alerts, authorizations }
|
|
120
|
+
return { health, groups, tasks, alerts, authorizations, topics: topicPage.topics }
|
|
102
121
|
}
|
|
103
122
|
function SidebarAction({ wide }) {
|
|
104
123
|
const isOpen = useOpen()
|
|
@@ -118,9 +137,69 @@ window.__ModuleLoader__.load({
|
|
|
118
137
|
{ state: 'waiting', label: '等待中', tone: colors.warning, background: 'color-mix(in srgb, var(--dsw-alias-state-warn-primary, #a56500) 8%, var(--dsw-alias-bg-layer-1, #fff))' },
|
|
119
138
|
{ 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
139
|
]
|
|
121
|
-
const pages = [{ id: 'groups', label: '群聊会话' }, { id: 'tasks', label: '任务看板' }, { id: 'authorizations', label: '人工介入' }, { id: 'archive', label: '归档任务' }, { id: 'alerts', label: '告警' }]
|
|
140
|
+
const pages = [{ id: 'groups', label: '群聊会话' }, { id: 'topics', label: '话题' }, { id: 'tasks', label: '任务看板' }, { id: 'authorizations', label: '人工介入' }, { id: 'archive', label: '归档任务' }, { id: 'alerts', label: '告警' }]
|
|
141
|
+
function TopicBrowser({ groups, tasks, target, updatedAt, onOpenTask }) {
|
|
142
|
+
const [groupId, setGroupId] = useState(target?.groupId || '')
|
|
143
|
+
const [offset, setOffset] = useState(0)
|
|
144
|
+
const [selection, setSelection] = useState(target)
|
|
145
|
+
const [messageOffset, setMessageOffset] = useState(0)
|
|
146
|
+
const [listing, setListing] = useState()
|
|
147
|
+
const [context, setContext] = useState()
|
|
148
|
+
const [listError, setListError] = useState('')
|
|
149
|
+
const [detailError, setDetailError] = useState('')
|
|
150
|
+
const [loading, setLoading] = useState(true)
|
|
151
|
+
const [detailLoading, setDetailLoading] = useState(false)
|
|
152
|
+
const [retry, setRetry] = useState(0)
|
|
153
|
+
const topicPageSize = 10
|
|
154
|
+
const messagePageSize = 25
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
let active = true
|
|
157
|
+
setLoading(true); setListError('')
|
|
158
|
+
const params = new URLSearchParams({ offset: String(offset), limit: String(topicPageSize) })
|
|
159
|
+
if (groupId) params.set('groupId', groupId)
|
|
160
|
+
get(`/state/topics?${params}`).then((value) => { if (active) { setListing(value); if (value.total > 0 && offset >= value.total) setOffset(Math.floor((value.total - 1) / topicPageSize) * topicPageSize) } }, (error) => { if (active) setListError(error.message) }).finally(() => { if (active) setLoading(false) })
|
|
161
|
+
return () => { active = false }
|
|
162
|
+
}, [groupId, offset, retry, updatedAt])
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
if (!selection) { setContext(undefined); return }
|
|
165
|
+
let active = true
|
|
166
|
+
setDetailLoading(true); setDetailError(''); setContext(undefined)
|
|
167
|
+
const params = new URLSearchParams({ groupId: selection.groupId, revision: String(selection.revision), offset: String(messageOffset), limit: String(messagePageSize) })
|
|
168
|
+
get(`/state/topics/${encodeURIComponent(selection.topicId)}?${params}`).then((value) => { if (active) setContext(value) }, (error) => { if (active) setDetailError(error.message) }).finally(() => { if (active) setDetailLoading(false) })
|
|
169
|
+
return () => { active = false }
|
|
170
|
+
}, [selection, messageOffset, retry])
|
|
171
|
+
const selectTopic = (topic) => { setSelection({ groupId: topic.groupId, topicId: topic.topicId, revision: topic.revision }); setMessageOffset(0) }
|
|
172
|
+
const topic = context?.topic
|
|
173
|
+
const group = groups.find((item) => item.groupId === groupId)
|
|
174
|
+
const relatedTasks = tasks.filter((task) => task.groupId === selection?.groupId && task.topicRefs?.some((ref) => ref.topicId === selection?.topicId))
|
|
175
|
+
const failure = (message) => React.createElement('div', { role: 'alert', style: { padding: 16, color: colors.danger } }, message, ' ', React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', onClick: () => setRetry((value) => value + 1) }, '重试'))
|
|
176
|
+
const pager = (pageOffset, total, pageSize, onChange, busy) => React.createElement('div', { style: { ...tableFooter, flexWrap: 'wrap' } }, React.createElement('span', { style: { marginRight: 'auto', color: colors.muted, fontSize: 12 } }, `${total} 条 · 第 ${Math.floor(pageOffset / pageSize) + 1} 页`), React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: busy || pageOffset === 0, onClick: () => onChange(Math.max(0, pageOffset - pageSize)) }, '上一页'), React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: busy || pageOffset + pageSize >= total, onClick: () => onChange(pageOffset + pageSize) }, '下一页'))
|
|
177
|
+
return React.createElement('section', { 'aria-label': '话题与上下文', style: { display: 'grid', gap: 16 } },
|
|
178
|
+
React.createElement('div', { style: { ...toolbar, flexWrap: 'wrap' } }, React.createElement(SelectMenu, { label: '筛选话题群聊', value: groupId, options: [{ id: '', label: '全部群聊' }, ...groups.map((item) => ({ id: item.groupId, label: item.name || item.groupId }))], onChange: (value) => { setGroupId(value); setOffset(0); setListing(undefined); setSelection(undefined) } }), React.createElement('span', { style: { fontSize: 12, color: colors.muted } }, group?.topicProgress ? `待归类 ${group.topicProgress.unroutedMessages} 条 · 待处理 ${group.topicProgress.pending} 个话题` : '接收、话题处理与任务完成分别计量')),
|
|
179
|
+
React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'flex-start', gap: 16 } },
|
|
180
|
+
React.createElement('section', { 'aria-label': '话题列表', 'aria-busy': loading, style: { ...tableFrame, flex: '1 1 340px', width: '100%', minWidth: 0, maxWidth: 420, minHeight: 200 } },
|
|
181
|
+
React.createElement('div', { role: 'status', style: { ...toolbar, minHeight: 52 } }, React.createElement('strong', { style: { fontSize: 14 } }, loading ? '正在刷新话题…' : '话题'), listing ? React.createElement('span', { style: { color: colors.muted, fontSize: 12, fontVariantNumeric: 'tabular-nums' } }, `${listing.total} 个`) : null),
|
|
182
|
+
listError ? failure(`话题加载失败:${listError}`) : !listing ? emptyState('正在读取话题…') : listing.topics.length === 0 ? emptyState('暂无话题,消息归类后会显示在这里') : React.createElement('ul', { style: { listStyle: 'none', margin: 0, padding: 0 } }, ...listing.topics.map((item) => { const selected = selection?.topicId === item.topicId && selection?.groupId === item.groupId; return React.createElement('li', { key: `${item.groupId}:${item.topicId}`, style: { borderBottom: `1px solid ${colors.border}` } }, React.createElement('button', { type: 'button', 'aria-pressed': selected, 'aria-label': item.title || item.topicId, onClick: () => selectTopic(item), style: { position: 'relative', width: '100%', minWidth: 0, border: 0, borderLeft: `3px solid ${selected ? colors.accent : 'transparent'}`, padding: '15px 16px 14px 13px', background: selected ? `color-mix(in srgb, ${colors.accent} 8%, ${colors.cardSurface})` : colors.cardSurface, color: 'inherit', cursor: 'pointer', textAlign: 'left', fontFamily: 'inherit', outlineOffset: -2 } },
|
|
183
|
+
React.createElement('strong', { title: item.title || item.topicId, style: { display: 'block', minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', overflowWrap: 'anywhere', fontSize: 13, fontWeight: 650, lineHeight: 1.55 } }, item.title || item.topicId),
|
|
184
|
+
React.createElement('p', { title: item.summary || '暂无摘要', style: { margin: '5px 0 0', display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', overflowWrap: 'anywhere', whiteSpace: 'pre-wrap', color: colors.muted, fontSize: 11.5, lineHeight: 1.55 } }, item.summary || '暂无摘要')
|
|
185
|
+
)) })),
|
|
186
|
+
listing ? pager(offset, listing.total, topicPageSize, setOffset, loading) : null),
|
|
187
|
+
React.createElement('section', { 'aria-label': '话题详情', 'aria-busy': detailLoading, style: { ...tableFrame, flex: '2 1 560px', width: '100%', minWidth: 0, minHeight: 200 } },
|
|
188
|
+
React.createElement('header', { style: { padding: '17px 18px 15px', borderBottom: `1px solid ${colors.border}`, background: colors.cardSurface } }, React.createElement('h2', { title: topic?.title, style: { minWidth: 0, margin: 0, overflowWrap: 'anywhere', fontSize: 18, fontWeight: 650, lineHeight: 1.5, letterSpacing: '-.01em' } }, topic?.title || '话题详情')),
|
|
189
|
+
detailError ? failure(`上下文加载失败:${detailError}`) : detailLoading ? React.createElement('div', { role: 'status' }, emptyState('正在读取固定版本上下文…')) : !context ? emptyState('选择一个话题查看消息和关联任务') : React.createElement('div', { role: 'region', 'aria-label': '话题详情内容', style: { minWidth: 0, maxHeight: 'calc(100vh - 260px)', padding: '0 18px 18px', display: 'grid', gap: 0, overflowY: 'auto', overflowX: 'hidden', overflowWrap: 'anywhere' } },
|
|
190
|
+
React.createElement('section', { 'aria-label': '话题摘要', style: { marginTop: 16, padding: '13px 14px', borderLeft: `3px solid ${colors.accent}`, borderRadius: '0 9px 9px 0', background: `color-mix(in srgb, ${colors.accent} 6%, ${colors.surface2})` } }, React.createElement('strong', { style: { display: 'block', fontSize: 12 } }, '话题摘要'), React.createElement('p', { style: { margin: '6px 0 0', whiteSpace: 'pre-wrap', color: topic?.summary ? 'inherit' : colors.muted, fontSize: 13, lineHeight: 1.65 } }, topic?.summary || '暂无摘要')),
|
|
191
|
+
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 240px), 1fr))', gap: 12, padding: '16px 0', borderBottom: `1px solid ${colors.border}` } },
|
|
192
|
+
React.createElement('section', { 'aria-label': '当前待解决问题', style: { minWidth: 0 } }, React.createElement('strong', { style: { display: 'block', marginBottom: 8, fontSize: 12 } }, `待解决问题 · ${topic?.openQuestions?.length || 0}`), topic?.openQuestions?.length ? React.createElement('ul', { style: { margin: 0, paddingLeft: 18, display: 'grid', gap: 6, fontSize: 12, lineHeight: 1.6 } }, ...topic.openQuestions.map((question, index) => React.createElement('li', { key: index }, question))) : React.createElement('span', { style: { color: colors.muted, fontSize: 12 } }, '当前没有待解决问题')),
|
|
193
|
+
React.createElement('section', { 'aria-label': '关联任务', style: { minWidth: 0 } }, React.createElement('strong', { style: { display: 'block', marginBottom: 8, fontSize: 12 } }, `关联任务 · ${relatedTasks.length}`), React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 7 } }, ...relatedTasks.map((task) => React.createElement(Button, { key: task.taskId, variant: 'outline', size: 'sm', type: 'button', disabled: !task.childSessionId || task.state === 'queued', onClick: () => onOpenTask(task), style: { maxWidth: '100%' } }, React.createElement('span', { style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, task.title || task.objective))), relatedTasks.length === 0 ? React.createElement('span', { style: { color: colors.muted, fontSize: 12 } }, '暂无关联任务') : null))),
|
|
194
|
+
React.createElement('section', { 'aria-label': '群聊消息', style: { marginTop: 4 } },
|
|
195
|
+
React.createElement('strong', { style: { display: 'block', padding: '14px 0', fontSize: 13, fontWeight: 600 } }, `群聊消息 · ${context.total} 条`),
|
|
196
|
+
React.createElement('div', { style: { display: 'grid' } }, ...(context.messages?.length ? context.messages.map((message, index) => React.createElement('article', { key: `${message.messageId}:${index}`, style: { position: 'relative', minWidth: 0, padding: '10px 0 15px 22px', borderLeft: `1px solid ${colors.border}` } }, React.createElement('span', { 'aria-hidden': true, style: { position: 'absolute', top: 15, left: -4, width: 7, height: 7, borderRadius: '50%', background: index === 0 ? colors.accent : colors.cardSurface, border: `1px solid ${index === 0 ? colors.accent : colors.border}` } }), React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'baseline', gap: '2px 8px', color: colors.muted, fontSize: 11 } }, React.createElement('strong', { style: { color: 'inherit', fontWeight: 600 } }, message.senderName || message.sourceKind || '未知发送人'), React.createElement('time', { dateTime: message.occurredAt || undefined, style: { fontVariantNumeric: 'tabular-nums' } }, fmt(message.occurredAt))), React.createElement('p', { style: { margin: '6px 0 0', whiteSpace: 'pre-wrap', overflowWrap: 'anywhere', fontSize: 13, lineHeight: 1.7 } }, message.text || '(空消息)'), message.quotedMessage?.content ? React.createElement('details', { style: { marginTop: 9, color: colors.muted, fontSize: 12 } }, React.createElement('summary', { style: { cursor: 'pointer', fontWeight: 600 } }, '查看引用消息'), React.createElement('blockquote', { style: { margin: '7px 0 0', padding: '8px 10px', borderLeft: `2px solid ${colors.border}`, background: colors.surface2, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere', lineHeight: 1.6 } }, message.quotedMessage.content)) : null)) : [emptyState('此版本没有消息')])),
|
|
197
|
+
pager(messageOffset, context.total, messagePageSize, setMessageOffset, detailLoading))
|
|
198
|
+
))))
|
|
199
|
+
}
|
|
122
200
|
function ObserverContent({ openSession }) {
|
|
123
201
|
const [activePage, setActivePage] = useState('groups')
|
|
202
|
+
const [topicTarget, setTopicTarget] = useState()
|
|
124
203
|
const [data, setData] = useState()
|
|
125
204
|
const [error, setError] = useState()
|
|
126
205
|
const [navigationError, setNavigationError] = useState()
|
|
@@ -168,6 +247,7 @@ window.__ModuleLoader__.load({
|
|
|
168
247
|
return () => document.removeEventListener('click', closeForSession, true)
|
|
169
248
|
}, [])
|
|
170
249
|
const groupsById = new Map((data?.groups || []).map((group) => [group.groupId, group]))
|
|
250
|
+
const topicsById = new Map((data?.topics || []).map((topic) => [`${topic.groupId}:${topic.topicId}`, topic]))
|
|
171
251
|
const navigate = async (sessionId, parentSessionId) => {
|
|
172
252
|
try { setNavigationError(undefined); setNavigatingSessionId(sessionId); await openSession(sessionId, parentSessionId); setOpen(false) }
|
|
173
253
|
catch (cause) { setNavigationError(cause instanceof Error ? cause.message : String(cause)) }
|
|
@@ -195,51 +275,54 @@ window.__ModuleLoader__.load({
|
|
|
195
275
|
}
|
|
196
276
|
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}`)
|
|
197
277
|
const selectedGroup = groupsById.get(selectedGroupId) || (data?.groups || [])[0]
|
|
198
|
-
const selectedMessages = [...(selectedGroup?.messages || [])].sort((left, right) => Number(right.sequence || 0) - Number(left.sequence || 0))
|
|
199
|
-
const
|
|
278
|
+
const selectedMessages = [...(selectedGroup?.messages || [])].filter((message) => message.sourceKind === 'dingtalk' || message.sourceKind === 'migration' || !message.sourceKind).sort((left, right) => Number(right.sequence || 0) - Number(left.sequence || 0))
|
|
279
|
+
const messageWorkflowState = (message) => {
|
|
280
|
+
if (message.routingStatus === 'failed') return 'failed'
|
|
281
|
+
if (message.routingStatus !== 'routed') return 'routing'
|
|
282
|
+
const related = (selectedGroup?.topics || []).flatMap((topic) => {
|
|
283
|
+
const entry = [...(topic.entries || [])].reverse().find((item) => item.messageId === message.messageId)
|
|
284
|
+
return entry?.action === 'add' && entry.messageVersion === message.messageVersion ? [{ topic, entry }] : []
|
|
285
|
+
})
|
|
286
|
+
if (related.some(({ topic, entry }) => (topic.processedRevision || 0) < entry.revision)) return 'processing'
|
|
287
|
+
return 'processed'
|
|
288
|
+
}
|
|
289
|
+
const filteredMessages = selectedMessages.filter((message) => messageDeliveryFilter === 'all' || messageWorkflowState(message) === messageDeliveryFilter)
|
|
200
290
|
const pageSize = 10
|
|
201
291
|
const pageCount = Math.max(1, Math.ceil(filteredMessages.length / pageSize))
|
|
202
292
|
const currentMessagePage = Math.min(messagePage, pageCount)
|
|
203
293
|
const visibleMessages = filteredMessages.slice((currentMessagePage - 1) * pageSize, currentMessagePage * pageSize)
|
|
204
294
|
const delivery = {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
'decision-commit-failed': { label: '处理提交失败', state: 'error' },
|
|
210
|
-
steered: { label: '已插话·判断中', state: 'ongoing' },
|
|
211
|
-
pending: { label: '投递中', state: 'ongoing' },
|
|
212
|
-
skipped: { label: '历史补拉·未投递', state: 'warning' },
|
|
213
|
-
unknown: { label: '历史状态未知', state: 'warning' },
|
|
295
|
+
routing: { label: '待归类', state: 'ongoing' },
|
|
296
|
+
processing: { label: '话题处理中', state: 'ongoing' },
|
|
297
|
+
processed: { label: '已处理', state: 'done' },
|
|
298
|
+
failed: { label: '归类失败', state: 'error' },
|
|
214
299
|
}
|
|
215
300
|
const messageRows = visibleMessages.map((message, rowIndex) => {
|
|
216
|
-
const status = delivery[message
|
|
301
|
+
const status = delivery[messageWorkflowState(message)]
|
|
217
302
|
return React.createElement('tr', { key: message.messageId, style: { background: rowIndex % 2 ? `color-mix(in srgb, ${colors.surface2} 55%, transparent)` : colors.cardSurface } },
|
|
218
303
|
React.createElement('td', { style: { ...tableBodyCell, width: 104 }, title: message.agentDeliveryError || '' }, clampTableContent(tableStatusTag(status.label, status.state, { fontWeight: 600 }))),
|
|
219
304
|
React.createElement('td', { style: { ...tableBodyCell, width: 160 } }, clampTableContent(React.createElement('strong', { style: { fontSize: 14, fontWeight: 600 } }, message.senderName || message.senderOpenDingTalkId || '发送人未记录'), React.createElement('div', { style: { marginTop: 3, fontSize: 11, color: colors.muted } }, fmt(message.occurredAt)))),
|
|
305
|
+
React.createElement('td', { style: { ...tableBodyCell, width: 160 } }, React.createElement('div', { style: { display: 'flex', gap: 4, minWidth: 0, overflow: 'hidden' } }, ...(message.topicRefs?.length ? message.topicRefs.map((ref) => React.createElement('button', { key: ref.topicId, type: 'button', title: ref.title, 'aria-label': `话题 ${ref.title}`, onClick: () => { setTopicTarget({ groupId: selectedGroup.groupId, topicId: ref.topicId, revision: ref.revision }); setActivePage('topics') }, style: { ...pill(colors.accent), display: 'inline-block', flex: '0 1 auto', minWidth: 0, maxWidth: 120, border: 0, padding: '2px 7px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', cursor: 'pointer', fontFamily: 'inherit' } }, ref.title)) : [React.createElement('span', { key: 'unrouted', style: { color: colors.muted, fontSize: 12 } }, '待归类')]))),
|
|
220
306
|
React.createElement('td', { title: message.text, style: tableBodyCell }, clampTableContent(message.text || '(空消息)')),
|
|
221
307
|
React.createElement('td', { style: { ...tableBodyCell, width: 240 } }, singleLineTableContent(React.createElement('code', { title: message.messageId, style: { fontSize: 14, color: colors.muted, whiteSpace: 'nowrap' } }, `#${message.sequence ?? '—'} · ${short(message.messageId)}`))))
|
|
222
308
|
})
|
|
223
309
|
const selectedOutbox = [...(selectedGroup?.outbox || [])].reverse()
|
|
224
|
-
const outboxState = (message) => message.
|
|
310
|
+
const outboxState = (message) => outboxDelivery(message).id
|
|
225
311
|
const filteredOutbox = selectedOutbox.filter((message) => outboxStatusFilter === 'all' || outboxState(message) === outboxStatusFilter)
|
|
226
312
|
const outboxPageSize = 10
|
|
227
313
|
const outboxPageCount = Math.max(1, Math.ceil(filteredOutbox.length / outboxPageSize))
|
|
228
314
|
const currentOutboxPage = Math.min(outboxPage, outboxPageCount)
|
|
229
315
|
const visibleOutbox = filteredOutbox.slice((currentOutboxPage - 1) * outboxPageSize, currentOutboxPage * outboxPageSize)
|
|
230
|
-
const outboundStatus = {
|
|
231
|
-
sent: { label: '已回读', state: 'done' },
|
|
232
|
-
pending: { label: '已回读', state: 'done' },
|
|
233
|
-
recalled: { label: '已撤回', state: 'neutral' },
|
|
234
|
-
}
|
|
235
316
|
const outboxRows = visibleOutbox.map((message, rowIndex) => {
|
|
236
|
-
const status = message
|
|
317
|
+
const status = outboxDelivery(message)
|
|
237
318
|
return React.createElement('tr', { key: message.outboundId, style: { background: rowIndex % 2 ? `color-mix(in srgb, ${colors.surface2} 55%, transparent)` : colors.cardSurface } },
|
|
238
|
-
React.createElement('td', { style: { ...tableBodyCell, width:
|
|
239
|
-
React.createElement('td', { title: message.text, style: tableBodyCell }, clampTableContent(message.text || '(空消息)')
|
|
319
|
+
React.createElement('td', { style: { ...tableBodyCell, width: 124 } }, clampTableContent(tableStatusTag(status.label, status.state, { fontWeight: 600 }))),
|
|
320
|
+
React.createElement('td', { title: message.text, style: tableBodyCell }, clampTableContent(message.text || '(空消息)'),
|
|
321
|
+
status.detail ? React.createElement('div', { style: { marginTop: 6, fontSize: 12, color: status.state === 'error' ? colors.danger : colors.warning, overflowWrap: 'anywhere' } }, status.detail, message.deliveryError ? `:${message.deliveryError}` : '') : null,
|
|
322
|
+
message.deliveryAttemptedAt ? React.createElement('div', { style: { marginTop: 3, fontSize: 11, color: colors.muted } }, `最近尝试 ${fmt(message.deliveryAttemptedAt)} · 共 ${message.deliveryAttemptCount || 1} 次`) : null),
|
|
240
323
|
React.createElement('td', { style: { ...tableBodyCell, width: 220 } },
|
|
241
324
|
React.createElement('div', { style: { minWidth: 0 } }, singleLineTableContent(React.createElement('code', { title: message.sourceMessageId, style: { fontSize: 14, color: colors.muted, whiteSpace: 'nowrap' } }, short(message.sourceMessageId))), message.replyToMessageId ? React.createElement('div', { style: { marginTop: 3, fontSize: 11, color: colors.muted, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } }, '回复 ', React.createElement('code', { title: message.replyToMessageId }, short(message.replyToMessageId))) : null)),
|
|
242
|
-
React.createElement('td', { style: { ...tableBodyCell, width: 220 } }, singleLineTableContent(React.createElement('code', { title: message.deliveredMessageId || '', style: { fontSize: 14, color: colors.muted, whiteSpace: 'nowrap' } }, message.deliveredMessageId ? short(message.deliveredMessageId) : '历史未记录'))),
|
|
325
|
+
React.createElement('td', { style: { ...tableBodyCell, width: 220 } }, singleLineTableContent(React.createElement('code', { title: message.deliveredMessageId || '', style: { fontSize: 14, color: colors.muted, whiteSpace: 'nowrap' } }, message.deliveredMessageId ? short(message.deliveredMessageId) : message.status === 'sent' ? '历史未记录' : '尚未确认'))),
|
|
243
326
|
React.createElement('td', { style: { ...tableBodyCell, width: 220 } }, singleLineTableContent(React.createElement('code', { title: message.outboundId, style: { fontSize: 14, color: colors.muted, whiteSpace: 'nowrap' } }, short(message.outboundId)))))
|
|
244
327
|
})
|
|
245
328
|
const renderTaskCard = (task) => {
|
|
@@ -267,7 +350,9 @@ window.__ModuleLoader__.load({
|
|
|
267
350
|
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' } },
|
|
268
351
|
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' } },
|
|
269
352
|
React.createElement('strong', { title: task.title || task.objective, style: { fontSize: 13, lineHeight: 1.45, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', overflowWrap: 'anywhere' } }, task.title || task.objective),
|
|
270
|
-
React.createElement('div', { style: { display: 'flex', alignItems: 'center',
|
|
353
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 5, minWidth: 0, overflow: 'hidden' } },
|
|
354
|
+
React.createElement('span', { title: group?.name || task.groupId, style: { ...pill(colors.muted), flex: '0 1 auto', maxWidth: 150, padding: '2px 7px', fontSize: 10.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, group?.name || task.groupId),
|
|
355
|
+
...(task.topicRefs || []).map((ref) => { const topic = topicsById.get(`${task.groupId}:${ref.topicId}`); const label = topic?.title || `话题 ${short(ref.topicId)}`; return React.createElement('span', { key: ref.topicId, role: 'button', tabIndex: 0, title: label, 'aria-label': `话题 ${label}`, 'data-task-card-action': 'open-topic', onClick: (event) => { event.stopPropagation(); setTopicTarget({ groupId: task.groupId, topicId: ref.topicId, revision: ref.revision }); setActivePage('topics') }, onKeyDown: (event) => { if (event.key !== 'Enter' && event.key !== ' ') return; event.preventDefault(); event.stopPropagation(); setTopicTarget({ groupId: task.groupId, topicId: ref.topicId, revision: ref.revision }); setActivePage('topics') }, style: { ...pill(colors.accent), display: 'inline-block', flex: '0 1 auto', minWidth: 0, maxWidth: 96, padding: '2px 7px', fontSize: 10.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', cursor: 'pointer' } }, label) })),
|
|
271
356
|
React.createElement('div', { title: displayObjective, style: { minWidth: 0, display: 'block', maxHeight: '4.5em', overflow: 'hidden', overflowWrap: 'anywhere', fontSize: 11, lineHeight: 1.5 } }, React.createElement('svg', { width: 12, height: 12, viewBox: '0 0 16 16', fill: 'none', stroke: 'currentColor', strokeWidth: 1.4, strokeLinecap: 'round', strokeLinejoin: 'round', role: 'img', 'aria-label': '任务目标', style: { display: 'inline-block', verticalAlign: -2, marginRight: 5, color: colors.muted } }, React.createElement('circle', { cx: 8, cy: 8, r: 5.5 }), React.createElement('circle', { cx: 8, cy: 8, r: 2 }), React.createElement('path', { d: 'M8 1v2M15 8h-2' })), displayObjective),
|
|
272
357
|
React.createElement('div', null,
|
|
273
358
|
showCheckpoints ? React.createElement('section', null,
|
|
@@ -277,8 +362,7 @@ window.__ModuleLoader__.load({
|
|
|
277
362
|
React.createElement('div', { style: { minWidth: 0, marginTop: 0, paddingTop: 6, borderTop: `1px solid ${colors.border}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 } },
|
|
278
363
|
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)),
|
|
279
364
|
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),
|
|
280
|
-
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)
|
|
281
|
-
null)
|
|
365
|
+
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))
|
|
282
366
|
}
|
|
283
367
|
const alertCategories = [
|
|
284
368
|
{ id: 'all', label: '全部类型' },
|
|
@@ -340,16 +424,16 @@ window.__ModuleLoader__.load({
|
|
|
340
424
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 8, flexWrap: 'wrap' } },
|
|
341
425
|
(data?.groups || []).length ? React.createElement(SelectMenu, { label: '选择群聊会话', value: selectedGroup?.groupId || '', options: (data?.groups || []).map((group) => ({ id: group.groupId, label: group.name || group.groupId })), onChange: (value) => { setSelectedGroupId(value); setMessagePage(1); setOutboxPage(1) }, fitContent: true }) : null,
|
|
342
426
|
groupTableView === 'messages'
|
|
343
|
-
? React.createElement(SelectMenu, { label: '
|
|
344
|
-
: React.createElement(SelectMenu, { label: '筛选发件状态', value: outboxStatusFilter, options: [{ id: 'all', label: '全部发件状态' }, { id: '
|
|
427
|
+
? React.createElement(SelectMenu, { label: '筛选处理状态', value: messageDeliveryFilter, options: [{ id: 'all', label: '全部处理状态' }, { id: 'routing', label: '待归类' }, { id: 'processing', label: '话题处理中' }, { id: 'processed', label: '已处理' }, { id: 'failed', label: '归类失败' }], onChange: (value) => { setMessageDeliveryFilter(value); setMessagePage(1) } })
|
|
428
|
+
: React.createElement(SelectMenu, { label: '筛选发件状态', value: outboxStatusFilter, options: [{ id: 'all', label: '全部发件状态' }, { id: 'queued', label: '待发送' }, { id: 'failed', label: '投递异常' }, { id: 'waiting', label: '待回读' }, { id: 'confirmed', label: '已发送' }, { id: 'recalled', label: '已撤回' }], onChange: (value) => { setOutboxStatusFilter(value); setOutboxPage(1) } })))
|
|
345
429
|
const messagesTable = React.createElement(React.Fragment, null,
|
|
346
430
|
React.createElement('div', { style: { overflowX: 'auto' } }, React.createElement('table', { style: { width: '100%', minWidth: 910, borderCollapse: 'collapse', tableLayout: 'fixed' } },
|
|
347
|
-
React.createElement('thead', null, React.createElement('tr', { style: { background: colors.surface2, textAlign: 'left' } }, React.createElement('th', { style: { ...tableHeadCell, width: 104 } }, '
|
|
348
|
-
React.createElement('tbody', null, ...(messageRows.length ? messageRows : [React.createElement('tr', { key: 'empty' }, React.createElement('td', { colSpan:
|
|
431
|
+
React.createElement('thead', null, React.createElement('tr', { style: { background: colors.surface2, textAlign: 'left' } }, React.createElement('th', { style: { ...tableHeadCell, width: 104 } }, '话题处理'), React.createElement('th', { style: { ...tableHeadCell, width: 160 } }, '发送人 / 时间'), React.createElement('th', { style: { ...tableHeadCell, width: 160 } }, '话题'), React.createElement('th', { style: tableHeadCell }, '消息内容'), React.createElement('th', { style: { ...tableHeadCell, width: 240 } }, '消息'))),
|
|
432
|
+
React.createElement('tbody', null, ...(messageRows.length ? messageRows : [React.createElement('tr', { key: 'empty' }, React.createElement('td', { colSpan: 5, style: { ...tableBodyCell, padding: 36, textAlign: 'center', color: colors.muted } }, '暂无符合条件的群聊消息'))])))),
|
|
349
433
|
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)) }, '下一页')))
|
|
350
434
|
const outboxTable = React.createElement(React.Fragment, null,
|
|
351
435
|
React.createElement('div', { style: { overflowX: 'auto' } }, React.createElement('table', { style: { width: '100%', minWidth: 1100, borderCollapse: 'collapse', tableLayout: 'fixed' } },
|
|
352
|
-
React.createElement('thead', null, React.createElement('tr', { style: { background: colors.surface2, textAlign: 'left' } }, React.createElement('th', { style: { ...tableHeadCell, width:
|
|
436
|
+
React.createElement('thead', null, React.createElement('tr', { style: { background: colors.surface2, textAlign: 'left' } }, React.createElement('th', { style: { ...tableHeadCell, width: 124 } }, '发送状态'), React.createElement('th', { style: tableHeadCell }, '消息内容'), React.createElement('th', { style: { ...tableHeadCell, width: 220 } }, '来源 / 回复目标'), React.createElement('th', { style: { ...tableHeadCell, width: 220 } }, '投递消息 ID'), React.createElement('th', { style: { ...tableHeadCell, width: 220 } }, '发信箱 ID'))),
|
|
353
437
|
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 发件记录'))])))),
|
|
354
438
|
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)) }, '下一页')))
|
|
355
439
|
const groupsPage = React.createElement('section', null, React.createElement('div', { style: tableFrame }, groupTableToolbar, groupTableView === 'messages' ? messagesTable : outboxTable))
|
|
@@ -430,7 +514,8 @@ window.__ModuleLoader__.load({
|
|
|
430
514
|
React.createElement('div', { style: { padding: '0 16px' } }, ...(alertView === 'active' ? (filteredActiveAlerts.length ? filteredActiveAlerts.map(renderAlert) : [React.createElement('div', { key: 'empty', style: { padding: '36px 0', textAlign: 'center', color: colors.muted, fontSize: 12 } }, '当前没有此类型异常')]) : (visibleResolvedAlerts.length ? visibleResolvedAlerts.map(renderAlert) : [React.createElement('div', { key: 'empty', style: { padding: '36px 0', textAlign: 'center', color: colors.muted, fontSize: 12 } }, '暂无此类型恢复记录')]))),
|
|
431
515
|
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))
|
|
432
516
|
)
|
|
433
|
-
const
|
|
517
|
+
const topicsPage = React.createElement(TopicBrowser, { groups: data?.groups || [], tasks: data?.tasks || [], target: topicTarget, updatedAt, onOpenTask: (task) => navigate(task.childSessionId, groupsById.get(task.groupId)?.residentSessionId) })
|
|
518
|
+
const pageContent = activePage === 'topics' ? topicsPage : activePage === 'tasks' ? tasksPage : activePage === 'authorizations' ? authorizationsPage : activePage === 'archive' ? archivePage : activePage === 'alerts' ? alertsPage : groupsPage
|
|
434
519
|
const pageViewport = React.createElement('div', { style: { width: '100%', maxWidth: 1320, minHeight: 'calc(100dvh - 138px)', boxSizing: 'border-box', margin: '0 auto' } }, pageContent)
|
|
435
520
|
const main = React.createElement('main', { style: { width: '100%', minHeight: 'calc(100dvh - 90px)', boxSizing: 'border-box', padding: activePage === 'tasks' ? '24px 24px 0' : '24px 24px 32px', display: 'grid', alignContent: 'start', gap: 20, pointerEvents: 'auto' } },
|
|
436
521
|
error ? React.createElement('div', { style: { ...card, borderColor: colors.danger, color: colors.danger } }, `无法连接 resident 插件:${error}`) : null,
|