report-agent-debug.vue 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. <template>
  2. <div class="debug-page">
  3. <div class="page-title">报告智能体单步调试</div>
  4. <el-alert
  5. title="该工具仅发起临时智能体调用,不会写入或覆盖正式报告的阶段状态、输入、输出和产物。"
  6. type="warning"
  7. :closable="false"
  8. show-icon
  9. />
  10. <el-card class="search-card" shadow="never">
  11. <el-form :inline="true" @submit.native.prevent>
  12. <el-form-item label="报告 ID">
  13. <el-input v-model.trim="reportId" clearable placeholder="请输入报告 ID" style="width: 360px" @keyup.enter.native="loadReport" />
  14. </el-form-item>
  15. <el-form-item>
  16. <el-button type="primary" :loading="loading" @click="loadReport">加载调试数据</el-button>
  17. </el-form-item>
  18. </el-form>
  19. </el-card>
  20. <template v-if="detail">
  21. <el-card class="report-card" shadow="never">
  22. <div>报告 ID:{{ detail.reportId }}</div>
  23. <div class="thread">正式流程线程:{{ detail.persistedThreadId || '尚未创建' }}</div>
  24. </el-card>
  25. <el-card v-for="stage in detail.stages" :key="stage.stageNo" class="stage-card" shadow="never">
  26. <div class="stage-header">
  27. <div>
  28. <strong>阶段 {{ stage.stageNo }}:{{ stage.stageCode }}</strong>
  29. <span class="agent-key">{{ stage.agentKey || '非智能体阶段' }}</span>
  30. </div>
  31. <div>
  32. <el-tag size="small">正式状态:{{ stage.status }}</el-tag>
  33. <el-button
  34. v-if="stage.agentKey && stage.currentInput"
  35. type="primary"
  36. size="small"
  37. :loading="runningStageNo === stage.stageNo"
  38. @click="startStage(stage)"
  39. >单步触发</el-button>
  40. <el-tooltip v-else-if="stage.agentKey" effect="dark" :content="stage.inputError || '当前无法组装输入'">
  41. <el-button size="small" disabled>无法触发</el-button>
  42. </el-tooltip>
  43. </div>
  44. </div>
  45. <el-alert v-if="stage.inputError" class="input-error" type="error" :closable="false" :title="`当前输入组装失败:${stage.inputError}`" />
  46. <el-collapse>
  47. <el-collapse-item title="本次将发送的输入" name="current-input">
  48. <pre v-if="stage.currentInput">{{ pretty(stage.currentInput) }}</pre>
  49. <span v-else class="empty">当前阶段没有可组装的智能体输入。</span>
  50. </el-collapse-item>
  51. <el-collapse-item title="正式流程已保存的输入快照" name="saved-input">
  52. <pre v-if="stage.storedInputManifest">{{ prettyJson(stage.storedInputManifest) }}</pre>
  53. <span v-else class="empty">暂无</span>
  54. </el-collapse-item>
  55. <el-collapse-item title="正式流程已保存的输出" name="saved-output">
  56. <pre v-if="stage.storedOutputJson">{{ prettyJson(stage.storedOutputJson) }}</pre>
  57. <span v-else class="empty">暂无</span>
  58. </el-collapse-item>
  59. </el-collapse>
  60. </el-card>
  61. </template>
  62. <el-card v-if="job" class="job-card" shadow="never">
  63. <div class="stage-header">
  64. <strong>临时调试任务:{{ job.stageCode }}</strong>
  65. <el-tag :type="jobTagType">{{ job.status }}</el-tag>
  66. </div>
  67. <p>任务 ID:{{ job.jobId }}</p>
  68. <p>线程 ID:{{ job.threadId || '创建中' }} 运行 ID:{{ job.runId || '提交中' }}</p>
  69. <el-alert v-if="job.errorMessage" type="error" :closable="false" :title="job.errorMessage" />
  70. <el-collapse v-if="job.input || job.output">
  71. <el-collapse-item title="调试任务输入" name="job-input"><pre>{{ pretty(job.input) }}</pre></el-collapse-item>
  72. <el-collapse-item title="智能体原始输出" name="job-output"><pre>{{ pretty(job.output) }}</pre></el-collapse-item>
  73. </el-collapse>
  74. </el-card>
  75. </div>
  76. </template>
  77. <script>
  78. import {
  79. getReportPipelineDebug,
  80. getReportPipelineDebugJob,
  81. startReportPipelineDebugStage
  82. } from '@/api/agent'
  83. export default {
  84. name: 'ReportAgentDebug',
  85. data () {
  86. return {
  87. reportId: '',
  88. loading: false,
  89. detail: null,
  90. job: null,
  91. runningStageNo: null,
  92. pollTimer: null
  93. }
  94. },
  95. computed: {
  96. jobTagType () {
  97. if (!this.job) return ''
  98. if (this.job.status === 'SUCCEEDED') return 'success'
  99. if (this.job.status === 'FAILED') return 'danger'
  100. return 'warning'
  101. }
  102. },
  103. beforeDestroy () {
  104. this.clearPoll()
  105. },
  106. methods: {
  107. pretty (value) {
  108. return value == null ? '暂无数据' : JSON.stringify(value, null, 2)
  109. },
  110. prettyJson (value) {
  111. try {
  112. return JSON.stringify(JSON.parse(value), null, 2)
  113. } catch (error) {
  114. return value
  115. }
  116. },
  117. showError (message) {
  118. this.$message.error(message || '请求失败')
  119. },
  120. async loadReport () {
  121. if (!/^\d+$/.test(this.reportId)) return this.showError('请输入有效的报告 ID')
  122. this.loading = true
  123. try {
  124. const response = await getReportPipelineDebug(this.reportId)
  125. if (!response || response.code !== 0) return this.showError(response && response.msg)
  126. this.detail = response.data
  127. } finally {
  128. this.loading = false
  129. }
  130. },
  131. async startStage (stage) {
  132. this.clearPoll()
  133. this.runningStageNo = stage.stageNo
  134. try {
  135. const response = await startReportPipelineDebugStage(this.detail.reportId, stage.stageNo)
  136. if (!response || response.code !== 0) {
  137. this.runningStageNo = null
  138. return this.showError(response && response.msg)
  139. }
  140. this.job = response.data
  141. this.pollJob()
  142. } catch (error) {
  143. this.runningStageNo = null
  144. this.showError('启动调试任务失败')
  145. }
  146. },
  147. async pollJob () {
  148. if (!this.job || !this.job.jobId) return
  149. try {
  150. const response = await getReportPipelineDebugJob(this.job.jobId)
  151. if (!response || response.code !== 0) {
  152. this.clearPoll()
  153. return this.showError(response && response.msg)
  154. }
  155. this.job = response.data
  156. if (this.job.status === 'RUNNING') {
  157. this.pollTimer = window.setTimeout(this.pollJob, 1500)
  158. } else {
  159. this.runningStageNo = null
  160. this.loadReport()
  161. }
  162. } catch (error) {
  163. this.clearPoll()
  164. this.runningStageNo = null
  165. this.showError('获取调试任务状态失败')
  166. }
  167. },
  168. clearPoll () {
  169. if (this.pollTimer) window.clearTimeout(this.pollTimer)
  170. this.pollTimer = null
  171. }
  172. }
  173. }
  174. </script>
  175. <style scoped lang="scss">
  176. .debug-page { padding: 24px; background: #f5f7fa; min-height: calc(100vh - 64px); }
  177. .page-title { font-size: 20px; font-weight: 600; margin-bottom: 16px; color: #303133; }
  178. .search-card, .report-card, .stage-card, .job-card { margin-top: 16px; }
  179. .report-card { display: flex; gap: 32px; color: #606266; }
  180. .thread { word-break: break-all; }
  181. .stage-header { display: flex; justify-content: space-between; align-items: center; gap: 16px; }
  182. .agent-key { margin-left: 12px; color: #909399; font-size: 13px; }
  183. .input-error { margin: 14px 0; }
  184. 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; }
  185. .empty { color: #909399; }
  186. .job-card p { font-size: 13px; color: #606266; word-break: break-all; }
  187. </style>