feat: 更新前端UI,增强审计和日志视图功能
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -15,43 +15,56 @@
|
||||
</article>
|
||||
|
||||
<template v-else-if="isHermes && hermesRun">
|
||||
<article class="detail-hero panel">
|
||||
<div class="hero-copy">
|
||||
<div class="hero-tags">
|
||||
<span class="level-pill" :class="resolveLevelTone(resolveRunLevel(hermesRun))">
|
||||
{{ resolveRunLevel(hermesRun) }}
|
||||
</span>
|
||||
<span class="status-pill" :class="resolveStatusTone(hermesRun.status)">
|
||||
{{ resolveStatusLabel(hermesRun.status) }}
|
||||
</span>
|
||||
</div>
|
||||
<h2>{{ resolveRunTitle(hermesRun) }}</h2>
|
||||
<p>{{ hermesRun.result_summary || '暂无运行摘要。' }}</p>
|
||||
</div>
|
||||
<button class="refresh-btn" type="button" :disabled="loading" @click="loadDetail">
|
||||
<i class="mdi mdi-refresh"></i>
|
||||
<span>刷新详情</span>
|
||||
</button>
|
||||
</article>
|
||||
|
||||
<div class="detail-grid">
|
||||
<article class="panel detail-card wide">
|
||||
<div class="card-head">
|
||||
<h3>基本信息</h3>
|
||||
<article class="detail-hero panel">
|
||||
<div class="hero-copy">
|
||||
<div class="hero-tags">
|
||||
<span class="level-pill" :class="resolveLevelTone(resolveRunLevel(hermesRun))">
|
||||
{{ resolveRunLevel(hermesRun) }}
|
||||
</span>
|
||||
<span class="status-pill" :class="resolveStatusTone(hermesRun)">
|
||||
{{ resolveStatusLabel(hermesRun) }}
|
||||
</span>
|
||||
</div>
|
||||
<h2>{{ resolveRunTitle(hermesRun) }}</h2>
|
||||
<p>{{ hermesRun.result_summary || '暂无运行摘要。' }}</p>
|
||||
<p v-if="hermesRun.status === 'running'" class="hero-hint">运行中每 5 秒自动刷新一次详情。</p>
|
||||
</div>
|
||||
<button class="refresh-btn" type="button" :disabled="loading" @click="loadDetail">
|
||||
<i class="mdi mdi-refresh"></i>
|
||||
<span>刷新详情</span>
|
||||
</button>
|
||||
</article>
|
||||
|
||||
<article
|
||||
v-if="hermesRunAlert"
|
||||
class="panel detail-alert"
|
||||
:class="hermesRunAlert.tone"
|
||||
>
|
||||
{{ hermesRunAlert.message }}
|
||||
</article>
|
||||
|
||||
<div class="detail-grid">
|
||||
<article class="panel detail-card wide">
|
||||
<div class="card-head">
|
||||
<h3>基本信息</h3>
|
||||
<p>围绕当前 Hermes 任务查看关键字段。</p>
|
||||
</div>
|
||||
<div class="info-grid">
|
||||
<div><span>Trace ID</span><strong>{{ hermesRun.run_id }}</strong></div>
|
||||
<div><span>开始时间</span><strong>{{ formatDateTime(hermesRun.started_at) }}</strong></div>
|
||||
<div><span>结束时间</span><strong>{{ formatDateTime(hermesRun.finished_at) }}</strong></div>
|
||||
<div><span>来源</span><strong>{{ resolveRunSourceLabel(hermesRun.source) }}</strong></div>
|
||||
<div><span>模块</span><strong>{{ resolveRunModuleLabel(hermesRun) }}</strong></div>
|
||||
<div><span>当前进度</span><strong>{{ resolveRunProgress(hermesRun) }}</strong></div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="panel detail-card">
|
||||
<div class="card-head">
|
||||
<div><span>Trace ID</span><strong>{{ hermesRun.run_id }}</strong></div>
|
||||
<div><span>开始时间</span><strong>{{ formatDateTime(hermesRun.started_at) }}</strong></div>
|
||||
<div><span>结束时间</span><strong>{{ formatDateTime(hermesRun.finished_at) }}</strong></div>
|
||||
<div><span>来源</span><strong>{{ resolveRunSourceLabel(hermesRun.source) }}</strong></div>
|
||||
<div><span>模块</span><strong>{{ resolveRunModuleLabel(hermesRun) }}</strong></div>
|
||||
<div><span>当前阶段</span><strong>{{ hermesRunStatus.phaseLabel }}</strong></div>
|
||||
<div><span>当前进度</span><strong>{{ resolveRunProgress(hermesRun) }}</strong></div>
|
||||
<div><span>执行耗时</span><strong>{{ resolveRunElapsedLabel(hermesRun) }}</strong></div>
|
||||
<div><span>最后心跳</span><strong>{{ resolveHeartbeatAtText(hermesRunHeartbeat) }}</strong></div>
|
||||
<div><span>心跳状态</span><strong>{{ hermesRunHeartbeat.label }}</strong></div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="panel detail-card">
|
||||
<div class="card-head">
|
||||
<h3>处理链路</h3>
|
||||
<p>按工具调用顺序查看执行链。</p>
|
||||
</div>
|
||||
@@ -62,20 +75,22 @@
|
||||
type="button"
|
||||
class="trace-step"
|
||||
:class="{ active: selectedToolCall?.id === toolCall.id }"
|
||||
@click="selectedToolCallId = toolCall.id"
|
||||
>
|
||||
<span class="step-index">{{ index + 1 }}</span>
|
||||
<div class="step-copy">
|
||||
<strong>{{ toolCall.tool_name }}</strong>
|
||||
<span>{{ toolCall.tool_type }} · {{ toolCall.duration_ms }}ms</span>
|
||||
</div>
|
||||
<span class="status-pill" :class="resolveToolStatusTone(toolCall.status)">
|
||||
{{ toolCall.status }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="inline-empty">当前运行暂无 ToolCall 明细。</div>
|
||||
</article>
|
||||
@click="selectedToolCallId = toolCall.id"
|
||||
>
|
||||
<span class="step-index">{{ index + 1 }}</span>
|
||||
<div class="step-copy">
|
||||
<strong>{{ toolCall.tool_name }}</strong>
|
||||
<span>{{ resolveToolCallMeta(toolCall) }}</span>
|
||||
</div>
|
||||
<span class="status-pill" :class="resolveToolStatusTone(toolCall.status)">
|
||||
{{ toolCall.status }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="inline-empty">
|
||||
当前暂无 ToolCall 明细。若长时间停在运行中且没有心跳,通常表示任务尚未真正进入 LightRAG 索引调用,或执行它的是旧版后端进程。
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article v-if="selectedToolCall" class="panel detail-card">
|
||||
<div class="card-head">
|
||||
@@ -175,12 +190,20 @@
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import { fetchAgentRunDetail } from '../services/agentAssets.js'
|
||||
import { fetchSystemLogEntry } from '../services/systemLogs.js'
|
||||
<script setup>
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import { fetchAgentRunDetail } from '../services/agentAssets.js'
|
||||
import { fetchSystemLogEntry } from '../services/systemLogs.js'
|
||||
import {
|
||||
AGENT_RUN_POLL_INTERVAL_MS,
|
||||
formatAgentRunElapsed,
|
||||
formatAgentRunProgress,
|
||||
formatDurationShort,
|
||||
resolveAgentRunHeartbeat,
|
||||
resolveAgentRunStatus
|
||||
} from '../utils/agentRunMonitor.js'
|
||||
|
||||
const SOURCE_LABELS = {
|
||||
schedule: '定时任务',
|
||||
@@ -191,16 +214,38 @@ const SOURCE_LABELS = {
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
const hermesRun = ref(null)
|
||||
const systemEntry = ref(null)
|
||||
const selectedToolCallId = ref('')
|
||||
|
||||
const isHermes = computed(() => route.params.logKind === 'hermes')
|
||||
const isSystem = computed(() => route.params.logKind === 'system')
|
||||
const selectedToolCall = computed(() =>
|
||||
(hermesRun.value?.tool_calls || []).find((item) => item.id === selectedToolCallId.value) || null
|
||||
)
|
||||
const error = ref('')
|
||||
const hermesRun = ref(null)
|
||||
const systemEntry = ref(null)
|
||||
const selectedToolCallId = ref('')
|
||||
const nowTick = ref(Date.now())
|
||||
let pollTimer = 0
|
||||
|
||||
const isHermes = computed(() => route.params.logKind === 'hermes')
|
||||
const isSystem = computed(() => route.params.logKind === 'system')
|
||||
const selectedToolCall = computed(() =>
|
||||
(hermesRun.value?.tool_calls || []).find((item) => item.id === selectedToolCallId.value) || null
|
||||
)
|
||||
const hermesRunStatus = computed(() => resolveAgentRunStatus(hermesRun.value, nowTick.value))
|
||||
const hermesRunHeartbeat = computed(() => resolveAgentRunHeartbeat(hermesRun.value, nowTick.value))
|
||||
const hermesRunAlert = computed(() => {
|
||||
if (!hermesRun.value) {
|
||||
return null
|
||||
}
|
||||
if (hermesRun.value.error_message) {
|
||||
return {
|
||||
tone: 'danger',
|
||||
message: hermesRun.value.error_message
|
||||
}
|
||||
}
|
||||
if (hermesRunStatus.value.isSuspicious) {
|
||||
return {
|
||||
tone: hermesRunStatus.value.tone === 'danger' ? 'danger' : 'warning',
|
||||
message: hermesRunStatus.value.note || '当前任务长时间没有有效进展,建议检查后台执行器。'
|
||||
}
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
function formatDateTime(value) {
|
||||
if (!value) return '未结束'
|
||||
@@ -216,20 +261,13 @@ function formatJson(value) {
|
||||
}
|
||||
}
|
||||
|
||||
function resolveStatusLabel(status) {
|
||||
if (status === 'running') return '运行中'
|
||||
if (status === 'succeeded') return '已完成'
|
||||
if (status === 'failed') return '失败'
|
||||
if (status === 'blocked') return '待确认'
|
||||
return status || '未知'
|
||||
}
|
||||
|
||||
function resolveStatusTone(status) {
|
||||
if (status === 'running') return 'warning'
|
||||
if (status === 'succeeded') return 'success'
|
||||
if (status === 'failed') return 'danger'
|
||||
return 'muted'
|
||||
}
|
||||
function resolveStatusLabel(run) {
|
||||
return resolveAgentRunStatus(run, nowTick.value).label
|
||||
}
|
||||
|
||||
function resolveStatusTone(run) {
|
||||
return resolveAgentRunStatus(run, nowTick.value).tone
|
||||
}
|
||||
|
||||
function resolveToolStatusTone(status) {
|
||||
return status === 'succeeded' ? 'success' : status === 'failed' ? 'danger' : 'warning'
|
||||
@@ -255,12 +293,14 @@ function resolveRunTitle(run) {
|
||||
return `Hermes 调用 · ${resolveRunModuleLabel(run)}`
|
||||
}
|
||||
|
||||
function resolveRunLevel(run) {
|
||||
const progress = run?.route_json?.progress || {}
|
||||
if (run?.status === 'failed' || run?.error_message) return 'ERROR'
|
||||
if (run?.status === 'blocked' || Number(progress.failed_documents || 0) > 0) return 'WARN'
|
||||
return 'INFO'
|
||||
}
|
||||
function resolveRunLevel(run) {
|
||||
const progress = run?.route_json?.progress || {}
|
||||
const statusInfo = resolveAgentRunStatus(run, nowTick.value)
|
||||
if (run?.status === 'failed' || run?.error_message) return 'ERROR'
|
||||
if (statusInfo.isSuspicious) return 'WARN'
|
||||
if (run?.status === 'blocked' || Number(progress.failed_documents || 0) > 0) return 'WARN'
|
||||
return 'INFO'
|
||||
}
|
||||
|
||||
function resolveLevelTone(level) {
|
||||
if (level === 'ERROR') return 'danger'
|
||||
@@ -269,27 +309,9 @@ function resolveLevelTone(level) {
|
||||
return 'muted'
|
||||
}
|
||||
|
||||
function resolveRunProgress(run) {
|
||||
const progress = run?.route_json?.progress || {}
|
||||
const percent = Number(progress.percent || 0)
|
||||
const completed = Number(progress.completed_documents || 0)
|
||||
const total = Number(progress.total_documents || 0)
|
||||
const failed = Number(progress.failed_documents || 0)
|
||||
const stage = String(progress.current_stage || '').trim()
|
||||
const stageLabelMap = {
|
||||
document_started: '文档启动',
|
||||
text_extracted: '文本已提取',
|
||||
candidate_chunks_selected: '已筛正文',
|
||||
extracting_candidates: '候选提炼中',
|
||||
candidate_extraction_completed: '候选提炼完成',
|
||||
document_completed: '文档完成',
|
||||
skipped: '跳过'
|
||||
}
|
||||
const stageLabel = stageLabelMap[stage] || (stage || '等待中')
|
||||
return total > 0
|
||||
? `${percent}% · ${completed}/${total} 文档 · ${stageLabel}${failed > 0 ? ` · 失败 ${failed}` : ''}`
|
||||
: `${percent}% · ${stageLabel}`
|
||||
}
|
||||
function resolveRunProgress(run) {
|
||||
return formatAgentRunProgress(run)
|
||||
}
|
||||
|
||||
function resolveSystemLevelTone(level) {
|
||||
if (level === 'ERROR' || level === 'CRITICAL') return 'danger'
|
||||
@@ -309,15 +331,47 @@ function resolveSystemParseLabel(status) {
|
||||
return status === 'parsed' ? '已结构化' : '待人工核查'
|
||||
}
|
||||
|
||||
function resolveSystemRecommendation(entry) {
|
||||
function resolveSystemRecommendation(entry) {
|
||||
if (!entry) return '暂无'
|
||||
if (entry.outcome === '失败') return '优先排查并关联上下游日志'
|
||||
if (entry.outcome === '异常' || entry.outcome === '告警') return '建议继续关注同模块后续记录'
|
||||
if (entry.parse_status !== 'parsed') return '建议人工核对原始日志'
|
||||
return '无需额外处理'
|
||||
}
|
||||
|
||||
function syncSelectedToolCall() {
|
||||
return '无需额外处理'
|
||||
}
|
||||
|
||||
function resolveRunElapsedLabel(run) {
|
||||
const elapsed = formatAgentRunElapsed(run, nowTick.value)
|
||||
if (elapsed === '—') {
|
||||
return elapsed
|
||||
}
|
||||
return run?.status === 'running' ? `已运行 ${elapsed}` : elapsed
|
||||
}
|
||||
|
||||
function resolveHeartbeatAtText(heartbeat) {
|
||||
if (heartbeat?.at) {
|
||||
return `${formatDateTime(heartbeat.at)} · ${heartbeat.text}`
|
||||
}
|
||||
return heartbeat?.text || '—'
|
||||
}
|
||||
|
||||
function resolveToolCallMeta(toolCall) {
|
||||
const toolType = String(toolCall?.tool_type || 'tool').trim()
|
||||
if (String(toolCall?.status || '').trim() === 'running') {
|
||||
const createdAt = new Date(toolCall?.created_at)
|
||||
if (!Number.isNaN(createdAt.getTime())) {
|
||||
return `${toolType} · 已运行 ${formatDurationShort(nowTick.value - createdAt.getTime())}`
|
||||
}
|
||||
return `${toolType} · 执行中`
|
||||
}
|
||||
|
||||
const durationMs = Number(toolCall?.duration_ms || 0)
|
||||
if (durationMs > 0) {
|
||||
return `${toolType} · ${durationMs}ms`
|
||||
}
|
||||
return `${toolType} · 已结束`
|
||||
}
|
||||
|
||||
function syncSelectedToolCall() {
|
||||
const calls = hermesRun.value?.tool_calls || []
|
||||
if (!calls.length) {
|
||||
selectedToolCallId.value = ''
|
||||
@@ -328,14 +382,39 @@ function syncSelectedToolCall() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDetail() {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
const id = String(route.params.logId || '')
|
||||
if (isHermes.value) {
|
||||
hermesRun.value = await fetchAgentRunDetail(id)
|
||||
systemEntry.value = null
|
||||
function stopPolling() {
|
||||
if (pollTimer) {
|
||||
window.clearInterval(pollTimer)
|
||||
pollTimer = 0
|
||||
}
|
||||
}
|
||||
|
||||
function syncPolling() {
|
||||
stopPolling()
|
||||
|
||||
if (!isHermes.value || hermesRun.value?.status !== 'running') {
|
||||
return
|
||||
}
|
||||
|
||||
pollTimer = window.setInterval(() => {
|
||||
nowTick.value = Date.now()
|
||||
if (!loading.value) {
|
||||
void loadDetail({ silent: true })
|
||||
}
|
||||
}, AGENT_RUN_POLL_INTERVAL_MS)
|
||||
}
|
||||
|
||||
async function loadDetail(options = {}) {
|
||||
const silent = options.silent === true
|
||||
if (!silent) {
|
||||
loading.value = true
|
||||
}
|
||||
error.value = ''
|
||||
try {
|
||||
const id = String(route.params.logId || '')
|
||||
if (isHermes.value) {
|
||||
hermesRun.value = await fetchAgentRunDetail(id)
|
||||
systemEntry.value = null
|
||||
syncSelectedToolCall()
|
||||
return
|
||||
}
|
||||
@@ -345,19 +424,35 @@ async function loadDetail() {
|
||||
return
|
||||
}
|
||||
throw new Error('不支持的日志类型。')
|
||||
} catch (nextError) {
|
||||
error.value = nextError?.message || '日志详情加载失败。'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
} catch (nextError) {
|
||||
error.value = nextError?.message || '日志详情加载失败。'
|
||||
} finally {
|
||||
nowTick.value = Date.now()
|
||||
syncPolling()
|
||||
if (!silent) {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function backToLogs() {
|
||||
router.push({ name: 'app-logs' })
|
||||
}
|
||||
|
||||
watch(() => [route.params.logKind, route.params.logId], loadDetail)
|
||||
onMounted(loadDetail)
|
||||
</script>
|
||||
watch(
|
||||
() => [route.params.logKind, route.params.logId],
|
||||
() => {
|
||||
void loadDetail()
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
void loadDetail()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopPolling()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped src="../assets/styles/views/log-detail-view.css"></style>
|
||||
|
||||
@@ -113,12 +113,16 @@
|
||||
<td class="summary-cell">
|
||||
<strong>{{ resolveRunTitle(run) }}</strong>
|
||||
<span>{{ formatSummary(run.result_summary) }}</span>
|
||||
<em class="summary-meta">{{ resolveRunSummaryMeta(run) }}</em>
|
||||
</td>
|
||||
<td class="trace-cell">{{ run.run_id }}</td>
|
||||
<td>
|
||||
<span class="status-pill" :class="resolveStatusTone(run.status)">
|
||||
{{ resolveStatusLabel(run.status) }}
|
||||
</span>
|
||||
<div class="status-stack">
|
||||
<span class="status-pill" :class="resolveStatusTone(run)">
|
||||
{{ resolveStatusLabel(run) }}
|
||||
</span>
|
||||
<span class="status-note">{{ resolveRunStatusNote(run) }}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -225,7 +229,7 @@
|
||||
<div class="analytics-head">
|
||||
<div>
|
||||
<h3>日志趋势</h3>
|
||||
<p>近 8 个小时的 Hermes 运行量与失败量</p>
|
||||
<p>近 8 个小时的 Hermes 启动量与失败量,不代表实时心跳</p>
|
||||
</div>
|
||||
</div>
|
||||
<LogTrendChart
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,20 @@
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import LogTrendChart from '../../components/charts/LogTrendChart.vue'
|
||||
import DonutChart from '../../components/charts/DonutChart.vue'
|
||||
import { fetchAgentRuns } from '../../services/agentAssets.js'
|
||||
import { fetchSystemLogEntries } from '../../services/systemLogs.js'
|
||||
import { useSystemState } from '../../composables/useSystemState.js'
|
||||
import { useToast } from '../../composables/useToast.js'
|
||||
import { isManagerUser } from '../../utils/accessControl.js'
|
||||
|
||||
const POLL_INTERVAL_MS = 5000
|
||||
import LogTrendChart from '../../components/charts/LogTrendChart.vue'
|
||||
import DonutChart from '../../components/charts/DonutChart.vue'
|
||||
import { fetchAgentRuns } from '../../services/agentAssets.js'
|
||||
import { fetchSystemLogEntries } from '../../services/systemLogs.js'
|
||||
import { useSystemState } from '../../composables/useSystemState.js'
|
||||
import { useToast } from '../../composables/useToast.js'
|
||||
import {
|
||||
AGENT_RUN_POLL_INTERVAL_MS,
|
||||
formatAgentRunElapsed,
|
||||
formatAgentRunProgress,
|
||||
resolveAgentRunHeartbeat,
|
||||
resolveAgentRunStatus
|
||||
} from '../../utils/agentRunMonitor.js'
|
||||
import { isManagerUser } from '../../utils/accessControl.js'
|
||||
|
||||
const SOURCE_LABELS = {
|
||||
schedule: '定时任务',
|
||||
@@ -30,37 +35,13 @@ function formatDateTime(value) {
|
||||
return date.toLocaleString('zh-CN', { hour12: false })
|
||||
}
|
||||
|
||||
function resolveStatusLabel(status) {
|
||||
if (status === 'running') {
|
||||
return '运行中'
|
||||
}
|
||||
if (status === 'succeeded') {
|
||||
return '已完成'
|
||||
}
|
||||
if (status === 'failed') {
|
||||
return '失败'
|
||||
}
|
||||
if (status === 'blocked') {
|
||||
return '待确认'
|
||||
}
|
||||
return status || '未知'
|
||||
}
|
||||
|
||||
function resolveStatusTone(status) {
|
||||
if (status === 'running') {
|
||||
return 'warning'
|
||||
}
|
||||
if (status === 'succeeded') {
|
||||
return 'success'
|
||||
}
|
||||
if (status === 'failed') {
|
||||
return 'danger'
|
||||
}
|
||||
if (status === 'blocked') {
|
||||
return 'muted'
|
||||
}
|
||||
return 'muted'
|
||||
}
|
||||
function resolveStatusLabel(run) {
|
||||
return resolveAgentRunStatus(run).label
|
||||
}
|
||||
|
||||
function resolveStatusTone(run) {
|
||||
return resolveAgentRunStatus(run).tone
|
||||
}
|
||||
|
||||
function resolveRunSourceLabel(source) {
|
||||
return SOURCE_LABELS[source] || source || '未标记'
|
||||
@@ -88,16 +69,20 @@ function resolveRunTitle(run) {
|
||||
return `Hermes 调用 · ${resolveRunModuleLabel(run)}`
|
||||
}
|
||||
|
||||
function resolveRunLevel(run) {
|
||||
const progress = run?.route_json?.progress || {}
|
||||
if (run?.status === 'failed' || run?.error_message) {
|
||||
return 'ERROR'
|
||||
}
|
||||
if (run?.status === 'blocked' || Number(progress.failed_documents || 0) > 0) {
|
||||
return 'WARN'
|
||||
}
|
||||
if (run?.status === 'running') {
|
||||
return 'INFO'
|
||||
function resolveRunLevel(run) {
|
||||
const progress = run?.route_json?.progress || {}
|
||||
const statusInfo = resolveAgentRunStatus(run)
|
||||
if (run?.status === 'failed' || run?.error_message) {
|
||||
return 'ERROR'
|
||||
}
|
||||
if (statusInfo.isSuspicious) {
|
||||
return 'WARN'
|
||||
}
|
||||
if (run?.status === 'blocked' || Number(progress.failed_documents || 0) > 0) {
|
||||
return 'WARN'
|
||||
}
|
||||
if (run?.status === 'running') {
|
||||
return 'INFO'
|
||||
}
|
||||
return 'INFO'
|
||||
}
|
||||
@@ -115,7 +100,7 @@ function resolveLevelTone(level) {
|
||||
return 'muted'
|
||||
}
|
||||
|
||||
function formatSummary(summary) {
|
||||
function formatSummary(summary) {
|
||||
const text = String(summary || '').trim()
|
||||
if (!text) {
|
||||
return '暂无摘要。'
|
||||
@@ -123,8 +108,39 @@ function formatSummary(summary) {
|
||||
if (text.length <= 64) {
|
||||
return text
|
||||
}
|
||||
return `${text.slice(0, 64)}...`
|
||||
}
|
||||
return `${text.slice(0, 64)}...`
|
||||
}
|
||||
|
||||
function resolveRunSummaryMeta(run) {
|
||||
const statusInfo = resolveAgentRunStatus(run)
|
||||
const progressText = formatAgentRunProgress(run)
|
||||
const elapsedLabel = run?.status === 'running' ? '已运行' : '耗时'
|
||||
const elapsedText = formatAgentRunElapsed(run)
|
||||
const parts = [`阶段 ${statusInfo.phaseLabel}`]
|
||||
|
||||
if (progressText) {
|
||||
parts.push(progressText)
|
||||
}
|
||||
if (elapsedText !== '—') {
|
||||
parts.push(`${elapsedLabel} ${elapsedText}`)
|
||||
}
|
||||
|
||||
return parts.join(' · ')
|
||||
}
|
||||
|
||||
function resolveRunStatusNote(run) {
|
||||
const statusInfo = resolveAgentRunStatus(run)
|
||||
if (statusInfo.note) {
|
||||
return statusInfo.note
|
||||
}
|
||||
|
||||
const heartbeat = resolveAgentRunHeartbeat(run)
|
||||
if (heartbeat.at !== null) {
|
||||
return `最后心跳 ${formatDateTime(heartbeat.at)}`
|
||||
}
|
||||
|
||||
return '暂无额外状态'
|
||||
}
|
||||
|
||||
function resolveSystemLevelTone(level) {
|
||||
if (level === 'ERROR' || level === 'CRITICAL') {
|
||||
@@ -368,9 +384,9 @@ export default {
|
||||
stopPolling()
|
||||
pollTimer = window.setInterval(() => {
|
||||
loadHermesRuns()
|
||||
loadSystemLogs()
|
||||
}, POLL_INTERVAL_MS)
|
||||
}
|
||||
loadSystemLogs()
|
||||
}, AGENT_RUN_POLL_INTERVAL_MS)
|
||||
}
|
||||
|
||||
function stopPolling() {
|
||||
if (pollTimer) {
|
||||
@@ -442,11 +458,13 @@ export default {
|
||||
pageSizes,
|
||||
resolveLevelTone,
|
||||
resolveRunLevel,
|
||||
resolveRunModuleLabel,
|
||||
resolveRunSourceLabel,
|
||||
resolveRunTitle,
|
||||
resolveStatusLabel,
|
||||
resolveStatusTone,
|
||||
resolveRunModuleLabel,
|
||||
resolveRunSourceLabel,
|
||||
resolveRunStatusNote,
|
||||
resolveRunSummaryMeta,
|
||||
resolveRunTitle,
|
||||
resolveStatusLabel,
|
||||
resolveStatusTone,
|
||||
resolveSystemLevelTone,
|
||||
resolveSystemOutcomeTone,
|
||||
runningRunCount,
|
||||
|
||||
@@ -28,3 +28,28 @@ export function buildOnlyOfficePreviewConfig(config, options = {}) {
|
||||
height: `${clampHeight(viewportHeight)}px`
|
||||
}
|
||||
}
|
||||
|
||||
export function buildOnlyOfficeEditorConfig(config, options = {}) {
|
||||
const viewportHeight = options.viewportHeight
|
||||
const editable = Boolean(options.editable)
|
||||
const fillContainer = Boolean(options.fillContainer)
|
||||
|
||||
return {
|
||||
...config,
|
||||
type: editable ? 'desktop' : 'embedded',
|
||||
editorConfig: {
|
||||
...(config.editorConfig || {}),
|
||||
embedded: editable
|
||||
? undefined
|
||||
: {
|
||||
embedUrl: '',
|
||||
fullscreenUrl: '',
|
||||
saveUrl: '',
|
||||
shareUrl: '',
|
||||
toolbarDocked: 'top'
|
||||
}
|
||||
},
|
||||
width: '100%',
|
||||
height: fillContainer ? '100%' : `${clampHeight(viewportHeight)}px`
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user