From 984b172d5d28288c145bc041e8b0bba12c30327b Mon Sep 17 00:00:00 2001 From: Lucas Oliveira <62335616+lucaseduoli@users.noreply.github.com> Date: Thu, 22 May 2025 11:44:25 -0300 Subject: [PATCH] feat: adds new Edit Details popover, removes flow menu, fixes nav alignment, adds new Flow Status overlay (#8087) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Updated flow settings component size * Added FlowSettingsComponent to contain modal content * Removed unused imports * Changed Flow Settings Modal to use new component * Changed Flow Menu styling, removing Saved and context menu, and adding a direct click to edit flow info * Removed unused styling * Updated nav position and truncation * updated alert styling * Added z index to header * Added flow settings coming from the bottom * Changed flow settings to not crash when there is no flow * Removed unused imports * Implemented flow details using popover * Removed onClick * Changed canvas controls position and color * Changed panel tooltip side and classes * Added log canvas component * Added children to flow logs modal * Added log canvas component into page * Changed position and shadow of canvas controls * removed endpoint name from edit flow settings * added endpoint name change into tweaks modal * Added endpoint editing to tweaks * Implemented storing the error in the flowBuildStatus * Updated type * Added Flow Building Component * Added Flow Building Component implementation * Added red color * Added past build flow params * Implemented design of flowBuildingComponent * Implemented build error storing on flowStore * Implemented build error on flow store * Changed notifications test * Set build error as null when building * Reset build error when exiting flow * Changed from error to buildError * Changed flowStore to have buildInfo instead of buildError * Changed flowBuildingComponent to have buildInfo and display successful builds * Added handleDismissed instead of setting dismissed as true * Updated tests to current Update implementation * Updated tests to remove click on built successfully * Updated tests and data-testid to match new Flow Name editing behavior * fixed auto login test * Fixed edit-flow-name test and save changes on node * fixed tests * Changed Share to Publish and added test ids * added Rename Flow util for tests * Changed tests to use new RenameFlow * Fixed auto save off * Added data test id to flow building component * Removed pulsing from Name Invalid * Made name editable but not saveable when invalid * Added character name reached on description * Added transition on pencil * Modularized alert store to separate notification history and notifications * Added errors to notification history * Fixed flow building component position and update all components * Fixed animations * Fixed animation * Added same animation to Update All Components * Updated animations to make update only appear when flow building is not appearing * fix flow settings test * Fixed build status not being redefined * ✨ (UpdateAllComponents/index.tsx): Refactor containerVariants to CONTAINER_VARIANTS for consistency and readability 📝 (visual-variants.ts): Add visual variants for buttons and time in flowBuildingComponent ♻️ (flowBuildingComponent/index.tsx): Import visual variants from separate file for better organization and maintainability * Fixed offset width of time --------- Co-authored-by: cristhianzl --- .../components/FlowMenu/index.tsx | 569 ++++-------------- .../components/HeaderMenu/index.tsx | 2 +- .../core/appHeaderComponent/index.tsx | 14 +- .../core/canvasControlsComponent/index.tsx | 13 +- .../core/editFlowSettingsComponent/index.tsx | 77 +-- .../core/flowSettingsComponent/index.tsx | 116 ++++ .../components/deploy-dropdown.tsx | 12 +- .../core/logCanvasControlsComponent/index.tsx | 27 + .../src/modals/apiModal/new-api-modal.tsx | 87 ++- .../src/modals/flowLogsModal/index.tsx | 10 +- .../src/modals/flowSettingsModal/index.tsx | 114 +--- .../PageComponent/MemoizedComponents.tsx | 4 +- .../components/PageComponent/index.tsx | 9 +- .../components/UpdateAllComponents/index.tsx | 129 ++-- .../helpers/visual-variants.ts | 31 + .../flowBuildingComponent/index.tsx | 285 +++++++++ .../pages/MainPage/components/list/index.tsx | 1 - src/frontend/src/stores/alertStore.ts | 170 ++---- src/frontend/src/stores/flowStore.ts | 80 ++- src/frontend/src/style/applies.css | 10 +- src/frontend/src/style/index.css | 2 + src/frontend/src/types/components/index.ts | 2 +- src/frontend/src/types/zustand/alert/index.ts | 2 + src/frontend/src/types/zustand/flow/index.ts | 23 +- src/frontend/tailwind.config.mjs | 1 + .../core/features/auto-login-off.spec.ts | 15 +- .../tests/core/features/freeze-path.spec.ts | 12 - .../tests/core/features/freeze.spec.ts | 26 +- src/frontend/tests/core/features/logs.spec.ts | 17 +- .../tests/core/features/store-shard-2.spec.ts | 18 +- .../features/user-flow-state-cleanup.spec.ts | 12 +- .../core/integrations/Basic Prompting.spec.ts | 4 - .../core/integrations/Blog Writer.spec.ts | 4 - .../core/integrations/Document QA.spec.ts | 4 - .../core/integrations/Dynamic Agent.spec.ts | 4 +- .../integrations/Hierarchical Agent.spec.ts | 4 - .../integrations/Instagram Copywriter.spec.ts | 4 - .../integrations/Invoice Summarizer.spec.ts | 3 - .../core/integrations/Market Research.spec.ts | 4 - .../core/integrations/Memory Chatbot.spec.ts | 4 - .../core/integrations/Prompt Chaining.spec.ts | 4 - .../SEO Keyword Generator.spec.ts | 4 - .../core/integrations/SaaS Pricing.spec.ts | 4 - .../Sequential Task Agent.spec.ts | 4 - .../Text Sentiment Analysis.spec.ts | 4 - .../Travel Planning Agent.spec.ts | 4 - .../Twitter Thread Generator.spec.ts | 4 - .../core/integrations/Vector Store.spec.ts | 8 +- .../core/integrations/textInputOutput.spec.ts | 8 +- .../regression/generalBugs-prompt.spec.ts | 8 +- .../regression/generalBugs-shard-4.spec.ts | 13 +- .../regression/generalBugs-shard-5.spec.ts | 4 - .../extended/features/auto-save-off.spec.ts | 17 +- .../extended/features/edit-flow-name.spec.ts | 50 +- .../extended/features/flowSettings.spec.ts | 54 +- .../extended/features/loop-component.spec.ts | 3 - .../extended/features/notifications.spec.ts | 12 +- .../chatInputOutputUser-shard-1.spec.ts | 7 - .../integrations/youtube-transcripts.spec.ts | 8 +- ...general-bugs-move-flow-from-folder.spec.ts | 13 +- .../general-bugs-save-changes-on-node.spec.ts | 7 +- .../general-bugs-shard-3836.spec.ts | 3 - .../regression/generalBugs-shard-1.spec.ts | 4 - src/frontend/tests/utils/rename-flow.ts | 50 ++ 64 files changed, 1067 insertions(+), 1155 deletions(-) create mode 100644 src/frontend/src/components/core/flowSettingsComponent/index.tsx create mode 100644 src/frontend/src/components/core/logCanvasControlsComponent/index.tsx create mode 100644 src/frontend/src/pages/FlowPage/components/flowBuildingComponent/helpers/visual-variants.ts create mode 100644 src/frontend/src/pages/FlowPage/components/flowBuildingComponent/index.tsx create mode 100644 src/frontend/tests/utils/rename-flow.ts diff --git a/src/frontend/src/components/core/appHeaderComponent/components/FlowMenu/index.tsx b/src/frontend/src/components/core/appHeaderComponent/components/FlowMenu/index.tsx index 4a915a025..7aad08241 100644 --- a/src/frontend/src/components/core/appHeaderComponent/components/FlowMenu/index.tsx +++ b/src/frontend/src/components/core/appHeaderComponent/components/FlowMenu/index.tsx @@ -1,55 +1,37 @@ -import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; - -import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate"; -import useAddFlow from "@/hooks/flows/use-add-flow"; -import useSaveFlow from "@/hooks/flows/use-save-flow"; -import useUploadFlow from "@/hooks/flows/use-upload-flow"; -import { useHotkeys } from "react-hotkeys-hook"; +import { memo, useMemo, useRef, useState } from "react"; import IconComponent from "@/components/common/genericIconComponent"; import ShadTooltip from "@/components/common/shadTooltipComponent"; +import FlowSettingsComponent from "@/components/core/flowSettingsComponent"; import { Button } from "@/components/ui/button"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { Input } from "@/components/ui/input"; -import { UPLOAD_ERROR_ALERT } from "@/constants/alerts_constants"; + Popover, + PopoverAnchor, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; import { SAVED_HOVER } from "@/constants/constants"; import { useGetRefreshFlowsQuery } from "@/controllers/API/queries/flows/use-get-refresh-flows-query"; import { useGetFoldersQuery } from "@/controllers/API/queries/folders/use-get-folders"; +import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate"; +import useSaveFlow from "@/hooks/flows/use-save-flow"; import { useUnsavedChanges } from "@/hooks/use-unsaved-changes"; -import ExportModal from "@/modals/exportModal"; -import FlowLogsModal from "@/modals/flowLogsModal"; -import FlowSettingsModal from "@/modals/flowSettingsModal"; -import ToolbarSelectItem from "@/pages/FlowPage/components/nodeToolbarComponent/toolbarSelectItem"; import useAlertStore from "@/stores/alertStore"; import useFlowsManagerStore from "@/stores/flowsManagerStore"; import useFlowStore from "@/stores/flowStore"; import { useShortcutsStore } from "@/stores/shortcuts"; import { swatchColors } from "@/utils/styleUtils"; import { cn, getNumberFromString } from "@/utils/utils"; -import { useQueryClient } from "@tanstack/react-query"; +import { useHotkeys } from "react-hotkeys-hook"; import { useShallow } from "zustand/react/shallow"; export const MenuBar = memo((): JSX.Element => { - const shortcuts = useShortcutsStore((state) => state.shortcuts); - const addFlow = useAddFlow(); - const setErrorData = useAlertStore((state) => state.setErrorData); const setSuccessData = useAlertStore((state) => state.setSuccessData); - const undo = useFlowsManagerStore((state) => state.undo); - const redo = useFlowsManagerStore((state) => state.redo); const saveLoading = useFlowsManagerStore((state) => state.saveLoading); const [openSettings, setOpenSettings] = useState(false); - const [openLogs, setOpenLogs] = useState(false); - const uploadFlow = useUploadFlow(); const navigate = useCustomNavigate(); const isBuilding = useFlowStore((state) => state.isBuilding); const saveFlow = useSaveFlow(); - const queryClient = useQueryClient(); const autoSaving = useFlowsManagerStore((state) => state.autoSaving); const { currentFlowName, @@ -72,19 +54,10 @@ export const MenuBar = memo((): JSX.Element => { })), ); const onFlowPage = useFlowStore((state) => state.onFlowPage); - const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow); - const stopBuilding = useFlowStore((state) => state.stopBuilding); - const [editingName, setEditingName] = useState(false); - const [flowName, setFlowName] = useState(currentFlowName ?? ""); - const [isInvalidName, setIsInvalidName] = useState(false); - const nameInputRef = useRef(null); - const [inputWidth, setInputWidth] = useState(0); const measureRef = useRef(null); const changesNotSaved = useUnsavedChanges(); - const [flowNames, setFlowNames] = useState([]); const { data: folders, isFetched: isFoldersFetched } = useGetFoldersQuery(); - const flows = useFlowsManagerStore((state) => state.flows); useGetRefreshFlowsQuery( { @@ -99,41 +72,6 @@ export const MenuBar = memo((): JSX.Element => { [folders, currentFlowFolderId], ); - function handleAddFlow() { - try { - addFlow().then((id) => { - setCurrentFlow(undefined); // Reset current flow for useEffect of flowPage to update the current flow - navigate("/flow/" + id); - }); - } catch (err) { - setErrorData(err as { title: string; list?: Array }); - } - } - - function handleReloadComponents() { - queryClient.prefetchQuery({ queryKey: ["useGetTypes"] }).then(() => { - setSuccessData({ title: "Components reloaded successfully" }); - }); - } - - function printByBuildStatus() { - if (isBuilding) { - return
Building...
; - } else if (saveLoading) { - return
Saving...
; - } - // return savedText; - return ( - - ); - } - const handleSave = () => { saveFlow().then(() => { setSuccessData({ title: "Saved successfully" }); @@ -143,90 +81,6 @@ export const MenuBar = memo((): JSX.Element => { const changes = useShortcutsStore((state) => state.changesSave); useHotkeys(changes, handleSave, { preventDefault: true }); - const handleEditName = useCallback( - (e: React.ChangeEvent) => { - const { value } = e.target; - const invalid = flowNames.includes(value); - setIsInvalidName(invalid); - setFlowName(value); - }, - [flowNames], - ); - - const handleKeyDown = useCallback( - (e: React.KeyboardEvent) => { - if (e.key === "Escape") { - setEditingName(false); - setFlowName(currentFlowName ?? ""); - setIsInvalidName(false); - } - if (e.key === "Enter") { - nameInputRef.current?.blur(); - } - }, - [currentFlowName], - ); - - const handleNameSubmit = useCallback(async () => { - if ( - flowName.trim() !== "" && - flowName !== currentFlowName && - !isInvalidName - ) { - const currentFlowSnapshot = useFlowStore.getState().currentFlow; - - const newFlow = { - ...currentFlowSnapshot!, - name: flowName, - id: currentFlowId!, - }; - - saveFlow(newFlow) - .then(() => { - setCurrentFlow(newFlow); - setSuccessData({ title: "Flow name updated successfully" }); - }) - .catch((error) => { - setErrorData({ - title: "Error updating flow name", - list: [(error as Error).message], - }); - setFlowName(currentFlowName ?? ""); - }); - } else if (isInvalidName) { - setErrorData({ - title: "Invalid flow name", - list: ["Name already exists"], - }); - setFlowName(currentFlowName ?? ""); - } else { - setFlowName(currentFlowName ?? ""); - } - setEditingName(false); - setIsInvalidName(false); - }, [ - flowName, - currentFlowName, - currentFlowId, - setCurrentFlow, - saveFlow, - setSuccessData, - setErrorData, - isInvalidName, - ]); - - useEffect(() => { - if (!editingName) { - setFlowName(currentFlowName ?? "Untitled Flow"); - } - }, [currentFlowName, editingName]); - - useEffect(() => { - if (measureRef.current) { - setInputWidth(measureRef.current.offsetWidth + 10); - } - }, [flowName, onFlowPage]); - const swatchIndex = (currentFlowGradient && !isNaN(parseInt(currentFlowGradient)) ? parseInt(currentFlowGradient) @@ -234,331 +88,118 @@ export const MenuBar = memo((): JSX.Element => { swatchColors.length; return onFlowPage ? ( -
- -
- / -
-
- -
- -
+ +
+
+ / +
+
+ +
+
- { - setEditingName(true); - setFlowName(currentFlowName ?? "Untitled Flow"); - const flows = useFlowsManagerStore.getState().flows; - setFlowNames( - flows - ?.map((flow) => flow.name) - .filter((name) => name !== currentFlowName) ?? [], - ); - }} - onBlur={handleNameSubmit} - value={flowName} - id="input-flow-name" - data-testid="input-flow-name" - placeholder="Untitled Flow" - /> -
-
- - + - - - Options - { - handleAddFlow(); - }} - className="cursor-pointer" - data-testid="menu_new_flow_button" - id="menu_new_flow_button" - > - - New - - - { - setOpenSettings(true); - }} - className="cursor-pointer" - data-testid="menu_edit_flow_button" - id="menu_edit_flow_button" - > - - Edit Details - - {!autoSaving && ( - - s.name.toLowerCase() === "changes save", - )?.shortcut! - } - /> - - )} - { - setOpenLogs(true); - }} - className="cursor-pointer" - data-testid="menu_logs_flow_button" - id="menu_logs_flow_button" - > - - Logs - - { - uploadFlow({ position: { x: 300, y: 100 } }) - .then(() => { - setSuccessData({ - title: "Uploaded successfully", - }); - }) - .catch((error) => { - setErrorData({ - title: UPLOAD_ERROR_ALERT, - list: [(error as Error).message], - }); - }); - }} - data-testid="menu_import_flow_button" - id="menu_import_flow_button" - > - - Import - - -
- - Export -
-
- { - undo(); - }} - className="cursor-pointer" - data-testid="menu_undo_flow_button" - id="menu_undo_flow_button" - > - s.name.toLowerCase() === "undo") - ?.shortcut! - } - /> - - { - redo(); - }} - className="cursor-pointer" - data-testid="menu_redo_flow_button" - id="menu_redo_flow_button" - > - s.name.toLowerCase() === "redo") - ?.shortcut! - } - /> - - { - handleReloadComponents(); - }} - className="cursor-pointer" - data-testid="menu_refresh_flow_button" - id="menu_refresh_flow_button" - > - - Refresh All - -
-
-
- - - -
-
- {!autoSaving && ( - - )} - -

- Auto-saving is disabled -

-

- - Enable auto-saving - {" "} - to avoid losing progress. -

-
- ) - } - side="bottom" - styleClasses="cursor-default z-10" - > -
-
-
- {printByBuildStatus()} -
- + side="bottom" + styleClasses="cursor-default z-10" + > +
+ +
+ + )}
- - - + + + + Flow Details + setOpenSettings(false)} /> + + ) : ( <> ); diff --git a/src/frontend/src/components/core/appHeaderComponent/components/HeaderMenu/index.tsx b/src/frontend/src/components/core/appHeaderComponent/components/HeaderMenu/index.tsx index 62d75987d..451df44bd 100644 --- a/src/frontend/src/components/core/appHeaderComponent/components/HeaderMenu/index.tsx +++ b/src/frontend/src/components/core/appHeaderComponent/components/HeaderMenu/index.tsx @@ -16,7 +16,7 @@ export const HeaderMenu = ({ children }) => ( export const HeaderMenuToggle = ({ children }) => ( diff --git a/src/frontend/src/components/core/appHeaderComponent/index.tsx b/src/frontend/src/components/core/appHeaderComponent/index.tsx index e9f88b8ad..20f5a9237 100644 --- a/src/frontend/src/components/core/appHeaderComponent/index.tsx +++ b/src/frontend/src/components/core/appHeaderComponent/index.tsx @@ -14,9 +14,7 @@ import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate"; import useTheme from "@/customization/hooks/use-custom-theme"; import useAlertStore from "@/stores/alertStore"; import { useEffect, useRef, useState } from "react"; -import { AccountMenu } from "./components/AccountMenu"; import FlowMenu from "./components/FlowMenu"; -import LangflowCounts from "./components/langflow-counts"; export default function AppHeader(): JSX.Element { const notificationCenter = useAlertStore((state) => state.notificationCenter); @@ -47,13 +45,13 @@ export default function AppHeader(): JSX.Element { const getNotificationBadge = () => { const baseClasses = "absolute h-1 w-1 rounded-full bg-destructive"; return notificationCenter - ? `${baseClasses} right-[5.1rem] top-[5px]` + ? `${baseClasses} right-[0.3rem] top-[5px]` : "hidden"; }; return (
{/* Left Section */} @@ -82,13 +80,13 @@ export default function AppHeader(): JSX.Element {
{/* Middle Section */} -
+
{/* Right Section */}
<> @@ -119,7 +117,7 @@ export default function AppHeader(): JSX.Element { } data-testid="notification_button" > -
+
diff --git a/src/frontend/src/components/core/canvasControlsComponent/index.tsx b/src/frontend/src/components/core/canvasControlsComponent/index.tsx index 5ec115f0d..b79160bc0 100644 --- a/src/frontend/src/components/core/canvasControlsComponent/index.tsx +++ b/src/frontend/src/components/core/canvasControlsComponent/index.tsx @@ -39,17 +39,20 @@ export const CustomControlButton = ({ return ( - +
@@ -109,7 +112,7 @@ const CanvasControls = ({ children }) => { return ( {/* Zoom In */} @@ -135,6 +138,7 @@ const CanvasControls = ({ children }) => { onClick={fitView} testId="fit_view" /> + {children} {/* Lock/Unlock */} { } testId="lock_unlock" /> - {children} ); }; diff --git a/src/frontend/src/components/core/editFlowSettingsComponent/index.tsx b/src/frontend/src/components/core/editFlowSettingsComponent/index.tsx index b2009d739..a67a85898 100644 --- a/src/frontend/src/components/core/editFlowSettingsComponent/index.tsx +++ b/src/frontend/src/components/core/editFlowSettingsComponent/index.tsx @@ -1,6 +1,6 @@ import React, { ChangeEvent, useState } from "react"; import { InputProps } from "../../../types/components"; -import { cn, isEndpointNameValid } from "../../../utils/utils"; +import { cn } from "../../../utils/utils"; import { Input } from "../../ui/input"; import { Label } from "../../ui/label"; import { Textarea } from "../../ui/textarea"; @@ -9,16 +9,15 @@ export const EditFlowSettings: React.FC = ({ name, invalidNameList = [], description, - endpointName, maxLength = 50, + descriptionMaxLength = 250, minLength = 1, setName, setDescription, - setEndpointName, }: InputProps): JSX.Element => { const [isMaxLength, setIsMaxLength] = useState(false); + const [isMaxDescriptionLength, setIsMaxDescriptionLength] = useState(false); const [isMinLength, setIsMinLength] = useState(false); - const [validEndpointName, setValidEndpointName] = useState(true); const [isInvalidName, setIsInvalidName] = useState(false); const handleNameChange = (event: ChangeEvent) => { @@ -43,34 +42,22 @@ export const EditFlowSettings: React.FC = ({ } setIsInvalidName(invalid); - // Only update the name if it's valid (not empty and not invalid) - if (value.length >= minLength && !invalid) { - setName!(value); - } else if (value.length === 0) { + setName!(value); + + if (value.length === 0) { // For empty string, update state but keep isMinLength true - setName!(""); setIsMinLength(true); } }; const handleDescriptionChange = (event: ChangeEvent) => { - setDescription!(event.target.value); - }; - - const handleEndpointNameChange = (event: ChangeEvent) => { const { value } = event.target; - // Validate the endpoint name - // use this regex r'^[a-zA-Z0-9_-]+$' - const isValid = isEndpointNameValid(event.target.value, maxLength); - setValidEndpointName(isValid); - - // Only update if valid and meets minimum length (if set) - if (isValid && value.length >= minLength) { - setEndpointName!(value); - } else if (value.length === 0) { - // Always allow empty endpoint name (it's optional) - setEndpointName!(""); + if (value.length >= descriptionMaxLength) { + setIsMaxDescriptionLength(true); + } else { + setIsMaxDescriptionLength(false); } + setDescription!(value); }; //this function is necessary to select the text when double clicking, this was not working with the onFocus event @@ -80,7 +67,7 @@ export const EditFlowSettings: React.FC = ({ <>