|
import React from "react";
|
|
import TextareaAutosize from "react-textarea-autosize";
|
|
import { useTranslation } from "react-i18next";
|
|
import { I18nKey } from "#/i18n/declaration";
|
|
import { cn } from "#/utils/utils";
|
|
import { SubmitButton } from "#/components/shared/buttons/submit-button";
|
|
import { StopButton } from "#/components/shared/buttons/stop-button";
|
|
|
|
interface ChatInputProps {
|
|
name?: string;
|
|
button?: "submit" | "stop";
|
|
disabled?: boolean;
|
|
showButton?: boolean;
|
|
value?: string;
|
|
maxRows?: number;
|
|
onSubmit: (message: string) => void;
|
|
onStop?: () => void;
|
|
onChange?: (message: string) => void;
|
|
onFocus?: () => void;
|
|
onBlur?: () => void;
|
|
onImagePaste?: (files: File[]) => void;
|
|
className?: React.HTMLAttributes<HTMLDivElement>["className"];
|
|
buttonClassName?: React.HTMLAttributes<HTMLButtonElement>["className"];
|
|
}
|
|
|
|
export function ChatInput({
|
|
name,
|
|
button = "submit",
|
|
disabled,
|
|
showButton = true,
|
|
value,
|
|
maxRows = 4,
|
|
onSubmit,
|
|
onStop,
|
|
onChange,
|
|
onFocus,
|
|
onBlur,
|
|
onImagePaste,
|
|
className,
|
|
buttonClassName,
|
|
}: ChatInputProps) {
|
|
const { t } = useTranslation();
|
|
const textareaRef = React.useRef<HTMLTextAreaElement>(null);
|
|
const [isDraggingOver, setIsDraggingOver] = React.useState(false);
|
|
|
|
const handlePaste = (event: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
|
|
|
if (onImagePaste && event.clipboardData.files.length > 0) {
|
|
const files = Array.from(event.clipboardData.files).filter((file) =>
|
|
file.type.startsWith("image/"),
|
|
);
|
|
|
|
if (files.length > 0) {
|
|
event.preventDefault();
|
|
onImagePaste(files);
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
const handleDragOver = (event: React.DragEvent<HTMLTextAreaElement>) => {
|
|
event.preventDefault();
|
|
if (event.dataTransfer.types.includes("Files")) {
|
|
setIsDraggingOver(true);
|
|
}
|
|
};
|
|
|
|
const handleDragLeave = (event: React.DragEvent<HTMLTextAreaElement>) => {
|
|
event.preventDefault();
|
|
setIsDraggingOver(false);
|
|
};
|
|
|
|
const handleDrop = (event: React.DragEvent<HTMLTextAreaElement>) => {
|
|
event.preventDefault();
|
|
setIsDraggingOver(false);
|
|
if (onImagePaste && event.dataTransfer.files.length > 0) {
|
|
const files = Array.from(event.dataTransfer.files).filter((file) =>
|
|
file.type.startsWith("image/"),
|
|
);
|
|
if (files.length > 0) {
|
|
onImagePaste(files);
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleSubmitMessage = () => {
|
|
const message = value || textareaRef.current?.value || "";
|
|
if (message.trim()) {
|
|
onSubmit(message);
|
|
onChange?.("");
|
|
if (textareaRef.current) {
|
|
textareaRef.current.value = "";
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleKeyPress = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
if (
|
|
event.key === "Enter" &&
|
|
!event.shiftKey &&
|
|
!disabled &&
|
|
!event.nativeEvent.isComposing
|
|
) {
|
|
event.preventDefault();
|
|
handleSubmitMessage();
|
|
}
|
|
};
|
|
|
|
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
onChange?.(event.target.value);
|
|
};
|
|
|
|
return (
|
|
<div
|
|
data-testid="chat-input"
|
|
className="flex items-end justify-end grow gap-1 min-h-6 w-full"
|
|
>
|
|
<TextareaAutosize
|
|
ref={textareaRef}
|
|
name={name}
|
|
placeholder={t(I18nKey.SUGGESTIONS$WHAT_TO_BUILD)}
|
|
onKeyDown={handleKeyPress}
|
|
onChange={handleChange}
|
|
onFocus={onFocus}
|
|
onBlur={onBlur}
|
|
onPaste={handlePaste}
|
|
onDrop={handleDrop}
|
|
onDragOver={handleDragOver}
|
|
onDragLeave={handleDragLeave}
|
|
value={value}
|
|
minRows={1}
|
|
maxRows={maxRows}
|
|
data-dragging-over={isDraggingOver}
|
|
className={cn(
|
|
"grow text-sm self-center placeholder:text-neutral-400 text-white resize-none outline-none ring-0",
|
|
"transition-all duration-200 ease-in-out",
|
|
isDraggingOver
|
|
? "bg-neutral-600/50 rounded-lg px-2"
|
|
: "bg-transparent",
|
|
className,
|
|
)}
|
|
/>
|
|
{showButton && (
|
|
<div className={buttonClassName}>
|
|
{button === "submit" && (
|
|
<SubmitButton isDisabled={disabled} onClick={handleSubmitMessage} />
|
|
)}
|
|
{button === "stop" && (
|
|
<StopButton isDisabled={disabled} onClick={onStop} />
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|