From 4bb4f02e5bd15a6af48990a011eafa089ce2bbd7 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Tue, 22 Oct 2024 15:07:27 -0300 Subject: [PATCH] feature: add new playground ui (#4193) * create newmodal.tsx * Add new icons to styleUtils.ts * Refactor import path for IOModal in chatComponent/index.tsx * Refactor import path for IOModal in chatComponent/index.tsx * Refactor session selector UI in IOFieldView component * Refactor NodeToolbarComponent to add a minimum width to SelectContent * Refactor IOModal to use newChatView component for ChatView * improve html structure to ensure aligment of texts * Refactor IOModal to improve sidebar behavior and alignment * add new input * Add Image icon to nodeIconsLucide in styleUtils.ts * Refactor uploadFileButton to use Image icon instead of PaperclipIcon * Refactor chat input layout for improved alignment and behavior * Refactor buttonSendWrapper component to show a "Stop" button with loading indicator * add a new TextAreaWrapper component * add icons and update colors * Add CornerDownLeft icon to nodeIconsLucide in styleUtils.ts * Refactor buttonSendWrapper component to use default button state * create newFilePreview * Refactor file preview component and update button styling * Refactor file preview component and update button styling * fix overflow bug * Refactor ChatView component to include a lockChat feature and display a flow running message * [autofix.ci] apply automated fixes * Refactor ChatView component to fix padding issue * [autofix.ci] apply automated fixes * Refactor dependencies in uv.lock to use a more specific version specifier for astra-assistants package * Refactor ChatView component to import newChatMessage instead of chatMessage * Refactor IOModal newModal component to conditionally display the session name * Refactor TextAreaWrapper component to update placeholder text and fix padding issue * Refactor TextAreaWrapper component to update placeholder text and fix padding issue * Refactor ChatView component to update lockChat UI and display flow running message * Refactor ChatView component to update lockChat UI and display flow running message * Refactor ChatMessage component to update sender name UI and fix message width issue * Refactor EditMessageButton component to update button UI and add tooltips * Refactor EditMessageButton component to update button UI and add tooltips * Refactor EditMessageField component to update UI and add functionality * update edit message field * fix group reference * Refactor CodeTabsComponent to update UI, add functionality, and improve code structure * Refactor CodeTabsComponent to update UI and improve code structure * Refactor CodeTabsComponent to simplify and improve UI * [autofix.ci] apply automated fixes * Refactor ChatView component to fix UI layout issue * fix overflow code boundaries problems * Refactor CSS to update background color variables * Refactor ChatCodeTabComponent to update UI and improve code structure * fix broken rebase changes * [autofix.ci] apply automated fixes * Refactor EditMessageButton component to remove delete functionality * Refactor SessionSelector component to update UI and improve code structure * Refactor SessionSelector component to update UI and improve code structure * [autofix.ci] apply automated fixes * refactor session selector to use old code and keep updates on newSessionSelector * create new button send wrapper * restore old button send wrapper and update imports * restore upload file button and create new UploadFileButton * [autofix.ci] apply automated fixes * Add feature flag for new IO modal * Refactor IOModal imports to support feature flag * update package-lock.json * [autofix.ci] apply automated fixes * remove console.log * Refactor session selector event handlers * Refactor file deletion in ChatInput component * [autofix.ci] apply automated fixes * Refactor file handling in ChatInput component * [autofix.ci] apply automated fixes * add user icon to messages * feat: Add "Run Flow" button to ChatInput component This commit adds a new button labeled "Run Flow" to the ChatInput component. When clicked, it triggers the sendMessage function with a repeat value of 1. Additionally, if there is no input in the chat, a message is displayed prompting the user to add a Chat Input component to their flow. --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- src/frontend/package-lock.json | 1 + .../src/components/cardComponent/index.tsx | 5 +- .../src/components/chatComponent/index.tsx | 6 +- .../ChatCodeTabComponent.tsx | 59 +++ .../components/codeTabsComponent/index.tsx | 2 +- .../components/storeCardComponent/index.tsx | 5 +- .../src/components/ui/select-custom.tsx | 2 +- .../src/customization/feature-flags.ts | 1 + .../sessionSelector/newSessionSelector.tsx | 214 ++++++++ .../newButtonSendWrapper.tsx | 86 +++ .../textAreaWrapper/newTextAreaWrapper.tsx | 82 +++ .../uploadFileButton/newUploadFileButton.tsx | 33 ++ .../chatInput/hooks/use-file-handler.ts | 88 +++ .../components/chatView/chatInput/index.tsx | 2 +- .../chatView/chatInput/newChatInput.tsx | 246 +++++++++ .../editMessageButton/newMessageOptions.tsx | 67 +++ .../editMessageField/newEditMessageField.tsx | 77 +++ .../chatView/chatMessage/newChatMessage.tsx | 500 ++++++++++++++++++ .../filePreviewChat/newFilePreview.tsx | 98 ++++ .../components/chatView/newChatView.tsx | 188 +++++++ src/frontend/src/modals/IOModal/newModal.tsx | 430 +++++++++++++++ .../components/nodeToolbarComponent/index.tsx | 2 +- src/frontend/src/pages/Playground/index.tsx | 5 +- src/frontend/src/style/applies.css | 2 +- src/frontend/src/style/classes.css | 18 + src/frontend/src/style/index.css | 6 +- src/frontend/src/utils/styleUtils.ts | 12 + src/frontend/tailwind.config.mjs | 1 + 28 files changed, 2226 insertions(+), 12 deletions(-) create mode 100644 src/frontend/src/components/codeTabsComponent/ChatCodeTabComponent.tsx create mode 100644 src/frontend/src/modals/IOModal/components/IOFieldView/components/sessionSelector/newSessionSelector.tsx create mode 100644 src/frontend/src/modals/IOModal/components/chatView/chatInput/components/buttonSendWrapper/newButtonSendWrapper.tsx create mode 100644 src/frontend/src/modals/IOModal/components/chatView/chatInput/components/textAreaWrapper/newTextAreaWrapper.tsx create mode 100644 src/frontend/src/modals/IOModal/components/chatView/chatInput/components/uploadFileButton/newUploadFileButton.tsx create mode 100644 src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-file-handler.ts create mode 100644 src/frontend/src/modals/IOModal/components/chatView/chatInput/newChatInput.tsx create mode 100644 src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/editMessageButton/newMessageOptions.tsx create mode 100644 src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/editMessageField/newEditMessageField.tsx create mode 100644 src/frontend/src/modals/IOModal/components/chatView/chatMessage/newChatMessage.tsx create mode 100644 src/frontend/src/modals/IOModal/components/chatView/filePreviewChat/newFilePreview.tsx create mode 100644 src/frontend/src/modals/IOModal/components/chatView/newChatView.tsx create mode 100644 src/frontend/src/modals/IOModal/newModal.tsx diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index af26f8ea4..51554664e 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -923,6 +923,7 @@ }, "node_modules/@clack/prompts/node_modules/is-unicode-supported": { "version": "1.3.0", + "extraneous": true, "inBundle": true, "license": "MIT", "engines": { diff --git a/src/frontend/src/components/cardComponent/index.tsx b/src/frontend/src/components/cardComponent/index.tsx index 763e37754..d5d2ab645 100644 --- a/src/frontend/src/components/cardComponent/index.tsx +++ b/src/frontend/src/components/cardComponent/index.tsx @@ -1,7 +1,9 @@ +import { ENABLE_NEW_IO_MODAL } from "@/customization/feature-flags"; import { track } from "@/customization/utils/analytics"; import { useState } from "react"; import { Control } from "react-hook-form"; -import IOModal from "../../modals/IOModal"; +import IOModalOld from "../../modals/IOModal"; +import IOModalNew from "../../modals/IOModal/newModal"; import useAlertStore from "../../stores/alertStore"; import useFlowsManagerStore from "../../stores/flowsManagerStore"; import { FlowType } from "../../types/flow"; @@ -22,6 +24,7 @@ import { FormControl, FormField } from "../ui/form"; import Loading from "../ui/loading"; import useDragStart from "./hooks/use-on-drag-start"; import { convertTestName } from "./utils/convert-test-name"; +const IOModal = ENABLE_NEW_IO_MODAL ? IOModalNew : IOModalOld; export default function CollectionCardComponent({ data, diff --git a/src/frontend/src/components/chatComponent/index.tsx b/src/frontend/src/components/chatComponent/index.tsx index 951e8b6c2..d5e46f526 100644 --- a/src/frontend/src/components/chatComponent/index.tsx +++ b/src/frontend/src/components/chatComponent/index.tsx @@ -1,10 +1,11 @@ -import { ENABLE_API } from "@/customization/feature-flags"; +import { ENABLE_API, ENABLE_NEW_IO_MODAL } from "@/customization/feature-flags"; import { track } from "@/customization/utils/analytics"; import { Transition } from "@headlessui/react"; import { useEffect, useMemo, useState } from "react"; import { useHotkeys } from "react-hotkeys-hook"; -import IOModal from "../../modals/IOModal"; import ApiModal from "../../modals/apiModal"; +import IOModalOld from "../../modals/IOModal"; +import IOModalNew from "../../modals/IOModal/newModal"; import ShareModal from "../../modals/shareModal"; import useFlowStore from "../../stores/flowStore"; import { useShortcutsStore } from "../../stores/shortcuts"; @@ -12,6 +13,7 @@ import { useStoreStore } from "../../stores/storeStore"; import { classNames, isThereModal } from "../../utils/utils"; import ForwardedIconComponent from "../genericIconComponent"; import { Separator } from "../ui/separator"; +const IOModal = ENABLE_NEW_IO_MODAL ? IOModalNew : IOModalOld; export default function FlowToolbar(): JSX.Element { const preventDefault = true; diff --git a/src/frontend/src/components/codeTabsComponent/ChatCodeTabComponent.tsx b/src/frontend/src/components/codeTabsComponent/ChatCodeTabComponent.tsx new file mode 100644 index 000000000..a1809314e --- /dev/null +++ b/src/frontend/src/components/codeTabsComponent/ChatCodeTabComponent.tsx @@ -0,0 +1,59 @@ +import { useState } from "react"; +import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; +import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism"; +import { useDarkStore } from "../../stores/darkStore"; +import IconComponent from "../genericIconComponent"; +import { Button } from "../ui/button"; + +type SimplifiedCodeTabProps = { + code: string; + language: string; +}; + +export default function SimplifiedCodeTabComponent({ + code, + language, +}: SimplifiedCodeTabProps) { + const [isCopied, setIsCopied] = useState(false); + + const copyToClipboard = () => { + if (!navigator.clipboard || !navigator.clipboard.writeText) { + return; + } + + navigator.clipboard.writeText(code).then(() => { + setIsCopied(true); + + setTimeout(() => { + setIsCopied(false); + }, 2000); + }); + }; + + return ( +
+
+ {language} + +
+ + {code} + +
+ ); +} diff --git a/src/frontend/src/components/codeTabsComponent/index.tsx b/src/frontend/src/components/codeTabsComponent/index.tsx index f17afa615..1fb933f01 100644 --- a/src/frontend/src/components/codeTabsComponent/index.tsx +++ b/src/frontend/src/components/codeTabsComponent/index.tsx @@ -144,7 +144,7 @@ export default function CodeTabsComponent({ ) : tab.name.toLowerCase() === "tweaks" ? ( <> - + ) : null} diff --git a/src/frontend/src/components/storeCardComponent/index.tsx b/src/frontend/src/components/storeCardComponent/index.tsx index ed729b4e8..896184b63 100644 --- a/src/frontend/src/components/storeCardComponent/index.tsx +++ b/src/frontend/src/components/storeCardComponent/index.tsx @@ -1,7 +1,9 @@ import { usePostLikeComponent } from "@/controllers/API/queries/store"; +import { ENABLE_NEW_IO_MODAL } from "@/customization/feature-flags"; import { useState } from "react"; import { getComponent } from "../../controllers/API"; -import IOModal from "../../modals/IOModal"; +import IOModalOld from "../../modals/IOModal"; +import IOModalNew from "../../modals/IOModal/newModal"; import useAlertStore from "../../stores/alertStore"; import useFlowsManagerStore from "../../stores/flowsManagerStore"; import { useStoreStore } from "../../stores/storeStore"; @@ -25,6 +27,7 @@ import Loading from "../ui/loading"; import useDataEffect from "./hooks/use-data-effect"; import useInstallComponent from "./hooks/use-handle-install"; import { convertTestName } from "./utils/convert-test-name"; +const IOModal = ENABLE_NEW_IO_MODAL ? IOModalNew : IOModalOld; export default function StoreCardComponent({ data, diff --git a/src/frontend/src/components/ui/select-custom.tsx b/src/frontend/src/components/ui/select-custom.tsx index 82ba540fe..ae4c0ecd3 100644 --- a/src/frontend/src/components/ui/select-custom.tsx +++ b/src/frontend/src/components/ui/select-custom.tsx @@ -33,7 +33,7 @@ const SelectContent = React.forwardRef< void; + session: string; + toggleVisibility: () => void; + isVisible: boolean; + inspectSession: (session: string) => void; + updateVisibleSession: (session: string) => void; + selectedView?: { type: string; id: string }; + setSelectedView: (view: { type: string; id: string } | undefined) => void; +}) { + const currentFlowId = useFlowStore((state) => state.currentFlow?.id); + const [isEditing, setIsEditing] = useState(false); + const [editedSession, setEditedSession] = useState(session); + const { mutate: updateSessionName } = useUpdateSessionName(); + const inputRef = useRef(null); + + useEffect(() => { + setEditedSession(session); + }, [session]); + + const handleEditClick = (e?: React.MouseEvent) => { + e?.stopPropagation(); + setIsEditing(true); + }; + + const handleInputChange = (e: React.ChangeEvent) => { + setEditedSession(e.target.value); + }; + + const handleConfirm = () => { + setIsEditing(false); + if (editedSession.trim() !== session) { + updateSessionName( + { old_session_id: session, new_session_id: editedSession.trim() }, + { + onSuccess: () => { + if (isVisible) { + updateVisibleSession(editedSession); + } + if ( + selectedView?.type === "Session" && + selectedView?.id === session + ) { + setSelectedView({ type: "Session", id: editedSession }); + } + }, + }, + ); + } + }; + + const handleCancel = () => { + setIsEditing(false); + setEditedSession(session); + }; + + const handleSelectChange = (value: string) => { + switch (value) { + case "rename": + handleEditClick(); + break; + case "messageLogs": + inspectSession(session); + break; + case "delete": + deleteSession(session); + break; + } + }; + + const handleOnBlur = (e: React.FocusEvent) => { + if ( + !e.relatedTarget || + e.relatedTarget.getAttribute("data-confirm") !== "true" + ) { + handleCancel(); + } + }; + + const onKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + e.preventDefault(); + e.stopPropagation(); + handleConfirm(); + } + }; + + return ( +
{ + if (isEditing) e.stopPropagation(); + else toggleVisibility(); + }} + className={cn( + "file-component-accordion-div group cursor-pointer rounded-md hover:bg-muted-foreground/30", + isVisible ? "bg-muted-foreground/15" : "", + )} + > +
+
+ {isEditing ? ( +
+ + + +
+ ) : ( + +
+ {session === currentFlowId ? "Default Session" : session} +
+
+ )} +
+ +
+
+ ); +} diff --git a/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/buttonSendWrapper/newButtonSendWrapper.tsx b/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/buttonSendWrapper/newButtonSendWrapper.tsx new file mode 100644 index 000000000..fec97f572 --- /dev/null +++ b/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/buttonSendWrapper/newButtonSendWrapper.tsx @@ -0,0 +1,86 @@ +import Loading from "@/components/ui/loading"; +import useFlowStore from "@/stores/flowStore"; +import IconComponent from "../../../../../../../components/genericIconComponent"; +import { Button } from "../../../../../../../components/ui/button"; +import { Case } from "../../../../../../../shared/components/caseComponent"; +import { FilePreviewType } from "../../../../../../../types/components"; +import { classNames } from "../../../../../../../utils/utils"; + +const BUTTON_STATES = { + NO_INPUT: "bg-high-indigo text-background", + HAS_CHAT_VALUE: "text-primary", + SHOW_STOP: "bg-zinc-400 text-white cursor-pointer", + DEFAULT: "bg-chat-send text-background", +}; + +type ButtonSendWrapperProps = { + send: () => void; + lockChat: boolean; + noInput: boolean; + chatValue: string; + files: FilePreviewType[]; +}; + +const ButtonSendWrapper = ({ + send, + lockChat, + noInput, + chatValue, + files, +}: ButtonSendWrapperProps) => { + const stopBuilding = useFlowStore((state) => state.stopBuilding); + + const isBuilding = useFlowStore((state) => state.isBuilding); + const showStopButton = lockChat || files.some((file) => file.loading); + const showPlayButton = !lockChat && noInput; + const showSendButton = + !(lockChat || files.some((file) => file.loading)) && !noInput; + + const getButtonState = () => { + if (showStopButton) return BUTTON_STATES.SHOW_STOP; + if (noInput) return BUTTON_STATES.NO_INPUT; + if (chatValue) return BUTTON_STATES.DEFAULT; + + return BUTTON_STATES.DEFAULT; + }; + + const buttonClasses = classNames("form-modal-send-button", getButtonState()); + + const handleClick = () => { + if (showStopButton && isBuilding) { + stopBuilding(); + } else if (!showStopButton) { + send(); + } + }; + + return ( + + ); +}; + +export default ButtonSendWrapper; diff --git a/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/textAreaWrapper/newTextAreaWrapper.tsx b/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/textAreaWrapper/newTextAreaWrapper.tsx new file mode 100644 index 000000000..430c50db2 --- /dev/null +++ b/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/textAreaWrapper/newTextAreaWrapper.tsx @@ -0,0 +1,82 @@ +import { useEffect } from "react"; +import { Textarea } from "../../../../../../../components/ui/textarea"; +import { classNames } from "../../../../../../../utils/utils"; + +const TextAreaWrapper = ({ + checkSendingOk, + send, + lockChat, + noInput, + chatValue, + setChatValue, + CHAT_INPUT_PLACEHOLDER, + CHAT_INPUT_PLACEHOLDER_SEND, + inputRef, + setInputFocus, + files, + isDragging, +}) => { + const getPlaceholderText = ( + isDragging: boolean, + noInput: boolean, + ): string => { + if (isDragging) { + return "Drop here"; + } else if (noInput) { + return CHAT_INPUT_PLACEHOLDER; + } else { + return "Send a message..."; + } + }; + + const lockClass = noInput + ? "form-modal-no-input bg-input" + : "form-modal-lock-false bg-background"; + + const fileClass = files.length > 0 ? "!rounded-t-none border-t-0" : ""; + + const additionalClassNames = + "form-input block w-full border-0 custom-scroll focus:border-ring focus:ring-0 p-0 sm:text-sm"; + + useEffect(() => { + if (!lockChat && !noInput) { + inputRef.current?.focus(); + } + }, [lockChat, noInput]); + + return ( +