353 lines
7.2 KiB
Vue
353 lines
7.2 KiB
Vue
<template>
|
||
<view class="ai-chat-container">
|
||
<!-- 聊天消息列表 -->
|
||
<scroll-view class="chat-list" scroll-y scroll-bottom="{{scrollBottom}}"
|
||
:style="{height: `calc(100vh - ${inputAreaHeight}px)`}">
|
||
<!-- 系统消息 -->
|
||
<view class="chat-item system">
|
||
<view class="chat-avatar system-avatar">
|
||
<text class="iconfont icon-robot"></text>
|
||
</view>
|
||
<view class="chat-content">
|
||
<view class="content-text">你好,我是智能助手,有什么可以帮你的?</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 用户消息 -->
|
||
<view class="chat-item user">
|
||
<view class="chat-content">
|
||
<view class="content-text">请问uniapp怎么实现语音流式传输?</view>
|
||
</view>
|
||
<view class="chat-avatar user-avatar">
|
||
<text class="iconfont icon-user"></text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- AI回复消息 -->
|
||
<view class="chat-item ai">
|
||
<view class="chat-avatar ai-avatar">
|
||
<text class="iconfont icon-ai"></text>
|
||
</view>
|
||
<view class="chat-content">
|
||
<view class="content-text">
|
||
UniApp实现语音流式传输可通过5+ API采集音频帧,结合WebSocket分块发送,核心步骤包括:
|
||
1. 初始化录音实例(16k采样率、单声道PCM);
|
||
2. 建立WebSocket连接;
|
||
3. 分块发送音频数据;
|
||
4. 接收实时ASR结果。
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 输入区域 + 按住发送按钮 -->
|
||
<view class="input-area" :style="{height: inputAreaHeight + 'px'}">
|
||
|
||
|
||
<!-- 按住发送按钮(底部固定) -->
|
||
<view class="send-btn-area">
|
||
<button class="hold-send-btn" @touchstart="startRecord">
|
||
<text class="btn-text">{{status ? '松开发送' : '按住 说话/发送'}}</text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
<yao-RecordFrame ref="recordFrame" @onFrameRecorded="frameRecorded" @currentDecibels="onCurrentDecibels"
|
||
@onStop="stopIt"></yao-RecordFrame>
|
||
</view>
|
||
</template>
|
||
<script>
|
||
import {
|
||
myApi
|
||
} from "@/uni_modules/ty-uts-test";
|
||
export default {
|
||
data() {
|
||
return {
|
||
inputAreaHeight: 80,
|
||
status: "未录音",
|
||
recorder: null, // 录音实例
|
||
recordTimer: null, // 定时读取定时器
|
||
};
|
||
},
|
||
onUnload() {
|
||
// 页面卸载清理资源
|
||
this.stopRecord();
|
||
},
|
||
methods: {
|
||
// 开始录音
|
||
async startRecord() {
|
||
// 获取电量信息
|
||
// uni.getBatteryInfo({
|
||
// success(res) {
|
||
// console.log(res);
|
||
// uni.showToast({
|
||
// title: "当前电量:" + res.level + '%',
|
||
// icon: 'none'
|
||
// });
|
||
// }
|
||
// })
|
||
// * 1、引入方法声明 import { myApi } from "@/uni_modules/uts-api"
|
||
// * 2、方法调用
|
||
// myApi({
|
||
// paramA: true,
|
||
// complete: (res) => {
|
||
// console.log(res)
|
||
// }
|
||
// });
|
||
|
||
this.$refs.recordFrame.start({
|
||
sampleRate: 16000,
|
||
frameSize: 1024,
|
||
gain: 1.0 //增益值,数字越高音频声音越大 1.0~20.0
|
||
})
|
||
|
||
},
|
||
// 停止录音
|
||
stopRecord() {
|
||
|
||
},
|
||
frameRecorded({
|
||
isLastFrame,
|
||
frameBuffer
|
||
}) {
|
||
// console.log(isLastFrame,frameBuffer);
|
||
if (!isLastFrame) {
|
||
this.frameBuffer = frameBuffer;
|
||
}
|
||
|
||
this.isLastFrame = isLastFrame ? 'true' : 'false';
|
||
},
|
||
onCurrentDecibels(decibels) {
|
||
//当前分贝值 最小可测分贝(-80) 最大可测分贝(-30)
|
||
console.log("当前分贝:" + decibels)
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
// 全局容器
|
||
.ai-chat-container {
|
||
width: 100%;
|
||
height: 100vh;
|
||
background-color: #f5f7fa;
|
||
box-sizing: border-box;
|
||
padding-bottom: env(safe-area-inset-bottom); // 适配iOS底部安全区
|
||
}
|
||
|
||
// 聊天列表
|
||
.chat-list {
|
||
width: 100%;
|
||
padding: 20rpx 16rpx;
|
||
box-sizing: border-box;
|
||
|
||
// 消息项通用样式
|
||
.chat-item {
|
||
display: flex;
|
||
margin-bottom: 30rpx;
|
||
max-width: 85%;
|
||
|
||
// 头像通用样式
|
||
.chat-avatar {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
margin-right: 20rpx;
|
||
background-color: #fff;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||
|
||
.iconfont {
|
||
font-size: 40rpx;
|
||
}
|
||
}
|
||
|
||
// 消息内容
|
||
.chat-content {
|
||
.content-text {
|
||
padding: 24rpx 28rpx;
|
||
border-radius: 20rpx;
|
||
line-height: 1.6;
|
||
font-size: 32rpx;
|
||
color: #333;
|
||
word-wrap: break-word;
|
||
word-break: break-all;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 系统消息
|
||
.system {
|
||
margin: 0 auto 30rpx;
|
||
|
||
.chat-avatar {
|
||
background-color: #e8f4f8;
|
||
|
||
.iconfont {
|
||
color: #4299e1;
|
||
}
|
||
}
|
||
|
||
.chat-content {
|
||
.content-text {
|
||
background-color: #e8f4f8;
|
||
color: #2d3748;
|
||
border-radius: 16rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 用户消息(右对齐)
|
||
.user {
|
||
margin-left: auto;
|
||
flex-direction: row-reverse;
|
||
|
||
.chat-avatar {
|
||
margin-right: 0;
|
||
margin-left: 20rpx;
|
||
background-color: #4299e1;
|
||
|
||
.iconfont {
|
||
color: #fff;
|
||
}
|
||
}
|
||
|
||
.chat-content {
|
||
.content-text {
|
||
background-color: #4299e1;
|
||
color: #fff;
|
||
border-radius: 20rpx 20rpx 8rpx 20rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
// AI消息(左对齐)
|
||
.ai {
|
||
.chat-avatar {
|
||
background-color: #9f7aea;
|
||
|
||
.iconfont {
|
||
color: #fff;
|
||
}
|
||
}
|
||
|
||
.chat-content {
|
||
.content-text {
|
||
background-color: #fff;
|
||
border-radius: 20rpx 20rpx 20rpx 8rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 输入区域
|
||
.input-area {
|
||
width: 100%;
|
||
background-color: #fff;
|
||
padding: 16rpx 20rpx;
|
||
box-sizing: border-box;
|
||
border-top: 1rpx solid #eee;
|
||
|
||
// 输入框容器
|
||
.input-box {
|
||
position: relative;
|
||
width: 100%;
|
||
margin-bottom: 20rpx;
|
||
|
||
.input-text {
|
||
width: 100%;
|
||
min-height: 80rpx;
|
||
max-height: 200rpx;
|
||
padding: 20rpx 24rpx;
|
||
padding-right: 80rpx;
|
||
box-sizing: border-box;
|
||
border: 1rpx solid #e5e7eb;
|
||
border-radius: 40rpx;
|
||
font-size: 32rpx;
|
||
line-height: 1.5;
|
||
background-color: #f9fafb;
|
||
resize: none;
|
||
outline: none;
|
||
}
|
||
|
||
.clear-btn {
|
||
position: absolute;
|
||
right: 20rpx;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
border-radius: 50%;
|
||
background-color: #e5e7eb;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 0;
|
||
margin: 0;
|
||
|
||
.iconfont {
|
||
font-size: 32rpx;
|
||
color: #6b7280;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 按住发送按钮区域
|
||
.send-btn-area {
|
||
width: 100%;
|
||
display: flex;
|
||
justify-content: center;
|
||
|
||
.hold-send-btn {
|
||
width: 80%;
|
||
height: 90rpx;
|
||
line-height: 90rpx;
|
||
border-radius: 45rpx;
|
||
background-color: #4299e1;
|
||
color: #fff;
|
||
font-size: 34rpx;
|
||
font-weight: 500;
|
||
box-shadow: 0 4rpx 12rpx rgba(66, 153, 225, 0.3);
|
||
transition: all 0.2s ease;
|
||
padding: 0;
|
||
margin: 0;
|
||
|
||
&:active {
|
||
background-color: #3182ce;
|
||
transform: scale(0.98);
|
||
}
|
||
|
||
.btn-text {
|
||
font-size: 34rpx;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 图标占位样式(可替换为自定义图标/图片)
|
||
.iconfont {
|
||
font-family: "iconfont" !important;
|
||
font-style: normal;
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
}
|
||
|
||
.icon-robot::before {
|
||
content: "\e600";
|
||
}
|
||
|
||
.icon-user::before {
|
||
content: "\e601";
|
||
}
|
||
|
||
.icon-ai::before {
|
||
content: "\e602";
|
||
}
|
||
|
||
.icon-clear::before {
|
||
content: "\e603";
|
||
}
|
||
</style> |