import React, { useMemo, useRef, useState } from "react"; import { InputProps } from "./props"; import { useChatContext } from "./ChatContext"; import AutoResizingTextarea from "./Textarea"; import { usePushToTalk } from "../../hooks/use-push-to-talk"; import { useAiContext } from "@vn-sdk/react-core"; import { PoweredByTag } from "./PoweredByTag"; const MAX_NEWLINES = 6; export const Input = ({ inProgress, onSend, isVisible = false, onStop, onUpload, hideStopButton = false, }: InputProps) => { const context = useChatContext(); const copilotContext = useAiContext(); const showPoweredBy = !copilotContext.aiApiConfig?.publicApiKey; const pushToTalkConfigured = copilotContext.aiApiConfig.textToSpeechUrl !== undefined && copilotContext.aiApiConfig.transcribeAudioUrl !== undefined; const textareaRef = useRef(null); const [isComposing, setIsComposing] = useState(false); const handleDivClick = (event: React.MouseEvent) => { const target = event.target as HTMLElement; // If the user clicked a button or inside a button, don't focus the textarea if (target.closest("button")) return; // If the user clicked the textarea, do nothing (it's already focused) if (target.tagName === "TEXTAREA") return; // Otherwise, focus the textarea textareaRef.current?.focus(); }; const [text, setText] = useState(""); const send = () => { if (inProgress) return; onSend(text); setText(""); textareaRef.current?.focus(); }; // tylerslaton: // // This scrolls VN SDK into view always. Reading the commit history, it was likely // added to fix a bug but it is causing issues now. // // For the future, if we want this behavior again, we will need to find a way to do it without // forcing VN SDK to always be in view. This code causes this because focusing an element // in most browsers will scroll that element into view. // // useEffect(() => { // if (isVisible) { // textareaRef.current?.focus(); // } // }, [isVisible]); const { pushToTalkState, setPushToTalkState } = usePushToTalk({ sendFunction: onSend, inProgress, }); const isInProgress = inProgress || pushToTalkState === "transcribing"; const buttonIcon = isInProgress && !hideStopButton ? context.icons.stopIcon : context.icons.sendIcon; const showPushToTalk = pushToTalkConfigured && (pushToTalkState === "idle" || pushToTalkState === "recording") && !inProgress; const canSend = useMemo(() => { const interruptEvent = copilotContext.langGraphInterruptAction?.event; const interruptInProgress = interruptEvent?.name === "LangGraphInterruptEvent" && !interruptEvent?.response; return ( !isInProgress && text.trim().length > 0 && pushToTalkState === "idle" && !interruptInProgress ); }, [copilotContext.langGraphInterruptAction?.event, isInProgress, text, pushToTalkState]); const canStop = useMemo(() => { return isInProgress && !hideStopButton; }, [isInProgress, hideStopButton]); const sendDisabled = !canSend && !canStop; return (
setText(event.target.value)} onCompositionStart={() => setIsComposing(true)} onCompositionEnd={() => setIsComposing(false)} onKeyDown={(event) => { if (event.key === "Enter" && !event.shiftKey && !isComposing) { event.preventDefault(); if (canSend) { send(); } } }} />
{onUpload && ( )}
{showPushToTalk && ( )}
); };