@contentgrowth/llm-service 0.8.9 → 0.9.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.
@@ -98,7 +98,7 @@ var MessageBubble = ({
98
98
  )
99
99
  ] }) });
100
100
  }
101
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `flex items-start gap-3 my-4 ${isUser ? "justify-end" : "justify-start"}`, children: [
101
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `flex items-start gap-3 my-1 ${isUser ? "justify-end" : "justify-start"}`, children: [
102
102
  !isUser && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex-shrink-0 h-8 w-8 rounded-full bg-blue-500 flex items-center justify-center text-white", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_solid.SparklesIcon, { className: "h-5 w-5" }) }),
103
103
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
104
104
  "div",
@@ -318,12 +318,23 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
318
318
  var import_react3 = require("react");
319
319
  var useAudioRecorder = (onStop) => {
320
320
  const [isRecording, setIsRecording] = (0, import_react3.useState)(false);
321
+ const [isSimulated, setIsSimulated] = (0, import_react3.useState)(false);
321
322
  const [blob, setBlob] = (0, import_react3.useState)(null);
322
323
  const [error, setError] = (0, import_react3.useState)(null);
323
324
  const mediaRecorderRef = (0, import_react3.useRef)(null);
324
325
  const chunksRef = (0, import_react3.useRef)([]);
325
326
  const start = (0, import_react3.useCallback)(async () => {
326
327
  try {
328
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
329
+ if (process.env.NODE_ENV === "development") {
330
+ console.warn("[useAudioRecorder] MediaDevices not available. Entering simulation mode...");
331
+ setIsRecording(true);
332
+ setIsSimulated(true);
333
+ setError(null);
334
+ return;
335
+ }
336
+ throw new Error("Media devices not available. Ensure you are using HTTPS or localhost.");
337
+ }
327
338
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
328
339
  const mediaRecorder = new MediaRecorder(stream);
329
340
  mediaRecorderRef.current = mediaRecorder;
@@ -351,12 +362,21 @@ var useAudioRecorder = (onStop) => {
351
362
  }
352
363
  }, [onStop]);
353
364
  const stop = (0, import_react3.useCallback)(() => {
365
+ if (isSimulated) {
366
+ setIsRecording(false);
367
+ setIsSimulated(false);
368
+ const simulatedBlob = new Blob(["simulated speech"], { type: "audio/simulated" });
369
+ setBlob(simulatedBlob);
370
+ if (onStop) onStop(simulatedBlob);
371
+ return;
372
+ }
354
373
  if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
355
374
  mediaRecorderRef.current.stop();
356
375
  }
357
- }, []);
376
+ }, [isSimulated, onStop]);
358
377
  return {
359
378
  isRecording,
379
+ isSimulated,
360
380
  start,
361
381
  stop,
362
382
  blob,
@@ -405,21 +425,24 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
405
425
  var _a, _b, _c, _d;
406
426
  const [internalMessage, setInternalMessage] = (0, import_react5.useState)("");
407
427
  const [voiceTrigger, setVoiceTrigger] = (0, import_react5.useState)(null);
408
- const [inputMode, setInputMode] = (0, import_react5.useState)(defaultInputMode);
428
+ const [isTranscribing, setIsTranscribing] = (0, import_react5.useState)(false);
429
+ const [voiceError, setVoiceError] = (0, import_react5.useState)(null);
409
430
  const [isFocused, setIsFocused] = (0, import_react5.useState)(false);
410
431
  const textareaRef = (0, import_react5.useRef)(null);
411
432
  const measurementRef = (0, import_react5.useRef)(null);
412
- const voiceContainerRef = (0, import_react5.useRef)(null);
413
- (0, import_react5.useEffect)(() => {
414
- var _a2;
415
- if (inputMode === "voice") {
416
- (_a2 = voiceContainerRef.current) == null ? void 0 : _a2.focus();
417
- }
418
- }, [inputMode]);
433
+ const pendingSelectionRef = (0, import_react5.useRef)(null);
419
434
  const isControlled = value !== void 0;
420
435
  const message = isControlled ? value : internalMessage;
421
436
  const messageRef = (0, import_react5.useRef)(message);
422
437
  messageRef.current = message;
438
+ (0, import_react5.useLayoutEffect)(() => {
439
+ if (pendingSelectionRef.current && textareaRef.current) {
440
+ const { start, end } = pendingSelectionRef.current;
441
+ textareaRef.current.focus();
442
+ textareaRef.current.setSelectionRange(start, end);
443
+ pendingSelectionRef.current = null;
444
+ }
445
+ }, [message]);
423
446
  const onChangeRef = (0, import_react5.useRef)(onChange);
424
447
  (0, import_react5.useEffect)(() => {
425
448
  onChangeRef.current = onChange;
@@ -432,6 +455,7 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
432
455
  voiceConfigRef.current = voiceConfig;
433
456
  }, [voiceConfig]);
434
457
  const triggerChange = (0, import_react5.useCallback)((newValue) => {
458
+ setVoiceError(null);
435
459
  if (isControlled && onChangeRef.current) {
436
460
  const syntheticEvent = {
437
461
  target: { value: newValue },
@@ -443,34 +467,30 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
443
467
  }
444
468
  }, [isControlled]);
445
469
  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));
446
- useProactiveResize(textareaRef, measurementRef, message, isInputDisabled || !!voiceTrigger || inputMode === "voice");
447
- const handleVoiceKeyDown = (e) => {
448
- if (inputMode !== "voice" || isInputDisabled) return;
449
- if (e.code !== "Space") return;
450
- const activeElement = document.activeElement;
451
- const isInputActive = activeElement && (activeElement.tagName === "INPUT" || activeElement.tagName === "TEXTAREA" || activeElement instanceof HTMLElement && activeElement.isContentEditable);
452
- if (isInputActive) return;
453
- e.preventDefault();
454
- e.stopPropagation();
455
- if (voiceTrigger === "click") return;
456
- if (!e.repeat && !voiceTrigger) {
457
- startRecording("space");
458
- }
459
- };
460
- const handleVoiceKeyUp = (e) => {
461
- if (inputMode !== "voice" || isInputDisabled) return;
462
- if (e.code === "Space") {
463
- if (voiceTrigger === "space") {
464
- e.preventDefault();
465
- stopRecording();
466
- }
470
+ useProactiveResize(textareaRef, measurementRef, message, isInputDisabled || !!voiceTrigger);
471
+ const insertTextAtCursor = (0, import_react5.useCallback)((text) => {
472
+ const textarea = textareaRef.current;
473
+ const currentVal = messageRef.current || "";
474
+ if (!textarea) {
475
+ triggerChange(currentVal + (currentVal ? " " : "") + text);
476
+ return;
467
477
  }
468
- };
478
+ const start = textarea.selectionStart;
479
+ const end = textarea.selectionEnd;
480
+ const before = currentVal.substring(0, start);
481
+ const after = currentVal.substring(end);
482
+ const prefix = start > 0 && !/\s$/.test(before) ? " " : "";
483
+ const newText = before + prefix + text + after;
484
+ const selectionStart = start + prefix.length;
485
+ const selectionEnd = selectionStart + text.length;
486
+ pendingSelectionRef.current = { start: selectionStart, end: selectionEnd };
487
+ triggerChange(newText);
488
+ }, [triggerChange]);
469
489
  const handleVoiceResult = (0, import_react5.useCallback)((text, isFinal) => {
470
490
  if (isFinal) {
471
- triggerChange(messageRef.current + (messageRef.current ? " " : "") + text);
491
+ insertTextAtCursor(text);
472
492
  }
473
- }, []);
493
+ }, [insertTextAtCursor]);
474
494
  const handleVoiceEnd = (0, import_react5.useCallback)(() => {
475
495
  var _a2, _b2;
476
496
  setVoiceTrigger(null);
@@ -480,24 +500,34 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
480
500
  const customRecorder = useAudioRecorder(async (blob) => {
481
501
  var _a2, _b2, _c2;
482
502
  setVoiceTrigger(null);
503
+ setIsTranscribing(true);
504
+ setVoiceError(null);
483
505
  (_b2 = (_a2 = voiceConfigRef.current) == null ? void 0 : _a2.onVoiceEnd) == null ? void 0 : _b2.call(_a2);
506
+ if (blob.type === "audio/simulated") {
507
+ console.log("[ChatInputArea] Handling simulated audio capture");
508
+ await new Promise((resolve) => setTimeout(resolve, 1500));
509
+ insertTextAtCursor("This is a simulated transcription for development testing.");
510
+ setIsTranscribing(false);
511
+ return;
512
+ }
484
513
  if ((_c2 = voiceConfigRef.current) == null ? void 0 : _c2.onAudioCapture) {
485
514
  try {
486
515
  const text = await voiceConfigRef.current.onAudioCapture(blob);
487
- if (text) triggerChange(messageRef.current + (messageRef.current ? " " : "") + text);
516
+ if (text) insertTextAtCursor(text);
488
517
  } catch (e) {
489
518
  console.error("[ChatInputArea] Audio capture failed", e);
519
+ setVoiceError(e.message || "Transcription failed");
520
+ } finally {
521
+ setIsTranscribing(false);
490
522
  }
523
+ } else {
524
+ setIsTranscribing(false);
491
525
  }
492
526
  });
493
527
  (0, import_react5.useImperativeHandle)(ref, () => ({
494
528
  focus: () => {
495
- var _a2, _b2;
496
- if (inputMode === "voice") {
497
- (_a2 = voiceContainerRef.current) == null ? void 0 : _a2.focus();
498
- } else {
499
- (_b2 = textareaRef.current) == null ? void 0 : _b2.focus();
500
- }
529
+ var _a2;
530
+ (_a2 = textareaRef.current) == null ? void 0 : _a2.focus();
501
531
  },
502
532
  setValue: (newValue) => {
503
533
  triggerChange(newValue);
@@ -528,8 +558,9 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
528
558
  };
529
559
  const startRecording = async (trigger) => {
530
560
  var _a2;
531
- if (voiceTrigger) return;
561
+ if (voiceTrigger || isTranscribing) return;
532
562
  setVoiceTrigger(trigger);
563
+ setVoiceError(null);
533
564
  (_a2 = voiceConfig == null ? void 0 : voiceConfig.onVoiceStart) == null ? void 0 : _a2.call(voiceConfig);
534
565
  if ((voiceConfig == null ? void 0 : voiceConfig.mode) === "native") {
535
566
  if (!nativeSpeech.isSupported) {
@@ -541,6 +572,10 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
541
572
  } else {
542
573
  await customRecorder.start();
543
574
  }
575
+ setTimeout(() => {
576
+ var _a3;
577
+ return (_a3 = textareaRef.current) == null ? void 0 : _a3.focus();
578
+ }, 0);
544
579
  };
545
580
  const stopRecording = () => {
546
581
  if (!voiceTrigger) return;
@@ -553,9 +588,7 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
553
588
  const getPlaceholder = () => {
554
589
  if (placeholder) return placeholder;
555
590
  if (voiceTrigger) return "Listening...";
556
- if (currentTask == null ? void 0 : currentTask.complete) {
557
- return "Task completed!";
558
- }
591
+ if (currentTask == null ? void 0 : currentTask.complete) return "Task completed!";
559
592
  if ((lastInteractiveMessage == null ? void 0 : lastInteractiveMessage.interactive) && (lastInteractiveMessage == null ? void 0 : lastInteractiveMessage.interactiveData) && !(lastInteractiveMessage == null ? void 0 : lastInteractiveMessage.isResponseSubmitted)) {
560
593
  const interactiveType = lastInteractiveMessage.interactiveData.function;
561
594
  switch (interactiveType) {
@@ -584,85 +617,61 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
584
617
  {
585
618
  type: "button",
586
619
  onClick: () => {
587
- if (inputMode === "voice" && voiceTrigger) {
620
+ if (voiceTrigger) {
588
621
  stopRecording();
622
+ } else {
623
+ startRecording("click");
589
624
  }
590
- setInputMode((prev) => prev === "text" ? "voice" : "text");
591
625
  },
592
- className: "mb-1 p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-full transition-colors flex-shrink-0 border border-gray-300 bg-white",
593
- title: inputMode === "text" ? "Switch to Voice" : "Switch to Keyboard",
594
- children: inputMode === "text" ? (
595
- // Voice Icon (Waveform)
596
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-5 h-5 text-gray-600", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M11.25 4.532A.75.75 0 0 1 12 5.25v13.5a.75.75 0 0 1-1.5 0V5.25a.75.75 0 0 1 .75-.718ZM7.5 8.25a.75.75 0 0 1 .75.75v5.25a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm9 0a.75.75 0 0 1 .75.75v5.25a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75ZM3.75 10.5a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5a.75.75 0 0 1 .75-.75Zm16.5 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5a.75.75 0 0 1 .75-.75Z" }) })
597
- ) : (
598
- // Keyboard Icon (Filled)
599
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-5 h-5 text-gray-600", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { fillRule: "evenodd", d: "M3 6a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6Zm4.5 3a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1-.75-.75V9Zm6 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1-.75-.75V9Zm6 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1-.75-.75V9Zm-12 4.5a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1-.75-.75v-1.5Zm6 0a.75.75 0 0 1 .75-.75h6.75a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-.75.75h-6.75a.75.75 0 0 1-.75-.75v-1.5Z", clipRule: "evenodd" }) })
600
- )
626
+ className: `mb-1 p-2 rounded-full transition-all duration-300 flex-shrink-0 border ${voiceTrigger ? "text-white border-orange-400 bg-orange-500 scale-110 shadow-lg animate-pulse" : "text-gray-500 border-gray-300 bg-white hover:text-gray-700 hover:bg-gray-100"}`,
627
+ title: voiceTrigger ? "Stop Recording" : "Start Voice Input",
628
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_outline.MicrophoneIcon, { className: "w-5 h-5" })
601
629
  }
602
630
  ),
603
631
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
604
632
  "div",
605
633
  {
606
- ref: voiceContainerRef,
607
- tabIndex: inputMode === "voice" ? 0 : -1,
608
- onKeyDown: handleVoiceKeyDown,
609
- onKeyUp: handleVoiceKeyUp,
610
- onFocus: () => setIsFocused(true),
611
- onBlur: () => setIsFocused(false),
612
- 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",
634
+ tabIndex: -1,
635
+ className: `flex-1 flex items-center border border-gray-300 rounded-lg overflow-hidden outline-none bg-white min-h-[42px] mb-1 transition-all ${voiceTrigger ? "ring-2 ring-orange-100 border-orange-300" : "focus-within:ring-2 focus-within:ring-blue-500 focus-within:border-blue-500"}`,
613
636
  children: [
614
- inputMode === "text" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
615
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
616
- "span",
617
- {
618
- ref: measurementRef,
619
- className: "absolute invisible whitespace-pre-wrap p-0 m-0 text-gray-700 leading-6",
620
- style: { fontSize: "1rem" }
621
- }
622
- ),
623
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
624
- "textarea",
625
- {
626
- ref: textareaRef,
627
- value: message,
628
- onChange: (e) => {
629
- if (isControlled && onChange) {
630
- onChange(e);
631
- } else {
632
- setInternalMessage(e.target.value);
633
- }
634
- },
635
- onKeyDown: handleKeyDown,
636
- placeholder: getPlaceholder(),
637
- disabled: isInputDisabled || !!voiceTrigger,
638
- rows: 1,
639
- 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" : ""}`
640
- }
641
- )
642
- ] }),
643
- inputMode === "voice" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex-grow flex flex-col justify-center items-center p-1 relative", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
644
- "button",
637
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
638
+ "span",
639
+ {
640
+ ref: measurementRef,
641
+ className: "absolute invisible whitespace-pre-wrap p-0 m-0 text-gray-700 leading-6",
642
+ style: { fontSize: "1rem" }
643
+ }
644
+ ),
645
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
646
+ "textarea",
645
647
  {
646
- type: "button",
647
- onClick: () => {
648
- if (voiceTrigger === "click") {
649
- stopRecording();
650
- } else if (!voiceTrigger) {
651
- startRecording("click");
648
+ ref: textareaRef,
649
+ value: message,
650
+ onChange: (e) => {
651
+ if (isControlled && onChange) {
652
+ onChange(e);
653
+ } else {
654
+ setInternalMessage(e.target.value);
652
655
  }
653
656
  },
654
- disabled: isInputDisabled || voiceTrigger === "space",
655
- 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" : ""}`,
656
- children: voiceTrigger ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
657
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "w-2 h-2 rounded-full bg-red-500 animate-ping mr-2" }),
658
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { children: [
659
- "Listening... ",
660
- voiceTrigger === "space" ? "(Release Space to send)" : "Tap to send"
661
- ] })
662
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Tap to Talk" })
657
+ onKeyDown: handleKeyDown,
658
+ onFocus: () => {
659
+ setIsFocused(true);
660
+ setVoiceError(null);
661
+ },
662
+ onBlur: () => setIsFocused(false),
663
+ placeholder: getPlaceholder(),
664
+ disabled: isInputDisabled,
665
+ readOnly: !!voiceTrigger || isTranscribing,
666
+ rows: 1,
667
+ className: `flex-grow px-4 py-2 outline-none text-gray-700 placeholder-gray-500 resize-none leading-6 w-full ${isInputDisabled ? "bg-gray-100 cursor-not-allowed" : "bg-transparent"} ${voiceTrigger || isTranscribing ? "cursor-default" : ""}`
663
668
  }
664
- ) }),
665
- (inputMode === "text" || isSending) && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "relative mx-2 flex-shrink-0", children: [
669
+ ),
670
+ isTranscribing && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex-shrink-0 animate-spin mr-2", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { className: "w-4 h-4 text-orange-500", viewBox: "0 0 24 24", children: [
671
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4", fill: "none" }),
672
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { className: "opacity-75", fill: "currentColor", 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" })
673
+ ] }) }),
674
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "relative mx-2 flex-shrink-0", children: [
666
675
  isSending && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "absolute -inset-1", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
667
676
  "svg",
668
677
  {
@@ -705,24 +714,10 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
705
714
  handleSubmit();
706
715
  }
707
716
  },
708
- disabled: (currentTask == null ? void 0 : currentTask.complete) || isSending && !onStop || isInputDisabled || !!voiceTrigger,
717
+ disabled: (currentTask == null ? void 0 : currentTask.complete) || isSending && !onStop || isInputDisabled,
709
718
  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"}`,
710
719
  title: isSending && onStop ? "Stop generating" : "Send message",
711
- children: isSending ? onStop ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_outline.StopIcon, { className: "h-5 w-5" }) : (
712
- // AND we show the overlay spinner outside?
713
- // Actually `ChatInput.tsx` lines 117-140 are `isLoading && (...)`. It is always shown when loading.
714
- // So we have a spinner ring AROUND the button (absolute -inset-1).
715
- // AND potentially a spinner INSIDE the button if no onStop?
716
- // In my case, I will stick to:
717
- // If onStop: Show StopIcon. Button is Red.
718
- // If !onStop: Show Spinner inside? Or just let the outer ring do the work?
719
- // Legacy `Spinner` component usage inside button suggests double spinner if we are not careful.
720
- // But usually `onStop` is provided for streaming.
721
- // If I look at the screenshot, it shows a RED button (with stop icon) and a BLUE ring around it.
722
- // That matches: Red button (bg-red-500) + Blue Spinner Ring (text-blue-500).
723
- // So I will replicate that structure.
724
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_outline.StopIcon, { className: "h-5 w-5" })
725
- ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_outline.PaperAirplaneIcon, { className: "h-5 w-5" })
720
+ children: isSending ? onStop ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_outline.StopIcon, { className: "h-5 w-5" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_outline.PaperAirplaneIcon, { className: "h-5 w-5" })
726
721
  }
727
722
  )
728
723
  ] })
@@ -731,18 +726,10 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
731
726
  )
732
727
  ] }),
733
728
  inputHint && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "text-sm text-red-500 bg-red-50 py-1 px-4 rounded-lg mt-1", children: inputHint }),
734
- hintText && inputMode === "text" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-xs text-gray-500 ml-12 mb-2 mt-1", children: hintText }),
735
- inputMode === "voice" && !voiceTrigger && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
736
- "p",
737
- {
738
- className: "text-[10px] text-gray-400 font-medium ml-12 text-center -mt-1 mb-1 cursor-pointer hover:text-gray-600 transition-colors",
739
- onClick: () => {
740
- var _a2;
741
- return (_a2 = voiceContainerRef.current) == null ? void 0 : _a2.focus();
742
- },
743
- children: isFocused ? "Click to talk or hold space to talk" : "Tap to talk or click here to focus and push space to talk"
744
- }
745
- )
729
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "ml-[46px] mb-2 mt-0.5 min-h-[0.75rem]", style: { marginLeft: "48px" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: `text-[10px] leading-tight transition-all duration-200 ${voiceError ? "text-red-500" : voiceTrigger || isTranscribing ? "text-orange-600 font-medium" : "text-gray-400"}`, children: voiceError ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "flex items-center gap-1 font-semibold italic", children: [
730
+ "Error: ",
731
+ voiceError
732
+ ] }) : isTranscribing ? "Transcribing, please wait..." : voiceTrigger ? "Listening... tap mic icon again to stop" : hintText || (voiceConfig ? "Type in text or tap mic icon to talk" : "Type your message...") }) })
746
733
  ] });
747
734
  });
748
735
  ChatInputArea.displayName = "ChatInputArea";
@@ -1265,7 +1252,7 @@ var ChatMessageList = ({
1265
1252
  "div",
1266
1253
  {
1267
1254
  ref: chatContainerRef,
1268
- className: "flex-1 overflow-y-auto p-4 space-y-4 bg-gray-50",
1255
+ className: "flex-1 overflow-y-auto p-4 space-y-8 bg-gray-50",
1269
1256
  children: [
1270
1257
  chatHistory.length === 0 && !isProcessing && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "text-center py-8", children: [
1271
1258
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h3", { className: "text-lg font-medium text-gray-700 mb-2", children: "How can I help you today?" }),
@@ -1321,7 +1308,7 @@ var ChatMessageList = ({
1321
1308
  "div",
1322
1309
  {
1323
1310
  ref: processingIndicatorRef,
1324
- className: "flex justify-start",
1311
+ className: "flex justify-start my-4",
1325
1312
  children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "bg-white text-gray-800 border border-gray-200 rounded-lg px-4 py-2 max-w-[85%]", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center", children: [
1326
1313
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-sm", children: processingHint }),
1327
1314
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "ml-2 flex space-x-1", children: [