Przeglądaj źródła

feat: 处理语音转文字

sunxiao 1 dzień temu
rodzic
commit
ce8f1eac24

+ 4 - 2
.env.development

@@ -8,5 +8,7 @@ VITE_APP_ENV = 'development'
 VITE_IMGAGE_PATH = 'https://static.fuxicarbon.com/bigModel/wechat'
 
 # 开发环境
-VITE_APP_BASE_API =  http://chat.sequoialibra.com:81/apiServe
-# VITE_APP_BASE_API =  http://192.168.40.18:8080
+# VITE_APP_BASE_API =  http://chat.sequoialibra.com:81/apiServe
+# VITE_APP_BASE_API = http://192.168.40.18:81/apiServe
+# VITE_APP_BASE_API = http://chat.sqlibra.com:81/apiServe
+VITE_APP_BASE_API = https://bigmodel.sqlibra.com/apiServe

+ 4 - 11
.env.production

@@ -1,19 +1,12 @@
 # 页面标题
-VITE_APP_TITLE = 佳木斯智能语音客服
+VITE_APP_TITLE = 大模型微信端 - prod
 
 # 生产环境配置
 VITE_APP_ENV = 'production'
 
-
 # 图片路径
-VITE_IMGAGE_PATH = 'https://agr-1253934376.cos.ap-shanghai.myqcloud.com/gzxsdsq'
-
-# 管理系统/开发环境
-# VITE_APP_BASE_API =  https://xsdev.agritechure.com/prod-api/
-VITE_APP_BASE_API =  https://xsd.agritechure.com/prod-api
+VITE_IMGAGE_PATH = 'https://static.fuxicarbon.com/bigModel/wechat'
 
-# 是否在打包时开启压缩,支持 gzip 和 brotli
-VITE_BUILD_COMPRESS = gzip
+# 开发环境
+VITE_APP_BASE_API =  http://chat.sequoialibra.com:81/apiServe
 
-VITE_APP_BASE_TEST = http://10.0.0.28:8080/
-VITE_APP_BASE_PROD = http://192.168.9.54:8080/

+ 156 - 0
src/assets/style/common.scss

@@ -304,4 +304,160 @@
     -o-transform: translateY(-100%) translateX(500%);
     transform: translateY(-100%) translateX(500%);
   }
+}
+
+.la-line-scale-pulse-out,
+.la-line-scale-pulse-out > view {
+    position: relative;
+    -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+            box-sizing: border-box;
+}
+.la-line-scale-pulse-out {
+    display: block;
+    font-size: 0;
+    color: #fff;
+}
+.la-line-scale-pulse-out.la-dark {
+    color: #333;
+}
+.la-line-scale-pulse-out > view {
+    display: inline-block;
+    float: none;
+    background-color: currentColor;
+    border: 0 solid currentColor;
+}
+.la-line-scale-pulse-out {
+    width: 40px;
+    height: 32px;
+}
+.la-line-scale-pulse-out > view {
+    width: 4px;
+    height: 32px;
+    margin: 2px;
+    margin-top: 0;
+    margin-bottom: 0;
+    border-radius: 0;
+    -webkit-animation: line-scale-pulse-out .9s infinite cubic-bezier(.85, .25, .37, .85);
+       -moz-animation: line-scale-pulse-out .9s infinite cubic-bezier(.85, .25, .37, .85);
+         -o-animation: line-scale-pulse-out .9s infinite cubic-bezier(.85, .25, .37, .85);
+            animation: line-scale-pulse-out .9s infinite cubic-bezier(.85, .25, .37, .85);
+}
+.la-line-scale-pulse-out > view:nth-child(3) {
+    -webkit-animation-delay: -.9s;
+       -moz-animation-delay: -.9s;
+         -o-animation-delay: -.9s;
+            animation-delay: -.9s;
+}
+.la-line-scale-pulse-out > view:nth-child(2),
+.la-line-scale-pulse-out > view:nth-child(4) {
+    -webkit-animation-delay: -.7s;
+       -moz-animation-delay: -.7s;
+         -o-animation-delay: -.7s;
+            animation-delay: -.7s;
+}
+.la-line-scale-pulse-out > view:nth-child(1),
+.la-line-scale-pulse-out > view:nth-child(5) {
+    -webkit-animation-delay: -.5s;
+       -moz-animation-delay: -.5s;
+         -o-animation-delay: -.5s;
+            animation-delay: -.5s;
+}
+.la-line-scale-pulse-out.la-sm {
+    width: 20px;
+    height: 16px;
+}
+.la-line-scale-pulse-out.la-sm > view {
+    width: 2px;
+    height: 16px;
+    margin: 1px;
+    margin-top: 0;
+    margin-bottom: 0;
+}
+.la-line-scale-pulse-out.la-2x {
+    width: 80px;
+    height: 64px;
+}
+.la-line-scale-pulse-out.la-2x > view {
+    width: 8px;
+    height: 64px;
+    margin: 4px;
+    margin-top: 0;
+    margin-bottom: 0;
+}
+.la-line-scale-pulse-out.la-3x {
+    width: 120px;
+    height: 96px;
+}
+.la-line-scale-pulse-out.la-3x > view {
+    width: 12px;
+    height: 96px;
+    margin: 6px;
+    margin-top: 0;
+    margin-bottom: 0;
+}
+/*
+ * Animation
+ */
+@-webkit-keyframes line-scale-pulse-out {
+    0% {
+        -webkit-transform: scaley(1);
+                transform: scaley(1);
+    }
+    50% {
+        -webkit-transform: scaley(.3);
+                transform: scaley(.3);
+    }
+    100% {
+        -webkit-transform: scaley(1);
+                transform: scaley(1);
+    }
+}
+@-moz-keyframes line-scale-pulse-out {
+    0% {
+        -moz-transform: scaley(1);
+             transform: scaley(1);
+    }
+    50% {
+        -moz-transform: scaley(.3);
+             transform: scaley(.3);
+    }
+    100% {
+        -moz-transform: scaley(1);
+             transform: scaley(1);
+    }
+}
+@-o-keyframes line-scale-pulse-out {
+    0% {
+        -o-transform: scaley(1);
+           transform: scaley(1);
+    }
+    50% {
+        -o-transform: scaley(.3);
+           transform: scaley(.3);
+    }
+    100% {
+        -o-transform: scaley(1);
+           transform: scaley(1);
+    }
+}
+@keyframes line-scale-pulse-out {
+    0% {
+        -webkit-transform: scaley(1);
+           -moz-transform: scaley(1);
+             -o-transform: scaley(1);
+                transform: scaley(1);
+    }
+    50% {
+        -webkit-transform: scaley(.3);
+           -moz-transform: scaley(.3);
+             -o-transform: scaley(.3);
+                transform: scaley(.3);
+    }
+    100% {
+        -webkit-transform: scaley(1);
+           -moz-transform: scaley(1);
+             -o-transform: scaley(1);
+                transform: scaley(1);
+    }
 }

+ 113 - 63
src/components/chat/ChatInput.vue

@@ -1,5 +1,8 @@
 <script setup>
+// const plugin = requirePlugin("WechatSI")
+
 import { ref, unref, onMounted } from 'vue';
+import { baseURL } from '@/utils/https';
 
 const modelInpValue = defineModel();
 const emit = defineEmits(['on-submit']);
@@ -12,11 +15,16 @@ const volumeLevel = ref(50); // 模拟音量级别
 let recorderManager = null;
 const audioPath = ref(null);
 
+// 实时语音识别
+// const manager = plugin.getRecordRecognitionManager();
+
+
+console.log( "manager", manager );
 
 const innerAudioContext = wx.createInnerAudioContext();
 
 // 类型 input|voice
-const inpType = ref('input'); 
+const inpType = ref('input');
 
 // 提交问题
 const onSubmit = () => {
@@ -43,17 +51,51 @@ const onChangeInpType = () => {
   inpType.value = inpType.value === 'input' ? 'voice' : 'input';
 }
 
+const uploadAudio = (filePath) => {
+  uni.showLoading({
+    title: '上传中...'
+  });
+  uni.uploadFile({
+    url: 'https://your-server.com/upload',
+    filePath: filePath,
+    name: 'audio', // 后端接收文件的字段名
+    formData: {
+      // 可以附加其他表单数据
+      'userId': '123',
+      'timestamp': new Date().getTime()
+    },
+    success: (uploadRes) => {
+      uni.hideLoading();
+      console.log('上传成功', uploadRes);
+      // 处理服务器返回的数据
+      const data = JSON.parse(uploadRes.data);
+      uni.showToast({
+        title: '上传成功',
+        icon: 'success'
+      });
+    },
+    fail: (error) => {
+      uni.hideLoading();
+      console.log('上传失败', error);
+      uni.showToast({
+        title: '上传失败',
+        icon: 'none'
+      });
+    }
+  });
+}
+
 
 const playRecording = () => {
-  
+
   if (innerAudioContext) {
     innerAudioContext.src = audioPath.value;
     innerAudioContext.play();
-    
+
     innerAudioContext.onPlay(() => {
       console.log('开始播放');
     });
-    
+
     innerAudioContext.onError((err) => {
       console.error('播放错误:', err);
       uni.showToast({ title: '播放失败', icon: 'none' });
@@ -65,7 +107,7 @@ const playRecording = () => {
 const startRecording = e => {
   isRecording.value = true;
   showVolume.value = true;
-  
+
   recorderManager.start({
     format: 'mp3',
     duration: 60000, // 最长1分钟
@@ -78,45 +120,53 @@ const startRecording = e => {
 
 function setupRecorderEvents() {
   if (!recorderManager) return;
-  
-  recorderManager.onStart(() => {
-    console.log('录音开始');
-  });
-  
-  recorderManager.onPause(() => {
-    console.log('录音暂停');
-  });
-  
-  recorderManager.onStop((res) => {
-    // audioPath.value = 'https://web-ext-storage.dcloud.net.cn/uni-app/ForElise.mp3';
-
-    audioPath.value = res.tempFilePath;
-
-    // setTimeout(() => {
-    //   innerAudioContext.src = 'https://dlink.host/musics/aHR0cHM6Ly9vbmVkcnYtbXkuc2hhcmVwb2ludC5jb20vOnU6L2cvcGVyc29uYWwvc3Rvcl9vbmVkcnZfb25taWNyb3NvZnRfY29tL0VjYzBzQUxiWFk5TWdHQl9GUVNkV2pJQm5wRmM0MktDZWpURnhhMjhELUdXeVE.mp3';
-    //   innerAudioContext.play();
-    // }, 1000)
 
-    console.log('录音停止', res);
-    // 这里可以处理录音结果,如上传或播放
-  });
-  recorderManager.onFrameRecorded((res) => {
-    // 可以在这里获取实时音量等信息
-    const volume = res.volumn; // 注意微信可能是volumn而不是volume
-    if (volume !== undefined) {
-      volumeLevel.value = Math.min(100, volume * 200); // 调整音量显示比例
-    }
+  uni.authorize({
+    scope: 'scope.record',
+    success() {
+      recorderManager.onStart(() => {
+        console.log('录音开始');
+      });
+
+      recorderManager.onPause(() => {
+        console.log('录音暂停');
+      });
+
+      recorderManager.onStop((res) => {
+
+        audioPath.value = res.tempFilePath;
+
+        // setTimeout(() => {
+        //   innerAudioContext.src = 'https://dlink.host/musics/aHR0cHM6Ly9vbmVkcnYtbXkuc2hhcmVwb2ludC5jb20vOnU6L2cvcGVyc29uYWwvc3Rvcl9vbmVkcnZfb25taWNyb3NvZnRfY29tL0VjYzBzQUxiWFk5TWdHQl9GUVNkV2pJQm5wRmM0MktDZWpURnhhMjhELUdXeVE.mp3';
+        //   innerAudioContext.play();
+        // }, 1000)
+
+        console.log('录音停止', res);
+        // 这里可以处理录音结果,如上传或播放
+      });
+      recorderManager.onFrameRecorded((res) => {
+        // 可以在这里获取实时音量等信息
+        const volume = res.volumn; // 注意微信可能是volumn而不是volume
+        if (volume !== undefined) {
+          volumeLevel.value = Math.min(100, volume * 200); // 调整音量显示比例
+        }
+      });
+    },
+    fail() {
+      uni.showToast({ title: '用户拒绝授权录音权限', icon: 'none' });
+    },
   });
+
 }
 
 function stopRecording() {
   if (!isRecording.value) return;
-  
+
   isRecording.value = false;
   showVolume.value = false;
-  
+
   recorderManager.stop();
-  
+
   // 停止音量检测
   // stopVolumeDetection();
 }
@@ -126,6 +176,8 @@ onMounted(() => {
   recorderManager = wx.getRecorderManager();
 
   setupRecorderEvents();
+
+
 });
 </script>
 
@@ -134,14 +186,17 @@ onMounted(() => {
     <view class="voice-inner">
       <view class="voice-card">
         <view class="voice-tip">
+          <view class="la-line-scale-pulse-out">
+            <view></view>
+            <view></view>
+            <view></view>
+            <view></view>
+            <view></view>
+          </view>
         </view>
       </view>
-      <view
-        class="vocie-btn_wrapper"
-        @touchstart="startRecording"
-        @touchend="stopRecording"
-        @touchcancel="stopRecording"
-      >
+      <view class="vocie-btn_wrapper" @touchstart="startRecording" @touchend="stopRecording"
+        @touchcancel="stopRecording">
         <view class="tips">{{ recordingTip }}</view>
         <view class="btn">
           <uni-icons type="sound" size="20" color="#777"></uni-icons>
@@ -157,26 +212,13 @@ onMounted(() => {
         <TheSvgIcon class="icon" src="icon-voice" size="42"></TheSvgIcon>
       </view>
       <view class="inp-inner">
-        <textarea
-          v-model.trim="modelInpValue"
-          :show-confirm-bar="false"
-          :cursor-spacing="30"
-          auto-height
-          :maxlength="2000"
-          class="chat-inp"
-          placeholder="输入您的问题或需求"
-          placeholder-style="color:#9A9A9A"
-          v-show="inpType == 'input'"
-        >
+        <textarea v-model.trim="modelInpValue" :show-confirm-bar="false" :cursor-spacing="30" auto-height
+          :maxlength="2000" class="chat-inp" placeholder="输入您的问题或需求" placeholder-style="color:#9A9A9A"
+          v-show="inpType == 'input'">
         </textarea>
 
-        <view
-          class="chat-voice"
-          v-show="inpType == 'voice'"
-          @touchstart="startRecording"
-          @touchend="stopRecording"
-          @touchcancel="stopRecording"
-        >
+        <view class="chat-voice" v-show="inpType == 'voice'" @touchstart="startRecording" @touchend="stopRecording"
+          @touchcancel="stopRecording">
           <text>按住 说话</text>
         </view>
       </view>
@@ -243,6 +285,7 @@ onMounted(() => {
       height: 56rpx;
       border-radius: 100%;
       background: #212121;
+
       .icon {
         transition: all 0.3s ease-in-out;
       }
@@ -266,18 +309,23 @@ onMounted(() => {
     height: 100%;
 
     .voice-card {
-      flex:1;
+      flex: 1;
       display: flex;
       align-items: center;
       justify-content: center;
       width: 100vw;
+
       .voice-tip {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        padding: 10rpx 20rpx;
         width: 100px;
-        height: 30px;
-        background: red;
+        border-radius: 10rpx;
+        background: #00aa5b;
       }
     }
-  
+
     .vocie-btn_wrapper {
       position: relative;
       display: flex;
@@ -288,6 +336,7 @@ onMounted(() => {
       height: 200rpx;
       width: 100vw;
       overflow: hidden;
+
       .tips {
         padding-bottom: 10rpx;
         flex-shrink: 0;
@@ -295,6 +344,7 @@ onMounted(() => {
         font-size: 24rpx;
         text-align: center;
       }
+
       .btn {
         flex: 1;
         display: flex;

+ 69 - 68
src/manifest.json

@@ -1,72 +1,73 @@
 {
-    "name" : "人工智能运营体",
-    "appid" : "wx809fa55d756098a7",
-    "description" : "",
-    "versionName" : "1.0.0",
-    "versionCode" : "100",
-    "transformPx" : false,
-    /* 5+App特有相关 */
-    "app-plus" : {
-        "usingComponents" : true,
-        "nvueStyleCompiler" : "uni-app",
-        "compilerVersion" : 3,
-        "splashscreen" : {
-            "alwaysShowBeforeRender" : true,
-            "waiting" : true,
-            "autoclose" : true,
-            "delay" : 0
-        },
-        /* 模块配置 */
-        "modules" : {},
-        /* 应用发布信息 */
-        "distribute" : {
-            /* android打包配置 */
-            "android" : {
-                "permissions" : [
-                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
-                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
-                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
-                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
-                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
-                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
-                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
-                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
-                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
-                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
-                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
-                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
-                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
-                    "<uses-feature android:name=\"android.hardware.camera\"/>",
-                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
-                ]
-            },
-            /* ios打包配置 */
-            "ios" : {},
-            /* SDK配置 */
-            "sdkConfigs" : {}
-        }
+  "name": "人工智能运营体",
+  "appid": "wx809fa55d756098a7",
+  "description": "",
+  "versionName": "1.0.0",
+  "versionCode": "100",
+  "transformPx": false,
+  /* 5+App特有相关 */
+  "app-plus": {
+    "usingComponents": true,
+    "nvueStyleCompiler": "uni-app",
+    "compilerVersion": 3,
+    "splashscreen": {
+      "alwaysShowBeforeRender": true,
+      "waiting": true,
+      "autoclose": true,
+      "delay": 0
     },
-    /* 快应用特有相关 */
-    "quickapp" : {},
-    /* 小程序特有相关 */
-    "mp-weixin" : {
-        "appid" : "wx809fa55d756098a7",
-        "setting" : {
-            "urlCheck" : false
-        },
-        "usingComponents" : true
+    /* 模块配置 */
+    "modules": {},
+    /* 应用发布信息 */
+    "distribute": {
+      /* android打包配置 */
+      "android": {
+        "permissions": [
+          "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+          "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+          "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+          "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+          "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+          "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+          "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+          "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+          "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+          "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+          "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+          "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+          "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+          "<uses-feature android:name=\"android.hardware.camera\"/>",
+          "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+        ]
+      },
+      /* ios打包配置 */
+      "ios": {},
+      /* SDK配置 */
+      "sdkConfigs": {}
+    }
+  },
+  /* 快应用特有相关 */
+  "quickapp": {},
+  /* 小程序特有相关 */
+  "mp-weixin": {
+    "appid": "wx809fa55d756098a7",
+    "setting": {
+      "urlCheck": false
     },
-    "mp-alipay" : {
-        "usingComponents" : true
-    },
-    "mp-baidu" : {
-        "usingComponents" : true
-    },
-    "mp-toutiao" : {
-        "usingComponents" : true
-    },
-    "uniStatistics": {  
-        "enable": false
-    },
-    "vueVersion" : "3"
+    "usingComponents": true,
+    "requiredPermissions": ["scope.record"]
+  },
+  "mp-alipay": {
+    "usingComponents": true
+  },
+  "mp-baidu": {
+    "usingComponents": true
+  },
+  "mp-toutiao": {
+    "usingComponents": true
+  },
+  "uniStatistics": {
+    "enable": false
+  },
+  "vueVersion": "3"
 }

+ 1 - 1
src/utils/https.js

@@ -10,7 +10,7 @@ const httpInterceptor = {
     if (!options.url.startsWith('http')) {
       options.url = baseURL + options.url
     }
-    options.timeout = 3000;
+    options.timeout = 5 * 60 * 1000;
     options.header = {...options.header};
     const token = userStore.userInfo?.token;
     token && (options.header.Authorization = 'Bearer ' + token);

+ 1 - 1
src/utils/streamRequest.js

@@ -17,7 +17,6 @@ export const streamChatRequest = async ({ data, onProgress, onSuccess, onComplet
 
   const requestTask = uni.request({
     url,
-    timeout: 20 * 60 * 1000,
     enableChunked: true,
     method: "POST",
     header: {
@@ -43,6 +42,7 @@ export const streamChatRequest = async ({ data, onProgress, onSuccess, onComplet
   let fullMessage = "";
 
   requestTask.onChunkReceived(async (res) => {
+    console.log(res);
     const uint8Array = new Uint8Array(res.data);
     const chunkText = uint8ArrayToString(uint8Array);;
     accumulatedText += chunkText;