| <script setup lang="ts"> |
| |
| import router from "@/router.ts"; |
| import { useSettingsStore } from "@/stores/config.ts"; |
| import { onMounted, onUnmounted, ref, reactive } from "vue"; |
| import { SettingTwoTone } from "@ant-design/icons-vue"; |
| import DynamicBall from "@/views/Home/Components/DynamicBall.vue"; |
| import ChatText from "@/views/Home/Components/ChatText.vue"; |
| import axios from "axios"; |
| import { test_server } from "@/config/client_config.ts"; |
| |
| const base_url = axios.defaults.baseURL |
| |
| const settingsStore = useSettingsStore() |
| |
| import mic_on from "@/assets/microphone.png" |
| import mic_off from "@/assets/microphone_off.png" |
| import text_on from "@/assets/text.png" |
| import text_off from "@/assets/text_off.png" |
| import close from "@/assets/close.png" |
| import download from "@/assets/download.png" |
| |
| const host = import.meta.env.PROD ? window.location.host : test_server |
| |
| let ws_prefix = 'ws' |
| if (host.startsWith('127.0.0.1') || host.startsWith('localhost')) { |
| ws_prefix = 'ws' |
| } else { |
| ws_prefix = 'wss' |
| } |
| const ws_url = `${ws_prefix}://` + host + `/api/v1/ws` |
| console.warn('ws_url: ', ws_url) |
| |
| const sock = ref(null) |
| const startWebSock = async () => { |
| console.warn('start websocket ...') |
| |
| |
| if (sock.value && sock.value.readyState !== WebSocket.CLOSED) { |
| |
| sock.value.close(); |
| } |
| |
| |
| const socket_url = `${ws_url}` |
| |
| sock.value = new WebSocket(socket_url) |
| |
| sock.value.binaryType = "arraybuffer"; |
| console.warn('created web socket ...') |
| |
| sock.value.addEventListener('open', () => { |
| console.log('WebSocket 连接成功'); |
| }); |
| |
| sock.value.addEventListener('close', () => { |
| console.log('WebSocket 连接已关闭'); |
| }); |
| |
| sock.value.onclose = (event: any) => { |
| console.log('code:', event.code, 'reason:', event.reason, 'wasClean:', event.wasClean) |
| |
| console.log('WebSocket 连接已关闭:', event); |
| }; |
| |
| sock.value.addEventListener('error', (error) => { |
| console.error('WebSocket 连接错误:', error); |
| }); |
| |
| sock.value.addEventListener('message', (event) => { |
| try { |
| const data = JSON.parse(event.data) |
| console.log('WebSocket 收到消息:', data); |
| if (data) { |
| updateViewData(data) |
| } |
| } catch (e) { |
| console.error("解析 WebSocket 消息失败:", e, "原始数据:", event.data); |
| } |
| }); |
| } |
| |
| |
| const stopWebSock = async () => { |
| if (sock.value) { |
| console.log("主动关闭 WebSocket 连接"); |
| |
| sock.value.close(1000, "User closed connection"); |
| sock.value = null; |
| } |
| } |
| |
| |
| const currentSession: any = reactive([]); |
| |
| const updateViewData = (data: any) => { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (data) { |
| if (data['message_type'] === 'question') { |
| |
| if (data.question && data.question.length > 0) { |
| |
| |
| if (currentSession.length > 0 && |
| currentSession[currentSession.length - 1].type === 'question' && |
| currentSession[currentSession.length - 1].task_id === data.task_id) { |
| currentSession[currentSession.length - 1].content = data.question; |
| } else { |
| |
| currentSession.push({ |
| type: 'question', |
| content: data.question, |
| task_id: data.task_id |
| }); |
| } |
| } |
| |
| } else if (data['message_type'] === 'answer') { |
| if (data.answer_index === 0) { |
| currentSession.push({ |
| type: 'answer', |
| content: data.answer, |
| task_id: data.task_id, |
| }); |
| } else { |
| |
| const lastAnswer = currentSession[currentSession.length - 1]; |
| if (lastAnswer && |
| lastAnswer.task_id === data.task_id && |
| lastAnswer.content !== null && lastAnswer.content.length > 0) { |
| if (lastAnswer.type === 'answer') { |
| lastAnswer.content += data.answer; |
| } else { |
| console.warn('Received answer without a matching question in the current session'); |
| currentSession.push({ |
| type: data.type, |
| content: data.answer, |
| task_id: data.task_id, |
| }); |
| } |
| |
| } else { |
| console.warn('Received answer without a matching question in the current session'); |
| } |
| } |
| } |
| } |
| } |
| |
| |
| onMounted( async () => { |
| |
| await startWebSock(); |
| }) |
| |
| onUnmounted( async () => { |
| console.warn('onUnmounted, stop web socket ...') |
| stopWebSock(); |
| }); |
| |
| const backAction = async () => { |
| const state = await stopAudioCapture(); |
| if (!state) { |
| console.error('Failed to stop audio chat system service'); |
| return; |
| } |
| router.replace('/') |
| } |
| |
| const stopActionLoading = ref<boolean>(false); |
| const stopAudioCapture = async () => { |
| try { |
| stopActionLoading.value = true; |
| |
| const response = await fetch(`${base_url}/system/stop`, { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: null |
| }); |
| if (!response.ok) { |
| |
| throw new Error(`HTTP error! status: ${response.status}`); |
| } |
| const data = await response.json(); |
| console.log('ASR Instance stopped successfully:', data); |
| return true; |
| } catch (error) { |
| console.error('Error stop record :', error); |
| return false; |
| } finally { |
| stopActionLoading.value = false; |
| } |
| } |
| |
| const micActionLoading = ref<boolean>(false); |
| const pauseAudioCapture = async () => { |
| try { |
| micActionLoading.value = true; |
| const response = await fetch(`${base_url}/system/pause`, { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: null |
| }); |
| if (!response.ok) { |
| |
| throw new Error(`HTTP error! status: ${response.status}`); |
| } |
| const data = await response.json(); |
| console.log('Mic paused successfully:', data); |
| return true; |
| } catch (error) { |
| console.error('Error pause mic:', error); |
| return false; |
| } finally { |
| micActionLoading.value = false; |
| } |
| } |
| |
| const resumeAudioCapture = async () => { |
| try { |
| micActionLoading.value = true; |
| const response = await fetch(`${base_url}/system/resume`, { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: null |
| }); |
| if (!response.ok) { |
| |
| throw new Error(`HTTP error! status: ${response.status}`); |
| } |
| const data = await response.json(); |
| console.log('Mic resume successfully:', data); |
| return true; |
| } catch (error) { |
| console.error('Error resume mic:', error); |
| return false; |
| } finally { |
| micActionLoading.value = false; |
| } |
| } |
| |
| |
| const mic_working = ref<boolean>(true); |
| const toggleMic = async () => { |
| if (mic_working.value) { |
| const state = await pauseAudioCapture(); |
| if (!state) { |
| console.error('Failed to stop audio chat system service'); |
| return; |
| } |
| } else { |
| const state = await resumeAudioCapture(); |
| if (!state) { |
| console.error('Failed to start audio chat system service'); |
| return; |
| } |
| } |
| mic_working.value = !mic_working.value; |
| console.log('mic_state', mic_working.value); |
| }; |
| const text_state = ref<boolean>(false); |
| const toggleText = () => { |
| text_state.value = !text_state.value; |
| console.log('text_state', text_state.value); |
| }; |
| |
| |
| </script> |
|
|
| <template> |
| <div class="chat-wrapper"> |
| <div class="content"> |
| <div v-if="!text_state"> |
| <DynamicBall :isPlaying="mic_working" /> |
| </div> |
| <div v-if="text_state"> |
| <ChatText :chatContent="currentSession" :isPlaying="mic_working" /> |
| </div> |
| </div> |
|
|
| <div class="actions"> |
| <div class="holder"> |
| <span> </span> |
| </div> |
| <div class="btns"> |
| <a-button type="text" style="width:64px; height: 64px;" :loading="micActionLoading" @click="toggleMic"> |
| <template #icon> |
| <img :src="mic_working == true ? mic_on : mic_off" width="50" height="50" alt="mic_on" /> |
| </template> |
| </a-button> |
| <a-button type="text" style="width:64px; height: 64px;" @click="toggleText"> |
| <template #icon> |
| <img :src="text_state == true ? text_on : text_off" width="50" height="50" alt="text_off" /> |
| </template> |
| </a-button> |
| <a-button type="text" style="width:64px; height: 64px;" :loading="stopActionLoading" @click="backAction"> |
| <template #icon> |
| <img :src="close" width="50" height="50" alt="close" /> |
| </template> |
| </a-button> |
| </div> |
| <div class="download-wrapper"> |
| |
| |
| |
| |
| |
| </div> |
| </div> |
| </div> |
| </template> |
|
|
| <style lang="scss" scoped> |
| .chat-wrapper { |
| width: 100%; |
| height: 100%; |
| background-image: url('@/assets/bg.png'); |
| background-repeat: no-repeat; |
| background-attachment: fixed; |
| background-size: cover; |
| background-position: center; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: space-between; |
| color: #fff; |
| |
| .content { |
| width: 100%; |
| height: auto; |
| display: flex; |
| flex-direction: column; |
| justify-content: space-around; |
| |
| .inner-content { |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| text-align: center; |
| padding: 20px; |
| |
| .text-box { |
| color: #000; |
| margin-bottom: 36px; |
| |
| .title { |
| font-size: 24px; |
| font-weight: 600; |
| margin-bottom: 24px; |
| } |
| |
| .sub-title { |
| font-size: 15px; |
| margin-top: 10px; |
| } |
| } |
| |
| .btn-box { |
| width: 224px; |
| height: 80px; |
| } |
| } |
| } |
| |
| .actions { |
| width: 100%; |
| height: 100px; |
| |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| |
| .holder { |
| width: 64px; |
| height: 48px; |
| } |
| .btns { |
| width: 450px; |
| height: 96px; |
| display: flex; |
| justify-content: space-around; |
| align-items: flex-start; |
| } |
| .download-wrapper { |
| width: 64px; |
| height: 64px; |
| display: flex; |
| justify-content: flex-start; |
| align-items: center; |
| margin-right: 0; |
| |
| img { |
| width: 24px; |
| height: 24px; |
| } |
| } |
| } |
| } |
| </style> |
|
|