@contentgrowth/llm-service 0.8.9 → 0.9.0
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/dist/ui/react/components/index.cjs +101 -137
- package/dist/ui/react/components/index.cjs.map +1 -1
- package/dist/ui/react/components/index.d.cts +1 -0
- package/dist/ui/react/components/index.d.ts +1 -0
- package/dist/ui/react/components/index.js +104 -140
- package/dist/ui/react/components/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -148,8 +148,8 @@ function ChatHeader({
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
// src/ui/react/components/ChatInputArea.tsx
|
|
151
|
-
import { useState as useState3, useRef as useRef3, useImperativeHandle, forwardRef, useEffect as useEffect3, useCallback as useCallback3 } from "react";
|
|
152
|
-
import { StopIcon, PaperAirplaneIcon } from "@heroicons/react/24/outline";
|
|
151
|
+
import { useState as useState3, useRef as useRef3, useImperativeHandle, forwardRef, useEffect as useEffect3, useCallback as useCallback3, useLayoutEffect } from "react";
|
|
152
|
+
import { MicrophoneIcon, StopIcon, PaperAirplaneIcon } from "@heroicons/react/24/outline";
|
|
153
153
|
|
|
154
154
|
// src/ui/react/hooks/useSpeechRecognition.ts
|
|
155
155
|
import { useState, useEffect, useCallback, useRef } from "react";
|
|
@@ -277,12 +277,23 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
|
|
|
277
277
|
import { useState as useState2, useRef as useRef2, useCallback as useCallback2 } from "react";
|
|
278
278
|
var useAudioRecorder = (onStop) => {
|
|
279
279
|
const [isRecording, setIsRecording] = useState2(false);
|
|
280
|
+
const [isSimulated, setIsSimulated] = useState2(false);
|
|
280
281
|
const [blob, setBlob] = useState2(null);
|
|
281
282
|
const [error, setError] = useState2(null);
|
|
282
283
|
const mediaRecorderRef = useRef2(null);
|
|
283
284
|
const chunksRef = useRef2([]);
|
|
284
285
|
const start = useCallback2(async () => {
|
|
285
286
|
try {
|
|
287
|
+
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
288
|
+
if (process.env.NODE_ENV === "development") {
|
|
289
|
+
console.warn("[useAudioRecorder] MediaDevices not available. Entering simulation mode...");
|
|
290
|
+
setIsRecording(true);
|
|
291
|
+
setIsSimulated(true);
|
|
292
|
+
setError(null);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
throw new Error("Media devices not available. Ensure you are using HTTPS or localhost.");
|
|
296
|
+
}
|
|
286
297
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
287
298
|
const mediaRecorder = new MediaRecorder(stream);
|
|
288
299
|
mediaRecorderRef.current = mediaRecorder;
|
|
@@ -310,12 +321,21 @@ var useAudioRecorder = (onStop) => {
|
|
|
310
321
|
}
|
|
311
322
|
}, [onStop]);
|
|
312
323
|
const stop = useCallback2(() => {
|
|
324
|
+
if (isSimulated) {
|
|
325
|
+
setIsRecording(false);
|
|
326
|
+
setIsSimulated(false);
|
|
327
|
+
const simulatedBlob = new Blob(["simulated speech"], { type: "audio/simulated" });
|
|
328
|
+
setBlob(simulatedBlob);
|
|
329
|
+
if (onStop) onStop(simulatedBlob);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
313
332
|
if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
|
|
314
333
|
mediaRecorderRef.current.stop();
|
|
315
334
|
}
|
|
316
|
-
}, []);
|
|
335
|
+
}, [isSimulated, onStop]);
|
|
317
336
|
return {
|
|
318
337
|
isRecording,
|
|
338
|
+
isSimulated,
|
|
319
339
|
start,
|
|
320
340
|
stop,
|
|
321
341
|
blob,
|
|
@@ -345,7 +365,7 @@ function useProactiveResize(textareaRef, measurementRef, value, disabled) {
|
|
|
345
365
|
}
|
|
346
366
|
|
|
347
367
|
// src/ui/react/components/ChatInputArea.tsx
|
|
348
|
-
import {
|
|
368
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
349
369
|
var ChatInputArea = forwardRef(({
|
|
350
370
|
onSubmit,
|
|
351
371
|
isSending,
|
|
@@ -364,21 +384,22 @@ var ChatInputArea = forwardRef(({
|
|
|
364
384
|
var _a, _b, _c, _d;
|
|
365
385
|
const [internalMessage, setInternalMessage] = useState3("");
|
|
366
386
|
const [voiceTrigger, setVoiceTrigger] = useState3(null);
|
|
367
|
-
const [inputMode, setInputMode] = useState3(defaultInputMode);
|
|
368
387
|
const [isFocused, setIsFocused] = useState3(false);
|
|
369
388
|
const textareaRef = useRef3(null);
|
|
370
389
|
const measurementRef = useRef3(null);
|
|
371
|
-
const
|
|
372
|
-
useEffect3(() => {
|
|
373
|
-
var _a2;
|
|
374
|
-
if (inputMode === "voice") {
|
|
375
|
-
(_a2 = voiceContainerRef.current) == null ? void 0 : _a2.focus();
|
|
376
|
-
}
|
|
377
|
-
}, [inputMode]);
|
|
390
|
+
const pendingSelectionRef = useRef3(null);
|
|
378
391
|
const isControlled = value !== void 0;
|
|
379
392
|
const message = isControlled ? value : internalMessage;
|
|
380
393
|
const messageRef = useRef3(message);
|
|
381
394
|
messageRef.current = message;
|
|
395
|
+
useLayoutEffect(() => {
|
|
396
|
+
if (pendingSelectionRef.current && textareaRef.current) {
|
|
397
|
+
const { start, end } = pendingSelectionRef.current;
|
|
398
|
+
textareaRef.current.focus();
|
|
399
|
+
textareaRef.current.setSelectionRange(start, end);
|
|
400
|
+
pendingSelectionRef.current = null;
|
|
401
|
+
}
|
|
402
|
+
}, [message]);
|
|
382
403
|
const onChangeRef = useRef3(onChange);
|
|
383
404
|
useEffect3(() => {
|
|
384
405
|
onChangeRef.current = onChange;
|
|
@@ -402,34 +423,30 @@ var ChatInputArea = forwardRef(({
|
|
|
402
423
|
}
|
|
403
424
|
}, [isControlled]);
|
|
404
425
|
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));
|
|
405
|
-
useProactiveResize(textareaRef, measurementRef, message, isInputDisabled || !!voiceTrigger
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
e.preventDefault();
|
|
413
|
-
e.stopPropagation();
|
|
414
|
-
if (voiceTrigger === "click") return;
|
|
415
|
-
if (!e.repeat && !voiceTrigger) {
|
|
416
|
-
startRecording("space");
|
|
417
|
-
}
|
|
418
|
-
};
|
|
419
|
-
const handleVoiceKeyUp = (e) => {
|
|
420
|
-
if (inputMode !== "voice" || isInputDisabled) return;
|
|
421
|
-
if (e.code === "Space") {
|
|
422
|
-
if (voiceTrigger === "space") {
|
|
423
|
-
e.preventDefault();
|
|
424
|
-
stopRecording();
|
|
425
|
-
}
|
|
426
|
+
useProactiveResize(textareaRef, measurementRef, message, isInputDisabled || !!voiceTrigger);
|
|
427
|
+
const insertTextAtCursor = useCallback3((text) => {
|
|
428
|
+
const textarea = textareaRef.current;
|
|
429
|
+
const currentVal = messageRef.current || "";
|
|
430
|
+
if (!textarea) {
|
|
431
|
+
triggerChange(currentVal + (currentVal ? " " : "") + text);
|
|
432
|
+
return;
|
|
426
433
|
}
|
|
427
|
-
|
|
434
|
+
const start = textarea.selectionStart;
|
|
435
|
+
const end = textarea.selectionEnd;
|
|
436
|
+
const before = currentVal.substring(0, start);
|
|
437
|
+
const after = currentVal.substring(end);
|
|
438
|
+
const prefix = start > 0 && !/\s$/.test(before) ? " " : "";
|
|
439
|
+
const newText = before + prefix + text + after;
|
|
440
|
+
const selectionStart = start + prefix.length;
|
|
441
|
+
const selectionEnd = selectionStart + text.length;
|
|
442
|
+
pendingSelectionRef.current = { start: selectionStart, end: selectionEnd };
|
|
443
|
+
triggerChange(newText);
|
|
444
|
+
}, [triggerChange]);
|
|
428
445
|
const handleVoiceResult = useCallback3((text, isFinal) => {
|
|
429
446
|
if (isFinal) {
|
|
430
|
-
|
|
447
|
+
insertTextAtCursor(text);
|
|
431
448
|
}
|
|
432
|
-
}, []);
|
|
449
|
+
}, [insertTextAtCursor]);
|
|
433
450
|
const handleVoiceEnd = useCallback3(() => {
|
|
434
451
|
var _a2, _b2;
|
|
435
452
|
setVoiceTrigger(null);
|
|
@@ -440,10 +457,15 @@ var ChatInputArea = forwardRef(({
|
|
|
440
457
|
var _a2, _b2, _c2;
|
|
441
458
|
setVoiceTrigger(null);
|
|
442
459
|
(_b2 = (_a2 = voiceConfigRef.current) == null ? void 0 : _a2.onVoiceEnd) == null ? void 0 : _b2.call(_a2);
|
|
460
|
+
if (blob.type === "audio/simulated") {
|
|
461
|
+
console.log("[ChatInputArea] Handling simulated audio capture");
|
|
462
|
+
insertTextAtCursor("This is a simulated transcription for development testing.");
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
443
465
|
if ((_c2 = voiceConfigRef.current) == null ? void 0 : _c2.onAudioCapture) {
|
|
444
466
|
try {
|
|
445
467
|
const text = await voiceConfigRef.current.onAudioCapture(blob);
|
|
446
|
-
if (text)
|
|
468
|
+
if (text) insertTextAtCursor(text);
|
|
447
469
|
} catch (e) {
|
|
448
470
|
console.error("[ChatInputArea] Audio capture failed", e);
|
|
449
471
|
}
|
|
@@ -451,12 +473,8 @@ var ChatInputArea = forwardRef(({
|
|
|
451
473
|
});
|
|
452
474
|
useImperativeHandle(ref, () => ({
|
|
453
475
|
focus: () => {
|
|
454
|
-
var _a2
|
|
455
|
-
|
|
456
|
-
(_a2 = voiceContainerRef.current) == null ? void 0 : _a2.focus();
|
|
457
|
-
} else {
|
|
458
|
-
(_b2 = textareaRef.current) == null ? void 0 : _b2.focus();
|
|
459
|
-
}
|
|
476
|
+
var _a2;
|
|
477
|
+
(_a2 = textareaRef.current) == null ? void 0 : _a2.focus();
|
|
460
478
|
},
|
|
461
479
|
setValue: (newValue) => {
|
|
462
480
|
triggerChange(newValue);
|
|
@@ -500,6 +518,10 @@ var ChatInputArea = forwardRef(({
|
|
|
500
518
|
} else {
|
|
501
519
|
await customRecorder.start();
|
|
502
520
|
}
|
|
521
|
+
setTimeout(() => {
|
|
522
|
+
var _a3;
|
|
523
|
+
return (_a3 = textareaRef.current) == null ? void 0 : _a3.focus();
|
|
524
|
+
}, 0);
|
|
503
525
|
};
|
|
504
526
|
const stopRecording = () => {
|
|
505
527
|
if (!voiceTrigger) return;
|
|
@@ -512,9 +534,7 @@ var ChatInputArea = forwardRef(({
|
|
|
512
534
|
const getPlaceholder = () => {
|
|
513
535
|
if (placeholder) return placeholder;
|
|
514
536
|
if (voiceTrigger) return "Listening...";
|
|
515
|
-
if (currentTask == null ? void 0 : currentTask.complete)
|
|
516
|
-
return "Task completed!";
|
|
517
|
-
}
|
|
537
|
+
if (currentTask == null ? void 0 : currentTask.complete) return "Task completed!";
|
|
518
538
|
if ((lastInteractiveMessage == null ? void 0 : lastInteractiveMessage.interactive) && (lastInteractiveMessage == null ? void 0 : lastInteractiveMessage.interactiveData) && !(lastInteractiveMessage == null ? void 0 : lastInteractiveMessage.isResponseSubmitted)) {
|
|
519
539
|
const interactiveType = lastInteractiveMessage.interactiveData.function;
|
|
520
540
|
switch (interactiveType) {
|
|
@@ -543,85 +563,54 @@ var ChatInputArea = forwardRef(({
|
|
|
543
563
|
{
|
|
544
564
|
type: "button",
|
|
545
565
|
onClick: () => {
|
|
546
|
-
if (
|
|
566
|
+
if (voiceTrigger) {
|
|
547
567
|
stopRecording();
|
|
568
|
+
} else {
|
|
569
|
+
startRecording("click");
|
|
548
570
|
}
|
|
549
|
-
setInputMode((prev) => prev === "text" ? "voice" : "text");
|
|
550
571
|
},
|
|
551
|
-
className:
|
|
552
|
-
title:
|
|
553
|
-
children:
|
|
554
|
-
// Voice Icon (Waveform)
|
|
555
|
-
/* @__PURE__ */ jsx5("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__ */ jsx5("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" }) })
|
|
556
|
-
) : (
|
|
557
|
-
// Keyboard Icon (Filled)
|
|
558
|
-
/* @__PURE__ */ jsx5("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__ */ jsx5("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" }) })
|
|
559
|
-
)
|
|
572
|
+
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"}`,
|
|
573
|
+
title: voiceTrigger ? "Stop Recording" : "Start Voice Input",
|
|
574
|
+
children: /* @__PURE__ */ jsx5(MicrophoneIcon, { className: "w-5 h-5" })
|
|
560
575
|
}
|
|
561
576
|
),
|
|
562
577
|
/* @__PURE__ */ jsxs3(
|
|
563
578
|
"div",
|
|
564
579
|
{
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
onKeyDown: handleVoiceKeyDown,
|
|
568
|
-
onKeyUp: handleVoiceKeyUp,
|
|
569
|
-
onFocus: () => setIsFocused(true),
|
|
570
|
-
onBlur: () => setIsFocused(false),
|
|
571
|
-
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",
|
|
580
|
+
tabIndex: -1,
|
|
581
|
+
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"}`,
|
|
572
582
|
children: [
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
"textarea",
|
|
584
|
-
{
|
|
585
|
-
ref: textareaRef,
|
|
586
|
-
value: message,
|
|
587
|
-
onChange: (e) => {
|
|
588
|
-
if (isControlled && onChange) {
|
|
589
|
-
onChange(e);
|
|
590
|
-
} else {
|
|
591
|
-
setInternalMessage(e.target.value);
|
|
592
|
-
}
|
|
593
|
-
},
|
|
594
|
-
onKeyDown: handleKeyDown,
|
|
595
|
-
placeholder: getPlaceholder(),
|
|
596
|
-
disabled: isInputDisabled || !!voiceTrigger,
|
|
597
|
-
rows: 1,
|
|
598
|
-
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" : ""}`
|
|
599
|
-
}
|
|
600
|
-
)
|
|
601
|
-
] }),
|
|
602
|
-
inputMode === "voice" && /* @__PURE__ */ jsx5("div", { className: "flex-grow flex flex-col justify-center items-center p-1 relative", children: /* @__PURE__ */ jsx5(
|
|
603
|
-
"button",
|
|
583
|
+
/* @__PURE__ */ jsx5(
|
|
584
|
+
"span",
|
|
585
|
+
{
|
|
586
|
+
ref: measurementRef,
|
|
587
|
+
className: "absolute invisible whitespace-pre-wrap p-0 m-0 text-gray-700 leading-6",
|
|
588
|
+
style: { fontSize: "1rem" }
|
|
589
|
+
}
|
|
590
|
+
),
|
|
591
|
+
/* @__PURE__ */ jsx5(
|
|
592
|
+
"textarea",
|
|
604
593
|
{
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
594
|
+
ref: textareaRef,
|
|
595
|
+
value: message,
|
|
596
|
+
onChange: (e) => {
|
|
597
|
+
if (isControlled && onChange) {
|
|
598
|
+
onChange(e);
|
|
599
|
+
} else {
|
|
600
|
+
setInternalMessage(e.target.value);
|
|
611
601
|
}
|
|
612
602
|
},
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
] }) : /* @__PURE__ */ jsx5("span", { children: "Tap to Talk" })
|
|
603
|
+
onKeyDown: handleKeyDown,
|
|
604
|
+
onFocus: () => setIsFocused(true),
|
|
605
|
+
onBlur: () => setIsFocused(false),
|
|
606
|
+
placeholder: getPlaceholder(),
|
|
607
|
+
disabled: isInputDisabled,
|
|
608
|
+
readOnly: !!voiceTrigger,
|
|
609
|
+
rows: 1,
|
|
610
|
+
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 ? "cursor-default" : ""}`
|
|
622
611
|
}
|
|
623
|
-
)
|
|
624
|
-
|
|
612
|
+
),
|
|
613
|
+
/* @__PURE__ */ jsxs3("div", { className: "relative mx-2 flex-shrink-0", children: [
|
|
625
614
|
isSending && /* @__PURE__ */ jsx5("div", { className: "absolute -inset-1", children: /* @__PURE__ */ jsxs3(
|
|
626
615
|
"svg",
|
|
627
616
|
{
|
|
@@ -664,24 +653,10 @@ var ChatInputArea = forwardRef(({
|
|
|
664
653
|
handleSubmit();
|
|
665
654
|
}
|
|
666
655
|
},
|
|
667
|
-
disabled: (currentTask == null ? void 0 : currentTask.complete) || isSending && !onStop || isInputDisabled
|
|
656
|
+
disabled: (currentTask == null ? void 0 : currentTask.complete) || isSending && !onStop || isInputDisabled,
|
|
668
657
|
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"}`,
|
|
669
658
|
title: isSending && onStop ? "Stop generating" : "Send message",
|
|
670
|
-
children: isSending ? onStop ? /* @__PURE__ */ jsx5(StopIcon, { className: "h-5 w-5" }) : (
|
|
671
|
-
// AND we show the overlay spinner outside?
|
|
672
|
-
// Actually `ChatInput.tsx` lines 117-140 are `isLoading && (...)`. It is always shown when loading.
|
|
673
|
-
// So we have a spinner ring AROUND the button (absolute -inset-1).
|
|
674
|
-
// AND potentially a spinner INSIDE the button if no onStop?
|
|
675
|
-
// In my case, I will stick to:
|
|
676
|
-
// If onStop: Show StopIcon. Button is Red.
|
|
677
|
-
// If !onStop: Show Spinner inside? Or just let the outer ring do the work?
|
|
678
|
-
// Legacy `Spinner` component usage inside button suggests double spinner if we are not careful.
|
|
679
|
-
// But usually `onStop` is provided for streaming.
|
|
680
|
-
// If I look at the screenshot, it shows a RED button (with stop icon) and a BLUE ring around it.
|
|
681
|
-
// That matches: Red button (bg-red-500) + Blue Spinner Ring (text-blue-500).
|
|
682
|
-
// So I will replicate that structure.
|
|
683
|
-
/* @__PURE__ */ jsx5(StopIcon, { className: "h-5 w-5" })
|
|
684
|
-
) : /* @__PURE__ */ jsx5(PaperAirplaneIcon, { className: "h-5 w-5" })
|
|
659
|
+
children: isSending ? onStop ? /* @__PURE__ */ jsx5(StopIcon, { className: "h-5 w-5" }) : /* @__PURE__ */ jsx5("div", { className: "w-5 h-5" }) : /* @__PURE__ */ jsx5(PaperAirplaneIcon, { className: "h-5 w-5" })
|
|
685
660
|
}
|
|
686
661
|
)
|
|
687
662
|
] })
|
|
@@ -690,18 +665,7 @@ var ChatInputArea = forwardRef(({
|
|
|
690
665
|
)
|
|
691
666
|
] }),
|
|
692
667
|
inputHint && /* @__PURE__ */ jsx5("div", { className: "text-sm text-red-500 bg-red-50 py-1 px-4 rounded-lg mt-1", children: inputHint }),
|
|
693
|
-
|
|
694
|
-
inputMode === "voice" && !voiceTrigger && /* @__PURE__ */ jsx5(
|
|
695
|
-
"p",
|
|
696
|
-
{
|
|
697
|
-
className: "text-[10px] text-gray-400 font-medium ml-12 text-center -mt-1 mb-1 cursor-pointer hover:text-gray-600 transition-colors",
|
|
698
|
-
onClick: () => {
|
|
699
|
-
var _a2;
|
|
700
|
-
return (_a2 = voiceContainerRef.current) == null ? void 0 : _a2.focus();
|
|
701
|
-
},
|
|
702
|
-
children: isFocused ? "Click to talk or hold space to talk" : "Tap to talk or click here to focus and push space to talk"
|
|
703
|
-
}
|
|
704
|
-
)
|
|
668
|
+
/* @__PURE__ */ jsx5("div", { className: "mb-2 mt-0.5 min-h-[0.75rem]", style: { marginLeft: "48px" }, children: /* @__PURE__ */ jsx5("p", { className: `text-[10px] leading-tight transition-colors duration-200 ${voiceTrigger ? "text-orange-600 font-medium" : "text-gray-400"}`, children: voiceTrigger ? "Listening... tap mic icon again to stop" : hintText || (voiceConfig ? "Type in text or tap mic icon to talk" : "Type your message...") }) })
|
|
705
669
|
] });
|
|
706
670
|
});
|
|
707
671
|
ChatInputArea.displayName = "ChatInputArea";
|