@memori.ai/memori-react 7.34.2 → 8.0.0-rc.1
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/CHANGELOG.md +35 -0
- package/dist/components/Chat/Chat.d.ts +1 -0
- package/dist/components/Chat/Chat.js +2 -2
- package/dist/components/Chat/Chat.js.map +1 -1
- package/dist/components/ChatInputs/ChatInputs.d.ts +1 -0
- package/dist/components/ChatInputs/ChatInputs.js +3 -3
- package/dist/components/ChatInputs/ChatInputs.js.map +1 -1
- package/dist/components/MemoriWidget/MemoriWidget.d.ts +3 -3
- package/dist/components/MemoriWidget/MemoriWidget.js +138 -425
- package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/dist/context/visemeContext.js +39 -30
- package/dist/context/visemeContext.js.map +1 -1
- package/dist/helpers/sanitizer.d.ts +6 -0
- package/dist/helpers/sanitizer.js +41 -0
- package/dist/helpers/sanitizer.js.map +1 -0
- package/dist/helpers/tts/ttsVoiceUtility.d.ts +158 -0
- package/dist/helpers/tts/ttsVoiceUtility.js +192 -0
- package/dist/helpers/tts/ttsVoiceUtility.js.map +1 -0
- package/dist/helpers/tts/useTTS.d.ts +26 -0
- package/dist/helpers/tts/useTTS.js +274 -0
- package/dist/helpers/tts/useTTS.js.map +1 -0
- package/dist/index.js +12 -7
- package/dist/index.js.map +1 -1
- package/esm/components/Chat/Chat.d.ts +1 -0
- package/esm/components/Chat/Chat.js +2 -2
- package/esm/components/Chat/Chat.js.map +1 -1
- package/esm/components/ChatInputs/ChatInputs.d.ts +1 -0
- package/esm/components/ChatInputs/ChatInputs.js +3 -3
- package/esm/components/ChatInputs/ChatInputs.js.map +1 -1
- package/esm/components/MemoriWidget/MemoriWidget.d.ts +3 -3
- package/esm/components/MemoriWidget/MemoriWidget.js +139 -426
- package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/esm/context/visemeContext.js +39 -30
- package/esm/context/visemeContext.js.map +1 -1
- package/esm/helpers/sanitizer.d.ts +6 -0
- package/esm/helpers/sanitizer.js +32 -0
- package/esm/helpers/sanitizer.js.map +1 -0
- package/esm/helpers/tts/ttsVoiceUtility.d.ts +158 -0
- package/esm/helpers/tts/ttsVoiceUtility.js +182 -0
- package/esm/helpers/tts/ttsVoiceUtility.js.map +1 -0
- package/esm/helpers/tts/useTTS.d.ts +26 -0
- package/esm/helpers/tts/useTTS.js +270 -0
- package/esm/helpers/tts/useTTS.js.map +1 -0
- package/esm/index.js +12 -7
- package/esm/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Chat/Chat.tsx +3 -0
- package/src/components/ChatInputs/ChatInputs.tsx +4 -2
- package/src/components/MemoriWidget/MemoriWidget.tsx +246 -637
- package/src/context/visemeContext.tsx +77 -55
- package/src/helpers/sanitizer.ts +71 -0
- package/src/helpers/tts/ttsVoiceUtility.ts +275 -0
- package/src/helpers/tts/useTTS.ts +431 -0
- package/src/index.tsx +14 -10
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
// Improved useTTS.ts with better viseme handling
|
|
2
|
+
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
3
|
+
import { sanitizeText } from '../sanitizer';
|
|
4
|
+
import { getLocalConfig } from '../configuration';
|
|
5
|
+
import Alert from '../../components/ui/Alert';
|
|
6
|
+
import { useViseme } from '../../context/visemeContext';
|
|
7
|
+
import { IAudioContext } from 'standardized-audio-context';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Configurazione per il TTS
|
|
11
|
+
*/
|
|
12
|
+
export interface TTSConfig {
|
|
13
|
+
provider: 'azure' | 'openai';
|
|
14
|
+
voice?: string;
|
|
15
|
+
model?: string;
|
|
16
|
+
region?: string; // richiesto per Azure
|
|
17
|
+
tenant?: string; // Tenant identifier for multi-tenant applications
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type VisemeData = {
|
|
21
|
+
visemeId: number;
|
|
22
|
+
audioOffset: number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Opzioni per l'hook useTTS
|
|
27
|
+
*/
|
|
28
|
+
export interface UseTTSOptions {
|
|
29
|
+
apiUrl?: string;
|
|
30
|
+
continuousSpeech?: boolean;
|
|
31
|
+
onEndSpeakStartListen?: () => void;
|
|
32
|
+
preview?: boolean;
|
|
33
|
+
disableSpeaker?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Create our own simplified audio context interface for better typing
|
|
37
|
+
interface SimpleAudioWrapper {
|
|
38
|
+
currentTime: number;
|
|
39
|
+
state: 'running' | 'suspended' | 'closed';
|
|
40
|
+
onstatechange: ((this: AudioContext, ev: Event) => any) | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Hook unificato che gestisce la sintesi vocale
|
|
45
|
+
*/
|
|
46
|
+
export function useTTS(
|
|
47
|
+
config: TTSConfig,
|
|
48
|
+
options: UseTTSOptions = {},
|
|
49
|
+
autoStart: boolean = false,
|
|
50
|
+
defaultEnableAudio: boolean = true,
|
|
51
|
+
defaultSpeakerActive: boolean = true
|
|
52
|
+
) {
|
|
53
|
+
// Stato locale
|
|
54
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
55
|
+
const [speakerMuted, setSpeakerMuted] = useState(
|
|
56
|
+
getLocalConfig(
|
|
57
|
+
'muteSpeaker',
|
|
58
|
+
!defaultEnableAudio || !defaultSpeakerActive || autoStart
|
|
59
|
+
)
|
|
60
|
+
);
|
|
61
|
+
// Get viseme methods from your context
|
|
62
|
+
const {
|
|
63
|
+
addViseme,
|
|
64
|
+
resetVisemeQueue,
|
|
65
|
+
startProcessing,
|
|
66
|
+
stopProcessing,
|
|
67
|
+
} = useViseme();
|
|
68
|
+
const [hasUserActivatedSpeak, setHasUserActivatedSpeak] = useState(false);
|
|
69
|
+
const [error, setError] = useState<Error | null>(null);
|
|
70
|
+
|
|
71
|
+
// Riferimenti
|
|
72
|
+
const audioRef = useRef<HTMLAudioElement | null>(null);
|
|
73
|
+
const audioWrapperRef = useRef<SimpleAudioWrapper | null>(null);
|
|
74
|
+
const globalSpeakRef = useRef<Function | null>(null);
|
|
75
|
+
const visemeLoadedRef = useRef<boolean>(false);
|
|
76
|
+
const isSpeakingRef = useRef<boolean>(false);
|
|
77
|
+
const apiUrl = options.apiUrl || '/api/tts';
|
|
78
|
+
|
|
79
|
+
// Load viseme data into the queue
|
|
80
|
+
const loadVisemeData = useCallback(
|
|
81
|
+
(visemeData: VisemeData[]) => {
|
|
82
|
+
// Make sure we're in a clean state before loading new visemes
|
|
83
|
+
resetVisemeQueue();
|
|
84
|
+
visemeLoadedRef.current = false;
|
|
85
|
+
|
|
86
|
+
if (visemeData && visemeData.length > 0) {
|
|
87
|
+
console.log(`[useTTS] Loading ${visemeData.length} viseme events`);
|
|
88
|
+
visemeData.forEach(viseme => {
|
|
89
|
+
addViseme(viseme.visemeId, viseme.audioOffset);
|
|
90
|
+
});
|
|
91
|
+
visemeLoadedRef.current = true;
|
|
92
|
+
return true;
|
|
93
|
+
} else {
|
|
94
|
+
console.warn('[useTTS] No viseme data available');
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
[addViseme, resetVisemeQueue]
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// Create audio wrapper for viseme processing
|
|
102
|
+
const createAudioWrapper = useCallback(() => {
|
|
103
|
+
if (!audioRef.current) {
|
|
104
|
+
console.warn('[useTTS] Cannot create audio wrapper: audio element is null');
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Create a clean wrapper for this audio session
|
|
109
|
+
const wrapper: SimpleAudioWrapper = {
|
|
110
|
+
state: 'running',
|
|
111
|
+
onstatechange: null,
|
|
112
|
+
get currentTime() {
|
|
113
|
+
return audioRef.current ? audioRef.current.currentTime : 0;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Add event listeners to update the state
|
|
118
|
+
const handlePause = () => {
|
|
119
|
+
wrapper.state = 'suspended';
|
|
120
|
+
if (wrapper.onstatechange) {
|
|
121
|
+
wrapper.onstatechange.call(null as any, new Event('statechange'));
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const handlePlay = () => {
|
|
126
|
+
wrapper.state = 'running';
|
|
127
|
+
if (wrapper.onstatechange) {
|
|
128
|
+
wrapper.onstatechange.call(null as any, new Event('statechange'));
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const handleEnded = () => {
|
|
133
|
+
wrapper.state = 'closed';
|
|
134
|
+
if (wrapper.onstatechange) {
|
|
135
|
+
wrapper.onstatechange.call(null as any, new Event('statechange'));
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Attach event listeners to the audio element
|
|
140
|
+
audioRef.current.addEventListener('pause', handlePause);
|
|
141
|
+
audioRef.current.addEventListener('play', handlePlay);
|
|
142
|
+
audioRef.current.addEventListener('ended', handleEnded);
|
|
143
|
+
|
|
144
|
+
// Store cleanup function
|
|
145
|
+
const cleanupEventListeners = () => {
|
|
146
|
+
if (audioRef.current) {
|
|
147
|
+
audioRef.current.removeEventListener('pause', handlePause);
|
|
148
|
+
audioRef.current.removeEventListener('play', handlePlay);
|
|
149
|
+
audioRef.current.removeEventListener('ended', handleEnded);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Store the cleanup function on the wrapper for later use
|
|
154
|
+
(wrapper as any).cleanup = cleanupEventListeners;
|
|
155
|
+
|
|
156
|
+
console.log('[useTTS] Created audio wrapper for viseme processing');
|
|
157
|
+
return wrapper;
|
|
158
|
+
}, []);
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Performs a complete cleanup of audio and viseme resources
|
|
162
|
+
*/
|
|
163
|
+
const cleanup = useCallback(() => {
|
|
164
|
+
console.log('[useTTS] Cleaning up audio and viseme resources');
|
|
165
|
+
|
|
166
|
+
// First, clean up audio wrapper
|
|
167
|
+
if (audioWrapperRef.current && (audioWrapperRef.current as any).cleanup) {
|
|
168
|
+
(audioWrapperRef.current as any).cleanup();
|
|
169
|
+
console.log('[useTTS] Cleaned up audio wrapper event listeners');
|
|
170
|
+
}
|
|
171
|
+
audioWrapperRef.current = null;
|
|
172
|
+
|
|
173
|
+
// Then stop viseme processing
|
|
174
|
+
stopProcessing();
|
|
175
|
+
console.log('[useTTS] Stopped viseme processing');
|
|
176
|
+
|
|
177
|
+
// Finally clean up audio resources
|
|
178
|
+
if (audioRef.current?.src) {
|
|
179
|
+
URL.revokeObjectURL(audioRef.current.src);
|
|
180
|
+
console.log('[useTTS] Revoked audio object URL');
|
|
181
|
+
audioRef.current = null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Reset flags
|
|
185
|
+
visemeLoadedRef.current = false;
|
|
186
|
+
isSpeakingRef.current = false;
|
|
187
|
+
}, [stopProcessing]);
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Stops audio playback and cleans up
|
|
191
|
+
*/
|
|
192
|
+
const stop = useCallback((): void => {
|
|
193
|
+
console.log('[useTTS] Stopping audio playback');
|
|
194
|
+
|
|
195
|
+
// Pause audio first
|
|
196
|
+
if (audioRef.current) {
|
|
197
|
+
audioRef.current.pause();
|
|
198
|
+
audioRef.current.currentTime = 0;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Set UI state
|
|
202
|
+
setIsPlaying(false);
|
|
203
|
+
|
|
204
|
+
// Clean up all resources
|
|
205
|
+
cleanup();
|
|
206
|
+
}, [cleanup]);
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Emette l'evento di fine riproduzione
|
|
210
|
+
*/
|
|
211
|
+
const emitEndSpeakEvent = useCallback(() => {
|
|
212
|
+
console.log('[useTTS] Emitting end speak event');
|
|
213
|
+
const e = new CustomEvent('MemoriEndSpeak');
|
|
214
|
+
document.dispatchEvent(e);
|
|
215
|
+
|
|
216
|
+
// Se è impostato il parlato continuo, avvia l'ascolto
|
|
217
|
+
if (options.continuousSpeech && options.onEndSpeakStartListen) {
|
|
218
|
+
console.log('[useTTS] Starting continuous speech listening');
|
|
219
|
+
options.onEndSpeakStartListen();
|
|
220
|
+
}
|
|
221
|
+
}, [options.continuousSpeech, options.onEndSpeakStartListen]);
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Sintetizza il testo in audio e lo riproduce
|
|
225
|
+
*/
|
|
226
|
+
const speak = useCallback(
|
|
227
|
+
async (text: string): Promise<void> => {
|
|
228
|
+
if (isSpeakingRef.current) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!text || options.preview || speakerMuted) {
|
|
233
|
+
emitEndSpeakEvent();
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
isSpeakingRef.current = true;
|
|
238
|
+
|
|
239
|
+
if (!hasUserActivatedSpeak) {
|
|
240
|
+
setHasUserActivatedSpeak(true);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
stop();
|
|
245
|
+
|
|
246
|
+
setIsPlaying(true);
|
|
247
|
+
setError(null);
|
|
248
|
+
|
|
249
|
+
const processedText = sanitizeText(text);
|
|
250
|
+
|
|
251
|
+
const response = await fetch('http://localhost:3000/api/tts', {
|
|
252
|
+
method: 'POST',
|
|
253
|
+
headers: {
|
|
254
|
+
'Content-Type': 'application/json',
|
|
255
|
+
},
|
|
256
|
+
body: JSON.stringify({
|
|
257
|
+
text: processedText,
|
|
258
|
+
tenant: config.tenant || 'www.aisuru.com',
|
|
259
|
+
voice: config.voice,
|
|
260
|
+
model: config.model || 'tts-1',
|
|
261
|
+
region: config.region,
|
|
262
|
+
provider: config.provider,
|
|
263
|
+
includeVisemes: true,
|
|
264
|
+
}),
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
if (!response.ok) {
|
|
268
|
+
const errorData = await response.json().catch(() => ({}));
|
|
269
|
+
throw new Error(errorData.error || `API error: ${response.status}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const visemeDataHeader = response.headers.get('X-Viseme-Data');
|
|
273
|
+
|
|
274
|
+
let hasVisemeData = false;
|
|
275
|
+
if (visemeDataHeader) {
|
|
276
|
+
try {
|
|
277
|
+
const visemeData: VisemeData[] = JSON.parse(visemeDataHeader);
|
|
278
|
+
hasVisemeData = loadVisemeData(visemeData);
|
|
279
|
+
} catch (err) {
|
|
280
|
+
console.error('[useTTS] Error parsing viseme data:', err);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const audioBlob = await response.blob();
|
|
285
|
+
const audioUrl = URL.createObjectURL(audioBlob);
|
|
286
|
+
|
|
287
|
+
audioRef.current = new Audio(audioUrl);
|
|
288
|
+
|
|
289
|
+
if (hasVisemeData) {
|
|
290
|
+
audioWrapperRef.current = createAudioWrapper();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
audioRef.current.oncanplaythrough = async () => {
|
|
294
|
+
try {
|
|
295
|
+
if (hasVisemeData && audioWrapperRef.current) {
|
|
296
|
+
startProcessing(audioWrapperRef.current as unknown as IAudioContext);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
await audioRef.current?.play();
|
|
300
|
+
|
|
301
|
+
if (audioRef.current) {
|
|
302
|
+
audioRef.current.oncanplaythrough = null;
|
|
303
|
+
}
|
|
304
|
+
} catch (e: any) {
|
|
305
|
+
cleanup();
|
|
306
|
+
emitEndSpeakEvent();
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
audioRef.current.onended = () => {
|
|
311
|
+
setIsPlaying(false);
|
|
312
|
+
isSpeakingRef.current = false;
|
|
313
|
+
cleanup();
|
|
314
|
+
emitEndSpeakEvent();
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
audioRef.current.onerror = () => {
|
|
318
|
+
setIsPlaying(false);
|
|
319
|
+
isSpeakingRef.current = false;
|
|
320
|
+
cleanup();
|
|
321
|
+
|
|
322
|
+
const errorMsg = new Error(`Audio playback failed. This may be due to a network issue or audio format problem.`);
|
|
323
|
+
setError(errorMsg);
|
|
324
|
+
emitEndSpeakEvent();
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
audioRef.current.load();
|
|
328
|
+
|
|
329
|
+
} catch (err) {
|
|
330
|
+
setIsPlaying(false);
|
|
331
|
+
isSpeakingRef.current = false;
|
|
332
|
+
cleanup();
|
|
333
|
+
const errorMsg = err instanceof Error ? err : new Error(String(err));
|
|
334
|
+
setError(errorMsg);
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
if ('speechSynthesis' in window) {
|
|
338
|
+
const utterance = new SpeechSynthesisUtterance(sanitizeText(text));
|
|
339
|
+
window.speechSynthesis.speak(utterance);
|
|
340
|
+
}
|
|
341
|
+
} catch (fallbackErr) {
|
|
342
|
+
console.error('[useTTS] Browser fallback synthesis error:', fallbackErr);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
emitEndSpeakEvent();
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
[
|
|
349
|
+
config,
|
|
350
|
+
speakerMuted,
|
|
351
|
+
options.preview,
|
|
352
|
+
hasUserActivatedSpeak,
|
|
353
|
+
stop,
|
|
354
|
+
cleanup,
|
|
355
|
+
loadVisemeData,
|
|
356
|
+
createAudioWrapper,
|
|
357
|
+
startProcessing,
|
|
358
|
+
emitEndSpeakEvent,
|
|
359
|
+
]
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Imposta lo stato del muto
|
|
364
|
+
*/
|
|
365
|
+
const toggleMute = useCallback(
|
|
366
|
+
(mute?: boolean) => {
|
|
367
|
+
const newMuteState = mute !== undefined ? mute : !speakerMuted;
|
|
368
|
+
console.log('[useTTS] Toggling mute state to:', newMuteState);
|
|
369
|
+
setSpeakerMuted(newMuteState);
|
|
370
|
+
|
|
371
|
+
// Se stiamo attivando il muto mentre l'audio sta suonando, fermiamo l'audio
|
|
372
|
+
if (newMuteState && isPlaying) {
|
|
373
|
+
stop();
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
[speakerMuted, isPlaying, stop]
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Aggiorna la variabile globale quando cambia isPlaying
|
|
381
|
+
*/
|
|
382
|
+
useEffect(() => {
|
|
383
|
+
console.log('[useTTS] Updating global memoriSpeaking state:', isPlaying);
|
|
384
|
+
if (typeof window !== 'undefined') {
|
|
385
|
+
(window as any).memoriSpeaking = isPlaying;
|
|
386
|
+
}
|
|
387
|
+
}, [isPlaying]);
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Hook per esporre la funzione speak globalmente
|
|
391
|
+
*/
|
|
392
|
+
useEffect(() => {
|
|
393
|
+
if (typeof window !== 'undefined') {
|
|
394
|
+
console.log('[useTTS] Setting up global speak function');
|
|
395
|
+
// Salviamo una referenza alla funzione originale, se esistente
|
|
396
|
+
globalSpeakRef.current = (window as any).speak;
|
|
397
|
+
|
|
398
|
+
// Assegniamo la nostra funzione
|
|
399
|
+
(window as any).speak = speak;
|
|
400
|
+
|
|
401
|
+
// Pulizia al dismount
|
|
402
|
+
return () => {
|
|
403
|
+
console.log('[useTTS] Cleaning up global speak function');
|
|
404
|
+
// Ripristiniamo la funzione originale se esisteva
|
|
405
|
+
(window as any).speak = globalSpeakRef.current;
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
}, [speak]);
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Pulizia delle risorse al dismount
|
|
412
|
+
*/
|
|
413
|
+
useEffect(() => {
|
|
414
|
+
return () => {
|
|
415
|
+
console.log('[useTTS] Component unmounting, cleaning up');
|
|
416
|
+
stop();
|
|
417
|
+
};
|
|
418
|
+
}, [stop]);
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
speak,
|
|
422
|
+
stop,
|
|
423
|
+
isPlaying,
|
|
424
|
+
speakerMuted,
|
|
425
|
+
toggleMute,
|
|
426
|
+
hasUserActivatedSpeak,
|
|
427
|
+
setHasUserActivatedSpeak,
|
|
428
|
+
error,
|
|
429
|
+
setError,
|
|
430
|
+
};
|
|
431
|
+
}
|
package/src/index.tsx
CHANGED
|
@@ -150,7 +150,7 @@ const Memori: React.FC<Props> = ({
|
|
|
150
150
|
}) => {
|
|
151
151
|
const [memori, setMemori] = useState<IMemori>();
|
|
152
152
|
const [tenant, setTenant] = useState<Tenant>();
|
|
153
|
-
const [
|
|
153
|
+
const [provider, setProvider] = useState<string | undefined>();
|
|
154
154
|
const { t } = useTranslation();
|
|
155
155
|
|
|
156
156
|
if (!((memoriID && ownerUserID) || (memoriName && ownerUserName))) {
|
|
@@ -162,17 +162,21 @@ const Memori: React.FC<Props> = ({
|
|
|
162
162
|
const client = memoriApiClient(apiURL, engineURL);
|
|
163
163
|
|
|
164
164
|
const fetchSpeechKey = useCallback(async () => {
|
|
165
|
-
const url =
|
|
166
|
-
|
|
167
|
-
(tenantID.startsWith('https://') ? tenantID : `https://${tenantID}`);
|
|
165
|
+
const url = baseURL ||
|
|
166
|
+
(tenantID.startsWith('https://') ? tenantID : `https://${tenantID}`);
|
|
168
167
|
try {
|
|
169
|
-
const result = await fetch(`${url}/api/speechkey
|
|
168
|
+
const result = await fetch(`${url}/api/speechkey?tenant=${tenantID}`, {
|
|
169
|
+
method: 'GET',
|
|
170
|
+
headers: {
|
|
171
|
+
'Content-Type': 'application/json',
|
|
172
|
+
},
|
|
173
|
+
});
|
|
170
174
|
const data = await result.json();
|
|
171
175
|
|
|
172
|
-
if (data.
|
|
173
|
-
|
|
176
|
+
if (data.provider) {
|
|
177
|
+
setProvider(data.provider);
|
|
174
178
|
} else {
|
|
175
|
-
console.log('
|
|
179
|
+
console.log('provider not found');
|
|
176
180
|
}
|
|
177
181
|
} catch (error) {
|
|
178
182
|
console.error('Error fetching speech key', error);
|
|
@@ -328,7 +332,7 @@ const Memori: React.FC<Props> = ({
|
|
|
328
332
|
initialContextVars={initialContextVars}
|
|
329
333
|
initialQuestion={initialQuestionLayout}
|
|
330
334
|
authToken={authToken}
|
|
331
|
-
|
|
335
|
+
ttsProvider={provider ? provider as 'azure' | 'openai' : 'azure'}
|
|
332
336
|
autoStart={
|
|
333
337
|
autoStart !== undefined
|
|
334
338
|
? autoStart
|
|
@@ -336,7 +340,7 @@ const Memori: React.FC<Props> = ({
|
|
|
336
340
|
? true
|
|
337
341
|
: autoStart
|
|
338
342
|
}
|
|
339
|
-
enableAudio={enableAudio && !!
|
|
343
|
+
enableAudio={enableAudio && !!provider}
|
|
340
344
|
defaultSpeakerActive={defaultSpeakerActive}
|
|
341
345
|
disableTextEnteredEvents={disableTextEnteredEvents}
|
|
342
346
|
onStateChange={onStateChange}
|