Files
tra-app/src/pages/index/dialog.vue
T

740 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="index-dialog-page">
<!-- <MarkdownPreview/> -->
<view class="top-bg"></view>
<view class="bot-bg"></view>
<scroll-view class="index-dialog-body" :scroll-y="true" upper-threshold="0" refresher-enabled="true"
refresher-default-style="none" @refresherrefresh="scrolltolower">
<view class="seek-setting">
<image src="@/static/images/dialog/new-dialog.png" alt="" class="image-icon-new"></image>
<image src="@/static/images/dialog/history.png" alt="" class="image-icon"></image>
</view>
<view class="body-title">
<!-- <uv-icon class="arrow-icon" name="arrow-left" @click="common.navigateBack()"/> -->
AI问答
</view>
<view class="ai-info">
<!-- <image src="@/static/images/dialog/pet-3.png" alt="" class="image-pet-bg"></image> -->
<image src="@/static/images/dialog/pet-bg-base.png" alt="" class="image-pet-bg"></image>
<image-preview :src="userInfo.mascotInfo?.imgAddr ||''" alt="" class="image-pet"></image-preview>
<view class="ai-info-bg-box">
<view class="hello-text">Hi我是{{ userInfo.mascotInfo?.mascotName || ''}}</view>
<view class="info-text">工作学习我都能帮你</view>
<view class="info-btn">试试他的能力</view>
</view>
</view>
<view class="questions-view">
<scroll-view :scroll-x="true" :show-scrollbar="false">
<view class="question-row">
<view class="question-item" v-for="item in Math.ceil(questionList.length/2)"
@click="sendText(questionList[item-1])">{{questionList[item-1]}}</view>
</view>
<view class="question-row">
<view class="question-item"
v-for="item in questionList.length - Math.ceil(questionList.length/2)"
@click="sendText(questionList[item + Math.ceil(questionList.length/2)-1])">
{{questionList[item + Math.ceil(questionList.length/2)-1]}}
</view>
</view>
</scroll-view>
</view>
<view class="talking-list">
<scroll-view class="talking-scroll-list" :scroll-y="true" :scroll-top="scrollTop"
:show-scrollbar="false" :scroll-with-animation="false">
<TalkingItem v-for="item in talkingList" :talkingType="item.type" :talkingInfo="item"
:activeVoiceId="activeVoiceTalkingItemId" @handleEvents="talkItemEvents">
</TalkingItem>
</scroll-view>
</view>
<view class="router-list">
<scroll-view class="" :scroll-x="true" :show-scrollbar="false">
<view class="btns-item" @click="routerTo('AI学')">
<image src="@/static/images/dialog/icons/study.png" alt="" class="image-icon"></image>
{{ 'AI学' }}
</view>
<view class="btns-item" @click="routerTo('AI练')">
<image src="@/static/images/dialog/icons/test.png" alt="" class="image-icon"></image>
{{ 'AI练' }}
</view>
<view class="btns-item" @click="routerTo('AI考')">
<image src="@/static/images/dialog/icons/exam.png" alt="" class="image-icon"></image>
{{ 'AI考' }}
</view>
<view class="btns-item" @click="routerTo('去PK')">
<image src="@/static/images/dialog/icons/PK.png" alt="" class="image-icon"></image>
{{ '去PK' }}
</view>
<view class="btns-item" @click="routerTo('任务')">
<image src="@/static/images/dialog/icons/task.png" alt="" class="image-icon"></image>
{{ '任务' }}
</view>
<view class="btns-item" @click="routerTo('错题')">
<image src="@/static/images/dialog/icons/wrong.png" alt="" class="image-icon"></image>
{{ '错题' }}
</view>
<view class="btns-item" @click="routerTo('分析')">
<image src="@/static/images/dialog/icons/static.png" alt="" class="image-icon"></image>
{{ '分析' }}
</view>
</scroll-view>
</view>
<TouchBtn class="talk-btns" @uploadVoice="uploadRecordNow" @submitAnswer="submitAnswerNow"
@startRecord="startRecordCallback" :loadingText="sendLoadingText" :isLoading="!answerIsOver"
:isDisabled="false" />
</scroll-view>
</view>
</template>
<script setup>
import {
ref,
unref,
onMounted,
watch,
nextTick,
computed
} from 'vue'
import common from '@/common/common';
import TouchBtn from '@/pages/course/components/touchBtn.vue'
import TalkingItem from './components/talking-item.vue';
import {
useSocketStore
} from "@/store/socket.js"
import {
useStorageStore
} from "@/store/storage.js"
import {
storeToRefs
} from 'pinia'
import {
onLoad,
onShow,
onUnload,
onHide,
onBackPress
} from '@dcloudio/uni-app';
import {
get_base_url,
goto_login_fun
} from '@/api/request'
import {
getUserInfo
} from '@/common/common';
import {
commonUploadVoiceFile
} from "@/api/common.js"
const socketStore = useSocketStore()
const {
SocketObj
} = storeToRefs(socketStore)
const userInfo = computed(() => getUserInfo())
console.log('userInfoxxx', getUserInfo());
const questionList = ref([
'对公小程序开户流程?',
'厅堂服务经理2025年管理办法?',
'厅堂服务经理2025年考核表单?',
'吉林银行重要空白凭证调微业务操作规程?',
'协助有权机关查询冻结扣划业务操作规程?',
'柜面个人账户操作规程?',
'久悬未取款账户如何办理销户?',
'境外来华人员个人账户业务开户如何办理?',
'营业机构日间管理风险防控要点有哪些?',
'现金管理平台柜面业务操作?'
])
const scrollBoxRef = ref()
const scrollTop = ref(99999)
const scrollToBottomNow = (time = 100) => {
nextTick(() => {
setTimeout(() => {
scrollTop.value += 100
scrollTop.value += 100
}, time)
})
}
const activeVoiceTalkingItemId = ref('')
// ws 初始化逻辑
const reconnectTimer = null // 重连timer
const isManualClose = ref(false) // 是否手动关闭
const reqBatch = ref('')
const seqNo = ref('')
const mascotId = computed(() => {
return unref(userInfo).mascotInfo?.mascotId || 'Tst00001'
})
const answerText = ref('')
const answerIsOver = ref(true)
const talkingList = ref([])
const showModelComfirm = ref(false)
const initsocketTask = () => {
socketStore.creatSocket('/traask/asksocket/open?summary=')
// unref(SocketObj).on('open', (res)=>{
// if(!answerIsOver.value){
// sendMessage({
// "commond" : 'ASK-RE-START',
// "body" : {
// reqBatch: reqBatch.value,
// seqNo: seqNo.value,
// answerContent: answerText.value
// }
// })
// }
// })
unref(SocketObj).on('error', () => {
if ((watchFlag.value === 1) || showModelComfirm.value) return
showModelComfirm.value = true
common.show('提示信息', '网络环境不稳定,请重试!', false, '', '确认').then((res) => {
common.redirectTo(`/pages/index/dialog`)
}).finally(() => {
showModelComfirm.value = false
})
})
unref(SocketObj).on('message', (res) => {
const {
rtnCode,
body,
commond
} = JSON.parse(res.data)
let _id = new Date().getTime() + 'id'
if (rtnCode === '0000') {
if (commond === 'ASK-OPEN') {
if (!answerIsOver.value) {
// 问题没完成, 重新尝试回答
sendMessage({
"commond": 'ASK-RE-START',
"body": {
reqBatch: reqBatch.value,
seqNo: seqNo.value,
answerContent: answerText.value
}
})
} else {
// 新连接
sendMessage({
"commond": 'ASK-START',
"body": {
mascotId: mascotId.value,
deviceImei: ''
}
})
}
return
}
if (commond === 'ASK-START') {
// console.log('开启对话!')
reqBatch.value = body.reqBatch
return
}
if (commond === 'ASK') {
console.log('发送问题成功!')
if ((talkingList.value.length > 0) && talkingList.value[talkingList.value.length - 1]
.isLoading) {
talkingList.value.pop()
}
seqNo.value = body.seqNo
touchBtnCallBack.value && touchBtnCallBack.value()
touchBtnCallBack.value = null
talkingList.value.push({
type: 'question',
talkingText: body.intrctContent,
isLoading: false,
id: _id,
...body,
talkingVoice: askData.value.talkingVoice
})
return
}
if (commond === 'ASK-STOP') {
// console.log('停止回答标记!')
stopAnswerCallBack.value && stopAnswerCallBack.value(askData.value)
setTimeout(() => {
stopAnswerCallBack.value = null
}, 10)
return
}
if (commond === 'ASK-ANSWER') {
if (body.event === 'start') {
console.log('开始回答!')
answerIsOver.value = false
answerText.value = ''
talkingList.value.push({
type: 'answer',
talkingText: answerText.value,
isLoading: false,
id: _id,
...body
})
} else if (body.event === 'token') {
answerIsOver.value = false
answerText.value += body.data
} else if (body.event === 'end') {
console.log('完成回答!')
answerIsOver.value = true
}
return
}
if (commond === 'ASK-RE-ANSWER') {
// console.log('开始继续回答!')
if (body.event === 'start') {
answerIsOver.value = false
answerText.value = ''
} else if (body.event === 'token') {
answerIsOver.value = false
answerText.value = body.data
} else if (body.event === 'end') {
answerIsOver.value = true
}
return
}
if (commond === 'ASK-ERR') {
console.log('提问失败,系统异常!')
return
}
} else {
if (['0005', 'QQ0005'].includes(res.rtnCode)) {
goto_login_fun();
}
}
})
watchFlag.value += 1
}
const sendLoadingText = ref('问题回答中,请在问题回答结束后重试!')
const reconnect = () => {
if (reconnectTimer || isManualClose.value) return
reconnectTimer = setTimeout(() => {
initsocketTask()
}, 3000)
}
const sendMessage = (data) => {
unref(SocketObj).send(data)
}
watch(() => answerText.value, (val) => {
if (val) {
changeText(val)
scrollToBottomNow()
}
})
const changeText = () => {
if (talkingList.value.length === 0) return
talkingList.value[talkingList.value.length - 1].talkingText = answerText.value
}
const stopAnswerCallBack = ref(null)
const talkItemEvents = (type, data, id = '') => {
switch (type) {
case 'reAnswer':
console.log('重新获取数据')
// 设置停止回调
stopAnswerCallBack.value = ($data) => {
sendMessage({
"commond": 'ASK',
"body": $data
})
}
// 停止获取
sendMessage({
"commond": 'ASK-STOP',
"body": {
reqBatch: reqBatch.value,
seqNo: seqNo.value,
}
})
break;
case 'stop':
console.log('停止获取数据')
stopAnswerCallBack.value = null
// ASK-STOP
sendMessage({
"commond": 'ASK-STOP',
"body": {
reqBatch: reqBatch.value,
seqNo: seqNo.value,
}
})
break;
case 'changePlay':
console.log('播放/暂停')
activeVoiceTalkingItemId.value = id
break;
default:
break;
}
}
const routerTo = (type) => {
switch (type) {
case 'AI学':
common.switchTab('/pages/course/index')
break;
default:
common.msg('页面建设中,敬请期待!')
break;
}
}
const scrolltolower = () => {
common.switchTab('/pages/index/index')
}
const touchBtnCallBack = ref(null)
const askData = ref({})
const sendText = (item) => {
submitAnswerNow(item)
}
// 发送语音
const uploadRecordNow = (voicePath) => {
// /traask/traAskchat/voiceTrans
//#ifndef APP-PLUS
// h5 假数据
// #endif
//#ifdef APP-PLUS
talkingList.value.push({
type: 'question',
talkingText: '',
isLoading: true
})
scrollToBottomNow()
console.log(voicePath, 111111111)
commonUploadVoiceFile(voicePath, '/traask/traAskchat/voiceTrans', {}).then(res => {
let _res = JSON.parse(res.data)
if (_res.rtnCode === '0000') {
if (!!(_res.body.transContent || '').trim()) {
askData.value = {
reqBatch: reqBatch.value,
intrctWay: '02', //01-文本;02-语音;03-图片
intrctContent: _res.body.transContent,
bucketKey: _res.body.fileId,
ossFileAddr: _res.body.ossFileAddr,
talkingVoice: voicePath
}
sendMessage({
"commond": 'ASK',
"body": askData.value
})
} else {
talkingList.value.pop()
uni.showToast({
title: '未识别到文字!',
icon: "none",
duration: 1000
});
}
} else {}
}).catch(err => {
talkingList.value.pop()
uni.showToast({
title: '系统异常,请联系管理员',
icon: "none",
duration: 1000
});
})
// #endif
}
// 发送文本
const submitAnswerNow = (val, cb = null) => {
if (!answerIsOver.value) {
// 问题回答中
return
}
touchBtnCallBack.value = cb
askData.value = {
reqBatch: reqBatch.value,
intrctWay: '01', //01-文本;02-语音;03-图片
intrctContent: val,
bucketKey: ''
}
sendMessage({
"commond": 'ASK',
"body": askData.value
})
}
const startRecordCallback = () => {
// 开始录音回调
}
const watchFlag = ref(1)
onBackPress((options) => {
const from = options.from
if (from === 'backbutton') {
// 判断上一级是否是登录,是登录就返回两层跳过登录页
const pages = getCurrentPages();
if (pages && pages.length > 1) {
const page = pages[pages.length - 2];
if (page.route === 'pages/login/login') {
console.log('进来了');
if (pages.length == 2) {
common.switchTab('/pages/index/index');
} else {
common.navigateBack(2)
}
return true
}
}
}
return false
});
onMounted(() => {
initsocketTask()
})
onHide(() => {
// if(watchFlag.value === 1) return
// // 停止获取
// sendMessage({
// "commond" : 'ASK-STOP',
// "body" : {
// reqBatch: reqBatch.value,
// seqNo: seqNo.value,
// }
// })
// socketStore?.closeSocket()
})
onShow(() => {
if (watchFlag.value === 1) return
// initsocketTask()
})
onUnload(() => {
watchFlag.value = 1
socketStore?.closeSocket()
})
</script>
<style scoped lang="scss">
.index-dialog-page {
position: relative;
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
}
.top-bg {
background: linear-gradient(16deg, rgba(234, 246, 255, 0) 0%, #c3e6ff 100%);
height: 480rpx;
width: 100%;
}
.bot-bg {
// background-color: #F6F8FF;
background: linear-gradient(136deg, rgba(234, 246, 255, 0) 0%, #c3e6ff 100%);
width: 100%;
flex: 1;
}
.index-dialog-body {
position: absolute;
overflow: hidden;
left: 0;
top: 0%;
height: 100%;
width: 100%;
padding: 0;
box-sizing: border-box;
// display: flex;
// flex-direction: column;
.body-title {
height: 140rpx;
text-align: center;
font-size: 33rpx;
padding-top: 70rpx;
line-height: 46rpx;
box-sizing: border-box;
font-weight: 500;
color: #000;
position: relative;
// margin-bottom: 23rpx;
.arrow-icon {
position: absolute;
left: 36rpx;
top: 80rpx;
color: #000;
}
}
.seek-setting {
position: absolute;
z-index: 3;
right: 45rpx;
top: 68rpx;
font-size: 26rpx;
width: 140rpx;
height: 52rpx;
padding: 10rpx 0;
overflow: hidden;
line-height: 32rpx;
text-align: right;
padding-right: 48rpx;
.image-icon {
width: 30rpx;
height: 32rpx;
position: absolute;
right: 0;
top: 10rpx;
}
.image-icon-new {
right: 50rpx;
width: 30rpx;
height: 32rpx;
position: absolute;
top: 10rpx;
}
}
.ai-info {
padding: 0 24rpx;
height: 250rpx;
overflow: hidden;
padding-top: 72rpx;
margin-bottom: 24rpx;
position: relative;
box-sizing: border-box;
.image-pet {
width: 230rpx;
height: 235rpx;
position: absolute;
right: 24rpx;
top: 15rpx;
z-index: 2;
}
.image-pet-bg {
position: absolute;
padding: 0 24rpx;
box-sizing: border-box;
width: 100%;
height: 230rpx;
left: 0;
bottom: 0;
z-index: 2;
}
.ai-info-bg-box {
position: absolute;
z-index: 3;
left: 0;
top: 68rpx;
width: 100%;
// background-color: #ccc;
padding: 0 64rpx;
height: 178rpx;
.hello-text {
height: 54rpx;
line-height: 44rpx;
font-size: 28rpx;
color: #666;
}
.info-text {
height: 44rpx;
line-height: 44rpx;
font-size: 32rpx;
color: #000;
font-weight: 600;
}
.info-btn {
width: 220rpx;
font-size: 29rpx;
height: 54rpx;
color: #fff;
background: linear-gradient(104deg, #5547f3 0%, #4e86f4 100%);
line-height: 54rpx;
text-align: center;
border-radius: 15rpx 15rpx 0 0;
position: absolute;
left: 68rpx;
bottom: 0;
}
}
}
.questions-view {
padding-left: 24rpx;
padding-right: 24rpx;
height: 184rpx;
box-sizing: border-box;
.question-row {
white-space: nowrap;
margin-bottom: 16rpx;
}
.question-item {
white-space: nowrap;
height: 66rpx;
line-height: 66rpx;
background-color: #fff;
border: 2rpx solid #e0dede;
padding: 0 16rpx;
border-radius: 11rpx;
margin-right: 16rpx;
display: inline-block;
font-size: 26rpx;
&:last-child {
margin-right: 0;
}
}
}
.router-list {
height: 84rpx;
padding: 22rpx 30rpx 0;
background-color: #fff;
border-radius: 15rpx 15rpx 0 0;
white-space: nowrap;
box-sizing: border-box;
.btns-item {
display: inline-block;
box-sizing: border-box;
height: 62rpx;
border: 2rpx solid #e0dede;
border-radius: 15rpx;
margin-right: 16rpx;
padding: 0 20rpx 0 64rpx;
line-height: 58rpx;
font-size: 29rpx;
position: relative;
.image-icon {
position: absolute;
width: 31rpx;
height: 31rpx;
top: 50%;
transform: translateY(-50%);
left: 24rpx;
}
&:last-child {
margin-right: 0;
}
}
}
.talking-list {
height: calc(100% - 832rpx);
padding: 12rpx 24rpx;
box-sizing: border-box;
.talking-scroll-list {
height: 100%;
}
}
}
.talking-list {
height: calc(100% - 832rpx);
padding: 12rpx 24rpx;
box-sizing: border-box;
.talking-scroll-list {
height: 100%;
}
}
</style>