聊天机器人
This commit is contained in:
parent
3a1cf6224a
commit
0be3d131e2
|
@ -0,0 +1,20 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 创建对话
|
||||||
|
export const createChart = ({ headers, data }) => {
|
||||||
|
return request({
|
||||||
|
url: '/qf/createChart',
|
||||||
|
method: 'post',
|
||||||
|
headers,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 大模型对话
|
||||||
|
export const sendChart = ({ headers, data }) => {
|
||||||
|
return request({
|
||||||
|
url: '/qf/sendTalk',
|
||||||
|
method: 'post',
|
||||||
|
headers,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
{{ displayedText }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, nextTick } from 'vue'
|
||||||
|
const props = defineProps({
|
||||||
|
text: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
speed: {
|
||||||
|
type: Number,
|
||||||
|
default: 50, // 默认每字符间隔100毫秒
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['loaded'])
|
||||||
|
|
||||||
|
const displayedText = ref('')
|
||||||
|
|
||||||
|
const startTyping = async () =>{
|
||||||
|
if(!props.text || props.text.length == 0) return
|
||||||
|
for (let i = 0; i < props.text.length; i++) {
|
||||||
|
displayedText.value += props.text.charAt(i);
|
||||||
|
if(displayedText.value.length == props.text.length -1){
|
||||||
|
emit('onSuccess')
|
||||||
|
}
|
||||||
|
emit('loaded')
|
||||||
|
await wait(props.speed);
|
||||||
|
nextTick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const wait = (ms) =>{
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() =>{
|
||||||
|
startTyping()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -3,7 +3,7 @@
|
||||||
<svg class="icon ai-icon" aria-hidden="true" @click="isOpen = true; isMax = false" v-if="!isOpen">
|
<svg class="icon ai-icon" aria-hidden="true" @click="isOpen = true; isMax = false" v-if="!isOpen">
|
||||||
<use xlink:href="#icon-aijiqiren"></use>
|
<use xlink:href="#icon-aijiqiren"></use>
|
||||||
</svg>
|
</svg>
|
||||||
<el-card v-drag v-else shadow="always" class="chart-card" :class="[isMax ? 'card-max' : '']">
|
<el-card v-else shadow="always" class="chart-card" :class="[isMax ? 'card-max' : '']">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex chart-header">
|
<div class="flex chart-header">
|
||||||
<div class="header-name flex">
|
<div class="header-name flex">
|
||||||
|
@ -14,46 +14,101 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="header-tool">
|
<div class="header-tool">
|
||||||
<i class="iconfont icon-window-max_line icon-tool" @click="isMax = !isMax"></i>
|
<i class="iconfont icon-window-max_line icon-tool" @click="isMax = !isMax"></i>
|
||||||
<i class="iconfont icon-close icon-tool" @click="isOpen = !isOpen"></i>
|
<i class="iconfont icon-close icon-tool" @click="closeChart"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<el-scrollbar height="400px">
|
<div class="chart-body">
|
||||||
<div class="default-chart flex">
|
<el-scrollbar ref="chatref">
|
||||||
<div>你好,{{ userStore.nickName }}</div>
|
<div class="default-chart flex">
|
||||||
<div>我是AIx教学助手,我可以帮助你:</div>
|
<div>你好,{{ userStore.nickName }}</div>
|
||||||
<div class="outer-ai flex">
|
<div>我是AIx教学助手,我可以帮助你:</div>
|
||||||
<ul class="ai-ul">
|
<div class="outer-ai flex">
|
||||||
<li class="flex ai-li" v-for="item in outerAi" :key="item.id" @click="onClick(item)"
|
<ul class="ai-ul">
|
||||||
:class="item.disabled ? 'li-disabled' : ''">
|
<li class="flex ai-li" v-for="item in outerAi" :key="item.id" @click="onClick(item)"
|
||||||
<el-image class="ai-img" :src="item.img" />
|
:class="item.disabled ? 'li-disabled' : ''">
|
||||||
<div class="ai-name flex">
|
<el-image class="ai-img" :src="item.img" />
|
||||||
<span class="title">{{ item.title }}</span>
|
<div class="ai-name flex">
|
||||||
<span>{{ item.secondTit }}</span>
|
<span class="title">{{ item.title }}</span>
|
||||||
</div>
|
<span>{{ item.secondTit }}</span>
|
||||||
</li>
|
</div>
|
||||||
</ul>
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<!--对话-->
|
||||||
<!--对话-->
|
<div class="chart-content" ref="innerRef">
|
||||||
<div class=""></div>
|
<div v-for="item in msgList" :key="item.timestamp">
|
||||||
</el-scrollbar>
|
<div class="author-con" v-if="item.name == 'user'">
|
||||||
|
<div class="author-msg">
|
||||||
|
{{ item.content }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="robot-msg">
|
||||||
|
<Answer :text="item.content ? item.content : ''" @loaded="chartLoaded" @onSuccess="isFinally = true"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="loaded" class="chart-loading">
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
<div class="chart-input">
|
||||||
|
<el-input v-model="msgVal" size="large" class="chart-ipt" @keyup.enter="sendMsg" @focus="isFocus = true" @blur="isFocus = false" />
|
||||||
|
<i class="iconfont icon-tujing" :class="[isFocus ? 'icon-focus' : '']" @click="sendMsg"></i>
|
||||||
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import useUserStore from '@/store/modules/user'
|
import useUserStore from '@/store/modules/user'
|
||||||
import outLink from '@/utils/linkConfig'
|
import outLink from '@/utils/linkConfig'
|
||||||
|
import Answer from './container/text.vue'
|
||||||
|
import { createChart, sendChart } from '@/api/ai/index'
|
||||||
|
|
||||||
import vDrag from '@/views/tool/directive/drag'
|
import vDrag from '@/views/tool/directive/drag'
|
||||||
|
|
||||||
|
|
||||||
const { ipcRenderer } = window.electron || {}
|
const { ipcRenderer } = window.electron || {}
|
||||||
|
|
||||||
const userStore = useUserStore().user
|
const userStore = useUserStore().user
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 大模型相关
|
||||||
|
* conversation_id : 会话ID 用于对话
|
||||||
|
* headers : 请求头
|
||||||
|
* app_id :大模型 应用id
|
||||||
|
* loaded : 回答状态
|
||||||
|
* msgList : 消息列表
|
||||||
|
* curAnswer : 当前回答的文字答案
|
||||||
|
*/
|
||||||
|
|
||||||
|
let conversation_id = null
|
||||||
|
const app_id = '712ff0df-ed6b-470f-bf87-8cfbaf757be5'
|
||||||
|
const headers = {
|
||||||
|
isToken: true,
|
||||||
|
Authorization: 'Bearer bce-v3/ALTAK-VpMGiUjWehcHSPZGjUKwB/c97384c2c71a0d3b1d1060d7f9e2a4eac3343732',
|
||||||
|
'Content-Type': 'application/json;charset=utf-8',
|
||||||
|
}
|
||||||
|
const loaded = ref(false)
|
||||||
|
const msgList = ref([])
|
||||||
|
const chatref = ref(null)
|
||||||
|
|
||||||
|
const innerRef = ref(null)
|
||||||
|
const isFinally = ref(true)
|
||||||
|
// 是否最大化、打开对话框
|
||||||
const isMax = ref(false)
|
const isMax = ref(false)
|
||||||
const isOpen = ref(true)
|
const isOpen = ref(false)
|
||||||
|
// 对话框框输入的值
|
||||||
|
const msgVal = ref('')
|
||||||
|
// 对话框是否聚焦
|
||||||
|
const isFocus = ref(false)
|
||||||
|
|
||||||
const outerAi = [
|
const outerAi = [
|
||||||
{
|
{
|
||||||
|
@ -86,6 +141,7 @@ const outerAi = [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// 打开web端相关内容
|
||||||
const onClick = ({ path, disabled }) => {
|
const onClick = ({ path, disabled }) => {
|
||||||
if (disabled) return
|
if (disabled) return
|
||||||
let configObj = outLink().getBaseData()
|
let configObj = outLink().getBaseData()
|
||||||
|
@ -98,6 +154,61 @@ const onClick = ({ path, disabled }) => {
|
||||||
cookieData: { ...configObj.data }
|
cookieData: { ...configObj.data }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建对话
|
||||||
|
const getChartId = () =>{
|
||||||
|
const data = { app_id }
|
||||||
|
createChart({ data, headers }).then( res =>{
|
||||||
|
if( res.code == 200){
|
||||||
|
localStorage.setItem("conversation_id", res.data.conversation_id);
|
||||||
|
conversation_id = res.data.conversation_id;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 大模型对话
|
||||||
|
const sendMsg = () =>{
|
||||||
|
//发送的内容不能为空
|
||||||
|
if(msgVal.value == '') return
|
||||||
|
|
||||||
|
if(!isFinally.value) return
|
||||||
|
isFinally.value = false
|
||||||
|
let msg = msgVal.value
|
||||||
|
msgVal.value = ''
|
||||||
|
msgList.value.push({ name: 'user', timestamp: + new Date(), content: msg})
|
||||||
|
chatref.value.setScrollTop(innerRef.value.clientHeight)
|
||||||
|
loaded.value = true
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
appId: app_id,
|
||||||
|
content: msg,
|
||||||
|
stream: false,
|
||||||
|
conversationId: conversation_id,
|
||||||
|
}
|
||||||
|
sendChart({ data, headers }).then(res =>{
|
||||||
|
loaded.value = false
|
||||||
|
msgList.value.push({name: 'robot', timestamp: + new Date(), content: res.data.answer})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const chartLoaded = () =>{
|
||||||
|
chatref.value.setScrollTop(innerRef.value.clientHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeChart = () =>{
|
||||||
|
loaded.value = false
|
||||||
|
msgList.value = []
|
||||||
|
isOpen.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
conversation_id = localStorage.getItem("conversation_id");
|
||||||
|
if (!conversation_id) {
|
||||||
|
getChartId();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -126,6 +237,15 @@ const onClick = ({ path, disabled }) => {
|
||||||
:deep(.el-card__header) {
|
:deep(.el-card__header) {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
height: calc(100% - 50px);
|
||||||
|
padding: 10px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-body {
|
||||||
|
height: calc(100% - 60px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-max {
|
.card-max {
|
||||||
|
@ -209,5 +329,121 @@ const onClick = ({ path, disabled }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chart-content {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
.author-con {
|
||||||
|
@include flex-direction-c;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-msg {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #E0DFFF;
|
||||||
|
padding: 5px 8px;
|
||||||
|
border-radius: 7px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.robot-msg {
|
||||||
|
background-color: #fff;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-input {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 15px;
|
||||||
|
|
||||||
|
.icon-tujing {
|
||||||
|
font-size: 26px;
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
color: #c0c0c2;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-focus {
|
||||||
|
color: #409EFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-ipt {
|
||||||
|
:deep(.el-input__wrapper) {
|
||||||
|
padding-right: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-loading,
|
||||||
|
.chart-loading>div {
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-loading {
|
||||||
|
display: block;
|
||||||
|
font-size: 0;
|
||||||
|
color: #66b1ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-loading.la-dark {
|
||||||
|
color: #66b1ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-loading>div {
|
||||||
|
display: inline-block;
|
||||||
|
float: none;
|
||||||
|
background-color: currentColor;
|
||||||
|
border: 0 solid currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-loading {
|
||||||
|
width: 54px;
|
||||||
|
height: 18px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-loading>div:nth-child(1) {
|
||||||
|
animation-delay: -200ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-loading>div:nth-child(2) {
|
||||||
|
animation-delay: -100ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-loading>div:nth-child(3) {
|
||||||
|
animation-delay: 0ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-loading>div {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 100%;
|
||||||
|
margin-right: 4px;
|
||||||
|
animation: ball-pulse 1s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ball-pulse {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
60%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
30% {
|
||||||
|
opacity: 0.1;
|
||||||
|
transform: scale(0.01);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -254,6 +254,7 @@ onMounted(async ()=>{
|
||||||
.iconfont{
|
.iconfont{
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
color: #707070;
|
color: #707070;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
&:hover{
|
&:hover{
|
||||||
color: #409EFF;
|
color: #409EFF;
|
||||||
|
|
Loading…
Reference in New Issue