@bytexbyte/nxtlinq-ai-agent-ui-react-development 0.3.1 → 0.3.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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatBotContext.d.ts","sourceRoot":"","sources":["../../src/context/ChatBotContext.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAyB/B,OAAO,EAEL,kBAAkB,EAClB,YAAY,EAEb,MAAM,uBAAuB,CAAC;AAM/B,eAAO,MAAM,UAAU,0BAMtB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,YAAY,
|
|
1
|
+
{"version":3,"file":"ChatBotContext.d.ts","sourceRoot":"","sources":["../../src/context/ChatBotContext.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAyB/B,OAAO,EAEL,kBAAkB,EAClB,YAAY,EAEb,MAAM,uBAAuB,CAAC;AAM/B,eAAO,MAAM,UAAU,0BAMtB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,YAAY,CAwvGlD,CAAC"}
|
|
@@ -110,6 +110,7 @@ piiDisplayMode = 'redacted', voiceThresholds, debugVoiceRms = false, sttGlossary
|
|
|
110
110
|
const [textToSpeechEnabled, setTextToSpeechEnabled] = useLocalStorage('chatbot-text-to-speech-enabled', false);
|
|
111
111
|
const [preferredChatMode, setPreferredChatMode] = useLocalStorage('chatbot-active-mode', 'text');
|
|
112
112
|
const [speechingIndex, setSpeechingIndex] = React.useState(undefined);
|
|
113
|
+
const [pendingAutoTts, setPendingAutoTts] = React.useState(null);
|
|
113
114
|
// Speech related refs
|
|
114
115
|
const audioCtxRef = React.useRef(null);
|
|
115
116
|
const audioSourceRef = React.useRef(null);
|
|
@@ -1483,6 +1484,7 @@ piiDisplayMode = 'redacted', voiceThresholds, debugVoiceRms = false, sttGlossary
|
|
|
1483
1484
|
const hasContent = content.trim() || (attachments && attachments.length > 0);
|
|
1484
1485
|
if (!hasContent || isLoading)
|
|
1485
1486
|
return;
|
|
1487
|
+
setPendingAutoTts(null);
|
|
1486
1488
|
const currentModel = getCurrentModel();
|
|
1487
1489
|
// Initialize with current model, will be updated with actual model from backend response
|
|
1488
1490
|
let actualModelUsed = currentModel.value;
|
|
@@ -2075,11 +2077,7 @@ piiDisplayMode = 'redacted', voiceThresholds, debugVoiceRms = false, sttGlossary
|
|
|
2075
2077
|
// Play text-to-speech for AI response if enabled
|
|
2076
2078
|
if (botResponse && botResponse.role === 'assistant' && textToSpeechEnabled) {
|
|
2077
2079
|
const ttsContent = botResponse.content;
|
|
2078
|
-
|
|
2079
|
-
playTextToSpeech(ttsContent).catch(error => {
|
|
2080
|
-
console.error('Failed to play text-to-speech:', error);
|
|
2081
|
-
});
|
|
2082
|
-
}, 100);
|
|
2080
|
+
setPendingAutoTts({ messageId: botResponse.id, text: ttsContent });
|
|
2083
2081
|
}
|
|
2084
2082
|
}
|
|
2085
2083
|
catch (error) {
|
|
@@ -2258,6 +2256,7 @@ piiDisplayMode = 'redacted', voiceThresholds, debugVoiceRms = false, sttGlossary
|
|
|
2258
2256
|
}, []);
|
|
2259
2257
|
// Stop text-to-speech (used when user sends new message)
|
|
2260
2258
|
const stopTextToSpeechAndReset = React.useCallback(() => {
|
|
2259
|
+
setPendingAutoTts(null);
|
|
2261
2260
|
stopTextToSpeech();
|
|
2262
2261
|
setRequiresGesture(false);
|
|
2263
2262
|
setIsTtsProcessing(false);
|
|
@@ -2343,6 +2342,27 @@ piiDisplayMode = 'redacted', voiceThresholds, debugVoiceRms = false, sttGlossary
|
|
|
2343
2342
|
console.error('TTS playback failed after retries:', lastError);
|
|
2344
2343
|
setIsTtsProcessing(false);
|
|
2345
2344
|
}, [textToSpeechEnabled, stopTextToSpeech, playTextToSpeechWithRetry]);
|
|
2345
|
+
// Auto-TTS gate: runs after React commits, so the reply text is on screen
|
|
2346
|
+
// before playback starts. Invariant: pendingAutoTts is only set after
|
|
2347
|
+
// sendMessage fully resolves (any stream has ended) — for `-stream` models
|
|
2348
|
+
// isLoading already turns false on the first chunk, so the isLoading check
|
|
2349
|
+
// alone would NOT be a sufficient gate if pending were ever set earlier
|
|
2350
|
+
// (e.g. at stream start for incremental TTS).
|
|
2351
|
+
React.useEffect(() => {
|
|
2352
|
+
if (!pendingAutoTts || !textToSpeechEnabled || isLoading)
|
|
2353
|
+
return;
|
|
2354
|
+
const targetMessage = messages.find(message => message.id === pendingAutoTts.messageId);
|
|
2355
|
+
const renderedText = targetMessage
|
|
2356
|
+
? `${targetMessage.content ?? ''}${targetMessage.partialContent ?? ''}`.trim()
|
|
2357
|
+
: '';
|
|
2358
|
+
if (!renderedText)
|
|
2359
|
+
return;
|
|
2360
|
+
const { text } = pendingAutoTts;
|
|
2361
|
+
setPendingAutoTts(null);
|
|
2362
|
+
playTextToSpeech(text).catch(error => {
|
|
2363
|
+
console.error('Failed to play text-to-speech:', error);
|
|
2364
|
+
});
|
|
2365
|
+
}, [messages, isLoading, pendingAutoTts, textToSpeechEnabled, playTextToSpeech]);
|
|
2346
2366
|
const retryTtsWithGesture = React.useCallback(async () => {
|
|
2347
2367
|
try {
|
|
2348
2368
|
setRequiresGesture(false);
|
package/package.json
CHANGED
|
@@ -174,6 +174,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
174
174
|
const [textToSpeechEnabled, setTextToSpeechEnabled] = useLocalStorage<boolean>('chatbot-text-to-speech-enabled', false);
|
|
175
175
|
const [preferredChatMode, setPreferredChatMode] = useLocalStorage<'text' | 'voice'>('chatbot-active-mode', 'text');
|
|
176
176
|
const [speechingIndex, setSpeechingIndex] = React.useState<number | undefined>(undefined);
|
|
177
|
+
const [pendingAutoTts, setPendingAutoTts] = React.useState<{ messageId: string; text: string } | null>(null);
|
|
177
178
|
|
|
178
179
|
// Speech related refs
|
|
179
180
|
const audioCtxRef = React.useRef<AudioContext | null>(null);
|
|
@@ -1677,6 +1678,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1677
1678
|
): Promise<void> => {
|
|
1678
1679
|
const hasContent = content.trim() || (attachments && attachments.length > 0);
|
|
1679
1680
|
if (!hasContent || isLoading) return;
|
|
1681
|
+
setPendingAutoTts(null);
|
|
1680
1682
|
|
|
1681
1683
|
const currentModel = getCurrentModel();
|
|
1682
1684
|
// Initialize with current model, will be updated with actual model from backend response
|
|
@@ -2318,11 +2320,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2318
2320
|
// Play text-to-speech for AI response if enabled
|
|
2319
2321
|
if (botResponse && botResponse.role === 'assistant' && textToSpeechEnabled) {
|
|
2320
2322
|
const ttsContent = botResponse.content;
|
|
2321
|
-
|
|
2322
|
-
playTextToSpeech(ttsContent).catch(error => {
|
|
2323
|
-
console.error('Failed to play text-to-speech:', error);
|
|
2324
|
-
});
|
|
2325
|
-
}, 100);
|
|
2323
|
+
setPendingAutoTts({ messageId: botResponse.id, text: ttsContent });
|
|
2326
2324
|
}
|
|
2327
2325
|
} catch (error) {
|
|
2328
2326
|
console.error('Failed to send message:', error);
|
|
@@ -2509,6 +2507,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2509
2507
|
|
|
2510
2508
|
// Stop text-to-speech (used when user sends new message)
|
|
2511
2509
|
const stopTextToSpeechAndReset = React.useCallback(() => {
|
|
2510
|
+
setPendingAutoTts(null);
|
|
2512
2511
|
stopTextToSpeech();
|
|
2513
2512
|
setRequiresGesture(false);
|
|
2514
2513
|
setIsTtsProcessing(false);
|
|
@@ -2616,6 +2615,28 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2616
2615
|
setIsTtsProcessing(false);
|
|
2617
2616
|
}, [textToSpeechEnabled, stopTextToSpeech, playTextToSpeechWithRetry]);
|
|
2618
2617
|
|
|
2618
|
+
// Auto-TTS gate: runs after React commits, so the reply text is on screen
|
|
2619
|
+
// before playback starts. Invariant: pendingAutoTts is only set after
|
|
2620
|
+
// sendMessage fully resolves (any stream has ended) — for `-stream` models
|
|
2621
|
+
// isLoading already turns false on the first chunk, so the isLoading check
|
|
2622
|
+
// alone would NOT be a sufficient gate if pending were ever set earlier
|
|
2623
|
+
// (e.g. at stream start for incremental TTS).
|
|
2624
|
+
React.useEffect(() => {
|
|
2625
|
+
if (!pendingAutoTts || !textToSpeechEnabled || isLoading) return;
|
|
2626
|
+
|
|
2627
|
+
const targetMessage = messages.find(message => message.id === pendingAutoTts.messageId);
|
|
2628
|
+
const renderedText = targetMessage
|
|
2629
|
+
? `${targetMessage.content ?? ''}${targetMessage.partialContent ?? ''}`.trim()
|
|
2630
|
+
: '';
|
|
2631
|
+
if (!renderedText) return;
|
|
2632
|
+
|
|
2633
|
+
const { text } = pendingAutoTts;
|
|
2634
|
+
setPendingAutoTts(null);
|
|
2635
|
+
playTextToSpeech(text).catch(error => {
|
|
2636
|
+
console.error('Failed to play text-to-speech:', error);
|
|
2637
|
+
});
|
|
2638
|
+
}, [messages, isLoading, pendingAutoTts, textToSpeechEnabled, playTextToSpeech]);
|
|
2639
|
+
|
|
2619
2640
|
const retryTtsWithGesture = React.useCallback(async () => {
|
|
2620
2641
|
try {
|
|
2621
2642
|
setRequiresGesture(false);
|