|
|
<template>
|
|
|
<div class="chat-container">
|
|
|
<div class="chat-list" ref="scrollContainer">
|
|
|
<!-- <div class="echarts-box" id="test"></div>
|
|
|
<div class="echarts-style-row">
|
|
|
<div class="style-row-name">风格切换:</div>
|
|
|
<div class="echarts-style-list">
|
|
|
<div class="style-item" v-for="(echartItem, echartIndex) in styleColor" :key="echartIndex">
|
|
|
{{ echartItem }}
|
|
|
</div>
|
|
|
</div>
|
|
|
</div> -->
|
|
|
<div
|
|
|
v-for="(item, index) in messages"
|
|
|
:key="index"
|
|
|
:class="['chat-item', item.from == 'user' ? 'user-message' : 'ai-message']"
|
|
|
>
|
|
|
<!-- 头像 -->
|
|
|
<img :src="item.from == 'user' ? userIcon : aiIcon" class="user-icon" alt="" />
|
|
|
<!-- 内容 -->
|
|
|
<div class="message-content">
|
|
|
<div class="message-time">{{ item.time }}</div>
|
|
|
<div class="message-text">
|
|
|
<span>{{ item.text }}</span>
|
|
|
<div v-if="item.from === 'ai' && item.chartType">
|
|
|
<div :id="item.chartType + index" class="echarts-box"></div>
|
|
|
<div class="echarts-style-row">
|
|
|
<div class="style-row-name">风格切换:</div>
|
|
|
<div class="echarts-style-list">
|
|
|
<div
|
|
|
class="style-item"
|
|
|
@click="hanlderStyleColor(item.chartType, index, echartIndex)"
|
|
|
:class="echartIndex == item.chartInstanceActiveIndex ? 'activeItem' : ''"
|
|
|
v-for="(echartItem, echartIndex) in styleColor"
|
|
|
:key="echartIndex"
|
|
|
>
|
|
|
{{ echartItem }}
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<n-input
|
|
|
class="chat-input"
|
|
|
round
|
|
|
v-model:value="keyWord"
|
|
|
type="textarea"
|
|
|
placeholder="请输入需要统计的数据"
|
|
|
:autosize="{ minRows: 5 }"
|
|
|
>
|
|
|
<template #suffix>
|
|
|
<div class="chat-suffix">
|
|
|
<span>常用问题</span>
|
|
|
<!-- <img src="~@/assets/images/ai/chat-send.png" class="chat-send" alt="" /> -->
|
|
|
<n-button :loading="loading" size="small" :class="['chat-send']" :bordered="false" @click="handleSend">
|
|
|
</n-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
</n-input>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
import moment from 'moment'
|
|
|
|
|
|
// @ts-ignore
|
|
|
import { getAiMsg } from '@/api/ai.js'
|
|
|
// @ts-ignore
|
|
|
import { AiChatroomType } from './types'
|
|
|
// @ts-ignore
|
|
|
import { PieChart } from './class/PieChart.ts'
|
|
|
// @ts-ignore
|
|
|
import { LineChart } from './class/LineChart.ts'
|
|
|
// @ts-ignore
|
|
|
import { BarChart } from './class/BarChart.ts'
|
|
|
|
|
|
import { ref, reactive, onMounted, onUnmounted, watch, nextTick } from 'vue'
|
|
|
import userIcon from '@/assets/images/ai/user-icon.png'
|
|
|
import aiIcon from '@/assets/images/ai/ai-icon.png'
|
|
|
import { useMessage } from 'naive-ui'
|
|
|
|
|
|
const messageDialog = useMessage()
|
|
|
const scrollContainer = ref<HTMLDivElement | null>(null)
|
|
|
// const chartContainer = ref<HTMLDivElement | null>(null)
|
|
|
let loading = ref(false)
|
|
|
let keyWord = ref('')
|
|
|
let messages: AiChatroomType[] = reactive([])
|
|
|
let chartInstance: LineChart | null = null
|
|
|
|
|
|
// 单系列数值数组示例
|
|
|
const chartData = {
|
|
|
xData: ['Q1', 'Q2', 'Q3', 'Q4'],
|
|
|
yData: [120, 150, 130, 180],
|
|
|
seriesName: '年度营收', // 自定义系列名称
|
|
|
color: '#ff6b6b' // 初始化颜色为红色
|
|
|
}
|
|
|
|
|
|
const styleColor: string[] = ['简约风', '商务风', '科技风']
|
|
|
|
|
|
const handleSend = async () => {
|
|
|
if (!keyWord.value.trim()) {
|
|
|
messageDialog.warning('请先输入您想知道的问题!')
|
|
|
return
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
*保存用户提问信息
|
|
|
*/
|
|
|
messages.push({
|
|
|
from: 'user',
|
|
|
text: keyWord.value,
|
|
|
time: moment().format('YYYY/MM/DD HH:mm:ss')
|
|
|
})
|
|
|
loading.value = true
|
|
|
try {
|
|
|
const res = await getAiMsg({ prompt: keyWord.value })
|
|
|
//保存AI回答
|
|
|
if (res.type) {
|
|
|
messages.push({
|
|
|
from: 'ai',
|
|
|
text: '以图表形式展示',
|
|
|
chartType: res.chartType,
|
|
|
time: moment().format('YYYY/MM/DD HH:mm:ss'),
|
|
|
chartInstanceItem: null,
|
|
|
chartInstanceActiveIndex: 0,
|
|
|
xData: res.xData,
|
|
|
yData: res.yData
|
|
|
})
|
|
|
nextTick(() => {
|
|
|
const lastIndex = messages.length - 1
|
|
|
const itemData = messages[lastIndex]
|
|
|
const xData = res.xData
|
|
|
const yData = res.yData.map((str: string) => parseInt(str, 10))
|
|
|
if (itemData.chartType === 'pie')
|
|
|
itemData.chartInstanceItem = new PieChart(`${itemData.chartType}${lastIndex}`, { xData, yData })
|
|
|
if (itemData.chartType === 'line') {
|
|
|
itemData.chartInstanceItem = new LineChart(`${itemData.chartType}${lastIndex}`, { xData, yData })
|
|
|
}
|
|
|
if (itemData.chartType === 'bar') {
|
|
|
itemData.chartInstanceItem = new BarChart(`${itemData.chartType}${lastIndex}`, { xData, yData })
|
|
|
}
|
|
|
})
|
|
|
} else {
|
|
|
//纯文本信息
|
|
|
messages.push({
|
|
|
from: 'ai',
|
|
|
text: res,
|
|
|
time: moment().format('YYYY/MM/DD HH:mm:ss')
|
|
|
})
|
|
|
}
|
|
|
keyWord.value = '' //清除搜索值
|
|
|
loading.value = false
|
|
|
} catch (error) {
|
|
|
//报错纯文本信息
|
|
|
messages.push({
|
|
|
from: 'ai',
|
|
|
text: '服务器繁忙,请稍后再试。',
|
|
|
time: moment().format('YYYY/MM/DD HH:mm:ss')
|
|
|
})
|
|
|
keyWord.value = '' //清除搜索值
|
|
|
loading.value = false
|
|
|
}
|
|
|
}
|
|
|
const hanlderStyleColor = (type: string, fatherIndex: number, childIndex: number) => {
|
|
|
const messagesItem = messages[fatherIndex]
|
|
|
if (messagesItem.chartInstanceActiveIndex == childIndex) return
|
|
|
messagesItem.chartInstanceActiveIndex = childIndex
|
|
|
messagesItem.chartInstanceItem?.changeColorStyle(childIndex)
|
|
|
}
|
|
|
onMounted(() => {
|
|
|
// chartInstance = new LineChart('test', chartData)
|
|
|
})
|
|
|
|
|
|
// 监听messages变化,自动滚动到底部
|
|
|
watch(
|
|
|
messages,
|
|
|
() => {
|
|
|
nextTick(() => {
|
|
|
if (scrollContainer.value) {
|
|
|
scrollContainer.value.scrollTop = scrollContainer.value.scrollHeight
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
{ deep: true }
|
|
|
)
|
|
|
onUnmounted(() => {
|
|
|
//清除
|
|
|
messages.forEach(instance => {
|
|
|
if (instance.chartType) {
|
|
|
instance.chartInstanceItem?.dispose()
|
|
|
}
|
|
|
})
|
|
|
})
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
.chat-container {
|
|
|
box-sizing: border-box;
|
|
|
padding: 20px;
|
|
|
height: 100%;
|
|
|
gap: 20px;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
}
|
|
|
|
|
|
.chat-input {
|
|
|
width: 50%;
|
|
|
}
|
|
|
.chat-suffix {
|
|
|
font-size: 14px;
|
|
|
height: 100%;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
flex-direction: column;
|
|
|
justify-content: space-between;
|
|
|
}
|
|
|
.chat-send {
|
|
|
cursor: pointer;
|
|
|
width: 46px;
|
|
|
height: 30px;
|
|
|
margin-bottom: 10px;
|
|
|
background: url('@/assets/images/ai/chat-send.png');
|
|
|
background-size: 100% 100%;
|
|
|
}
|
|
|
|
|
|
.chat-list {
|
|
|
overflow-y: auto;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 25px;
|
|
|
width: 50%;
|
|
|
height: 100%;
|
|
|
// border: 1px solid red;
|
|
|
}
|
|
|
.chat-item {
|
|
|
display: flex;
|
|
|
gap: 6px;
|
|
|
}
|
|
|
.user-icon {
|
|
|
height: 50px;
|
|
|
width: 50px;
|
|
|
}
|
|
|
|
|
|
.user-message {
|
|
|
flex-direction: row-reverse;
|
|
|
.message-content {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: flex-end;
|
|
|
}
|
|
|
}
|
|
|
.ai-message {
|
|
|
flex-direction: row;
|
|
|
.message-content {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: flex-start;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.message-content {
|
|
|
font-size: 14px;
|
|
|
font-weight: 400;
|
|
|
.message-time {
|
|
|
color: #c3cad9;
|
|
|
}
|
|
|
width: calc(100% - 110px);
|
|
|
padding-top: 15px;
|
|
|
.message-text {
|
|
|
width: auto;
|
|
|
color: #ffffff;
|
|
|
font-size: 15px;
|
|
|
margin-top: 10px;
|
|
|
padding: 18px;
|
|
|
background: #282a31;
|
|
|
border-radius: 10px 10px 10px 10px;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
::v-deep .n-input .n-input__suffix .n-base-loading {
|
|
|
color: #283e81 !important;
|
|
|
}
|
|
|
|
|
|
/* 设置滚动条整体样式 */
|
|
|
::-webkit-scrollbar {
|
|
|
width: 0px;
|
|
|
}
|
|
|
.echarts-box {
|
|
|
height: 300px;
|
|
|
width: 500px;
|
|
|
// border: 1px solid red;
|
|
|
}
|
|
|
.echarts-style-row {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
.style-row-name {
|
|
|
font-size: 14px;
|
|
|
color: #cfd5e5;
|
|
|
font-weight: 400;
|
|
|
}
|
|
|
.echarts-style-list {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
.style-item {
|
|
|
cursor: pointer;
|
|
|
font-size: 14px;
|
|
|
color: #cfd5e5;
|
|
|
font-weight: 400;
|
|
|
background: #31363e;
|
|
|
border-radius: 2px 2px 2px 2px;
|
|
|
padding: 3px 10px;
|
|
|
}
|
|
|
.activeItem {
|
|
|
background: rgba(63, 123, 248, 0.1);
|
|
|
color: #3f7bf8;
|
|
|
}
|
|
|
}
|
|
|
</style>
|