387 lines
11 KiB
Vue
387 lines
11 KiB
Vue
<template>
|
|
<div class="main-container">
|
|
<el-row :gutter="10" class="content-row">
|
|
<!-- 左侧聊天对话框 -->
|
|
<el-col :span="showChart ? 14 : 24" class="chat-panel">
|
|
<ChatBox
|
|
v-if="isRemarkLoaded"
|
|
:chat-type="chatType"
|
|
:user-id="userId"
|
|
:placeholder="placeholder"
|
|
:openRemark="openRemark"
|
|
:recommend-questions="recommendQuestions"
|
|
@stream-complete="handleStreamComplete"
|
|
@source-click="handleSourceClick"
|
|
@message-received="handleMessageReceived"
|
|
@chat-created="handleChatCreated"
|
|
@chat-switched="handleChatSwitched"
|
|
/>
|
|
</el-col>
|
|
|
|
<!-- 右侧图表区域 -->
|
|
<el-col :span="10" v-if="showChart" v-loading="loadChart" class="chart-panel">
|
|
<div class="chart-div">
|
|
<div class="chart-content">
|
|
<ChatChart :chatData="chatData" />
|
|
</div>
|
|
<!-- <div class="chart-actions">
|
|
<el-button type="primary" @click="openPdftable">显示 PDF 列表</el-button>
|
|
</div> -->
|
|
</div>
|
|
</el-col>
|
|
</el-row>
|
|
<el-dialog title="PDF 列表" v-model="dialogVisible" width="50%" @close="handleClose">
|
|
<el-table :data="pdfFiles" style="width: 100%">
|
|
<el-table-column prop="key" label="名称" fit></el-table-column>
|
|
<el-table-column label="操作" width="180">
|
|
<template #default="scope">
|
|
<el-button size="small" @click="openPdf(scope.row.value)">打开</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
<span slot="footer" class="dialog-footer">
|
|
<el-button @click="dialogVisible = false">关闭</el-button>
|
|
</span>
|
|
</el-dialog>
|
|
<el-dialog v-model="pdfVisible">
|
|
<PdfTable ref="pdftableref" :pdfUrl="pdfUrl" />
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import ChatChart from './components/ChatChart.vue'
|
|
import ChatBox from './components/ChatBox.vue'
|
|
import { webSocketService } from '@/utils/websocket'
|
|
import { useUserStore } from '@/store/modules/user'
|
|
import { useAclStore } from '@/store/modules/acl'
|
|
import { ref, reactive, onMounted, onBeforeUnmount, computed } from 'vue'
|
|
import { getFilePathList } from '@/api/pdfTbale'
|
|
import PdfTable from './components/PdfTable.vue'
|
|
import { sendMessages } from '@/api/historicalRecords'
|
|
import { getMaintStatData, type TraceFile } from '@/api/chat'
|
|
import { ElMessage } from 'element-plus'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { getRemark,getRecommendations} from '@/api/prologue'
|
|
|
|
const { t, locale } = useI18n()
|
|
const userStore = useUserStore()
|
|
const aclStore = useAclStore()
|
|
|
|
// ChatBox组件所需的配置
|
|
const chatType = ref('1') // 聊天类型
|
|
const userId = computed(() => aclStore.getUserId) // 用户ID
|
|
const placeholder = ref('输入您的问题...') // 输入框占位符
|
|
const openRemark = ref('')
|
|
const recommendQuestions = ref<string[]>([])
|
|
// const openRemark = ref(`Hello😊, I am a BRI assistant, a senior train fault analysis expert. You can ask questions about train faults, and I can analyze the retrieved data. Please refer to the following tips:
|
|
// | Question templates for different functions | Description |
|
|
// |--------------|----------|
|
|
// | Fault diagnosis | [Train car number] + [Fault problem/fault phenomenon] |
|
|
// | Maintenance record query | [Train car number] + [Latest N maintenance records] |`) // 开场白
|
|
// const recommendQuestions = ref(['T34 train, What to do if the Passenger Door cannot be opened','The latest 5 maintenance records of T34 train'])
|
|
|
|
const showChart = ref(false)
|
|
const pdfUrl = ref('/static-resources/files/pdfFile/开悟大模型智能体平台简介1.pdf')
|
|
const pdfVisible = ref(false)
|
|
const dialogVisible = ref(false)
|
|
const pdfFiles = ref([])
|
|
const loadChart = ref(true)
|
|
const isRemarkLoaded = ref(false)
|
|
|
|
// 定义类型
|
|
type ConversationIdInfo = Record<string, string>
|
|
|
|
// 响应式变量(初始值解析为对象)
|
|
const conversationIdInfo = ref<ConversationIdInfo | null>(parseLocalStorageValue(localStorage.getItem('conversationIdInfo')))
|
|
|
|
// 安全解析 localStorage 值
|
|
function parseLocalStorageValue(value: string | null): ConversationIdInfo | null {
|
|
if (!value) return null
|
|
try {
|
|
const parsed = JSON.parse(value) as ConversationIdInfo
|
|
return Object.keys(parsed).length > 0 ? parsed : null
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
const tableData = ref([])
|
|
const pieChartData = ref([])
|
|
const chatData = reactive({
|
|
tableData: [] as TableDataItem[],
|
|
pieChartData: [] as PieChartDataItem[],
|
|
})
|
|
|
|
// 定义类型接口
|
|
interface TableDataItem {
|
|
date: string
|
|
rootCause: string
|
|
incidentId: number
|
|
symptom: string
|
|
trainNo: string
|
|
carNo: string
|
|
location: string
|
|
findings: string
|
|
action: string
|
|
system: string
|
|
system1: string
|
|
equipment: string
|
|
time: string
|
|
}
|
|
|
|
interface PieChartDataItem {
|
|
percent: string
|
|
name: string
|
|
}
|
|
|
|
// 获取数据库中的 openRemark 数据
|
|
const fetchOpenRemark = async () => {
|
|
try {
|
|
const response = await getRemark(chatType.value) // 根据 chatType 获取开场白内容
|
|
if (response.data && response.data.content) {
|
|
openRemark.value = response.data.content // 更新 openRemark
|
|
} else {
|
|
openRemark.value = '默认的开场白' // 如果没有返回内容,设置默认值
|
|
}
|
|
isRemarkLoaded.value = true // 数据加载完成后设置为 true
|
|
} catch (error) {
|
|
ElMessage.error('加载开场白失败,请稍后重试')
|
|
console.error(error)
|
|
isRemarkLoaded.value = true
|
|
}
|
|
}
|
|
|
|
// 获取推荐问题
|
|
const fetchRecommendations = async () => {
|
|
try {
|
|
const response = await getRecommendations(chatType.value) // 根据 chatType 获取推荐问题列表
|
|
if (response.data) {
|
|
recommendQuestions.value = response.data.map(item => item.questionContent) // 更新推荐问题
|
|
} else {
|
|
recommendQuestions.value = []
|
|
}
|
|
} catch (error) {
|
|
ElMessage.error('加载推荐问题失败,请稍后重试')
|
|
console.error(error)
|
|
}
|
|
}
|
|
|
|
// 在组件加载时获取 openRemark 和推荐问题数据
|
|
onMounted(() => {
|
|
fetchOpenRemark()
|
|
fetchRecommendations()
|
|
})
|
|
|
|
|
|
|
|
const openPdf = (url: string) => {
|
|
pdfUrl.value = url.replace('/var/www/assets/', '/')
|
|
console.log('pdfUrl.value', pdfUrl.value)
|
|
pdfVisible.value = true
|
|
}
|
|
|
|
const handleClose = () => {
|
|
// console.log('Dialog closed');
|
|
}
|
|
|
|
const openPdftable = () => {
|
|
console.log('conversationIdInfo@@', conversationIdInfo)
|
|
const values = Object.values(conversationIdInfo.value || {})
|
|
let conversationId = ''
|
|
if (values.length > 0) {
|
|
const firstValue = values[0]
|
|
conversationId = typeof firstValue === 'string' ? firstValue : ''
|
|
}
|
|
console.log('conversationId@@', conversationId)
|
|
if (conversationId) {
|
|
getFilePathList(conversationId).then(({ data }) => {
|
|
console.log('data', data)
|
|
pdfFiles.value = data
|
|
})
|
|
}
|
|
console.log('pdfFiles.value', pdfFiles.value)
|
|
dialogVisible.value = true
|
|
}
|
|
|
|
// 处理 storage 事件
|
|
const handleStorageChange = (event: StorageEvent) => {
|
|
if (event.key === 'conversationIdInfo') {
|
|
conversationIdInfo.value = parseLocalStorageValue(event.newValue)
|
|
}
|
|
}
|
|
|
|
// ChatBox组件事件处理器
|
|
|
|
// 处理流式请求完成事件
|
|
const handleStreamComplete = ({ conversationId, messageId, content }) => {
|
|
console.log('流式请求完成:', { conversationId, messageId, content })
|
|
getMaintStatData(conversationId,messageId).then(({ data }) => {
|
|
//console.log('data', data)
|
|
console.log('data', data)
|
|
if(data.isExistRecord){
|
|
chatData.tableData = data.tableData
|
|
chatData.pieChartData = data.pieChartData
|
|
showChart.value = true
|
|
loadChart.value = false
|
|
}else{
|
|
console.log('常规问答: data',data)
|
|
showChart.value = false
|
|
loadChart.value = false
|
|
}
|
|
|
|
})
|
|
}
|
|
|
|
// 处理文件来源点击事件
|
|
const handleSourceClick = (source: TraceFile) => {
|
|
console.log('点击文件来源:', source)
|
|
ElMessage.info(`点击了文件: ${source.fileName}`)
|
|
// 可以在这里添加文件预览或下载逻辑
|
|
}
|
|
|
|
// 处理消息接收事件
|
|
const handleMessageReceived = (message: any) => {
|
|
console.log('收到新消息:', message)
|
|
// 可以在这里处理消息接收后的逻辑
|
|
}
|
|
|
|
// 处理新会话创建事件
|
|
const handleChatCreated = (chatIndex: number) => {
|
|
console.log('创建新会话:', chatIndex)
|
|
// 可以在这里处理新会话创建后的逻辑
|
|
}
|
|
|
|
// 处理会话切换事件
|
|
const handleChatSwitched = (chatIndex: number) => {
|
|
console.log('切换会话:', chatIndex)
|
|
// 可以在这里处理会话切换后的逻辑
|
|
}
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
window.addEventListener('storage', handleStorageChange)
|
|
webSocketService.on('message', (data) => {
|
|
try {
|
|
const parsedData = JSON.parse(JSON.parse(data))
|
|
if (parsedData.pieChartData && parsedData.tableData) {
|
|
chatData.tableData = parsedData.tableData
|
|
chatData.pieChartData = parsedData.pieChartData
|
|
console.log('send chatData', chatData)
|
|
showChart.value = true
|
|
loadChart.value = false
|
|
}
|
|
} catch (error) {
|
|
console.error('JSON解析错误:', error)
|
|
}
|
|
})
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
window.removeEventListener('storage', handleStorageChange)
|
|
})
|
|
|
|
// 发送历史消息
|
|
const sendHistoryMessage = (conversationId: string, query: string) => {
|
|
const dateTime = new Date()
|
|
const options: Intl.DateTimeFormatOptions = {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit',
|
|
hour12: false,
|
|
}
|
|
const message = {
|
|
conversationId: conversationId,
|
|
questionTime: dateTime
|
|
.toLocaleString('zh-CN', options)
|
|
.replace(/\//g, '-')
|
|
.replace(/(\d+)-(\d+)-(\d+), (\d+):(\d+):(\d+)/, '$1-$2-$3 $4:$5:$6'),
|
|
content: query,
|
|
}
|
|
sendMessages(message).then(({ data }) => {
|
|
console.log('data', data)
|
|
})
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.main-container {
|
|
height: 100vh;
|
|
padding: 8px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.content-row {
|
|
flex: 1;
|
|
height: 100%;
|
|
margin: 0 !important;
|
|
}
|
|
|
|
.chat-panel {
|
|
height: 100%;
|
|
padding: 0 4px;
|
|
}
|
|
|
|
.chart-panel {
|
|
height: 100%;
|
|
padding: 0 4px;
|
|
}
|
|
|
|
.chart-div {
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
.chart-content {
|
|
flex: 1;
|
|
min-height: 0;
|
|
}
|
|
|
|
.chart-actions {
|
|
flex-shrink: 0;
|
|
padding: 10px 0;
|
|
text-align: center;
|
|
}
|
|
|
|
.chart-area {
|
|
height: calc(100vh - 40px);
|
|
}
|
|
|
|
.chart-item {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.el-card {
|
|
margin-bottom: 15px;
|
|
transition: box-shadow 0.2s;
|
|
}
|
|
|
|
.el-card:hover {
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
h4 {
|
|
margin: 0 0 16px 0;
|
|
font-size: 16px;
|
|
color: #1f2d3d;
|
|
}
|
|
|
|
/* 确保行和列占满容器高度 */
|
|
.el-row {
|
|
height: 100%;
|
|
}
|
|
|
|
.el-col {
|
|
height: 100%;
|
|
}
|
|
</style>
|