|
@@ -6,7 +6,7 @@
|
|
|
<image src="@/static/new_dialog.png" style="width: 42rpx;height: 42rpx;margin-left: 40rpx;" @tap="startNewDialog"></image>
|
|
|
</view>
|
|
|
</u-navbar>
|
|
|
- <div class="dialogs container" ref="myContainer">
|
|
|
+ <div class="dialogs container" ref="messageContainer">
|
|
|
<div class="d_answer init">
|
|
|
<div class="da_top adfac">
|
|
|
<image src="@/static/logo.png"></image>
|
|
@@ -43,7 +43,7 @@
|
|
|
</div>
|
|
|
<div class="ask adfacjb">
|
|
|
<div class="a_l">
|
|
|
- <input type="text" v-model="question" placeholder="请输入您的问题" @confirm="sendQuestion">
|
|
|
+ <input type="text" v-model="question" placeholder="请输入您的问题" @confirm="sendQuestion" ref="questionInp">
|
|
|
</div>
|
|
|
<image class="a_r" src="@/static/send.png" @tap="sendQuestion"></image>
|
|
|
</div>
|
|
@@ -73,12 +73,19 @@
|
|
|
|
|
|
<script>
|
|
|
var timer = null;
|
|
|
+ let requestTask = null;
|
|
|
+ import { BaseApi } from '../http/baseApi.js'
|
|
|
import Tabbar from '@/components/CusTabbar/index.vue'
|
|
|
export default {
|
|
|
components:{ Tabbar },
|
|
|
data(){
|
|
|
return {
|
|
|
+ retryCount: 3, // 最大重试次数
|
|
|
+ currentRetry: 0, // 当前重试次数
|
|
|
+ isRequesting: false, // 请求状态锁
|
|
|
question:'',
|
|
|
+ streamingResponse:'',
|
|
|
+ receivedData:'',
|
|
|
dialogList:[],
|
|
|
windex:0,
|
|
|
commentShow:false,
|
|
@@ -111,23 +118,107 @@
|
|
|
clearInterval(timer)
|
|
|
this.dialogList = [];
|
|
|
this.question = '';
|
|
|
+ this.streamingResponse = '';
|
|
|
},
|
|
|
historyClose(){
|
|
|
this.historyShow = false;
|
|
|
},
|
|
|
+ // 封装带重试机制的请求方法
|
|
|
+ async sendRequestWithRetry() {
|
|
|
+ if (this.isRequesting) return;
|
|
|
+ this.isRequesting = true;
|
|
|
+ this.currentRetry = 0;
|
|
|
+ try {
|
|
|
+ await this._executeRequest();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('最终请求失败:', error);
|
|
|
+ this.$showToast('请求失败,请稍后重试')
|
|
|
+ } finally {
|
|
|
+ this.isRequesting = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 实际执行请求的方法
|
|
|
+ _executeRequest() {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ requestTask = uni.request({
|
|
|
+ url: `${BaseApi}/core/chat/streamingMessage`,
|
|
|
+ method: 'POST',
|
|
|
+ timeout: 10000,
|
|
|
+ data:{
|
|
|
+ query: this.question,
|
|
|
+ timestamp:Date.now()
|
|
|
+ },
|
|
|
+ header: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ 'token': uni.getStorageSync('token') || ''
|
|
|
+ },
|
|
|
+ enableChunked: true, // 启用流式接收
|
|
|
+ responseType:'text',
|
|
|
+ success: (res) => {
|
|
|
+ if (res.statusCode === 200) {
|
|
|
+ this._handleSuccess(res.data);
|
|
|
+ resolve();
|
|
|
+ } else {
|
|
|
+ this._handleError(`状态码异常: ${res.statusCode}`, resolve, reject);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ this._handleError(err.errMsg, resolve, reject);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ requestTask.onChunkReceived(async (res) => {
|
|
|
+ const uint8Array = new Uint8Array(res.data);
|
|
|
+ const decoder = new TextDecoder("utf-8");
|
|
|
+ const decodedString = decoder.decode(uint8Array);
|
|
|
+ let newtext = decodedString.replaceAll('data:','')
|
|
|
+ newtext = newtext.replace(/\s+/g,'');
|
|
|
+ if(newtext){
|
|
|
+ let answer = this.dialogList[this.dialogList.length-1].answer+newtext;
|
|
|
+ this.$set(this.dialogList[this.dialogList.length-1],'answer',answer);
|
|
|
+ uni.pageScrollTo({
|
|
|
+ scrollTop: 99999,
|
|
|
+ duration: 300
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ },
|
|
|
+ // 成功处理
|
|
|
+ _handleSuccess(data) {
|
|
|
+ if (data) {
|
|
|
+ this.streamingResponse += data;
|
|
|
+ console.log(this.streamingResponse,'streamingResponse');
|
|
|
+ }
|
|
|
+ this.currentRetry = 0; // 重置重试计数器
|
|
|
+ },
|
|
|
+ // 错误处理
|
|
|
+ _handleError(errorMsg, resolve, reject) {
|
|
|
+ console.error(`请求失败 (${this.currentRetry}/${this.retryCount}):`, errorMsg);
|
|
|
+ if (this._shouldRetry(errorMsg)) {
|
|
|
+ this.currentRetry++;
|
|
|
+ setTimeout(() => {
|
|
|
+ this._executeRequest().then(resolve).catch(reject);
|
|
|
+ }, this._getRetryDelay());
|
|
|
+ } else {
|
|
|
+ reject(errorMsg);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 判断是否需要重试
|
|
|
+ _shouldRetry(errorMsg) {
|
|
|
+ const retryableErrors = [
|
|
|
+ 'timeout',
|
|
|
+ 'request:fail',
|
|
|
+ 'Network Error'
|
|
|
+ ];
|
|
|
+ return this.currentRetry < this.retryCount && retryableErrors.some(e => errorMsg.includes(e));
|
|
|
+ },
|
|
|
+ // 获取指数退避延迟时间
|
|
|
+ _getRetryDelay() {
|
|
|
+ return Math.min(1000 * Math.pow(2, this.currentRetry), 10000);
|
|
|
+ },
|
|
|
sendQuestion(){
|
|
|
if(!this.question) return this.$showToast('请输入问题');
|
|
|
- let answer = `<div style="font-size: 30rpx;color: #252525;line-height: 54rpx;">
|
|
|
- 你好!请问你是想了解教练的相关信息,还是需要运动、健身或其他方面的指导呢?
|
|
|
- 无论是健身计划、技巧提升,还是职业教练的选择,我都可以为你提供建议。请告诉我你的具体需求,我会尽力帮助你!
|
|
|
- <br><br>
|
|
|
- 例如:
|
|
|
- <br><br>
|
|
|
- <b>·健身教练:</b>想要制定增肌/减脂计划?<br>
|
|
|
- <b>运动技巧:</b>想提升某项运动(如游泳、跑步)的水平?<br>
|
|
|
- <b>职业教练:</b>如何考取教练资格证或选择靠谱的教练?<br>
|
|
|
- 期待你的详细需求!
|
|
|
- </div>`;
|
|
|
let qa = {
|
|
|
question:JSON.parse(JSON.stringify(this.question)),
|
|
|
answer:'',
|
|
@@ -137,16 +228,8 @@
|
|
|
share:false,
|
|
|
}
|
|
|
this.dialogList = [...this.dialogList,...[qa]];
|
|
|
+ this.sendRequestWithRetry();
|
|
|
this.question = '';
|
|
|
- timer = setInterval(()=>{
|
|
|
- if(this.windex<answer.length){
|
|
|
- this.windex+=1;
|
|
|
- this.$set(this.dialogList[this.dialogList.length-1],'answer',answer.substring(0,this.windex));
|
|
|
- }else{
|
|
|
- this.windex = 0;
|
|
|
- clearInterval(timer)
|
|
|
- }
|
|
|
- },50)
|
|
|
},
|
|
|
toCopy(item,index){
|
|
|
this.$set(this.dialogList[index],'copy',!this.dialogList[index].copy);
|