lyc-dev #187
|
@ -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">
|
||||
<use xlink:href="#icon-aijiqiren"></use>
|
||||
</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>
|
||||
<div class="flex chart-header">
|
||||
<div class="header-name flex">
|
||||
|
@ -14,46 +14,101 @@
|
|||
</div>
|
||||
<div class="header-tool">
|
||||
<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>
|
||||
</template>
|
||||
<el-scrollbar height="400px">
|
||||
<div class="default-chart flex">
|
||||
<div>你好,{{ userStore.nickName }}</div>
|
||||
<div>我是AIx教学助手,我可以帮助你:</div>
|
||||
<div class="outer-ai flex">
|
||||
<ul class="ai-ul">
|
||||
<li class="flex ai-li" v-for="item in outerAi" :key="item.id" @click="onClick(item)"
|
||||
:class="item.disabled ? 'li-disabled' : ''">
|
||||
<el-image class="ai-img" :src="item.img" />
|
||||
<div class="ai-name flex">
|
||||
<span class="title">{{ item.title }}</span>
|
||||
<span>{{ item.secondTit }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="chart-body">
|
||||
<el-scrollbar ref="chatref">
|
||||
<div class="default-chart flex">
|
||||
<div>你好,{{ userStore.nickName }}</div>
|
||||
<div>我是AIx教学助手,我可以帮助你:</div>
|
||||
<div class="outer-ai flex">
|
||||
<ul class="ai-ul">
|
||||
<li class="flex ai-li" v-for="item in outerAi" :key="item.id" @click="onClick(item)"
|
||||
:class="item.disabled ? 'li-disabled' : ''">
|
||||
<el-image class="ai-img" :src="item.img" />
|
||||
<div class="ai-name flex">
|
||||
<span class="title">{{ item.title }}</span>
|
||||
<span>{{ item.secondTit }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--对话-->
|
||||
<div class=""></div>
|
||||
</el-scrollbar>
|
||||
<!--对话-->
|
||||
<div class="chart-content" ref="innerRef">
|
||||
<div v-for="item in msgList" :key="item.timestamp">
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
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'
|
||||
|
||||
|
||||
const { ipcRenderer } = window.electron || {}
|
||||
|
||||
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 isOpen = ref(true)
|
||||
const isOpen = ref(false)
|
||||
// 对话框框输入的值
|
||||
const msgVal = ref('')
|
||||
// 对话框是否聚焦
|
||||
const isFocus = ref(false)
|
||||
|
||||
const outerAi = [
|
||||
{
|
||||
|
@ -86,6 +141,7 @@ const outerAi = [
|
|||
}
|
||||
]
|
||||
|
||||
// 打开web端相关内容
|
||||
const onClick = ({ path, disabled }) => {
|
||||
if (disabled) return
|
||||
let configObj = outLink().getBaseData()
|
||||
|
@ -98,6 +154,61 @@ const onClick = ({ path, disabled }) => {
|
|||
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>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -126,6 +237,15 @@ const onClick = ({ path, disabled }) => {
|
|||
:deep(.el-card__header) {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
height: calc(100% - 50px);
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.chart-body {
|
||||
height: calc(100% - 60px);
|
||||
}
|
||||
}
|
||||
|
||||
.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>
|
|
@ -254,6 +254,7 @@ onMounted(async ()=>{
|
|||
.iconfont{
|
||||
font-size: 28px;
|
||||
color: #707070;
|
||||
font-weight: bold;
|
||||
}
|
||||
&:hover{
|
||||
color: #409EFF;
|
||||
|
|
Loading…
Reference in New Issue