diff --git a/miniprogram/pages/qa_exercise/qa_exercise.ts b/miniprogram/pages/qa_exercise/qa_exercise.ts index 6cf54ec..48bdf30 100644 --- a/miniprogram/pages/qa_exercise/qa_exercise.ts +++ b/miniprogram/pages/qa_exercise/qa_exercise.ts @@ -134,6 +134,7 @@ interface IData { showChatSuggestions: boolean chatSuggestions: Array<{ label: string; text: string }> inputBottom: number + isRecording: boolean } interface IPageInstance { @@ -144,6 +145,10 @@ interface IPageInstance { suggestionTimer?: number isSuggestionTouching?: boolean audioCtx?: WechatMiniprogram.InnerAudioContext + recorderManager?: WechatMiniprogram.RecorderManager + handleRecordStart: () => void + handleRecordEnd: () => void + uploadAndRecognizeAudio: (filePath: string) => Promise onKeyboardHeightChange: (res: any) => void startPlaceholderTimer: () => void startSuggestionTimer: () => void @@ -342,13 +347,63 @@ Page({ chatPlaceholder: '请输入...', showChatSuggestions: false, chatSuggestions: [], - inputBottom: 0 + inputBottom: 0, + isRecording: false }, onKeyboardHeightChange(res: any) { this.setData({ inputBottom: res.height }) }, + handleRecordStart() { + if (!this.recorderManager) return + wx.authorize({ + scope: 'scope.record', + success: () => { + this.recorderManager?.start({ format: 'mp3' }) + }, + fail: () => { + wx.showModal({ + title: '提示', + content: '需要录音权限才能使用语音输入', + success: (res) => { + if (res.confirm) { + wx.openSetting() + } + } + }) + } + }) + }, + + handleRecordEnd() { + if (this.data.isRecording && this.recorderManager) { + this.recorderManager.stop() + } + }, + + async uploadAndRecognizeAudio(filePath: string) { + wx.showLoading({ title: '识别中...' }) + try { + const fileId = await apiManager.uploadFile(filePath) + + const sessionId = this.data.conversationLatestSession?.session_id + if (!sessionId) throw new Error('没有会话ID') + + const res = await apiManager.recognizeQaAudio(sessionId, fileId) + if (res.data && res.data.text) { + this.setData({ chatInputValue: res.data.text }) + } else { + wx.showToast({ title: '未能识别出文字', icon: 'none' }) + } + } catch (e) { + console.error('语音识别失败', e) + wx.showToast({ title: '识别失败', icon: 'none' }) + } finally { + wx.hideLoading() + } + }, + pollTimer: undefined, variationPollTimer: undefined, conversationPollTimer: undefined, @@ -560,6 +615,26 @@ Page({ async onLoad(options: Record) { wx.onKeyboardHeightChange(this.onKeyboardHeightChange) + this.recorderManager = wx.getRecorderManager() + this.recorderManager.onStart(() => { + this.setData({ isRecording: true }) + wx.showToast({ title: '正在录音...', icon: 'none', duration: 60000 }) + }) + this.recorderManager.onStop((res) => { + this.setData({ isRecording: false }) + wx.hideToast() + const { tempFilePath, duration } = res + if (duration < 500) { + wx.showToast({ title: '说话时间太短', icon: 'none' }) + return + } + this.uploadAndRecognizeAudio(tempFilePath) + }) + this.recorderManager.onError((err) => { + console.error('录音报错', err) + this.setData({ isRecording: false }) + wx.showToast({ title: '录音失败', icon: 'none' }) + }) try { const app = getApp() diff --git a/miniprogram/pages/qa_exercise/qa_exercise.wxml b/miniprogram/pages/qa_exercise/qa_exercise.wxml index a84122f..0bb7c4c 100644 --- a/miniprogram/pages/qa_exercise/qa_exercise.wxml +++ b/miniprogram/pages/qa_exercise/qa_exercise.wxml @@ -25,12 +25,11 @@ - + - @@ -48,7 +47,6 @@ - {{item.text}} @@ -338,9 +336,12 @@ bind:blur="onChatBlur" > - + + + + @@ -383,7 +384,7 @@ 题目解析 - + {{qaDetailQuestionText}} diff --git a/miniprogram/pages/qa_exercise/qa_exercise.wxss b/miniprogram/pages/qa_exercise/qa_exercise.wxss index c5919eb..21c3dec 100644 --- a/miniprogram/pages/qa_exercise/qa_exercise.wxss +++ b/miniprogram/pages/qa_exercise/qa_exercise.wxss @@ -26,17 +26,20 @@ .question-scroll-wrapper { flex: 1; - height: 0; - min-height: 0; - display: block; + width: 100%; + position: relative; overflow: hidden; /* padding-bottom: calc(110rpx + env(safe-area-inset-bottom)); */ /* transition: padding-bottom 0.3s ease; */ transition: margin-bottom 0.3s ease; } .inner-scroll { - height: 100%; + position: absolute; + top: 0; + left: 0; width: 100%; + /* height: 100%; */ + height: 500rpx; } .chat-mode-scroll { /* padding-bottom: calc(110rpx + env(safe-area-inset-bottom)); */ diff --git a/miniprogram/pages/scene_sentence/scene_sentence.wxml b/miniprogram/pages/scene_sentence/scene_sentence.wxml index da879da..8c149be 100644 --- a/miniprogram/pages/scene_sentence/scene_sentence.wxml +++ b/miniprogram/pages/scene_sentence/scene_sentence.wxml @@ -18,7 +18,7 @@ {{scene.list[currentIndex].sentenceZh}} - + #{{item}} diff --git a/miniprogram/utils/api.ts b/miniprogram/utils/api.ts index d6d0ca9..7988535 100755 --- a/miniprogram/utils/api.ts +++ b/miniprogram/utils/api.ts @@ -687,6 +687,15 @@ class ApiManager { }) } + // 语音识别 + async recognizeQaAudio(sessionId: string, fileId: string): Promise> { + return this.request( + `/api/v1/qa/conversations/${sessionId}/recognize_audio`, + 'POST', + { file_id: fileId } + ) + } + // 上传文件(第一步:上传文件获取ID) async uploadFile(filePath: string, retryCount: number = 0): Promise { if (USE_CLOUD) {