Przeglądaj źródła

feat: 增加报告智能体调试页面

Developer 2 tygodni temu
rodzic
commit
d7faa49ba4

BIN
dist.zip


+ 31 - 9
src/api/agent/index.js

@@ -457,14 +457,36 @@ export function getReportList() {
     method: 'get'
   })
 }
-export function genPersonReport(data) {
-  return request({
-    url: `/core/teammember/que/genMemberReport`,
-    method: 'post',
-    data
-  })
-}
-// ---end 报告管理
+export function genPersonReport(data) {
+  return request({
+    url: `/core/teammember/que/genMemberReport`,
+    method: 'post',
+    data
+  })
+}
+
+// --- 报告智能体调试(内部临时工具)
+export function getReportPipelineDebug(reportId) {
+  return request({
+    url: `/core/report/pipeline/debug/${reportId}`,
+    method: 'get'
+  })
+}
+
+export function startReportPipelineDebugStage(reportId, stageNo) {
+  return request({
+    url: `/core/report/pipeline/debug/${reportId}/stages/${stageNo}`,
+    method: 'post'
+  })
+}
+
+export function getReportPipelineDebugJob(jobId) {
+  return request({
+    url: `/core/report/pipeline/debug/jobs/${jobId}`,
+    method: 'get'
+  })
+}
+// ---end 报告管理
 
 
 // ---start 邮件管理
@@ -598,4 +620,4 @@ export function getRegisterRecordList(query) {
     params: query
   })
 }
-// ---end 注册记录
+// ---end 注册记录

+ 1 - 0
src/router/index.js

@@ -63,6 +63,7 @@ export const moduleRoutes = {
     { path: '/agentKnowledgeFile', component: () => import('@/views/modules/agent/knowledge/file'), name: 'agentKnowledgeFile', meta: { title: '知识库文件', isTab: false } },
     { path: '/agentQuestionnaireSchedule', component: () => import('@/views/modules/agent/questionnaire/schedule'), name: 'agentQuestionnaireSchedule', meta: { title: '问卷作答进度', isTab: false } },
     { path: '/agentQuestionnaireReport', component: () => import('@/views/modules/agent/questionnaire/report'), name: 'agentQuestionnaireReport', meta: { title: '问卷报告', isTab: false } },
+    { path: '/reportAgentDebug', component: () => import('@/views/modules/agent/report-agent-debug'), name: 'reportAgentDebug', meta: { title: '报告智能体调试', isTab: false } },
     { path: '/agentQuestionnaireDetail', component: () => import('@/views/modules/agent/questionnaire/detail'), name: 'agentQuestionnaireDetail', meta: { title: '问卷详情', isTab: false } },
     { path: '/agentQuestionnairePublish', component: () => import('@/views/modules/agent/questionnaire/publish'), name: 'agentQuestionnairePublish', meta: { title: '发布问卷', isTab: false } },
     { path: '/agentUserInfo', component: () => import('@/views/modules/agent/userInfo'), name: 'agentUserInfo', meta: { title: '个人信息', isTab: false } },

+ 195 - 0
src/views/modules/agent/report-agent-debug.vue

@@ -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>