interview.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. <template>
  2. <view class="container">
  3. <view class="header">
  4. <text class="title">语音对话</text>
  5. </view>
  6. <view class="chat-container">
  7. <scroll-view scroll-y class="chat-messages" :scroll-top="scrollTop">
  8. <view v-for="(message, index) in messages" :key="index" class="message" :class="message.role">
  9. <text>{{ message.content }}</text>
  10. </view>
  11. </scroll-view>
  12. </view>
  13. <view class="controls">
  14. <button class="voice-btn" :class="{ recording: isRecording }" @touchstart="startRecording" @touchend="stopRecording">
  15. <text>{{ isRecording ? '松开结束' : '按住说话' }}</text>
  16. </button>
  17. </view>
  18. </view>
  19. </template>
  20. <script>
  21. export default {
  22. data() {
  23. return {
  24. messages: [],
  25. isRecording: false,
  26. recorderManager: null,
  27. innerAudioContext: null,
  28. scrollTop: 0,
  29. apiUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions'
  30. }
  31. },
  32. onLoad() {
  33. // 初始化录音管理器
  34. this.recorderManager = uni.getRecorderManager();
  35. this.innerAudioContext = uni.createInnerAudioContext();
  36. // 监听录音结束事件
  37. this.recorderManager.onStop((res) => {
  38. this.processAudio(res.tempFilePath);
  39. });
  40. },
  41. methods: {
  42. startRecording() {
  43. this.isRecording = true;
  44. // 开始录音
  45. this.recorderManager.start({
  46. format: 'mp3',
  47. sampleRate: 16000,
  48. numberOfChannels: 1
  49. });
  50. },
  51. stopRecording() {
  52. this.isRecording = false;
  53. // 停止录音
  54. this.recorderManager.stop();
  55. },
  56. processAudio(filePath) {
  57. // 显示加载状态
  58. uni.showLoading({
  59. title: '处理中...'
  60. });
  61. // 将录音文件转换为文本(语音识别)
  62. // 这里需要接入语音识别服务,以下为示例
  63. this.speechToText(filePath).then(text => {
  64. // 添加用户消息
  65. this.addMessage('user', text);
  66. // 调用DashScope API
  67. this.callDashScopeAPI(text);
  68. }).catch(err => {
  69. uni.hideLoading();
  70. uni.showToast({
  71. title: '语音识别失败',
  72. icon: 'none'
  73. });
  74. console.error(err);
  75. });
  76. },
  77. speechToText(filePath) {
  78. // 临时解决方案:直接返回模拟的语音识别结果
  79. return new Promise((resolve) => {
  80. console.log('录音文件路径:', filePath);
  81. // 模拟语音识别结果
  82. setTimeout(() => {
  83. resolve('你好,我需要帮助');
  84. }, 500);
  85. });
  86. // 注释掉原来的实现,等待后续接入真实的语音识别服务
  87. /*
  88. return new Promise((resolve, reject) => {
  89. uni.uploadFile({
  90. url: '您的语音识别服务URL',
  91. filePath: filePath,
  92. name: 'file',
  93. success: (res) => {
  94. const data = JSON.parse(res.data);
  95. if (data && data.result) {
  96. resolve(data.result);
  97. } else {
  98. // 模拟语音识别结果,实际项目中请删除此行
  99. resolve('你好,我需要帮助');
  100. // reject(new Error('语音识别失败'));
  101. }
  102. },
  103. fail: reject
  104. });
  105. });
  106. */
  107. },
  108. callDashScopeAPI(userInput) {
  109. // 构建请求体
  110. const requestBody = {
  111. model: "qwen-plus",
  112. messages: [
  113. {
  114. role: "system",
  115. content: "You are a helpful assistant."
  116. },
  117. {
  118. role: "user",
  119. content: userInput
  120. }
  121. ]
  122. };
  123. // 发送请求到DashScope API
  124. uni.request({
  125. url: this.apiUrl,
  126. method: 'POST',
  127. header: {
  128. 'Content-Type': 'application/json',
  129. 'Authorization': 'Bearer YOUR_API_KEY' // 替换为您的API密钥
  130. },
  131. data: requestBody,
  132. success: (res) => {
  133. uni.hideLoading();
  134. if (res.statusCode === 200 && res.data && res.data.choices && res.data.choices.length > 0) {
  135. const assistantMessage = res.data.choices[0].message.content;
  136. this.addMessage('assistant', assistantMessage);
  137. // 可选:将回复转为语音(文本转语音)
  138. this.textToSpeech(assistantMessage);
  139. } else {
  140. uni.showToast({
  141. title: '获取回复失败',
  142. icon: 'none'
  143. });
  144. console.error('API响应错误:', res);
  145. }
  146. },
  147. fail: (err) => {
  148. uni.hideLoading();
  149. uni.showToast({
  150. title: '网络请求失败',
  151. icon: 'none'
  152. });
  153. console.error(err);
  154. }
  155. });
  156. },
  157. addMessage(role, content) {
  158. this.messages.push({ role, content });
  159. // 滚动到底部
  160. this.$nextTick(() => {
  161. this.scrollTop = 9999;
  162. });
  163. },
  164. textToSpeech(text) {
  165. // 这里需要接入文本转语音服务
  166. // 以下为示例,实际实现需要根据您使用的文本转语音服务调整
  167. uni.request({
  168. url: '您的文本转语音服务URL',
  169. method: 'POST',
  170. data: { text },
  171. success: (res) => {
  172. if (res.statusCode === 200 && res.data && res.data.audio_url) {
  173. // 播放语音
  174. this.innerAudioContext.src = res.data.audio_url;
  175. this.innerAudioContext.play();
  176. }
  177. }
  178. });
  179. }
  180. }
  181. }
  182. </script>
  183. <style>
  184. .container {
  185. display: flex;
  186. flex-direction: column;
  187. height: 100vh;
  188. background-color: #f5f5f5;
  189. }
  190. .header {
  191. padding: 20rpx;
  192. background-color: #007AFF;
  193. text-align: center;
  194. }
  195. .title {
  196. color: #ffffff;
  197. font-size: 36rpx;
  198. font-weight: bold;
  199. }
  200. .chat-container {
  201. flex: 1;
  202. padding: 20rpx;
  203. overflow: hidden;
  204. }
  205. .chat-messages {
  206. height: 100%;
  207. }
  208. .message {
  209. margin-bottom: 20rpx;
  210. padding: 20rpx;
  211. border-radius: 10rpx;
  212. max-width: 80%;
  213. word-break: break-word;
  214. }
  215. .message.user {
  216. align-self: flex-end;
  217. background-color: #007AFF;
  218. color: white;
  219. margin-left: auto;
  220. }
  221. .message.assistant {
  222. align-self: flex-start;
  223. background-color: #E5E5EA;
  224. color: #333;
  225. }
  226. .controls {
  227. padding: 20rpx;
  228. background-color: #ffffff;
  229. border-top: 1px solid #e0e0e0;
  230. }
  231. .voice-btn {
  232. width: 100%;
  233. height: 80rpx;
  234. line-height: 80rpx;
  235. text-align: center;
  236. background-color: #007AFF;
  237. color: white;
  238. border-radius: 40rpx;
  239. }
  240. .voice-btn.recording {
  241. background-color: #FF3B30;
  242. }
  243. </style>