@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,CAmuGlD,CAAC"}
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
- setTimeout(() => {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytexbyte/nxtlinq-ai-agent-ui-react-development",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Official React Web UI for nxtlinq AI Agent — drop-in chat widget",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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
- setTimeout(() => {
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);