| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- <template>
- <div class="debug-page">
- <div class="page-title">报告智能体单步调试</div>
- <el-alert
- title="该工具仅发起临时智能体调用,不会写入或覆盖正式报告的阶段状态、输入、输出和产物。"
- type="warning"
- :closable="false"
- show-icon
- />
- <el-card class="search-card" shadow="never">
- <el-form :inline="true" @submit.native.prevent>
- <el-form-item label="报告 ID">
- <el-input v-model.trim="reportId" clearable placeholder="请输入报告 ID" style="width: 360px" @keyup.enter.native="loadReport" />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" :loading="loading" @click="loadReport">加载调试数据</el-button>
- </el-form-item>
- </el-form>
- </el-card>
- <template v-if="detail">
- <el-card class="report-card" shadow="never">
- <div>报告 ID:{{ detail.reportId }}</div>
- <div class="thread">正式流程线程:{{ detail.persistedThreadId || '尚未创建' }}</div>
- </el-card>
- <el-card v-for="stage in detail.stages" :key="stage.stageNo" class="stage-card" shadow="never">
- <div class="stage-header">
- <div>
- <strong>阶段 {{ stage.stageNo }}:{{ stage.stageCode }}</strong>
- <span class="agent-key">{{ stage.agentKey || '非智能体阶段' }}</span>
- </div>
- <div>
- <el-tag size="small">正式状态:{{ stage.status }}</el-tag>
- <el-button
- v-if="stage.agentKey && stage.currentInput"
- type="primary"
- size="small"
- :loading="runningStageNo === stage.stageNo"
- @click="startStage(stage)"
- >单步触发</el-button>
- <el-tooltip v-else-if="stage.agentKey" effect="dark" :content="stage.inputError || '当前无法组装输入'">
- <el-button size="small" disabled>无法触发</el-button>
- </el-tooltip>
- </div>
- </div>
- <el-alert v-if="stage.inputError" class="input-error" type="error" :closable="false" :title="`当前输入组装失败:${stage.inputError}`" />
- <el-collapse>
- <el-collapse-item title="本次将发送的输入" name="current-input">
- <pre v-if="stage.currentInput">{{ pretty(stage.currentInput) }}</pre>
- <span v-else class="empty">当前阶段没有可组装的智能体输入。</span>
- </el-collapse-item>
- <el-collapse-item title="正式流程已保存的输入快照" name="saved-input">
- <pre v-if="stage.storedInputManifest">{{ prettyJson(stage.storedInputManifest) }}</pre>
- <span v-else class="empty">暂无</span>
- </el-collapse-item>
- <el-collapse-item title="正式流程已保存的输出" name="saved-output">
- <pre v-if="stage.storedOutputJson">{{ prettyJson(stage.storedOutputJson) }}</pre>
- <span v-else class="empty">暂无</span>
- </el-collapse-item>
- </el-collapse>
- </el-card>
- </template>
- <el-card v-if="job" class="job-card" shadow="never">
- <div class="stage-header">
- <strong>临时调试任务:{{ job.stageCode }}</strong>
- <el-tag :type="jobTagType">{{ job.status }}</el-tag>
- </div>
- <p>任务 ID:{{ job.jobId }}</p>
- <p>线程 ID:{{ job.threadId || '创建中' }} 运行 ID:{{ job.runId || '提交中' }}</p>
- <el-alert v-if="job.errorMessage" type="error" :closable="false" :title="job.errorMessage" />
- <el-collapse v-if="job.input || job.output">
- <el-collapse-item title="调试任务输入" name="job-input"><pre>{{ pretty(job.input) }}</pre></el-collapse-item>
- <el-collapse-item title="智能体原始输出" name="job-output"><pre>{{ pretty(job.output) }}</pre></el-collapse-item>
- </el-collapse>
- </el-card>
- </div>
- </template>
- <script>
- import {
- getReportPipelineDebug,
- getReportPipelineDebugJob,
- startReportPipelineDebugStage
- } from '@/api/agent'
- export default {
- name: 'ReportAgentDebug',
- data () {
- return {
- reportId: '',
- loading: false,
- detail: null,
- job: null,
- runningStageNo: null,
- pollTimer: null
- }
- },
- computed: {
- jobTagType () {
- if (!this.job) return ''
- if (this.job.status === 'SUCCEEDED') return 'success'
- if (this.job.status === 'FAILED') return 'danger'
- return 'warning'
- }
- },
- beforeDestroy () {
- this.clearPoll()
- },
- methods: {
- pretty (value) {
- return value == null ? '暂无数据' : JSON.stringify(value, null, 2)
- },
- prettyJson (value) {
- try {
- return JSON.stringify(JSON.parse(value), null, 2)
- } catch (error) {
- return value
- }
- },
- showError (message) {
- this.$message.error(message || '请求失败')
- },
- async loadReport () {
- if (!/^\d+$/.test(this.reportId)) return this.showError('请输入有效的报告 ID')
- this.loading = true
- try {
- const response = await getReportPipelineDebug(this.reportId)
- if (!response || response.code !== 0) return this.showError(response && response.msg)
- this.detail = response.data
- } finally {
- this.loading = false
- }
- },
- async startStage (stage) {
- this.clearPoll()
- this.runningStageNo = stage.stageNo
- try {
- const response = await startReportPipelineDebugStage(this.detail.reportId, stage.stageNo)
- if (!response || response.code !== 0) {
- this.runningStageNo = null
- return this.showError(response && response.msg)
- }
- this.job = response.data
- this.pollJob()
- } catch (error) {
- this.runningStageNo = null
- this.showError('启动调试任务失败')
- }
- },
- async pollJob () {
- if (!this.job || !this.job.jobId) return
- try {
- const response = await getReportPipelineDebugJob(this.job.jobId)
- if (!response || response.code !== 0) {
- this.clearPoll()
- return this.showError(response && response.msg)
- }
- this.job = response.data
- if (this.job.status === 'RUNNING') {
- this.pollTimer = window.setTimeout(this.pollJob, 1500)
- } else {
- this.runningStageNo = null
- this.loadReport()
- }
- } catch (error) {
- this.clearPoll()
- this.runningStageNo = null
- this.showError('获取调试任务状态失败')
- }
- },
- clearPoll () {
- if (this.pollTimer) window.clearTimeout(this.pollTimer)
- this.pollTimer = null
- }
- }
- }
- </script>
- <style scoped lang="scss">
- .debug-page { padding: 24px; background: #f5f7fa; min-height: calc(100vh - 64px); }
- .page-title { font-size: 20px; font-weight: 600; margin-bottom: 16px; color: #303133; }
- .search-card, .report-card, .stage-card, .job-card { margin-top: 16px; }
- .report-card { display: flex; gap: 32px; color: #606266; }
- .thread { word-break: break-all; }
- .stage-header { display: flex; justify-content: space-between; align-items: center; gap: 16px; }
- .agent-key { margin-left: 12px; color: #909399; font-size: 13px; }
- .input-error { margin: 14px 0; }
- pre { margin: 0; padding: 14px; max-height: 420px; overflow: auto; background: #1e293b; color: #e2e8f0; border-radius: 4px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; }
- .empty { color: #909399; }
- .job-card p { font-size: 13px; color: #606266; word-break: break-all; }
- </style>
|