@contentgrowth/llm-service 0.8.4 → 0.8.5

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.
@@ -148,7 +148,7 @@ function ChatHeader({
148
148
  }
149
149
 
150
150
  // src/ui/react/components/ChatInputArea.tsx
151
- import { useState as useState3, useRef as useRef3, useImperativeHandle, forwardRef } from "react";
151
+ import { useState as useState3, useRef as useRef3, useImperativeHandle, forwardRef, useEffect as useEffect3 } from "react";
152
152
  import { StopIcon, PaperAirplaneIcon } from "@heroicons/react/24/outline";
153
153
 
154
154
  // src/ui/react/hooks/useSpeechRecognition.ts
@@ -159,6 +159,8 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
159
159
  const [error, setError] = useState(null);
160
160
  const [isSupported, setIsSupported] = useState(false);
161
161
  const recognitionRef = useRef(null);
162
+ const isSimulatingRef = useRef(false);
163
+ const simulationTimeoutRef = useRef(null);
162
164
  useEffect(() => {
163
165
  if (typeof window !== "undefined") {
164
166
  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
@@ -173,6 +175,7 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
173
175
  setError(null);
174
176
  };
175
177
  recognition.onend = () => {
178
+ if (isSimulatingRef.current) return;
176
179
  setIsListening(false);
177
180
  if (onEnd) onEnd();
178
181
  };
@@ -194,15 +197,18 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
194
197
  recognition.onerror = (event) => {
195
198
  if (event.error === "not-allowed" && process.env.NODE_ENV === "development") {
196
199
  console.warn("Speech recognition blocked. Simulating input for development...");
200
+ isSimulatingRef.current = true;
197
201
  setError(null);
198
202
  setIsListening(true);
199
- setTimeout(() => {
203
+ simulationTimeoutRef.current = setTimeout(() => {
200
204
  const mockText = "This is a simulated voice input for testing.";
201
205
  setTranscript((prev) => prev + (prev ? " " : "") + mockText);
202
206
  if (onResult) onResult(mockText, true);
207
+ isSimulatingRef.current = false;
203
208
  setIsListening(false);
204
209
  if (onEnd) onEnd();
205
- }, 1e3);
210
+ simulationTimeoutRef.current = null;
211
+ }, 3e3);
206
212
  return;
207
213
  }
208
214
  console.error("Speech recognition error", event.error);
@@ -213,11 +219,13 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
213
219
  }
214
220
  }
215
221
  return () => {
216
- if (recognitionRef.current) {
217
- recognitionRef.current.stop();
222
+ if (isSimulatingRef.current && simulationTimeoutRef.current) {
223
+ clearTimeout(simulationTimeoutRef.current);
224
+ simulationTimeoutRef.current = null;
218
225
  }
226
+ recognitionRef.current.stop();
219
227
  };
220
- }, [onResult, onEnd]);
228
+ }, [onResult, onEnd, language]);
221
229
  const start = useCallback(() => {
222
230
  if (recognitionRef.current && !isListening) {
223
231
  try {
@@ -229,10 +237,23 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
229
237
  }
230
238
  }, [isListening]);
231
239
  const stop = useCallback(() => {
240
+ if (isSimulatingRef.current) {
241
+ if (simulationTimeoutRef.current) {
242
+ clearTimeout(simulationTimeoutRef.current);
243
+ simulationTimeoutRef.current = null;
244
+ }
245
+ const mockText = "This is a simulated voice input for testing.";
246
+ setTranscript((prev) => prev + (prev ? " " : "") + mockText);
247
+ if (onResult) onResult(mockText, true);
248
+ isSimulatingRef.current = false;
249
+ setIsListening(false);
250
+ if (onEnd) onEnd();
251
+ return;
252
+ }
232
253
  if (recognitionRef.current && isListening) {
233
254
  recognitionRef.current.stop();
234
255
  }
235
- }, [isListening]);
256
+ }, [isListening, onResult, onEnd]);
236
257
  const resetTranscript = useCallback(() => {
237
258
  setTranscript("");
238
259
  }, []);
@@ -330,14 +351,23 @@ var ChatInputArea = forwardRef(({
330
351
  hintText,
331
352
  placeholder,
332
353
  value,
333
- onChange
354
+ onChange,
355
+ defaultInputMode = "text"
334
356
  }, ref) => {
335
357
  var _a, _b, _c, _d;
336
358
  const [internalMessage, setInternalMessage] = useState3("");
337
- const [isVoiceActive, setIsVoiceActive] = useState3(false);
338
- const [inputMode, setInputMode] = useState3("text");
359
+ const [voiceTrigger, setVoiceTrigger] = useState3(null);
360
+ const [inputMode, setInputMode] = useState3(defaultInputMode);
361
+ const [isFocused, setIsFocused] = useState3(false);
339
362
  const textareaRef = useRef3(null);
340
363
  const measurementRef = useRef3(null);
364
+ const voiceContainerRef = useRef3(null);
365
+ useEffect3(() => {
366
+ var _a2;
367
+ if (inputMode === "voice") {
368
+ (_a2 = voiceContainerRef.current) == null ? void 0 : _a2.focus();
369
+ }
370
+ }, [inputMode]);
341
371
  const isControlled = value !== void 0;
342
372
  const message = isControlled ? value : internalMessage;
343
373
  const { voice: globalVoice } = useChatConfig();
@@ -355,17 +385,39 @@ var ChatInputArea = forwardRef(({
355
385
  }
356
386
  };
357
387
  const isInputDisabled = (currentTask == null ? void 0 : currentTask.complete) || (lastInteractiveMessage == null ? void 0 : lastInteractiveMessage.interactive) && (((_b = lastInteractiveMessage == null ? void 0 : lastInteractiveMessage.interactiveData) == null ? void 0 : _b.function) === "form" && !(lastInteractiveMessage == null ? void 0 : lastInteractiveMessage.isResponseSubmitted) || ((_c = lastInteractiveMessage == null ? void 0 : lastInteractiveMessage.interactiveData) == null ? void 0 : _c.function) === "confirm" && !(lastInteractiveMessage == null ? void 0 : lastInteractiveMessage.isResponseSubmitted));
358
- useProactiveResize(textareaRef, measurementRef, message, isInputDisabled || isVoiceActive || inputMode === "voice");
388
+ useProactiveResize(textareaRef, measurementRef, message, isInputDisabled || !!voiceTrigger || inputMode === "voice");
389
+ const handleVoiceKeyDown = (e) => {
390
+ if (inputMode !== "voice" || isInputDisabled) return;
391
+ if (e.code !== "Space") return;
392
+ const activeElement = document.activeElement;
393
+ const isInputActive = activeElement && (activeElement.tagName === "INPUT" || activeElement.tagName === "TEXTAREA" || activeElement instanceof HTMLElement && activeElement.isContentEditable);
394
+ if (isInputActive) return;
395
+ e.preventDefault();
396
+ e.stopPropagation();
397
+ if (voiceTrigger === "click") return;
398
+ if (!e.repeat && !voiceTrigger) {
399
+ startRecording("space");
400
+ }
401
+ };
402
+ const handleVoiceKeyUp = (e) => {
403
+ if (inputMode !== "voice" || isInputDisabled) return;
404
+ if (e.code === "Space") {
405
+ if (voiceTrigger === "space") {
406
+ e.preventDefault();
407
+ stopRecording();
408
+ }
409
+ }
410
+ };
359
411
  const nativeSpeech = useSpeechRecognition((text) => {
360
412
  triggerChange(message + (message ? " " : "") + text);
361
413
  }, () => {
362
414
  var _a2;
363
- setIsVoiceActive(false);
415
+ setVoiceTrigger(null);
364
416
  (_a2 = voiceConfig == null ? void 0 : voiceConfig.onVoiceEnd) == null ? void 0 : _a2.call(voiceConfig);
365
417
  }, voiceConfig == null ? void 0 : voiceConfig.language);
366
418
  const customRecorder = useAudioRecorder(async (blob) => {
367
419
  var _a2;
368
- setIsVoiceActive(false);
420
+ setVoiceTrigger(null);
369
421
  (_a2 = voiceConfig == null ? void 0 : voiceConfig.onVoiceEnd) == null ? void 0 : _a2.call(voiceConfig);
370
422
  if (voiceConfig == null ? void 0 : voiceConfig.onAudioCapture) {
371
423
  try {
@@ -378,8 +430,12 @@ var ChatInputArea = forwardRef(({
378
430
  });
379
431
  useImperativeHandle(ref, () => ({
380
432
  focus: () => {
381
- var _a2;
382
- (_a2 = textareaRef.current) == null ? void 0 : _a2.focus();
433
+ var _a2, _b2;
434
+ if (inputMode === "voice") {
435
+ (_a2 = voiceContainerRef.current) == null ? void 0 : _a2.focus();
436
+ } else {
437
+ (_b2 = textareaRef.current) == null ? void 0 : _b2.focus();
438
+ }
383
439
  },
384
440
  setValue: (newValue) => {
385
441
  triggerChange(newValue);
@@ -408,15 +464,15 @@ var ChatInputArea = forwardRef(({
408
464
  handleSubmit();
409
465
  }
410
466
  };
411
- const startRecording = async () => {
467
+ const startRecording = async (trigger) => {
412
468
  var _a2;
413
- if (isVoiceActive) return;
414
- setIsVoiceActive(true);
469
+ if (voiceTrigger) return;
470
+ setVoiceTrigger(trigger);
415
471
  (_a2 = voiceConfig == null ? void 0 : voiceConfig.onVoiceStart) == null ? void 0 : _a2.call(voiceConfig);
416
472
  if ((voiceConfig == null ? void 0 : voiceConfig.mode) === "native") {
417
473
  if (!nativeSpeech.isSupported) {
418
474
  alert("Speech recognition is not supported in this browser.");
419
- setIsVoiceActive(false);
475
+ setVoiceTrigger(null);
420
476
  return;
421
477
  }
422
478
  nativeSpeech.start();
@@ -425,7 +481,7 @@ var ChatInputArea = forwardRef(({
425
481
  }
426
482
  };
427
483
  const stopRecording = () => {
428
- if (!isVoiceActive) return;
484
+ if (!voiceTrigger) return;
429
485
  if ((voiceConfig == null ? void 0 : voiceConfig.mode) === "native") {
430
486
  nativeSpeech.stop();
431
487
  } else {
@@ -434,7 +490,7 @@ var ChatInputArea = forwardRef(({
434
490
  };
435
491
  const getPlaceholder = () => {
436
492
  if (placeholder) return placeholder;
437
- if (isVoiceActive) return "Listening...";
493
+ if (voiceTrigger) return "Listening...";
438
494
  if (currentTask == null ? void 0 : currentTask.complete) {
439
495
  return "Task completed!";
440
496
  }
@@ -466,7 +522,7 @@ var ChatInputArea = forwardRef(({
466
522
  {
467
523
  type: "button",
468
524
  onClick: () => {
469
- if (inputMode === "voice" && isVoiceActive) {
525
+ if (inputMode === "voice" && voiceTrigger) {
470
526
  stopRecording();
471
527
  }
472
528
  setInputMode((prev) => prev === "text" ? "voice" : "text");
@@ -482,117 +538,149 @@ var ChatInputArea = forwardRef(({
482
538
  )
483
539
  }
484
540
  ),
485
- /* @__PURE__ */ jsxs3("div", { className: "flex-1 flex items-center border border-gray-300 rounded-lg overflow-hidden focus-within:ring-2 focus-within:ring-blue-500 focus-within:border-blue-500 bg-white min-h-[42px] mb-1", children: [
486
- inputMode === "text" && /* @__PURE__ */ jsxs3(Fragment, { children: [
487
- /* @__PURE__ */ jsx5(
488
- "span",
489
- {
490
- ref: measurementRef,
491
- className: "absolute invisible whitespace-pre-wrap p-0 m-0 text-gray-700 leading-6",
492
- style: { fontSize: "1rem" }
493
- }
494
- ),
495
- /* @__PURE__ */ jsx5(
496
- "textarea",
497
- {
498
- ref: textareaRef,
499
- value: message,
500
- onChange: (e) => {
501
- if (isControlled && onChange) {
502
- onChange(e);
503
- } else {
504
- setInternalMessage(e.target.value);
541
+ /* @__PURE__ */ jsxs3(
542
+ "div",
543
+ {
544
+ ref: voiceContainerRef,
545
+ tabIndex: inputMode === "voice" ? 0 : -1,
546
+ onKeyDown: handleVoiceKeyDown,
547
+ onKeyUp: handleVoiceKeyUp,
548
+ onFocus: () => setIsFocused(true),
549
+ onBlur: () => setIsFocused(false),
550
+ className: "flex-1 flex items-center border border-gray-300 rounded-lg overflow-hidden outline-none focus-within:ring-2 focus-within:ring-blue-500 focus-within:border-blue-500 bg-white min-h-[42px] mb-1",
551
+ children: [
552
+ inputMode === "text" && /* @__PURE__ */ jsxs3(Fragment, { children: [
553
+ /* @__PURE__ */ jsx5(
554
+ "span",
555
+ {
556
+ ref: measurementRef,
557
+ className: "absolute invisible whitespace-pre-wrap p-0 m-0 text-gray-700 leading-6",
558
+ style: { fontSize: "1rem" }
505
559
  }
506
- },
507
- onKeyDown: handleKeyDown,
508
- placeholder: getPlaceholder(),
509
- disabled: isInputDisabled || isVoiceActive,
510
- rows: 1,
511
- className: `flex-grow px-4 py-2 outline-none text-gray-700 placeholder-gray-500 disabled:bg-gray-100 resize-none leading-6 w-full ${isInputDisabled ? "cursor-not-allowed" : ""}`
512
- }
513
- )
514
- ] }),
515
- inputMode === "voice" && /* @__PURE__ */ jsx5("div", { className: "flex-grow flex justify-center items-center p-1", children: /* @__PURE__ */ jsx5(
516
- "button",
517
- {
518
- type: "button",
519
- onMouseDown: startRecording,
520
- onMouseUp: stopRecording,
521
- onTouchStart: startRecording,
522
- onTouchEnd: stopRecording,
523
- disabled: isInputDisabled,
524
- className: `flex-grow py-2 text-center font-medium rounded-md transition-colors select-none ${isVoiceActive ? "bg-blue-100 text-blue-700" : "bg-gray-50 text-gray-700 hover:bg-gray-100"}`,
525
- children: isVoiceActive ? "Release to Send" : "Hold to Talk"
526
- }
527
- ) }),
528
- (inputMode === "text" || isSending) && /* @__PURE__ */ jsxs3("div", { className: "relative mx-2 flex-shrink-0", children: [
529
- isSending && /* @__PURE__ */ jsx5("div", { className: "absolute -inset-1", children: /* @__PURE__ */ jsxs3(
530
- "svg",
531
- {
532
- className: "animate-spin h-full w-full text-blue-500 opacity-75",
533
- xmlns: "http://www.w3.org/2000/svg",
534
- fill: "none",
535
- viewBox: "0 0 24 24",
536
- children: [
537
- /* @__PURE__ */ jsx5(
538
- "circle",
539
- {
540
- className: "opacity-25",
541
- cx: "12",
542
- cy: "12",
543
- r: "10",
544
- stroke: "currentColor",
545
- strokeWidth: "4"
546
- }
547
- ),
548
- /* @__PURE__ */ jsx5(
549
- "path",
550
- {
551
- className: "opacity-75",
552
- fill: "currentColor",
553
- d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
560
+ ),
561
+ /* @__PURE__ */ jsx5(
562
+ "textarea",
563
+ {
564
+ ref: textareaRef,
565
+ value: message,
566
+ onChange: (e) => {
567
+ if (isControlled && onChange) {
568
+ onChange(e);
569
+ } else {
570
+ setInternalMessage(e.target.value);
571
+ }
572
+ },
573
+ onKeyDown: handleKeyDown,
574
+ placeholder: getPlaceholder(),
575
+ disabled: isInputDisabled || !!voiceTrigger,
576
+ rows: 1,
577
+ className: `flex-grow px-4 py-2 outline-none text-gray-700 placeholder-gray-500 disabled:bg-gray-100 resize-none leading-6 w-full ${isInputDisabled ? "cursor-not-allowed" : ""}`
578
+ }
579
+ )
580
+ ] }),
581
+ inputMode === "voice" && /* @__PURE__ */ jsx5("div", { className: "flex-grow flex flex-col justify-center items-center p-1 relative", children: /* @__PURE__ */ jsx5(
582
+ "button",
583
+ {
584
+ type: "button",
585
+ onClick: () => {
586
+ if (voiceTrigger === "click") {
587
+ stopRecording();
588
+ } else if (!voiceTrigger) {
589
+ startRecording("click");
554
590
  }
555
- )
556
- ]
557
- }
558
- ) }),
559
- /* @__PURE__ */ jsx5(
560
- "button",
561
- {
562
- type: "button",
563
- onClick: (e) => {
564
- if (isSending && onStop) {
565
- e.preventDefault();
566
- onStop();
567
- } else {
568
- handleSubmit();
591
+ },
592
+ disabled: isInputDisabled || voiceTrigger === "space",
593
+ className: `w-full py-2 text-center font-medium rounded-md transition-all select-none flex items-center justify-center gap-2 ${voiceTrigger ? "bg-red-50 text-red-600 animate-pulse border border-red-200" : "bg-gray-50 text-gray-700 hover:bg-gray-100"} ${voiceTrigger === "space" ? "opacity-90 cursor-default" : ""}`,
594
+ children: voiceTrigger ? /* @__PURE__ */ jsxs3(Fragment, { children: [
595
+ /* @__PURE__ */ jsx5("div", { className: "w-2 h-2 rounded-full bg-red-500 animate-ping mr-2" }),
596
+ /* @__PURE__ */ jsxs3("span", { children: [
597
+ "Listening... ",
598
+ voiceTrigger === "space" ? "(Release Space to send)" : "Tap to send"
599
+ ] })
600
+ ] }) : /* @__PURE__ */ jsx5("span", { children: "Tap to Talk" })
601
+ }
602
+ ) }),
603
+ (inputMode === "text" || isSending) && /* @__PURE__ */ jsxs3("div", { className: "relative mx-2 flex-shrink-0", children: [
604
+ isSending && /* @__PURE__ */ jsx5("div", { className: "absolute -inset-1", children: /* @__PURE__ */ jsxs3(
605
+ "svg",
606
+ {
607
+ className: "animate-spin h-full w-full text-blue-500 opacity-75",
608
+ xmlns: "http://www.w3.org/2000/svg",
609
+ fill: "none",
610
+ viewBox: "0 0 24 24",
611
+ children: [
612
+ /* @__PURE__ */ jsx5(
613
+ "circle",
614
+ {
615
+ className: "opacity-25",
616
+ cx: "12",
617
+ cy: "12",
618
+ r: "10",
619
+ stroke: "currentColor",
620
+ strokeWidth: "4"
621
+ }
622
+ ),
623
+ /* @__PURE__ */ jsx5(
624
+ "path",
625
+ {
626
+ className: "opacity-75",
627
+ fill: "currentColor",
628
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
629
+ }
630
+ )
631
+ ]
569
632
  }
570
- },
571
- disabled: (currentTask == null ? void 0 : currentTask.complete) || isSending && !onStop || isInputDisabled || isVoiceActive,
572
- className: `relative z-10 text-white rounded-full p-2 transition-colors duration-200 disabled:bg-gray-400 disabled:cursor-not-allowed ${isSending && onStop ? "bg-red-500 hover:bg-red-600" : "bg-blue-600 hover:bg-blue-700"}`,
573
- title: isSending && onStop ? "Stop generating" : "Send message",
574
- children: isSending ? onStop ? /* @__PURE__ */ jsx5(StopIcon, { className: "h-5 w-5" }) : (
575
- // AND we show the overlay spinner outside?
576
- // Actually `ChatInput.tsx` lines 117-140 are `isLoading && (...)`. It is always shown when loading.
577
- // So we have a spinner ring AROUND the button (absolute -inset-1).
578
- // AND potentially a spinner INSIDE the button if no onStop?
579
- // In my case, I will stick to:
580
- // If onStop: Show StopIcon. Button is Red.
581
- // If !onStop: Show Spinner inside? Or just let the outer ring do the work?
582
- // Legacy `Spinner` component usage inside button suggests double spinner if we are not careful.
583
- // But usually `onStop` is provided for streaming.
584
- // If I look at the screenshot, it shows a RED button (with stop icon) and a BLUE ring around it.
585
- // That matches: Red button (bg-red-500) + Blue Spinner Ring (text-blue-500).
586
- // So I will replicate that structure.
587
- /* @__PURE__ */ jsx5(StopIcon, { className: "h-5 w-5" })
588
- ) : /* @__PURE__ */ jsx5(PaperAirplaneIcon, { className: "h-5 w-5" })
589
- }
590
- )
591
- ] })
592
- ] })
633
+ ) }),
634
+ /* @__PURE__ */ jsx5(
635
+ "button",
636
+ {
637
+ type: "button",
638
+ onClick: (e) => {
639
+ if (isSending && onStop) {
640
+ e.preventDefault();
641
+ onStop();
642
+ } else {
643
+ handleSubmit();
644
+ }
645
+ },
646
+ disabled: (currentTask == null ? void 0 : currentTask.complete) || isSending && !onStop || isInputDisabled || !!voiceTrigger,
647
+ className: `relative z-10 text-white rounded-full p-2 transition-colors duration-200 disabled:bg-gray-400 disabled:cursor-not-allowed ${isSending && onStop ? "bg-red-500 hover:bg-red-600" : "bg-blue-600 hover:bg-blue-700"}`,
648
+ title: isSending && onStop ? "Stop generating" : "Send message",
649
+ children: isSending ? onStop ? /* @__PURE__ */ jsx5(StopIcon, { className: "h-5 w-5" }) : (
650
+ // AND we show the overlay spinner outside?
651
+ // Actually `ChatInput.tsx` lines 117-140 are `isLoading && (...)`. It is always shown when loading.
652
+ // So we have a spinner ring AROUND the button (absolute -inset-1).
653
+ // AND potentially a spinner INSIDE the button if no onStop?
654
+ // In my case, I will stick to:
655
+ // If onStop: Show StopIcon. Button is Red.
656
+ // If !onStop: Show Spinner inside? Or just let the outer ring do the work?
657
+ // Legacy `Spinner` component usage inside button suggests double spinner if we are not careful.
658
+ // But usually `onStop` is provided for streaming.
659
+ // If I look at the screenshot, it shows a RED button (with stop icon) and a BLUE ring around it.
660
+ // That matches: Red button (bg-red-500) + Blue Spinner Ring (text-blue-500).
661
+ // So I will replicate that structure.
662
+ /* @__PURE__ */ jsx5(StopIcon, { className: "h-5 w-5" })
663
+ ) : /* @__PURE__ */ jsx5(PaperAirplaneIcon, { className: "h-5 w-5" })
664
+ }
665
+ )
666
+ ] })
667
+ ]
668
+ }
669
+ )
593
670
  ] }),
594
671
  inputHint && /* @__PURE__ */ jsx5("div", { className: "text-sm text-red-500 bg-red-50 py-1 px-4 rounded-lg mt-1", children: inputHint }),
595
- hintText && inputMode === "text" && /* @__PURE__ */ jsx5("p", { className: "text-xs text-gray-500 ml-12 mb-2 mt-1", children: hintText })
672
+ hintText && inputMode === "text" && /* @__PURE__ */ jsx5("p", { className: "text-xs text-gray-500 ml-12 mb-2 mt-1", children: hintText }),
673
+ inputMode === "voice" && !voiceTrigger && /* @__PURE__ */ jsx5(
674
+ "p",
675
+ {
676
+ className: "text-[10px] text-gray-400 font-medium ml-12 text-center -mt-1 mb-1 cursor-pointer hover:text-gray-600 transition-colors",
677
+ onClick: () => {
678
+ var _a2;
679
+ return (_a2 = voiceContainerRef.current) == null ? void 0 : _a2.focus();
680
+ },
681
+ children: isFocused ? "Click to talk or hold space to talk" : "Tap to talk or click here to focus and push space to talk"
682
+ }
683
+ )
596
684
  ] });
597
685
  });
598
686
  ChatInputArea.displayName = "ChatInputArea";