|
|
<template>
|
|
|
<view class="p-home-chat g_w_all g_bg_f_5">
|
|
|
<view class="chat-content g_clear_scroll">
|
|
|
<scroll-view :scroll-y="true" :scroll-top="stesttest" :scroll-with-animation="true"
|
|
|
:style="{
|
|
|
height: wh,
|
|
|
}"
|
|
|
:upper-threshold="200" @scrolltoupper ='getMore' @scroll="getScrollInfo"
|
|
|
>
|
|
|
<view class="g_flex_row_center g_fs_12 g_pt_12 g_pb_12 obj_1" v-show="list && list.length >= 10"
|
|
|
style="color: #b3b7bc;"
|
|
|
>
|
|
|
{{ isFinish == 1 ? "查看更多" : "已全部加载" }}
|
|
|
</view>
|
|
|
<view style="padding-top: 12px" class="chatBox obj_2">
|
|
|
<view class="g_mb_16 chat-item"
|
|
|
v-for="(item, index) in list" :key="index"
|
|
|
:class="item.robotTag == 1 ? '' : (item.robotTag == 0 ? 'g_flex_row_end' : 'g_flex_row_center')"
|
|
|
:id="'id_' + item.id"
|
|
|
>
|
|
|
<view class="m-item chat-left g_flex_row_start" v-if="item.robotTag == 1">
|
|
|
<view>
|
|
|
<view v-if="index + 1 == list.length && index > 0 && isShowLeftLoading" class="left_animate g_flex_row_start">
|
|
|
<view class="g_flex_row_start g_bg_f g_pl_16 g_pr_16 g_pt_10 g_pb_10"
|
|
|
style="border-radius: 12px;"
|
|
|
>
|
|
|
<view class="g_c_6 g_flex_column_center" style="font-size: 17px;" v-if="leftAnimate1">
|
|
|
搜索中
|
|
|
</view>
|
|
|
<view class="g_c_6 g_flex_column_center" style="font-size: 17px;" v-if="leftAnimate2">
|
|
|
已搜索{{jobnum}}个职位
|
|
|
</view>
|
|
|
<view class="g_c_6 g_flex_column_center" style="font-size: 17px;" v-if="leftAnimate3">
|
|
|
智能匹配中
|
|
|
</view>
|
|
|
<view class="g_flex_column_center" hover-class="none" hover-stop-propagation="false"
|
|
|
style="height: 25px;"
|
|
|
v-if="!leftAnimate2"
|
|
|
>
|
|
|
<view class="loader">
|
|
|
<text class="dot"></text>
|
|
|
<text class="dot"></text>
|
|
|
<text class="dot"></text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
<view v-else>
|
|
|
<view class="msg g_c_0"
|
|
|
:class="item.chat_type == 'markdown' ? '' : (item.chat_type ? 'g_bg_f g_pl_10 g_pr_10 g_pt_10 g_pb_10' : '')"
|
|
|
@longpress="handleLongPress(item)"
|
|
|
:style="{
|
|
|
'max-width':item.chat_type == 'text' ? (index == 0 && item.messageType == 98 && !helloText ? 'calc(100vw - 20px)' : '80vw') : 'calc(100vw - 20px)',
|
|
|
'min-width': item.chat_type == 'text' ? 'auto' : 'calc(100vw - 20px)',
|
|
|
}"
|
|
|
>
|
|
|
<view v-if="gptType == 'ai' || gptType == 'ai-text'">
|
|
|
|
|
|
|
|
|
<view class="" v-if="item.chat_type == 'text' && index == 0 && item.messageType == 98">
|
|
|
<g-chat-gree v-if="!helloText" />
|
|
|
<view v-if="helloText" style="font-size: 17px;">
|
|
|
你好,我是{{ helloText }}机器人
|
|
|
</view>
|
|
|
</view>
|
|
|
<view class="" v-if="item.chat_type == 'text' && index == 0 && item.messageType != 98">
|
|
|
<g-chat-message :cusMessage="item.msg" :isRender="0" />
|
|
|
</view>
|
|
|
|
|
|
|
|
|
<view class="" v-if="item.chat_type == 'text' && index > 0">
|
|
|
<g-chat-message :cusMessage="item.msg" v-if="index > 0" :isRender="isRender && index == list.length - 1 ? 1 : 0" />
|
|
|
</view>
|
|
|
|
|
|
<view class="" v-if="item.chat_type == 'markdown'">
|
|
|
<g-chat-markdown :cusList="item.comList"
|
|
|
:isbor="item.isRecordJobEnd ? 1 : 0"
|
|
|
@exportMore.stop="goHotMore(item)"
|
|
|
/>
|
|
|
<div v-if="item.isRecordJobEnd">
|
|
|
<view class="markgroup g_bg_f">
|
|
|
<div class="g_flex_row_between g_h_all"
|
|
|
style="margin: 0 10px;border-top: 1px solid #eee;width: calc(100% - 20px);">
|
|
|
<div class="g_flex_row_start" @click.stop="getReload(item)" style="width: 50%;">
|
|
|
<div class="g_flex_column_center g_pl_6 g_mr_4"
|
|
|
>
|
|
|
<i class="iconfont icon-sync" style="color: #666666;font-size: 12px;position: relative;top: 1px;"></i>
|
|
|
</div>
|
|
|
<div class="g_flex_column_center" style="color: #666666;font-size: 14px;">换一批</div>
|
|
|
</div>
|
|
|
<div class="g_flex_row_end" @click.stop="goHotMore(item)" style="width: 50%;">
|
|
|
<div class="g_flex_column_center" style="color: #666666;font-size: 14px;">查看更多</div>
|
|
|
<div class="g_flex_column_center g_pr_6 g_ml_4">
|
|
|
<i class="iconfont icon-gengduo11" style="color: #666666;font-size: 12px;"></i>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</view>
|
|
|
<view class="g_h_16" v-if="index == list.length - 1"></view>
|
|
|
</div>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
<view class="g_h_16" v-if="index == list.length - 1"></view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
<view class="m-time g_text_c g_fs_12" v-else-if="item.robotTag == 2"
|
|
|
style="color: #b3b7bc;"
|
|
|
>
|
|
|
{{ formatMessageTime(item.message) }}
|
|
|
</view>
|
|
|
|
|
|
<view class="m-item chat-right" v-else style="max-width: 80vw">
|
|
|
<view class="msg g_bg_main g_c_f g_fs_17 g_pl_16 g_pr_16 g_pt_10 g_pb_10"
|
|
|
@longpress="handleLongPress(item)" style="min-height: 25px;"
|
|
|
v-if="!item.isHidden"
|
|
|
>
|
|
|
<g-chat-message :cusMessage="item.msg" style="max-width: 80%" v-if="item.msg" :data-msg='item.msg' />
|
|
|
|
|
|
<view v-if="index == (list.length - 1) && isShowRightLoading && !item.msg" class="right_animate g_flex_row_end g_mr_10 obj_4">
|
|
|
<view class="g_flex_row_end" style="border-radius: 12px;">
|
|
|
<view class="g_c_f g_flex_column_center" style="font-size: 17px;margin-right: 4px;" v-if="rightAnimate1">
|
|
|
识别中
|
|
|
</view>
|
|
|
<view class="g_c_f g_flex_column_center" style="font-size: 17px;margin-right: 4px;" v-if="rightAnimate2">
|
|
|
转写中
|
|
|
</view>
|
|
|
<view class="g_flex_column_center" style="height: 25px;">
|
|
|
<view class="loades">
|
|
|
<text class="dos"></text>
|
|
|
<text class="dos"></text>
|
|
|
<text class="dos"></text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
<view class="g_h_16" v-if="index == list.length - 1"></view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</scroll-view>
|
|
|
</view>
|
|
|
<view class="chat-operate g_flex_c g_bg_f_5" :style="writeStyle">
|
|
|
<!-- {{wh}} === {{writeStyle}} -->
|
|
|
<view class="g_bg_f m-input g_flex_row_center g_position_rela"
|
|
|
:class="hasTopPadding && msgType == 'text' ? 'hasTopPadding' : ''" :style="{
|
|
|
height: msgType == 'voice' ? '56px' : `${textareaHeight + 16}px`,
|
|
|
maxHeight: '240px',
|
|
|
overflow: 'auto',
|
|
|
}" style="align-items: flex-end; min-height: 56px">
|
|
|
<!-- 左侧 -->
|
|
|
<view class="g_flex_none g_flex_c g_h_56 g_w_56 g_posi_relative" @click.stop="handleUpdateMsgType()"
|
|
|
:class="voiceStatus == 0 && spec ? 'g_bg_main' : voiceStatus == 1 ? 'g_bg_f0a' : voiceStatus == -2 ? 'g_c_t g_bg_main' : ''"
|
|
|
style="border-radius: 40px 0px 0px 40px;position: relative;" v-if="gptType != 'ai-text'">
|
|
|
<i class="iconfont"
|
|
|
:class="msgType == 'text' ? 'icon-huatongyuyin g_fs_22' : 'icon-weixinjianpan2 g_fs_26'"
|
|
|
style="position: absolute;"
|
|
|
:style="{
|
|
|
'left':msgType == 'text' ? '19px' :'17px',
|
|
|
'top':msgType == 'text' ? '17px' :'15px'
|
|
|
}"
|
|
|
v-if="voiceStatus == -1"></i>
|
|
|
</view>
|
|
|
<!-- 中部 -->
|
|
|
<view class="g_flex_1 g_flex_column_end">
|
|
|
<view v-if="msgType == 'text'" class="m-input-point g_w_all g_h_56 g_flex_column_end">
|
|
|
<div class="container g_flex_column_end g_h_32" ref="container">
|
|
|
<textarea auto-height cursor-spacing="50" class="g_fs_17 g_c_0"
|
|
|
:class="hasTopPadding ? 'g_mt_0' : ''" id="textarea" placeholder="请输入内容"
|
|
|
v-model="sendMsg"
|
|
|
style="position: absolute; width: calc(100vw - 140px); top: 50%; transform: translateY(-50%); padding: 0 0 0 0px"
|
|
|
@input="onInput" @focus="onFocus" @blur="onBlur"
|
|
|
:adjust-position="isIosWxapp ? false : true"
|
|
|
@keyboardheightchange="keyboardheightchange" @linechange="linechange" />
|
|
|
</div>
|
|
|
</view>
|
|
|
<view v-else>
|
|
|
<view v-if="voiceStatus == 0"
|
|
|
class="voice-tip g_flex_c"
|
|
|
style="color: #666666;
|
|
|
font-size: 12px;
|
|
|
position: fixed;
|
|
|
left: 50%;
|
|
|
transform: translateX(-50%);
|
|
|
bottom: calc(150rpx + constant(safe-area-inset-bottom));
|
|
|
bottom: calc(150rpx + env(safe-area-inset-bottom));
|
|
|
width: 100vw;
|
|
|
height: 30px;
|
|
|
background-color: rgba(237,237,237,1);
|
|
|
"
|
|
|
>
|
|
|
松手发送,上移取消
|
|
|
</view>
|
|
|
<view v-if="voiceStatus == 1"
|
|
|
class="voice-tip g_flex_c"
|
|
|
style="color: #fe0000;
|
|
|
font-size: 12px;
|
|
|
position: fixed;
|
|
|
left: 50%;
|
|
|
transform: translateX(-50%);
|
|
|
bottom: calc(150rpx + constant(safe-area-inset-bottom));
|
|
|
bottom: calc(150rpx + env(safe-area-inset-bottom));
|
|
|
width: 100vw;
|
|
|
height: 30px;
|
|
|
background-color: rgba(237,237,237,1);
|
|
|
"
|
|
|
>
|
|
|
松手取消
|
|
|
</view>
|
|
|
<view class="g_w_all g_h_56 m-voice-point g_flex_c g_fw_700 g_fs_17"
|
|
|
@touchstart="onTouchStart" @touchend="onTouchEnd" @touchmove="onTouchMove"
|
|
|
:class="voiceStatus == 1 ? 'g_c_f g_bg_f0a' : voiceStatus == 0 ? 'g_bg_main' : voiceStatus == -2 ? 'g_bg_main' : ''">
|
|
|
{{ voiceStatus == 1 ? "" : voiceStatus == -1 ? "按住 说话" : "" }}
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
<!-- 发送 -->
|
|
|
<view class="g_flex_none g_flex_c g_h_56 g_w_56 g_position_rela" @click.stop="handleSendMsg"
|
|
|
:class="voiceStatus == 0 && spec ? 'g_bg_main' : voiceStatus == 1 ? 'g_bg_f0a' : voiceStatus == -2 ? 'g_c_t g_bg_main' : ''"
|
|
|
style="border-radius: 0 40px 40px 0;position: relative;">
|
|
|
<i class="iconfont icon-fasong g_fs_32"
|
|
|
:class="sendIconStatus ? 'g_c_main' : voiceStatus == -2 ? 'g_c_t g_bg_main' : 'g_c_b'"
|
|
|
style="position: absolute;right: 13px;top: 12px;"
|
|
|
v-if="voiceStatus == -1"></i>
|
|
|
</view>
|
|
|
|
|
|
<!-- 清屏 -->
|
|
|
<i class="iconfont icon-close-circle g_fs_26 g_c_9"
|
|
|
style="position: absolute; right: 22px; top: 50%; transform: translateY(-80%)"
|
|
|
v-if="textareaHeight > 80 && msgType == 'text'" @click.stop="clearMsg"></i>
|
|
|
|
|
|
<!-- 录音交互动画 -->
|
|
|
<view class="longpress-mask" :data-status="voiceStatus" @touchend="onTouchEnd">
|
|
|
<view class="longpress-top-mask g_h_56 g_flex_c"
|
|
|
style="bottom: calc(12px + constant(safe-area-inset-bottom)); bottom: calc(12px + env(safe-area-inset-bottom))"
|
|
|
v-if="voiceStatus == 0 || voiceStatus == 1 || voiceStatus == -2">
|
|
|
<div class="column-voice g_flex_row_center">
|
|
|
<div v-for="(item, index) in 20" :key="index" class="g_flex_column_center">
|
|
|
<div class="column-item g_bg_f" :style="{
|
|
|
animation: 'voi_animate 1.5s infinite ' + 0.1 * index + 's',
|
|
|
'-webkit-animation': 'voi_animate 1.5s infinite ' + 0.1 * index + 's',
|
|
|
}"></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</view>
|
|
|
<!-- <view class="longpress-bottom-mask"
|
|
|
style="height: calc(12px + constant(safe-area-inset-bottom)); height: calc(12px + env(safe-area-inset-bottom))"
|
|
|
v-if="voiceStatus == 0 || voiceStatus == -2"></view> -->
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
|
|
|
<!-- <view class="g_w_50 g_h_50 g_flex_c"
|
|
|
style="position: fixed;right: 20px;bottom: 140px;border-radius: 50%;background-color: red;"
|
|
|
@click="delAll"
|
|
|
>
|
|
|
<i class="iconfont icon-clear g_c_f" style="font-size: 20px;"></i>
|
|
|
</view> -->
|
|
|
|
|
|
<gao-ChatSSEClient
|
|
|
ref="chatSSEClientRef"
|
|
|
@onOpen="openCore"
|
|
|
@onError="errorCore"
|
|
|
@onMessage="messageCore"
|
|
|
@onFinish="finishCore"
|
|
|
/>
|
|
|
</view>
|
|
|
</template>
|
|
|
<!-- #ifdef APP -->
|
|
|
<script module="yourModuleName" lang="renderjs">
|
|
|
//此模块内部只能用选项式API风格,vue2、vue3均可用,请照抄这段代码;不可改成setup组合式API风格,否则可能不能import vue导致编译失败
|
|
|
/**需要编译成App时,你需要添加一个renderjs模块,然后一模一样的import上面那些js(微信的js除外)
|
|
|
,因为App中默认是在renderjs(WebView)中进行录音和音频编码
|
|
|
。如果配置了 RecordApp.UniWithoutAppRenderjs=true 且未调用依赖renderjs的功能时(如nvue、可视化、仅H5中可用的插件)
|
|
|
,可不提供此renderjs模块,同时逻辑层中需要将相关import的条件编译去掉**/
|
|
|
import 'recorder-core'
|
|
|
import RecordApp from 'recorder-core/src/app-support/app'
|
|
|
import './Recorder-UniCore/app-uni-support.js' //renderjs中似乎不支持"@/"打头的路径,如果编译路径错误请改正路径即可
|
|
|
|
|
|
//按需引入你需要的录音格式支持文件,和插件
|
|
|
import 'recorder-core/src/engine/mp3'
|
|
|
import 'recorder-core/src/engine/mp3-engine'
|
|
|
//按需引入你需要的录音格式支持文件,如果需要多个格式支持,把这些格式的编码引擎js文件统统引入进来即可
|
|
|
import "recorder-core/src/engine/wav";
|
|
|
import 'recorder-core/src/extensions/waveview'
|
|
|
|
|
|
export default {
|
|
|
mounted() {
|
|
|
//App的renderjs必须调用的函数,传入当前模块this
|
|
|
RecordApp.UniRenderjsRegister(this);
|
|
|
},
|
|
|
methods: {
|
|
|
//这里定义的方法,在逻辑层中可通过 RecordApp.UniWebViewVueCall(this,'this.xxxFunc()') 直接调用
|
|
|
//调用逻辑层的方法,请直接用 this.$ownerInstance.callMethod("xxxFunc",{args}) 调用,二进制数据需转成base64来传递
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
<!-- #endif -->
|
|
|
<script>
|
|
|
import Ajax from "../../utils/ajax.js";
|
|
|
import coziAjax from "../utils/cozi.js";
|
|
|
import gChatMessage from "../components/chatEvent/message.vue";
|
|
|
import gChatGree from "../components/chatEvent/greeting.vue";
|
|
|
import gChatMarkdown from "../components/chatEvent/markdown.vue";
|
|
|
import {
|
|
|
AsrClient
|
|
|
} from "../utils/AsrClient.js";
|
|
|
//必须引入的Recorder核心(文件路径是 /src/recorder-core.js 下同),使用import、require都行
|
|
|
import Recorder from "recorder-core"; //注意如果未引用Recorder变量,可能编译时会被优化删除(如vue3 tree-shaking),请改成 import 'recorder-core',或随便调用一下 Recorder.a=1 保证强引用
|
|
|
|
|
|
//必须引入的RecordApp核心文件(文件路径是 /src/app-support/app.js)
|
|
|
import RecordApp from "recorder-core/src/app-support/app";
|
|
|
|
|
|
//所有平台必须引入的uni-app支持文件(如果编译出现路径错误,请把@换成 ../../ 这种)
|
|
|
import "./Recorder-UniCore/app-uni-support.js";
|
|
|
|
|
|
/** 需要编译成微信小程序时,引入微信小程序支持文件 **/
|
|
|
// #ifdef MP-WEIXIN
|
|
|
import "recorder-core/src/app-support/app-miniProgram-wx-support.js";
|
|
|
// #endif
|
|
|
|
|
|
/** H5、小程序环境中:引入需要的格式编码器、可视化插件,App环境中在renderjs中引入 **/
|
|
|
// 注意:如果App中需要在逻辑层中调用Recorder的编码/转码功能,需要去掉此条件编译,否则会报未加载编码器的错误
|
|
|
// #ifdef H5 || MP-WEIXIN
|
|
|
//按需引入你需要的录音格式支持文件,如果需要多个格式支持,把这些格式的编码引擎js文件统统引入进来即可
|
|
|
import "recorder-core/src/engine/wav";
|
|
|
|
|
|
//可选的插件支持项,把需要的插件按需引入进来即可
|
|
|
import "recorder-core/src/extensions/waveview";
|
|
|
// #endif
|
|
|
var _wsTimer = null;
|
|
|
let defaultMsg = '你好,我是伯才智能匹配AI大鹏,可以帮老乡快速匹配工作(支持语音输入)。为了匹配更准确,需多提供老乡需求信息,例如:1. 性别2. 年龄3. 意向城市4. 工作要求(如吃住、班次等)示例:有位32岁大姐,想去常州找个长白班的工作。快告诉我老乡需求,开始匹配吧!'
|
|
|
export default {
|
|
|
components: {
|
|
|
gChatMessage,
|
|
|
gChatGree,
|
|
|
gChatMarkdown,
|
|
|
},
|
|
|
onShareAppMessage() {
|
|
|
return this.G.shareFun();
|
|
|
},
|
|
|
data() {
|
|
|
return {
|
|
|
isHidden:false,
|
|
|
jobnum:0,
|
|
|
rightAnimate1:false,
|
|
|
rightAnimate2:false,
|
|
|
defaultChat:[
|
|
|
{
|
|
|
msg:defaultMsg,
|
|
|
chat_type:'text',
|
|
|
robotTag:1,
|
|
|
messageType:98
|
|
|
}
|
|
|
],
|
|
|
previousHeight:0,
|
|
|
stesttest:0,
|
|
|
writeStyle: 'bottom:0',
|
|
|
isIosWxapp: false,
|
|
|
scrollIntoView: '',
|
|
|
wh: uni.getSystemInfoSync().screenHeight + 'px',
|
|
|
isFinish: 1,
|
|
|
isFluency: false,
|
|
|
isAside: {
|
|
|
isShow: false,
|
|
|
},
|
|
|
content: "",
|
|
|
textareaHeight: 30,
|
|
|
initialHeight: 0,
|
|
|
isShowLeftLoading:false,
|
|
|
leftAnimate1:false,
|
|
|
leftAnimate2:false,
|
|
|
leftAnimate3:false,
|
|
|
localBaseImg: this.G.store().localBaseImg,
|
|
|
scrollTop: 0,
|
|
|
list: [],
|
|
|
|
|
|
dzj: "",
|
|
|
msgType: "voice",
|
|
|
sendMsg: "",
|
|
|
sendIconStatus: false,
|
|
|
voiceMsg: "按住 说话",
|
|
|
voiceStatus: -1, // -1 录音前 & 录音结束 0 录音时且在指定范围 1录音时但不在指定范围 -2点击
|
|
|
longPressDelay: 230, // 设定长按所需时间,单位毫秒
|
|
|
spec: true,
|
|
|
isAuth: false,
|
|
|
content: "", // 内容
|
|
|
hasTopPadding: false,
|
|
|
allJob: [],
|
|
|
talkId: 0,
|
|
|
gptType: "",
|
|
|
bottomHeight: 100,
|
|
|
longPressTimer: null,
|
|
|
isStartRecord: false,
|
|
|
sendMsgGroup: [],
|
|
|
socketTask: null,
|
|
|
sequenceCounter: 1,
|
|
|
innerAudioContext: null,
|
|
|
cid: -1,
|
|
|
hisPage: 1,
|
|
|
ws_send_ready: false,
|
|
|
reconnectCount: 0,
|
|
|
isSending: false,
|
|
|
form: 'chat',
|
|
|
codeGroup: [],
|
|
|
stip: 'text',
|
|
|
isShowRightLoading: false,
|
|
|
isReturn: false,
|
|
|
isShowWelcome:false,
|
|
|
lastClickTime: 0, // 记录上一次点击时间
|
|
|
isFastClick: false, // 是否是快速点击
|
|
|
hotObj:{},
|
|
|
isjob:false,
|
|
|
aiPage:0,
|
|
|
ishide:false,
|
|
|
isShowFirst:false,
|
|
|
helloText:'',
|
|
|
isShowDel:false,
|
|
|
jobRendered:false,
|
|
|
endType:'text',
|
|
|
sendInfo:{
|
|
|
chat_id:'',
|
|
|
conversation_id:''
|
|
|
},
|
|
|
isLiuSend:false,
|
|
|
isCancel:false,
|
|
|
isRender:false,
|
|
|
};
|
|
|
},
|
|
|
onLoad(options) {
|
|
|
let that = this;
|
|
|
console.log('聊天页获取参数',options)
|
|
|
that.cid = options.cid;
|
|
|
that.form = options.form;
|
|
|
that.stip = options.stip;
|
|
|
that.helloText = options.title;
|
|
|
|
|
|
if (options.type) {
|
|
|
that.gptType = options.type;
|
|
|
} else {
|
|
|
that.gptType = "ai";
|
|
|
}
|
|
|
|
|
|
// 初始化音频上下文
|
|
|
that.innerAudioContext = uni.createInnerAudioContext();
|
|
|
that.innerAudioContext.autoplay = false; // 设置为 false,手动控制播放
|
|
|
that.innerAudioContext.onPlay(() => {
|
|
|
});
|
|
|
that.innerAudioContext.onError((res) => {});
|
|
|
this.isMounted = true;
|
|
|
//页面onShow时【必须调用】的函数,传入当前组件this
|
|
|
RecordApp.UniPageOnShow(this);
|
|
|
},
|
|
|
onShow() {
|
|
|
let that = this;
|
|
|
that.isHidden = false;
|
|
|
uni.removeStorageSync('ls_obj')
|
|
|
that.checkRecordingPermission();
|
|
|
|
|
|
if (this.isMounted) RecordApp.UniPageOnShow(this);
|
|
|
|
|
|
that.initASRClient();
|
|
|
// 监听ws状态
|
|
|
uni.$on("ws_send_ready", function(data) {
|
|
|
that.ws_send_ready = data.data;
|
|
|
});
|
|
|
|
|
|
// 异常监听
|
|
|
uni.$on("asrEvent_close", function(data) {
|
|
|
that.isShowRightLoading = false;
|
|
|
that.rightAnimate1 = false;
|
|
|
that.rightAnimate2 = false;
|
|
|
if (data.code == 400) {
|
|
|
// 链接超限,重连
|
|
|
} else {
|
|
|
that.endType = 'text'
|
|
|
that.isHidden = true;
|
|
|
that.list[that.list.length - 1].isHidden = true;
|
|
|
uni.showToast({
|
|
|
icon: "error",
|
|
|
title: data.data,
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
// 监听ws事件
|
|
|
uni.$on("asrEvent_message", function(data) {
|
|
|
let _asrData = {};
|
|
|
if (data.data) {
|
|
|
_asrData = JSON.parse(data.data);
|
|
|
if (_asrData.sequence == 1) {
|
|
|
// 开始录音
|
|
|
} else if (_asrData.sequence < 0) {
|
|
|
// 将 获取的_asrData.sequence 推进数组并去重,然后遍历该数组,判断哪个元素是负值,在负值中执行结束录音的操作
|
|
|
// 将当前 sequence 推入数组
|
|
|
that.codeGroup.push(_asrData.sequence);
|
|
|
// 去重
|
|
|
that.codeGroup = [...new Set(that.codeGroup)];
|
|
|
// 遍历数组,检查是否有负值
|
|
|
for (let i = 0; i < that.codeGroup.length; i++) {
|
|
|
if (that.codeGroup[i] < 0) {
|
|
|
// 结束录音
|
|
|
that.asrClient.asr_close();
|
|
|
if (_asrData.result && _asrData.result.length > 0) {
|
|
|
let asrStr = _asrData.result[0].text;
|
|
|
that.$nextTick(()=>{
|
|
|
that.scrollToBottom(()=>{
|
|
|
that.$nextTick(()=>{
|
|
|
that.animate();
|
|
|
that.sendAudioAI(asrStr);
|
|
|
})
|
|
|
},'拿到一句话识别',100)
|
|
|
})
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
// 发送中
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
setTimeout(() => {
|
|
|
that.hisPage = 1;
|
|
|
}, 10);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// #ifdef MP-WEIXIN
|
|
|
const accountInfo = uni.getAccountInfoSync();
|
|
|
const env = accountInfo.miniProgram.envVersion;
|
|
|
|
|
|
if (env === 'develop') {
|
|
|
that.isShowDel = true;
|
|
|
} else if (env === 'trial') {
|
|
|
that.isShowDel = true;
|
|
|
} else if (env === 'release') {
|
|
|
that.isShowDel = false;
|
|
|
}
|
|
|
// #endif
|
|
|
// #ifdef APP-PLUS
|
|
|
that.isShowDel = false;
|
|
|
// #endif
|
|
|
|
|
|
if(uni.getStorageSync('isExecute') == 0){
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
uni.showLoading({
|
|
|
title: '加载中',
|
|
|
})
|
|
|
// that.list = [];
|
|
|
that.G.Post(that.api.chat_getConversationld, {
|
|
|
robotId:uni.getStorageSync('robot_id'),
|
|
|
agencyId:uni.getStorageSync("apply-agencyId")
|
|
|
}, (res) => {
|
|
|
uni.hideLoading();
|
|
|
that.talkId = res;
|
|
|
that.cid = res;
|
|
|
that.invokeGetCoziInfoIfAllowed((coziRes)=>{
|
|
|
if (that.cid && String(that.cid).length > 4) {
|
|
|
that.talkId = that.cid;
|
|
|
that.list = [];
|
|
|
that.isShowWelcome = false;
|
|
|
that.isShowFirst = false
|
|
|
that.getHistory();
|
|
|
} else {
|
|
|
that.isShowWelcome = true;
|
|
|
that.list = that.defaultChat;
|
|
|
that.initSocket();
|
|
|
that.isShowFirst = true;
|
|
|
}
|
|
|
|
|
|
|
|
|
if(that.ishide){
|
|
|
that.ishide = false;
|
|
|
that.scrollToBottom()
|
|
|
}
|
|
|
that.previousHeight = 0;
|
|
|
that.codeGroup = [];
|
|
|
const isWxApp = uni.getSystemInfoSync().uniPlatform == 'mp-weixin'
|
|
|
const isIosWxapp = uni.getSystemInfoSync().platform == 'ios' && isWxApp
|
|
|
that.isIosWxapp = isIosWxapp;
|
|
|
|
|
|
that.isFinish = 1;
|
|
|
that.voiceStatus = -1;
|
|
|
that.isStartRecord = false;
|
|
|
// 创建查询对象
|
|
|
const query = wx.createSelectorQuery();
|
|
|
// 选择指定的 DOM 元素
|
|
|
query.select(".chat-operate").boundingClientRect();
|
|
|
// 执行查询
|
|
|
query.exec((res) => {
|
|
|
if (res && res[0]) {
|
|
|
const height = res[0].height;
|
|
|
that.bottomHeight = height;
|
|
|
that.wh = `calc(${uni.getSystemInfoSync().windowHeight}px - ${that.bottomHeight}px)`;
|
|
|
}
|
|
|
});
|
|
|
})
|
|
|
});
|
|
|
|
|
|
|
|
|
},
|
|
|
onHide(){
|
|
|
this.ishide = true;
|
|
|
this.leaveEvent()
|
|
|
},
|
|
|
onUnload() {
|
|
|
this.leaveEvent()
|
|
|
},
|
|
|
watch: {
|
|
|
sendMsg(val) {
|
|
|
if (val) {
|
|
|
this.sendIconStatus = true;
|
|
|
} else {
|
|
|
this.sendIconStatus = false;
|
|
|
}
|
|
|
},
|
|
|
isShowRightLoading(val) {
|
|
|
let that = this;
|
|
|
if(val == true){
|
|
|
setTimeout(() => {
|
|
|
that.$nextTick(()=>{
|
|
|
that.scrollToBottom(()=>{},'watch 监听 右侧loading',300)
|
|
|
})
|
|
|
}, 100);
|
|
|
}
|
|
|
},
|
|
|
isShowLeftLoading(val){
|
|
|
let that = this;
|
|
|
if(val == true){
|
|
|
setTimeout(() => {
|
|
|
that.$nextTick(()=>{
|
|
|
that.scrollToBottom(()=>{},'watch 监听 左侧loading',300)
|
|
|
})
|
|
|
}, 101);
|
|
|
}
|
|
|
},
|
|
|
},
|
|
|
methods: {
|
|
|
shouldCallGetCoziInfo() {
|
|
|
const lastCalledTime = uni.getStorageSync('last_called_getCoziInfo');
|
|
|
const now = new Date().getTime();
|
|
|
|
|
|
if (!lastCalledTime || now - lastCalledTime >= 60 * 60 * 1000) {
|
|
|
return true;
|
|
|
}
|
|
|
return false;
|
|
|
},
|
|
|
|
|
|
invokeGetCoziInfoIfAllowed(callback=()=>{}) {
|
|
|
if (this.shouldCallGetCoziInfo()) {
|
|
|
this.getCoziInfo((coziRes) => {
|
|
|
callback();
|
|
|
});
|
|
|
|
|
|
uni.setStorageSync('last_called_getCoziInfo', new Date().getTime());
|
|
|
} else{
|
|
|
callback();
|
|
|
}
|
|
|
},
|
|
|
delAll(){
|
|
|
let that = this;
|
|
|
uni.showModal({
|
|
|
title: '提示',
|
|
|
content: '确定要删除该机器人的全部记录吗?',
|
|
|
success: (res) => {
|
|
|
if (res.confirm) {
|
|
|
that.G.Post(
|
|
|
that.api.chat_delMsg, {
|
|
|
deleteAll:1,
|
|
|
conversationId: that.cid,
|
|
|
},() => {
|
|
|
uni.showToast({
|
|
|
icon:'success',
|
|
|
title:'删除成功'
|
|
|
})
|
|
|
uni.navigateBack();
|
|
|
}
|
|
|
)
|
|
|
}else{}
|
|
|
}
|
|
|
});
|
|
|
},
|
|
|
leaveEvent(){
|
|
|
let that = this;
|
|
|
that.spec = false;
|
|
|
that.hisPage = 1;
|
|
|
this.previousHeight = 0;
|
|
|
// this.stesttest = 0;
|
|
|
this.clearHeartbeat();
|
|
|
this.asrClient.asr_close();
|
|
|
_wsTimer = null;
|
|
|
uni.$off('asrEvent_message')
|
|
|
uni.hideLoading();
|
|
|
uni.removeStorageSync('test_file')
|
|
|
this.isShowRightLoading = false;
|
|
|
that.rightAnimate1 = false;
|
|
|
that.rightAnimate2 = false;
|
|
|
this.isShowLeftLoading = false;
|
|
|
this.leftAnimate1 = false;
|
|
|
this.leftAnimate2 = false;
|
|
|
this.leftAnimate3 = false;
|
|
|
this.G.startAbort(1)
|
|
|
},
|
|
|
initFormPageInfo() {
|
|
|
let that = this;
|
|
|
|
|
|
that.isShowLeftLoading = false;
|
|
|
that.leftAnimate1 = false;
|
|
|
that.leftAnimate2 = false;
|
|
|
that.leftAnimate3 = false;
|
|
|
|
|
|
that.isShowRightLoading = true;
|
|
|
that.rightAnimate1 = false;
|
|
|
that.rightAnimate2 = false;
|
|
|
|
|
|
that.list.push({
|
|
|
msg: "",
|
|
|
isGood: false,
|
|
|
isDown: false,
|
|
|
chat_type: "",
|
|
|
robotTag: 0,
|
|
|
})
|
|
|
that.startRightAnimate();
|
|
|
that.asrClient.asr_sync_connect('main');
|
|
|
let _wsTimer = setInterval(() => {
|
|
|
if (that.ws_send_ready) {
|
|
|
var audioChunks_copy = uni.getStorageSync('test_file').split(',').map(item => {
|
|
|
return item * 1
|
|
|
});
|
|
|
var audioChunks = new Int8Array(audioChunks_copy)
|
|
|
const CHUNK_SIZE = 1024 * 20;
|
|
|
for (let i = 0; i < audioChunks.length; i += CHUNK_SIZE) {
|
|
|
const end = Math.min(i + CHUNK_SIZE, audioChunks.length);
|
|
|
const chunk = audioChunks.subarray(i, end); // 关键修改
|
|
|
that.asrClient.asr_send(chunk, end === audioChunks.length);
|
|
|
}
|
|
|
that.$nextTick(()=>{
|
|
|
that.scrollToBottom(()=>{
|
|
|
that.$nextTick(()=>{
|
|
|
clearInterval(_wsTimer); // 停止值的更新
|
|
|
})
|
|
|
},'解析文件结束',100)
|
|
|
})
|
|
|
} else {
|
|
|
if (that.reconnectCount < 10) {
|
|
|
that.reconnectCount++;
|
|
|
that.asrClient.asr_close();
|
|
|
uni.removeStorageSync('asytip');
|
|
|
that.asrClient.asr_sync_connect('main');
|
|
|
} else {
|
|
|
clearInterval(_wsTimer);
|
|
|
_wsTimer = null;
|
|
|
that.asrClient.asr_close();
|
|
|
setTimeout(() => {
|
|
|
uni.hideToast();
|
|
|
}, 800);
|
|
|
}
|
|
|
}
|
|
|
}, 1000);
|
|
|
},
|
|
|
|
|
|
|
|
|
//请求录音权限
|
|
|
recReq() {
|
|
|
var that = this;
|
|
|
//编译成App时提供的授权许可(编译成H5、小程序为免费授权可不填写);如果未填写授权许可,将会在App打开后第一次调用请求录音权限时,弹出“未获得商用授权时,App上仅供测试”提示框
|
|
|
//RecordApp.UniAppUseLicense='我已获得UniAppID=*****的商用授权';
|
|
|
|
|
|
RecordApp.RequestPermission_H5OpenSet = {
|
|
|
audioTrackSet: {
|
|
|
noiseSuppression: true,
|
|
|
echoCancellation: true,
|
|
|
autoGainControl: true
|
|
|
}
|
|
|
}; //这个是Start中的audioTrackSet配置,在h5(H5、App+renderjs)中必须提前配置,因为h5中RequestPermission会直接打开录音
|
|
|
|
|
|
RecordApp.UniWebViewActivate(this); //App环境下必须先切换成当前页面WebView
|
|
|
RecordApp.RequestPermission(
|
|
|
() => {
|
|
|
console.log("已获得录音权限,可以开始录音了");
|
|
|
that.isAuth = true;
|
|
|
},
|
|
|
(msg, isUserNotAllow) => {
|
|
|
if (isUserNotAllow) {
|
|
|
//用户拒绝了录音权限
|
|
|
//这里你应当编写代码进行引导用户给录音权限,不同平台分别进行编写
|
|
|
uni.showModal({
|
|
|
title: '提示',
|
|
|
content: '需要录音权限才能使用语音功能,请前往设置开启权限',
|
|
|
success: function (res) {
|
|
|
if (res.confirm) {
|
|
|
uni.openSetting({
|
|
|
success: (settingData) => {
|
|
|
if (settingData.authSetting['scope.record']) {
|
|
|
uni.showToast({ title: '授权成功' });
|
|
|
that.msgType = "voice";
|
|
|
that.isAuth = true;
|
|
|
that.checkRecordingPermission(); // 重新尝试请求权限
|
|
|
} else {
|
|
|
uni.showToast({ icon: 'none', title: '授权失败' });
|
|
|
}
|
|
|
},
|
|
|
});
|
|
|
}
|
|
|
},
|
|
|
});
|
|
|
}
|
|
|
console.error("请求录音权限失败:" + msg);
|
|
|
that.msgType = 'text'
|
|
|
}
|
|
|
);
|
|
|
},
|
|
|
|
|
|
//开始录音
|
|
|
recStart() {
|
|
|
this.codeGroup = [];
|
|
|
//Android App如果要后台录音,需要启用后台录音保活服务(iOS不需要),需使用配套原生插件、或使用第三方保活插件
|
|
|
//RecordApp.UniNativeUtsPluginCallAsync("androidNotifyService",{ title:"正在录音" ,content:"正在录音中,请勿关闭App运行" }).then(()=>{...}).catch((e)=>{...}) 注意必须RecordApp.RequestPermission得到权限后调用
|
|
|
// const params_msg = this.asrClient.asr_send_full_client_req();
|
|
|
|
|
|
//录音配置信息
|
|
|
var set = {
|
|
|
type: "wav",
|
|
|
sampleRate: 16000,
|
|
|
bitRate: 16, //mp3格式,指定采样率hz、比特率kbps,其他参数使用默认配置;注意:是数字的参数必须提供数字,不要用字符串;需要使用的type类型,需提前把格式支持文件加载进来,比如使用wav格式需要提前加载wav.js编码引擎
|
|
|
/*,audioTrackSet:{ //可选,如果需要同时播放声音(比如语音通话),需要打开回声消除(并不一定会生效;打开后声音可能会从听筒播放,部分环境下(如小程序、App原生插件)可调用接口切换成扬声器外放)
|
|
|
//注意:H5、App+renderjs中需要在请求录音权限前进行相同配置RecordApp.RequestPermission_H5OpenSet后此配置才会生效
|
|
|
echoCancellation:true,noiseSuppression:true,autoGainControl:true} */
|
|
|
onProcess: (buffers, powerLevel, duration, sampleRate, newBufferIdx, asyncEnd) => {
|
|
|
//全平台通用:可实时上传(发送)数据,配合Recorder.SampleData方法,将buffers中的新数据连续的转换成pcm上传,或使用mock方法将新数据连续的转码成其他格式上传,可以参考Recorder文档里面的:Demo片段列表 -> 实时转码并上传-通用版;基于本功能可以做到:实时转发数据、实时保存数据、实时语音识别(ASR)等
|
|
|
|
|
|
//注意:App里面是在renderjs中进行实际的音频格式编码操作,此处的buffers数据是renderjs实时转发过来的,修改此处的buffers数据不会改变renderjs中buffers,所以不会改变生成的音频文件,可在onProcess_renderjs中进行修改操作就没有此问题了;如需清理buffers内存,此处和onProcess_renderjs中均需要进行清理,H5、小程序中无此限制
|
|
|
//注意:如果你要用只支持在浏览器中使用的Recorder扩展插件,App里面请在renderjs中引入此扩展插件,然后在onProcess_renderjs中调用这个插件;H5可直接在这里进行调用,小程序不支持这类插件;如果调用插件的逻辑比较复杂,建议封装成js文件,这样逻辑层、renderjs中直接import,不需要重复编写
|
|
|
|
|
|
//H5、小程序等可视化图形绘制,直接运行在逻辑层;App里面需要在onProcess_renderjs中进行这些操作
|
|
|
// #ifdef H5 || MP-WEIXIN
|
|
|
if (this.waveView) this.waveView.input(buffers[buffers.length - 1], powerLevel,
|
|
|
sampleRate);
|
|
|
// #endif
|
|
|
|
|
|
/*实时释放清理内存,用于支持长时间录音;在指定了有效的type时,编码器内部可能还会有其他缓冲,必须同时提供takeoffEncodeChunk才能清理内存,否则type需要提供unknown格式来阻止编码器内部缓冲,App的onProcess_renderjs中需要进行相同操作
|
|
|
if(this.clearBufferIdx>newBufferIdx){ this.clearBufferIdx=0 } //重新录音了就重置
|
|
|
for(var i=this.clearBufferIdx||0;i<newBufferIdx;i++) buffers[i]=null;
|
|
|
this.clearBufferIdx=newBufferIdx; */
|
|
|
},
|
|
|
onProcess_renderjs: `function(buffers,powerLevel,duration,sampleRate,newBufferIdx,asyncEnd){
|
|
|
//App中在这里修改buffers会改变生成的音频文件,但注意:buffers会先转发到逻辑层onProcess后才会调用本方法,因此在逻辑层的onProcess中需要重新修改一遍
|
|
|
//本方法可以返回true,renderjs中的onProcess将开启异步模式,处理完后调用asyncEnd结束异步,注意:这里异步修改的buffers一样的不会在逻辑层的onProcess中生效
|
|
|
//App中是在renderjs中进行的可视化图形绘制,因此需要写在这里,this是renderjs模块的this(也可以用This变量);如果代码比较复杂,请直接在renderjs的methods里面放个方法xxxFunc,这里直接使用this.xxxFunc(args)进行调用
|
|
|
if(this.waveView) this.waveView.input(buffers[buffers.length-1],powerLevel,sampleRate);
|
|
|
|
|
|
/*和onProcess中一样进行释放清理内存,用于支持长时间录音
|
|
|
if(this.clearBufferIdx>newBufferIdx){ this.clearBufferIdx=0 } //重新录音了就重置
|
|
|
for(var i=this.clearBufferIdx||0;i<newBufferIdx;i++) buffers[i]=null;
|
|
|
this.clearBufferIdx=newBufferIdx; */
|
|
|
}`,
|
|
|
onProcessBefore_renderjs: `function(buffers,powerLevel,duration,sampleRate,newBufferIdx){
|
|
|
//App中本方法会在逻辑层onProcess之前调用,因此修改的buffers会转发给逻辑层onProcess,本方法没有asyncEnd参数不支持异步处理
|
|
|
//一般无需提供本方法只用onProcess_renderjs就行,renderjs的onProcess内部调用过程:onProcessBefore_renderjs -> 转发给逻辑层onProcess -> onProcess_renderjs
|
|
|
}`,
|
|
|
|
|
|
takeoffEncodeChunk: true ?
|
|
|
null :
|
|
|
(chunkBytes) => {
|
|
|
//全平台通用:实时接收到编码器编码出来的音频片段数据,chunkBytes是Uint8Array二进制数据,可以实时上传(发送)出去
|
|
|
//App中如果未配置RecordApp.UniWithoutAppRenderjs时,建议提供此回调,因为录音结束后会将整个录音文件从renderjs传回逻辑层,由于uni-app的逻辑层和renderjs层数据交互性能实在太拉跨了,大点的文件传输会比较慢,提供此回调后可避免Stop时产生超大数据回传
|
|
|
//App中使用原生插件时,可方便的将数据实时保存到同一文件,第一帧时append:false新建文件,后面的append:true追加到文件
|
|
|
//RecordApp.UniNativeUtsPluginCallAsync("writeFile",{path:"xxx.mp3",append:回调次数!=1, dataBase64:RecordApp.UniBtoa(chunkBytes.buffer)}).then(...).catch(...)
|
|
|
},
|
|
|
takeoffEncodeChunk_renderjs: true ?
|
|
|
null :
|
|
|
`function(chunkBytes){
|
|
|
//App中这里可以做一些仅在renderjs中才生效的事情,不提供也行,this是renderjs模块的this(也可以用This变量)
|
|
|
}`,
|
|
|
|
|
|
start_renderjs: `function(){
|
|
|
//App中可以放一个函数,在Start成功时renderjs中会先调用这里的代码,this是renderjs模块的this(也可以用This变量)
|
|
|
//放一些仅在renderjs中才生效的事情,比如初始化,不提供也行
|
|
|
}`,
|
|
|
stop_renderjs: `function(arrayBuffer,duration,mime){
|
|
|
//App中可以放一个函数,在Stop成功时renderjs中会先调用这里的代码,this是renderjs模块的this(也可以用This变量)
|
|
|
//放一些仅在renderjs中才生效的事情,不提供也行
|
|
|
}`,
|
|
|
};
|
|
|
|
|
|
RecordApp.UniWebViewActivate(this); //App环境下必须先切换成当前页面WebView
|
|
|
RecordApp.Start(
|
|
|
set,
|
|
|
() => {
|
|
|
// that.codeGroup = [];
|
|
|
console.log("已开始录音");
|
|
|
//【稳如老狗WDT】可选的,监控是否在正常录音有onProcess回调,如果长时间没有回调就代表录音不正常
|
|
|
//var wdt=this.watchDogTimer=setInterval ... 请参考示例Demo的main_recTest.vue中的watchDogTimer实现
|
|
|
|
|
|
//创建音频可视化图形绘制,App环境下是在renderjs中绘制,H5、小程序等是在逻辑层中绘制,因此需要提供两段相同的代码
|
|
|
//view里面放一个canvas,canvas需要指定宽高(下面style里指定了300*100)
|
|
|
//<canvas type="2d" class="recwave-WaveView" style="width:300px;height:100px"></canvas>
|
|
|
// RecordApp.UniFindCanvas(this,[".recwave-WaveView"],`
|
|
|
// this.waveView=Recorder.WaveView({compatibleCanvas:canvas1, width:300, height:100});
|
|
|
// `,(canvas1)=>{
|
|
|
// this.waveView=Recorder.WaveView({compatibleCanvas:canvas1, width:300, height:100});
|
|
|
// });
|
|
|
},
|
|
|
(msg) => {
|
|
|
console.error("开始录音失败:" + msg);
|
|
|
}
|
|
|
);
|
|
|
},
|
|
|
|
|
|
//暂停录音
|
|
|
recPause() {
|
|
|
if (RecordApp.GetCurrentRecOrNull()) {
|
|
|
RecordApp.Pause();
|
|
|
}
|
|
|
},
|
|
|
//继续录音
|
|
|
recResume() {
|
|
|
if (RecordApp.GetCurrentRecOrNull()) {
|
|
|
RecordApp.Resume();
|
|
|
}
|
|
|
},
|
|
|
|
|
|
//停止录音
|
|
|
recStop() {
|
|
|
//RecordApp.UniNativeUtsPluginCallAsync("androidNotifyService",{ close:true }) //关闭Android App后台录音保活服务
|
|
|
let that = this;
|
|
|
RecordApp.Stop(
|
|
|
(arrayBuffer, duration, mime) => {
|
|
|
if (that.spec) {
|
|
|
that.list.push({
|
|
|
msg: "",
|
|
|
isGood: false,
|
|
|
isDown: false,
|
|
|
chat_type: "",
|
|
|
robotTag: 0,
|
|
|
})
|
|
|
that.startRightAnimate(()=>{});
|
|
|
that.$nextTick(()=>{
|
|
|
that.scrollToBottom(()=>{
|
|
|
//全平台通用:arrayBuffer是音频文件二进制数据,可以保存成文件或者发送给服务器
|
|
|
//App中如果在Start参数中提供了stop_renderjs,renderjs中的函数会比这个函数先执行
|
|
|
|
|
|
//注意:当Start时提供了takeoffEncodeChunk后,你需要自行实时保存录音文件数据,因此Stop时返回的arrayBuffer的长度将为0字节
|
|
|
|
|
|
//如果是H5环境,也可以直接构造成Blob/File文件对象,和Recorder使用一致
|
|
|
// #ifdef H5
|
|
|
var blob = new Blob([arrayBuffer], {
|
|
|
type: mime
|
|
|
});
|
|
|
var file = new File([arrayBuffer], "recorder.wav");
|
|
|
//uni.uploadFile({file:file, ...}) //参考demo中的test_upload_saveFile.vue
|
|
|
// #endif
|
|
|
|
|
|
//如果是App、小程序环境,可以直接保存到本地文件,然后调用相关网络接口上传
|
|
|
// #ifdef APP || MP-WEIXIN
|
|
|
var audioChunks = new Int8Array(arrayBuffer);
|
|
|
_wsTimer = setInterval(() => {
|
|
|
if (that.ws_send_ready) {
|
|
|
const CHUNK_SIZE = 1024 * 20;
|
|
|
for (let i = 0; i < audioChunks.length; i += CHUNK_SIZE) {
|
|
|
const end = Math.min(i + CHUNK_SIZE, audioChunks.length);
|
|
|
const chunk = audioChunks.subarray(i, end); // 关键修改
|
|
|
that.asrClient.asr_send(chunk, end === audioChunks.length);
|
|
|
}
|
|
|
that.$nextTick(()=>{
|
|
|
that.scrollToBottom(()=>{
|
|
|
that.$nextTick(()=>{
|
|
|
clearInterval(_wsTimer); // 停止值的更新
|
|
|
})
|
|
|
},'当前页解析结束',100)
|
|
|
})
|
|
|
} else {
|
|
|
if (that.reconnectCount < 10) {
|
|
|
that.reconnectCount++;
|
|
|
that.asrClient.asr_close();
|
|
|
uni.removeStorageSync('asytip');
|
|
|
that.asrClient.asr_sync_connect();
|
|
|
} else {
|
|
|
clearInterval(_wsTimer);
|
|
|
_wsTimer = null;
|
|
|
that.asrClient.asr_close();
|
|
|
// uni.showToast({
|
|
|
// title: "识别异常,请重试",
|
|
|
// icon: "none",
|
|
|
// });
|
|
|
setTimeout(() => {
|
|
|
uni.hideToast();
|
|
|
}, 800);
|
|
|
}
|
|
|
}
|
|
|
}, 1000);
|
|
|
},100,100)
|
|
|
})
|
|
|
}
|
|
|
// #endif
|
|
|
},
|
|
|
(msg) => {
|
|
|
console.error("结束录音失败:" + msg);
|
|
|
}
|
|
|
);
|
|
|
},
|
|
|
getMore() {
|
|
|
let that = this;
|
|
|
if (that.isFinish == 1) {
|
|
|
that.hisPage++;
|
|
|
uni.setStorageSync('isExecute',1)
|
|
|
that.getHistory("concat");
|
|
|
}
|
|
|
},
|
|
|
formatMessageTime(dateStr) {
|
|
|
const now = new Date();
|
|
|
const date = new Date(dateStr);
|
|
|
|
|
|
// 判断是否为有效日期
|
|
|
if (isNaN(date.getTime())) return "";
|
|
|
|
|
|
const year = date.getFullYear();
|
|
|
const month = date.getMonth() + 1;
|
|
|
const day = date.getDate();
|
|
|
const hour = date.getHours().toString().padStart(2, "0");
|
|
|
const minute = date.getMinutes().toString().padStart(2, "0");
|
|
|
|
|
|
// 计算时间差(单位:毫秒)
|
|
|
const diffMs = now - date;
|
|
|
|
|
|
// 1. 刚刚(1分钟以内)
|
|
|
if (diffMs < 60000) {
|
|
|
return "刚刚";
|
|
|
}
|
|
|
|
|
|
// 2. 今天的消息(显示时间 HH:mm)
|
|
|
if (
|
|
|
now.getFullYear() === year &&
|
|
|
now.getMonth() + 1 === month &&
|
|
|
now.getDate() === day
|
|
|
) {
|
|
|
return `${hour}:${minute}`;
|
|
|
}
|
|
|
|
|
|
// 3. 昨天的消息
|
|
|
const yesterday = new Date(now);
|
|
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
|
if (
|
|
|
yesterday.getFullYear() === year &&
|
|
|
yesterday.getMonth() + 1 === month &&
|
|
|
yesterday.getDate() === day
|
|
|
) {
|
|
|
return `昨天 ${hour}:${minute}`;
|
|
|
}
|
|
|
|
|
|
// 4. 今年的消息(不跨年)
|
|
|
if (now.getFullYear() === year) {
|
|
|
return `${month}月${day}日 ${hour}:${minute}`;
|
|
|
}
|
|
|
|
|
|
// 5. 去年的消息或更早(跨年)
|
|
|
return `${year}年${month}月${day}日 ${hour}:${minute}`;
|
|
|
},
|
|
|
getHistory($type = "init") {
|
|
|
let that = this;
|
|
|
const query = uni.createSelectorQuery().in(this);
|
|
|
if(uni.getStorageSync('isExecute') == 0){
|
|
|
return false;
|
|
|
}
|
|
|
// 获取当前滚动位置
|
|
|
query.select(".chat-content .chatBox").boundingClientRect(res => {
|
|
|
|
|
|
let currentScrollTop = res ? res.height : 0;
|
|
|
|
|
|
if ($type == "concat") {
|
|
|
uni.showToast({
|
|
|
title: "加载中...",
|
|
|
icon: "loading",
|
|
|
duration: 1000,
|
|
|
});
|
|
|
}
|
|
|
|
|
|
that.G.Post(
|
|
|
that.api.chat_getHistory, {
|
|
|
classify: 0,
|
|
|
conversationId: that.cid,
|
|
|
pageNum: that.hisPage,
|
|
|
pageSize: 10,
|
|
|
},
|
|
|
(historyRes) => {
|
|
|
|
|
|
uni.setStorageSync('isExecute',0)
|
|
|
if ($type == "concat") {
|
|
|
uni.hideToast();
|
|
|
}
|
|
|
console.log("获取历史记录:", historyRes,' that.hisPage:',that.hisPage);
|
|
|
|
|
|
if (historyRes && historyRes.length > 0) {
|
|
|
let _array = historyRes.map((item) => {
|
|
|
item.chatType = "text";
|
|
|
item.ids = '';
|
|
|
item.comList = [];
|
|
|
try {
|
|
|
console.log('0811 try')
|
|
|
item.chatType = "markdown";
|
|
|
item.message = JSON.parse(item.message);
|
|
|
item.ids = item.message.map(itm => itm['职位ID']).join(',');
|
|
|
} catch (e) {
|
|
|
console.log('0811 catch')
|
|
|
if(item.messageType == 99){
|
|
|
item.chatType = "markdown";
|
|
|
item.message = item.message;
|
|
|
item.ids = item.attr2;
|
|
|
}else{
|
|
|
item.chatType = "text";
|
|
|
if(item.message){
|
|
|
if(item.robotTag == 2){
|
|
|
item.message = String(item.message).replace(/.{3}$/, '');
|
|
|
}else{
|
|
|
item.message = item.message;
|
|
|
}
|
|
|
}
|
|
|
item.ids = '';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
classify: item.classify,
|
|
|
ids: item.ids,
|
|
|
msg: item.message,
|
|
|
id: item.id,
|
|
|
message: item.message,
|
|
|
isGood: false,
|
|
|
isDown: false,
|
|
|
chat_type: item.chatType,
|
|
|
robotTag: item.robotTag,
|
|
|
comList: item.comList,
|
|
|
createTime: item.createTime,
|
|
|
messageType:item.messageType || 0,
|
|
|
attr1:item.attr1 || '',
|
|
|
attr2:item.attr2 || '',
|
|
|
};
|
|
|
});
|
|
|
console.log('0811 001')
|
|
|
if ($type != "concat") {
|
|
|
uni.showLoading({ title: '加载中' });
|
|
|
setTimeout(()=>{
|
|
|
uni.hideLoading();
|
|
|
},300)
|
|
|
that.isShowWelcome = false;
|
|
|
}
|
|
|
console.log('0811 002')
|
|
|
const promises = _array.map(item => {
|
|
|
if (item.ids && item.ids.replace(/,/g, "")) {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
that.getHomeList(item.ids, (hotList) => {
|
|
|
item.comList = hotList;
|
|
|
resolve();
|
|
|
}, 1, 'hostory');
|
|
|
});
|
|
|
} else {
|
|
|
return Promise.resolve(); // 如果没有 ids,直接返回已解决的 Promise
|
|
|
}
|
|
|
});
|
|
|
console.log('0811 003')
|
|
|
Promise.all(promises).then(() => {
|
|
|
console.log('0811 004')
|
|
|
if ($type != "concat") {
|
|
|
uni.hideLoading();
|
|
|
}
|
|
|
if ($type == "concat") {
|
|
|
if (historyRes && historyRes.length < 10) {
|
|
|
that.isFinish = 2;
|
|
|
} else {
|
|
|
that.isFinish = 1;
|
|
|
}
|
|
|
// 向上拼接
|
|
|
that.list = [..._array, ...that.list];
|
|
|
|
|
|
that.$nextTick(() => {
|
|
|
const queryRestore = uni.createSelectorQuery().in(this);
|
|
|
queryRestore.select(".chatBox").boundingClientRect(resNew => {
|
|
|
if (resNew) {
|
|
|
const heightDiff = resNew.height - currentScrollTop;
|
|
|
that.stesttest = heightDiff > 0 ? heightDiff : 0;
|
|
|
}
|
|
|
}).exec();
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
that.isFinish = 1;
|
|
|
that.list = _array;
|
|
|
if(that.aiPage <= 1){
|
|
|
that.hotObj = {};
|
|
|
that.isjob = false;
|
|
|
function parseStringToObject(str) {
|
|
|
const obj = {};
|
|
|
const pairs = str.split(',').map(pair => pair.trim());
|
|
|
pairs.forEach(pair => {
|
|
|
const [key, value] = pair.split(':').map(part => part.trim());
|
|
|
obj[key] = value.replace(/^['"]|['"]$/g, '');
|
|
|
});
|
|
|
return obj;
|
|
|
}
|
|
|
that.handleRecordJobEnd(()=>{
|
|
|
that.list.forEach((item,index) => {
|
|
|
if(item.messageType == 99 && item.isRecordJobEnd){
|
|
|
let _objs = parseStringToObject(item.attr1);
|
|
|
that.isjob = true;
|
|
|
that.hotObj['sex'] = _objs['sex'];
|
|
|
that.hotObj['ageRangeStr'] = _objs['ageRangeStr'];
|
|
|
that.hotObj['cityName'] = _objs['cityName'];
|
|
|
that.hotObj['labelNames'] = _objs['labelNames'];
|
|
|
let _oa = {};
|
|
|
_oa['sex'] = _objs['sex'];
|
|
|
_oa['ageRangeStr'] = _objs['ageRangeStr'];
|
|
|
_oa['cityName'] = _objs['cityName'];
|
|
|
_oa['labelNames'] = _objs['labelNames'];
|
|
|
uni.setStorageSync('ls_obj', JSON.stringify(_oa))
|
|
|
item.ids = item.attr2;
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
};
|
|
|
|
|
|
if(uni.getStorageSync('btn_form') == 1){
|
|
|
that.startRightAnimate(()=>{});
|
|
|
uni.removeStorageSync('btn_form')
|
|
|
}
|
|
|
that.$nextTick(() => {
|
|
|
that.initFormPage()
|
|
|
});
|
|
|
}).catch((rea)=>{
|
|
|
console.log('reaaaa',rea)
|
|
|
});
|
|
|
} else {
|
|
|
that.isFinish = 2;
|
|
|
that.$nextTick(()=>{
|
|
|
that.initFormPage()
|
|
|
if ($type != "concat") {
|
|
|
that.scrollToBottom(()=>{},'当前页-无记录',300);
|
|
|
}
|
|
|
if($type == "init"){
|
|
|
that.list = that.defaultChat;
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
);
|
|
|
}).exec();
|
|
|
},
|
|
|
initFormPage(){
|
|
|
let that = this;
|
|
|
// 页面-获取文字
|
|
|
if (that.form == 'msgdata') {
|
|
|
that.sendAI('msgdata');
|
|
|
}
|
|
|
// 页面-获取音频
|
|
|
if (that.form == 'audiodata') {
|
|
|
that.initFormPageInfo()
|
|
|
}
|
|
|
// 页面-查看会话
|
|
|
if (that.form == 'viwdata') {
|
|
|
that.scrollToBottom(()=>{},'查看会话',300);
|
|
|
}
|
|
|
},
|
|
|
getHomeList(ids, callback = () => {}, $pageNum = 1,$form='') {
|
|
|
let that = this;
|
|
|
let _url = that.api.new_job_list;
|
|
|
let params = {}
|
|
|
if(that.aiPage <= 1){
|
|
|
params = {
|
|
|
shareJobIds: ids,
|
|
|
recruitment: 1,
|
|
|
pageNum: that.aiPage
|
|
|
}
|
|
|
}else{
|
|
|
params = Object.assign(that.hotObj,{
|
|
|
recruitment: 1,
|
|
|
pageNum: that.aiPage,
|
|
|
pageSize:5
|
|
|
})
|
|
|
}
|
|
|
if(params.pageNum == 0){
|
|
|
params.pageNum = 1;
|
|
|
}
|
|
|
that.G.Post(_url, params, (res) => {
|
|
|
let resData = {};
|
|
|
resData = res;
|
|
|
if (resData.recordList && resData.recordList.length > 0) {
|
|
|
resData.recordList = that.G.toGetAddressv3(resData.recordList);
|
|
|
resData.recordList = that.G.toGetAge(resData.recordList);
|
|
|
that.G.Post("/yishoudan/labels/type/app/80", {}, (res) => {
|
|
|
uni.setStorageSync("JOB_REQUIRE_LIST", {
|
|
|
data: res.labels,
|
|
|
time: Date.now()
|
|
|
});
|
|
|
resData.recordList = that.G.yijobCopy(resData.recordList);
|
|
|
});
|
|
|
}
|
|
|
resData.recordList.forEach(item => {
|
|
|
item.gender = that.G.getGenderByMinAge(item);
|
|
|
item.fuWuFei = that.G.setReturnFee(item.returnFee, item.returnFeeType);
|
|
|
item.baoNum = uni.getStorageSync("lin-bao") ? uni.getStorageSync("lin-bao") / 10 :
|
|
|
Math.ceil(item.liuNum / 10);
|
|
|
item.cus_price = item.salaryClassify != 7 ? that.G.getSalaryClassifyValueHtml(item
|
|
|
.salaryClassify, item.salaryClassifyValue) : "月薪";
|
|
|
})
|
|
|
callback(resData.recordList)
|
|
|
});
|
|
|
},
|
|
|
handlePlayAudio() {
|
|
|
let that = this;
|
|
|
if (that.audio_url) {
|
|
|
that.innerAudioContext.src = that.audio_url; // 设置音频文件路径
|
|
|
that.innerAudioContext.play(); // 开始播放
|
|
|
that.innerAudioContext.onPlay(() => {
|
|
|
});
|
|
|
that.innerAudioContext.onError((res) => {});
|
|
|
} else {}
|
|
|
},
|
|
|
initASRClient() {
|
|
|
let that = this;
|
|
|
try {
|
|
|
// 初始化 ASR 客户端
|
|
|
this.asrClient = new AsrClient("wss://openspeech.bytedance.com/api/v2/asr");
|
|
|
} catch (error) {
|
|
|
console.error("Error initializing ASR client:", error);
|
|
|
}
|
|
|
},
|
|
|
readAudioFileAsArrayBuffer(filePath) {
|
|
|
var that = this;
|
|
|
return new Promise((resolve, reject) => {
|
|
|
uni.getFileSystemManager().readFile({
|
|
|
filePath: filePath,
|
|
|
responseType: "arraybuffer", // 指定响应类型为数组缓冲区
|
|
|
success: (res) => {
|
|
|
resolve(new Int8Array(res.data));
|
|
|
},
|
|
|
fail: (err) => {
|
|
|
reject(err);
|
|
|
},
|
|
|
});
|
|
|
});
|
|
|
},
|
|
|
clearHeartbeat() {
|
|
|
clearInterval(this.heartbeatInterval);
|
|
|
},
|
|
|
// 获取扣子信息
|
|
|
getCoziInfo(callback=()=>{}) {
|
|
|
let that = this;
|
|
|
// 获取扣子 token
|
|
|
that.G.Get("/yishoudan/common/structure/getConfig", {}, (res) => {
|
|
|
uni.setStorageSync("cozi_token", res.token);
|
|
|
callback(res.token);
|
|
|
});
|
|
|
},
|
|
|
// 创建会话
|
|
|
coziCreate(callback=()=>{}){
|
|
|
let that = this;
|
|
|
coziAjax.coziPost('/v1/conversation/create',{
|
|
|
"bot_id": uni.getStorageSync('robot_id'),
|
|
|
"messages": [
|
|
|
{
|
|
|
"content_type": "text",
|
|
|
"role": "user",
|
|
|
"type": "question"
|
|
|
}
|
|
|
],
|
|
|
"custom_variables":{
|
|
|
"agencyId":uni.getStorageSync("apply-agencyId"),
|
|
|
}
|
|
|
},(sdkRes) =>{
|
|
|
callback(sdkRes)
|
|
|
});
|
|
|
},
|
|
|
// 发起会话
|
|
|
coziChat($sendMessage,callback=()=>{}){
|
|
|
let that = this;
|
|
|
that.jobRendered = false;
|
|
|
that.$refs.chatSSEClientRef.startChat({
|
|
|
url: "https://api.coze.cn/v3/chat?conversation_id=" + that.cid,
|
|
|
// 请求头
|
|
|
headers: {
|
|
|
Authorization: "Bearer " + uni.getStorageSync("cozi_token"),
|
|
|
"Content-Type": "application/json;charset=UTF-8",
|
|
|
},
|
|
|
// 默认为 post
|
|
|
method: "post",
|
|
|
body: {
|
|
|
"bot_id": uni.getStorageSync('robot_id'),
|
|
|
"user_id": "user",
|
|
|
"stream": true,
|
|
|
"additional_messages": [
|
|
|
{
|
|
|
"content": $sendMessage,
|
|
|
"content_type": "text",
|
|
|
"role": "user",
|
|
|
"type": "question"
|
|
|
}
|
|
|
],
|
|
|
"custom_variables":{
|
|
|
"agencyId":uni.getStorageSync("apply-agencyId"),
|
|
|
}
|
|
|
},
|
|
|
});
|
|
|
callback()
|
|
|
},
|
|
|
// 取消会话
|
|
|
coziCancelChat(callback=()=>{}){
|
|
|
let that = this;
|
|
|
that.isRender = false;
|
|
|
if(that.sendInfo.conversation_id && that.sendInfo.chat_id){
|
|
|
that.isCancel = true;
|
|
|
coziAjax.coziPost('/v3/chat/cancel',{
|
|
|
"conversation_id":that.sendInfo.conversation_id,
|
|
|
"chat_id":that.sendInfo.chat_id
|
|
|
},(sdkRes) => {
|
|
|
that.isCancel = false;
|
|
|
that.sendInfo = {};
|
|
|
callback(sdkRes)
|
|
|
});
|
|
|
}else{
|
|
|
that.sendInfo = {};
|
|
|
callback({
|
|
|
"code": 400,
|
|
|
"message": "会话不存在"
|
|
|
})
|
|
|
}
|
|
|
},
|
|
|
stopCore($form='default'){
|
|
|
let that = this;
|
|
|
that.list = that.list.slice(0, -1);
|
|
|
that.$refs.chatSSEClientRef.stopChat();
|
|
|
// 调用发送消息
|
|
|
that.handleSendMsg()
|
|
|
},
|
|
|
openCore(err){
|
|
|
},
|
|
|
errorCore(err){
|
|
|
},
|
|
|
messageCore(msg){
|
|
|
let that = this;
|
|
|
if(that.isCancel){
|
|
|
that.isRender = false;
|
|
|
that.stopCore('停止流式输出');
|
|
|
return false;
|
|
|
}
|
|
|
if(JSON.parse(msg.data) == '[DONE]'){
|
|
|
that.isRender = false;
|
|
|
that.isLiuSend = false;
|
|
|
that.handleEnd();
|
|
|
}else{
|
|
|
that.isRender = true;
|
|
|
that.isLiuSend = true;
|
|
|
console.log('000000000000000000000',JSON.parse(msg.data))
|
|
|
if(JSON.parse(msg.data) && JSON.parse(msg.data).type == 'answer'){
|
|
|
that.sendInfo = {
|
|
|
chat_id:JSON.parse(msg.data).chat_id,
|
|
|
conversation_id:JSON.parse(msg.data).conversation_id
|
|
|
}
|
|
|
|
|
|
if(JSON.parse(msg.data).content && JSON.parse(msg.data).content != ' ' && !JSON.parse(msg.data).updated_at && JSON.parse(msg.data).content.length < 5){
|
|
|
// console.log('文本类型**************************************回复:',JSON.parse(msg.data).content)
|
|
|
// 流式输出增量渲染
|
|
|
that.endType = 'text';
|
|
|
that.handleTextRender(JSON.parse(msg.data))
|
|
|
}else{
|
|
|
that.$nextTick(()=>{
|
|
|
that.scrollToBottom(()=>{},'类型异常',3);
|
|
|
})
|
|
|
try {
|
|
|
const parsedData = JSON.parse(msg.data);
|
|
|
try {
|
|
|
const contentData = JSON.parse(parsedData.content);
|
|
|
if (Array.isArray(contentData)) {
|
|
|
// 处理数组内容,如渲染到页面或更新状态
|
|
|
// console.log('推荐职位类型**************************************回复:',contentData)
|
|
|
that.endType = 'markdown';
|
|
|
that.handleJobRender(contentData)
|
|
|
}else {}
|
|
|
} catch (contentError) {}
|
|
|
} catch (dataError) {}
|
|
|
}
|
|
|
}else if(JSON.parse(msg.data) && JSON.parse(msg.data).type == 'verbose'){
|
|
|
that.$nextTick(()=>{
|
|
|
that.scrollToBottom(()=>{},'errr',10);
|
|
|
})
|
|
|
}else if(JSON.parse(msg.data) && JSON.parse(msg.data).type == 'tool_response' && JSON.parse(msg.data).content == '暂未找到匹配的岗位'){
|
|
|
// 异常
|
|
|
that.isShowLeftLoading = false;
|
|
|
uni.showToast({
|
|
|
title: '暂未找到匹配的岗位',
|
|
|
icon: 'none',
|
|
|
duration:2000
|
|
|
})
|
|
|
that.list.splice(that.list.length - 1, 1);
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
finishCore(err){
|
|
|
},
|
|
|
handleTextRender($smg){
|
|
|
let that = this;
|
|
|
const targetIndex = that.list.length - 1;
|
|
|
const parsed = $smg;
|
|
|
that.list[targetIndex].chat_type = "text";
|
|
|
that.list[targetIndex].robotTag = 1;
|
|
|
that.list[targetIndex].ids = '';
|
|
|
const newContent = parsed.content;
|
|
|
// 增量追加内容
|
|
|
that.$set(that.list[targetIndex], 'msg', (that.list[targetIndex].msg || '') + newContent);
|
|
|
that.isSending = false;
|
|
|
that.isShowLeftLoading = false;
|
|
|
that.leftAnimate1 = false;
|
|
|
that.leftAnimate2 = false;
|
|
|
that.leftAnimate3 = false;
|
|
|
that.$nextTick(()=>{
|
|
|
that.scrollToBottom(()=>{},'流式输出中',10);
|
|
|
})
|
|
|
},
|
|
|
handleJobRender($array){
|
|
|
let that = this;
|
|
|
if (that.jobRendered) return; // 已执行过则返回
|
|
|
|
|
|
that.jobRendered = true;
|
|
|
that.hotObj = {};
|
|
|
that.isjob = false;
|
|
|
try {
|
|
|
const parsedContent = $array;
|
|
|
that.aiPage = 1;
|
|
|
|
|
|
that.list.forEach((ita,ina) => {
|
|
|
ita.opbtn = 0
|
|
|
})
|
|
|
that.list[that.list.length - 1].opbtn = 1;
|
|
|
|
|
|
that.list[that.list.length - 1].msg = parsedContent;
|
|
|
that.list[that.list.length - 1].chat_type = "markdown";
|
|
|
that.list[that.list.length - 1].robotTag = 1;
|
|
|
that.list[that.list.length - 1].ids = parsedContent.map((itm,inx) => {
|
|
|
if(inx == 0){
|
|
|
that.isjob = true;
|
|
|
that.hotObj['sex'] = itm['sex'];
|
|
|
that.hotObj['ageRangeStr'] = itm['ageRangeStr'];
|
|
|
that.hotObj['cityName'] = itm['cityName'];
|
|
|
that.hotObj['labelNames'] = itm['labelNames'];
|
|
|
|
|
|
let _oa = {};
|
|
|
_oa['sex'] = itm['sex'];
|
|
|
_oa['ageRangeStr'] = itm['ageRangeStr'];
|
|
|
_oa['cityName'] = itm['cityName'];
|
|
|
_oa['labelNames'] = itm['labelNames'];
|
|
|
uni.setStorageSync('ls_obj', JSON.stringify(_oa))
|
|
|
}
|
|
|
return itm['职位ID']
|
|
|
}).join(',');
|
|
|
that.getHomeList(that.list[that.list.length - 1].ids, (hotList) => {
|
|
|
that.list[that.list.length - 1].comList = [];
|
|
|
that.list[that.list.length - 1].comList = hotList;
|
|
|
},1,'send');
|
|
|
} catch (e) {}
|
|
|
},
|
|
|
handleEnd(){
|
|
|
let that = this;
|
|
|
that.saveHistory(1, that.list[that.list.length - 1].msg, (historyData) => {
|
|
|
console.log('最后一条消息保存成功',that.endType)
|
|
|
that.list[that.list.length - 1].id = historyData.id;
|
|
|
that.isSending = false;
|
|
|
that.handleSet(that.list[that.list.length - 1].msg, "", 0, 300);
|
|
|
},'messageEnd');
|
|
|
},
|
|
|
|
|
|
initSocket() {
|
|
|
let that = this;
|
|
|
that.coziCreate((sdkRes)=>{
|
|
|
that.talkId = sdkRes.id;
|
|
|
that.cid = sdkRes.id;
|
|
|
|
|
|
that.saveHistory(1, that.helloText ? '你好,我是' + that.helloText + '机器人' : '你好,我是伯才智能匹配AI大鹏,可以帮老乡快速匹配工作(支持语音输入)。为了匹配更准确,需多提供老乡需求信息,例如:1. 性别2. 年龄3. 意向城市4. 工作要求(如吃住、班次等)示例:有位32岁大姐,想去常州找个长白班的工作。快告诉我老乡需求,开始匹配吧!', (historyData) => {
|
|
|
that.defaultChat.id = historyData.id;
|
|
|
that.list = that.defaultChat;
|
|
|
},'welcome');
|
|
|
})
|
|
|
},
|
|
|
|
|
|
keyboardheightchange(e) {
|
|
|
var that = this;
|
|
|
if (e.target.dataset.height == 0 && this.sendMsg == "") {
|
|
|
this.textareaHeight = 30;
|
|
|
}
|
|
|
if (this.isIosWxapp) {
|
|
|
if (e.detail.height == 0) {
|
|
|
this.writeStyle = `bottom:0`
|
|
|
this.wh = `calc(${uni.getSystemInfoSync().windowHeight}px - ${this.bottomHeight}px)`
|
|
|
// this.ghHeight = 'height:84px'
|
|
|
} else {
|
|
|
this.writeStyle = `bottom: calc(${e.detail.height}px - env(safe-area-inset-bottom) + 12rpx)`
|
|
|
// this.ghHeight = `height:calc(84px + ${e.detail.height}px)`
|
|
|
this.wh =
|
|
|
`calc(${uni.getSystemInfoSync().windowHeight}px - ${this.bottomHeight}px - ${e.detail.height}px)`
|
|
|
}
|
|
|
that.scrollToBottom(()=>{},'键盘',100);
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
onInput(e) {
|
|
|
this.content = e.target.value;
|
|
|
this.updateTextareaHeight();
|
|
|
},
|
|
|
onFocus() {
|
|
|
this.initialHeight = this.textareaHeight;
|
|
|
},
|
|
|
onBlur() {
|
|
|
this.updateTextareaHeight();
|
|
|
},
|
|
|
updateTextareaHeight() {},
|
|
|
linechange(e) {
|
|
|
if (e.detail.lineCount > 1) {
|
|
|
this.hasTopPadding = true;
|
|
|
} else {
|
|
|
this.hasTopPadding = false;
|
|
|
}
|
|
|
},
|
|
|
getScrollInfo(e){
|
|
|
// console.log('滚动信息:',e.detail)
|
|
|
},
|
|
|
scrollToBottom(callback=()=>{},$from='',$time=300) {
|
|
|
let that = this;
|
|
|
const query = uni.createSelectorQuery().in(this);
|
|
|
setTimeout(() => {
|
|
|
query.select(".chatBox").boundingClientRect(res => {
|
|
|
that.$nextTick(()=>{
|
|
|
if (res) {
|
|
|
let newHeight = res.height;
|
|
|
if (newHeight === that.previousHeight) {
|
|
|
newHeight += 1;
|
|
|
}
|
|
|
that.previousHeight = newHeight;
|
|
|
if ($from == 100) {
|
|
|
that.stesttest = newHeight + 41;
|
|
|
} else if ($from == 100) {
|
|
|
that.stesttest = newHeight + 42;
|
|
|
} else {
|
|
|
that.stesttest = newHeight + 40;
|
|
|
}
|
|
|
} else {
|
|
|
that.previousHeight = 0;
|
|
|
}
|
|
|
// console.log('滚动动画:',that.stesttest, '备注:',$from,' 延迟:',$time)
|
|
|
callback();
|
|
|
})
|
|
|
}).exec();
|
|
|
}, $time);
|
|
|
},
|
|
|
handleUpdateGood($item, $tip) {
|
|
|
if ($tip == "good") {
|
|
|
if ($item.isGood) {
|
|
|
$item.isGood = false;
|
|
|
} else {
|
|
|
$item.isGood = true;
|
|
|
}
|
|
|
} else {
|
|
|
if ($item.isDown) {
|
|
|
$item.isDown = false;
|
|
|
} else {
|
|
|
$item.isDown = true;
|
|
|
}
|
|
|
}
|
|
|
this.animate();
|
|
|
},
|
|
|
handleClickCopy($content = "") {
|
|
|
let that = this;
|
|
|
uni.setClipboardData({
|
|
|
data: $content,
|
|
|
success: () => {},
|
|
|
fail: (err) => {},
|
|
|
});
|
|
|
that.animate();
|
|
|
},
|
|
|
handleUpdateMsgType() {
|
|
|
let that = this;
|
|
|
that.form = '';
|
|
|
this.animate();
|
|
|
if (this.msgType == "text") {
|
|
|
this.msgType = "voice";
|
|
|
this.checkRecordingPermission();
|
|
|
} else {
|
|
|
this.msgType = "text";
|
|
|
}
|
|
|
that.writeStyle = `bottom:0`
|
|
|
that.wh = `calc(${uni.getSystemInfoSync().windowHeight}px - ${this.bottomHeight}px)`
|
|
|
},
|
|
|
handleNextMsg($type=0){
|
|
|
let that = this;
|
|
|
if($type == 0){
|
|
|
that.stopCore(' 流式 取消会话');
|
|
|
}
|
|
|
// 取消会话
|
|
|
that.coziCancelChat((res)=>{
|
|
|
if(res.code == 400){
|
|
|
return false
|
|
|
}else{
|
|
|
// 再次发送
|
|
|
// that.coziChat(that.sendMsg,()=>{
|
|
|
// that.sendMsg = '';
|
|
|
// })
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
handleSendMsg() {
|
|
|
let that = this;
|
|
|
if (that.msgType == "voice") {
|
|
|
return false;
|
|
|
}
|
|
|
if(that.isShowLeftLoading){
|
|
|
uni.showToast({
|
|
|
title:'匹配中,请稍候',
|
|
|
icon:'none'
|
|
|
})
|
|
|
return false;
|
|
|
}
|
|
|
if (that.isLiuSend) {
|
|
|
that.isLiuSend = false;
|
|
|
that.handleNextMsg();
|
|
|
return false;
|
|
|
}
|
|
|
if (that.sendMsg == "") {
|
|
|
uni.showToast({
|
|
|
icon: "none",
|
|
|
title: "请输入内容",
|
|
|
});
|
|
|
return false;
|
|
|
}
|
|
|
that.animate();
|
|
|
that.sendAI();
|
|
|
that.writeStyle = `bottom:0`
|
|
|
that.wh = `calc(${uni.getSystemInfoSync().windowHeight}px - ${this.bottomHeight}px)`
|
|
|
that.$nextTick(()=>{
|
|
|
that.sendMsg = "";
|
|
|
that.updateTextareaHeight();
|
|
|
that.textareaHeight = 30;
|
|
|
that.$nextTick(()=>{
|
|
|
that.textareaHeight = 30;
|
|
|
})
|
|
|
})
|
|
|
},
|
|
|
|
|
|
checkRecordingPermission() {
|
|
|
this.recReq();
|
|
|
},
|
|
|
onTouchMove(e) {
|
|
|
let that = this;
|
|
|
if (!that.isAuth) {
|
|
|
that.voiceStatus = -1;
|
|
|
return false;
|
|
|
}
|
|
|
const touch = e.touches[0];
|
|
|
let _x = touch.clientX,
|
|
|
_y = touch.clientY;
|
|
|
|
|
|
if (_y > uni.getSystemInfoSync().windowHeight - that.bottomHeight) {
|
|
|
this.voiceStatus = 0;
|
|
|
this.spec = true;
|
|
|
} else {
|
|
|
this.voiceStatus = 1;
|
|
|
this.spec = false;
|
|
|
}
|
|
|
},
|
|
|
|
|
|
onTouchStart() {
|
|
|
let that = this;
|
|
|
that.isHidden = false;
|
|
|
if (!that.isAuth) {
|
|
|
uni.showToast({
|
|
|
icon: "none",
|
|
|
title: "授权中,请稍候",
|
|
|
});
|
|
|
return false;
|
|
|
}
|
|
|
if(that.isShowLeftLoading){
|
|
|
uni.showToast({
|
|
|
title:'匹配中,请稍候',
|
|
|
icon:'none'
|
|
|
})
|
|
|
return false;
|
|
|
}
|
|
|
if (that.isLiuSend) {
|
|
|
that.isLiuSend = false;
|
|
|
that.handleNextMsg();
|
|
|
return false;
|
|
|
}
|
|
|
that.animate("heavy");
|
|
|
uni.removeStorageSync('asytip');
|
|
|
that.reconnectCount = 0;
|
|
|
that.voiceStatus = -2;
|
|
|
that.isStartRecord = false;
|
|
|
that.longPressTimer = setTimeout(() => {
|
|
|
that.asrClient.asr_sync_connect();
|
|
|
that.isStartRecord = true;
|
|
|
that.spec = true;
|
|
|
that.startRecording();
|
|
|
}, that.longPressDelay);
|
|
|
},
|
|
|
onTouchEnd() {
|
|
|
let that = this;
|
|
|
if (!that.isAuth) {
|
|
|
that.voiceStatus = -1;
|
|
|
return false;
|
|
|
}
|
|
|
clearTimeout(that.longPressTimer);
|
|
|
if (that.isAuth) {
|
|
|
const currentTime = new Date().getTime();
|
|
|
const timeDiff = currentTime - that.lastClickTime;
|
|
|
that.isShowRightLoading = false;
|
|
|
that.rightAnimate1 = false;
|
|
|
that.rightAnimate2 = false;
|
|
|
if (timeDiff > 0 && timeDiff < 300) {
|
|
|
that.isFastClick = true;
|
|
|
} else {
|
|
|
that.isFastClick = false;
|
|
|
}
|
|
|
that.lastClickTime = currentTime;
|
|
|
that.scrollToBottom(()=>{
|
|
|
that.stopRecording();
|
|
|
},'触摸离开',100)
|
|
|
} else {
|
|
|
uni.showToast({
|
|
|
icon: "none",
|
|
|
title: "授权中,请稍候",
|
|
|
});
|
|
|
return false;
|
|
|
}
|
|
|
},
|
|
|
startRecording() {
|
|
|
let that = this;
|
|
|
setTimeout(() => {
|
|
|
that.voiceStatus = 0; // 标记为正在录音
|
|
|
if (!that.isFluency) {
|
|
|
that.isSending = false;
|
|
|
// 语音识别开始
|
|
|
that.recStart();
|
|
|
}
|
|
|
}, 100);
|
|
|
},
|
|
|
stopRecording() {
|
|
|
let that = this;
|
|
|
that.baseEnd();
|
|
|
setTimeout(
|
|
|
() => {
|
|
|
that.baseEnd();
|
|
|
},
|
|
|
that.isFluency ? 500 : 0
|
|
|
);
|
|
|
},
|
|
|
baseEnd() {
|
|
|
let that = this;
|
|
|
that.voiceStatus = -1;
|
|
|
if (that.isStartRecord) {
|
|
|
that.recStop();
|
|
|
that.isStartRecord = false;
|
|
|
that.isReturn = true;
|
|
|
} else {
|
|
|
that.isReturn = false;
|
|
|
}
|
|
|
that.$forceUpdate();
|
|
|
},
|
|
|
sendAudioAI($msg) {
|
|
|
let that = this;
|
|
|
that.isHidden = false;
|
|
|
if (that.isSending) {
|
|
|
return;
|
|
|
}
|
|
|
that.isSending = true;
|
|
|
that.$nextTick(()=>{
|
|
|
that.scrollToBottom(()=>{
|
|
|
that.voiceStatus = -1;
|
|
|
that.sendBaseData($msg,'','audio');
|
|
|
},'发送语音',300)
|
|
|
})
|
|
|
},
|
|
|
sendAI($form = "", $value = "") {
|
|
|
let that = this;
|
|
|
that.isHidden = false;
|
|
|
that.textareaHeight = 30;
|
|
|
if ($form == 'msgdata') {
|
|
|
that.sendBaseData(uni.getStorageSync('ls-chat-text'), $form);
|
|
|
} else {
|
|
|
that.sendBaseData(that.sendMsg,'','text');
|
|
|
}
|
|
|
},
|
|
|
sendBaseData($sendMessage = "", $form = '',$msgtype='') {
|
|
|
let that = this;
|
|
|
uni.removeStorageSync('ls_obj')
|
|
|
if ($form == 'msgdata') {
|
|
|
uni.removeStorageSync('ls-chat-text')
|
|
|
}
|
|
|
that.isShowRightLoading = false;
|
|
|
that.rightAnimate1 = false;
|
|
|
that.rightAnimate2 = false;
|
|
|
let currentTime = new Date();
|
|
|
let shouldInsertNewRecord = true;
|
|
|
if (that.list.length > 0) {
|
|
|
let lastItem = that.list[that.list.length - 1];
|
|
|
if (lastItem.createTime) {
|
|
|
let lastCreateTime = new Date(lastItem.createTime);
|
|
|
let timeDifference = (currentTime - lastCreateTime) / (1000 * 60);
|
|
|
if (timeDifference <= 5) {
|
|
|
shouldInsertNewRecord = false;
|
|
|
}else{
|
|
|
shouldInsertNewRecord = true;
|
|
|
}
|
|
|
}else{
|
|
|
shouldInsertNewRecord = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if($msgtype == 'audio'){
|
|
|
that.rightPush(shouldInsertNewRecord,'','audio');
|
|
|
const targetIndex = shouldInsertNewRecord ? that.list.length - 1 : that.list.length - 1;
|
|
|
let index = 0;
|
|
|
const typingSpeed = 88;
|
|
|
|
|
|
const typingInterval = setInterval(() => {
|
|
|
if (index < $sendMessage.length) {
|
|
|
this.$set(that.list[targetIndex], 'msg', that.list[targetIndex].msg + $sendMessage[index]);
|
|
|
index++;
|
|
|
this.scrollToBottom();
|
|
|
} else {
|
|
|
clearInterval(typingInterval);
|
|
|
that.thenResult($sendMessage);
|
|
|
}
|
|
|
}, typingSpeed);
|
|
|
}else{
|
|
|
that.rightPush(shouldInsertNewRecord,$sendMessage,'text');
|
|
|
that.thenResult($sendMessage);
|
|
|
}
|
|
|
},
|
|
|
rightPush(shouldInsertNewRecord,$sendMessage,$type=''){
|
|
|
let that = this;
|
|
|
if($type == 'audio'){
|
|
|
that.list[that.list.length - 1].msg = $sendMessage;
|
|
|
that.list[that.list.length - 1].chat_type = "job";
|
|
|
that.list[that.list.length - 1].robotTag = 0;
|
|
|
}else{
|
|
|
if (shouldInsertNewRecord) {
|
|
|
that.list.push(
|
|
|
{
|
|
|
msg: new Date().getTime(),
|
|
|
message: new Date().getTime(),
|
|
|
chat_type: "",
|
|
|
robotTag: 2,
|
|
|
},
|
|
|
{
|
|
|
msg: $sendMessage,
|
|
|
chat_type: "job",
|
|
|
robotTag: 0,
|
|
|
}
|
|
|
);
|
|
|
}else{
|
|
|
that.list.push({
|
|
|
msg: $sendMessage,
|
|
|
chat_type: "job",
|
|
|
robotTag: 0,
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
thenResult($sendMessage=''){
|
|
|
let that = this;
|
|
|
that.scrollToBottom(()=>{
|
|
|
that.list.push({
|
|
|
msg: "",
|
|
|
isGood: false,
|
|
|
isDown: false,
|
|
|
chat_type: "",
|
|
|
robotTag: 1,
|
|
|
});
|
|
|
that.startLeftAnimate(()=>{});
|
|
|
that.scrollToBottom(()=>{
|
|
|
if (!$sendMessage) {
|
|
|
return false;
|
|
|
}
|
|
|
that.saveHistory(0, $sendMessage, (historyData) => {
|
|
|
that.list[that.list.length - 2].id = historyData.id;
|
|
|
// sdk-聊天
|
|
|
that.coziChat($sendMessage,()=>{
|
|
|
$sendMessage = "";
|
|
|
});
|
|
|
},'sendmsg')
|
|
|
},'发送消息',300);
|
|
|
},'显示loading',100)
|
|
|
},
|
|
|
saveHistory(robotTag = 0, $message = "", callabck = () => {},$type='') {
|
|
|
let that = this,
|
|
|
params = {
|
|
|
classify: 0,
|
|
|
conversationId: that.cid,
|
|
|
userId: uni.getStorageSync("apply-uid"),
|
|
|
agencyId: uni.getStorageSync("apply-agencyId"),
|
|
|
robotTag: robotTag,
|
|
|
message: $message,
|
|
|
form: 'root'
|
|
|
};
|
|
|
if ($message) {
|
|
|
that.form = '';
|
|
|
if(that.endType == 'markdown'){
|
|
|
params['messageType'] = 99;
|
|
|
params['attr1'] = Object.entries(that.hotObj).map(([key, value]) => `${key}: '${value}'`).join(',')
|
|
|
params['attr2'] = that.list[that.list.length - 1].ids;// ids
|
|
|
params['attr3'] = 'jobcard'
|
|
|
params['message'] = '推荐职位';
|
|
|
}else{
|
|
|
params['messageType'] = 97;
|
|
|
params['attr3'] = '';
|
|
|
}
|
|
|
if($type == 'welcome'){
|
|
|
params['messageType'] = 98;
|
|
|
}
|
|
|
params.robotId = uni.getStorageSync('robot_id')
|
|
|
params.agencyId = uni.getStorageSync('apply-agencyId')
|
|
|
that.G.Post(
|
|
|
that.api.chat_addHistory, params,(res) => {
|
|
|
that.handleRecordJobEnd();
|
|
|
that.isjob = false;
|
|
|
callabck(res);
|
|
|
}
|
|
|
);
|
|
|
}
|
|
|
},
|
|
|
// 是否是推荐职位的最后一个
|
|
|
handleRecordJobEnd(callback= ()=>{}) {
|
|
|
let that = this;
|
|
|
if (that.list.length > 0) {
|
|
|
// 初始化所有项为 false
|
|
|
that.list.forEach((item) => {
|
|
|
item.isRecordJobEnd = false;
|
|
|
});
|
|
|
|
|
|
// 找到最后一个符合条件的项(chat_type 为 markdown)
|
|
|
for (let i = that.list.length - 1; i >= 0; i--) {
|
|
|
if (that.list[i].chat_type === 'markdown') {
|
|
|
that.list[i].isRecordJobEnd = true;
|
|
|
break; // 找到后退出循环
|
|
|
}
|
|
|
}
|
|
|
|
|
|
callback();
|
|
|
}
|
|
|
},
|
|
|
handleSet(str, result = "", index, time) {
|
|
|
let that = this;
|
|
|
that.$nextTick(()=>{
|
|
|
that.isShowLeftLoading = false;
|
|
|
that.animate()
|
|
|
that.leftAnimate1 = false;
|
|
|
that.leftAnimate2 = false;
|
|
|
that.leftAnimate3 = false;
|
|
|
that.sendMsg = '';
|
|
|
that.$nextTick(()=>{
|
|
|
that.scrollToBottom(()=>{},'handleSet方法',300);
|
|
|
})
|
|
|
})
|
|
|
},
|
|
|
getRandomElements(array, count) {
|
|
|
let shuffled = array.slice();
|
|
|
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
|
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
|
}
|
|
|
return shuffled.slice(0, count);
|
|
|
},
|
|
|
|
|
|
animate($type = "heavy") {
|
|
|
const systemInfo = uni.getSystemInfoSync();
|
|
|
if(systemInfo.platform === 'ios'){
|
|
|
|
|
|
}else{
|
|
|
uni.vibrateShort({
|
|
|
type: $type,
|
|
|
fail(err) {
|
|
|
console.log("震动失败:", err);
|
|
|
},
|
|
|
});
|
|
|
}
|
|
|
},
|
|
|
clearMsg() {
|
|
|
this.sendMsg = "";
|
|
|
setTimeout(() => {
|
|
|
this.textareaHeight = 30;
|
|
|
}, 100);
|
|
|
},
|
|
|
goHotMore($item) {
|
|
|
let that = this;
|
|
|
if(this.hotObj && this.hotObj['sex']){
|
|
|
console.log('流程1')
|
|
|
uni.navigateTo({
|
|
|
url: `/root/other/channelPage?jobparams=${JSON.stringify(this.hotObj)}`,
|
|
|
});
|
|
|
}else{
|
|
|
console.log('流程2')
|
|
|
if(uni.getStorageSync('ls_obj') && JSON.parse(uni.getStorageSync('ls_obj'))){
|
|
|
that.isjob = true;
|
|
|
let itm = JSON.parse(uni.getStorageSync('ls_obj'));
|
|
|
that.hotObj['sex'] = itm['sex'];
|
|
|
that.hotObj['ageRangeStr'] = itm['ageRangeStr'];
|
|
|
that.hotObj['cityName'] = itm['cityName'];
|
|
|
that.hotObj['labelNames'] = itm['labelNames'];
|
|
|
}else{
|
|
|
console.log('流程3',$item)
|
|
|
uni.navigateTo({
|
|
|
url: `/root/other/channelPage`,
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
getReload($item) {
|
|
|
let that = this;
|
|
|
that.aiPage++;
|
|
|
that.list.push({
|
|
|
msg: "",
|
|
|
isGood: false,
|
|
|
isDown: false,
|
|
|
chat_type: "",
|
|
|
robotTag: 1,
|
|
|
});
|
|
|
that.$nextTick(()=>{
|
|
|
that.startLeftAnimate(()=>{
|
|
|
that.scrollToBottom(()=>{
|
|
|
that.list.forEach((ita,ina) => {
|
|
|
ita.opbtn = 0
|
|
|
})
|
|
|
that.list[that.list.length - 1].msg = $item.msg;
|
|
|
that.list[that.list.length - 1].chat_type = "markdown";
|
|
|
that.list[that.list.length - 1].robotTag = 1;
|
|
|
that.list[that.list.length - 1].ids = $item.ids;
|
|
|
that.list[that.list.length - 1].opbtn = 1;
|
|
|
that.getHomeList(that.list[that.list.length - 1].ids, (hotList) => {
|
|
|
that.list[that.list.length - 1].comList = [];
|
|
|
that.list[that.list.length - 1].comList = hotList;
|
|
|
|
|
|
let _art = [];
|
|
|
if (hotList && hotList.length > 0) {
|
|
|
_art = hotList.map(item => {
|
|
|
return {
|
|
|
'职位ID': item.id
|
|
|
}
|
|
|
})
|
|
|
} else {
|
|
|
_art = []
|
|
|
}
|
|
|
that.saveHistory(1, JSON.stringify(_art), (historyData) => {
|
|
|
that.list[that.list.length - 1].id = $item.id;
|
|
|
that.isSending = false;
|
|
|
that.handleSet(JSON.stringify(_art), "", 0, 300);
|
|
|
},'update');
|
|
|
}, $item.page,'more')
|
|
|
},'换一批',300);
|
|
|
});
|
|
|
})
|
|
|
},
|
|
|
startLeftAnimate(callback=()=>{}){
|
|
|
let that = this;
|
|
|
that.animate()
|
|
|
that.jobnum = 0;
|
|
|
that.isShowLeftLoading = true;
|
|
|
setTimeout(()=>{
|
|
|
that.animate()
|
|
|
that.leftAnimate1 = true;
|
|
|
that.scrollToBottom(()=>{
|
|
|
setTimeout(()=>{
|
|
|
that.animate()
|
|
|
that.leftAnimate1 = false;
|
|
|
console.log('endType',that.endType)
|
|
|
that.jobnum = that.getRandomNumber(100,500);
|
|
|
that.leftAnimate2 = true;
|
|
|
that.scrollToBottom(()=>{
|
|
|
setTimeout(()=>{
|
|
|
that.jobnum = that.getRandomNumber(500,2000);
|
|
|
setTimeout(()=>{
|
|
|
that.jobnum = that.getRandomNumber(2000,4000);
|
|
|
setTimeout(()=>{
|
|
|
that.jobnum = that.getRandomNumber(4000,8000);
|
|
|
setTimeout(()=>{
|
|
|
that.animate()
|
|
|
that.leftAnimate2 = false;
|
|
|
that.leftAnimate3 = true;
|
|
|
that.scrollToBottom(()=>{
|
|
|
setTimeout(()=>{
|
|
|
callback();
|
|
|
},800)
|
|
|
},'左3',10)
|
|
|
},800)
|
|
|
},400)
|
|
|
},400)
|
|
|
},400)
|
|
|
},'左2',10)
|
|
|
},800)
|
|
|
},'左1',10);
|
|
|
},800)
|
|
|
},
|
|
|
startRightAnimate(callback=()=>{}){
|
|
|
let that = this;
|
|
|
that.animate()
|
|
|
that.isShowRightLoading = true;
|
|
|
setTimeout(()=>{
|
|
|
that.animate()
|
|
|
that.rightAnimate1 = true;
|
|
|
that.scrollToBottom(()=>{
|
|
|
setTimeout(()=>{
|
|
|
that.animate()
|
|
|
that.rightAnimate1 = false;
|
|
|
that.rightAnimate2 = true;
|
|
|
that.scrollToBottom(()=>{
|
|
|
setTimeout(()=>{
|
|
|
callback();
|
|
|
},800)
|
|
|
},'右2',10)
|
|
|
},800)
|
|
|
},'右1',10);
|
|
|
},800)
|
|
|
},
|
|
|
getRandomNumber(min, max) {
|
|
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
|
},
|
|
|
handleLongPress($item) {
|
|
|
let that = this,
|
|
|
_array = [];
|
|
|
if($item.messageType == 99){
|
|
|
_array = ['删除'];
|
|
|
}else{
|
|
|
_array = ['删除','复制']
|
|
|
}
|
|
|
uni.showActionSheet({
|
|
|
itemList: _array,
|
|
|
success: function (res) {
|
|
|
if(res.tapIndex == 0){
|
|
|
that.handleDelete($item);
|
|
|
}else{
|
|
|
that.handleCopy($item);
|
|
|
}
|
|
|
},
|
|
|
fail: function (res) {
|
|
|
}
|
|
|
});
|
|
|
},
|
|
|
handleCopy($item){
|
|
|
uni.setClipboardData({
|
|
|
data: $item.msg,
|
|
|
success: () => {
|
|
|
uni.showToast({ title: '复制成功' });
|
|
|
},
|
|
|
fail: () => {
|
|
|
uni.showToast({ title: '复制失败', icon: 'none' });
|
|
|
}
|
|
|
});
|
|
|
},
|
|
|
handleDelete($item){
|
|
|
let that = this;
|
|
|
uni.showModal({
|
|
|
title: '提示',
|
|
|
content: '确定要删除此条消息吗?',
|
|
|
success: (res) => {
|
|
|
if (res.confirm) {
|
|
|
that.handleDelMsg($item);
|
|
|
}else{}
|
|
|
}
|
|
|
});
|
|
|
},
|
|
|
handleDelMsg($item){
|
|
|
let that = this;
|
|
|
if($item.id){
|
|
|
that.G.Post(
|
|
|
that.api.chat_delMsg, {
|
|
|
deleteAll:0,
|
|
|
ids: $item.id,
|
|
|
classify:$item.classify,
|
|
|
conversationId: that.cid,
|
|
|
},() => {
|
|
|
that.list.splice(that.list.findIndex(item => item.id === $item.id), 1);
|
|
|
// that.$nextTick(()=>{
|
|
|
// that.scrollToBottom(()=>{},'删除消息',300);
|
|
|
// })
|
|
|
}
|
|
|
)
|
|
|
}else{
|
|
|
uni.showToast({
|
|
|
icon:'none',
|
|
|
title:'请重试'
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
};
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss">
|
|
|
.g_c_b {
|
|
|
color: #bbb;
|
|
|
}
|
|
|
|
|
|
.p-home-chat {
|
|
|
// background-color: #f5f5f5;
|
|
|
min-height: 100vh;
|
|
|
|
|
|
.g_bg_f0a {
|
|
|
background-color: #fe0000;
|
|
|
}
|
|
|
|
|
|
.chat-content {
|
|
|
width: calc(100% - 0px);
|
|
|
margin: 0 auto;
|
|
|
padding-bottom: 0px;
|
|
|
|
|
|
.chat-left {
|
|
|
padding: 0 10px;
|
|
|
|
|
|
.msg {
|
|
|
border-radius: 12px;
|
|
|
line-height: 1.5;
|
|
|
// letter-spacing: 1.5px;
|
|
|
word-break: break-all;
|
|
|
min-height: 25px;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.chat-right {
|
|
|
padding: 0 10px;
|
|
|
|
|
|
.msg {
|
|
|
border-radius: 12px;
|
|
|
line-height: 1.5;
|
|
|
// letter-spacing: 1.5px;
|
|
|
word-break: break-all;
|
|
|
min-height: 25px;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.chat-operate {
|
|
|
position: fixed;
|
|
|
width: 100%;
|
|
|
left: 0;
|
|
|
bottom: 0;
|
|
|
z-index: 1;
|
|
|
padding-bottom: calc(24rpx + constant(safe-area-inset-bottom));
|
|
|
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
|
|
padding-top: 12px;
|
|
|
// background-color: #f5f5f5;
|
|
|
|
|
|
.m-input {
|
|
|
width: calc(100% - 20px);
|
|
|
margin: 0 auto;
|
|
|
border-radius: 40px;
|
|
|
height: 56px;
|
|
|
font-size: 16px;
|
|
|
}
|
|
|
|
|
|
input {
|
|
|
height: 56px;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.btn-share {
|
|
|
position: absolute;
|
|
|
right: 0;
|
|
|
top: 0;
|
|
|
z-index: 1;
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
opacity: 0;
|
|
|
}
|
|
|
|
|
|
.longpress-top-mask {
|
|
|
position: fixed;
|
|
|
left: 0;
|
|
|
// bottom: 120px;
|
|
|
width: 100vw;
|
|
|
// height: 170px;
|
|
|
z-index: 99;
|
|
|
background-color: transparent;
|
|
|
box-shadow: 0 -5px 10px rgba(245, 245, 245, 0.5);
|
|
|
}
|
|
|
|
|
|
.longpress-bottom-mask {
|
|
|
position: fixed;
|
|
|
left: 0;
|
|
|
bottom: 0;
|
|
|
width: 100vw;
|
|
|
// height: 170px;
|
|
|
z-index: 99;
|
|
|
background-color: rgba(245, 245, 245, 0.5);
|
|
|
box-shadow: 0 -5px 10px rgba(245, 245, 245, 0.5);
|
|
|
}
|
|
|
|
|
|
.longpress-bottom-down {
|
|
|
position: fixed;
|
|
|
left: 0;
|
|
|
bottom: 0;
|
|
|
width: 100vw;
|
|
|
// height: 170px;
|
|
|
z-index: 99;
|
|
|
background-color: #3578f6;
|
|
|
box-shadow: 0 -5px 10px rgba(245, 245, 245, 0.5);
|
|
|
}
|
|
|
|
|
|
@keyframes voi_animate {
|
|
|
0% {
|
|
|
height: 50%;
|
|
|
background-color: #ffffff;
|
|
|
}
|
|
|
|
|
|
20% {
|
|
|
height: 50%;
|
|
|
background-color: #ffffff;
|
|
|
}
|
|
|
|
|
|
50% {
|
|
|
height: 100%;
|
|
|
background-color: #ffffff;
|
|
|
}
|
|
|
|
|
|
80% {
|
|
|
height: 50%;
|
|
|
background-color: #ffffff;
|
|
|
}
|
|
|
|
|
|
100% {
|
|
|
height: 50%;
|
|
|
background-color: #ffffff;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.column-voice {
|
|
|
width: 100%;
|
|
|
height: 22px;
|
|
|
overflow: hidden;
|
|
|
// max-width: calc(100% - 140px);
|
|
|
max-width: calc(100% - 140px);
|
|
|
margin: 0 auto;
|
|
|
|
|
|
.column-item {
|
|
|
width: 3px;
|
|
|
height: 100%;
|
|
|
margin-left: 6px;
|
|
|
border-radius: 10px;
|
|
|
background-color: #ffffff;
|
|
|
vertical-align: middle;
|
|
|
display: inline-block;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* 容器样式 */
|
|
|
.loader {
|
|
|
display: flex;
|
|
|
}
|
|
|
|
|
|
/* 单个点的样式 */
|
|
|
.dot {
|
|
|
width: 10px;
|
|
|
height: 10px;
|
|
|
margin: 0 2px;
|
|
|
border-radius: 50%;
|
|
|
background-color: #666;
|
|
|
animation: dotPulse 1s infinite ease-in-out;
|
|
|
}
|
|
|
|
|
|
/* 容器样式 */
|
|
|
.loades {
|
|
|
display: flex;
|
|
|
}
|
|
|
|
|
|
/* 单个点的样式 */
|
|
|
.dos {
|
|
|
width: 10px;
|
|
|
height: 10px;
|
|
|
margin: 0 2px;
|
|
|
border-radius: 50%;
|
|
|
background-color: #fff;
|
|
|
animation: dotPulse 1s infinite ease-in-out;
|
|
|
}
|
|
|
|
|
|
/* 动画定义 */
|
|
|
@keyframes dotPulse {
|
|
|
|
|
|
0%,
|
|
|
80%,
|
|
|
100% {
|
|
|
transform: scale(0.9);
|
|
|
}
|
|
|
|
|
|
40% {
|
|
|
transform: scale(1.1);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* 第二个点的延时 */
|
|
|
.dot:nth-child(2) {
|
|
|
animation-delay: -0.33s;
|
|
|
}
|
|
|
|
|
|
/* 第三个点的延时 */
|
|
|
.dot:nth-child(3) {
|
|
|
animation-delay: -0.66s;
|
|
|
}
|
|
|
|
|
|
/* 第二个点的延时 */
|
|
|
.dos:nth-child(2) {
|
|
|
animation-delay: -0.33s;
|
|
|
}
|
|
|
|
|
|
/* 第三个点的延时 */
|
|
|
.dos:nth-child(3) {
|
|
|
animation-delay: -0.66s;
|
|
|
}
|
|
|
|
|
|
.container {
|
|
|
// position: relative;
|
|
|
// overflow-y: auto; /* 设置滚动条 */
|
|
|
// height: 100%; /* 设置容器高度 */
|
|
|
}
|
|
|
|
|
|
textarea {
|
|
|
resize: none;
|
|
|
/* 禁止手动调整大小 */
|
|
|
overflow: hidden;
|
|
|
/* 隐藏超出部分 */
|
|
|
width: 100%;
|
|
|
/* 设置宽度 */
|
|
|
}
|
|
|
|
|
|
.hasTopPadding {
|
|
|
padding-top: 8px !important;
|
|
|
box-sizing: content-box !important;
|
|
|
}
|
|
|
|
|
|
.biggerSize::after {
|
|
|
content: "";
|
|
|
/* display: inline-block; */
|
|
|
width: 60px;
|
|
|
height: 60px;
|
|
|
position: absolute;
|
|
|
left: 50%;
|
|
|
top: 50%;
|
|
|
z-index: 99;
|
|
|
transform: translate(-50%, -50%);
|
|
|
}
|
|
|
|
|
|
.markgroup {
|
|
|
height: 56px;
|
|
|
border-bottom-left-radius: 8px;
|
|
|
border-bottom-right-radius: 8px;
|
|
|
}
|
|
|
.g_fs_32 {
|
|
|
font-size: 32px;
|
|
|
}
|
|
|
</style> |