| 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>
|
| );
|
| }
|
|
|