@jchaffin/voicekit 0.2.0 → 0.2.2
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/adapters/deepgram.js +12 -6
- package/dist/adapters/deepgram.mjs +12 -6
- package/dist/adapters/elevenlabs.js +11 -5
- package/dist/adapters/elevenlabs.mjs +11 -5
- package/dist/adapters/livekit.js +9 -3
- package/dist/adapters/livekit.mjs +9 -3
- package/dist/adapters/openai.js +5 -1
- package/dist/adapters/openai.mjs +5 -1
- package/dist/index.js +18 -14
- package/dist/index.mjs +18 -14
- package/package.json +1 -1
|
@@ -73,12 +73,18 @@ var DeepgramSession = class extends EventEmitter {
|
|
|
73
73
|
if (this.options.model) url.searchParams.set("model", this.options.model);
|
|
74
74
|
if (this.options.language) url.searchParams.set("language", this.options.language);
|
|
75
75
|
this.ws = new WebSocket(url.toString());
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
try {
|
|
77
|
+
await new Promise((resolve, reject) => {
|
|
78
|
+
const ws = this.ws;
|
|
79
|
+
ws.onopen = () => resolve();
|
|
80
|
+
ws.onerror = () => reject(new Error("WebSocket connection failed"));
|
|
81
|
+
ws.onclose = () => this.emit("status_change", "DISCONNECTED");
|
|
82
|
+
});
|
|
83
|
+
} catch (err) {
|
|
84
|
+
this.ws?.close();
|
|
85
|
+
this.ws = null;
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
82
88
|
this.ws.onmessage = (event) => {
|
|
83
89
|
try {
|
|
84
90
|
const msg = JSON.parse(event.data);
|
|
@@ -19,12 +19,18 @@ var DeepgramSession = class extends EventEmitter {
|
|
|
19
19
|
if (this.options.model) url.searchParams.set("model", this.options.model);
|
|
20
20
|
if (this.options.language) url.searchParams.set("language", this.options.language);
|
|
21
21
|
this.ws = new WebSocket(url.toString());
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
try {
|
|
23
|
+
await new Promise((resolve, reject) => {
|
|
24
|
+
const ws = this.ws;
|
|
25
|
+
ws.onopen = () => resolve();
|
|
26
|
+
ws.onerror = () => reject(new Error("WebSocket connection failed"));
|
|
27
|
+
ws.onclose = () => this.emit("status_change", "DISCONNECTED");
|
|
28
|
+
});
|
|
29
|
+
} catch (err) {
|
|
30
|
+
this.ws?.close();
|
|
31
|
+
this.ws = null;
|
|
32
|
+
throw err;
|
|
33
|
+
}
|
|
28
34
|
this.ws.onmessage = (event) => {
|
|
29
35
|
try {
|
|
30
36
|
const msg = JSON.parse(event.data);
|
|
@@ -73,11 +73,17 @@ var ElevenLabsSession = class extends EventEmitter {
|
|
|
73
73
|
async connect(config) {
|
|
74
74
|
const wsUrl = config.authToken?.startsWith("wss://") ? config.authToken : `${ELEVENLABS_WS_BASE}?agent_id=${this.agentId}`;
|
|
75
75
|
this.ws = new WebSocket(wsUrl);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
try {
|
|
77
|
+
await new Promise((resolve, reject) => {
|
|
78
|
+
const ws = this.ws;
|
|
79
|
+
ws.onopen = () => resolve();
|
|
80
|
+
ws.onerror = () => reject(new Error("ElevenLabs WebSocket connection failed"));
|
|
81
|
+
});
|
|
82
|
+
} catch (err) {
|
|
83
|
+
this.ws?.close();
|
|
84
|
+
this.ws = null;
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
81
87
|
this.ws.onmessage = (event) => {
|
|
82
88
|
try {
|
|
83
89
|
const msg = JSON.parse(event.data);
|
|
@@ -19,11 +19,17 @@ var ElevenLabsSession = class extends EventEmitter {
|
|
|
19
19
|
async connect(config) {
|
|
20
20
|
const wsUrl = config.authToken?.startsWith("wss://") ? config.authToken : `${ELEVENLABS_WS_BASE}?agent_id=${this.agentId}`;
|
|
21
21
|
this.ws = new WebSocket(wsUrl);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
try {
|
|
23
|
+
await new Promise((resolve, reject) => {
|
|
24
|
+
const ws = this.ws;
|
|
25
|
+
ws.onopen = () => resolve();
|
|
26
|
+
ws.onerror = () => reject(new Error("ElevenLabs WebSocket connection failed"));
|
|
27
|
+
});
|
|
28
|
+
} catch (err) {
|
|
29
|
+
this.ws?.close();
|
|
30
|
+
this.ws = null;
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
27
33
|
this.ws.onmessage = (event) => {
|
|
28
34
|
try {
|
|
29
35
|
const msg = JSON.parse(event.data);
|
package/dist/adapters/livekit.js
CHANGED
|
@@ -106,9 +106,15 @@ var LiveKitSession = class extends EventEmitter {
|
|
|
106
106
|
this.room.on(RoomEvent.Reconnected, () => {
|
|
107
107
|
this.emit("status_change", "CONNECTED");
|
|
108
108
|
});
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
try {
|
|
110
|
+
await this.room.connect(this.serverUrl, config.authToken);
|
|
111
|
+
await this.room.localParticipant.setMicrophoneEnabled(true);
|
|
112
|
+
this.emit("status_change", "CONNECTED");
|
|
113
|
+
} catch (err) {
|
|
114
|
+
await this.room.disconnect();
|
|
115
|
+
this.room = null;
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
112
118
|
}
|
|
113
119
|
async disconnect() {
|
|
114
120
|
if (this.room) {
|
|
@@ -42,9 +42,15 @@ var LiveKitSession = class extends EventEmitter {
|
|
|
42
42
|
this.room.on(RoomEvent.Reconnected, () => {
|
|
43
43
|
this.emit("status_change", "CONNECTED");
|
|
44
44
|
});
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
try {
|
|
46
|
+
await this.room.connect(this.serverUrl, config.authToken);
|
|
47
|
+
await this.room.localParticipant.setMicrophoneEnabled(true);
|
|
48
|
+
this.emit("status_change", "CONNECTED");
|
|
49
|
+
} catch (err) {
|
|
50
|
+
await this.room.disconnect();
|
|
51
|
+
this.room = null;
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
48
54
|
}
|
|
49
55
|
async disconnect() {
|
|
50
56
|
if (this.room) {
|
package/dist/adapters/openai.js
CHANGED
|
@@ -152,12 +152,16 @@ var OpenAISession = class extends EventEmitter {
|
|
|
152
152
|
await new Promise((resolve) => {
|
|
153
153
|
const onDone = (event) => {
|
|
154
154
|
if (event.type === "response.done" || event.type === "response.cancelled") {
|
|
155
|
+
clearTimeout(timeoutId);
|
|
155
156
|
this.off("raw_event", onDone);
|
|
156
157
|
resolve();
|
|
157
158
|
}
|
|
158
159
|
};
|
|
159
160
|
this.on("raw_event", onDone);
|
|
160
|
-
setTimeout(
|
|
161
|
+
const timeoutId = setTimeout(() => {
|
|
162
|
+
this.off("raw_event", onDone);
|
|
163
|
+
resolve();
|
|
164
|
+
}, 1500);
|
|
161
165
|
});
|
|
162
166
|
}
|
|
163
167
|
this.session.sendMessage(text);
|
package/dist/adapters/openai.mjs
CHANGED
|
@@ -96,12 +96,16 @@ var OpenAISession = class extends EventEmitter {
|
|
|
96
96
|
await new Promise((resolve) => {
|
|
97
97
|
const onDone = (event) => {
|
|
98
98
|
if (event.type === "response.done" || event.type === "response.cancelled") {
|
|
99
|
+
clearTimeout(timeoutId);
|
|
99
100
|
this.off("raw_event", onDone);
|
|
100
101
|
resolve();
|
|
101
102
|
}
|
|
102
103
|
};
|
|
103
104
|
this.on("raw_event", onDone);
|
|
104
|
-
setTimeout(
|
|
105
|
+
const timeoutId = setTimeout(() => {
|
|
106
|
+
this.off("raw_event", onDone);
|
|
107
|
+
resolve();
|
|
108
|
+
}, 1500);
|
|
105
109
|
});
|
|
106
110
|
}
|
|
107
111
|
this.session.sendMessage(text);
|
package/dist/index.js
CHANGED
|
@@ -217,6 +217,7 @@ function VoiceProvider({
|
|
|
217
217
|
}, 500);
|
|
218
218
|
} catch (error) {
|
|
219
219
|
console.error("VoiceKit connection failed:", error);
|
|
220
|
+
sessionRef.current = null;
|
|
220
221
|
onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
221
222
|
updateStatus("DISCONNECTED");
|
|
222
223
|
}
|
|
@@ -823,6 +824,7 @@ function useAudioRecorder() {
|
|
|
823
824
|
if (mediaRecorderRef.current?.state === "recording") {
|
|
824
825
|
return;
|
|
825
826
|
}
|
|
827
|
+
recordedChunksRef.current = [];
|
|
826
828
|
try {
|
|
827
829
|
const mediaRecorder = new MediaRecorder(stream, { mimeType: "audio/webm" });
|
|
828
830
|
mediaRecorder.ondataavailable = (event) => {
|
|
@@ -1172,11 +1174,7 @@ function useSessionHistory() {
|
|
|
1172
1174
|
const { itemId, role, content = [] } = item;
|
|
1173
1175
|
if (itemId && role) {
|
|
1174
1176
|
let text = extractMessageText(content);
|
|
1175
|
-
if (
|
|
1176
|
-
text = "";
|
|
1177
|
-
} else if (role === "user" && !text) {
|
|
1178
|
-
return;
|
|
1179
|
-
}
|
|
1177
|
+
if (!text) text = "";
|
|
1180
1178
|
const guardrailMessage = sketchilyDetectGuardrailMessage(text);
|
|
1181
1179
|
if (guardrailMessage) {
|
|
1182
1180
|
const failureDetails = JSON.parse(guardrailMessage);
|
|
@@ -1219,6 +1217,7 @@ function useSessionHistory() {
|
|
|
1219
1217
|
const itemId = item.item_id;
|
|
1220
1218
|
if (interruptedItemsRef.current.has(itemId)) return;
|
|
1221
1219
|
if (itemId) {
|
|
1220
|
+
const displayedText = displayedTextRef.current.get(itemId) ?? accumulatedTextRef.current.get(itemId);
|
|
1222
1221
|
const timer = deltaTimerRef.current.get(itemId);
|
|
1223
1222
|
if (timer) clearTimeout(timer);
|
|
1224
1223
|
deltaTimerRef.current.delete(itemId);
|
|
@@ -1227,14 +1226,13 @@ function useSessionHistory() {
|
|
|
1227
1226
|
displayedTextRef.current.delete(itemId);
|
|
1228
1227
|
accumulatedTextRef.current.delete(itemId);
|
|
1229
1228
|
totalAudioDurationRef.current.delete(itemId);
|
|
1230
|
-
const displayedText = displayedTextRef.current.get(itemId);
|
|
1231
1229
|
const finalText = displayedText || item.transcript || "";
|
|
1232
1230
|
const stripped = finalText.replace(/[\s.…]+/g, "");
|
|
1233
1231
|
if (stripped.length > 0) {
|
|
1234
1232
|
updateTranscriptMessage(itemId, finalText, false);
|
|
1235
1233
|
}
|
|
1236
1234
|
updateTranscriptItem(itemId, { status: "DONE" });
|
|
1237
|
-
const transcriptItem =
|
|
1235
|
+
const transcriptItem = transcriptItemsRef.current.find((i) => i.itemId === itemId);
|
|
1238
1236
|
if (transcriptItem?.guardrailResult?.status === "IN_PROGRESS") {
|
|
1239
1237
|
updateTranscriptItem(itemId, {
|
|
1240
1238
|
guardrailResult: {
|
|
@@ -1259,7 +1257,8 @@ function useSessionHistory() {
|
|
|
1259
1257
|
const category = moderation.moderationCategory ?? "NONE";
|
|
1260
1258
|
const rationale = moderation.moderationRationale ?? "";
|
|
1261
1259
|
const offendingText = moderation.testText;
|
|
1262
|
-
|
|
1260
|
+
const assistantItemId = lastAssistant.itemId ?? lastAssistant.id;
|
|
1261
|
+
updateTranscriptItem(assistantItemId, {
|
|
1263
1262
|
guardrailResult: {
|
|
1264
1263
|
status: "DONE",
|
|
1265
1264
|
category,
|
|
@@ -1326,13 +1325,15 @@ function useRealtimeSession(callbacks = {}) {
|
|
|
1326
1325
|
const [status, setStatus] = (0, import_react8.useState)("DISCONNECTED");
|
|
1327
1326
|
const { logClientEvent, logServerEvent } = useEvent();
|
|
1328
1327
|
const codecParamRef = (0, import_react8.useRef)("opus");
|
|
1328
|
+
const callbacksRef = (0, import_react8.useRef)(callbacks);
|
|
1329
|
+
callbacksRef.current = callbacks;
|
|
1329
1330
|
const updateStatus = (0, import_react8.useCallback)(
|
|
1330
1331
|
(s) => {
|
|
1331
1332
|
setStatus(s);
|
|
1332
|
-
|
|
1333
|
+
callbacksRef.current.onConnectionChange?.(s);
|
|
1333
1334
|
logClientEvent({}, s);
|
|
1334
1335
|
},
|
|
1335
|
-
[
|
|
1336
|
+
[logClientEvent]
|
|
1336
1337
|
);
|
|
1337
1338
|
const historyHandlers = useSessionHistory().current;
|
|
1338
1339
|
const interruptedRef = (0, import_react8.useRef)(/* @__PURE__ */ new Set());
|
|
@@ -1392,7 +1393,7 @@ function useRealtimeSession(callbacks = {}) {
|
|
|
1392
1393
|
);
|
|
1393
1394
|
});
|
|
1394
1395
|
session.on("agent_handoff", (_from, to) => {
|
|
1395
|
-
|
|
1396
|
+
callbacksRef.current.onAgentHandoff?.(to);
|
|
1396
1397
|
});
|
|
1397
1398
|
session.on("guardrail_tripped", (info) => {
|
|
1398
1399
|
historyHandlers.handleGuardrailTripped(
|
|
@@ -1429,7 +1430,7 @@ function useRealtimeSession(callbacks = {}) {
|
|
|
1429
1430
|
console.error("Session error:", msg);
|
|
1430
1431
|
logServerEvent({ type: "error", message: msg });
|
|
1431
1432
|
});
|
|
1432
|
-
}, [
|
|
1433
|
+
}, [historyHandlers, logServerEvent]);
|
|
1433
1434
|
const connect = (0, import_react8.useCallback)(
|
|
1434
1435
|
async ({
|
|
1435
1436
|
getEphemeralKey,
|
|
@@ -1445,6 +1446,9 @@ function useRealtimeSession(callbacks = {}) {
|
|
|
1445
1446
|
"useRealtimeSession: `adapter` is required in ConnectOptions. Pass an adapter like openai() from @jchaffin/voicekit/openai."
|
|
1446
1447
|
);
|
|
1447
1448
|
}
|
|
1449
|
+
if (!initialAgents?.length) {
|
|
1450
|
+
throw new Error("useRealtimeSession: `initialAgents` must be a non-empty array.");
|
|
1451
|
+
}
|
|
1448
1452
|
updateStatus("CONNECTING");
|
|
1449
1453
|
const ek = await getEphemeralKey();
|
|
1450
1454
|
const rootAgent = initialAgents[0];
|
|
@@ -1667,8 +1671,8 @@ function SuggestionProvider({
|
|
|
1667
1671
|
(0, import_react9.useEffect)(() => {
|
|
1668
1672
|
const handler = (e) => {
|
|
1669
1673
|
const detail = e.detail;
|
|
1670
|
-
if (detail
|
|
1671
|
-
setSuggestionsState(detail.group);
|
|
1674
|
+
if (detail) {
|
|
1675
|
+
setSuggestionsState(detail.group ?? null);
|
|
1672
1676
|
}
|
|
1673
1677
|
};
|
|
1674
1678
|
window.addEventListener(SUGGESTION_EVENT, handler);
|
package/dist/index.mjs
CHANGED
|
@@ -159,6 +159,7 @@ function VoiceProvider({
|
|
|
159
159
|
}, 500);
|
|
160
160
|
} catch (error) {
|
|
161
161
|
console.error("VoiceKit connection failed:", error);
|
|
162
|
+
sessionRef.current = null;
|
|
162
163
|
onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
163
164
|
updateStatus("DISCONNECTED");
|
|
164
165
|
}
|
|
@@ -604,6 +605,7 @@ function useAudioRecorder() {
|
|
|
604
605
|
if (mediaRecorderRef.current?.state === "recording") {
|
|
605
606
|
return;
|
|
606
607
|
}
|
|
608
|
+
recordedChunksRef.current = [];
|
|
607
609
|
try {
|
|
608
610
|
const mediaRecorder = new MediaRecorder(stream, { mimeType: "audio/webm" });
|
|
609
611
|
mediaRecorder.ondataavailable = (event) => {
|
|
@@ -958,11 +960,7 @@ function useSessionHistory() {
|
|
|
958
960
|
const { itemId, role, content = [] } = item;
|
|
959
961
|
if (itemId && role) {
|
|
960
962
|
let text = extractMessageText(content);
|
|
961
|
-
if (
|
|
962
|
-
text = "";
|
|
963
|
-
} else if (role === "user" && !text) {
|
|
964
|
-
return;
|
|
965
|
-
}
|
|
963
|
+
if (!text) text = "";
|
|
966
964
|
const guardrailMessage = sketchilyDetectGuardrailMessage(text);
|
|
967
965
|
if (guardrailMessage) {
|
|
968
966
|
const failureDetails = JSON.parse(guardrailMessage);
|
|
@@ -1005,6 +1003,7 @@ function useSessionHistory() {
|
|
|
1005
1003
|
const itemId = item.item_id;
|
|
1006
1004
|
if (interruptedItemsRef.current.has(itemId)) return;
|
|
1007
1005
|
if (itemId) {
|
|
1006
|
+
const displayedText = displayedTextRef.current.get(itemId) ?? accumulatedTextRef.current.get(itemId);
|
|
1008
1007
|
const timer = deltaTimerRef.current.get(itemId);
|
|
1009
1008
|
if (timer) clearTimeout(timer);
|
|
1010
1009
|
deltaTimerRef.current.delete(itemId);
|
|
@@ -1013,14 +1012,13 @@ function useSessionHistory() {
|
|
|
1013
1012
|
displayedTextRef.current.delete(itemId);
|
|
1014
1013
|
accumulatedTextRef.current.delete(itemId);
|
|
1015
1014
|
totalAudioDurationRef.current.delete(itemId);
|
|
1016
|
-
const displayedText = displayedTextRef.current.get(itemId);
|
|
1017
1015
|
const finalText = displayedText || item.transcript || "";
|
|
1018
1016
|
const stripped = finalText.replace(/[\s.…]+/g, "");
|
|
1019
1017
|
if (stripped.length > 0) {
|
|
1020
1018
|
updateTranscriptMessage(itemId, finalText, false);
|
|
1021
1019
|
}
|
|
1022
1020
|
updateTranscriptItem(itemId, { status: "DONE" });
|
|
1023
|
-
const transcriptItem =
|
|
1021
|
+
const transcriptItem = transcriptItemsRef.current.find((i) => i.itemId === itemId);
|
|
1024
1022
|
if (transcriptItem?.guardrailResult?.status === "IN_PROGRESS") {
|
|
1025
1023
|
updateTranscriptItem(itemId, {
|
|
1026
1024
|
guardrailResult: {
|
|
@@ -1045,7 +1043,8 @@ function useSessionHistory() {
|
|
|
1045
1043
|
const category = moderation.moderationCategory ?? "NONE";
|
|
1046
1044
|
const rationale = moderation.moderationRationale ?? "";
|
|
1047
1045
|
const offendingText = moderation.testText;
|
|
1048
|
-
|
|
1046
|
+
const assistantItemId = lastAssistant.itemId ?? lastAssistant.id;
|
|
1047
|
+
updateTranscriptItem(assistantItemId, {
|
|
1049
1048
|
guardrailResult: {
|
|
1050
1049
|
status: "DONE",
|
|
1051
1050
|
category,
|
|
@@ -1112,13 +1111,15 @@ function useRealtimeSession(callbacks = {}) {
|
|
|
1112
1111
|
const [status, setStatus] = useState5("DISCONNECTED");
|
|
1113
1112
|
const { logClientEvent, logServerEvent } = useEvent();
|
|
1114
1113
|
const codecParamRef = useRef6("opus");
|
|
1114
|
+
const callbacksRef = useRef6(callbacks);
|
|
1115
|
+
callbacksRef.current = callbacks;
|
|
1115
1116
|
const updateStatus = useCallback6(
|
|
1116
1117
|
(s) => {
|
|
1117
1118
|
setStatus(s);
|
|
1118
|
-
|
|
1119
|
+
callbacksRef.current.onConnectionChange?.(s);
|
|
1119
1120
|
logClientEvent({}, s);
|
|
1120
1121
|
},
|
|
1121
|
-
[
|
|
1122
|
+
[logClientEvent]
|
|
1122
1123
|
);
|
|
1123
1124
|
const historyHandlers = useSessionHistory().current;
|
|
1124
1125
|
const interruptedRef = useRef6(/* @__PURE__ */ new Set());
|
|
@@ -1178,7 +1179,7 @@ function useRealtimeSession(callbacks = {}) {
|
|
|
1178
1179
|
);
|
|
1179
1180
|
});
|
|
1180
1181
|
session.on("agent_handoff", (_from, to) => {
|
|
1181
|
-
|
|
1182
|
+
callbacksRef.current.onAgentHandoff?.(to);
|
|
1182
1183
|
});
|
|
1183
1184
|
session.on("guardrail_tripped", (info) => {
|
|
1184
1185
|
historyHandlers.handleGuardrailTripped(
|
|
@@ -1215,7 +1216,7 @@ function useRealtimeSession(callbacks = {}) {
|
|
|
1215
1216
|
console.error("Session error:", msg);
|
|
1216
1217
|
logServerEvent({ type: "error", message: msg });
|
|
1217
1218
|
});
|
|
1218
|
-
}, [
|
|
1219
|
+
}, [historyHandlers, logServerEvent]);
|
|
1219
1220
|
const connect = useCallback6(
|
|
1220
1221
|
async ({
|
|
1221
1222
|
getEphemeralKey,
|
|
@@ -1231,6 +1232,9 @@ function useRealtimeSession(callbacks = {}) {
|
|
|
1231
1232
|
"useRealtimeSession: `adapter` is required in ConnectOptions. Pass an adapter like openai() from @jchaffin/voicekit/openai."
|
|
1232
1233
|
);
|
|
1233
1234
|
}
|
|
1235
|
+
if (!initialAgents?.length) {
|
|
1236
|
+
throw new Error("useRealtimeSession: `initialAgents` must be a non-empty array.");
|
|
1237
|
+
}
|
|
1234
1238
|
updateStatus("CONNECTING");
|
|
1235
1239
|
const ek = await getEphemeralKey();
|
|
1236
1240
|
const rootAgent = initialAgents[0];
|
|
@@ -1453,8 +1457,8 @@ function SuggestionProvider({
|
|
|
1453
1457
|
useEffect6(() => {
|
|
1454
1458
|
const handler = (e) => {
|
|
1455
1459
|
const detail = e.detail;
|
|
1456
|
-
if (detail
|
|
1457
|
-
setSuggestionsState(detail.group);
|
|
1460
|
+
if (detail) {
|
|
1461
|
+
setSuggestionsState(detail.group ?? null);
|
|
1458
1462
|
}
|
|
1459
1463
|
};
|
|
1460
1464
|
window.addEventListener(SUGGESTION_EVENT, handler);
|