Skip to content

Commit

Permalink
refactor: optimize flow saving functionality and implement manual sav…
Browse files Browse the repository at this point in the history
…ing (#3283)

* fixed patch update flow

* fixed update flow patch to receive id by payload

* created save flow hook with auto save and manual save functions

* fix poetry lock

* added auto save check with environment variable

* removed unused user

* separated autosave and put the flow as a creation with nodes and edges

* removed set nodes that skipped saving

* implemented auto save hook

* removed autosave from setNodes and setEdges

* added auto save hook and saved on viewport move and added useEffect to save on nodes and edges changed

* changed type of setNodes

* removed unused var

* removed deletion of empty flow

* Added saving of flow on button when autoSave is disabled

* disable saving when the nodes are empty

* removed save loading as false when the access token is renewed

* implemented useDebounce

* added save loading to save flow hook

* removed setting nodes and edges on fetching, since they are set when the current flow is updated

* removed unused var

* use debounce hook to save flow

* set nodes and edges on current flow id change

* removed useplaygroundeffect

* removed unused import

* put set save loading before the If

* removed flow cleaning and inputs setting, since the inputs and outputs are set on the ResetFlow function

* updated to use ResetFlow function to update everything regarding flow

* removed flow pool get on resetFlow, for it to be fetched only if the user is inside the flow

* updated packagelock

* Changed router to outlet on app.tsx to use createRouter

* Created authSettingsGuard to guard the general settings

* Fixed routes to use createBrowserRouter to allow the use of useBlocker

* Changed index.tsx to use RouterProvider and the router just created

* Changed flowStore to have a local flow state

* Implemented setting the current flow state when saving the flow

* Added the update of current flow when auto saving

* changed current flow to use the current flow from Flow Store instead of Flows Manager Store

* Changed codeTabsComponent Tweaks check to show if its checked

* Removed unused variables

* Removed browser router from context wrapper

* Removed unused console.log

* Changed initialSetup to just run when opening the modal

* changed confirmationModal to have destructiveCancel and to only call onCancel if the other buttons were not pressed

* Created a SaveChangesModal that confirms if the user wants to save their changes

* Get folder by id when folder id changes too

* Changed reset flow calls to store whole flow

* Added check if user is exiting page to prevent him when there are unsaved changes

* Added new types on ConfirmationModalType

* Implement save on clicking the save button on the header

* added save component shortcut to use save shortcut as save flow

* added save component shortcut on shortcutsStore type

* changed save shortcut to save component on node toolbar

* added save shortcut to header menubar

* changed shortcuts name to be compatible with existing ones

* changed shortcuts to be backwards compatible

* changed save to changes to maintain retrocompatibility

* changed save_component to save to maintain retrocompatibility

* Changed time difference to unsaved changes

* changed the toolbar select item to get the right save shortcut

* Changed save flow to use current flow from useFlowStore instead of the previous saved flow

* changed changesNotSaved to include flow name and metadata

* Added way of saving the flow settings just locally instead of directly to database

* Changed shareModal to save flow with hook

* removed old auto saving on connect

* Removed save functions from flowsManagerStore

* refactor: Remove unused imports and state variables in EditFlowSettings component

* use current flow not saved one and refactored page to not receive flow

* added check of isFlowPage to display the menubar

* Added checks to render playground if API key is valid and if Flows exists

* Added check to not display X on chat on playground page

* Updated flows variable to be undefined by start to prevent things from loading before flows initialize

* Implemented log builds parameter to not allow the builds to be logged if user not on flowPage
  • Loading branch information
lucaseduoli authored Aug 12, 2024
1 parent 7a93a70 commit 7264028
Show file tree
Hide file tree
Showing 62 changed files with 731 additions and 787 deletions.
1 change: 0 additions & 1 deletion src/frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions src/frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useContext, useEffect } from "react";
import { Cookies } from "react-cookie";
import { ErrorBoundary } from "react-error-boundary";
import { Outlet } from "react-router-dom";
import "reactflow/dist/style.css";
import "./App.css";
import AlertDisplayArea from "./alerts/displayArea";
Expand All @@ -22,7 +23,6 @@ import { useGetHealthQuery } from "./controllers/API/queries/health";
import { useGetVersionQuery } from "./controllers/API/queries/version";
import { setupAxiosDefaults } from "./controllers/API/utils";
import useTrackLastVisitedPath from "./hooks/use-track-last-visited-path";
import Router from "./routes";
import useAlertStore from "./stores/alertStore";
import useAuthStore from "./stores/authStore";
import { useDarkStore } from "./stores/darkStore";
Expand Down Expand Up @@ -172,8 +172,7 @@ export default function App() {
>
<LoadingComponent remSize={50} />
</div>

<Router />
<Outlet />
</>
</ErrorBoundary>
<div></div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
import { cloneDeep } from "lodash";
import { useUpdateNodeInternals } from "reactflow";
import ForwardedIconComponent from "../../../../components/genericIconComponent";
import ShadTooltip from "../../../../components/shadTooltipComponent";
import { Button } from "../../../../components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "../../../../components/ui/dropdown-menu";
import useFlowStore from "../../../../stores/flowStore";
import { outputComponentType } from "../../../../types/components";
import { NodeDataType } from "../../../../types/flow";
import { cn } from "../../../../utils/utils";

export default function OutputComponent({
Expand All @@ -23,9 +11,6 @@ export default function OutputComponent({
name,
proxy,
}: outputComponentType) {
const setNode = useFlowStore((state) => state.setNode);
const updateNodeInternals = useUpdateNodeInternals();

const displayProxy = (children) => {
if (proxy) {
return (
Expand Down
1 change: 0 additions & 1 deletion src/frontend/src/CustomNodes/GenericNode/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,6 @@ export default function GenericNode({
function handlePlayWShortcut() {
if (buildStatus === BuildStatus.BUILDING || isBuilding || !selected) return;
setValidationStatus(null);
console.log(data.node?.display_name);
buildFlow({ stopNodeId: data.id });
}

Expand Down
19 changes: 19 additions & 0 deletions src/frontend/src/components/authSettingsGuard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import FeatureFlags from "@/../feature-config.json";
import useAuthStore from "@/stores/authStore";
import { useStoreStore } from "@/stores/storeStore";
import { Navigate } from "react-router-dom";

export const AuthSettingsGuard = ({ children }) => {
const autoLogin = useAuthStore((state) => state.autoLogin);
const hasStore = useStoreStore((state) => state.hasStore);

// Hides the General settings if there is nothing to show
const showGeneralSettings =
FeatureFlags.ENABLE_PROFILE_ICONS || hasStore || !autoLogin;

if (showGeneralSettings) {
return children;
} else {
return <Navigate replace to="global-variables" />;
}
};

This file was deleted.

30 changes: 5 additions & 25 deletions src/frontend/src/components/cardComponent/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { usePostLikeComponent } from "@/controllers/API/queries/store";
import { useEffect, useState } from "react";
import { createRoot } from "react-dom/client";
import { useState } from "react";
import { Control } from "react-hook-form";
import { getComponent, postLikeComponent } from "../../controllers/API";
import { getComponent } from "../../controllers/API";
import IOModal from "../../modals/IOModal";
import DeleteConfirmationModal from "../../modals/deleteConfirmationModal";
import useAlertStore from "../../stores/alertStore";
import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { useStoreStore } from "../../stores/storeStore";
import { FlowType } from "../../types/flow";
Expand All @@ -31,7 +29,6 @@ import Loading from "../ui/loading";
import useDataEffect from "./hooks/use-data-effect";
import useInstallComponent from "./hooks/use-handle-install";
import useDragStart from "./hooks/use-on-drag-start";
import usePlaygroundEffect from "./hooks/use-playground-effect";
import { convertTestName } from "./utils/convert-test-name";

export default function CollectionCardComponent({
Expand All @@ -56,24 +53,16 @@ export default function CollectionCardComponent({
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const setValidApiKey = useStoreStore((state) => state.updateValidApiKey);
const cleanFlowPool = useFlowStore((state) => state.CleanFlowPool);
const isStore = false;
const [loading, setLoading] = useState(false);
const [likedByUser, setLikedByUser] = useState(data?.liked_by_user ?? false);
const [likesCount, setLikesCount] = useState(data?.liked_by_count ?? 0);
const [downloadsCount, setDownloadsCount] = useState(
data?.downloads_count ?? 0,
);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
const getFlowById = useFlowsManagerStore((state) => state.getFlowById);
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
const setNodes = useFlowStore((state) => state.setNodes);
const setEdges = useFlowStore((state) => state.setEdges);
const [openPlayground, setOpenPlayground] = useState(false);
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId,
);
const [loadingPlayground, setLoadingPlayground] = useState(false);

const selectedFlowsComponentsCards = useFlowsManagerStore(
Expand All @@ -96,16 +85,6 @@ export default function CollectionCardComponent({
return inputs.length > 0 || outputs.length > 0;
}

usePlaygroundEffect(
currentFlowId,
playground!,
openPlayground,
currentFlow,
setNodes,
setEdges,
cleanFlowPool,
);

useDataEffect(data, setLikedByUser, setLikesCount, setDownloadsCount);

const { handleInstall } = useInstallComponent(
Expand Down Expand Up @@ -317,7 +296,7 @@ export default function CollectionCardComponent({
setLoadingPlayground(false);
return;
}
setCurrentFlowId(data.id);
setCurrentFlow(flow);
setOpenPlayground(true);
setLoadingPlayground(false);
} else {
Expand Down Expand Up @@ -473,7 +452,7 @@ export default function CollectionCardComponent({
setLoadingPlayground(false);
return;
}
setCurrentFlowId(data.id);
setCurrentFlow(flow);
setOpenPlayground(true);
setLoadingPlayground(false);
} else {
Expand Down Expand Up @@ -510,6 +489,7 @@ export default function CollectionCardComponent({
</Card>
{openPlayground && (
<IOModal
key={data.id}
cleanOnClose={true}
open={openPlayground}
setOpen={setOpenPlayground}
Expand Down
7 changes: 2 additions & 5 deletions src/frontend/src/components/chatComponent/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import FeatureFlags from "@/../feature-config.json";
import { Transition } from "@headlessui/react";
import { useMemo, useRef, useState } from "react";
import { useMemo, useState } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import IOModal from "../../modals/IOModal";
import ApiModal from "../../modals/apiModal";
import ShareModal from "../../modals/shareModal";
import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { useShortcutsStore } from "../../stores/shortcuts";
import { useStoreStore } from "../../stores/storeStore";
import { classNames, isThereModal } from "../../utils/utils";
Expand Down Expand Up @@ -47,9 +46,7 @@ export default function FlowToolbar(): JSX.Element {
const hasStore = useStoreStore((state) => state.hasStore);
const validApiKey = useStoreStore((state) => state.validApiKey);
const hasApiKey = useStoreStore((state) => state.hasApiKey);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);

const prevNodesRef = useRef<any[] | undefined>();
const currentFlow = useFlowStore((state) => state.currentFlow);

const ModalMemo = useMemo(
() => (
Expand Down
1 change: 1 addition & 0 deletions src/frontend/src/components/codeTabsComponent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export default function CodeTabsComponent({
}}
id="tweaks-switch"
onCheckedChange={setActiveTweaks}
checked={activeTweaks}
autoFocus={false}
/>
<Label
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React, { ChangeEvent, useState } from "react";
import { Input } from "../../components/ui/input";
import { Label } from "../../components/ui/label";
import { Textarea } from "../../components/ui/textarea";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { InputProps } from "../../types/components";
import { cn, isEndpointNameValid } from "../../utils/utils";

Expand All @@ -19,7 +18,6 @@ export const EditFlowSettings: React.FC<InputProps> = ({
const [isMaxLength, setIsMaxLength] = useState(false);
const [validEndpointName, setValidEndpointName] = useState(true);
const [isInvalidName, setIsInvalidName] = useState(false);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);

const handleNameChange = (event: ChangeEvent<HTMLInputElement>) => {
const { value } = event.target;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import {
} from "../../../ui/dropdown-menu";

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 { customStringify } from "@/utils/reactflowUtils";
import { useHotkeys } from "react-hotkeys-hook";
import { useNavigate } from "react-router-dom";
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
import { SAVED_HOVER } from "../../../../constants/constants";
Expand All @@ -29,7 +32,6 @@ import { Button } from "../../../ui/button";
export const MenuBar = ({}: {}): JSX.Element => {
const shortcuts = useShortcutsStore((state) => state.shortcuts);
const addFlow = useAddFlow();
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const setErrorData = useAlertStore((state) => state.setErrorData);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setLockChat = useFlowStore((state) => state.setLockChat);
Expand All @@ -46,6 +48,24 @@ export const MenuBar = ({}: {}): JSX.Element => {
const navigate = useNavigate();
const isBuilding = useFlowStore((state) => state.isBuilding);
const getTypes = useTypesStore((state) => state.getTypes);
const saveFlow = useSaveFlow();
const shouldAutosave = process.env.LANGFLOW_AUTO_SAVE !== "false";
const currentFlow = useFlowStore((state) => state.currentFlow);
const currentSavedFlow = useFlowsManagerStore((state) => state.currentFlow);
const updatedAt = currentSavedFlow?.updated_at;
const onFlowPage = useFlowStore((state) => state.onFlowPage);

const changesNotSaved =
customStringify(currentFlow) !== customStringify(currentSavedFlow) &&
!shouldAutosave;

const savedText =
updatedAt && changesNotSaved
? new Date(updatedAt).toLocaleString("en-US", {
hour: "numeric",
minute: "numeric",
})
: "Saved";

function handleAddFlow() {
try {
Expand All @@ -69,10 +89,19 @@ export const MenuBar = ({}: {}): JSX.Element => {
} else if (saveLoading) {
return "Saving...";
}
return "Saved";
return savedText;
}

return currentFlow ? (
const handleSave = () => {
saveFlow().then(() => {
setSuccessData({ title: "Saved successfully" });
});
};

const changes = useShortcutsStore((state) => state.changes);
useHotkeys(changes, handleSave, { preventDefault: true });

return currentFlow && onFlowPage ? (
<div className="round-button-div">
<div className="header-menu-bar">
<DropdownMenu>
Expand Down Expand Up @@ -112,6 +141,20 @@ export const MenuBar = ({}: {}): JSX.Element => {
<IconComponent name="Settings2" className="header-menu-options" />
Settings
</DropdownMenuItem>
{!shouldAutosave && (
<DropdownMenuItem onClick={handleSave} className="cursor-pointer">
<ToolbarSelectItem
value="Save"
icon="Save"
dataTestId=""
shortcut={
shortcuts.find(
(s) => s.name.toLowerCase() === "changes save",
)?.shortcut!
}
/>
</DropdownMenuItem>
)}
<DropdownMenuItem
onClick={() => {
setOpenLogs(true);
Expand Down Expand Up @@ -205,11 +248,11 @@ export const MenuBar = ({}: {}): JSX.Element => {
></FlowSettingsModal>
<FlowLogsModal open={openLogs} setOpen={setOpenLogs}></FlowLogsModal>
</div>
{(currentFlow.updated_at || saveLoading) && (
{(updatedAt || saveLoading) && (
<ShadTooltip
content={
SAVED_HOVER +
new Date(currentFlow.updated_at ?? "").toLocaleString("en-US", {
new Date(updatedAt ?? "").toLocaleString("en-US", {
hour: "numeric",
minute: "numeric",
second: "numeric",
Expand All @@ -220,13 +263,32 @@ export const MenuBar = ({}: {}): JSX.Element => {
>
<div className="flex cursor-default items-center gap-2 text-sm text-muted-foreground transition-all">
<div className="flex cursor-default items-center gap-1.5 text-sm text-muted-foreground transition-all">
<IconComponent
name={isBuilding || saveLoading ? "Loader2" : "CheckCircle2"}
<Button
unstyled
disabled={shouldAutosave || !changesNotSaved}
className={cn(
"h-4 w-4",
isBuilding || saveLoading ? "animate-spin" : "animate-wiggle",
!shouldAutosave && changesNotSaved
? "hover:text-primary"
: "",
)}
/>
onClick={handleSave}
>
<IconComponent
name={
isBuilding || saveLoading
? "Loader2"
: changesNotSaved
? "Save"
: "CheckCircle2"
}
className={cn(
"h-4 w-4",
isBuilding || saveLoading
? "animate-spin"
: "animate-wiggle",
)}
/>
</Button>
<div>{printByBuildStatus()}</div>
</div>
<button
Expand Down
Loading

0 comments on commit 7264028

Please sign in to comment.