@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
|
@@ -200,6 +200,8 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
|
|
|
200
200
|
const [error, setError] = (0, import_react2.useState)(null);
|
|
201
201
|
const [isSupported, setIsSupported] = (0, import_react2.useState)(false);
|
|
202
202
|
const recognitionRef = (0, import_react2.useRef)(null);
|
|
203
|
+
const isSimulatingRef = (0, import_react2.useRef)(false);
|
|
204
|
+
const simulationTimeoutRef = (0, import_react2.useRef)(null);
|
|
203
205
|
(0, import_react2.useEffect)(() => {
|
|
204
206
|
if (typeof window !== "undefined") {
|
|
205
207
|
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
@@ -214,6 +216,7 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
|
|
|
214
216
|
setError(null);
|
|
215
217
|
};
|
|
216
218
|
recognition.onend = () => {
|
|
219
|
+
if (isSimulatingRef.current) return;
|
|
217
220
|
setIsListening(false);
|
|
218
221
|
if (onEnd) onEnd();
|
|
219
222
|
};
|
|
@@ -235,15 +238,18 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
|
|
|
235
238
|
recognition.onerror = (event) => {
|
|
236
239
|
if (event.error === "not-allowed" && process.env.NODE_ENV === "development") {
|
|
237
240
|
console.warn("Speech recognition blocked. Simulating input for development...");
|
|
241
|
+
isSimulatingRef.current = true;
|
|
238
242
|
setError(null);
|
|
239
243
|
setIsListening(true);
|
|
240
|
-
setTimeout(() => {
|
|
244
|
+
simulationTimeoutRef.current = setTimeout(() => {
|
|
241
245
|
const mockText = "This is a simulated voice input for testing.";
|
|
242
246
|
setTranscript((prev) => prev + (prev ? " " : "") + mockText);
|
|
243
247
|
if (onResult) onResult(mockText, true);
|
|
248
|
+
isSimulatingRef.current = false;
|
|
244
249
|
setIsListening(false);
|
|
245
250
|
if (onEnd) onEnd();
|
|
246
|
-
|
|
251
|
+
simulationTimeoutRef.current = null;
|
|
252
|
+
}, 3e3);
|
|
247
253
|
return;
|
|
248
254
|
}
|
|
249
255
|
console.error("Speech recognition error", event.error);
|
|
@@ -254,11 +260,13 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
|
|
|
254
260
|
}
|
|
255
261
|
}
|
|
256
262
|
return () => {
|
|
257
|
-
if (
|
|
258
|
-
|
|
263
|
+
if (isSimulatingRef.current && simulationTimeoutRef.current) {
|
|
264
|
+
clearTimeout(simulationTimeoutRef.current);
|
|
265
|
+
simulationTimeoutRef.current = null;
|
|
259
266
|
}
|
|
267
|
+
recognitionRef.current.stop();
|
|
260
268
|
};
|
|
261
|
-
}, [onResult, onEnd]);
|
|
269
|
+
}, [onResult, onEnd, language]);
|
|
262
270
|
const start = (0, import_react2.useCallback)(() => {
|
|
263
271
|
if (recognitionRef.current && !isListening) {
|
|
264
272
|
try {
|
|
@@ -270,10 +278,23 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
|
|
|
270
278
|
}
|
|
271
279
|
}, [isListening]);
|
|
272
280
|
const stop = (0, import_react2.useCallback)(() => {
|
|
281
|
+
if (isSimulatingRef.current) {
|
|
282
|
+
if (simulationTimeoutRef.current) {
|
|
283
|
+
clearTimeout(simulationTimeoutRef.current);
|
|
284
|
+
simulationTimeoutRef.current = null;
|
|
285
|
+
}
|
|
286
|
+
const mockText = "This is a simulated voice input for testing.";
|
|
287
|
+
setTranscript((prev) => prev + (prev ? " " : "") + mockText);
|
|
288
|
+
if (onResult) onResult(mockText, true);
|
|
289
|
+
isSimulatingRef.current = false;
|
|
290
|
+
setIsListening(false);
|
|
291
|
+
if (onEnd) onEnd();
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
273
294
|
if (recognitionRef.current && isListening) {
|
|
274
295
|
recognitionRef.current.stop();
|
|
275
296
|
}
|
|
276
|
-
}, [isListening]);
|
|
297
|
+
}, [isListening, onResult, onEnd]);
|
|
277
298
|
const resetTranscript = (0, import_react2.useCallback)(() => {
|
|
278
299
|
setTranscript("");
|
|
279
300
|
}, []);
|
|
@@ -371,14 +392,23 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
|
|
|
371
392
|
hintText,
|
|
372
393
|
placeholder,
|
|
373
394
|
value,
|
|
374
|
-
onChange
|
|
395
|
+
onChange,
|
|
396
|
+
defaultInputMode = "text"
|
|
375
397
|
}, ref) => {
|
|
376
398
|
var _a, _b, _c, _d;
|
|
377
399
|
const [internalMessage, setInternalMessage] = (0, import_react5.useState)("");
|
|
378
|
-
const [
|
|
379
|
-
const [inputMode, setInputMode] = (0, import_react5.useState)(
|
|
400
|
+
const [voiceTrigger, setVoiceTrigger] = (0, import_react5.useState)(null);
|
|
401
|
+
const [inputMode, setInputMode] = (0, import_react5.useState)(defaultInputMode);
|
|
402
|
+
const [isFocused, setIsFocused] = (0, import_react5.useState)(false);
|
|
380
403
|
const textareaRef = (0, import_react5.useRef)(null);
|
|
381
404
|
const measurementRef = (0, import_react5.useRef)(null);
|
|
405
|
+
const voiceContainerRef = (0, import_react5.useRef)(null);
|
|
406
|
+
(0, import_react5.useEffect)(() => {
|
|
407
|
+
var _a2;
|
|
408
|
+
if (inputMode === "voice") {
|
|
409
|
+
(_a2 = voiceContainerRef.current) == null ? void 0 : _a2.focus();
|
|
410
|
+
}
|
|
411
|
+
}, [inputMode]);
|
|
382
412
|
const isControlled = value !== void 0;
|
|
383
413
|
const message = isControlled ? value : internalMessage;
|
|
384
414
|
const { voice: globalVoice } = useChatConfig();
|
|
@@ -396,17 +426,39 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
|
|
|
396
426
|
}
|
|
397
427
|
};
|
|
398
428
|
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));
|
|
399
|
-
useProactiveResize(textareaRef, measurementRef, message, isInputDisabled ||
|
|
429
|
+
useProactiveResize(textareaRef, measurementRef, message, isInputDisabled || !!voiceTrigger || inputMode === "voice");
|
|
430
|
+
const handleVoiceKeyDown = (e) => {
|
|
431
|
+
if (inputMode !== "voice" || isInputDisabled) return;
|
|
432
|
+
if (e.code !== "Space") return;
|
|
433
|
+
const activeElement = document.activeElement;
|
|
434
|
+
const isInputActive = activeElement && (activeElement.tagName === "INPUT" || activeElement.tagName === "TEXTAREA" || activeElement instanceof HTMLElement && activeElement.isContentEditable);
|
|
435
|
+
if (isInputActive) return;
|
|
436
|
+
e.preventDefault();
|
|
437
|
+
e.stopPropagation();
|
|
438
|
+
if (voiceTrigger === "click") return;
|
|
439
|
+
if (!e.repeat && !voiceTrigger) {
|
|
440
|
+
startRecording("space");
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
const handleVoiceKeyUp = (e) => {
|
|
444
|
+
if (inputMode !== "voice" || isInputDisabled) return;
|
|
445
|
+
if (e.code === "Space") {
|
|
446
|
+
if (voiceTrigger === "space") {
|
|
447
|
+
e.preventDefault();
|
|
448
|
+
stopRecording();
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
};
|
|
400
452
|
const nativeSpeech = useSpeechRecognition((text) => {
|
|
401
453
|
triggerChange(message + (message ? " " : "") + text);
|
|
402
454
|
}, () => {
|
|
403
455
|
var _a2;
|
|
404
|
-
|
|
456
|
+
setVoiceTrigger(null);
|
|
405
457
|
(_a2 = voiceConfig == null ? void 0 : voiceConfig.onVoiceEnd) == null ? void 0 : _a2.call(voiceConfig);
|
|
406
458
|
}, voiceConfig == null ? void 0 : voiceConfig.language);
|
|
407
459
|
const customRecorder = useAudioRecorder(async (blob) => {
|
|
408
460
|
var _a2;
|
|
409
|
-
|
|
461
|
+
setVoiceTrigger(null);
|
|
410
462
|
(_a2 = voiceConfig == null ? void 0 : voiceConfig.onVoiceEnd) == null ? void 0 : _a2.call(voiceConfig);
|
|
411
463
|
if (voiceConfig == null ? void 0 : voiceConfig.onAudioCapture) {
|
|
412
464
|
try {
|
|
@@ -419,8 +471,12 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
|
|
|
419
471
|
});
|
|
420
472
|
(0, import_react5.useImperativeHandle)(ref, () => ({
|
|
421
473
|
focus: () => {
|
|
422
|
-
var _a2;
|
|
423
|
-
(
|
|
474
|
+
var _a2, _b2;
|
|
475
|
+
if (inputMode === "voice") {
|
|
476
|
+
(_a2 = voiceContainerRef.current) == null ? void 0 : _a2.focus();
|
|
477
|
+
} else {
|
|
478
|
+
(_b2 = textareaRef.current) == null ? void 0 : _b2.focus();
|
|
479
|
+
}
|
|
424
480
|
},
|
|
425
481
|
setValue: (newValue) => {
|
|
426
482
|
triggerChange(newValue);
|
|
@@ -449,15 +505,15 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
|
|
|
449
505
|
handleSubmit();
|
|
450
506
|
}
|
|
451
507
|
};
|
|
452
|
-
const startRecording = async () => {
|
|
508
|
+
const startRecording = async (trigger) => {
|
|
453
509
|
var _a2;
|
|
454
|
-
if (
|
|
455
|
-
|
|
510
|
+
if (voiceTrigger) return;
|
|
511
|
+
setVoiceTrigger(trigger);
|
|
456
512
|
(_a2 = voiceConfig == null ? void 0 : voiceConfig.onVoiceStart) == null ? void 0 : _a2.call(voiceConfig);
|
|
457
513
|
if ((voiceConfig == null ? void 0 : voiceConfig.mode) === "native") {
|
|
458
514
|
if (!nativeSpeech.isSupported) {
|
|
459
515
|
alert("Speech recognition is not supported in this browser.");
|
|
460
|
-
|
|
516
|
+
setVoiceTrigger(null);
|
|
461
517
|
return;
|
|
462
518
|
}
|
|
463
519
|
nativeSpeech.start();
|
|
@@ -466,7 +522,7 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
|
|
|
466
522
|
}
|
|
467
523
|
};
|
|
468
524
|
const stopRecording = () => {
|
|
469
|
-
if (!
|
|
525
|
+
if (!voiceTrigger) return;
|
|
470
526
|
if ((voiceConfig == null ? void 0 : voiceConfig.mode) === "native") {
|
|
471
527
|
nativeSpeech.stop();
|
|
472
528
|
} else {
|
|
@@ -475,7 +531,7 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
|
|
|
475
531
|
};
|
|
476
532
|
const getPlaceholder = () => {
|
|
477
533
|
if (placeholder) return placeholder;
|
|
478
|
-
if (
|
|
534
|
+
if (voiceTrigger) return "Listening...";
|
|
479
535
|
if (currentTask == null ? void 0 : currentTask.complete) {
|
|
480
536
|
return "Task completed!";
|
|
481
537
|
}
|
|
@@ -507,7 +563,7 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
|
|
|
507
563
|
{
|
|
508
564
|
type: "button",
|
|
509
565
|
onClick: () => {
|
|
510
|
-
if (inputMode === "voice" &&
|
|
566
|
+
if (inputMode === "voice" && voiceTrigger) {
|
|
511
567
|
stopRecording();
|
|
512
568
|
}
|
|
513
569
|
setInputMode((prev) => prev === "text" ? "voice" : "text");
|
|
@@ -523,117 +579,149 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
|
|
|
523
579
|
)
|
|
524
580
|
}
|
|
525
581
|
),
|
|
526
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
"
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
} else {
|
|
545
|
-
setInternalMessage(e.target.value);
|
|
582
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
583
|
+
"div",
|
|
584
|
+
{
|
|
585
|
+
ref: voiceContainerRef,
|
|
586
|
+
tabIndex: inputMode === "voice" ? 0 : -1,
|
|
587
|
+
onKeyDown: handleVoiceKeyDown,
|
|
588
|
+
onKeyUp: handleVoiceKeyUp,
|
|
589
|
+
onFocus: () => setIsFocused(true),
|
|
590
|
+
onBlur: () => setIsFocused(false),
|
|
591
|
+
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",
|
|
592
|
+
children: [
|
|
593
|
+
inputMode === "text" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
594
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
595
|
+
"span",
|
|
596
|
+
{
|
|
597
|
+
ref: measurementRef,
|
|
598
|
+
className: "absolute invisible whitespace-pre-wrap p-0 m-0 text-gray-700 leading-6",
|
|
599
|
+
style: { fontSize: "1rem" }
|
|
546
600
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
children: [
|
|
578
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
579
|
-
"circle",
|
|
580
|
-
{
|
|
581
|
-
className: "opacity-25",
|
|
582
|
-
cx: "12",
|
|
583
|
-
cy: "12",
|
|
584
|
-
r: "10",
|
|
585
|
-
stroke: "currentColor",
|
|
586
|
-
strokeWidth: "4"
|
|
587
|
-
}
|
|
588
|
-
),
|
|
589
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
590
|
-
"path",
|
|
591
|
-
{
|
|
592
|
-
className: "opacity-75",
|
|
593
|
-
fill: "currentColor",
|
|
594
|
-
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"
|
|
601
|
+
),
|
|
602
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
603
|
+
"textarea",
|
|
604
|
+
{
|
|
605
|
+
ref: textareaRef,
|
|
606
|
+
value: message,
|
|
607
|
+
onChange: (e) => {
|
|
608
|
+
if (isControlled && onChange) {
|
|
609
|
+
onChange(e);
|
|
610
|
+
} else {
|
|
611
|
+
setInternalMessage(e.target.value);
|
|
612
|
+
}
|
|
613
|
+
},
|
|
614
|
+
onKeyDown: handleKeyDown,
|
|
615
|
+
placeholder: getPlaceholder(),
|
|
616
|
+
disabled: isInputDisabled || !!voiceTrigger,
|
|
617
|
+
rows: 1,
|
|
618
|
+
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" : ""}`
|
|
619
|
+
}
|
|
620
|
+
)
|
|
621
|
+
] }),
|
|
622
|
+
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)(
|
|
623
|
+
"button",
|
|
624
|
+
{
|
|
625
|
+
type: "button",
|
|
626
|
+
onClick: () => {
|
|
627
|
+
if (voiceTrigger === "click") {
|
|
628
|
+
stopRecording();
|
|
629
|
+
} else if (!voiceTrigger) {
|
|
630
|
+
startRecording("click");
|
|
595
631
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
632
|
+
},
|
|
633
|
+
disabled: isInputDisabled || voiceTrigger === "space",
|
|
634
|
+
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" : ""}`,
|
|
635
|
+
children: voiceTrigger ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
636
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "w-2 h-2 rounded-full bg-red-500 animate-ping mr-2" }),
|
|
637
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { children: [
|
|
638
|
+
"Listening... ",
|
|
639
|
+
voiceTrigger === "space" ? "(Release Space to send)" : "Tap to send"
|
|
640
|
+
] })
|
|
641
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Tap to Talk" })
|
|
642
|
+
}
|
|
643
|
+
) }),
|
|
644
|
+
(inputMode === "text" || isSending) && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "relative mx-2 flex-shrink-0", children: [
|
|
645
|
+
isSending && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "absolute -inset-1", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
646
|
+
"svg",
|
|
647
|
+
{
|
|
648
|
+
className: "animate-spin h-full w-full text-blue-500 opacity-75",
|
|
649
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
650
|
+
fill: "none",
|
|
651
|
+
viewBox: "0 0 24 24",
|
|
652
|
+
children: [
|
|
653
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
654
|
+
"circle",
|
|
655
|
+
{
|
|
656
|
+
className: "opacity-25",
|
|
657
|
+
cx: "12",
|
|
658
|
+
cy: "12",
|
|
659
|
+
r: "10",
|
|
660
|
+
stroke: "currentColor",
|
|
661
|
+
strokeWidth: "4"
|
|
662
|
+
}
|
|
663
|
+
),
|
|
664
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
665
|
+
"path",
|
|
666
|
+
{
|
|
667
|
+
className: "opacity-75",
|
|
668
|
+
fill: "currentColor",
|
|
669
|
+
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"
|
|
670
|
+
}
|
|
671
|
+
)
|
|
672
|
+
]
|
|
610
673
|
}
|
|
611
|
-
},
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
674
|
+
) }),
|
|
675
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
676
|
+
"button",
|
|
677
|
+
{
|
|
678
|
+
type: "button",
|
|
679
|
+
onClick: (e) => {
|
|
680
|
+
if (isSending && onStop) {
|
|
681
|
+
e.preventDefault();
|
|
682
|
+
onStop();
|
|
683
|
+
} else {
|
|
684
|
+
handleSubmit();
|
|
685
|
+
}
|
|
686
|
+
},
|
|
687
|
+
disabled: (currentTask == null ? void 0 : currentTask.complete) || isSending && !onStop || isInputDisabled || !!voiceTrigger,
|
|
688
|
+
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"}`,
|
|
689
|
+
title: isSending && onStop ? "Stop generating" : "Send message",
|
|
690
|
+
children: isSending ? onStop ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_outline.StopIcon, { className: "h-5 w-5" }) : (
|
|
691
|
+
// AND we show the overlay spinner outside?
|
|
692
|
+
// Actually `ChatInput.tsx` lines 117-140 are `isLoading && (...)`. It is always shown when loading.
|
|
693
|
+
// So we have a spinner ring AROUND the button (absolute -inset-1).
|
|
694
|
+
// AND potentially a spinner INSIDE the button if no onStop?
|
|
695
|
+
// In my case, I will stick to:
|
|
696
|
+
// If onStop: Show StopIcon. Button is Red.
|
|
697
|
+
// If !onStop: Show Spinner inside? Or just let the outer ring do the work?
|
|
698
|
+
// Legacy `Spinner` component usage inside button suggests double spinner if we are not careful.
|
|
699
|
+
// But usually `onStop` is provided for streaming.
|
|
700
|
+
// If I look at the screenshot, it shows a RED button (with stop icon) and a BLUE ring around it.
|
|
701
|
+
// That matches: Red button (bg-red-500) + Blue Spinner Ring (text-blue-500).
|
|
702
|
+
// So I will replicate that structure.
|
|
703
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_outline.StopIcon, { className: "h-5 w-5" })
|
|
704
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_outline.PaperAirplaneIcon, { className: "h-5 w-5" })
|
|
705
|
+
}
|
|
706
|
+
)
|
|
707
|
+
] })
|
|
708
|
+
]
|
|
709
|
+
}
|
|
710
|
+
)
|
|
634
711
|
] }),
|
|
635
712
|
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 }),
|
|
636
|
-
hintText && inputMode === "text" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-xs text-gray-500 ml-12 mb-2 mt-1", children: hintText })
|
|
713
|
+
hintText && inputMode === "text" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-xs text-gray-500 ml-12 mb-2 mt-1", children: hintText }),
|
|
714
|
+
inputMode === "voice" && !voiceTrigger && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
715
|
+
"p",
|
|
716
|
+
{
|
|
717
|
+
className: "text-[10px] text-gray-400 font-medium ml-12 text-center -mt-1 mb-1 cursor-pointer hover:text-gray-600 transition-colors",
|
|
718
|
+
onClick: () => {
|
|
719
|
+
var _a2;
|
|
720
|
+
return (_a2 = voiceContainerRef.current) == null ? void 0 : _a2.focus();
|
|
721
|
+
},
|
|
722
|
+
children: isFocused ? "Click to talk or hold space to talk" : "Tap to talk or click here to focus and push space to talk"
|
|
723
|
+
}
|
|
724
|
+
)
|
|
637
725
|
] });
|
|
638
726
|
});
|
|
639
727
|
ChatInputArea.displayName = "ChatInputArea";
|