feat: 重构知识库系统,移除Hermes集成,增强RAG和同步功能

主要变更:
- 移除Hermes智能体及相关回调服务
- 新增知识库RAG、同步、调度、规范化和索引任务服务
- 重构orchestrator服务,增强运行时聊天功能
- 更新前端聊天、政策制度、设置等页面样式和逻辑
- 更新expense_claims和document_intelligence服务
- 删除llm_wiki相关服务和测试文件
- 更新docker-compose配置和启动脚本
This commit is contained in:
caoxiaozhu
2026-05-17 08:38:41 +00:00
parent 212c935308
commit 68f663f2f4
308 changed files with 83729 additions and 13588 deletions

View File

@@ -1,363 +1,363 @@
<template>
<section class="log-detail-page">
<div class="detail-scroll">
<article v-if="loading" class="panel detail-state">
<i class="mdi mdi-loading mdi-spin"></i>
<strong>正在加载日志详情</strong>
<p>系统正在读取当前记录的结构化信息</p>
</article>
<article v-else-if="error" class="panel detail-state error">
<i class="mdi mdi-alert-circle-outline"></i>
<strong>日志详情加载失败</strong>
<p>{{ error }}</p>
<button type="button" @click="loadDetail">重新加载</button>
</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>
<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">
<h3>处理链路</h3>
<p>按工具调用顺序查看执行链</p>
</div>
<div v-if="(hermesRun.tool_calls || []).length" class="trace-steps">
<button
v-for="(toolCall, index) in hermesRun.tool_calls || []"
:key="toolCall.id"
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>
<article v-if="selectedToolCall" class="panel detail-card">
<div class="card-head">
<h3>当前 ToolCall</h3>
<p>查看当前工具调用的请求与返回</p>
</div>
<div class="payload-grid">
<div>
<h4>请求参数</h4>
<pre class="code-block">{{ formatJson(selectedToolCall.request_json) }}</pre>
</div>
<div>
<h4>返回结果</h4>
<pre class="code-block">{{ formatJson(selectedToolCall.response_json) }}</pre>
</div>
</div>
</article>
<article class="panel detail-card wide">
<div class="card-head">
<h3>路由上下文</h3>
<p>保留 Hermes 路由与进度原文便于管理员核查</p>
</div>
<pre class="code-block large">{{ formatJson(hermesRun.route_json) }}</pre>
</article>
</div>
</template>
<template v-else-if="isSystem && systemEntry">
<article class="detail-hero panel">
<div class="hero-copy">
<div class="hero-tags">
<span class="level-pill" :class="resolveSystemLevelTone(systemEntry.level)">
{{ systemEntry.level }}
</span>
<span class="status-pill" :class="resolveSystemOutcomeTone(systemEntry.outcome)">
{{ systemEntry.outcome }}
</span>
</div>
<h2>{{ systemEntry.summary || systemEntry.message }}</h2>
<p>{{ systemEntry.event_type }} · {{ systemEntry.logger || '未标记模块' }}</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>
<p>按单条系统日志提取出的结构化字段</p>
</div>
<div class="info-grid">
<div><span>发生时间</span><strong>{{ formatDateTime(systemEntry.timestamp) }}</strong></div>
<div><span>日志来源</span><strong>{{ systemEntry.source_file }} #{{ systemEntry.line_number }}</strong></div>
<div><span>模块</span><strong>{{ systemEntry.logger || '未标记' }}</strong></div>
<div><span>Request ID</span><strong>{{ systemEntry.request_id || '—' }}</strong></div>
<div><span>请求方法</span><strong>{{ systemEntry.method || '—' }}</strong></div>
<div><span>响应状态</span><strong>{{ systemEntry.status_code ?? '—' }}</strong></div>
<div><span>请求路径</span><strong>{{ systemEntry.path || '—' }}</strong></div>
<div><span>处理耗时</span><strong>{{ systemEntry.duration_ms == null ? '—' : `${systemEntry.duration_ms}ms` }}</strong></div>
</div>
</article>
<article class="panel detail-card">
<div class="card-head">
<h3>解析反馈</h3>
<p>系统对当前日志的归类与处理建议</p>
</div>
<div class="feedback-grid">
<div><span>事件类型</span><strong>{{ systemEntry.event_type }}</strong></div>
<div><span>解析状态</span><strong>{{ resolveSystemParseLabel(systemEntry.parse_status) }}</strong></div>
<div><span>处理结果</span><strong>{{ systemEntry.outcome }}</strong></div>
<div><span>建议动作</span><strong>{{ resolveSystemRecommendation(systemEntry) }}</strong></div>
</div>
</article>
<article class="panel detail-card">
<div class="card-head">
<h3>原始日志</h3>
<p>保留该条记录的完整原文便于排障核对</p>
</div>
<pre class="code-block large">{{ systemEntry.raw }}</pre>
</article>
</div>
</template>
</div>
<footer class="detail-actions">
<button class="back-action" type="button" @click="backToLogs">
<i class="mdi mdi-arrow-left"></i>
<span>返回日志列表</span>
</button>
</footer>
</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'
const SOURCE_LABELS = {
schedule: '定时任务',
system_event: '系统事件',
user_message: '用户触发'
}
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
)
function formatDateTime(value) {
if (!value) return '未结束'
const date = new Date(value)
return Number.isNaN(date.getTime()) ? String(value) : date.toLocaleString('zh-CN', { hour12: false })
}
function formatJson(value) {
try {
return JSON.stringify(value || {}, null, 2)
} catch {
return String(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 resolveToolStatusTone(status) {
return status === 'succeeded' ? 'success' : status === 'failed' ? 'danger' : 'warning'
}
function resolveRunSourceLabel(source) {
return SOURCE_LABELS[source] || source || '未标记'
}
function resolveRunModuleLabel(run) {
const routeJson = run?.route_json || {}
if (routeJson.job_type === 'llm_wiki_sync') return '知识归纳'
if (routeJson.selected_agent) return String(routeJson.selected_agent)
if (routeJson.folder) return String(routeJson.folder)
return resolveRunSourceLabel(run?.source)
}
function resolveRunTitle(run) {
const routeJson = run?.route_json || {}
if (routeJson.job_type === 'llm_wiki_sync') {
return `LLM Wiki 归纳 · ${routeJson.folder || '未指定目录'}`
}
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 resolveLevelTone(level) {
if (level === 'ERROR') return 'danger'
if (level === 'WARN') return 'warning'
if (level === 'INFO') return 'info'
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 resolveSystemLevelTone(level) {
if (level === 'ERROR' || level === 'CRITICAL') return 'danger'
if (level === 'WARNING' || level === 'WARN') return 'warning'
if (level === 'INFO') return 'info'
return 'muted'
}
function resolveSystemOutcomeTone(outcome) {
if (outcome === '失败') return 'danger'
if (outcome === '异常' || outcome === '告警') return 'warning'
if (outcome === '成功') return 'success'
return 'muted'
}
function resolveSystemParseLabel(status) {
return status === 'parsed' ? '已结构化' : '待人工核查'
}
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() {
const calls = hermesRun.value?.tool_calls || []
if (!calls.length) {
selectedToolCallId.value = ''
return
}
if (!calls.some((item) => item.id === selectedToolCallId.value)) {
selectedToolCallId.value = calls[0].id
}
}
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
syncSelectedToolCall()
return
}
if (isSystem.value) {
systemEntry.value = await fetchSystemLogEntry(id)
hermesRun.value = null
return
}
throw new Error('不支持的日志类型。')
} catch (nextError) {
error.value = nextError?.message || '日志详情加载失败。'
} finally {
loading.value = false
}
}
function backToLogs() {
router.push({ name: 'app-logs' })
}
watch(() => [route.params.logKind, route.params.logId], loadDetail)
onMounted(loadDetail)
</script>
<style scoped src="../assets/styles/views/log-detail-view.css"></style>
<template>
<section class="log-detail-page">
<div class="detail-scroll">
<article v-if="loading" class="panel detail-state">
<i class="mdi mdi-loading mdi-spin"></i>
<strong>正在加载日志详情</strong>
<p>系统正在读取当前记录的结构化信息</p>
</article>
<article v-else-if="error" class="panel detail-state error">
<i class="mdi mdi-alert-circle-outline"></i>
<strong>日志详情加载失败</strong>
<p>{{ error }}</p>
<button type="button" @click="loadDetail">重新加载</button>
</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>
<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">
<h3>处理链路</h3>
<p>按工具调用顺序查看执行链</p>
</div>
<div v-if="(hermesRun.tool_calls || []).length" class="trace-steps">
<button
v-for="(toolCall, index) in hermesRun.tool_calls || []"
:key="toolCall.id"
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>
<article v-if="selectedToolCall" class="panel detail-card">
<div class="card-head">
<h3>当前 ToolCall</h3>
<p>查看当前工具调用的请求与返回</p>
</div>
<div class="payload-grid">
<div>
<h4>请求参数</h4>
<pre class="code-block">{{ formatJson(selectedToolCall.request_json) }}</pre>
</div>
<div>
<h4>返回结果</h4>
<pre class="code-block">{{ formatJson(selectedToolCall.response_json) }}</pre>
</div>
</div>
</article>
<article class="panel detail-card wide">
<div class="card-head">
<h3>路由上下文</h3>
<p>保留 Hermes 路由与进度原文便于管理员核查</p>
</div>
<pre class="code-block large">{{ formatJson(hermesRun.route_json) }}</pre>
</article>
</div>
</template>
<template v-else-if="isSystem && systemEntry">
<article class="detail-hero panel">
<div class="hero-copy">
<div class="hero-tags">
<span class="level-pill" :class="resolveSystemLevelTone(systemEntry.level)">
{{ systemEntry.level }}
</span>
<span class="status-pill" :class="resolveSystemOutcomeTone(systemEntry.outcome)">
{{ systemEntry.outcome }}
</span>
</div>
<h2>{{ systemEntry.summary || systemEntry.message }}</h2>
<p>{{ systemEntry.event_type }} · {{ systemEntry.logger || '未标记模块' }}</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>
<p>按单条系统日志提取出的结构化字段</p>
</div>
<div class="info-grid">
<div><span>发生时间</span><strong>{{ formatDateTime(systemEntry.timestamp) }}</strong></div>
<div><span>日志来源</span><strong>{{ systemEntry.source_file }} #{{ systemEntry.line_number }}</strong></div>
<div><span>模块</span><strong>{{ systemEntry.logger || '未标记' }}</strong></div>
<div><span>Request ID</span><strong>{{ systemEntry.request_id || '—' }}</strong></div>
<div><span>请求方法</span><strong>{{ systemEntry.method || '—' }}</strong></div>
<div><span>响应状态</span><strong>{{ systemEntry.status_code ?? '—' }}</strong></div>
<div><span>请求路径</span><strong>{{ systemEntry.path || '—' }}</strong></div>
<div><span>处理耗时</span><strong>{{ systemEntry.duration_ms == null ? '—' : `${systemEntry.duration_ms}ms` }}</strong></div>
</div>
</article>
<article class="panel detail-card">
<div class="card-head">
<h3>解析反馈</h3>
<p>系统对当前日志的归类与处理建议</p>
</div>
<div class="feedback-grid">
<div><span>事件类型</span><strong>{{ systemEntry.event_type }}</strong></div>
<div><span>解析状态</span><strong>{{ resolveSystemParseLabel(systemEntry.parse_status) }}</strong></div>
<div><span>处理结果</span><strong>{{ systemEntry.outcome }}</strong></div>
<div><span>建议动作</span><strong>{{ resolveSystemRecommendation(systemEntry) }}</strong></div>
</div>
</article>
<article class="panel detail-card">
<div class="card-head">
<h3>原始日志</h3>
<p>保留该条记录的完整原文便于排障核对</p>
</div>
<pre class="code-block large">{{ systemEntry.raw }}</pre>
</article>
</div>
</template>
</div>
<footer class="detail-actions">
<button class="back-action" type="button" @click="backToLogs">
<i class="mdi mdi-arrow-left"></i>
<span>返回日志列表</span>
</button>
</footer>
</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'
const SOURCE_LABELS = {
schedule: '定时任务',
system_event: '系统事件',
user_message: '用户触发'
}
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
)
function formatDateTime(value) {
if (!value) return '未结束'
const date = new Date(value)
return Number.isNaN(date.getTime()) ? String(value) : date.toLocaleString('zh-CN', { hour12: false })
}
function formatJson(value) {
try {
return JSON.stringify(value || {}, null, 2)
} catch {
return String(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 resolveToolStatusTone(status) {
return status === 'succeeded' ? 'success' : status === 'failed' ? 'danger' : 'warning'
}
function resolveRunSourceLabel(source) {
return SOURCE_LABELS[source] || source || '未标记'
}
function resolveRunModuleLabel(run) {
const routeJson = run?.route_json || {}
if (routeJson.job_type === 'knowledge_index_sync' || routeJson.job_type === 'llm_wiki_sync') return '\u77e5\u8bc6\u5f52\u7eb3'
if (routeJson.selected_agent) return String(routeJson.selected_agent)
if (routeJson.folder) return String(routeJson.folder)
return resolveRunSourceLabel(run?.source)
}
function resolveRunTitle(run) {
const routeJson = run?.route_json || {}
if (routeJson.job_type === 'knowledge_index_sync' || routeJson.job_type === 'llm_wiki_sync') {
return `\u77e5\u8bc6\u5f52\u7eb3 \u00b7 ${routeJson.folder || '\u672a\u6307\u5b9a\u76ee\u5f55'}`
}
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 resolveLevelTone(level) {
if (level === 'ERROR') return 'danger'
if (level === 'WARN') return 'warning'
if (level === 'INFO') return 'info'
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 resolveSystemLevelTone(level) {
if (level === 'ERROR' || level === 'CRITICAL') return 'danger'
if (level === 'WARNING' || level === 'WARN') return 'warning'
if (level === 'INFO') return 'info'
return 'muted'
}
function resolveSystemOutcomeTone(outcome) {
if (outcome === '失败') return 'danger'
if (outcome === '异常' || outcome === '告警') return 'warning'
if (outcome === '成功') return 'success'
return 'muted'
}
function resolveSystemParseLabel(status) {
return status === 'parsed' ? '已结构化' : '待人工核查'
}
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() {
const calls = hermesRun.value?.tool_calls || []
if (!calls.length) {
selectedToolCallId.value = ''
return
}
if (!calls.some((item) => item.id === selectedToolCallId.value)) {
selectedToolCallId.value = calls[0].id
}
}
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
syncSelectedToolCall()
return
}
if (isSystem.value) {
systemEntry.value = await fetchSystemLogEntry(id)
hermesRun.value = null
return
}
throw new Error('不支持的日志类型。')
} catch (nextError) {
error.value = nextError?.message || '日志详情加载失败。'
} finally {
loading.value = false
}
}
function backToLogs() {
router.push({ name: 'app-logs' })
}
watch(() => [route.params.logKind, route.params.logId], loadDetail)
onMounted(loadDetail)
</script>
<style scoped src="../assets/styles/views/log-detail-view.css"></style>