@phonghq/go-chat 1.0.11 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/icons/IconAiCheck.vue.d.ts +2 -0
- package/dist/assets/icons/call/IconClose.vue.d.ts +2 -0
- package/dist/assets/icons/call/IconSoundDownload.vue.d.ts +2 -0
- package/dist/chat/App.vue.d.ts +7 -2
- package/dist/chat/page/customer-detail/CustomerDetail.vue.d.ts +1 -1
- package/dist/chat/page/home/ChatList.vue.d.ts +29 -1
- package/dist/chat/page/home/ChatMessage.vue.d.ts +2 -1
- package/dist/chat/page/home/Home.vue.d.ts +1 -1
- package/dist/components/chat/ScrollEvent/ScrollEvent.vue.d.ts +1 -0
- package/dist/components/chat/call/Calling.vue.d.ts +8 -2
- package/dist/components/chat/common/input/InputSearch.vue.d.ts +1 -1
- package/dist/components/chat/select/SelectBase.vue.d.ts +22 -0
- package/dist/components/common/drawer/DrawerBase.vue.d.ts +1 -1
- package/dist/components/common/modal/ModalBase.vue.d.ts +1 -1
- package/dist/composable/useCallHelper.d.ts +7 -2
- package/dist/composable/useInitData.d.ts +2 -4
- package/dist/composable/usePlivo.d.ts +9 -0
- package/dist/go-chat.es.js +14676 -12322
- package/dist/go-chat.umd.js +44 -14
- package/dist/plugins/websocket.d.ts +12 -2
- package/dist/router/index.d.ts +2 -0
- package/dist/style.css +1 -1
- package/dist/test/assets/icons/IconAiCheck.vue.js +28 -0
- package/dist/test/assets/icons/call/IconClose.vue.js +26 -0
- package/dist/test/assets/icons/call/IconMic.vue.js +9 -9
- package/dist/test/assets/icons/call/IconSoundDownload.vue.js +50 -0
- package/dist/test/chat/App.vue.js +144 -90
- package/dist/test/chat/page/customer-detail/CustomerDetail.vue.js +6 -5
- package/dist/test/chat/page/home/ChatList.vue.js +30 -9
- package/dist/test/chat/page/home/ChatMessage.vue.js +23 -12
- package/dist/test/chat/page/home/Home.vue.js +4 -3
- package/dist/test/chat/page/home/NewCustomer.vue.js +0 -12
- package/dist/test/components/chat/ScrollEvent/ScrollEvent.vue.js +7 -1
- package/dist/test/components/chat/call/Calling.vue.js +277 -111
- package/dist/test/components/chat/common/input/InputSearch.vue.js +2 -2
- package/dist/test/components/chat/select/SelectBase.vue.js +98 -0
- package/dist/test/components/common/drawer/DrawerBaseCustom.vue.js +0 -1
- package/dist/test/composable/data.json +32 -0
- package/dist/test/composable/useCallHelper.js +146 -33
- package/dist/test/composable/useDigibot.js +1 -1
- package/dist/test/composable/useInitData.js +17 -12
- package/dist/test/composable/usePlivo.js +138 -0
- package/dist/test/constant/color.js +1 -1
- package/dist/test/plugins/axios.js +2 -1
- package/dist/test/plugins/mqtt.js +11 -8
- package/dist/test/plugins/websocket.js +108 -19
- package/dist/test/router/index.js +39 -0
- package/dist/test/types/call.js +10 -1
- package/dist/test/utils/chat/auth.js +10 -2
- package/dist/test/utils/chat/call.js +48 -8
- package/dist/test/utils/chat/phone-string.js +4 -0
- package/dist/test/utils/chat/user.js +7 -2
- package/dist/test/views/NotFound.vue.js +47 -0
- package/dist/test/views/TenantPhone.vue.js +270 -0
- package/dist/types/call.d.ts +9 -0
- package/dist/types/chat/global.d.ts +4 -0
- package/dist/utils/chat/auth.d.ts +5 -1
- package/dist/utils/chat/call.d.ts +6 -2
- package/dist/utils/chat/phone-string.d.ts +1 -0
- package/dist/utils/chat/user.d.ts +4 -0
- package/dist/views/NotFound.vue.d.ts +2 -0
- package/dist/views/TenantPhone.vue.d.ts +2 -0
- package/package.json +2 -1
- package/dist/composable/TestSound.d.ts +0 -64
- package/dist/test/composable/TestSound.js +0 -196
package/dist/types/call.d.ts
CHANGED
|
@@ -2,3 +2,12 @@ export type IResCall = {
|
|
|
2
2
|
from: string;
|
|
3
3
|
to: string;
|
|
4
4
|
};
|
|
5
|
+
export declare const enum PLIVO_CALL_STATUS {
|
|
6
|
+
CONNECTING = "Connecting...",
|
|
7
|
+
CALLING = "calling",
|
|
8
|
+
RINGING = "ringing",
|
|
9
|
+
CONNECT_FAILED = "failed",
|
|
10
|
+
CALL_START = "in-progress",
|
|
11
|
+
CALL_END = "completed",
|
|
12
|
+
NO_ANSWER = "no-answer"
|
|
13
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ComputedRef } from 'vue';
|
|
1
2
|
export type GoChatProps = {
|
|
2
3
|
token?: string;
|
|
3
4
|
id?: string;
|
|
@@ -5,4 +6,7 @@ export type GoChatProps = {
|
|
|
5
6
|
response?: PAGE_RESPONSE;
|
|
6
7
|
isLib?: boolean;
|
|
7
8
|
};
|
|
9
|
+
export interface GoChatInstance {
|
|
10
|
+
unreadCount: ComputedRef<number>;
|
|
11
|
+
}
|
|
8
12
|
export type PAGE_RESPONSE = 'mobile' | 'tablet';
|
|
@@ -40,6 +40,10 @@ export declare const dataLoginLink: import("vue").Ref<{
|
|
|
40
40
|
export declare const setDataLogin: (id: string, token: string, domain: string) => void;
|
|
41
41
|
export declare const loginTenant: (body: IBodyLoginTenant) => Promise<any>;
|
|
42
42
|
export declare const loginLink: (params: IPramsLoginLink) => Promise<any>;
|
|
43
|
-
export declare const getProfile: () => Promise<
|
|
43
|
+
export declare const getProfile: () => Promise<IResProfile>;
|
|
44
|
+
export declare const submitTenantPhone: (body: {
|
|
45
|
+
phone: string;
|
|
46
|
+
tenant_id: string;
|
|
47
|
+
}) => Promise<any>;
|
|
44
48
|
export declare const logout: () => Promise<void>;
|
|
45
49
|
export {};
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { IResUser } from '../../types/message';
|
|
2
|
-
import type { IResCall } from '../../types/call';
|
|
3
2
|
export declare const getIceService: () => Promise<import("axios").AxiosResponse<any, any>>;
|
|
4
|
-
export declare const callClient: (
|
|
3
|
+
export declare const callClient: (data: {
|
|
4
|
+
call_uuid: string;
|
|
5
|
+
clientId: string;
|
|
6
|
+
}) => Promise<void>;
|
|
5
7
|
export declare const callOutBound: (user: IResUser) => Promise<void>;
|
|
6
8
|
export declare const plivoCall: (user: IResUser) => Promise<any>;
|
|
7
9
|
export declare const plivoEndCall: (uuid: string) => Promise<void>;
|
|
10
|
+
export declare const downloadRecord: (url_pub: string) => Promise<void>;
|
|
11
|
+
export declare const getPlivoAccessToken: () => Promise<any>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const formatPhone10number: (phone: string, dial: string) => string;
|
|
@@ -187,6 +187,10 @@ export declare const userHistory: import("vue").Ref<{
|
|
|
187
187
|
visit_count: number;
|
|
188
188
|
} | null>;
|
|
189
189
|
export declare const getUserHistory: (phone: string) => Promise<GetCustomerHistoryResponse>;
|
|
190
|
+
export declare const getUserDetailChat: (params: {
|
|
191
|
+
phone: string;
|
|
192
|
+
client_id: string;
|
|
193
|
+
}) => Promise<any>;
|
|
190
194
|
export declare function getItemsByYear<T = any>(data: any, key?: string): {
|
|
191
195
|
year: string;
|
|
192
196
|
items: T[];
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
export default _default;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
export default _default;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phonghq/go-chat",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"private": false,
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"lucide-vue-next": "^0.536.0",
|
|
35
35
|
"mqtt": "^4.3.7",
|
|
36
36
|
"pinia": "^2.1.7",
|
|
37
|
+
"plivo-browser-sdk": "^2.2.21",
|
|
37
38
|
"prettier": "3.3.3",
|
|
38
39
|
"reka-ui": "^2.6.0",
|
|
39
40
|
"tailwind-merge": "^3.3.1",
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
export declare function useAudioStream(wsUrl: string): {
|
|
2
|
-
ws: import("vue").Ref<{
|
|
3
|
-
binaryType: BinaryType;
|
|
4
|
-
readonly bufferedAmount: number;
|
|
5
|
-
readonly extensions: string;
|
|
6
|
-
onclose: ((this: WebSocket, ev: CloseEvent) => any) | null;
|
|
7
|
-
onerror: ((this: WebSocket, ev: Event) => any) | null;
|
|
8
|
-
onmessage: ((this: WebSocket, ev: MessageEvent<any>) => any) | null;
|
|
9
|
-
onopen: ((this: WebSocket, ev: Event) => any) | null;
|
|
10
|
-
readonly protocol: string;
|
|
11
|
-
readonly readyState: number;
|
|
12
|
-
readonly url: string;
|
|
13
|
-
close: (code?: number | undefined, reason?: string | undefined) => void;
|
|
14
|
-
send: (data: string | ArrayBufferView | Blob | ArrayBufferLike) => void;
|
|
15
|
-
readonly CONNECTING: 0;
|
|
16
|
-
readonly OPEN: 1;
|
|
17
|
-
readonly CLOSING: 2;
|
|
18
|
-
readonly CLOSED: 3;
|
|
19
|
-
addEventListener: {
|
|
20
|
-
<K extends keyof WebSocketEventMap>(type: K, listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any, options?: boolean | AddEventListenerOptions | undefined): void;
|
|
21
|
-
(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | undefined): void;
|
|
22
|
-
};
|
|
23
|
-
removeEventListener: {
|
|
24
|
-
<K_1 extends keyof WebSocketEventMap>(type: K_1, listener: (this: WebSocket, ev: WebSocketEventMap[K_1]) => any, options?: boolean | EventListenerOptions | undefined): void;
|
|
25
|
-
(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions | undefined): void;
|
|
26
|
-
};
|
|
27
|
-
dispatchEvent: (event: Event) => boolean;
|
|
28
|
-
} | null, WebSocket | {
|
|
29
|
-
binaryType: BinaryType;
|
|
30
|
-
readonly bufferedAmount: number;
|
|
31
|
-
readonly extensions: string;
|
|
32
|
-
onclose: ((this: WebSocket, ev: CloseEvent) => any) | null;
|
|
33
|
-
onerror: ((this: WebSocket, ev: Event) => any) | null;
|
|
34
|
-
onmessage: ((this: WebSocket, ev: MessageEvent<any>) => any) | null;
|
|
35
|
-
onopen: ((this: WebSocket, ev: Event) => any) | null;
|
|
36
|
-
readonly protocol: string;
|
|
37
|
-
readonly readyState: number;
|
|
38
|
-
readonly url: string;
|
|
39
|
-
close: (code?: number | undefined, reason?: string | undefined) => void;
|
|
40
|
-
send: (data: string | ArrayBufferView | Blob | ArrayBufferLike) => void;
|
|
41
|
-
readonly CONNECTING: 0;
|
|
42
|
-
readonly OPEN: 1;
|
|
43
|
-
readonly CLOSING: 2;
|
|
44
|
-
readonly CLOSED: 3;
|
|
45
|
-
addEventListener: {
|
|
46
|
-
<K extends keyof WebSocketEventMap>(type: K, listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any, options?: boolean | AddEventListenerOptions | undefined): void;
|
|
47
|
-
(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | undefined): void;
|
|
48
|
-
};
|
|
49
|
-
removeEventListener: {
|
|
50
|
-
<K_1 extends keyof WebSocketEventMap>(type: K_1, listener: (this: WebSocket, ev: WebSocketEventMap[K_1]) => any, options?: boolean | EventListenerOptions | undefined): void;
|
|
51
|
-
(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions | undefined): void;
|
|
52
|
-
};
|
|
53
|
-
dispatchEvent: (event: Event) => boolean;
|
|
54
|
-
} | null>;
|
|
55
|
-
statusText: import("vue").Ref<string, string>;
|
|
56
|
-
micLevel: import("vue").Ref<number, number>;
|
|
57
|
-
recording: import("vue").Ref<boolean, boolean>;
|
|
58
|
-
connect: () => void;
|
|
59
|
-
disconnect: () => void;
|
|
60
|
-
resumeAudio: () => void;
|
|
61
|
-
enqueueSpeakerChunk: (arrayBuffer: ArrayBuffer) => Promise<void>;
|
|
62
|
-
processSpeakerQueue: () => void;
|
|
63
|
-
startRecording: () => Promise<void>;
|
|
64
|
-
};
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import { ref, onBeforeUnmount } from 'vue';
|
|
2
|
-
export function useAudioStream(wsUrl) {
|
|
3
|
-
const SAMPLE_RATE = 24000;
|
|
4
|
-
const CHUNK_SIZE = 480;
|
|
5
|
-
const PREBUFFER_SEC = 0.4;
|
|
6
|
-
let audioCtx;
|
|
7
|
-
let processor;
|
|
8
|
-
let input;
|
|
9
|
-
let stream;
|
|
10
|
-
const ws = ref(null);
|
|
11
|
-
const statusText = ref('Tap to Speak with Vico');
|
|
12
|
-
// Speaker queue
|
|
13
|
-
let speakerQueue = [];
|
|
14
|
-
let nextPlayTime = 0;
|
|
15
|
-
// UI state
|
|
16
|
-
const recording = ref(false);
|
|
17
|
-
const micLevel = ref(0);
|
|
18
|
-
// Status management
|
|
19
|
-
const STATUS = {
|
|
20
|
-
IDLE: 'Tap to Speak with Vico',
|
|
21
|
-
CONNECTING: 'Connecting...',
|
|
22
|
-
LISTENING: 'Listening...',
|
|
23
|
-
SPEAKING: 'Speaking...'
|
|
24
|
-
};
|
|
25
|
-
let currentStatus = STATUS.IDLE;
|
|
26
|
-
function setStatus(newStatus) {
|
|
27
|
-
if (currentStatus !== newStatus) {
|
|
28
|
-
currentStatus = newStatus;
|
|
29
|
-
statusText.value = newStatus;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
// 🎤 Float32 → PCM16
|
|
33
|
-
function floatTo16BitPCM(float32Array) {
|
|
34
|
-
const buffer = new ArrayBuffer(float32Array.length * 2);
|
|
35
|
-
const view = new DataView(buffer);
|
|
36
|
-
for (let i = 0; i < float32Array.length; i++) {
|
|
37
|
-
let s = Math.max(-1, Math.min(1, float32Array[i]));
|
|
38
|
-
view.setInt16(i * 2, s < 0 ? s * 0x8000 : s * 0x7fff, true);
|
|
39
|
-
}
|
|
40
|
-
return buffer;
|
|
41
|
-
}
|
|
42
|
-
// 🔊 PCM16 → Float32
|
|
43
|
-
function int16ToFloat32(int16Array) {
|
|
44
|
-
const float32 = new Float32Array(int16Array.length);
|
|
45
|
-
for (let i = 0; i < int16Array.length; i++) {
|
|
46
|
-
float32[i] = int16Array[i] / 32768;
|
|
47
|
-
}
|
|
48
|
-
return float32;
|
|
49
|
-
}
|
|
50
|
-
// 📥 enqueue speaker chunk
|
|
51
|
-
async function enqueueSpeakerChunk(arrayBuffer) {
|
|
52
|
-
const int16View = new Int16Array(arrayBuffer);
|
|
53
|
-
const float32Data = int16ToFloat32(int16View);
|
|
54
|
-
speakerQueue.push(float32Data);
|
|
55
|
-
}
|
|
56
|
-
// 🔊 process queue
|
|
57
|
-
function processSpeakerQueue() {
|
|
58
|
-
try {
|
|
59
|
-
if (speakerQueue.length > 0) {
|
|
60
|
-
const chunk = speakerQueue.shift();
|
|
61
|
-
if (chunk) {
|
|
62
|
-
const audioBuffer = audioCtx.createBuffer(1, chunk.length, SAMPLE_RATE);
|
|
63
|
-
audioBuffer.getChannelData(0).set(chunk);
|
|
64
|
-
const source = audioCtx.createBufferSource();
|
|
65
|
-
source.buffer = audioBuffer;
|
|
66
|
-
source.connect(audioCtx.destination);
|
|
67
|
-
if (nextPlayTime < audioCtx.currentTime + 0.05) {
|
|
68
|
-
nextPlayTime = audioCtx.currentTime + PREBUFFER_SEC;
|
|
69
|
-
}
|
|
70
|
-
source.start();
|
|
71
|
-
nextPlayTime += audioBuffer.duration;
|
|
72
|
-
setStatus(STATUS.SPEAKING);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
else if (recording.value) {
|
|
76
|
-
setStatus(STATUS.LISTENING);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
catch (e) {
|
|
80
|
-
console.log(e);
|
|
81
|
-
}
|
|
82
|
-
requestAnimationFrame(processSpeakerQueue);
|
|
83
|
-
}
|
|
84
|
-
// 🎤 start mic
|
|
85
|
-
async function startRecording() {
|
|
86
|
-
audioCtx = new AudioContext({ sampleRate: SAMPLE_RATE });
|
|
87
|
-
return;
|
|
88
|
-
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
89
|
-
input = audioCtx.createMediaStreamSource(stream);
|
|
90
|
-
processor = audioCtx.createScriptProcessor(1024, 1, 1);
|
|
91
|
-
processor.onaudioprocess = (e) => {
|
|
92
|
-
if (!ws.value || ws.value.readyState !== WebSocket.OPEN)
|
|
93
|
-
return;
|
|
94
|
-
const inputData = e.inputBuffer.getChannelData(0);
|
|
95
|
-
// calculate mic level
|
|
96
|
-
let sum = 0;
|
|
97
|
-
for (let i = 0; i < inputData.length; i++)
|
|
98
|
-
sum += inputData[i] ** 2;
|
|
99
|
-
micLevel.value = Math.sqrt(sum / inputData.length);
|
|
100
|
-
// chunking & send
|
|
101
|
-
for (let i = 0; i < inputData.length; i += CHUNK_SIZE) {
|
|
102
|
-
const slice = inputData.slice(i, i + CHUNK_SIZE);
|
|
103
|
-
const binaryChunk = floatTo16BitPCM(slice);
|
|
104
|
-
ws.value.send(binaryChunk);
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
input.connect(processor);
|
|
108
|
-
processor.connect(audioCtx.destination);
|
|
109
|
-
recording.value = true;
|
|
110
|
-
setStatus(STATUS.LISTENING);
|
|
111
|
-
}
|
|
112
|
-
// ⏹ stop mic
|
|
113
|
-
function stopRecording() {
|
|
114
|
-
recording.value = false;
|
|
115
|
-
processor?.disconnect();
|
|
116
|
-
input?.disconnect();
|
|
117
|
-
stream?.getTracks().forEach((t) => t.stop());
|
|
118
|
-
if (audioCtx && audioCtx.state !== 'closed') {
|
|
119
|
-
audioCtx
|
|
120
|
-
.close()
|
|
121
|
-
.then(() => console.log('AudioContext closed successfully.'))
|
|
122
|
-
.catch((err) => console.error('Error closing AudioContext:', err))
|
|
123
|
-
.finally(() => (micLevel.value = 0));
|
|
124
|
-
}
|
|
125
|
-
setStatus(STATUS.IDLE);
|
|
126
|
-
}
|
|
127
|
-
const getAudioContext = () => {
|
|
128
|
-
if (!audioCtx || audioCtx.state === 'closed') {
|
|
129
|
-
audioCtx = new AudioContext({ sampleRate: SAMPLE_RATE });
|
|
130
|
-
}
|
|
131
|
-
return audioCtx;
|
|
132
|
-
};
|
|
133
|
-
const safeResumeAudio = () => {
|
|
134
|
-
const ctx = getAudioContext();
|
|
135
|
-
if (ctx.state === 'suspended') {
|
|
136
|
-
ctx.resume().catch((err) => console.error('Error resuming AudioContext:', err));
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
function connect() {
|
|
140
|
-
if (ws.value && ws.value.readyState === WebSocket.OPEN)
|
|
141
|
-
return;
|
|
142
|
-
setStatus(STATUS.CONNECTING);
|
|
143
|
-
ws.value = new WebSocket(wsUrl);
|
|
144
|
-
ws.value.binaryType = 'arraybuffer';
|
|
145
|
-
ws.value.onopen = () => {
|
|
146
|
-
console.log('✅ WS connected');
|
|
147
|
-
startRecording();
|
|
148
|
-
processSpeakerQueue();
|
|
149
|
-
};
|
|
150
|
-
ws.value.onmessage = (event) => {
|
|
151
|
-
if (event.data instanceof ArrayBuffer) {
|
|
152
|
-
enqueueSpeakerChunk(event.data);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
if (typeof event.data === 'string') {
|
|
156
|
-
try {
|
|
157
|
-
const msg = JSON.parse(event.data);
|
|
158
|
-
if (msg.type === 'AudioStop' || msg.code === 'UserStartedSpeaking') {
|
|
159
|
-
// speakerQueue.length = 0
|
|
160
|
-
nextPlayTime = 0;
|
|
161
|
-
setStatus(STATUS.LISTENING);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
catch (err) {
|
|
166
|
-
console.warn('⚠️ Parse JSON error:', err, event.data);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
console.log('⚠️ Unknown WS message, closing...');
|
|
170
|
-
disconnect();
|
|
171
|
-
};
|
|
172
|
-
ws.value.onclose = () => {
|
|
173
|
-
console.log('❌ WS closed');
|
|
174
|
-
stopRecording();
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
function disconnect() {
|
|
178
|
-
ws.value?.close();
|
|
179
|
-
stopRecording();
|
|
180
|
-
}
|
|
181
|
-
onBeforeUnmount(() => {
|
|
182
|
-
disconnect();
|
|
183
|
-
});
|
|
184
|
-
return {
|
|
185
|
-
ws,
|
|
186
|
-
statusText,
|
|
187
|
-
micLevel,
|
|
188
|
-
recording,
|
|
189
|
-
connect,
|
|
190
|
-
disconnect,
|
|
191
|
-
resumeAudio: safeResumeAudio,
|
|
192
|
-
enqueueSpeakerChunk,
|
|
193
|
-
processSpeakerQueue,
|
|
194
|
-
startRecording
|
|
195
|
-
};
|
|
196
|
-
}
|