diff --git a/apps/browser/components/Chat.tsx b/apps/browser/components/Chat.tsx index 747465dc..9c2efb8b 100644 --- a/apps/browser/components/Chat.tsx +++ b/apps/browser/components/Chat.tsx @@ -14,19 +14,19 @@ import Disclaimer from "@/components/modals/Disclaimer"; import Logo from "@/components/Logo"; import Button from "@/components/Button"; import ChatInputButton from "@/components/ChatInputButton"; -import TextField from "@/components/TextField"; import React, { useState, ChangeEvent } from "react"; import { UploadSimple } from "@phosphor-icons/react"; import { useAtom } from "jotai"; import clsx from "clsx"; import { InMemoryFile } from "@nerfzael/memory-fs"; +import TextAreaField from "./inputs/TextAreaField"; export interface ChatLog { title: string; content?: string; user: string; color?: string; - created_at?: string + created_at?: string; } export interface ChatProps { @@ -44,21 +44,22 @@ const Chat: React.FC = ({ isRunning, onGoalSubmit, onUpload, - status + status, }: ChatProps) => { const [{ id: chatId, name: chatName }] = useAtom(chatInfoAtom); const [showDisclaimer, setShowDisclaimer] = useAtom(showDisclaimerAtom); const [, setError] = useAtom(errorAtom); const [welcomeModalOpen, setWelcomeModalOpen] = useAtom(welcomeModalAtom); const [signInModalOpen] = useAtom(signInModalAtom); - const [settingsModalOpen] = useAtom(settingsModalAtom) + const [settingsModalOpen] = useAtom(settingsModalAtom); const [message, setMessage] = useState(""); const { getInputProps, open } = useWorkspaceUploadDrop(onUpload); const firstTimeUser = useFirstTimeUser(); - const shouldShowExamplePrompts = !chatId || (!logs.length && !isStarting && !isRunning); + const shouldShowExamplePrompts = + !chatId || (!logs.length && !isStarting && !isRunning); const handleGoalSubmit = async (goal: string): Promise => { if (firstTimeUser) { @@ -79,7 +80,7 @@ const Chat: React.FC = ({ return; } open(); - } + }; return (
= ({ {shouldShowExamplePrompts ? ( ) : ( - + )}
= ({ )} > {shouldShowExamplePrompts && ( - + )}
= ({ shouldShowExamplePrompts ? "max-w-[42rem] " : "max-w-[56rem]" )} > - ) => { - setMessage(event.target.value) + onChange={(event: ChangeEvent) => { + setMessage(event.target.value); }} onKeyDown={(event: React.KeyboardEvent) => { - if (event.key === "Enter" && !isStarting && !isRunning) { - return handleGoalSubmit(message); + if (!event.shiftKey && event.key === "Enter") { + event.preventDefault(); + if (!isStarting && !isRunning) { + return handleGoalSubmit(message); + } } }} placeholder="Ask Evo anything..." - className="!rounded-lg !p-4 !pl-12" + className="max-h-[200px] resize-none !rounded-lg !p-4 !pl-12" leftAdornment={ <> - @@ -146,7 +157,12 @@ const Chat: React.FC = ({
setShowDisclaimer(false)} />
diff --git a/apps/browser/components/Sidebar.tsx b/apps/browser/components/Sidebar.tsx index 17bf3a4a..0926e5d0 100644 --- a/apps/browser/components/Sidebar.tsx +++ b/apps/browser/components/Sidebar.tsx @@ -8,7 +8,7 @@ import { useWorkspaceUploadUpdate } from "@/lib/hooks/useWorkspaceUploadUpdate"; import Logo from "@/components/Logo"; import Avatar from "@/components/Avatar"; import Button from "@/components/Button"; -import TextField from "@/components/TextField"; +import TextField from "@/components/inputs/TextField"; import DropdownAccount from "@/components/DropdownAccount"; import Workspace from "@/components/Workspace"; import React, { memo, useEffect, useRef, useState } from "react"; diff --git a/apps/browser/components/TextField.tsx b/apps/browser/components/TextField.tsx deleted file mode 100644 index d40ef33f..00000000 --- a/apps/browser/components/TextField.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import clsx from "clsx"; -import { - useState, - DetailedHTMLProps, - ChangeEvent, - InputHTMLAttributes, - ReactNode, -} from "react"; - -interface TextFieldProps - extends DetailedHTMLProps< - InputHTMLAttributes, - HTMLInputElement - > { - label?: string; - error?: string; - checked?: boolean; - leftAdornment?: ReactNode; - leftAdornmentClassnames?: string; - rightAdornment?: ReactNode; - rightAdornmentClassnames?: string; - onChange?: (e: ChangeEvent) => void; -} - -const TextField = ({ - type = "input", - leftAdornment, - leftAdornmentClassnames, - rightAdornment, - rightAdornmentClassnames, - className, - label, - error, - checked, - ...props -}: TextFieldProps) => { - const [isChecked, setIsChecked] = - useState(checked); - - const handleCheck = () => { - const newValue = !isChecked; - setIsChecked(newValue); - if (props.onChange) { - const event = { - target: { - type: "checkbox", - checked: newValue, - }, - } as ChangeEvent; - props.onChange(event); - } - }; - - return ( -
- {label && } - {type === "checkbox" ? ( -
-
-
- ) : ( -
- {leftAdornment && ( -
- {leftAdornment} -
- )} - - {rightAdornment && ( -
- {rightAdornment} -
- )} -
- )} - {error &&
{error}
} -
- ); -}; - -export default TextField; diff --git a/apps/browser/components/inputs/BaseInput.tsx b/apps/browser/components/inputs/BaseInput.tsx new file mode 100644 index 00000000..e3f24371 --- /dev/null +++ b/apps/browser/components/inputs/BaseInput.tsx @@ -0,0 +1,55 @@ +import clsx from "clsx"; +import { ReactNode } from "react"; + +export interface BaseInputProps { + label?: string; + error?: string; + leftAdornment?: ReactNode; + leftAdornmentClassnames?: string; + rightAdornment?: ReactNode; + rightAdornmentClassnames?: string; + className?: string; + children?: ReactNode; +} + +const BaseInput = ({ + label, + error, + leftAdornmentClassnames, + leftAdornment, + rightAdornmentClassnames, + rightAdornment, + children, +}: BaseInputProps) => { + return ( +
+ {label && } +
+ {leftAdornment && ( +
+ {leftAdornment} +
+ )} + {children} + {rightAdornment && ( +
+ {rightAdornment} +
+ )} +
+ {error &&
{error}
} +
+ ); +}; + +export default BaseInput; diff --git a/apps/browser/components/inputs/Checkbox.tsx b/apps/browser/components/inputs/Checkbox.tsx new file mode 100644 index 00000000..8815f810 --- /dev/null +++ b/apps/browser/components/inputs/Checkbox.tsx @@ -0,0 +1,55 @@ +// Checkbox.tsx +import clsx from "clsx"; +import BaseInput from "./BaseInput"; +import { + ChangeEvent, + DetailedHTMLProps, + InputHTMLAttributes, + useState, +} from "react"; + +interface CheckboxProps + extends DetailedHTMLProps< + InputHTMLAttributes, + HTMLInputElement + > { + label?: string; + error?: string; +} + +const Checkbox: React.FC = ({ + label, + error, + className, + ...props +}) => { + const [isChecked, setIsChecked] = useState(props.checked); + + const handleCheck = () => { + const newValue = !isChecked; + setIsChecked(newValue); + if (props.onChange) { + const event = { + target: { + type: "checkbox", + checked: newValue, + }, + } as ChangeEvent; + props.onChange(event); + } + }; + + return ( +
+ {label && } +
+
+
+
+ ); +}; + +export default Checkbox; diff --git a/apps/browser/components/inputs/TextAreaField.tsx b/apps/browser/components/inputs/TextAreaField.tsx new file mode 100644 index 00000000..386b979e --- /dev/null +++ b/apps/browser/components/inputs/TextAreaField.tsx @@ -0,0 +1,69 @@ +// TextAreaField.tsx +import clsx from "clsx"; +import BaseInput, { BaseInputProps } from "./BaseInput"; +import { + DetailedHTMLProps, + TextareaHTMLAttributes, + useRef, + useEffect, +} from "react"; + +interface TextAreaFieldProps + extends DetailedHTMLProps< + TextareaHTMLAttributes, + HTMLTextAreaElement + >, + BaseInputProps {} + +const TextAreaField = ({ + leftAdornment, + rightAdornment, + leftAdornmentClassnames, + rightAdornmentClassnames, + className, + label, + error, + ...props +}: TextAreaFieldProps) => { + const textareaRef = useRef(null); + + useEffect(() => { + if (textareaRef.current) { + textareaRef.current.style.height = "auto"; + textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`; + if (textareaRef.current.scrollHeight > 200) { + textareaRef.current.style.overflowY = "auto"; + } else { + textareaRef.current.style.overflowY = "hidden"; + } + } + }, [props.value]); + + return ( + +