@zzusp/dingtalk-dsh-observer 0.5.13 → 0.5.15
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 +86 -38
package/package.json
CHANGED
package/web-client.js
CHANGED
|
@@ -52,17 +52,59 @@ 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
|
|
56
|
-
|
|
55
|
+
const outboxDelivery = (message) => {
|
|
56
|
+
if (message.recallStatus === 'recalled') return { id: 'recalled', label: '已撤回', state: 'neutral' }
|
|
57
|
+
if (message.recallStatus === 'failed') return { id: 'recall-failed', label: '撤回待处理', state: 'warning', detail: message.recallError === 'replacement_delivery_unknown' ? '已停止旧意图发送;历史是否送达仍未知,等待核验' : `旧消息撤回失败${message.recallRetryAt ? ',将有限重试' : ',需核验后处理'}:${message.recallError || '原因未记录'}` }
|
|
58
|
+
if (message.supersededByOutboundId || message.status === 'superseded') return { id: 'superseded', label: '已替代', state: 'neutral', detail: message.deliveredMessageId ? '已记录送达,等待替换通知送达后撤回' : '已停止旧意图发送;未声称历史消息未送达' }
|
|
59
|
+
if (message.status === 'sent') return { id: 'confirmed', label: message.deliveredMessageId ? '已回读' : '已发送', state: 'done' }
|
|
60
|
+
if (message.deliveryBlockedAt) return { id: 'failed', label: '发送待处理', state: 'error', detail: '服务端已拒绝发送,停止自动重试;需核验失败原因' }
|
|
61
|
+
const reason = message.deliveryPendingReason
|
|
62
|
+
const details = {
|
|
63
|
+
preflight_history_partial: '发送前检查:历史消息不完整,本次未发送',
|
|
64
|
+
preflight_failed: '发送前检查失败,本次未发送',
|
|
65
|
+
send_failed: '发送调用失败,尚未确认送达',
|
|
66
|
+
delivery_unknown: '发送结果未知,等待确认',
|
|
67
|
+
postflight_history_partial: '发送后回读:历史消息不完整',
|
|
68
|
+
postflight_failed: '发送后回读失败',
|
|
69
|
+
message_not_observed: '已调用发送,回读尚未找到对应消息',
|
|
70
|
+
}
|
|
71
|
+
const detail = details[reason] || reason || (message.deliveryError ? '投递异常' : '')
|
|
72
|
+
if (['delivery_unknown', 'postflight_history_partial', 'postflight_failed', 'message_not_observed'].includes(reason)) return { id: 'waiting', label: '待回读', state: 'warning', detail }
|
|
73
|
+
if (reason === 'preflight_failed' || reason === 'preflight_history_partial') return { id: 'failed', label: '发送受阻', state: 'error', detail }
|
|
74
|
+
if (message.deliveryError || reason === 'send_failed') return { id: 'failed', label: '投递异常', state: 'error', detail }
|
|
75
|
+
return { id: 'queued', label: '待发送', state: 'ongoing', detail }
|
|
76
|
+
}
|
|
77
|
+
const ui = { textSm: 12, textMd: 14, textLg: 16, textXl: 20, space1: 4, space2: 8, space3: 12, space4: 16, space6: 24, radiusSm: 8, radiusMd: 12 }
|
|
78
|
+
const observerCss = `
|
|
79
|
+
[data-dingtalk-observer] :is(button, input, select, textarea, summary, [tabindex]):focus-visible {
|
|
80
|
+
outline: 2px solid var(--dsw-alias-brand-primary, #4d6bfe) !important;
|
|
81
|
+
outline-offset: 2px !important;
|
|
82
|
+
}
|
|
83
|
+
@media (max-width: 960px) {
|
|
84
|
+
[data-dingtalk-observer] .observer-main { padding: 16px !important; }
|
|
85
|
+
[data-dingtalk-observer] .observer-topics-layout { display: grid !important; grid-template-columns: minmax(280px, 2fr) minmax(0, 3fr); }
|
|
86
|
+
[data-dingtalk-observer] .observer-topic-list { max-width: none !important; }
|
|
87
|
+
[data-dingtalk-observer] .observer-task-board { grid-template-columns: repeat(2, minmax(280px, 1fr)) !important; }
|
|
88
|
+
}
|
|
89
|
+
@media (max-width: 720px) {
|
|
90
|
+
[data-dingtalk-observer] .observer-main { padding: 12px !important; }
|
|
91
|
+
[data-dingtalk-observer] .observer-topics-layout,
|
|
92
|
+
[data-dingtalk-observer] .observer-task-board { grid-template-columns: minmax(0, 1fr) !important; }
|
|
93
|
+
[data-dingtalk-observer] .observer-topic-detail { min-height: 320px !important; }
|
|
94
|
+
[data-dingtalk-observer] .observer-toolbar { align-items: stretch !important; }
|
|
95
|
+
}
|
|
96
|
+
`
|
|
97
|
+
const pill = (tone) => ({ display: 'inline-flex', alignItems: 'center', borderRadius: 999, padding: `${ui.space1}px ${ui.space2}px`, fontSize: ui.textSm, color: tone, background: `color-mix(in srgb, ${tone} 12%, transparent)` })
|
|
98
|
+
const card = { border: `1px solid ${colors.border}`, borderRadius: ui.radiusMd, background: colors.cardSurface, padding: ui.space4 }
|
|
57
99
|
const tableFrame = { border: `1px solid ${colors.border}`, borderRadius: 12, background: colors.cardSurface, overflow: 'hidden' }
|
|
58
|
-
const tableHeadCell = { height: 42, boxSizing: 'border-box', padding:
|
|
59
|
-
const tableBodyCell = { padding:
|
|
100
|
+
const tableHeadCell = { height: 42, boxSizing: 'border-box', padding: `0 ${ui.space4}px`, color: colors.muted, fontSize: ui.textSm, fontWeight: 600, verticalAlign: 'middle' }
|
|
101
|
+
const tableBodyCell = { padding: `${ui.space3}px ${ui.space4}px`, borderTop: `1px solid ${colors.border}`, fontSize: ui.textMd, lineHeight: 1.55, verticalAlign: 'middle' }
|
|
60
102
|
const tableBodyContent = { display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', overflowWrap: 'anywhere' }
|
|
61
103
|
const clampTableContent = (...children) => React.createElement('div', { style: tableBodyContent }, ...children)
|
|
62
104
|
const singleLineTableContent = (...children) => React.createElement('div', { style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, ...children)
|
|
63
105
|
const tableFooter = { minHeight: 48, boxSizing: 'border-box', display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: 8, padding: '8px 12px', borderTop: `1px solid ${colors.border}`, background: colors.surface2 }
|
|
64
106
|
const toolbar = { minHeight: 48, boxSizing: 'border-box', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, padding: '8px 12px', borderBottom: `1px solid ${colors.border}`, background: colors.cardSurface }
|
|
65
|
-
const navigationTabStyle = (selected, { paddingBottom =
|
|
107
|
+
const navigationTabStyle = (selected, { paddingBottom = 12 } = {}) => ({ position: 'relative', boxSizing: 'border-box', border: 0, borderBottom: selected ? `2px solid ${colors.accent}` : '2px solid transparent', borderRadius: 0, padding: `0 0 ${paddingBottom}px`, cursor: 'pointer', font: 'inherit', fontSize: ui.textMd, fontWeight: 500, lineHeight: '20px', color: selected ? colors.accent : colors.muted, background: 'transparent', whiteSpace: 'nowrap' })
|
|
66
108
|
const statusTone = {
|
|
67
109
|
done: 'var(--dsw-alias-state-success-primary, #248a3d)',
|
|
68
110
|
warning: 'var(--dsw-alias-state-warn-primary, #a56500)',
|
|
@@ -81,7 +123,11 @@ window.__ModuleLoader__.load({
|
|
|
81
123
|
const anchor = React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', 'aria-label': label, 'aria-expanded': open, onClick: () => setOpen((current) => !current), style: { minWidth: fitContent ? 0 : minWidth, width: fitContent ? 'fit-content' : undefined, maxWidth: fitContent ? 220 : undefined, justifyContent: fitContent ? 'flex-start' : 'space-between', gap: fitContent ? 8 : 16, fontWeight: 400 } }, React.createElement('span', { style: { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, selected?.label || value), React.createElement('span', { 'aria-hidden': true, style: { flex: '0 0 auto', color: colors.muted, fontSize: 9, transform: open ? 'rotate(180deg)' : 'none', transition: 'transform .15s ease' } }, '▼'))
|
|
82
124
|
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 })
|
|
83
125
|
}
|
|
84
|
-
const emptyState = (
|
|
126
|
+
const emptyState = (title, { detail, actionLabel, onAction } = {}) => React.createElement('div', { style: { minHeight: 112, display: 'grid', placeItems: 'center', padding: ui.space4, textAlign: 'center', color: colors.muted, fontSize: ui.textSm, border: `1px dashed ${colors.border}`, borderRadius: ui.radiusSm, background: colors.surface2 } },
|
|
127
|
+
React.createElement('div', { style: { display: 'grid', justifyItems: 'center', gap: ui.space2 } },
|
|
128
|
+
React.createElement('strong', { style: { color: 'inherit', fontSize: ui.textMd, fontWeight: 600 } }, title),
|
|
129
|
+
detail ? React.createElement('span', null, detail) : null,
|
|
130
|
+
actionLabel && onAction ? React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', onClick: onAction }, actionLabel) : null))
|
|
85
131
|
async function get(path) {
|
|
86
132
|
const response = await fetch(`${ENDPOINT}${path}`, { method: 'GET', headers: { accept: 'application/json' } })
|
|
87
133
|
const value = await response.json()
|
|
@@ -155,17 +201,17 @@ window.__ModuleLoader__.load({
|
|
|
155
201
|
const relatedTasks = tasks.filter((task) => task.groupId === selection?.groupId && task.topicRefs?.some((ref) => ref.topicId === selection?.topicId))
|
|
156
202
|
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) }, '重试'))
|
|
157
203
|
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) }, '下一页'))
|
|
158
|
-
return React.createElement('section', { 'aria-label': '话题与上下文', style: { display: 'grid', gap:
|
|
159
|
-
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:
|
|
160
|
-
React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'flex-start', gap:
|
|
161
|
-
React.createElement('section', { 'aria-label': '话题列表', 'aria-busy': loading, style: { ...tableFrame, flex: '1 1 340px', width: '100%', minWidth: 0, maxWidth: 420, minHeight: 200 } },
|
|
204
|
+
return React.createElement('section', { 'aria-label': '话题与上下文', style: { display: 'grid', gap: ui.space4 } },
|
|
205
|
+
React.createElement('div', { className: 'observer-toolbar', style: { ...toolbar, flexWrap: 'wrap' } }, React.createElement(SelectMenu, { label: '筛选话题群聊', value: groupId, options: [{ id: '', label: '全部群聊' }, ...groups.map((item) => ({ id: item.groupId, label: item.name || item.groupId }))], onChange: (value) => { setGroupId(value); setOffset(0); setListing(undefined); setSelection(undefined) } }), React.createElement('span', { style: { fontSize: ui.textSm, color: colors.muted } }, group?.topicProgress ? `待归类 ${group.topicProgress.unroutedMessages} 条 · 待处理 ${group.topicProgress.pending} 个话题` : '接收、话题处理与任务完成分别计量')),
|
|
206
|
+
React.createElement('div', { className: 'observer-topics-layout', style: { display: 'flex', flexWrap: 'wrap', alignItems: 'flex-start', gap: ui.space4 } },
|
|
207
|
+
React.createElement('section', { className: 'observer-topic-list', 'aria-label': '话题列表', 'aria-busy': loading, style: { ...tableFrame, flex: '1 1 340px', width: '100%', minWidth: 0, maxWidth: 420, minHeight: 200 } },
|
|
162
208
|
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),
|
|
163
|
-
listError ? failure(`话题加载失败:${listError}`) : !listing ? emptyState('正在读取话题…') : listing.topics.length === 0 ? emptyState('
|
|
164
|
-
React.createElement('strong', { title: item.title || item.topicId, style: { display: 'block', minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', overflowWrap: 'anywhere', fontSize:
|
|
165
|
-
React.createElement('p', { title: item.summary || '暂无摘要', style: { margin:
|
|
209
|
+
listError ? failure(`话题加载失败:${listError}`) : !listing ? emptyState('正在读取话题…') : listing.topics.length === 0 ? emptyState('暂无话题', { detail: '新消息完成归类后会显示在这里。', actionLabel: '重新读取', onAction: () => setRetry((value) => value + 1) }) : React.createElement('ul', { style: { listStyle: 'none', margin: 0, padding: 0 } }, ...listing.topics.map((item) => { const selected = selection?.topicId === item.topicId && selection?.groupId === item.groupId; return React.createElement('li', { key: `${item.groupId}:${item.topicId}`, style: { borderBottom: `1px solid ${colors.border}` } }, React.createElement('button', { type: 'button', 'aria-pressed': selected, 'aria-label': item.title || item.topicId, onClick: () => selectTopic(item), style: { position: 'relative', width: '100%', minWidth: 0, border: 0, borderLeft: `3px solid ${selected ? colors.accent : 'transparent'}`, padding: `${ui.space4}px ${ui.space4}px ${ui.space4}px ${ui.space3}px`, background: selected ? `color-mix(in srgb, ${colors.accent} 8%, ${colors.cardSurface})` : colors.cardSurface, color: 'inherit', cursor: 'pointer', textAlign: 'left', fontFamily: 'inherit', outlineOffset: -2 } },
|
|
210
|
+
React.createElement('strong', { title: item.title || item.topicId, style: { display: 'block', minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', overflowWrap: 'anywhere', fontSize: ui.textMd, fontWeight: 650, lineHeight: 1.5 } }, item.title || item.topicId),
|
|
211
|
+
React.createElement('p', { title: item.summary || '暂无摘要', style: { margin: `${ui.space1}px 0 0`, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', overflowWrap: 'anywhere', whiteSpace: 'pre-wrap', color: colors.muted, fontSize: ui.textSm, lineHeight: 1.6 } }, item.summary || '暂无摘要')
|
|
166
212
|
)) })),
|
|
167
213
|
listing ? pager(offset, listing.total, topicPageSize, setOffset, loading) : null),
|
|
168
|
-
React.createElement('section', { 'aria-label': '话题详情', 'aria-busy': detailLoading, style: { ...tableFrame, flex: '2 1 560px', width: '100%', minWidth: 0, minHeight: 200 } },
|
|
214
|
+
React.createElement('section', { className: 'observer-topic-detail', 'aria-label': '话题详情', 'aria-busy': detailLoading, style: { ...tableFrame, flex: '2 1 560px', width: '100%', minWidth: 0, minHeight: 200 } },
|
|
169
215
|
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 || '话题详情')),
|
|
170
216
|
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' } },
|
|
171
217
|
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 || '暂无摘要')),
|
|
@@ -288,25 +334,22 @@ window.__ModuleLoader__.load({
|
|
|
288
334
|
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)}`))))
|
|
289
335
|
})
|
|
290
336
|
const selectedOutbox = [...(selectedGroup?.outbox || [])].reverse()
|
|
291
|
-
const outboxState = (message) => message.
|
|
337
|
+
const outboxState = (message) => outboxDelivery(message).id
|
|
292
338
|
const filteredOutbox = selectedOutbox.filter((message) => outboxStatusFilter === 'all' || outboxState(message) === outboxStatusFilter)
|
|
293
339
|
const outboxPageSize = 10
|
|
294
340
|
const outboxPageCount = Math.max(1, Math.ceil(filteredOutbox.length / outboxPageSize))
|
|
295
341
|
const currentOutboxPage = Math.min(outboxPage, outboxPageCount)
|
|
296
342
|
const visibleOutbox = filteredOutbox.slice((currentOutboxPage - 1) * outboxPageSize, currentOutboxPage * outboxPageSize)
|
|
297
|
-
const outboundStatus = {
|
|
298
|
-
sent: { label: '已回读', state: 'done' },
|
|
299
|
-
pending: { label: '已回读', state: 'done' },
|
|
300
|
-
recalled: { label: '已撤回', state: 'neutral' },
|
|
301
|
-
}
|
|
302
343
|
const outboxRows = visibleOutbox.map((message, rowIndex) => {
|
|
303
|
-
const status = message
|
|
344
|
+
const status = outboxDelivery(message)
|
|
304
345
|
return React.createElement('tr', { key: message.outboundId, style: { background: rowIndex % 2 ? `color-mix(in srgb, ${colors.surface2} 55%, transparent)` : colors.cardSurface } },
|
|
305
|
-
React.createElement('td', { style: { ...tableBodyCell, width:
|
|
306
|
-
React.createElement('td', { title: message.text, style: tableBodyCell }, clampTableContent(message.text || '(空消息)')
|
|
346
|
+
React.createElement('td', { style: { ...tableBodyCell, width: 124 } }, clampTableContent(tableStatusTag(status.label, status.state, { fontWeight: 600 }))),
|
|
347
|
+
React.createElement('td', { title: message.text, style: tableBodyCell }, clampTableContent(message.text || '(空消息)'),
|
|
348
|
+
status.detail ? React.createElement('div', { style: { marginTop: 6, fontSize: 12, color: status.state === 'error' ? colors.danger : colors.warning, overflowWrap: 'anywhere' } }, status.detail, !message.supersededByOutboundId && !message.recallStatus && message.deliveryError ? `:${message.deliveryError}` : '') : null,
|
|
349
|
+
message.deliveryAttemptedAt ? React.createElement('div', { style: { marginTop: 3, fontSize: 11, color: colors.muted } }, `最近尝试 ${fmt(message.deliveryAttemptedAt)} · 共 ${message.deliveryAttemptCount || 1} 次`) : null),
|
|
307
350
|
React.createElement('td', { style: { ...tableBodyCell, width: 220 } },
|
|
308
351
|
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)),
|
|
309
|
-
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) : '历史未记录'))),
|
|
352
|
+
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' ? '历史未记录' : '尚未确认'))),
|
|
310
353
|
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)))))
|
|
311
354
|
})
|
|
312
355
|
const renderTaskCard = (task) => {
|
|
@@ -409,7 +452,7 @@ window.__ModuleLoader__.load({
|
|
|
409
452
|
(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,
|
|
410
453
|
groupTableView === 'messages'
|
|
411
454
|
? 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) } })
|
|
412
|
-
: React.createElement(SelectMenu, { label: '筛选发件状态', value: outboxStatusFilter, options: [{ id: 'all', label: '全部发件状态' }, { id: '
|
|
455
|
+
: React.createElement(SelectMenu, { label: '筛选发件状态', value: outboxStatusFilter, options: [{ id: 'all', label: '全部发件状态' }, { id: 'queued', label: '待发送' }, { id: 'failed', label: '投递异常' }, { id: 'waiting', label: '待回读' }, { id: 'confirmed', label: '已发送' }, { id: 'superseded', label: '已替代' }, { id: 'recall-failed', label: '撤回待处理' }, { id: 'recalled', label: '已撤回' }], onChange: (value) => { setOutboxStatusFilter(value); setOutboxPage(1) } })))
|
|
413
456
|
const messagesTable = React.createElement(React.Fragment, null,
|
|
414
457
|
React.createElement('div', { style: { overflowX: 'auto' } }, React.createElement('table', { style: { width: '100%', minWidth: 910, borderCollapse: 'collapse', tableLayout: 'fixed' } },
|
|
415
458
|
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 } }, '消息'))),
|
|
@@ -417,17 +460,22 @@ window.__ModuleLoader__.load({
|
|
|
417
460
|
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)) }, '下一页')))
|
|
418
461
|
const outboxTable = React.createElement(React.Fragment, null,
|
|
419
462
|
React.createElement('div', { style: { overflowX: 'auto' } }, React.createElement('table', { style: { width: '100%', minWidth: 1100, borderCollapse: 'collapse', tableLayout: 'fixed' } },
|
|
420
|
-
React.createElement('thead', null, React.createElement('tr', { style: { background: colors.surface2, textAlign: 'left' } }, React.createElement('th', { style: { ...tableHeadCell, width:
|
|
463
|
+
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'))),
|
|
421
464
|
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 发件记录'))])))),
|
|
422
465
|
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)) }, '下一页')))
|
|
423
466
|
const groupsPage = React.createElement('section', null, React.createElement('div', { style: tableFrame }, groupTableToolbar, groupTableView === 'messages' ? messagesTable : outboxTable))
|
|
424
|
-
const tasksPage = React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(4, minmax(300px, 1fr))', gap:
|
|
467
|
+
const tasksPage = React.createElement('div', { className: 'observer-task-board', style: { display: 'grid', gridTemplateColumns: 'repeat(4, minmax(300px, 1fr))', gap: ui.space3, overflowX: 'auto', alignItems: 'start', paddingBottom: ui.space2 } }, ...bucketColumns)
|
|
425
468
|
const authorizationStatus = {
|
|
426
469
|
'pending-send': { label: '待发送', state: 'warning' },
|
|
427
470
|
'waiting-reply': { label: '等待处理', state: 'warning' },
|
|
428
471
|
answered: { label: '已处理', state: 'done' },
|
|
472
|
+
superseded: { label: '已失效', state: 'neutral' },
|
|
429
473
|
}
|
|
430
474
|
const authorizationDecision = { approved: { label: '已继续', state: 'done' }, rejected: { label: '不执行', state: 'error' } }
|
|
475
|
+
const isPendingAuthorization = (item) => item.status === 'pending-send' || item.status === 'waiting-reply'
|
|
476
|
+
const authorizationPresentation = (item) => item.status === 'answered'
|
|
477
|
+
? authorizationDecision[item.decision] || authorizationStatus.answered
|
|
478
|
+
: authorizationStatus[item.status] || { label: '状态异常', state: 'error' }
|
|
431
479
|
const decideAuthorization = async (requestId, decision) => {
|
|
432
480
|
try {
|
|
433
481
|
setNavigationError(undefined); setDecidingAuthorizationId(requestId)
|
|
@@ -437,21 +485,21 @@ window.__ModuleLoader__.load({
|
|
|
437
485
|
finally { setDecidingAuthorizationId('') }
|
|
438
486
|
}
|
|
439
487
|
const authorizationItems = [...(data?.authorizations || [])].sort((left, right) => {
|
|
440
|
-
const leftPending = left
|
|
488
|
+
const leftPending = isPendingAuthorization(left) ? 1 : 0; const rightPending = isPendingAuthorization(right) ? 1 : 0
|
|
441
489
|
return rightPending - leftPending || String(right.createdAt || '').localeCompare(String(left.createdAt || ''))
|
|
442
490
|
})
|
|
443
|
-
const filteredAuthorizationItems = authorizationItems.filter((item) => authorizationFilter === 'all' || (authorizationFilter === 'pending' ? item.status
|
|
491
|
+
const filteredAuthorizationItems = authorizationItems.filter((item) => authorizationFilter === 'all' || (authorizationFilter === 'pending' ? isPendingAuthorization(item) : authorizationFilter === 'superseded' ? item.status === 'superseded' : item.decision === authorizationFilter))
|
|
444
492
|
const authorizationPageSize = 10
|
|
445
493
|
const authorizationPageCount = Math.max(1, Math.ceil(filteredAuthorizationItems.length / authorizationPageSize))
|
|
446
494
|
const currentAuthorizationPage = Math.min(authorizationPage, authorizationPageCount)
|
|
447
495
|
const visibleAuthorizationItems = filteredAuthorizationItems.slice((currentAuthorizationPage - 1) * authorizationPageSize, currentAuthorizationPage * authorizationPageSize)
|
|
448
496
|
const selectedAuthorization = authorizationItems.find((item) => item.requestId === selectedAuthorizationId)
|
|
497
|
+
const selectedAuthorizationPending = selectedAuthorization ? isPendingAuthorization(selectedAuthorization) : false
|
|
449
498
|
const authorizationDetailField = (label, value) => React.createElement('div', { style: { display: 'grid', gap: 5 } },
|
|
450
499
|
React.createElement('strong', { style: { fontSize: 11, color: colors.muted } }, label),
|
|
451
500
|
React.createElement('div', { style: { fontSize: 13, lineHeight: 1.65, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } }, value || '—'))
|
|
452
501
|
const authorizationRows = visibleAuthorizationItems.map((item, rowIndex) => {
|
|
453
|
-
const
|
|
454
|
-
const status = pending ? authorizationStatus[item.status] : authorizationDecision[item.decision] || authorizationStatus.answered
|
|
502
|
+
const status = authorizationPresentation(item)
|
|
455
503
|
const group = groupsById.get(item.groupId)
|
|
456
504
|
return React.createElement('div', { key: item.requestId, style: { width: '100%', minWidth: 0, boxSizing: 'border-box', display: 'grid', gridTemplateColumns: '104px minmax(220px, 1.1fr) minmax(280px, 1.5fr) 126px 52px', alignItems: 'center', gap: 16, borderTop: `1px solid ${colors.border}`, background: rowIndex % 2 ? `color-mix(in srgb, ${colors.surface2} 55%, transparent)` : colors.cardSurface, color: 'inherit', padding: '12px 16px', fontSize: 14 } },
|
|
457
505
|
React.createElement('div', { style: { justifySelf: 'start' } }, tableStatusTag(status.label, status.state, { fontWeight: 600 })),
|
|
@@ -463,7 +511,7 @@ window.__ModuleLoader__.load({
|
|
|
463
511
|
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)' } },
|
|
464
512
|
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)' } },
|
|
465
513
|
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 } },
|
|
466
|
-
React.createElement('div', { style: { minWidth: 0 } }, React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 } }, statusTag(
|
|
514
|
+
React.createElement('div', { style: { minWidth: 0 } }, React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 } }, statusTag(authorizationPresentation(selectedAuthorization).label, authorizationPresentation(selectedAuthorization).state), React.createElement('span', { style: { color: colors.muted, fontSize: 11.5 } }, fmt(selectedAuthorization.createdAt))), React.createElement('h3', { style: { margin: 0, fontSize: 18, fontWeight: 600, lineHeight: 1.5 } }, selectedAuthorization.objective || '人工介入事项'), React.createElement('code', { style: { display: 'block', marginTop: 10, color: colors.muted, fontSize: 10.5 } }, selectedAuthorization.requestId)),
|
|
467
515
|
React.createElement(Button, { variant: 'ghost', size: 'sm', type: 'button', 'aria-label': '关闭人工介入事项', onClick: () => setSelectedAuthorizationId(''), style: { flex: '0 0 auto', minWidth: 52, whiteSpace: 'nowrap' } }, '关闭')),
|
|
468
516
|
React.createElement('div', { style: { flex: '1 1 auto', overflowY: 'auto', padding: '0 24px 24px' } },
|
|
469
517
|
React.createElement('section', { style: { padding: '24px 0', borderBottom: `1px solid ${colors.border}` } }, authorizationDetailField('需要人工处理', selectedAuthorization.requestedAction)),
|
|
@@ -473,13 +521,13 @@ window.__ModuleLoader__.load({
|
|
|
473
521
|
React.createElement('details', { style: { padding: '18px 0', borderBottom: `1px solid ${colors.border}` } }, React.createElement('summary', { style: { cursor: 'pointer', fontSize: 12, fontWeight: 600 } }, `已尝试 · ${(selectedAuthorization.attemptedActions || []).length}`), React.createElement('div', { style: { marginTop: 14 } }, authorizationDetailField('', (selectedAuthorization.attemptedActions || []).map((value, index) => `${index + 1}. ${value}`).join('\n')))),
|
|
474
522
|
React.createElement('section', { style: { padding: '20px 0', borderBottom: `1px solid ${colors.border}` } }, authorizationDetailField('关联信息', `Task ID:${selectedAuthorization.taskId || '—'}\n群聊:${groupsById.get(selectedAuthorization.groupId)?.name || selectedAuthorization.groupId || '—'}`)),
|
|
475
523
|
selectedAuthorization.status === 'answered' ? React.createElement('section', { style: { padding: '20px 0' } }, authorizationDetailField('处理结果', `${selectedAuthorization.decision === 'approved' ? '继续任务' : selectedAuthorization.decision === 'rejected' ? '不执行' : selectedAuthorization.decision || '已处理'}\n处理渠道:${selectedAuthorization.decisionSource === 'web' ? '运行看板' : selectedAuthorization.decisionSource === 'dingtalk' ? '钉钉私聊' : selectedAuthorization.decisionSource || '历史迁移'}\n处理时间:${fmt(selectedAuthorization.decidedAt)}\n处理意见:${selectedAuthorization.reply || '—'}`)) : null),
|
|
476
|
-
|
|
477
|
-
React.createElement('input', { 'aria-label':
|
|
524
|
+
selectedAuthorizationPending ? React.createElement('footer', { style: { flex: '0 0 auto', display: 'grid', gridTemplateColumns: '1fr auto auto', gap: 10, alignItems: 'center', padding: '16px 24px', borderTop: `1px solid ${colors.border}`, background: colors.surface2 } },
|
|
525
|
+
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 } }),
|
|
478
526
|
React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: decidingAuthorizationId === selectedAuthorization.requestId, onClick: () => decideAuthorization(selectedAuthorization.requestId, 'rejected'), style: { color: colors.danger } }, '不执行'),
|
|
479
|
-
React.createElement(Button, { variant: 'primary', size: 'sm', type: 'button', disabled: decidingAuthorizationId === selectedAuthorization.requestId, onClick: () => decideAuthorization(selectedAuthorization.requestId, 'approved') }, decidingAuthorizationId === selectedAuthorization.requestId ? '处理中…' : '
|
|
527
|
+
React.createElement(Button, { variant: 'primary', size: 'sm', type: 'button', disabled: decidingAuthorizationId === selectedAuthorization.requestId, onClick: () => decideAuthorization(selectedAuthorization.requestId, 'approved') }, decidingAuthorizationId === selectedAuthorization.requestId ? '处理中…' : '批准该事项并继续')) : null)) : null
|
|
480
528
|
const authorizationsPage = React.createElement(React.Fragment, null, React.createElement('section', null,
|
|
481
529
|
React.createElement('div', { style: tableFrame },
|
|
482
|
-
React.createElement('div', { style: { ...toolbar, justifyContent: 'flex-end' } }, React.createElement(SelectMenu, { label: '筛选处理状态', value: authorizationFilter, options: [{ id: 'all', label: '全部处理状态' }, { id: 'pending', label: '等待处理' }, { id: 'approved', label: '已继续' }, { id: 'rejected', label: '不执行' }], onChange: (value) => { setAuthorizationFilter(value); setAuthorizationPage(1) }, fitContent: true })),
|
|
530
|
+
React.createElement('div', { style: { ...toolbar, justifyContent: 'flex-end' } }, React.createElement(SelectMenu, { label: '筛选处理状态', value: authorizationFilter, options: [{ id: 'all', label: '全部处理状态' }, { id: 'pending', label: '等待处理' }, { id: 'approved', label: '已继续' }, { id: 'rejected', label: '不执行' }, { id: 'superseded', label: '已失效' }], onChange: (value) => { setAuthorizationFilter(value); setAuthorizationPage(1) }, fitContent: true })),
|
|
483
531
|
React.createElement('div', { style: { height: 42, boxSizing: 'border-box', display: 'grid', gridTemplateColumns: '104px minmax(220px, 1.1fr) minmax(280px, 1.5fr) 126px 52px', alignItems: 'center', gap: 16, padding: '0 16px', background: colors.surface2, color: colors.muted, fontSize: 13, fontWeight: 600 } }, React.createElement('span', null, '状态'), React.createElement('span', null, '任务'), React.createElement('span', null, '阻塞事项'), React.createElement('span', null, '提出时间'), React.createElement('span', null, '操作')),
|
|
484
532
|
React.createElement('div', null, ...(authorizationRows.length ? authorizationRows : [React.createElement('div', { key: 'empty', style: { padding: 36, textAlign: 'center', color: colors.muted, fontSize: 12 } }, '暂无人工介入事项')])),
|
|
485
533
|
React.createElement('div', { style: tableFooter },
|
|
@@ -501,12 +549,12 @@ window.__ModuleLoader__.load({
|
|
|
501
549
|
const topicsPage = React.createElement(TopicBrowser, { groups: data?.groups || [], tasks: data?.tasks || [], target: topicTarget, updatedAt, onOpenTask: (task) => navigate(task.childSessionId, groupsById.get(task.groupId)?.residentSessionId) })
|
|
502
550
|
const pageContent = activePage === 'topics' ? topicsPage : activePage === 'tasks' ? tasksPage : activePage === 'authorizations' ? authorizationsPage : activePage === 'archive' ? archivePage : activePage === 'alerts' ? alertsPage : groupsPage
|
|
503
551
|
const pageViewport = React.createElement('div', { style: { width: '100%', maxWidth: 1320, minHeight: 'calc(100dvh - 138px)', boxSizing: 'border-box', margin: '0 auto' } }, pageContent)
|
|
504
|
-
const main = React.createElement('main', { style: { width: '100%', minHeight: 'calc(100dvh - 90px)', boxSizing: 'border-box', padding: activePage === 'tasks' ?
|
|
552
|
+
const main = React.createElement('main', { className: 'observer-main', style: { width: '100%', minHeight: 'calc(100dvh - 90px)', boxSizing: 'border-box', padding: activePage === 'tasks' ? `${ui.space6}px ${ui.space6}px 0` : `${ui.space6}px ${ui.space6}px ${ui.space6 + ui.space2}px`, display: 'grid', alignContent: 'start', gap: ui.space6, pointerEvents: 'auto' } },
|
|
505
553
|
error ? React.createElement('div', { style: { ...card, borderColor: colors.danger, color: colors.danger } }, `无法连接 resident 插件:${error}`) : null,
|
|
506
554
|
navigationError ? React.createElement('div', { style: { ...card, borderColor: colors.danger, color: colors.danger } }, `无法打开 DSH Session:${navigationError}`) : null,
|
|
507
555
|
pageViewport
|
|
508
556
|
)
|
|
509
|
-
return React.createElement('div', { style: { width: '100%', height: '100%', minWidth: 0, minHeight: 0, background: colors.surface, color: 'inherit', overflow: 'auto' } }, header, main)
|
|
557
|
+
return React.createElement('div', { 'data-dingtalk-observer': '', style: { width: '100%', height: '100%', minWidth: 0, minHeight: 0, background: colors.surface, color: 'inherit', overflow: 'auto' } }, React.createElement('style', null, observerCss), header, main)
|
|
510
558
|
}
|
|
511
559
|
function apply(ctx) {
|
|
512
560
|
ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({ name: 'sidebar.footer.action', id: 'dingtalk-dsh-observer-entry', order: 5, inject: () => ({}) }, SidebarAction))
|