@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.
- package/dist/ui/react/components/index.cjs +215 -127
- 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 +216 -128
- package/dist/ui/react/components/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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 (
|
|
217
|
-
|
|
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 [
|
|
338
|
-
const [inputMode, setInputMode] = useState3(
|
|
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 ||
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
(
|
|
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 (
|
|
414
|
-
|
|
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
|
-
|
|
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 (!
|
|
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 (
|
|
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" &&
|
|
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(
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
"
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
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";
|