AgentCPM深度研报助手JavaScript前端集成:打造交互式研报分析平台
AgentCPM深度研报助手JavaScript前端集成打造交互式研报分析平台你是不是也遇到过这种情况面对一份几十页甚至上百页的行业研报想快速提炼核心观点、分析数据趋势却感觉无从下手只能一页页地翻看效率极低。或者作为产品经理你希望为用户提供一个能自动解析研报、生成可视化图表和智能摘要的工具但后端模型已经就绪前端交互却成了难题。今天我们就来聊聊如何用JavaScript特别是结合Vue.js框架为类似AgentCPM这样的深度研报分析后端打造一个既专业又易用的前端交互平台。我们将抛开复杂的理论直接聚焦于工程落地手把手带你构建一个包含文件上传、实时进度、结果可视化和历史管理的完整前端应用。1. 项目概览与核心价值在开始写代码之前我们先明确一下这个前端平台要解决的核心问题。它的价值不在于炫技而在于切实提升用户处理研报的效率与体验。想象一下一个金融分析师或市场研究员他需要的不是另一个复杂的软件而是一个能让他快速上传PDF研报、实时看到分析进度、并以清晰图表和结构化摘要呈现结果的工具。我们的前端就是要成为连接用户与强大AI分析能力之间的那座桥梁让复杂的分析过程变得直观、可控。整个前端应用将围绕几个核心场景展开一键上传与分析用户拖拽或选择研报文件后前端负责上传并启动后端分析任务。进度透明化分析过程可能耗时前端需要实时反馈任务状态如“解析中”、“分析中”、“生成图表”消除用户等待的焦虑感。结果可视化呈现将后端返回的枯燥数据如财务指标趋势、行业对比数据转化为交互式图表折线图、柱状图和清晰的文本摘要。历史记录与管理用户可以查看、搜索和重新打开过往的分析报告形成知识沉淀。接下来我们就从零开始一步步实现这些功能。2. 技术选型与项目初始化工欲善其事必先利其器。为了高效开发我们选择Vue 3作为主要框架它清晰的响应式系统和组件化开发模式非常适合构建此类交互复杂的应用。2.1 技术栈说明框架Vue 3 Composition API。相比Options APIComposition API在逻辑组织和复用上更灵活尤其是处理异步状态和复杂交互时。构建工具Vite。启动快热更新迅速能极大提升开发体验。UI组件库Element Plus。它提供了丰富、美观且成熟的组件如上传(ElUpload)、进度条(ElProgress)、表格(ElTable)、标签页(ElTabs)等能让我们快速搭建出专业界面。图表库ECharts。这是国内非常强大的可视化库文档丰富社区活跃能够轻松绘制各种复杂的统计图表。HTTP客户端Axios。用于处理与AgentCPM后端服务的所有网络请求包括文件上传、轮询任务状态、获取分析结果等。状态管理Pinia。Vue官方推荐的状态管理库比Vuex更简洁直观用于管理全局的应用状态比如用户信息、历史报告列表、当前任务状态等。2.2 项目初始化与基础配置首先我们创建一个新的Vite项目并安装必要的依赖。# 创建Vue项目 npm create vuelatest agentcpm-frontend # 按照提示选择需要的特性这里建议加上TypeScript和Pinia cd agentcpm-frontend # 安装核心依赖 npm install element-plus axios echarts # 安装Element Plus图标库 npm install element-plus/icons-vue # 安装开发依赖用于按需引入Element Plus组件减小打包体积 npm install -D unplugin-vue-components unplugin-auto-import接下来配置vite.config.ts实现Element Plus的自动按需导入这能省去大量手动import的麻烦。// vite.config.ts import { defineConfig } from vite import vue from vitejs/plugin-vue import AutoImport from unplugin-auto-import/vite import Components from unplugin-vue-components/vite import { ElementPlusResolver } from unplugin-vue-components/resolvers export default defineConfig({ plugins: [ vue(), // 自动导入API AutoImport({ resolvers: [ElementPlusResolver()], }), // 自动导入组件 Components({ resolvers: [ElementPlusResolver()], }), ], })然后在main.ts中全局引入ECharts和样式。// main.ts import { createApp } from vue import App from ./App.vue import * as echarts from echarts // 导入整个echarts // 创建Vue应用 const app createApp(App) // 将echarts挂载到全局属性方便在组件中使用 app.config.globalProperties.$echarts echarts app.mount(#app)最后初始化一个Pinia Store用于状态管理。我们创建一个stores/report.ts。// stores/report.ts import { defineStore } from pinia import { ref } from vue // 定义报告项的类型 export interface ReportItem { id: string fileName: string uploadTime: string status: pending | processing | success | error progress: number // 0-100 result?: { summary: string chartsData: any[] // 存储图表配置数据 keyMetrics: Array{name: string, value: string | number} } } export const useReportStore defineStore(report, () { // 历史报告列表 const reportList refReportItem[]([]) // 当前正在分析的报告ID const currentReportId refstring | null(null) // 添加新报告 const addReport (file: File) { const newReport: ReportItem { id: report_${Date.now()}, fileName: file.name, uploadTime: new Date().toLocaleString(), status: pending, progress: 0 } reportList.value.unshift(newReport) // 新报告放在最前面 currentReportId.value newReport.id return newReport.id } // 更新报告状态和进度 const updateReportProgress (id: string, status: ReportItem[status], progress: number) { const report reportList.value.find(r r.id id) if (report) { report.status status report.progress progress } } // 更新报告结果 const updateReportResult (id: string, result: ReportItem[result]) { const report reportList.value.find(r r.id id) if (report) { report.result result report.status success report.progress 100 } } return { reportList, currentReportId, addReport, updateReportProgress, updateReportResult } })基础架子搭好了现在我们开始构建核心功能组件。3. 核心功能组件实现我们将应用拆解为几个核心组件这样结构清晰也便于维护。3.1 文件上传与任务触发组件这是用户操作的起点。我们使用Element Plus的ElUpload组件并封装其行为。!-- components/ReportUploader.vue -- template div classupload-area el-upload classupload-demo drag action# // 这里action设为#因为我们用自定义上传 :auto-uploadfalse :on-changehandleFileChange :show-file-listfalse accept.pdf,.doc,.docx el-icon classel-icon--uploadupload-filled //el-icon div classel-upload__text 将研报文件拖到此处或 em点击上传/em /div template #tip div classel-upload__tip 支持上传 PDF、Word 文档单文件大小不超过 50MB /div /template /el-upload !-- 上传后的文件信息和开始分析按钮 -- div v-ifcurrentFile classfile-info el-icondocument //el-icon span{{ currentFile.name }}/span el-button typeprimary :loadingisUploading clickstartAnalysis 开始深度分析 /el-button /div /div /template script setup langts import { ref } from vue import { UploadFilled, Document } from element-plus/icons-vue import { ElMessage } from element-plus import { useReportStore } from /stores/report import { uploadReport, startAnalysisTask } from /api/report // 假设的API模块 const reportStore useReportStore() const currentFile refFile | null(null) const isUploading ref(false) const handleFileChange (file: any) { currentFile.value file.raw } const startAnalysis async () { if (!currentFile.value) return isUploading.value true try { // 1. 在Store中创建新报告记录 const reportId reportStore.addReport(currentFile.value) // 2. 调用API上传文件 const uploadResult await uploadReport(currentFile.value) // uploadResult 可能包含服务器上的文件ID或路径 reportStore.updateReportProgress(reportId, processing, 30) // 3. 调用API启动分析任务 const taskId await startAnalysisTask(uploadResult.fileId) reportStore.updateReportProgress(reportId, processing, 60) // 4. 开始轮询任务状态这部分逻辑可以放在父组件或单独的服务中 // 例如开始一个定时器轮询查询 taskId 的状态 // pollTaskStatus(taskId, reportId) ElMessage.success(分析任务已启动请稍候...) } catch (error) { ElMessage.error(文件上传或任务启动失败) console.error(error) } finally { isUploading.value false } } /script style scoped .upload-area { padding: 40px; text-align: center; border: 2px dashed #dcdfe6; border-radius: 8px; background-color: #fafafa; } .file-info { margin-top: 20px; display: flex; align-items: center; justify-content: center; gap: 10px; } /style3.2 分析进度实时展示组件用户启动分析后最关心的是进度。我们需要一个组件来实时显示状态。!-- components/AnalysisProgress.vue -- template div v-ifcurrentReport classprogress-container h3分析进度{{ currentReport.fileName }}/h3 el-progress :percentagecurrentReport.progress :statusprogressStatus :stroke-width16 striped striped-flow :duration10 / div classstatus-info el-tag :typestatusTagType{{ statusText }}/el-tag span classtime上传于{{ currentReport.uploadTime }}/span /div !-- 这里可以放置更详细的状态步骤例如 el-steps :activeactiveStep simple el-step title文件上传 / el-step title文本解析 / el-step title数据分析 / el-step title生成报告 / /el-steps -- /div /template script setup langts import { computed } from vue import { useReportStore } from /stores/report import type { ReportItem } from /stores/report const reportStore useReportStore() const currentReport computed(() { if (!reportStore.currentReportId) return null return reportStore.reportList.find(r r.id reportStore.currentReportId) }) const progressStatus computed(() { if (!currentReport.value) return undefined switch (currentReport.value.status) { case success: return success case error: return exception default: return undefined } }) const statusTagType computed(() { if (!currentReport.value) return info switch (currentReport.value.status) { case pending: return info case processing: return warning case success: return success case error: return danger default: return info } }) const statusText computed(() { if (!currentReport.value) return 等待上传 switch (currentReport.value.status) { case pending: return 等待分析 case processing: return 分析中... case success: return 分析完成 case error: return 分析失败 default: return 未知状态 } }) /script style scoped .progress-container { padding: 20px; background: white; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.1); } .status-info { margin-top: 15px; display: flex; justify-content: space-between; align-items: center; } .time { font-size: 0.9em; color: #909399; } /style3.3 分析结果可视化组件这是展示价值的核心。当分析完成后我们需要将后端返回的数据渲染成图表和摘要。!-- components/ReportVisualization.vue -- template div v-ifreportResult classvisualization-container !-- 关键指标卡片 -- div classmetrics-grid el-card v-formetric in reportResult.keyMetrics :keymetric.name shadowhover template #header div classmetric-header{{ metric.name }}/div /template div classmetric-value{{ metric.value }}/div /el-card /div !-- 图表展示区 -- div classcharts-section h3核心数据趋势/h3 div classcharts-grid div v-for(chartOption, index) in reportResult.chartsData :keyindex classchart-wrapper div :idchart-${index} classchart-container/div /div /div /div !-- 文本摘要 -- div classsummary-section h3研报核心摘要/h3 el-card shadownever div classsummary-content{{ reportResult.summary }}/div /el-card /div /div div v-else classempty-placeholder el-empty description暂无分析结果请先上传并分析一份研报 / /div /template script setup langts import { onMounted, onUnmounted, ref, watch, nextTick } from vue import { useReportStore } from /stores/report import type { ReportItem } from /stores/report const reportStore useReportStore() const chartInstances refany[]([]) // 存储ECharts实例 const currentReport computed(() { if (!reportStore.currentReportId) return null return reportStore.reportList.find(r r.id reportStore.currentReportId) }) const reportResult computed(() currentReport.value?.result) // 监听结果变化渲染图表 watch(reportResult, (newVal) { if (newVal newVal.chartsData) { // 等待DOM更新后渲染图表 nextTick(() { renderCharts(newVal.chartsData) }) } }, { immediate: true }) const renderCharts (chartsData: any[]) { // 先销毁旧的图表实例 disposeCharts() chartsData.forEach((option, index) { const chartDom document.getElementById(chart-${index}) if (chartDom) { const chartInstance echarts.init(chartDom) chartInstance.setOption(option) chartInstances.value.push(chartInstance) } }) } const disposeCharts () { chartInstances.value.forEach(instance { instance.dispose() }) chartInstances.value [] } // 组件卸载时清理 onUnmounted(() { disposeCharts() }) /script style scoped .visualization-container { padding: 20px; } .metrics-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px; margin-bottom: 30px; } .metric-header { font-weight: bold; color: #303133; } .metric-value { font-size: 24px; font-weight: bold; color: #409EFF; text-align: center; padding: 10px 0; } .charts-section, .summary-section { margin-top: 30px; } .charts-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(500px, 1fr)); gap: 20px; } .chart-container { width: 100%; height: 400px; } .summary-content { line-height: 1.8; white-space: pre-wrap; /* 保留换行符 */ } .empty-placeholder { padding: 60px 20px; text-align: center; } /style3.4 历史报告管理组件最后我们需要一个地方让用户管理他们的分析历史。!-- components/ReportHistory.vue -- template div classhistory-container h3历史分析报告/h3 el-table :datafilteredReports stylewidth: 100% stripe el-table-column propfileName label文件名 width300 template #default{ row } div classfile-cell el-icondocument //el-icon span classfile-name{{ row.fileName }}/span /div /template /el-table-column el-table-column propuploadTime label上传时间 width180 sortable / el-table-column propstatus label状态 width120 template #default{ row } el-tag :typegetStatusTagType(row.status) sizesmall {{ getStatusText(row.status) }} /el-tag /template /el-table-column el-table-column label操作 width150 template #default{ row } el-button v-ifrow.status success typeprimary link clickviewReport(row) 查看 /el-button el-button typedanger link clickdeleteReport(row.id) 删除 /el-button /template /el-table-column /el-table /div /template script setup langts import { computed } from vue import { Document } from element-plus/icons-vue import { ElMessage, ElMessageBox } from element-plus import { useReportStore } from /stores/report import type { ReportItem } from /stores/report const reportStore useReportStore() const filteredReports computed(() reportStore.reportList) const getStatusTagType (status: ReportItem[status]) { const map { pending: info, processing: warning, success: success, error: danger } return map[status] || info } const getStatusText (status: ReportItem[status]) { const map { pending: 等待中, processing: 分析中, success: 已完成, error: 失败 } return map[status] || 未知 } const viewReport (report: ReportItem) { // 切换当前查看的报告触发可视化组件更新 reportStore.currentReportId report.id ElMessage.info(正在查看报告${report.fileName}) } const deleteReport async (id: string) { try { await ElMessageBox.confirm(确定要删除此报告吗, 提示, { confirmButtonText: 确定, cancelButtonText: 取消, type: warning }) // 从store中移除 const index reportStore.reportList.findIndex(r r.id id) if (index -1) { reportStore.reportList.splice(index, 1) // 如果删除的是当前查看的报告清空当前ID if (reportStore.currentReportId id) { reportStore.currentReportId null } } ElMessage.success(删除成功) } catch { // 用户取消了删除 } } /script style scoped .history-container { padding: 20px; background: white; border-radius: 8px; } .file-cell { display: flex; align-items: center; gap: 8px; } .file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /style4. 状态管理与API集成我们已经有了组件现在需要将它们串联起来并实现与后端的真实交互。关键在于任务状态轮询和数据转换。4.1 API服务层封装创建一个api/report.ts文件来集中管理所有与研报分析相关的后端接口调用。// api/report.ts import axios from axios // 创建axios实例配置基础URL和超时时间 const request axios.create({ baseURL: https://your-agentcpm-backend.com/api, // 替换为你的后端地址 timeout: 30000 }) // 文件上传 export const uploadReport async (file: File) { const formData new FormData() formData.append(file, file) const response await request.post(/upload, formData, { headers: { Content-Type: multipart/form-data } }) return response.data // 假设返回 { fileId: xxx, filePath: xxx } } // 启动分析任务 export const startAnalysisTask async (fileId: string) { const response await request.post(/analyze, { fileId }) return response.data.taskId // 假设返回 { taskId: xxx } } // 查询任务状态 export const getTaskStatus async (taskId: string) { const response await request.get(/task/${taskId}/status) return response.data // 假设返回 { status: processing|success|failed, progress: 85, message: ... } } // 获取分析结果 export const getAnalysisResult async (taskId: string) { const response await request.get(/task/${taskId}/result) return response.data // 返回完整的分析结果 }4.2 任务轮询与状态同步这是实现“实时进度”的关键。我们可以在上传组件或一个全局服务中启动轮询。// utils/taskPoller.js (或放在Uploader组件内) import { getTaskStatus, getAnalysisResult } from /api/report import { useReportStore } from /stores/report export const startPollingTask async (taskId, reportId) { const reportStore useReportStore() const maxAttempts 60 // 最多轮询60次 const interval 2000 // 每2秒轮询一次 let attempts 0 const poll async () { if (attempts maxAttempts) { reportStore.updateReportProgress(reportId, error, 0) console.error(任务轮询超时) return } try { const statusData await getTaskStatus(taskId) // 更新进度 reportStore.updateReportProgress(reportId, mapStatus(statusData.status), statusData.progress) if (statusData.status success) { // 任务成功获取结果 const resultData await getAnalysisResult(taskId) // 将后端数据格式转换为前端需要的格式 const formattedResult formatResult(resultData) reportStore.updateReportResult(reportId, formattedResult) return // 轮询结束 } else if (statusData.status failed) { reportStore.updateReportProgress(reportId, error, 0) return // 轮询结束 } else { // 任务仍在处理中继续轮询 attempts setTimeout(poll, interval) } } catch (error) { console.error(轮询任务状态失败:, error) reportStore.updateReportProgress(reportId, error, 0) } } poll() // 开始轮询 } // 映射后端状态到前端状态 const mapStatus (backendStatus) { const map { pending: pending, processing: processing, success: success, failed: error } return map[backendStatus] || pending } // 格式化后端结果为前端组件需要的结构 const formatResult (backendData) { // 这是一个示例你需要根据AgentCPM后端实际返回的数据结构进行调整 return { summary: backendData.summary || 暂无摘要, keyMetrics: backendData.metrics?.map(m ({ name: m.label, value: m.value })) || [], chartsData: backendData.charts?.map(chartConfig ({ title: { text: chartConfig.title }, tooltip: {}, xAxis: { data: chartConfig.xAxis }, yAxis: {}, series: [{ type: line, data: chartConfig.data }] })) || [] } }然后在ReportUploader.vue的startAnalysis函数中调用startPollingTask(taskId, reportId)即可。4.3 主页面组装最后我们将所有组件组合到主页面中。!-- App.vue 或 views/Home.vue -- template div classapp-container el-container el-header h1AgentCPM 深度研报分析平台/h1 /el-header el-main el-row :gutter20 !-- 左侧上传与进度 -- el-col :span8 report-uploader / el-divider / analysis-progress / /el-col !-- 右侧结果展示与历史 -- el-col :span16 el-tabs typeborder-card el-tab-pane label分析结果 report-visualization / /el-tab-pane el-tab-pane label历史报告 report-history / /el-tab-pane /el-tabs /el-col /el-row /el-main /el-container /div /template script setup import ReportUploader from ./components/ReportUploader.vue import AnalysisProgress from ./components/AnalysisProgress.vue import ReportVisualization from ./components/ReportVisualization.vue import ReportHistory from ./components/ReportHistory.vue /script style .app-container { min-height: 100vh; background-color: #f5f7fa; } .el-header { background-color: #409EFF; color: white; line-height: 60px; text-align: center; } .el-main { padding: 20px; } /style5. 总结与展望走到这里一个具备基本功能的交互式研报分析平台前端就搭建得差不多了。我们利用Vue 3的响应式特性和组件化配合Element Plus和ECharts实现了从文件上传、状态监控到结果可视化的完整链路。Pinia帮助我们清晰地管理了应用状态使得数据流在组件间传递变得简单可控。实际开发中你可能会遇到更多细节需要打磨比如大文件分片上传、更精细的进度提示如解析页数、分析章节、图表类型的动态适配、报告导出PDF/图片、用户权限管理等等。但核心的思路是不变的以用户体验为中心将后端强大的AI分析能力通过清晰、流畅、直观的前端界面呈现出来。这个项目更像是一个起点和蓝图。你可以根据AgentCPM后端实际提供的API接口和数据格式调整数据转换逻辑(formatResult)。也可以根据业务需求丰富可视化形式比如加入词云图、关系图谱等。希望这套实践方案能为你构建自己的AI应用前端提供一个扎实的参考。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2424364.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!