|
@@ -0,0 +1,195 @@
|
|
|
|
|
+<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>
|