AI销管
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

561 行
13 KiB

  1. <template>
  2. <view class="pages">
  3. <digital-human :width="0" rootStyle="position: fixed; left: 50%; top: 100px; transform: translateX(-50%);">
  4. </digital-human>
  5. <image class="bgs" src="https://qufang.oss-cn-beijing.aliyuncs.com/serverBg.jpg" mode="" />
  6. <view class="server-box">
  7. <view class="server-head">
  8. <image class="servehead" src="@/static/image/servehead.png" mode="" />
  9. <image class="voiceing" src="@/static/image/voiceing.png" mode="" />
  10. </view>
  11. <!-- 消息列表 -->
  12. <scroll-view class="scroll-box" scroll-y :scroll-into-view="scrollId">
  13. <!-- 循环体 -->
  14. <block v-for="(item, index) in messageList">
  15. <!-- 左侧 -->
  16. <view :class="item.direction" :key="index" :id="`scrollId${index}`">
  17. <view class="msg-text">
  18. <view class="msg-texts">
  19. {{ item.text }}
  20. </view>
  21. <image @tap="rePlayText(item.text)" v-if="item.direction == 'lside' && index !== 0 "
  22. class="voiceplay" src="@/static/image/voiceplay.png" mode="" />
  23. </view>
  24. <view class="timer">{{ item.time }}</view>
  25. </view>
  26. </block>
  27. </scroll-view>
  28. <view class="send-box">
  29. <view v-if="sendType" class="input-box">
  30. <input v-model="inputText" class="inputs" placeholder-class="placestyle" type="text"
  31. confirm-type="send" @confirm="sendChat" placeholder="输入你想咨询的问题或点击右边的麦克风和我聊天" />
  32. </view>
  33. <view v-else class="voice-box" @touchstart="touchStart" @touchend="touchEnd">
  34. <text>{{ voiceState }}</text>
  35. </view>
  36. <view class="change-type">
  37. <image @tap="changeSendType(false)" v-if="sendType" src="@/static/image/voiceInput.png" mode="" />
  38. <image @tap="changeSendType(true)" v-else src="@/static/image/textInput.png" mode="" />
  39. </view>
  40. </view>
  41. </view>
  42. <view v-if="recording" class="luyin">
  43. <image src="@/static/image/recordingIcon.png" mode="" />
  44. <text>正在录音...</text>
  45. </view>
  46. </view>
  47. </template>
  48. <script>
  49. const pluginConfig = { // 数字人配置
  50. "asrConfig": {
  51. "class": "TencentASR",
  52. "secretKey": "",
  53. "secretId": "AKIDyvwuvrfikhYK1dDj9Vlv154zmVAjFdt0",
  54. "appId": 1310500600,
  55. "partnerId": "0002",
  56. "signatureUrl": "https://commercial-integration-asr-int-api.xiaoice.com/api/v3/asr/tencentSignature",
  57. "hotWordId": "2ab7be50d11f11ecbfd6525400aec391",
  58. "silenceTime": 240,
  59. "engineModelType": "16k_zh",
  60. "needVad": 1,
  61. "filterDirty": 1,
  62. "filterModal": 0,
  63. "filterPunc": 1,
  64. "vadSilenceTime": 240,
  65. "convertNumMode": 1,
  66. "wordInfo": 2
  67. },
  68. "character": "chenzheling-yellow",
  69. "characterConfig": {
  70. "characters": {
  71. "chenzheling-yellow": {
  72. "name": "chenzheling-yellow-half",
  73. "id": "chenzheling-yellow",
  74. "frameWidth": 740,
  75. "frameHeight": 1260,
  76. "faceWidth": 190,
  77. "faceHeight": 300,
  78. "facePositionLeft": 450,
  79. "facePositionTop": 550,
  80. "facePositionLeftOffset": -180,
  81. "facePositionTopOffset": -430,
  82. "frameRate": 25,
  83. "defaultIdle": "A-idle",
  84. "idles": {
  85. "A-idle": {
  86. "id": "A-idle",
  87. "version": 6,
  88. "frameImageLength": 227,
  89. "frameImageType": "webp",
  90. "resetFrameSingleNum": 8,
  91. "resetFrames": [7, 15, 23, 31, 39, 47, 55, 63, 71, 79, 87, 95, 103, 111, 119, 127, 135, 143, 151, 159, 167, 175, 183, 191, 199, 207, 215, 223],
  92. "frameImageBackupType": "png"
  93. }
  94. },
  95. "gestures": {},
  96. "crops": {}
  97. }
  98. },
  99. "sourcePath": "https://commercial-cdn.xiaoice.com/assets/images/characters",
  100. "batch": 16,
  101. "minLoadFrameNum": 100
  102. },
  103. "chatConfig": {
  104. "class": "CXHubChat",
  105. "url": "https://commercial-fab.xiaoice.com/api/hub/organizations/org-qufangwang/agents/64af7b6d6e1ba4813c4bd6ae/environments/draft/sessions",
  106. "defaultReply": [{
  107. "text": "对不起,我没听清~",
  108. "gesture": ""
  109. }],
  110. "csChatUrl": "https://commercial-fab.xiaoice.com/api/hub/organizations/org-qufangwang/characters//sessions"
  111. },
  112. "logConfig": {
  113. "class": "AliTraceLogger",
  114. "metaData": {
  115. "role": "prod",
  116. "organizationId": "org-qufangwang",
  117. "partnerId": "dh-api",
  118. "agentId": "64af7b6d6e1ba4813c4bd6ae"
  119. }
  120. },
  121. "talkConfig": {
  122. "class": "CarouselTalk",
  123. "url": "https://commercial-fab.xiaoice.com",
  124. "options": {
  125. "path": "/api/v1/carousel/avatars/chenzheling-yellow/socket.io",
  126. "wsPath": "/api/v1/carousel/avatars/chenzheling-yellow/websocket",
  127. "transports": ["websocket", "polling"],
  128. "query": "?image_format=.webp&voice_id=64af7b6da9d616bd022f6b69"
  129. },
  130. "maxRetryTime": 5,
  131. "retryTimeInterval": 100
  132. }
  133. }
  134. const plugin = requirePlugin('digital-human-plugin').api; // 数字人实例
  135. const plugins = requirePlugin("WechatSI");
  136. let manager = plugins.getRecordRecognitionManager();
  137. export default {
  138. data() {
  139. return {
  140. sendType: true, // true 为文字输入 false 为语音输入
  141. messageList: [], // 消息列表
  142. inputText: '', // 文字输入
  143. voiceState: '按住 说话', //
  144. recording: false, // 展示录音提示框
  145. scrollId: '', // 默认不滚动
  146. };
  147. },
  148. onLoad() {
  149. uni.showLoading({
  150. title: '加载中...',
  151. })
  152. plugin.init({
  153. ...pluginConfig,
  154. onReceivedAsrText: (text) => {},
  155. onSuccess: (res) => {
  156. console.log(res, 'onSuccess')
  157. uni.hideLoading()
  158. let text = 'Hi~我是专业房产顾问嘉欣,有问题可以问我呦,试试说句“你好”和我打个招呼吧'
  159. plugin.human?.talkAsync(text)
  160. this.messageList.push({
  161. text: text,
  162. direction: 'lside'
  163. })
  164. },
  165. onFailed: res => {
  166. uni.hideLoading()
  167. console.log(res, 'onFailed')
  168. },
  169. onError: (res) => {
  170. uni.hideLoading()
  171. console.log(res, 'onError')
  172. }
  173. })
  174. this.initRecord()
  175. },
  176. onShow() {
  177. // #ifdef MP-WEIXIN
  178. // 内存告警处理
  179. wx.onMemoryWarning((level) => {
  180. plugin.clearCache()
  181. })
  182. wx.setInnerAudioOption({
  183. // IOS静音键的情况下允许播放声音
  184. obeyMuteSwitch: false,
  185. // 播放声音时中断其他app端声音
  186. mixWithOther: false,
  187. })
  188. // #endif
  189. },
  190. onHide() {},
  191. methods: {
  192. /**
  193. * 切换输入方式
  194. */
  195. changeSendType(e) {
  196. this.sendType = e
  197. },
  198. // // 模拟聊天回复接口
  199. async getReply(text) {
  200. console.log(this.setUid())
  201. await uni.request({
  202. url: `${pluginConfig.chatConfig.url}/${this.setUid()}`,
  203. method: 'POST',
  204. data: {
  205. "text": text,
  206. "currentSceneInfo": {
  207. "sceneId": ""
  208. }
  209. },
  210. success: async (res) => {
  211. const replay = res.data.sceneInfo[0].audioText[0]
  212. await plugin.human?.interrupt()
  213. let arr = []
  214. arr.push({
  215. direction: 'rside',
  216. text: text,
  217. time: this.getTimeNow()
  218. })
  219. arr.push({
  220. direction: 'lside',
  221. text: res.data.sceneInfo[0].audioText[0],
  222. time: this.getTimeNow()
  223. })
  224. this.messageList = [...this.messageList, ...arr]
  225. this.inputText = ''
  226. this.scrollId = `scrollId${this.messageList.length-1}`
  227. await plugin.human?.talkAsync(replay)
  228. }
  229. })
  230. },
  231. /**
  232. * 重播当前对话
  233. */
  234. async rePlayText(e) {
  235. console.log(e)
  236. // 暂停当前对话(如果有)
  237. await plugin.human?.interrupt()
  238. await plugin.human?.talkAsync(e)
  239. },
  240. /**
  241. * 获取当前时间
  242. */
  243. getTimeNow() {
  244. let now = new Date();
  245. let year = now.getFullYear(); //获取完整的年份(4位,1970-????)
  246. let month = now.getMonth() + 1; //获取当前月份(0-11,0代表1月)
  247. let today = now.getDate(); //获取当前日(1-31)
  248. let hour = now.getHours(); //获取当前小时数(0-23)
  249. let minute = now.getMinutes(); //获取当前分钟数(0-59)
  250. let second = now.getSeconds(); //获取当前秒数(0-59)
  251. let nowTime = ''
  252. nowTime = year + '-' + this.fillZero(month) + '-' + this.fillZero(today) + ' ' + this.fillZero(hour) +
  253. ':' +
  254. this.fillZero(minute) + ':' + this.fillZero(second)
  255. return nowTime
  256. },
  257. // 补零
  258. fillZero(str) {
  259. var realNum;
  260. if (str < 10) {
  261. realNum = '0' + str;
  262. } else {
  263. realNum = str;
  264. }
  265. return realNum;
  266. },
  267. /**
  268. * 发送文字
  269. */
  270. async sendChat(e) {
  271. await this.getReply(e.detail.value)
  272. },
  273. /**
  274. * 长按识别
  275. */
  276. touchStart() {
  277. this.recording = true
  278. manager.start({
  279. duration: 60000,
  280. lang: "zh_CN"
  281. });
  282. },
  283. /**
  284. * 长按结束
  285. * */
  286. touchEnd() {
  287. this.recording = false
  288. manager.stop();
  289. },
  290. /**
  291. * 初始化语音识别回调
  292. * 绑定语音播放开始事件
  293. */
  294. initRecord() {
  295. manager.onStart = (res) => {
  296. this.voiceState = "正在录音"
  297. };
  298. //有新的识别内容返回,则会调用此事件
  299. manager.onRecognize = (res) => {}
  300. // 识别结束事件
  301. manager.onStop = async (res) => {
  302. if (res.result) {
  303. await this.getReply(res.result)
  304. }
  305. this.voiceState = "按住 说话"
  306. }
  307. // 识别错误事件
  308. manager.onError = (res) => {}
  309. },
  310. // 简单生成uid
  311. setUid() {
  312. let uid = new Date().getTime() + Math.random().toString(36).substr(2);
  313. return uid
  314. },
  315. }
  316. };
  317. </script>
  318. <style lang="scss" scoped>
  319. /* pages/intelligentvoiceassistant/intelligentvoiceassistant.wxss */
  320. view {
  321. box-sizing: border-box;
  322. }
  323. .pages {
  324. position: relative;
  325. width: 100vw;
  326. height: 100vh;
  327. display: flex;
  328. flex-direction: column;
  329. justify-content: flex-end;
  330. }
  331. .bgs {
  332. position: absolute;
  333. z-index: 0;
  334. width: 100%;
  335. height: 100%;
  336. }
  337. .server-box {
  338. position: relative;
  339. z-index: 10;
  340. width: calc(100%-1rpx);
  341. height: 658rpx;
  342. background: rgba(0, 0, 0, 0.5);
  343. backdrop-filter: saturate(180%) blur(20px);
  344. border-radius: 32rpx 32rpx 0 0;
  345. border: 1rpx solid #FFFFFF;
  346. }
  347. .server-head {
  348. margin: -130rpx 0 0 0;
  349. padding: 0 32rpx;
  350. width: 100%;
  351. display: flex;
  352. align-items: flex-end;
  353. }
  354. .servehead {
  355. width: 192rpx;
  356. height: 192rpx;
  357. }
  358. .voiceing {
  359. margin-left: 12rpx;
  360. width: 78rpx;
  361. height: 50rpx;
  362. }
  363. /*
  364. .close {
  365. width: ;
  366. } */
  367. .scroll-box {
  368. width: 100%;
  369. height: calc(658rpx - 192rpx + 130rpx - 104rpx);
  370. }
  371. .lside {
  372. display: flex;
  373. flex-direction: column;
  374. align-items: flex-start;
  375. }
  376. .rside {
  377. display: flex;
  378. flex-direction: column;
  379. align-items: flex-end;
  380. }
  381. .msg-texts {
  382. margin: 36rpx 32rpx 12rpx;
  383. padding: 16rpx 28rpx;
  384. background: #fff;
  385. border-radius: 20rpx;
  386. }
  387. .lside .voiceplay {
  388. margin: 0 32rpx 16rpx 16rpx;
  389. }
  390. .lside .msg-text {
  391. display: flex;
  392. align-items: flex-end;
  393. }
  394. .lside .msg-text .msg-texts {
  395. margin: 36rpx 16rpx 12rpx 32rpx;
  396. }
  397. .timer {
  398. margin: 0 32rpx;
  399. font-size: 24rpx;
  400. font-family: PingFangSC-Regular, PingFang SC;
  401. font-weight: 400;
  402. color: #FFFFFF;
  403. line-height: 33rpx;
  404. }
  405. .rside .msg-texts {
  406. background: #2671E2;
  407. color: #fff;
  408. }
  409. .send-box {
  410. padding: 0 24rpx;
  411. width: 100%;
  412. height: 104rpx;
  413. background: #5D6168;
  414. display: flex;
  415. align-items: center;
  416. /* justify-content: center; */
  417. }
  418. .change-type {
  419. margin: 0 0 0 24rpx;
  420. width: 72rpx;
  421. height: 72rpx;
  422. }
  423. .change-type image {
  424. width: 72rpx;
  425. height: 72rpx;
  426. }
  427. .input-box,
  428. .voice-box {
  429. width: 606rpx;
  430. height: 72rpx;
  431. background: #FFFFFF;
  432. border-radius: 12rpx;
  433. }
  434. .voice-box {
  435. display: flex;
  436. justify-content: center;
  437. align-items: center;
  438. }
  439. .voice-box text {
  440. font-size: 28rpx;
  441. font-family: PingFangSC-Medium, PingFang SC;
  442. font-weight: 500;
  443. color: #000000;
  444. line-height: 40rpx;
  445. }
  446. .inputs {
  447. padding: 0 28rpx;
  448. box-sizing: border-box;
  449. width: 100%;
  450. height: 100%;
  451. font-size: 22rpx;
  452. font-family: PingFangSC-Regular, PingFang SC;
  453. font-weight: 400;
  454. line-height: 30rpx;
  455. }
  456. .placestyle {
  457. font-size: 22rpx;
  458. font-family: PingFangSC-Regular, PingFang SC;
  459. font-weight: 400;
  460. color: #CCCCCC;
  461. line-height: 30rpx;
  462. }
  463. .luyin {
  464. position: fixed;
  465. left: 50%;
  466. top: 50%;
  467. transform: translate(-50%, -50%);
  468. z-index: 100;
  469. width: 280rpx;
  470. height: 258rpx;
  471. background: rgba(0, 0, 0, 0.7);
  472. border-radius: 20rpx;
  473. display: flex;
  474. flex-direction: column;
  475. justify-content: center;
  476. align-items: center;
  477. }
  478. .luyin image {
  479. margin: 0 0 20rpx 0;
  480. width: 46rpx;
  481. height: 62rpx;
  482. }
  483. .luyin text {
  484. font-size: 28rpx;
  485. font-family: PingFangSC-Regular, PingFang SC;
  486. font-weight: 400;
  487. color: #FFFFFF;
  488. line-height: 40rpx;
  489. }
  490. .voiceplay {
  491. flex-shrink: 0;
  492. width: 64rpx;
  493. height: 64rpx;
  494. }
  495. </style>