1
1
<script setup lang="ts">
2
2
import Button from ' @/components/Button.vue'
3
3
import { generatTranslate , generateText } from ' @/server/api'
4
- import { verifyOpenKey } from ' @/utils'
4
+ import { base64ToBlob , blobToBase64 , verifyOpenKey } from ' @/utils'
5
5
import { useConversationStore } from ' @/stores'
6
6
7
7
interface Translates {
@@ -27,6 +27,7 @@ const {
27
27
stopRecognizeSpeech,
28
28
ssmlToSpeak,
29
29
isSynthesizing,
30
+ audioBlob,
30
31
} = useSpeechService ({ langs: store .allLanguage as any , isFetchAllVoice: false })
31
32
32
33
// states
@@ -106,10 +107,11 @@ async function onSubmit() {
106
107
107
108
store .changeConversations ([
108
109
... currentChatMessages .value ,
109
- { content: message .value , role: ' user' },
110
+ { content: message .value , role: ' user' , audioBlob: await blobToBase64 ( audioBlob . value ) },
110
111
])
112
+ const tempCurrentChatMessages = currentChatMessages .value .map (x => ({ content: x .content , role: x .role })) // 发送的请求中需去除audioBlob
111
113
const systemMessage = currentChatMessages .value [0 ]
112
- const relativeMessage = [... chatMessages . value , { content: message .value , role: ' user' }].slice (- (Number (chatRememberCount .value ))) // 保留最近的几条消息
114
+ const relativeMessage = [... tempCurrentChatMessages , { content: message .value , role: ' user' }].slice (- (Number (chatRememberCount .value ))) // 保留最近的几条消息
113
115
const prompts = [systemMessage , ... relativeMessage ] as ChatMessage []
114
116
115
117
message .value = ' '
@@ -134,13 +136,40 @@ async function onSubmit() {
134
136
store .changeLoading (false )
135
137
}
136
138
139
+ // assistant speak
137
140
function speak(content : string , index : number ) {
141
+ restartAudio ()
138
142
if (isPlaying .value || isSynthesizing .value ) return
139
143
speakIndex .value = index
140
144
text .value = content
141
145
ssmlToSpeak (content )
142
146
}
143
147
148
+ // user speak
149
+ let audio = new Audio ()
150
+
151
+ function restartAudio() {
152
+ audio .pause ()
153
+ audio .currentTime = 0
154
+ isPlaying .value = false
155
+ // audio.play()
156
+ }
157
+
158
+ function userSpeak(audioData : string , index : number ) {
159
+ if (isPlaying .value || isSynthesizing .value ) return
160
+ speakIndex .value = index
161
+ audio = new Audio (URL .createObjectURL (base64ToBlob (audioData )))
162
+ audio .play ()
163
+ audio .onplay = () => {
164
+ isPlaying .value = true
165
+ }
166
+
167
+ audio .onended = () => {
168
+ isPlaying .value = false
169
+ speakIndex .value = - 1
170
+ }
171
+ }
172
+
144
173
const recognize = async () => {
145
174
try {
146
175
console .log (' isRecognizing' , isRecognizing .value )
@@ -194,15 +223,15 @@ const translate = async (text: string, i: number) => {
194
223
<div class =" w-10 h-10" >
195
224
<img w-full h-full object-fill rounded-full :src =" item.role === 'user' ? selfAvatar : currentAvatar" alt =" " >
196
225
</div >
197
-
198
226
<div style =" flex-basis :fit-content " mx-2 >
199
227
<p p-2 my-2 chat-box >
200
228
{{ item.content }}
201
229
</p >
202
- <p v-show =" item.role === 'assistant' && translates[item.content + i]?.isShow " p-2 my-2 chat-box >
230
+ <p v-show =" translates[item.content + i]?.isShow " p-2 my-2 chat-box >
203
231
{{ translates[item.content + i]?.result }}
204
232
</p >
205
233
234
+ <!-- assistant -->
206
235
<p v-if =" item.role === 'assistant'" mt-2 flex >
207
236
<template v-if =" speakIndex !== i " >
208
237
<span class =" chat-btn" @click =" speak(item.content, i)" >
@@ -224,6 +253,31 @@ const translate = async (text: string, i: number) => {
224
253
<i icon-btn i-eos-icons:bubble-loading />
225
254
</span >
226
255
</p >
256
+
257
+ <!-- user -->
258
+ <p v-else mt-2 flex >
259
+ <template v-if =" item .audioBlob " >
260
+ <template v-if =" speakIndex !== i " >
261
+ <span class =" chat-btn" @click =" userSpeak(item.audioBlob, i)" >
262
+ <i icon-btn rotate-270 i-ic:sharp-wifi />
263
+ </span >
264
+ </template >
265
+ <template v-else >
266
+ <span v-if =" isPlaying" class =" chat-btn" @click =" restartAudio()" >
267
+ <i icon-btn rotate-270 i-svg-spinners:wifi-fade />
268
+ </span >
269
+ <span v-else class =" chat-btn" @click =" userSpeak(item.audioBlob, i)" >
270
+ <i icon-btn rotate-270 i-ic:sharp-wifi />
271
+ </span >
272
+ </template >
273
+ </template >
274
+ <span v-if =" !isTranslating || translateIndex !== i" ml-1 class =" chat-btn" @click =" translate(item.content, i)" >
275
+ <i icon-btn i-carbon:ibm-watson-language-translator />
276
+ </span >
277
+ <span v-else ml-1 class =" chat-btn" >
278
+ <i icon-btn i-eos-icons:bubble-loading />
279
+ </span >
280
+ </p >
227
281
</div >
228
282
</div >
229
283
</template >
0 commit comments