Browse Source

初版提交

刘琳琳 3 days ago
parent
commit
9a6d6c74f5

+ 5 - 0
.idea/.gitignore

@@ -0,0 +1,5 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/

+ 12 - 0
.idea/ampe-digital-human-demo.iml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/temp" />
+      <excludeFolder url="file://$MODULE_DIR$/.tmp" />
+      <excludeFolder url="file://$MODULE_DIR$/tmp" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/ampe-digital-human-demo.iml" filepath="$PROJECT_DIR$/.idea/ampe-digital-human-demo.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 3 - 0
app.acss

@@ -0,0 +1,3 @@
+page {
+  background: #f7f7f7;
+}

+ 11 - 0
app.js

@@ -0,0 +1,11 @@
+App({
+  onLaunch(options) {
+    // 第一次打开
+    // options.query == {number:1}
+    console.info('App onLaunch');
+  },
+  onShow(options) {
+    // 从后台被 scheme 重新打开
+    // options.query == {number:1}
+  },
+});

+ 18 - 0
app.json

@@ -0,0 +1,18 @@
+{
+  "pages": [
+    "pages/index/index",
+    "pages/avatar-demo/avatar-demo"
+  ],
+  "window": {
+    "defaultTitle": "",
+    "transparentTitle": "always",
+    "titlePenetrate": "YES",
+    "allowsBounceVertical": "NO"
+  },
+  "behavior": {
+    "requestReferrerStrategy": "page",
+    "requestReferrerStyle": "full",
+    "requestDefaultEnableCookie": true,
+    "connectSocketDefaultMultiple": true
+  }
+}

+ 3 - 0
mini.project.json

@@ -0,0 +1,3 @@
+{
+  "format": 2
+}

+ 63 - 0
pages/avatar-demo/avatar-demo.acss

@@ -0,0 +1,63 @@
+.rtcroom-full-screen {
+  width: 100%;
+}
+
+.main-center-fullscreen {
+  position: fixed;
+  top: 0;
+  bottom: 0;
+  right: 0;
+  left: 0;
+  z-index: 999;
+  /* width: 7.5rem;
+  height: 16.24rem; */
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  box-sizing: border-box;
+  background-image: linear-gradient(-33deg, #0b164c 7%, #1b2c7e 74%);
+}
+
+.chat-box {
+  position: absolute;
+  z-index: 9999;
+  width: 90%;
+  min-height: 60px;
+  max-height: 300px;
+  background: rgba(255, 255, 255, 0.46);
+  bottom: 180px;
+  padding: 5px;
+  left: 50%;
+  transform: translateX(-50%);
+  display: flex;
+  flex-direction: column;
+  /*padding: ;*/
+  border-radius: 10px;
+  overflow: scroll;
+
+}
+
+.btnContainer {
+  position: absolute;
+  z-index: 9999;
+  width: calc(90% + 10px);
+  height: 120px;
+  bottom: 50px;
+  left: 50%;
+  transform: translateX(-50%);
+  display: flex;
+  flex-wrap: wrap;
+  /*justify-content: center;*/
+  align-items: center;
+}
+
+.btn-avatar {
+  /* position:absolute; */
+  width: calc((100% - 40px) / 4);
+  margin: 5px;
+  border-radius: 10px;
+  font-size: 13px;
+  /* z-index: 9991; */
+}
+
+

+ 40 - 0
pages/avatar-demo/avatar-demo.axml

@@ -0,0 +1,40 @@
+<view>
+  <view class="{{'main-center-fullscreen'}}">
+    <rtc-room
+      class="{{'rtcroom-full-screen'}}"
+      id="{{elementId}}"
+      roomId="{{webrtcInfo.roomId}}"
+      token="{{webrtcInfo.rtoken}}"
+      userId="{{webrtcInfo.uid}}"
+      signature="{{webrtcInfo.signature}}"
+
+      extraInfo="{{extraInfo}}"
+      enable-camera="{{false}}"
+      mute="{{true}}"
+      onRoomInfo="onRoomInfo"
+      onEnterRoom = "onEnterRoom"
+      onError="onError"
+      onFirstRender = "onFirstRender"
+      onReceiveMessage = "onReceiveMessage"
+
+    />
+
+    <view class="chat-box">
+      <scroll-view  id="scrollView" scroll-y="true" style="max-height:300px" scroll-top="{{scrollTop}}">
+        <block a:for="{{chatList}}">
+          <view id="message-{{index}}" style="line-height: 30px;word-wrap: break-word;color: {{item.type== 0?'red':''}}">{{['','Q:','A:'][item.type]}}{{item.text}}</view>
+        </block>
+      </scroll-view>
+    </view>
+
+    <view class="btnContainer">
+      <button type="default" class="btn-avatar" onTap="startAsr" plain>开始录音</button>
+      <button type="default" class="btn-avatar" onTap="interrupt" plain>打断</button>
+      <button type="default" class="btn-avatar" onTap="stopAsr" plain>停止录音</button>
+      <button type="default" class="btn-avatar" onTap="onClear" plain>清空</button>
+      <button type="default" class="btn-avatar" style="flex:1" onTap="query2" plain>你是谁</button>
+      <button type="default" class="btn-avatar" style="flex:1" onTap="query1" plain>今天天气怎么样</button>
+<!--      <button type="default" class="btn-avatar" onTap="stopLive" plain>关闭流服务</button>-->
+    </view>
+  </view>
+</view>

+ 318 - 0
pages/avatar-demo/avatar-demo.js

@@ -0,0 +1,318 @@
+import AvatarllmService from '../../util/ant-avatar-tinyapp-llm-service.min.js';
+
+Page({
+  data: {
+    // rtc参数
+    elementId:'myRtcroom',
+    "webrtcInfo": {
+      "roomId": "",
+      "rtoken": "",
+      "uid": "",
+      "signature": ""
+    },
+
+    // 数字人参数
+    avartarParam:{
+      // tenantCode:"NpZ_5pv1", // 请联系蚂蚁数字人团队获取
+      // interactCode:"8f94744e717347bbaa0e4a7cc0389e68", // 请联系蚂蚁数字人团队获取
+      tenantCode:"hanghui", // 请联系蚂蚁数字人团队获取
+      interactCode:"177d745998de4c1f90159a5a3de8e1d8", // 请联系蚂蚁数字人团队获取
+      interactiveConfig:{},
+      streamId:'',
+    },
+    llmService:null,
+    chatList: [],
+    scrollTop: 0
+  },
+
+  onLoad() {
+      // 初始化数字人服务
+      this.initLLMService();
+  },
+
+  onUnload(){
+    if (this.data.avartarParam.streamId) {
+      this.stopLive();
+    }
+  },
+
+  /*************************** Avatar  ******************************/
+  initLLMService() {
+    // 新建 llmService
+    this.data.llmService = new AvatarllmService(
+
+      this.data.avartarParam.tenantCode,
+      this.data.avartarParam.interactCode,
+      this.data.elementId,
+      this,
+      null,
+      'cn',
+    );
+
+    // 获取交互配置
+    this.data.llmService.init((result) => {
+      console.log(result)
+      if (result && result.interactiveConfigResult) {
+        const interactiveConfig = result.interactiveConfigResult
+        this.data.avartarParam.interactiveConfig = interactiveConfig
+
+        // 开播
+        this.startLive();
+      }
+    });
+
+    // ----------------------- Event ----------------------------
+    this.data.llmService.onInitSuccess = (streamId) => {
+      console.log('【Lynn】init success, avatar ready, streamId【流服务初始化成功的回调,此时已拉流成功。建议此回调之后展示数字人对话的播放器】: ', streamId);
+    }
+
+    this.data.llmService.onASRTextFinish = (text) => {
+      // 停止录音
+      if (!text) return;
+      let lastQ = ( this.data.chatList || []).at(-1)
+      if (lastQ && lastQ.type == 1 && !lastQ.isEnd) {
+        let newArr = this.data.chatList
+        let lastIndex = this.data.chatList.length - 1
+        newArr[lastIndex] = {
+          ...newArr[lastIndex],
+          text: text,
+          isEnd: true
+        }
+        this.updateChat(newArr)
+      }
+      console.log('【Lynn】onASRTextFinish【语音识别完成,识别到的最终文本】: ', text);
+
+      this.stopAsr();
+      // this.interrupt()
+      this.sendText(text);
+    };
+
+    this.data.llmService.onASRTextChange = (text) => {
+      let lastQ =( this.data.chatList || []).at(-1) || {}
+      if (lastQ && lastQ.type == 1 && !lastQ.isEnd) {
+        let newArr = this.data.chatList
+        let lastIndex = this.data.chatList.length - 1
+        newArr[lastIndex] = {
+          ...newArr[lastIndex],
+          text
+        }
+        this.updateChat(newArr)
+      } else {
+        let list = this.data.chatList.concat([{
+          type: 1,
+          text,
+          isEnd: false
+        }])
+        this.updateChat(list)
+      }
+      console.log('【Lynn】onASRTextChange【语音识别过程中,识别到的中间文本】: ', text);
+    };
+
+    this.data.llmService.onRecordingStart = () => {
+      let list = this.data.chatList.concat([{
+        type: 0,
+        text: '....开始录音的回调,麦克风已开启成功....',
+        isEnd: true
+      }])
+      this.updateChat(list)
+      console.log('【Lynn】onRecordingStart【开始录音的回调,麦克风已开启成功】');
+    };
+
+    this.data.llmService.onRecordingStop = (
+
+    ) => {
+      let list = this.data.chatList.concat([{
+        type: 0,
+        text: '....停止录音的回调,麦克风已关闭....',
+        isEnd: true
+      }])
+      this.updateChat(list)
+      console.log('【Lynn】onRecordingStop【停止录音的回调,麦克风已关闭】');
+    };
+
+    // 收到数字人对话的结果
+    this.data.llmService.onAvatarMessage = (result) => {
+      console.log('【onAvatarMessage】',result)
+      const text = result.text;
+      let lastA =( this.data.chatList || []).at(-1)
+      if (lastA && lastA.type == 2 && !lastA.isEnd) {
+        let newArr = this.data.chatList
+        let lastIndex = this.data.chatList.length - 1
+        newArr[lastIndex] = {
+          ...newArr[lastIndex],
+          text: (newArr[lastIndex].text || '') + text
+        }
+        this.updateChat(newArr)
+      } else {
+        let list = this.data.chatList.concat([{
+          type: 2,
+          text,
+          isEnd: false
+        }])
+        this.updateChat(list)
+      }
+      let output = text;
+      console.log('【数字人返回的对话结果,分段返回,会多次调用。--------------开始--------------】:', text)
+      if (text.includes('$$')) {
+        output = text.replace('$$', '');
+      }
+      console.log('【Lynn】output:', output);
+      console.log('【数字人返回的对话结果,分段返回,会多次调用。--------------结束--------------】')
+    };
+
+    this.data.llmService.onAvatarPlayBegin = () => {
+      console.log('【Lynn】onAvatarPlayBegin 【数字⼈播报开始】');
+    };
+
+    this.data.llmService.onAvatarPlayEnd = () => {
+      let newArr = this.data.chatList
+      let lastIndex = this.data.chatList.length - 1
+      newArr[lastIndex] = {
+        ...newArr[lastIndex],
+        isEnd: true
+      }
+      this.updateChat(newArr)
+      console.log('【Lynn】onAvatarPlayEnd 【数字⼈播报结束】');
+    };
+
+    // 设置静默超时的时间,单位是 秒
+    // this.data.llmService.overtimeInterval = 30
+    this.data.llmService.onOverTimeTrigger = (streamId) => {
+      console.log('【Lynn】onStopLiveSuccess 【静默超时的回调】');
+      let list = this.data.chatList.concat([{
+        type: 0,
+        text: '....静默超时....',
+        isEnd: true
+      }])
+      this.updateChat(list)
+      this.stopLive()
+    };
+  },
+
+  // 开启流服务
+  startLive(){
+    this.data.llmService.startLive((res)=>{
+      if(res){
+        // 保存下streamID
+        if(res.streamId){
+          this.data.avartarParam.streamId = res.streamId
+        }
+
+        // 设置下webrtcInfo参数
+        if (res.webrtcInfo) {
+          this.setData({
+            webrtcInfo: res.webrtcInfo,
+          });
+          let list = this.data.chatList.concat([{
+            type: 0,
+            text: '....开启流服务....',
+            isEnd: true
+          }])
+          this.updateChat(list)
+          console.log("【Lynn】【开启流服务】", this.data.webrtcInfo)
+        }
+      }
+    })
+  },
+
+  // 开始录音
+  startAsr(){
+    this.interrupt()
+    let list = this.data.chatList.concat([{
+      type: 0,
+      text: '....开始录音....',
+      isEnd: true
+    }])
+    this.updateChat(list)
+    this.data.llmService.startAsr();
+  },
+
+  // 停止录音
+  stopAsr(){
+    console.log('【停止录音。。。。。。】')
+    let list = this.data.chatList.concat([{
+      type: 0,
+      text: '....停止录音....',
+      isEnd: true
+    }])
+    this.updateChat(list)
+    this.data.llmService.stopAsr();
+  },
+
+  // 发送消息
+  sendText(text){
+    this.data.llmService.sendText(text, this.data.avartarParam.streamId);
+  },
+
+  // 打断消息
+  interrupt() {
+    if (this.data.llmService) {
+      console.log('【打断消息。。。。。】')
+      this.setData({
+        chatList: this.data.chatList.concat([{
+          type: 0,
+          text: '....打断消息....',
+          isEnd: true
+        }])
+      })
+      this.data.llmService.interruptBoardcast()
+    }
+  },
+
+  // 关闭流服务
+  stopLive(){
+    this.data.llmService.stopLive(this.data.avartarParam.streamId).then((res) => {
+
+    });
+  },
+
+  query1(){
+    this.interrupt()
+    let text = '今天天气怎么样'
+    let list = this.data.chatList.concat([{
+      type: 1,
+      text,
+      isEnd: true
+    }])
+    this.updateChat(list)
+    this.sendText(text);
+  },
+
+  query2(){
+    this.interrupt()
+    let text = '你是谁'
+    let list = this.data.chatList.concat([{
+      type: 1,
+      text,
+      isEnd: true
+    }])
+    this.updateChat(list)
+    this.sendText(text);
+  },
+  onClear() {
+    this.setData({
+      chatList: []
+    })
+  },
+  updateChat(list){
+    this.setData({
+      chatList: list
+    })
+    console.log('updateChat', list)
+    const _this = this
+    // 获取 scroll-view 的节点
+    const selectorQuery  = my.createSelectorQuery();
+    // 滚动到最后一条消息
+    selectorQuery
+        .select(`#message-${_this.data.chatList.length - 1}`)
+        .boundingClientRect()
+        .exec(messageRect => {
+          console.log(messageRect)
+          if(messageRect[0]){
+            _this.setData({
+              scrollTop: (messageRect[0].top || 0) + (messageRect[0].bottom || 0) + (_this.data.chatList.length * 30) // 假设容器足够高,设置一个大的 scrollTop 值
+            });
+          }
+        })
+  }
+ });

+ 4 - 0
pages/avatar-demo/avatar-demo.json

@@ -0,0 +1,4 @@
+{
+  "usingComponents": {},
+  "styleIsolation": "apply-shared"
+}

+ 5 - 0
pages/index/index.axml

@@ -0,0 +1,5 @@
+
+<view>
+  this is a blank page
+  <button size="default" type="primary" onTap="goAvatar">Button</button>
+</view>

+ 39 - 0
pages/index/index.js

@@ -0,0 +1,39 @@
+Page({
+  onLoad(query) {
+    // 页面加载
+    console.info(`Page onLoad with query: ${JSON.stringify(query)}`);
+  },
+  onReady() {
+    // 页面加载完成
+  },
+  onShow() {
+    // 页面显示
+  },
+  onHide() {
+    // 页面隐藏
+  },
+  onUnload() {
+    // 页面被关闭
+  },
+  onTitleClick() {
+    // 标题被点击
+  },
+  onPullDownRefresh() {
+    // 页面被下拉
+  },
+  onReachBottom() {
+    // 页面被拉到底部
+  },
+  onShareAppMessage() {
+    // 返回自定义分享信息
+    return {
+      title: 'My App',
+      desc: 'My App description',
+      path: 'pages/index/index',
+    };
+  },
+  goAvatar(){
+    my.showToast({ content: 'clickBtn'});
+    my.navigateTo({ url: '../avatar-demo/avatar-demo' });
+  },
+});

+ 1 - 0
pages/index/index.json

@@ -0,0 +1 @@
+{}

File diff suppressed because it is too large
+ 0 - 0
util/ant-avatar-tinyapp-llm-service.min.js


Some files were not shown because too many files changed in this diff