119119 @TouchStart =" startRecording"
120120 @TouchEnd =" TouchEnd"
121121 :time =" recorderTime"
122- :start =" !mediaRecorderStatus "
122+ :start =" recorderStatus === 'START' "
123123 :disabled =" loading"
124124 />
125125 <el-input
126126 v-else
127127 ref =" quickInputRef"
128128 v-model =" inputValue"
129129 :placeholder ="
130- startRecorderTime
130+ recorderStatus === 'START'
131131 ? `${$t('chat.inputPlaceholder.speaking')}...`
132- : recorderLoading
132+ : recorderStatus === 'TRANSCRIBING'
133133 ? `${$t('chat.inputPlaceholder.recorderLoading')}...`
134134 : $t('chat.inputPlaceholder.default')
135135 "
143143 <template v-if =" props .applicationDetails .stt_model_enable " >
144144 <span v-if =" mode === 'mobile'" >
145145 <el-button text @click =" isMicrophone = !isMicrophone" >
146+ <!-- 键盘 -->
146147 <AppIcon v-if =" isMicrophone" iconName =" app-keyboard" ></AppIcon >
147148 <el-icon v-else >
149+ <!-- 录音 -->
148150 <Microphone />
149151 </el-icon >
150152 </el-button >
154156 :disabled =" loading"
155157 text
156158 @click =" startRecording"
157- v-if =" mediaRecorderStatus "
159+ v-if =" recorderStatus === 'STOP' "
158160 >
159161 <el-icon >
160162 <Microphone />
165167 <el-text type =" info"
166168 >00:{{ recorderTime < 10 ? `0${recorderTime}` : recorderTime }}</el-text
167169 >
168- <el-button text type =" primary" @click =" stopRecording" :loading =" recorderLoading" >
170+ <el-button
171+ text
172+ type =" primary"
173+ @click =" stopRecording"
174+ :loading =" recorderStatus === 'TRANSCRIBING'"
175+ >
169176 <AppIcon iconName =" app-video-stop" ></AppIcon >
170177 </el-button >
171178 </div >
172179 </span >
173180 </template >
174181
175- <template v-if =" ( ! startRecorderTime && ! recorderLoading ) || mode === ' mobile' " >
182+ <template v-if =" recorderStatus === ' STOP ' || mode === ' mobile' " >
176183 <span v-if =" props.applicationDetails.file_upload_enable" class =" flex align-center ml-4" >
177184 <el-upload
178185 action =" #"
234241 </div >
235242</template >
236243<script setup lang="ts">
237- import { ref , computed , onMounted , nextTick } from ' vue'
244+ import { ref , computed , onMounted , nextTick , watch } from ' vue'
238245import Recorder from ' recorder-core'
239246import TouchChat from ' ./TouchChat.vue'
240247import applicationApi from ' @/api/application'
@@ -417,124 +424,147 @@ const uploadFile = async (file: any, fileList: any) => {
417424 }
418425 })
419426}
420-
427+ // 语音录制任务id
421428const intervalId = ref <any | null >(null )
429+ // 语音录制开始秒数
422430const recorderTime = ref (0 )
423- const startRecorderTime = ref (false )
424- const recorderLoading = ref (false )
431+ // START:开始录音 TRANSCRIBING:转换文字中
432+ const recorderStatus = ref <' START' | ' TRANSCRIBING' | ' STOP' >(' STOP' )
433+
425434const inputValue = ref <string >(' ' )
426435const uploadImageList = ref <Array <any >>([])
427436const uploadDocumentList = ref <Array <any >>([])
428437const uploadVideoList = ref <Array <any >>([])
429438const uploadAudioList = ref <Array <any >>([])
430- const mediaRecorderStatus = ref ( true )
439+
431440const showDelete = ref (' ' )
432441
433- // 定义响应式引用
434- const mediaRecorder = ref <any >(null )
435442const isDisabledChat = computed (
436443 () => ! (inputValue .value .trim () && (props .appId || props .applicationDetails ?.name ))
437444)
438- // 移动端语音
445+ // 是否显示移动端语音按钮
439446const isMicrophone = ref (false )
440-
447+ watch (isMicrophone , (value : boolean ) => {
448+ if (value ) {
449+ // 如果显示就申请麦克风权限
450+ recorderManage .open ()
451+ } else {
452+ // 关闭麦克风
453+ recorderManage .close ()
454+ }
455+ })
441456const TouchEnd = (bool : Boolean ) => {
442457 if (bool ) {
443458 stopRecording ()
459+ recorderStatus .value = ' STOP'
444460 } else {
445461 stopTimer ()
446- mediaRecorder .value .close ()
447- mediaRecorder .value = null
462+ recorderStatus .value = ' STOP'
448463 }
449464}
450-
451- // 开始录音
452- const startRecording = async () => {
453- try {
454- // 取消录音控制台日志
455- Recorder .CLog = function () {}
456- mediaRecorder .value = new Recorder ({
465+ // 取消录音控制台日志
466+ Recorder .CLog = function () {}
467+
468+ class RecorderManage {
469+ recorder? : any
470+ uploadRecording: (blob : Blob , duration : number ) => void
471+ constructor (uploadRecording : (blob : Blob , duration : number ) => void ) {
472+ this .uploadRecording = uploadRecording
473+ }
474+ open() {
475+ const recorder = new Recorder ({
457476 type: ' mp3' ,
458477 bitRate: 128 ,
459478 sampleRate: 16000
460479 })
461-
462- mediaRecorder .value .open (
463- () => {
464- mediaRecorder .value .start ()
465- mediaRecorderStatus .value = false
480+ if (! this .recorder ) {
481+ recorder .open (() => {
482+ this .recorder = recorder
483+ }, this .errorCallBack )
484+ }
485+ }
486+ start() {
487+ if (this .recorder ) {
488+ this .recorder .start ()
489+ recorderStatus .value = ' START'
490+ handleTimeChange ()
491+ } else {
492+ const recorder = new Recorder ({
493+ type: ' mp3' ,
494+ bitRate: 128 ,
495+ sampleRate: 16000
496+ })
497+ recorder .open (() => {
498+ this .recorder = recorder
499+ recorder .start ()
500+ recorderStatus .value = ' START'
466501 handleTimeChange ()
467- },
468- (err : any ) => {
469- stopTimer ()
470- mediaRecorder .value .close ()
471- MsgAlert (
472- t (' common.tip' ),
473- ` ${t (' chat.tip.recorderTip' )}
474- <img src="${new URL (` @/assets/tipIMG.jpg ` , import .meta .url ).href }" style="width: 100%;" /> ` ,
475- {
502+ }, this .errorCallBack )
503+ }
504+ }
505+ stop() {
506+ if (this .recorder ) {
507+ this .recorder .stop (
508+ (blob : Blob , duration : number ) => {
509+ if (mode !== ' mobile' ) {
510+ this .close ()
511+ }
512+ this .uploadRecording (blob , duration )
513+ },
514+ (err : any ) => {
515+ MsgAlert (t (' common.tip' ), err , {
476516 confirmButtonText: t (' chat.tip.confirm' ),
477517 dangerouslyUseHTMLString: true ,
478518 customClass: ' record-tip-confirm'
479- }
480- )
481- }
482- )
483- } catch (error ) {
484- MsgAlert (
485- t (' common.tip' ),
486- ` ${t (' chat.tip.recorderTip' )}
487- <img src="${new URL (` @/assets/tipIMG.jpg ` , import .meta .url ).href }" style="width: 100%;" /> ` ,
488- {
519+ })
520+ }
521+ )
522+ }
523+ }
524+ close() {
525+ if (this .recorder ) {
526+ this .recorder .close ()
527+ this .recorder = undefined
528+ }
529+ }
530+
531+ private errorCallBack(err : any , isUserNotAllow : boolean ) {
532+ if (isUserNotAllow ) {
533+ MsgAlert (t (' common.tip' ), err , {
489534 confirmButtonText: t (' chat.tip.confirm' ),
490535 dangerouslyUseHTMLString: true ,
491536 customClass: ' record-tip-confirm'
492- }
493- )
494- mediaRecorder .value .close ()
495- stopTimer ()
496- }
497- }
498-
499- // 停止录音
500- const stopRecording = () => {
501- startRecorderTime .value = false
502- recorderTime .value = 0
503- if (mediaRecorder .value ) {
504- mediaRecorderStatus .value = true
505- mediaRecorder .value .stop (
506- (blob : Blob , duration : number ) => {
507- // 测试blob是否能正常播放
508- // const link = document.createElement('a')
509- // link.href = window.URL.createObjectURL(blob)
510- // link.download = 'abc.mp3'
511- // link.click()
512- uploadRecording (blob ) // 上传录音文件
513- },
514- (err : any ) => {
515- console .error (` ${t (' chat.tip.recorderError' )}: ` , err )
516- }
517- )
537+ })
538+ } else {
539+ MsgAlert (
540+ t (' common.tip' ),
541+ ` ${err }
542+ <div style="width: 100%;height:1px;border-top:1px var(--el-border-color) var(--el-border-style);margin:10px 0;"></div>
543+ ${t (' chat.tip.recorderTip' )}
544+ <img src="${new URL (` @/assets/tipIMG.jpg ` , import .meta .url ).href }" style="width: 100%;" /> ` ,
545+ {
546+ confirmButtonText: t (' chat.tip.confirm' ),
547+ dangerouslyUseHTMLString: true ,
548+ customClass: ' record-tip-confirm'
549+ }
550+ )
551+ }
518552 }
519553}
520-
521554// 上传录音文件
522555const uploadRecording = async (audioBlob : Blob ) => {
523556 try {
524557 // 非自动发送切换输入框
525558 if (! props .applicationDetails .stt_autosend ) {
526559 isMicrophone .value = false
527560 }
528- recorderLoading .value = true
529-
561+ recorderStatus .value = ' TRANSCRIBING'
530562 const formData = new FormData ()
531563 formData .append (' file' , audioBlob , ' recording.mp3' )
532564 bus .emit (' on:transcribing' , true )
533565 applicationApi
534566 .postSpeechToText (props .applicationDetails .id as string , formData , localLoading )
535567 .then ((response ) => {
536- recorderLoading .value = false
537- mediaRecorder .value .close ()
538568 inputValue .value = typeof response .data === ' string' ? response .data : ' '
539569 // 自动发送
540570 if (props .applicationDetails .stt_autosend ) {
@@ -546,21 +576,35 @@ const uploadRecording = async (audioBlob: Blob) => {
546576 }
547577 })
548578 .catch ((error ) => {
549- recorderLoading .value = false
550579 console .error (` ${t (' chat.uploadFile.errorMessage' )}: ` , error )
551580 })
552- .finally (() => bus .emit (' on:transcribing' , false ))
581+ .finally (() => {
582+ recorderStatus .value = ' STOP'
583+ bus .emit (' on:transcribing' , false )
584+ })
553585 } catch (error ) {
554- recorderLoading .value = false
586+ recorderStatus .value = ' STOP '
555587 console .error (` ${t (' chat.uploadFile.errorMessage' )}: ` , error )
556588 }
557589}
590+ const recorderManage = new RecorderManage (uploadRecording )
591+ // 开始录音
592+ const startRecording = () => {
593+ recorderManage .start ()
594+ }
595+
596+ // 停止录音
597+ const stopRecording = () => {
598+ recorderManage .stop ()
599+ }
558600
559601const handleTimeChange = () => {
560- startRecorderTime .value = true
561602 recorderTime .value = 0
603+ if (intervalId .value ) {
604+ return
605+ }
562606 intervalId .value = setInterval (() => {
563- if (! startRecorderTime .value ) {
607+ if (recorderStatus .value === ' STOP ' ) {
564608 clearInterval (intervalId .value ! )
565609 intervalId .value = null
566610 return
@@ -569,20 +613,21 @@ const handleTimeChange = () => {
569613 recorderTime .value ++
570614
571615 if (recorderTime .value === 60 ) {
572- stopRecording ()
573- clearInterval (intervalId .value ! )
574- intervalId .value = null
575- startRecorderTime .value = false
616+ if (mode !== ' mobile' ) {
617+ stopRecording ()
618+ clearInterval (intervalId .value ! )
619+ intervalId .value = null
620+ recorderStatus .value = ' STOP'
621+ }
576622 }
577623 }, 1000 )
578624}
579625// 停止计时的函数
580626const stopTimer = () => {
581627 if (intervalId .value !== null ) {
582628 clearInterval (intervalId .value )
629+ recorderTime .value = 0
583630 intervalId .value = null
584- startRecorderTime .value = false
585- mediaRecorderStatus .value = true
586631 }
587632}
588633
@@ -598,7 +643,9 @@ function autoSendMessage() {
598643 uploadDocumentList .value = []
599644 uploadAudioList .value = []
600645 uploadVideoList .value = []
601- quickInputRef .value .textareaStyle .height = ' 45px'
646+ if (quickInputRef .value ) {
647+ quickInputRef .value .textareaStyle .height = ' 45px'
648+ }
602649}
603650
604651function sendChatHandle(event ? : any ) {
0 commit comments