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 ( +