新增ai问答演示

This commit is contained in:
杨航
2025-09-23 19:30:50 +08:00
parent 44c9f7f3fd
commit 1f659e4dbd
5 changed files with 165 additions and 185 deletions
+1 -1
View File
@@ -25,6 +25,6 @@ VITE_APP_BASE_H5_API_Url = 'http://25.18.122.65:7001'
# VITE_APP_BASE_H5_API_Url = 'http://25.18.122.78:9786'
VITE_APP_BASE_H5_API_Url_TRASTUDY = 'http://25.64.16.130:9602'
# VITE_APP_BASE_H5_API_Url_TRAASK = 'http://25.64.16.144:9605'
VITE_APP_BASE_H5_API_Url_TRAASK = 'http://25.64.16.144:9605'
+11 -1
View File
@@ -50,4 +50,14 @@ export const updateLikeStat = (data) => {
method: 'post',
data
});
};
};
// example /traask/traTestchat/chatVoice
export const chatVoice = (data) => {
return request({
url: '/traask/traTestchat/chatVoice',
method: 'post',
data
});
};
+9 -13
View File
@@ -5,7 +5,7 @@
</view>
</view>
<view class="answer-content" v-else>
<view :class="['talking-gif', !voiceLoading&&(props.activeVoiceId === dataInfo.id) ? 'is-play' : '']">
<view :class="['talking-gif', !voiceLoading&&isPlaying ? 'is-play' : '']">
</view>
<view class="talking-cont">
<MarkdownPreview :mdString="mdText" />
@@ -32,9 +32,10 @@
<view class="btns-cont">
<view class="replay-btn" >
<image class="icon" src="@/static/images/course/play.png" style="width: 100%; height: 100%"
v-show="!(!voiceLoading&&(props.activeVoiceId === dataInfo.id))"
v-show="!isPlaying"
@click.stop="playVoice()" ></image>
<image class="icon"src="@/static/images/course/pause.png"
v-show="isPlaying"
style="width: 100%; height: 100%" @click.stop="pauseVoice"></image>
</view>
</view>
@@ -48,9 +49,10 @@ import MarkdownPreview from '@/components/markdown-preview/markdown-preview.vue'
import common from '@/common/common'
import {
downloadVoiceFile,
downloadFile
} from '@/api/common.js';
const emit = defineEmits(['handleEvents'])
const emit = defineEmits(['handleEvents', 'changePlay'])
const props = defineProps({
dataInfo: {
default: () => ({}),
@@ -110,9 +112,11 @@ const isPlaying = ref(false)
talkVoiceObj.value.onEnded(()=>{
emit('changePlay', '')
talkVoiceObj.value.src = ''
isPlaying.value = false
})
talkVoiceObj.value.onError(()=>{
talkVoiceObj.value.src = ''
isPlaying.value = false
})
// itemVoice.value = unref(dataInfo).talkingVoice
// talkVoiceObj.value.src = itemVoice.value
@@ -143,16 +147,9 @@ const isPlaying = ref(false)
isPlaying.value = true
voiceLoading.value = true;
setTimeout(() => {
let _params = {}
let _url =
'/trastudy/traStdyInfo/readContent?' +
Object.keys(_params)
.map((t) => {
return encodeURIComponent(t) + '=' + encodeURIComponent(_params[t]);
})
.join('&');
downloadVoiceFile(_url)
downloadFile(props.dataInfo?.talkingVoiceId)
.then((res) => {
if (res?.data) {
common.msg(res.data.message);
@@ -162,7 +159,6 @@ const isPlaying = ref(false)
itemVoice.value = res.tempFilePath;
talkVoiceObj.value.src = res.tempFilePath;
talkVoiceObj.value.play();
storageStore.setStorage('audio', _url + _params.teachNo, itemVoice.value);
})
.catch((err) => {
voiceLoading.value = false;
+143 -169
View File
@@ -3,14 +3,8 @@
<!-- <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"
>
<scroll-view class="index-dialog-body" :scroll-y="true" upper-threshold="0" refresher-enabled="true"
refresher-default-style="none" @refresherrefresh="scrolltolower">
<view class="body-title">
<!-- <uv-icon class="arrow-icon" name="arrow-left" @click="common.navigateBack()"/> -->
AI问答演示
@@ -49,34 +43,16 @@
</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, index) in talkingList"
:talkingType="item.type"
:talkingInfo="item"
:isLast="index + 1 === talkingList.length"
:isAnswering="!answerIsOver"
:activeVoiceId="activeVoiceTalkingItemId"
@handleEvents="talkItemEvents"
></TalkingItem>
<scroll-view class="talking-scroll-list" :scroll-y="true" :scroll-top="scrollTop" :show-scrollbar="false"
:scroll-with-animation="false">
<TalkingItem v-for="(item, index) in talkingList" :talkingType="item.type" :talkingInfo="item"
:isLast="index + 1 === talkingList.length" :isAnswering="!answerIsOver"
:activeVoiceId="activeVoiceTalkingItemId" @handleEvents="talkItemEvents"></TalkingItem>
</scroll-view>
</view>
<TouchBtn
class="talk-btns"
@uploadVoice="uploadRecordNow"
@submitAnswer="submitAnswerNow"
@startRecord="startRecordCallback"
:loadingText="sendLoadingText"
:isLoading="!answerIsOver"
:adjustPosition="true"
:isDisabled="isHistory"
/>
<TouchBtn class="talk-btns" @uploadVoice="uploadRecordNow" @submitAnswer="submitAnswerNow"
@startRecord="startRecordCallback" :loadingText="sendLoadingText" :isLoading="!answerIsOver"
:adjustPosition="true" :isDisabled="isHistory" />
</scroll-view>
</view>
@@ -85,37 +61,72 @@
</template>
<script setup>
import { ref, unref, onMounted, watch, nextTick, computed, onActivated } from 'vue';
import {
ref,
unref,
onMounted,
watch,
nextTick,
computed,
onActivated
} from 'vue';
import common from '@/common/common';
import { setupKeyboardHeightListener } from '@/common/common';
import {
setupKeyboardHeightListener
} 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, getPhoneEnvBool } from '@/common/common';
import { commonUploadVoiceFile } from '@/api/common.js';
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,
getPhoneEnvBool
} from '@/common/common';
import {
commonUploadVoiceFile
} from '@/api/common.js';
import {
queryHotQuestionApi,
insertTraAskFeedback,
queryAskHisByBatchIdPaging,
updateLikeStat,
updateDownvoteStat
updateDownvoteStat,
chatVoice
} from '@/api/dialog.js';
const { statusBarHeight } = uni.getWindowInfo();
const {
statusBarHeight
} = uni.getWindowInfo();
const pageTopPadding = computed(() => statusBarHeight + 'px');
const socketStore = useSocketStore();
const { SocketObj } = storeToRefs(socketStore);
const {
SocketObj
} = storeToRefs(socketStore);
const feedbackRef = ref();
const watermarkRef = ref();
const userInfo = ref(getUserInfo());
const userParams = '/' + (unref(userInfo)?.userName??'') + ' ' + (unref(userInfo)?.loginName??'');
const userParams = '/' + (unref(userInfo)?.userName ?? '') + ' ' + (unref(userInfo)?.loginName ?? '');
const questionList = ref([
// '对公小程序开户流程?',
// '厅堂服务经理2025年管理办法?',
@@ -151,13 +162,16 @@
const answerText = ref('');
const answerFileList = ref('');
const answerIsOver = ref(true);
const talkingList = ref([{
type: 'answer',
isLoading: false,
id: 1 + 'loading',
talkingText: '是否手动关闭',
talkingVoice: 'askData.value.talkingVoice'
}]);
const talkingList = ref([
// {
// type: 'answer',
// isLoading: false,
// id: 1 + 'loading',
// talkingText: '是否手动关闭',
// talkingVoice: 'askData.value.talkingVoice'
// }
]);
const createNewDialog = () => {
if (talkingList.value.length === 0) {
@@ -188,7 +202,11 @@
});
});
unref(SocketObj).on('message', (res) => {
const { rtnCode, body, commond } = JSON.parse(res.data);
const {
rtnCode,
body,
commond
} = JSON.parse(res.data);
let _id = new Date().getTime() + 'id';
if (rtnCode === '0000') {
if (commond === 'ASK-OPEN') {
@@ -313,14 +331,39 @@
watchFlag.value += 1;
};
const sendLoadingText = ref('问题回答中,请在问题回答结束后重试!');
const reconnect = () => {
if (reconnectTimer || isManualClose.value) return;
reconnectTimer = setTimeout(() => {
initsocketTask();
}, 3000);
};
const sendMessage = (data) => {
unref(SocketObj).send(data);
touchBtnCallBack.value && touchBtnCallBack.value();
touchBtnCallBack.value = null;
let _id = new Date().getTime() + 'id';
talkingList.value.push({
type: 'question',
talkingText: data.body.intrctContent,
isLoading: false,
...data.body,
id: _id + 'q',
talkingVoice: askData.value.talkingVoice || ''
});
talkingList.value.push({
type: 'answer',
isLoading: true,
id: _id + 'loading'
});
chatVoice({
chatVoice: data.body.intrctContent
}).then(res => {
talkingList.value.pop()
talkingList.value.push({
type: 'answer',
talkingText: res.body.content,
isLoading: false,
id: _id + 'a',
talkingFiles: [],
talkingCrsList: [],
talkingVoice: '',
talkingVoiceId: res.body.fileId
});
})
};
watch(
() => answerText.value,
@@ -338,7 +381,12 @@
};
const stopAnswerCallBack = ref(null);
const talkItemEvents = ({ type, data, id, info = {} }) => {
const talkItemEvents = ({
type,
data,
id,
info = {}
}) => {
switch (type) {
case 'reAnswer':
console.log('重新提问回答');
@@ -423,30 +471,11 @@
uni.openDocument({
filePath: res,
showMenu: true,
success: function () {
success: function() {
console.log('打开文档成功');
}
});
});
// uni.downloadFile({
// url: fileUrl,
// success: function (res) {
// uni.saveFile({
// tempFilePath: res.tempFilePath,
// success: function (resS) {
// var filePath = resS.savedFilePath
// uni.openDocument({
// filePath: filePath,
// showMenu: true,
// success: function () {
// console.log('打开文档成功')
// }
// })
// }
// })
// }
// })
}
break;
case 'toCourse':
@@ -462,8 +491,7 @@
return new Promise((resolve, reject) => {
console.log('开始下载:', fileUrl);
const dtask = plus.downloader.createDownload(
fileUrl,
{
fileUrl, {
filename: localFilePath
},
(d, status) => {
@@ -522,11 +550,6 @@
console.log(res);
talkingList.value = (res.body || []).map((t, index) => {
// let _obj = JSON.parse( t.respContent)
// console.log(_obj,0)
// if(t.chatRole === 'TCH'){
// console.log(JSON.parse(_obj),1)
// }
let _text;
let _talkingCrsList;
let _talkingFiles;
@@ -559,25 +582,6 @@
historyRef.value.close();
});
};
const routerTo = (type) => {
switch (type) {
case 'AI学':
common.switchTab('/pages/course/index');
break;
case 'AI考':
common.navigateTo('/pages/testingHall/index');
break;
case '任务':
common.navigateTo('/pages/learningTasks/index');
break;
case '错题':
common.navigateTo('/pages/wrongQuestionRecord/index');
break;
default:
common.msg('页面建设中,敬请期待!');
break;
}
};
const touchBtnCallBack = ref(null);
const askData = ref({});
@@ -599,27 +603,27 @@
scrollToBottomNow();
commonUploadVoiceFile(voicePath, '/traask/traAskchat/voiceTrans', {})
.then((_res) => {
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
});
}
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
});
}
})
.catch((err) => {
talkingList.value.pop();
@@ -658,26 +662,6 @@
common.navigateBack();
};
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') {
if (pages.length == 2) {
common.switchTab('/pages/index/index');
} else {
common.navigateBack(2);
}
return true;
}
}
}
return false;
});
const getQuestionList = () => {
queryHotQuestionApi().then((res) => {
console.log(res);
@@ -694,7 +678,7 @@
const loadData = async () => {
userInfo.value = getUserInfo();
// getQuestionList();
initsocketTask();
// initsocketTask();
};
onLoad(() => {
changeAppHeightBottom();
@@ -745,31 +729,17 @@
const deleteFolder = () => {
//#ifdef APP-PLUS
plus.io.resolveLocalFileSystemURL('_doc/pdf_view', function (entry) {
plus.io.resolveLocalFileSystemURL('_doc/pdf_view', function(entry) {
console.log('entry', entry);
entry.removeRecursively(function (res) {
entry.removeRecursively(function(res) {
console.log('删除成功');
});
});
// #endif
};
//#ifdef APP-PLUS
onHide(() => {
uni.offKeyboardHeightChange(setHeight);
});
onShow(() => {
uni.onKeyboardHeightChange(setHeight);
nextTick(() => {
watermarkRef.value?.updateWatermarkText();
});
});
// #endif
onUnload(() => {
watchFlag.value = 1;
deleteFolder();
socketStore?.closeSocket();
// setupKeyboardHeightListener(changeAppHeightBottom)
});
defineExpose({
refreshData
@@ -846,6 +816,7 @@
line-height: 32rpx;
text-align: right;
padding-right: 48rpx;
.more-list {
position: absolute;
top: 100%;
@@ -857,9 +828,11 @@
padding: 16rpx 0;
display: none;
z-index: 21;
&.show {
display: block;
}
.more-item {
line-height: 60rpx;
font-size: 29rpx;
@@ -868,6 +841,7 @@
z-index: 22;
}
}
.image-icon {
width: 30rpx;
height: 32rpx;
@@ -1039,4 +1013,4 @@
// height: 100%;
// }
// }
</style>
</style>
+1 -1
View File
@@ -101,6 +101,7 @@
</view>
<view class="cell_div">
<uv-cell-group :border="false">
<uv-cell title="问答演示" :isLink="true" @click="common.navigateTo('/pages/example/dialog')"></uv-cell>
<uv-cell
title="消息通知"
:isLink="true"
@@ -115,7 +116,6 @@
:border="false"
@click="common.navigateTo('/pages/preview/index')"
></uv-cell>
<!-- <uv-cell title="问答演示" :isLink="true" @click="common.navigateTo('/pages/example/dialog')"></uv-cell> -->
</uv-cell-group>
</view>
<view class="cell_div">