You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

326 lines
8.3 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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