123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- <template>
- <div
- tabindex="0"
- class="chat-online relative flex size-full flex-col items-center justify-center overflow-hidden rounded-lg bg-[url('@/assets/images/chat/bg-2.png')] bg-[length:100%_100%] bg-no-repeat"
- @focus="state.isFocus = true"
- @blur="state.isFocus = false"
- @mousedown.left="onSpeakStart"
- @mouseup.left="onSpeakStop"
- @keydown.space="onSpaceSpeakStart"
- @keyup.space="onSpaceSpeakStop"
- @keyup.enter="onCall"
- @keyup.esc="$emit('hangUp')"
- >
- <div
- class="absolute top-1/10 flex flex-col items-center gap-3 text-2xl font-bold text-[#303133]"
- >
- <img src="@/assets/images/app/app-default-logo.png" class="size-42" />
- 应用名称
- </div>
- <template v-if="!state.isWaiting">
- <div class="flex flex-col items-center">
- <div>
- <soundWave :volume="volumeCpt" :num="30" />
- </div>
- <div class="mt-4 h-7 text-lg font-bold text-[var(--czr-main-color)]">
- <template v-if="state.isAsk">正在聆听 {{ durationCpt }}</template>
- <template v-else-if="state.isThink">正在思考……</template>
- <template v-else-if="state.isAnswer">正在回答</template>
- <template v-else>等待提问中……</template>
- </div>
- <div class="mt-3 flex items-center text-sm text-[#909399]">
- <SvgIcon
- name="czr_tip"
- color="var(--czr-warning-color)"
- size="20"
- class="mr-1"
- />
- <template v-if="state.isAsk"> 松开鼠标左键/Space以提问 </template>
- <template v-else-if="state.isThink"> 正在思考中,请稍后…… </template>
- <template v-else-if="state.isAnswer"> 鼠标左键/Space以打断 </template>
- <template v-else> 按住鼠标左键/Space开始提问 </template>
- </div>
- </div>
- </template>
- <div class="absolute bottom-1/15 flex w-full justify-center gap-[20%]">
- <template v-if="state.isWaiting">
- <div
- class="__hover flex flex-col items-center gap-2 text-[#0064F9]"
- @click.stop="onCall"
- >
- <div class="relative flex size-38 items-center justify-center">
- <img src="@/assets/images/chat/chat-call.png" class="size-full" />
- <div
- class="absolute z-0 size-full scale-70 animate-ping rounded-full bg-[#0064F9]/20"
- style="animation-duration: 1.5s"
- ></div>
- </div>
- <div class="text-sm font-bold">接听</div>
- <div class="text-xs">Enter</div>
- </div>
- </template>
- <div
- class="__hover flex flex-col items-center gap-2 text-[#FF5454]"
- @click.stop="$emit('hangUp')"
- >
- <div class="relative flex size-38 items-center justify-center">
- <img src="@/assets/images/chat/chat-hang.png" class="size-full" />
- </div>
- <div class="text-sm font-bold">挂断</div>
- <div class="text-xs">Esc</div>
- </div>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { computed, reactive } from 'vue'
- import soundWave from './audio/sound-wave.vue'
- import useSpeechToAudio from '@/views/chat/audio/useSpeechToAudio'
- import { ElMessage } from 'element-plus'
- import useAudio from '@/views/chat/audio/useAudio'
- const emit = defineEmits(['hangUp'])
- const state: any = reactive({
- isWaiting: true, // 等待接听
- isAsk: false, // 提问录音中
- isAnswer: false, // 聆听回答中
- isThink: false, // 思考回答中
- isFocus: false, // 语音聊天是否聚焦
- duration: 0, // 提问录音时长
- timer: null, // 开始提问防抖
- clickSpace: false, // 按下空格会一直触发,添加个判断
- answerVolume: 0, //回答音频的音量
- answerAudioControl: null,
- })
- const durationCpt = computed(() => {
- const minutes = Math.floor(state.duration / 60)
- const seconds = Math.floor(state.duration % 60)
- return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
- })
- const onSpeak = ({ duration }) => {
- state.isAsk = true
- state.duration = duration
- }
- const onEnd = (audio) => {
- state.isAsk = false
- if (state.duration < 2) {
- ElMessage.warning('提问时长过短,请持续提问!')
- return
- }
- console.log(audio)
- state.isThink = true
- setTimeout(() => {
- state.isThink = false
- useAudio(
- // 'https://lf-bot-studio-plugin-resource.coze.cn/obj/bot-studio-platform-plugin-tos/sami_podcast/tts/134d9d71475043199ef7372567fa9689.mp3',
- 'https://lf-bot-studio-plugin-resource.coze.cn/obj/bot-studio-platform-plugin-tos/artist/image/32afe00820aa466192ce0f7b6495e946.mp3',
- () => {
- state.isAnswer = false
- },
- ).then(({ volume, play, stop, pause }: any) => {
- state.isAnswer = true
- state.answerAudioControl = {
- play,
- stop,
- pause,
- }
- state.answerVolume = volume
- play()
- })
- }, 1000)
- }
- const { speak, stop, volume } = useSpeechToAudio({
- onEnd,
- onSpeak,
- timeout: 0,
- })
- const volumeCpt = computed(() => {
- if (state.isAsk) {
- return volume.value
- }
- if (state.isAnswer) {
- return state.answerVolume
- }
- return 0
- })
- const onCall = () => {
- if (state.isWaiting) {
- state.isWaiting = false
- state.isAsk = false
- state.isThink = false
- state.isAnswer = false
- }
- }
- const onSpeakStart = () => {
- if (!state.isWaiting && !state.isAsk && !state.clickSpace) {
- state.timer = setTimeout(() => {
- if (state.isAnswer) {
- onStopAnswer()
- }
- speak()
- }, 300)
- }
- }
- const onSpeakStop = () => {
- clearTimeout(state.timer)
- if (!state.isWaiting && state.isAsk) {
- stop()
- }
- }
- const onSpaceSpeakStart = () => {
- if (!state.isThink) {
- onSpeakStart()
- state.clickSpace = true
- }
- }
- const onSpaceSpeakStop = () => {
- onSpeakStop()
- state.clickSpace = false
- }
- const onStopAnswer = () => {
- state.isAnswer = false
- state.answerAudioControl?.stop()
- }
- </script>
- <style lang="scss" scoped>
- .chat-online {
- * {
- outline: none;
- }
- }
- </style>
|