Sfoglia il codice sorgente

数字人请求支持语音生成控制
语音识别组件加入新版

liyanbo 1 mese fa
parent
commit
a8b9cba666
2 ha cambiato i file con 117 aggiunte e 4 eliminazioni
  1. 7 4
      src/api/questions.js
  2. 110 0
      src/components/ai/voice/VoiceInputDoubao.vue

+ 7 - 4
src/api/questions.js

@@ -7,7 +7,7 @@ export function CreateDialogue (data){
   return axios({
     url: "bjdxWeb/ai/create-dialogue",
     method: 'post',
-    data 
+    data
   })
 }
 
@@ -22,7 +22,9 @@ export async function sendChatMessageStream (
     enableContext,
     onMessage,
     onError,
-    onClose
+    onClose,
+    playAudio = true,
+    _attachments = undefined
 ) {
   // 获取 token
   const token = localStorage.getItem('token');
@@ -39,7 +41,8 @@ export async function sendChatMessageStream (
       conversationId,
       content,
       useContext: enableContext,
-      contentAnswer
+      contentAnswer,
+      playAudio
     }),
     onmessage: onMessage,
     onerror: onError,
@@ -115,4 +118,4 @@ export function aIGenerateCourse (data){
     method: 'post',
     data
   })
-}
+}

+ 110 - 0
src/components/ai/voice/VoiceInputDoubao.vue

@@ -0,0 +1,110 @@
+<template>
+  <div class="doubao-voice-input" :class="{ 'is-recording': isRecording }">
+    <VoiceInputApi
+      ref="voiceInputRef"
+      :input-selector="inputSelector"
+      :lang="lang"
+      :max-duration="maxDuration"
+      @voice-recognized="handleVoiceRecognized"
+      @recording-status-changed="handleRecordingStatusChanged"
+    />
+    <span class="voice-status" aria-live="polite">{{ isRecording ? '正在聆听,点击结束' : '语音输入' }}</span>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import VoiceInputApi from './VoiceInput_Api.vue'
+
+defineProps({
+  lang: { type: String, default: 'zh-CN' },
+  maxDuration: { type: Number, default: 30 },
+  inputSelector: { type: String, default: 'textarea' }
+})
+
+const emit = defineEmits(['voiceRecognized', 'recordingStatusChanged'])
+const voiceInputRef = ref(null)
+const isRecording = ref(false)
+
+const handleVoiceRecognized = (result) => emit('voiceRecognized', result)
+const handleRecordingStatusChanged = (recording) => {
+  isRecording.value = recording
+  emit('recordingStatusChanged', recording)
+}
+
+defineExpose({
+  toggleSpeechInput: () => voiceInputRef.value?.toggleSpeechInput(),
+  isRecording
+})
+</script>
+
+<style scoped lang="scss">
+.doubao-voice-input {
+  position: relative;
+  display: flex;
+  align-items: center;
+}
+
+:deep(.voice-input-container) { display: flex; }
+
+:deep(.speech-btn) {
+  display: grid !important;
+  width: 38px;
+  height: 38px;
+  padding: 0 !important;
+  place-items: center;
+  border: 0 !important;
+  border-radius: 11px !important;
+  color: #a9c6ff;
+  background: transparent !important;
+  box-shadow: none !important;
+  transition: color .2s ease, background .2s ease, transform .2s ease !important;
+}
+
+:deep(.speech-btn:hover:not(:disabled)) {
+  color: #edf4ff;
+  background: rgba(137, 180, 255, .16) !important;
+}
+
+:deep(.speech-btn .el-icon) { color: currentColor !important; font-size: 18px !important; }
+
+:deep(.speech-btn.recording) {
+  display: flex !important;
+  width: 86px;
+  padding: 0 9px !important;
+  gap: 5px !important;
+  color: #fff;
+  background: linear-gradient(135deg, #e44250, #b91f47) !important;
+  box-shadow: 0 7px 20px rgba(209, 45, 70, .3) !important;
+  animation: voice-breath 1.35s ease-in-out infinite;
+}
+
+:deep(.speech-btn.recording .waveform-container) { max-width: 43px !important; min-width: 43px; }
+:deep(.speech-btn.recording .countdown-text) { color: #fff !important; font-size: 12px !important; }
+:deep(.speech-btn.loading) { color: #c7dbff; background: rgba(137, 180, 255, .12) !important; }
+:deep(.loading-spinner) { border-color: rgba(194, 218, 255, .28) !important; border-top-color: #c6dcff !important; }
+
+.voice-status {
+  position: absolute;
+  right: 0;
+  bottom: calc(100% + 9px);
+  padding: 5px 8px;
+  border: 1px solid rgba(167, 198, 250, .24);
+  border-radius: 7px;
+  color: #dce9ff;
+  background: rgba(7, 25, 59, .92);
+  font-size: 11px;
+  line-height: 1;
+  white-space: nowrap;
+  pointer-events: none;
+  opacity: 0;
+  transform: translateY(3px);
+  transition: .2s ease;
+}
+
+.doubao-voice-input:hover .voice-status,
+.doubao-voice-input.is-recording .voice-status { opacity: 1; transform: translateY(0); }
+.doubao-voice-input.is-recording .voice-status { border-color: rgba(255, 160, 172, .36); }
+
+@keyframes voice-breath { 50% { transform: translateY(-1px); box-shadow: 0 9px 26px rgba(222, 58, 83, .5) !important; } }
+</style>