refactor: optimize flow saving functionality and implement manual saving (#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
This commit is contained in:
Lucas Oliveira 2024-08-12 19:21:52 -03:00 committed by GitHub
commit 7264028e41
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 734 additions and 790 deletions

View file

@ -1079,7 +1079,6 @@
},
"node_modules/@clack/prompts/node_modules/is-unicode-supported": {
"version": "1.3.0",
"extraneous": true,
"inBundle": true,
"license": "MIT",
"engines": {

View file

@ -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";
@ -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";
@ -172,8 +172,7 @@ export default function App() {
>
<LoadingComponent remSize={50} />
</div>
<Router />
<Outlet />
</>
</ErrorBoundary>
<div></div>

View file

@ -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({
@ -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 (

View file

@ -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 });
}

View file

@ -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" />;
}
};

View file

@ -1,27 +0,0 @@
import { useEffect } from "react";
import { FlowType } from "../../../types/flow";
const usePlaygroundEffect = (
currentFlowId: string,
playground: boolean,
openPlayground: boolean,
currentFlow: FlowType | undefined,
setNodes: (value: any, value2: boolean) => void,
setEdges: (value: any, value2: boolean) => void,
cleanFlowPool: () => void,
) => {
useEffect(() => {
if (currentFlowId && playground) {
if (openPlayground) {
setNodes(currentFlow?.data?.nodes ?? [], true);
setEdges(currentFlow?.data?.edges ?? [], true);
} else {
setNodes([], true);
setEdges([], true);
}
cleanFlowPool();
}
}, [openPlayground]);
};
export default usePlaygroundEffect;

View file

@ -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";
@ -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({
@ -56,7 +53,6 @@ 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);
@ -64,16 +60,9 @@ export default function CollectionCardComponent({
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(
@ -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(
@ -317,7 +296,7 @@ export default function CollectionCardComponent({
setLoadingPlayground(false);
return;
}
setCurrentFlowId(data.id);
setCurrentFlow(flow);
setOpenPlayground(true);
setLoadingPlayground(false);
} else {
@ -473,7 +452,7 @@ export default function CollectionCardComponent({
setLoadingPlayground(false);
return;
}
setCurrentFlowId(data.id);
setCurrentFlow(flow);
setOpenPlayground(true);
setLoadingPlayground(false);
} else {
@ -510,6 +489,7 @@ export default function CollectionCardComponent({
</Card>
{openPlayground && (
<IOModal
key={data.id}
cleanOnClose={true}
open={openPlayground}
setOpen={setOpenPlayground}

View file

@ -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";
@ -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(
() => (

View file

@ -86,6 +86,7 @@ export default function CodeTabsComponent({
}}
id="tweaks-switch"
onCheckedChange={setActiveTweaks}
checked={activeTweaks}
autoFocus={false}
/>
<Label

View file

@ -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";
@ -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;

View file

@ -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";
@ -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);
@ -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 {
@ -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>
@ -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);
@ -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",
@ -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

View file

@ -1,7 +1,7 @@
import { useContext } from "react";
import { FaDiscord, FaGithub } from "react-icons/fa";
import { RiTwitterXFill } from "react-icons/ri";
import { Link, useLocation, useNavigate, useParams } from "react-router-dom";
import { Link, useLocation, useNavigate } from "react-router-dom";
import AlertDropdown from "../../alerts/alertDropDown";
import {
BASE_URL_API,
@ -12,11 +12,9 @@ import { AuthContext } from "../../contexts/authContext";
import FeatureFlags from "@/../feature-config.json";
import { useLogout } from "@/controllers/API/queries/auth";
import useDeleteFlow from "@/hooks/flows/use-delete-flow";
import useAuthStore from "@/stores/authStore";
import useAlertStore from "../../stores/alertStore";
import { useDarkStore } from "../../stores/darkStore";
import useFlowStore from "../../stores/flowStore";
import { useLocationStore } from "../../stores/locationStore";
import { useStoreStore } from "../../stores/storeStore";
import IconComponent, { ForwardedIconComponent } from "../genericIconComponent";
@ -44,10 +42,7 @@ export default function Header(): JSX.Element {
const logout = useAuthStore((state) => state.logout);
const navigate = useNavigate();
const deleteFlow = useDeleteFlow();
const hasStore = useStoreStore((state) => state.hasStore);
const { id } = useParams();
const nodes = useFlowStore((state) => state.nodes);
const dark = useDarkStore((state) => state.dark);
const setDark = useDarkStore((state) => state.setDark);
@ -58,11 +53,6 @@ export default function Header(): JSX.Element {
const profileImageUrl = `${BASE_URL_API}files/profile_pictures/${
userData?.profile_image ?? "Space/046-rocket.svg"
}`;
async function checkForChanges(): Promise<void> {
if (nodes.length === 0 && id) {
await deleteFlow({ id });
}
}
const redirectToLastLocation = () => {
const lastFlowVisitedIndex = routeHistory
@ -100,14 +90,13 @@ export default function Header(): JSX.Element {
return (
<div className="header-arrangement">
<div className="header-start-display lg:w-[407px]">
<Link to="/all" className="cursor-pointer" onClick={checkForChanges}>
<Link to="/all" className="cursor-pointer">
<span className="ml-4 text-2xl"></span>
</Link>
{showArrowReturnIcon && (
<Button
unstyled
onClick={() => {
checkForChanges();
redirectToLastLocation();
}}
>
@ -129,7 +118,6 @@ export default function Header(): JSX.Element {
: "secondary"
}
size="sm"
onClick={checkForChanges}
>
<IconComponent name="Home" className="h-4 w-4" />
<div className="hidden flex-1 md:block">{USER_PROJECTS_HEADER}</div>
@ -142,7 +130,6 @@ export default function Header(): JSX.Element {
className="gap-2"
variant={location.pathname === "/store" ? "primary" : "secondary"}
size="sm"
onClick={checkForChanges}
data-testid="button-store"
>
<IconComponent name="Store" className="h-4 w-4" />

View file

@ -1,8 +1,8 @@
import useSaveFlow from "@/hooks/flows/use-save-flow";
import {
UPLOAD_ALERT_LIST,
WRONG_FILE_ERROR_ALERT,
} from "../../../constants/alerts_constants";
import { updateFlowInDatabase } from "../../../controllers/API";
import { uploadFlowToFolder } from "../../../pages/MainPage/services";
import useAlertStore from "../../../stores/alertStore";
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
@ -21,6 +21,7 @@ const useFileDrop = (
const setErrorData = useAlertStore((state) => state.setErrorData);
const refreshFolders = useFolderStore((state) => state.refreshFolders);
const flows = useFlowsManagerStore((state) => state.flows);
const saveFlow = useSaveFlow();
const triggerFolderChange = (folderId) => {
if (folderChangeCallback) {
@ -106,21 +107,21 @@ const useFileDrop = (
};
const uploadFromDragCard = (flowId, folderId) => {
const selectedFlow = flows.find((flow) => flow.id === flowId);
const selectedFlow = flows?.find((flow) => flow.id === flowId);
if (!selectedFlow) {
throw new Error("Flow not found");
}
const updatedFlow = { ...selectedFlow, folder_id: folderId };
const newName = addVersionToDuplicates(updatedFlow, flows);
const newName = addVersionToDuplicates(updatedFlow, flows ?? []);
updatedFlow.name = newName;
setFolderDragging(false);
setFolderIdDragging("");
updateFlowInDatabase(updatedFlow).then(() => {
saveFlow(updatedFlow).then(() => {
refreshFolders();
triggerFolderChange(folderId);
});

View file

@ -770,9 +770,13 @@ export const defaultShortcuts = [
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Shift + D`,
},
{
name: "Save",
name: "Changes Save",
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + S`,
},
{
name: "Save Component",
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Alt + S`,
},
{
name: "Delete",
shortcut: "Backspace",

View file

@ -5,11 +5,8 @@ import {
} from "@/constants/constants";
import { useGetUserData } from "@/controllers/API/queries/auth";
import useAuthStore from "@/stores/authStore";
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { createContext, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import Cookies from "universal-cookie";
import { getLoggedUser, requestLogout } from "../controllers/API";
import useAlertStore from "../stores/alertStore";
import { useFolderStore } from "../stores/foldersStore";
import { useStoreStore } from "../stores/storeStore";
@ -31,7 +28,6 @@ const initialValue: AuthContextType = {
export const AuthContext = createContext<AuthContextType>(initialValue);
export function AuthProvider({ children }): React.ReactElement {
const navigate = useNavigate();
const cookies = new Cookies();
const [accessToken, setAccessToken] = useState<string | null>(
cookies.get(LANGFLOW_ACCESS_TOKEN) ?? null,
@ -46,12 +42,7 @@ export function AuthProvider({ children }): React.ReactElement {
const checkHasStore = useStoreStore((state) => state.checkHasStore);
const fetchApiData = useStoreStore((state) => state.fetchApiData);
const setAllFlows = useFlowsManagerStore((state) => state.setAllFlows);
const setSelectedFolder = useFolderStore((state) => state.setSelectedFolder);
const setIsAuthenticated = useAuthStore((state) => state.setIsAuthenticated);
const setIsAdmin = useAuthStore((state) => state.setIsAdmin);
const setIsLoading = useFlowsManagerStore((state) => state.setIsLoading);
const autoLogin = useAuthStore((state) => state.autoLogin);
const { mutate: mutateLoggedUser } = useGetUserData();

View file

@ -1,6 +1,5 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactNode } from "react";
import { BrowserRouter } from "react-router-dom";
import { ReactFlowProvider } from "reactflow";
import { TooltipProvider } from "../components/ui/tooltip";
import { ApiInterceptor } from "../controllers/API/api";
@ -11,18 +10,16 @@ export default function ContextWrapper({ children }: { children: ReactNode }) {
//element to wrap all context
return (
<>
<BrowserRouter>
<QueryClientProvider client={queryClient}>
<AuthProvider>
<TooltipProvider skipDelayDuration={0}>
<ReactFlowProvider>
<ApiInterceptor />
{children}
</ReactFlowProvider>
</TooltipProvider>
</AuthProvider>
</QueryClientProvider>
</BrowserRouter>
<QueryClientProvider client={queryClient}>
<AuthProvider>
<TooltipProvider skipDelayDuration={0}>
<ReactFlowProvider>
<ApiInterceptor />
{children}
</ReactFlowProvider>
</TooltipProvider>
</AuthProvider>
</QueryClientProvider>
</>
);
}

View file

@ -1,6 +1,5 @@
import { LANGFLOW_ACCESS_TOKEN } from "@/constants/constants";
import useAuthStore from "@/stores/authStore";
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig } from "axios";
import { useContext, useEffect } from "react";
import { Cookies } from "react-cookie";
@ -21,8 +20,6 @@ function ApiInterceptor() {
const autoLogin = useAuthStore((state) => state.autoLogin);
const setErrorData = useAlertStore((state) => state.setErrorData);
let { accessToken, authenticationErrorCount } = useContext(AuthContext);
const setSaveLoading = useFlowsManagerStore((state) => state.setSaveLoading);
const { mutate: mutationLogout } = useLogout();
const { mutate: mutationRenewAccessToken } = useRefreshAccessToken();
const logout = useAuthStore((state) => state.logout);
@ -152,7 +149,6 @@ function ApiInterceptor() {
onSuccess: async (data) => {
authenticationErrorCount = 0;
await remakeRequest(error);
setSaveLoading(false);
authenticationErrorCount = 0;
},
onError: (error) => {

View file

@ -141,34 +141,6 @@ export async function saveFlowToDatabase(newFlow: {
throw error;
}
}
/**
* Updates an existing flow in the database.
*
* @param {FlowType} updatedFlow - The updated flow data.
* @returns {Promise<any>} The updated flow data.
* @throws Will throw an error if the update fails.
*/
export async function updateFlowInDatabase(
updatedFlow: FlowType,
): Promise<FlowType> {
try {
const response = await api.patch(`${BASE_URL_API}flows/${updatedFlow.id}`, {
name: updatedFlow.name,
data: updatedFlow.data,
description: updatedFlow.description,
folder_id: updatedFlow.folder_id === "" ? null : updatedFlow.folder_id,
endpoint_name: updatedFlow.endpoint_name,
});
if (response && response?.status !== 200) {
throw new Error(`HTTP error! status: ${response?.status}`);
}
return response?.data;
} catch (error) {
console.error(error);
throw error;
}
}
/**
* Reads all flows from the database.

View file

@ -1,5 +1,4 @@
import useFlowStore from "@/stores/flowStore";
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { FlowPoolType } from "@/types/zustand/flow";
import { keepPreviousData } from "@tanstack/react-query";
import { AxiosResponse } from "axios";
@ -20,7 +19,7 @@ export const useGetBuildsQuery: useQueryFunctionType<
const { query } = UseRequestProcessor();
const setFlowPool = useFlowStore((state) => state.setFlowPool);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const currentFlow = useFlowStore((state) => state.currentFlow);
const getBuildsFn = async (
params: BuildsQueryParams,

View file

@ -6,41 +6,34 @@ import { getURL } from "../../helpers/constants";
import { UseRequestProcessor } from "../../services/request-processor";
interface IPatchUpdateFlow {
id: string;
name: string;
data: ReactFlowJsonObject;
description: string;
folder_id: string;
endpoint_name: string;
}
interface IPatchUpdateFlowParams {
id: string;
folder_id: string | null | undefined;
endpoint_name: string | null | undefined;
}
export const usePatchUpdateFlow: useMutationFunctionType<
IPatchUpdateFlowParams,
undefined,
IPatchUpdateFlow
> = (params, options?) => {
> = (options?) => {
const { mutate } = UseRequestProcessor();
const PatchUpdateFlowFn = async (payload: IPatchUpdateFlow): Promise<any> => {
const response = await api.patch(`${getURL("FLOWS")}/${params}`, {
const response = await api.patch(`${getURL("FLOWS")}/${payload.id}`, {
name: payload.name,
data: payload.data,
description: payload.description,
folder_id: payload.folder_id === "" ? null : payload.folder_id,
endpoint_name: payload.endpoint_name,
folder_id: payload.folder_id || null,
endpoint_name: payload.endpoint_name || null,
});
return response.data;
};
const mutation: UseMutationResult<IPatchUpdateFlow, any, IPatchUpdateFlow> =
mutate(
["usePatchUpdateFlow", { id: params.id }],
PatchUpdateFlowFn,
options,
);
mutate(["usePatchUpdateFlow"], PatchUpdateFlowFn, options);
return mutation;
};

View file

@ -49,7 +49,7 @@ const useAddFlow = () => {
const my_collection_id = useFolderStore.getState().myCollectionId;
if (params?.override && flow) {
const flowId = flows.find((f) => f.name === flow.name);
const flowId = flows?.find((f) => f.name === flow.name);
if (flowId) {
await deleteFlow({ id: flowId.id });
}
@ -60,7 +60,7 @@ const useAddFlow = () => {
flow,
);
const newName = addVersionToDuplicates(newFlow, flows);
const newName = addVersionToDuplicates(newFlow, flows ?? []);
newFlow.name = newName;
newFlow.folder_id = useFolderStore.getState().folderUrl;
@ -68,7 +68,10 @@ const useAddFlow = () => {
onSuccess: ({ id }) => {
newFlow.id = id;
// Add the new flow to the list of flows.
const { data, flows: myFlows } = processFlows([newFlow, ...flows]);
const { data, flows: myFlows } = processFlows([
newFlow,
...(flows ?? []),
]);
setFlows(myFlows);
useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data },

View file

@ -0,0 +1,19 @@
import { SAVE_DEBOUNCE_TIME } from "@/constants/constants";
import { FlowType } from "@/types/flow";
import { useDebounce } from "../use-debounce";
import useSaveFlow from "./use-save-flow";
const useAutoSaveFlow = () => {
const saveFlow = useSaveFlow();
const shouldAutosave = process.env.LANGFLOW_AUTO_SAVE !== "false";
const autoSaveFlow = shouldAutosave
? useDebounce((flow?: FlowType) => {
saveFlow(flow);
}, SAVE_DEBOUNCE_TIME)
: () => {};
return autoSaveFlow;
};
export default useAutoSaveFlow;

View file

@ -27,7 +27,7 @@ const useDeleteFlow = () => {
{
onSuccess: () => {
const { data, flows: myFlows } = processFlows(
flows.filter((flow) => !id.includes(flow.id)),
(flows ?? []).filter((flow) => !id.includes(flow.id)),
);
setFlows(myFlows);
setIsLoading(false);

View file

@ -0,0 +1,100 @@
import { usePatchUpdateFlow } from "@/controllers/API/queries/flows/use-patch-update-flow";
import useAlertStore from "@/stores/alertStore";
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import useFlowStore from "@/stores/flowStore";
import { FlowType } from "@/types/flow";
const useSaveFlow = () => {
const flows = useFlowsManagerStore((state) => state.flows);
const setFlows = useFlowsManagerStore((state) => state.setFlows);
const setErrorData = useAlertStore((state) => state.setErrorData);
const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance);
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const setSaveLoading = useFlowsManagerStore((state) => state.setSaveLoading);
const setCurrentFlow = useFlowStore((state) => state.setCurrentFlow);
const currentSavedFlow = useFlowsManagerStore((state) => state.currentFlow);
const currentFlow = useFlowStore((state) => state.currentFlow);
const flowData = currentFlow?.data;
const { mutate } = usePatchUpdateFlow();
const saveFlow = async (flow?: FlowType): Promise<void> => {
setSaveLoading(true);
return new Promise<void>((resolve, reject) => {
if (currentFlow) {
flow = flow || {
...currentFlow,
data: {
...flowData,
nodes,
edges,
viewport: reactFlowInstance?.getViewport() ?? {
zoom: 1,
x: 0,
y: 0,
},
},
};
}
if (flow && flow.data) {
const { id, name, data, description, folder_id, endpoint_name } = flow;
if (!currentSavedFlow?.data?.nodes.length || data.nodes.length > 0) {
mutate(
{ id, name, data, description, folder_id, endpoint_name },
{
onSuccess: (updatedFlow) => {
setSaveLoading(false);
if (flows) {
// updates flow in state
setFlows(
flows.map((flow) => {
if (flow.id === updatedFlow.id) {
return updatedFlow;
}
return flow;
}),
);
setCurrentFlow(updatedFlow);
resolve();
} else {
setErrorData({
title: "Failed to save flow",
list: ["Flows variable undefined"],
});
reject(new Error("Flows variable undefined"));
}
},
onError: (e) => {
setErrorData({
title: "Failed to save flow",
list: [e.message],
});
setSaveLoading(false);
reject(e);
},
},
);
} else {
setErrorData({
title: "Failed to save flow",
list: ["Can't save empty flow"],
});
setSaveLoading(false);
reject(new Error("Can't save empty flow"));
}
} else {
setErrorData({
title: "Failed to save flow",
list: ["Flow not found"],
});
reject(new Error("Flow not found"));
}
});
};
return saveFlow;
};
export default useSaveFlow;

View file

@ -0,0 +1,13 @@
import { debounce } from "lodash";
import { useLayoutEffect, useMemo, useRef } from "react";
export function useDebounce(callback, delay) {
const callbackRef = useRef(callback);
useLayoutEffect(() => {
callbackRef.current = callback;
});
return useMemo(
() => debounce((...args) => callbackRef.current(...args), delay),
[delay],
);
}

View file

@ -1,5 +1,4 @@
import ReactDOM from "react-dom/client";
import App from "./App";
import ContextWrapper from "./contexts";
import reportWebVitals from "./reportWebVitals";
@ -8,6 +7,10 @@ import "./style/index.css";
// @ts-ignore
import "./style/applies.css";
// @ts-ignore
import { Suspense } from "react";
import { RouterProvider } from "react-router-dom";
import LoadingComponent from "./components/loadingComponent";
import router from "./routes";
import "./style/classes.css";
const root = ReactDOM.createRoot(
@ -15,7 +18,15 @@ const root = ReactDOM.createRoot(
);
root.render(
<ContextWrapper>
<App />
<Suspense
fallback={
<div className="loading-page-panel">
<LoadingComponent remSize={50} />
</div>
}
>
<RouterProvider router={router} />
</Suspense>
</ContextWrapper>,
);
reportWebVitals();

View file

@ -11,7 +11,6 @@ import {
FS_ERROR_TEXT,
SN_ERROR_TEXT,
} from "../../../../constants/constants";
import { deleteFlowPool } from "../../../../controllers/API";
import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";

View file

@ -33,6 +33,7 @@ export default function IOModal({
open,
setOpen,
disable,
isPlayground,
}: IOModalPropsType): JSX.Element {
const allNodes = useFlowStore((state) => state.nodes);
const inputs = useFlowStore((state) => state.inputs).filter(
@ -107,7 +108,7 @@ export default function IOModal({
const setLockChat = useFlowStore((state) => state.setLockChat);
const [chatValue, setChatValue] = useState("");
const isBuilding = useFlowStore((state) => state.isBuilding);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
const setNode = useFlowStore((state) => state.setNode);
const [sessions, setSessions] = useState<string[]>([]);
const messages = useMessagesStore((state) => state.messages);
@ -115,7 +116,7 @@ export default function IOModal({
const { refetch } = useGetMessagesQuery({
mode: "union",
id: currentFlow?.id,
id: currentFlowId,
});
async function sendMessage({
@ -164,7 +165,7 @@ export default function IOModal({
useEffect(() => {
const sessions = new Set<string>();
messages
.filter((message) => message.flow_id === currentFlow!.id)
.filter((message) => message.flow_id === currentFlowId)
.forEach((row) => {
sessions.add(row.session_id);
});
@ -178,6 +179,7 @@ export default function IOModal({
open={open}
setOpen={setOpen}
disable={disable}
type={isPlayground ? "modal" : undefined}
onSubmit={() => sendMessage({ repeat: 1 })}
>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
@ -375,7 +377,7 @@ export default function IOModal({
size="md"
className="block truncate"
>
{session === currentFlow?.id
{session === currentFlowId
? "Default Session"
: session}
</Badge>
@ -491,7 +493,7 @@ export default function IOModal({
) && (
<SessionView
session={selectedViewField.id}
id={currentFlow!.id}
id={currentFlowId}
/>
)}
</div>

View file

@ -35,9 +35,6 @@ export default function ApiModal({
useEffect(() => {
if (open) initialSetup(autoLogin, flow);
}, [open, flow?.data?.nodes]);
useEffect(() => {
setActiveTab("0");
}, [open]);

View file

@ -33,6 +33,7 @@ function ConfirmationModal({
confirmationText,
children,
destructive = false,
destructiveCancel = false,
icon,
data,
index,
@ -44,13 +45,18 @@ function ConfirmationModal({
}: ConfirmationModalType) {
const Icon: any = nodeIconsLucide[icon];
const [modalOpen, setModalOpen] = useState(open ?? false);
const [flag, setFlag] = useState(false);
useEffect(() => {
if (open) setModalOpen(open);
}, [open]);
useEffect(() => {
if (onClose) onClose!(modalOpen);
if (onClose && modalOpen === false && !flag) {
onClose();
} else if (flag) {
setFlag(false);
}
}, [modalOpen]);
const triggerChild = React.Children.toArray(children).find(
@ -61,7 +67,7 @@ function ConfirmationModal({
);
return (
<BaseModal size={size} open={open} setOpen={setModalOpen}>
<BaseModal size={size} open={modalOpen} setOpen={setModalOpen}>
<BaseModal.Trigger>{triggerChild}</BaseModal.Trigger>
<BaseModal.Header description={titleHeader ?? null}>
<span className="pr-2">{title}</span>
@ -86,6 +92,7 @@ function ConfirmationModal({
className="ml-3"
variant={destructive ? "destructive" : "default"}
onClick={() => {
setFlag(true);
setModalOpen(false);
onConfirm(index, data);
}}
@ -96,8 +103,9 @@ function ConfirmationModal({
<Button
className=""
variant="outline"
variant={destructiveCancel ? "destructive" : "outline"}
onClick={() => {
setFlag(true);
if (onCancel) onCancel();
setModalOpen(false);
}}

View file

@ -1,3 +1,4 @@
import useFlowStore from "@/stores/flowStore";
import { ReactNode, forwardRef, useEffect, useState } from "react";
import EditFlowSettings from "../../components/editFlowSettingsComponent";
import IconComponent from "../../components/genericIconComponent";
@ -10,7 +11,6 @@ import {
} from "../../constants/constants";
import useAlertStore from "../../stores/alertStore";
import { useDarkStore } from "../../stores/darkStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { downloadFlow, removeApiKeys } from "../../utils/reactflowUtils";
import BaseModal from "../baseModal";
@ -19,7 +19,7 @@ const ExportModal = forwardRef(
const version = useDarkStore((state) => state.version);
const setNoticeData = useAlertStore((state) => state.setNoticeData);
const [checked, setChecked] = useState(false);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const currentFlow = useFlowStore((state) => state.currentFlow);
useEffect(() => {
setName(currentFlow!.name);
setDescription(currentFlow!.description);

View file

@ -1,8 +1,11 @@
import useSaveFlow from "@/hooks/flows/use-save-flow";
import useAlertStore from "@/stores/alertStore";
import useFlowStore from "@/stores/flowStore";
import { cloneDeep } from "lodash";
import { useEffect, useState } from "react";
import EditFlowSettings from "../../components/editFlowSettingsComponent";
import IconComponent from "../../components/genericIconComponent";
import { SETTINGS_DIALOG_SUBTITLE } from "../../constants/constants";
import useAlertStore from "../../stores/alertStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { FlowSettingsPropsType } from "../../types/components";
import { FlowType } from "../../types/flow";
@ -13,8 +16,10 @@ export default function FlowSettingsModal({
open,
setOpen,
}: FlowSettingsPropsType): JSX.Element {
const saveFlow = useFlowsManagerStore((state) => state.saveFlow);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const saveFlow = useSaveFlow();
const currentFlow = useFlowStore((state) => state.currentFlow);
const setCurrentFlow = useFlowStore((state) => state.setCurrentFlow);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const flows = useFlowsManagerStore((state) => state.flows);
useEffect(() => {
setName(currentFlow!.name);
@ -28,35 +33,43 @@ export default function FlowSettingsModal({
);
const [isSaving, setIsSaving] = useState(false);
const [disableSave, setDisableSave] = useState(true);
const shouldAutosave = process.env.LANGFLOW_AUTO_SAVE !== "false";
function handleClick(): void {
setIsSaving(true);
currentFlow!.name = name;
currentFlow!.description = description;
currentFlow!.endpoint_name =
if (!currentFlow) return;
const newFlow = cloneDeep(currentFlow);
newFlow.name = name;
newFlow.description = description;
newFlow.endpoint_name =
endpoint_name && endpoint_name.length > 0 ? endpoint_name : null;
saveFlow(currentFlow!)
?.then(() => {
setOpen(false);
setIsSaving(false);
})
.catch((err) => {
useAlertStore.getState().setErrorData({
title: "Error while saving changes",
list: [err?.response?.data.detail ?? ""],
if (shouldAutosave) {
saveFlow(newFlow)
?.then(() => {
setOpen(false);
setIsSaving(false);
setSuccessData({ title: "Changes saved successfully" });
})
.catch(() => {
setIsSaving(false);
});
console.error(err);
setIsSaving(false);
});
} else {
setCurrentFlow(newFlow);
setOpen(false);
setIsSaving(false);
}
}
const [nameLists, setNameList] = useState<string[]>([]);
useEffect(() => {
const tempNameList: string[] = [];
flows.forEach((flow: FlowType) => {
if ((flow.is_component ?? false) === false) tempNameList.push(flow.name);
});
setNameList(tempNameList.filter((name) => name !== currentFlow!.name));
if (flows) {
const tempNameList: string[] = [];
flows.forEach((flow: FlowType) => {
if ((flow.is_component ?? false) === false)
tempNameList.push(flow.name);
});
setNameList(tempNameList.filter((name) => name !== currentFlow!.name));
}
}, [flows]);
useEffect(() => {

View file

@ -0,0 +1,22 @@
import ConfirmationModal from "../confirmationModal";
export function SaveChangesModal({ onSave, onProceed, onCancel }) {
return (
<ConfirmationModal
open={true}
onClose={onCancel}
destructiveCancel
title={"Exit without saving?"}
cancelText={"Exit anyway"}
confirmationText={"Save and Exit"}
icon={"Save"}
onConfirm={onSave}
onCancel={onProceed}
size="x-small"
>
<ConfirmationModal.Content>
You have unsaved changes. Would you like to save them before exiting?
</ConfirmationModal.Content>
</ConfirmationModal>
);
}

View file

@ -1,4 +1,5 @@
import { useGetTagsQuery } from "@/controllers/API/queries/store";
import useSaveFlow from "@/hooks/flows/use-save-flow";
import { cloneDeep } from "lodash";
import { ReactNode, useEffect, useMemo, useState } from "react";
import EditFlowSettings from "../../components/editFlowSettingsComponent";
@ -8,13 +9,11 @@ import { Button } from "../../components/ui/button";
import { Checkbox } from "../../components/ui/checkbox";
import {
getStoreComponents,
getStoreTags,
saveFlowStore,
updateFlowStore,
} from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import { useDarkStore } from "../../stores/darkStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { useStoreStore } from "../../stores/storeStore";
import { FlowType } from "../../types/flow";
import {
@ -59,7 +58,7 @@ export default function ShareModal({
const [unavaliableNames, setUnavaliableNames] = useState<
{ id: string; name: string }[]
>([]);
const saveFlow = useFlowsManagerStore((state) => state.saveFlow);
const saveFlow = useSaveFlow();
const { data, isFetching } = useGetTagsQuery();
const [loadingNames, setLoadingNames] = useState(false);
@ -105,7 +104,7 @@ export default function ShareModal({
function successShare() {
if (!is_component) {
saveFlow(flow!, true);
saveFlow(flow);
}
setSuccessData({
title: `${is_component ? "Component" : "Flow"} shared successfully!`,

View file

@ -38,7 +38,6 @@ import { AuthContext } from "../../contexts/authContext";
import ConfirmationModal from "../../modals/confirmationModal";
import UserManagementModal from "../../modals/userManagementModal";
import useAlertStore from "../../stores/alertStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { Users } from "../../types/api";
import { UserInputType } from "../../types/components";
@ -51,19 +50,11 @@ export default function AdminPage() {
const setErrorData = useAlertStore((state) => state.setErrorData);
const { userData } = useContext(AuthContext);
const [totalRowsCount, setTotalRowsCount] = useState(0);
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId,
);
const { mutate: mutateDeleteUser } = useDeleteUsers();
const { mutate: mutateUpdateUser } = useUpdateUser();
const { mutate: mutateAddUser } = useAddUser();
// set null id
useEffect(() => {
setCurrentFlowId("");
}, []);
const userList = useRef([]);
useEffect(() => {

View file

@ -1,7 +1,7 @@
import LoadingComponent from "@/components/loadingComponent";
import { useGetBuildsQuery } from "@/controllers/API/queries/_builds";
import useAutoSaveFlow from "@/hooks/flows/use-autosave-flow";
import useUploadFlow from "@/hooks/flows/use-upload-flow";
import { getInputsAndOutputs } from "@/utils/storeUtils";
import _, { cloneDeep } from "lodash";
import {
KeyboardEvent,
@ -36,10 +36,9 @@ import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { useShortcutsStore } from "../../../../stores/shortcuts";
import { useTypesStore } from "../../../../stores/typesStore";
import { APIClassType } from "../../../../types/api";
import { FlowType, NodeType } from "../../../../types/flow";
import { NodeType } from "../../../../types/flow";
import {
checkOldComponents,
cleanEdges,
generateFlow,
generateNodeFromFlow,
getNodeId,
@ -57,17 +56,9 @@ const nodeTypes = {
genericNode: GenericNode,
};
export default function Page({
flow,
view,
}: {
flow: FlowType;
view?: boolean;
}): JSX.Element {
export default function Page({ view }: { view?: boolean }): JSX.Element {
const uploadFlow = useUploadFlow();
const autoSaveCurrentFlow = useFlowsManagerStore(
(state) => state.autoSaveCurrentFlow,
);
const autoSaveFlow = useAutoSaveFlow();
const types = useTypesStore((state) => state.types);
const templates = useTypesStore((state) => state.templates);
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
@ -83,7 +74,6 @@ export default function Page({
const onEdgesChange = useFlowStore((state) => state.onEdgesChange);
const setNodes = useFlowStore((state) => state.setNodes);
const setEdges = useFlowStore((state) => state.setEdges);
const cleanFlow = useFlowStore((state) => state.cleanFlow);
const deleteNode = useFlowStore((state) => state.deleteNode);
const deleteEdge = useFlowStore((state) => state.deleteEdge);
const undo = useFlowsManagerStore((state) => state.undo);
@ -97,22 +87,17 @@ export default function Page({
(state) => state.setLastCopiedSelection,
);
const onConnect = useFlowStore((state) => state.onConnect);
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
const currentSavedFlow = useFlowsManagerStore((state) => state.currentFlow);
const setErrorData = useAlertStore((state) => state.setErrorData);
const setNoticeData = useAlertStore((state) => state.setNoticeData);
const updateCurrentFlow = useFlowStore((state) => state.updateCurrentFlow);
const [selectionMenuVisible, setSelectionMenuVisible] = useState(false);
const edgeUpdateSuccessful = useRef(true);
const position = useRef({ x: 0, y: 0 });
const [lastSelection, setLastSelection] =
useState<OnSelectionChangeParams | null>(null);
const setFlowState = useFlowStore((state) => state.setFlowState);
const setInputs = useFlowStore((state) => state.setInputs);
const setOutputs = useFlowStore((state) => state.setOutputs);
const setHasIO = useFlowStore((state) => state.setHasIO);
const { inputs, outputs } = getInputsAndOutputs(flow.data!.nodes);
const viewport = flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 };
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
function handleGroupNode() {
takeSnapshot();
@ -159,16 +144,6 @@ export default function Page({
};
}, [lastCopiedSelection, lastSelection, takeSnapshot, selectionMenuVisible]);
useEffect(() => {
if (reactFlowInstance && currentFlowId) {
if (viewport.x == 0 && viewport.y == 0) {
reactFlowInstance.fitView();
} else {
reactFlowInstance.setViewport(viewport);
}
}
}, [currentFlowId, reactFlowInstance]);
const { isFetching, refetch } = useGetBuildsQuery({});
const showCanvas =
@ -177,19 +152,9 @@ export default function Page({
!isFetching;
useEffect(() => {
if (!isFetching) {
let newEdges = cleanEdges(flow.data!.nodes, flow.data!.edges);
setNodes(flow.data!.nodes);
setEdges(newEdges);
setFlowState(undefined);
setInputs(inputs);
setOutputs(outputs);
setHasIO(inputs.length > 0 || outputs.length > 0);
}
}, [isFetching]);
useEffect(() => {
if (checkOldComponents({ nodes: flow?.data?.nodes ?? [] })) {
refetch();
useFlowStore.setState({ autoSaveFlow });
if (checkOldComponents({ nodes })) {
setNoticeData({
title:
"Components created before Langflow 1.0 may be unstable. Ensure components are up to date.",
@ -197,13 +162,6 @@ export default function Page({
}
}, [currentFlowId]);
useEffect(() => {
refetch();
return () => {
cleanFlow();
};
}, [currentFlowId]);
function handleUndo(e: KeyboardEvent) {
if (!isWrappedWithClass(e, "noflow")) {
e.preventDefault();
@ -338,15 +296,17 @@ export default function Page({
// 👉 you can place your event handlers here
}, [takeSnapshot]);
const onNodeDragStop: NodeDragHandler = useCallback(() => {
autoSaveCurrentFlow(nodes, edges, reactFlowInstance?.getViewport()!);
// 👉 you can place your event handlers here
}, [takeSnapshot, autoSaveCurrentFlow, nodes, edges, reactFlowInstance]);
const onMoveEnd: OnMove = useCallback(() => {
// 👇 make moving the canvas undoable
autoSaveCurrentFlow(nodes, edges, reactFlowInstance?.getViewport()!);
}, [takeSnapshot, autoSaveCurrentFlow, nodes, edges, reactFlowInstance]);
autoSaveFlow();
updateCurrentFlow({ viewport: reactFlowInstance?.getViewport() });
}, [takeSnapshot, autoSaveFlow, nodes, edges, reactFlowInstance]);
const onNodeDragStop: NodeDragHandler = useCallback(() => {
// 👇 make moving the canvas undoable
autoSaveFlow();
updateCurrentFlow({ nodes });
}, [takeSnapshot, autoSaveFlow, nodes, edges, reactFlowInstance]);
const onSelectionDragStart: SelectionDragHandler = useCallback(() => {
// 👇 make dragging a selection undoable
@ -497,13 +457,13 @@ export default function Page({
onEdgeUpdateStart={onEdgeUpdateStart}
onEdgeUpdateEnd={onEdgeUpdateEnd}
onNodeDragStart={onNodeDragStart}
onNodeDragStop={onNodeDragStop}
onSelectionDragStart={onSelectionDragStart}
onSelectionEnd={onSelectionEnd}
onSelectionStart={onSelectionStart}
connectionLineComponent={ConnectionLineComponent}
onDragOver={onDragOver}
onMoveEnd={onMoveEnd}
onNodeDragStop={onNodeDragStop}
onDrop={onDrop}
onSelectionChange={onSelectionChange}
deleteKeyCode={[]}

View file

@ -1,17 +1,13 @@
import { cloneDeep } from "lodash";
import { LinkIcon, SparklesIcon } from "lucide-react";
import { Fragment, useEffect, useMemo, useState } from "react";
import { Fragment, useEffect, useState } from "react";
import IconComponent from "../../../../components/genericIconComponent";
import ShadTooltip from "../../../../components/shadTooltipComponent";
import { Input } from "../../../../components/ui/input";
import { Separator } from "../../../../components/ui/separator";
import { PRIORITY_SIDEBAR_ORDER } from "../../../../constants/constants";
import ExportModal from "../../../../modals/exportModal";
import ShareModal from "../../../../modals/shareModal";
import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { useStoreStore } from "../../../../stores/storeStore";
import { useTypesStore } from "../../../../stores/typesStore";
import { APIClassType, APIObjectType } from "../../../../types/api";
import {
@ -19,7 +15,7 @@ import {
nodeIconsLucide,
nodeNames,
} from "../../../../utils/styleUtils";
import { classNames, removeCountFromString } from "../../../../utils/utils";
import { removeCountFromString } from "../../../../utils/utils";
import DisclosureComponent from "../DisclosureComponent";
import ParentDisclosureComponent from "../ParentDisclosureComponent";
import SidebarDraggableComponent from "./sideBarDraggableComponent";
@ -31,10 +27,6 @@ export default function ExtraSidebar(): JSX.Element {
const templates = useTypesStore((state) => state.templates);
const getFilterEdge = useFlowStore((state) => state.getFilterEdge);
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const hasStore = useStoreStore((state) => state.hasStore);
const hasApiKey = useStoreStore((state) => state.hasApiKey);
const validApiKey = useStoreStore((state) => state.validApiKey);
const setErrorData = useAlertStore((state) => state.setErrorData);
const [dataFilter, setFilterData] = useState(data);
@ -187,63 +179,6 @@ export default function ExtraSidebar(): JSX.Element {
}
}, [getFilterEdge, data]);
const ModalMemo = useMemo(
() => (
<ShareModal
is_component={false}
component={currentFlow!}
disabled={!hasApiKey || !validApiKey || !hasStore}
>
<button
disabled={!hasApiKey || !validApiKey || !hasStore}
className={classNames(
"extra-side-bar-buttons gap-[4px] text-sm font-semibold",
!hasApiKey || !validApiKey || !hasStore
? "button-disable cursor-default text-muted-foreground"
: "",
)}
>
<IconComponent
name="Share3"
className={classNames(
"-m-0.5 -ml-1 h-6 w-6",
!hasApiKey || !validApiKey || !hasStore
? "extra-side-bar-save-disable"
: "",
)}
/>
Share
</button>
</ShareModal>
),
[hasApiKey, validApiKey, currentFlow, hasStore],
);
const ExportMemo = useMemo(
() => (
<ExportModal>
<button className={classNames("extra-side-bar-buttons")}>
<IconComponent name="FileDown" className="side-bar-button-size" />
</button>
</ExportModal>
),
[],
);
const getIcon = useMemo(() => {
return (SBSectionName: string) => {
if (nodeIconsLucide[SBSectionName]) {
return (
<IconComponent
name={SBSectionName}
strokeWidth={1.5}
className="w-[22px] text-primary"
/>
);
}
};
}, []);
return (
<div className="side-bar-arrangement">
<div className="side-bar-search-div-placement">

View file

@ -72,7 +72,7 @@ export const SidebarDraggableComponent = forwardRef(
);
break;
case "delete":
const flowId = flows.find((f) => f.name === display_name);
const flowId = flows?.find((f) => f.name === display_name);
if (flowId) deleteFlow({ id: flowId.id });
break;
}

View file

@ -82,7 +82,7 @@ export default function NodeToolbarComponent({
const validApiKey = useStoreStore((state) => state.validApiKey);
const shortcuts = useShortcutsStore((state) => state.shortcuts);
const unselectAll = useFlowStore((state) => state.unselectAll);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
const addFlow = useAddFlow();
function handleMinimizeWShortcut(e: KeyboardEvent) {
@ -179,7 +179,7 @@ export default function NodeToolbarComponent({
function handleFreezeAll(e: KeyboardEvent) {
if (isWrappedWithClass(e, "noflow")) return;
e.preventDefault();
FreezeAllVertices({ flowId: currentFlow!.id, stopNodeId: data.id });
FreezeAllVertices({ flowId: currentFlowId, stopNodeId: data.id });
}
const advanced = useShortcutsStore((state) => state.advanced);
@ -280,7 +280,7 @@ export default function NodeToolbarComponent({
}));
break;
case "freezeAll":
FreezeAllVertices({ flowId: currentFlow!.id, stopNodeId: data.id });
FreezeAllVertices({ flowId: currentFlowId, stopNodeId: data.id });
break;
case "code":
setOpenModal(!openModal);
@ -355,7 +355,7 @@ export default function NodeToolbarComponent({
}
};
const isSaved = flows.some((flow) =>
const isSaved = flows?.some((flow) =>
Object.values(flow).includes(data.node?.display_name!),
);
@ -480,7 +480,7 @@ export default function NodeToolbarComponent({
event.preventDefault();
takeSnapshot();
FreezeAllVertices({
flowId: currentFlow!.id,
flowId: currentFlowId,
stopNodeId: data.id,
});
}}
@ -543,7 +543,8 @@ export default function NodeToolbarComponent({
<SelectItem value={"save"}>
<ToolbarSelectItem
shortcut={
shortcuts.find((obj) => obj.name === "Save")?.shortcut!
shortcuts.find((obj) => obj.name === "Save Component")
?.shortcut!
}
value={"Save"}
icon={"SaveAll"}
@ -712,7 +713,7 @@ export default function NodeToolbarComponent({
});
setSuccessData({ title: `${data.id} successfully overridden!` });
}}
onClose={setShowOverrideModal}
onClose={() => setShowOverrideModal(false)}
onCancel={() => {
addFlow({
flow: flowComponent,

View file

@ -1,7 +1,10 @@
import FeatureFlags from "@/../feature-config.json";
import { useGetGlobalVariables } from "@/controllers/API/queries/variables";
import useSaveFlow from "@/hooks/flows/use-save-flow";
import { SaveChangesModal } from "@/modals/saveChangesModal";
import { customStringify } from "@/utils/reactflowUtils";
import { useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useBlocker, useNavigate, useParams } from "react-router-dom";
import FlowToolbar from "../../components/chatComponent";
import Header from "../../components/headerComponent";
import { useDarkStore } from "../../stores/darkStore";
@ -11,34 +14,66 @@ import Page from "./components/PageComponent";
import ExtraSidebar from "./components/extraSidebarComponent";
export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId,
);
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
const currentFlow = useFlowStore((state) => state.currentFlow);
const currentSavedFlow = useFlowsManagerStore((state) => state.currentFlow);
const changesNotSaved =
customStringify(currentFlow) !== customStringify(currentSavedFlow);
const blocker = useBlocker(changesNotSaved);
const version = useDarkStore((state) => state.version);
const setOnFlowPage = useFlowStore((state) => state.setOnFlowPage);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const { id } = useParams();
const navigate = useNavigate();
useGetGlobalVariables();
const saveFlow = useSaveFlow();
const flows = useFlowsManagerStore((state) => state.flows);
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
const handleSave = () => {
saveFlow().then(() => (blocker.proceed ? blocker.proceed() : null));
};
useEffect(() => {
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
if (changesNotSaved) {
event.preventDefault();
event.returnValue = ""; // Required for Chrome
}
};
window.addEventListener("beforeunload", handleBeforeUnload);
return () => {
window.removeEventListener("beforeunload", handleBeforeUnload);
};
}, [changesNotSaved, navigate]);
// Set flow tab id
useEffect(() => {
const isAnExistingFlow = flows.some((flow) => flow.id === id);
if (flows && currentFlowId === "") {
const isAnExistingFlow = flows.find((flow) => flow.id === id);
if (!isAnExistingFlow) {
navigate("/all");
return;
if (!isAnExistingFlow) {
navigate("/all");
return;
}
setCurrentFlow(isAnExistingFlow);
}
}, [id, flows]);
setCurrentFlowId(id!);
useEffect(() => {
setOnFlowPage(true);
return () => {
setOnFlowPage(false);
setCurrentFlow();
};
}, [id]);
return (
<>
<Header />
@ -49,7 +84,7 @@ export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
<main className="flex flex-1">
{/* Primary column */}
<div className="h-full w-full">
<Page flow={currentFlow} />
<Page />
</div>
{!view && <FlowToolbar />}
</main>
@ -66,6 +101,13 @@ export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
<div className={version ? "mt-2" : "mt-1"}> v{version}</div>
</a>
</div>
{blocker.state === "blocked" && (
<SaveChangesModal
onSave={handleSave}
onCancel={() => (blocker.reset ? blocker.reset() : null)}
onProceed={() => (blocker.proceed ? blocker.proceed() : null)}
/>
)}
</>
);
}

View file

@ -89,7 +89,7 @@ export default function ComponentsComponent({
handleSelectAll(false);
setShouldSelectAll(true);
getFolderById(folderId ? folderId : myCollectionId);
}, [location]);
}, [location, folderId, myCollectionId]);
useFilteredFlows(flowsFromFolder!, searchFlowsComponents, setAllFlows);

View file

@ -1,6 +1,6 @@
import { useDeleteFolders } from "@/controllers/API/queries/folders";
import useAlertStore from "@/stores/alertStore";
import { useEffect, useState } from "react";
import { useState } from "react";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
import DropdownButton from "../../../../components/dropdownButtonComponent";
import PageLayout from "../../../../components/pageLayout";
@ -9,17 +9,12 @@ import {
MY_COLLECTION_DESC,
USER_PROJECTS_HEADER,
} from "../../../../constants/constants";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { useFolderStore } from "../../../../stores/foldersStore";
import ModalsComponent from "../../components/modalsComponent";
import useDropdownOptions from "../../hooks/use-dropdown-options";
import { getFolderById } from "../../services";
export default function HomePage(): JSX.Element {
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId,
);
const location = useLocation();
const pathname = location.pathname;
const [openModal, setOpenModal] = useState(false);
@ -35,10 +30,6 @@ export default function HomePage(): JSX.Element {
const myCollectionId = useFolderStore((state) => state.myCollectionId);
const getFoldersApi = useFolderStore((state) => state.getFoldersApi);
useEffect(() => {
setCurrentFlowId("");
}, [pathname]);
const dropdownOptions = useDropdownOptions({
navigate,
is_component,

View file

@ -1,26 +1,20 @@
import { useEffect, useState } from "react";
import { useStoreStore } from "@/stores/storeStore";
import { useEffect } from "react";
import { useParams } from "react-router-dom";
import LoadingComponent from "../../components/loadingComponent";
import { getComponent } from "../../controllers/API";
import IOModal from "../../modals/IOModal";
import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import cloneFLowWithParent from "../../utils/storeUtils";
export default function PlaygroundPage() {
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const getFlowById = useFlowsManagerStore((state) => state.getFlowById);
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId,
);
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
const flows = useFlowsManagerStore((state) => state.flows);
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
const setNodes = useFlowStore((state) => state.setNodes);
const setEdges = useFlowStore((state) => state.setEdges);
const cleanFlowPool = useFlowStore((state) => state.CleanFlowPool);
const currentSavedFlow = useFlowsManagerStore((state) => state.currentFlow);
const validApiKey = useStoreStore((state) => state.validApiKey);
const { id } = useParams();
const [loading, setLoading] = useState(true);
async function getFlowData() {
const res = await getComponent(id!);
const newFlow = cloneFLowWithParent(res, res.id, false, true);
@ -29,32 +23,22 @@ export default function PlaygroundPage() {
// Set flow tab id
useEffect(() => {
if (getFlowById(id!)) {
setCurrentFlowId(id!);
} else {
getFlowData().then((flow) => {
if (flows) {
const flow = getFlowById(id!);
if (flow) {
setCurrentFlow(flow);
});
} else {
if (validApiKey)
getFlowData().then((flow) => {
setCurrentFlow(flow);
});
}
}
}, [id]);
useEffect(() => {
if (currentFlow) {
setNodes(currentFlow?.data?.nodes ?? [], true);
setEdges(currentFlow?.data?.edges ?? [], true);
cleanFlowPool();
setLoading(false);
}
return () => {
setNodes([], true);
setEdges([], true);
cleanFlowPool();
};
}, [currentFlow]);
}, [id, flows, validApiKey]);
return (
<div className="flex h-full w-full flex-col items-center justify-center align-middle">
{loading ? (
{!currentSavedFlow ? (
<div>
<LoadingComponent remSize={24}></LoadingComponent>
</div>

View file

@ -4,7 +4,7 @@ import {
} from "@/controllers/API/queries/auth";
import * as Form from "@radix-ui/react-form";
import { cloneDeep } from "lodash";
import { useContext, useEffect, useState } from "react";
import { useContext, useState } from "react";
import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent";
import InputComponent from "../../components/inputComponent";
@ -18,7 +18,6 @@ import {
import { CONTROL_PATCH_USER_STATE } from "../../constants/constants";
import { AuthContext } from "../../contexts/authContext";
import useAlertStore from "../../stores/alertStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import {
inputHandlerEventType,
patchUserInputStateType,
@ -26,18 +25,9 @@ import {
import { gradients } from "../../utils/styleUtils";
import GradientChooserComponent from "../SettingsPage/pages/GeneralPage/components/ProfilePictureForm/components/profilePictureChooserComponent";
export default function ProfileSettingsPage(): JSX.Element {
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId,
);
const [inputState, setInputState] = useState<patchUserInputStateType>(
CONTROL_PATCH_USER_STATE,
);
// set null id
useEffect(() => {
setCurrentFlowId("");
}, []);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const { userData, setUserData } = useContext(AuthContext);

View file

@ -1,19 +1,12 @@
import FeatureFlags from "@/../feature-config.json";
import useAuthStore from "@/stores/authStore";
import { useStoreStore } from "@/stores/storeStore";
import { useEffect } from "react";
import { Outlet } from "react-router-dom";
import ForwardedIconComponent from "../../components/genericIconComponent";
import PageLayout from "../../components/pageLayout";
import SidebarNav from "../../components/sidebarComponent";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
export default function SettingsPage(): JSX.Element {
const pathname = location.pathname;
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId,
);
const autoLogin = useAuthStore((state) => state.autoLogin);
const hasStore = useStoreStore((state) => state.hasStore);
@ -21,10 +14,6 @@ export default function SettingsPage(): JSX.Element {
const showGeneralSettings =
FeatureFlags.ENABLE_PROFILE_ICONS || hasStore || !autoLogin;
useEffect(() => {
setCurrentFlowId("");
}, [pathname]);
const sidebarNavItems: {
href?: string;
title: string;

View file

@ -18,7 +18,6 @@ import { useParams } from "react-router-dom";
import { CONTROL_PATCH_USER_STATE } from "../../../../constants/constants";
import { AuthContext } from "../../../../contexts/authContext";
import useAlertStore from "../../../../stores/alertStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { useStoreStore } from "../../../../stores/storeStore";
import {
inputHandlerEventType,
@ -31,10 +30,6 @@ import ProfilePictureFormComponent from "./components/ProfilePictureForm";
import StoreApiKeyFormComponent from "./components/StoreApiKeyForm";
export const GeneralPage = () => {
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId,
);
const { scrollId } = useParams();
const [inputState, setInputState] = useState<patchUserInputStateType>(
@ -112,7 +107,7 @@ export const GeneralPage = () => {
}
};
useScrollToElement(scrollId, setCurrentFlowId);
useScrollToElement(scrollId);
const { mutate } = usePostAddApiKey({
onSuccess: () => {

View file

@ -1,9 +1,6 @@
import { useEffect } from "react";
const useScrollToElement = (
scrollId: string | null | undefined,
setCurrentFlowId: (currentFlowId: string) => void,
) => {
const useScrollToElement = (scrollId: string | null | undefined) => {
useEffect(() => {
const element = document.getElementById(scrollId ?? "null");
if (element) {
@ -11,10 +8,6 @@ const useScrollToElement = (
element.scrollIntoView({ behavior: "smooth" });
}
}, [scrollId]);
useEffect(() => {
setCurrentFlowId("");
}, [setCurrentFlowId]);
};
export default useScrollToElement;

View file

@ -28,7 +28,7 @@ import {
} from "../../constants/alerts_constants";
import { STORE_DESC, STORE_TITLE } from "../../constants/constants";
import { AuthContext } from "../../contexts/authContext";
import { getStoreComponents, getStoreTags } from "../../controllers/API";
import { getStoreComponents } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { useStoreStore } from "../../stores/storeStore";
@ -46,9 +46,6 @@ export default function StorePage(): JSX.Element {
const { apiKey } = useContext(AuthContext);
const setErrorData = useAlertStore((state) => state.setErrorData);
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId,
);
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
const [loading, setLoading] = useState(true);
const { id } = useParams();
@ -148,11 +145,6 @@ export default function StorePage(): JSX.Element {
});
}
// Set a null id
useEffect(() => {
setCurrentFlowId("");
}, []);
function resetPagination() {
setPageIndex(1);
setPageSize(12);

View file

@ -1,23 +1,47 @@
import { useGetGlobalVariables } from "@/controllers/API/queries/variables";
import useFlowStore from "@/stores/flowStore";
import { useEffect } from "react";
import { useParams } from "react-router-dom";
import { useNavigate, useParams } from "react-router-dom";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import Page from "../FlowPage/components/PageComponent";
export default function ViewPage() {
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId,
);
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
const setOnFlowPage = useFlowStore((state) => state.setOnFlowPage);
const { id } = useParams();
const navigate = useNavigate();
useGetGlobalVariables();
const flows = useFlowsManagerStore((state) => state.flows);
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
// Set flow tab id
useEffect(() => {
setCurrentFlowId(id!);
if (flows && currentFlowId === "") {
const isAnExistingFlow = flows.find((flow) => flow.id === id);
if (!isAnExistingFlow) {
navigate("/all");
return;
}
setCurrentFlow(isAnExistingFlow);
}
}, [id, flows]);
useEffect(() => {
setOnFlowPage(true);
return () => {
setOnFlowPage(false);
setCurrentFlow();
};
}, [id]);
return (
<div className="flow-page-positioning">
{currentFlow && <Page view flow={currentFlow} />}
<Page view />
</div>
);
}

View file

@ -1,16 +1,20 @@
import FeatureFlags from "@/../feature-config.json";
import useAuthStore from "@/stores/authStore";
import { useStoreStore } from "@/stores/storeStore";
import { Suspense, lazy } from "react";
import { Navigate, Route, Routes } from "react-router-dom";
import { lazy } from "react";
import {
createBrowserRouter,
createRoutesFromElements,
Navigate,
Route,
} from "react-router-dom";
import App from "./App";
import { ProtectedAdminRoute } from "./components/authAdminGuard";
import { ProtectedRoute } from "./components/authGuard";
import { ProtectedLoginRoute } from "./components/authLoginGuard";
import { AuthSettingsGuard } from "./components/authSettingsGuard";
import { CatchAllRoute } from "./components/catchAllRoutes";
import LoadingComponent from "./components/loadingComponent";
import { StoreGuard } from "./components/storeGuard";
import MessagesPage from "./pages/SettingsPage/pages/messagesPage";
const MessagesPage = lazy(
() => import("./pages/SettingsPage/pages/messagesPage"),
);
const AdminPage = lazy(() => import("./pages/AdminPage"));
const LoginAdminPage = lazy(() => import("./pages/AdminPage/LoginPage"));
const ApiKeysPage = lazy(
@ -38,184 +42,161 @@ const SignUp = lazy(() => import("./pages/SignUpPage"));
const StorePage = lazy(() => import("./pages/StorePage"));
const ViewPage = lazy(() => import("./pages/ViewPage"));
const Router = () => {
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;
return (
<Suspense
fallback={
<div className="loading-page-panel">
<LoadingComponent remSize={50} />
</div>
}
>
<Routes>
const router = createBrowserRouter(
createRoutesFromElements([
<Route path="" element={<App />}>
<Route
path="/"
element={
<ProtectedRoute>
<HomePage />
</ProtectedRoute>
}
>
<Route index element={<Navigate replace to={"all"} />} />
<Route
path="/"
path="flows/*"
element={<MyCollectionComponent key="flows" type="flow" />}
/>
<Route
path="components/*"
element={<MyCollectionComponent key="components" type="component" />}
/>
<Route
path="all/*"
element={<MyCollectionComponent key="all" type="all" />}
/>
</Route>
<Route
path="/settings"
element={
<ProtectedRoute>
<SettingsPage />
</ProtectedRoute>
}
>
<Route index element={<Navigate replace to={"general"} />} />
<Route path="global-variables" element={<GlobalVariablesPage />} />
<Route path="api-keys" element={<ApiKeysPage />} />
<Route
path="general/:scrollId?"
element={
<ProtectedRoute>
<HomePage />
</ProtectedRoute>
<AuthSettingsGuard>
<GeneralPage />
</AuthSettingsGuard>
}
>
<Route index element={<Navigate replace to={"all"} />} />
<Route
path="flows/*"
element={<MyCollectionComponent key="flows" type="flow" />}
/>
<Route
path="components/*"
element={
<MyCollectionComponent key="components" type="component" />
}
/>
<Route
path="all/*"
element={<MyCollectionComponent key="all" type="all" />}
/>
</Route>
/>
<Route path="shortcuts" element={<ShortcutsPage />} />
<Route path="messages" element={<MessagesPage />} />
</Route>
<Route
path="/store"
element={
<ProtectedRoute>
<StoreGuard>
<StorePage />
</StoreGuard>
</ProtectedRoute>
}
/>
<Route
path="/store/:id/"
element={
<ProtectedRoute>
<StoreGuard>
<StorePage />
</StoreGuard>
</ProtectedRoute>
}
/>
<Route path="/playground/:id/">
<Route
path="/settings"
path=""
element={
<ProtectedRoute>
<SettingsPage />
</ProtectedRoute>
}
>
<Route
index
element={
<Navigate
replace
to={showGeneralSettings ? "general" : "global-variables"}
/>
}
/>
<Route path="global-variables" element={<GlobalVariablesPage />} />
<Route path="api-keys" element={<ApiKeysPage />} />
{showGeneralSettings && (
<Route path="general/:scrollId?" element={<GeneralPage />} />
)}
<Route path="shortcuts" element={<ShortcutsPage />} />
<Route path="messages" element={<MessagesPage />} />
</Route>
<Route
path="/store"
element={
<ProtectedRoute>
<StoreGuard>
<StorePage />
</StoreGuard>
<PlaygroundPage />
</ProtectedRoute>
}
/>
<Route
path="/store/:id/"
element={
<ProtectedRoute>
<StoreGuard>
<StorePage />
</StoreGuard>
</ProtectedRoute>
}
/>
<Route path="/playground/:id/">
<Route
path=""
element={
<ProtectedRoute>
<PlaygroundPage />
</ProtectedRoute>
}
/>
</Route>
<Route path="/flow/:id/">
<Route
path="*"
element={
<ProtectedRoute>
<FlowPage />
</ProtectedRoute>
}
/>
<Route
path=""
element={
<ProtectedRoute>
<FlowPage />
</ProtectedRoute>
}
/>
<Route
path="view"
element={
<ProtectedRoute>
<ViewPage />
</ProtectedRoute>
}
/>
</Route>
</Route>
<Route path="/flow/:id/">
<Route
path="*"
element={
<ProtectedRoute>
<CatchAllRoute />
<FlowPage />
</ProtectedRoute>
}
/>
<Route
path="/login"
path=""
element={
<ProtectedLoginRoute>
<LoginPage />
</ProtectedLoginRoute>
<ProtectedRoute>
<FlowPage />
</ProtectedRoute>
}
/>
<Route
path="/signup"
path="view"
element={
<ProtectedLoginRoute>
<SignUp />
</ProtectedLoginRoute>
<ProtectedRoute>
<ViewPage />
</ProtectedRoute>
}
/>
</Route>
<Route
path="*"
element={
<ProtectedRoute>
<CatchAllRoute />
</ProtectedRoute>
}
/>
<Route
path="/login"
element={
<ProtectedLoginRoute>
<LoginPage />
</ProtectedLoginRoute>
}
/>
<Route
path="/signup"
element={
<ProtectedLoginRoute>
<SignUp />
</ProtectedLoginRoute>
}
/>
<Route
path="/login/admin"
element={
<ProtectedLoginRoute>
<LoginAdminPage />
</ProtectedLoginRoute>
}
/>
<Route
path="/admin"
element={
<ProtectedAdminRoute>
<AdminPage />
</ProtectedAdminRoute>
}
/>
<Route path="/account">
<Route
path="/login/admin"
path="delete"
element={
<ProtectedLoginRoute>
<LoginAdminPage />
</ProtectedLoginRoute>
<ProtectedRoute>
<DeleteAccountPage />
</ProtectedRoute>
}
/>
></Route>
</Route>
</Route>,
]),
);
<Route
path="/admin"
element={
<ProtectedAdminRoute>
<AdminPage />
</ProtectedAdminRoute>
}
/>
<Route path="/account">
<Route
path="delete"
element={
<ProtectedRoute>
<DeleteAccountPage />
</ProtectedRoute>
}
></Route>
</Route>
</Routes>
</Suspense>
);
};
export default Router;
export default router;

View file

@ -1,5 +1,5 @@
import { create } from "zustand";
import { getRepoStars, getVersion } from "../controllers/API";
import { getRepoStars } from "../controllers/API";
import { DarkStoreType } from "../types/zustand/dark";
const startedStars = Number(window.localStorage.getItem("githubStars")) ?? 0;

View file

@ -19,7 +19,6 @@ import {
MISSED_ERROR_ALERT,
} from "../constants/alerts_constants";
import { BuildStatus } from "../constants/enums";
import { getFlowPool } from "../controllers/API";
import { VertexBuildTypeAPI } from "../types/api";
import { ChatInputType, ChatOutputType } from "../types/chat";
import {
@ -51,6 +50,7 @@ import { useTypesStore } from "./typesStore";
// this is our useStore hook that we can use in our components to get parts of the store and call actions
const useFlowStore = create<FlowStoreType>((set, get) => ({
autoSaveFlow: undefined,
componentsToUpdate: false,
updateComponentsToUpdate: (nodes) => {
let outdatedNodes = false;
@ -153,8 +153,10 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
setPending: (isPending) => {
set({ isPending });
},
resetFlow: ({ nodes, edges, viewport }) => {
const currentFlow = useFlowsManagerStore.getState().currentFlow;
resetFlow: (flow) => {
const nodes = flow?.data?.nodes ?? [];
const edges = flow?.data?.edges ?? [];
const viewport = flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 };
let brokenEdges = detectBrokenEdgesEdges(nodes, edges);
if (brokenEdges.length > 0) {
useAlertStore.getState().setErrorData({
@ -172,13 +174,10 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
inputs,
outputs,
hasIO: inputs.length > 0 || outputs.length > 0,
flowPool: {},
currentFlow: flow,
});
get().reactFlowInstance!.setViewport(viewport);
if (currentFlow) {
getFlowPool({ flowId: currentFlow.id }).then((flowPool) => {
set({ flowPool: flowPool.data.vertex_builds });
});
}
get().reactFlowInstance?.setViewport(viewport);
},
setIsBuilding: (isBuilding) => {
set({ isBuilding });
@ -195,6 +194,16 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
},
setReactFlowInstance: (newState) => {
set({ reactFlowInstance: newState });
const viewport = get().currentFlow?.data?.viewport ?? {
zoom: 1,
x: 0,
y: 0,
};
if (viewport.x == 0 && viewport.y == 0) {
newState.fitView();
} else {
newState.setViewport(viewport);
}
},
onNodesChange: (changes: NodeChange[]) => {
set({
@ -206,7 +215,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
edges: applyEdgeChanges(changes, get().edges),
});
},
setNodes: (change, skipSave = false) => {
setNodes: (change) => {
let newChange = typeof change === "function" ? change(get().nodes) : change;
let newEdges = cleanEdges(newChange, get().edges);
const { inputs, outputs } = getInputsAndOutputs(newChange);
@ -219,30 +228,18 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
outputs,
hasIO: inputs.length > 0 || outputs.length > 0,
});
const flowsManager = useFlowsManagerStore.getState();
if (!get().isBuilding && !skipSave && get().onFlowPage) {
flowsManager.autoSaveCurrentFlow(
newChange,
newEdges,
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 },
);
if (get().autoSaveFlow) {
get().autoSaveFlow!();
}
},
setEdges: (change, skipSave = false) => {
setEdges: (change) => {
let newChange = typeof change === "function" ? change(get().edges) : change;
set({
edges: newChange,
flowState: undefined,
});
const flowsManager = useFlowsManagerStore.getState();
if (!get().isBuilding && !skipSave && get().onFlowPage) {
flowsManager.autoSaveCurrentFlow(
get().nodes,
newChange,
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 },
);
if (get().autoSaveFlow) {
get().autoSaveFlow!();
}
},
setNode: (
@ -485,13 +482,6 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
return newEdges;
});
useFlowsManagerStore
.getState()
.autoSaveCurrentFlow(
get().nodes,
newEdges,
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 },
);
},
unselectAll: () => {
let newNodes = cloneDeep(get().nodes);
@ -672,8 +662,9 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
useFlowStore.getState().updateBuildStatus(idList, BuildStatus.BUILDING);
},
onValidateNodes: validateSubgraph,
nodes: get().onFlowPage ? get().nodes : undefined,
edges: get().onFlowPage ? get().edges : undefined,
nodes: get().nodes || undefined,
edges: get().edges || undefined,
logBuilds: get().onFlowPage,
});
get().setIsBuilding(false);
get().setLockChat(false);
@ -744,6 +735,27 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
});
set({ flowBuildStatus: newFlowBuildStatus });
},
currentFlow: undefined,
setCurrentFlow: (flow) => {
set({ currentFlow: flow });
},
updateCurrentFlow: ({ nodes, edges, viewport }) => {
set({
currentFlow: {
...get().currentFlow!,
data: {
nodes: nodes ?? get().currentFlow?.data?.nodes ?? [],
edges: edges ?? get().currentFlow?.data?.edges ?? [],
viewport: viewport ??
get().currentFlow?.data?.viewport ?? {
x: 0,
y: 0,
zoom: 1,
},
},
},
});
},
}));
export default useFlowStore;

View file

@ -1,13 +1,6 @@
import { AxiosError } from "axios";
import { cloneDeep } from "lodash";
import pDebounce from "p-debounce";
import { Edge, Node, Viewport } from "reactflow";
import { create } from "zustand";
import { SAVE_DEBOUNCE_TIME } from "../constants/constants";
import {
readFlowsFromDatabase,
updateFlowInDatabase,
} from "../controllers/API";
import { readFlowsFromDatabase } from "../controllers/API";
import { FlowType } from "../types/flow";
import {
FlowsManagerStoreType,
@ -36,22 +29,17 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
set({ examples });
},
currentFlowId: "",
setCurrentFlow: (flow: FlowType) => {
set((state) => ({
setCurrentFlow: (flow: FlowType | undefined) => {
set({
currentFlow: flow,
currentFlowId: flow.id,
}));
currentFlowId: flow?.id ?? "",
});
useFlowStore.getState().resetFlow(flow);
},
getFlowById: (id: string) => {
return get().flows.find((flow) => flow.id === id);
return get().flows?.find((flow) => flow.id === id);
},
setCurrentFlowId: (currentFlowId: string) => {
set((state) => ({
currentFlowId,
currentFlow: state.flows.find((flow) => flow.id === currentFlowId),
}));
},
flows: [],
flows: undefined,
allFlows: [],
setAllFlows: (allFlows: FlowType[]) => {
set({ allFlows });
@ -107,60 +95,6 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
});
});
},
autoSaveCurrentFlow: (nodes: Node[], edges: Edge[], viewport: Viewport) => {
if (get().currentFlow) {
get().saveFlow(
{ ...get().currentFlow!, data: { nodes, edges, viewport } },
true,
);
}
},
saveFlow: (flow: FlowType, silent?: boolean) => {
set({ saveLoading: true }); // set saveLoading true immediately
return get().saveFlowDebounce(flow, silent); // call the debounced function directly
},
saveFlowDebounce: pDebounce((flow: FlowType, silent?: boolean) => {
const folderUrl = useFolderStore.getState().folderUrl;
const hasFolderUrl = folderUrl != null && folderUrl !== "";
flow.folder_id = hasFolderUrl
? useFolderStore.getState().folderUrl
: useFolderStore.getState().myCollectionId ?? "";
set({ saveLoading: true });
return new Promise<void>((resolve, reject) => {
updateFlowInDatabase(flow)
.then((updatedFlow) => {
if (updatedFlow) {
// updates flow in state
if (!silent) {
useAlertStore
.getState()
.setSuccessData({ title: "Changes saved successfully" });
}
get().setFlows(
get().flows.map((flow) => {
if (flow.id === updatedFlow.id) {
return updatedFlow;
}
return flow;
}),
);
//update tabs state
resolve();
set({ saveLoading: false });
}
})
.catch((err) => {
useAlertStore.getState().setErrorData({
title: "Error while saving changes",
list: [(err as AxiosError).message],
});
reject(err);
});
});
}, SAVE_DEBOUNCE_TIME),
takeSnapshot: () => {
const currentFlowId = get().currentFlowId;
// push the current graph to the past state

View file

@ -20,7 +20,8 @@ export const useShortcutsStore = create<shortcutsStoreType>((set, get) => ({
duplicate: "mod+d",
component: "mod+shift+s",
docs: "mod+shift+d",
save: "mod+s",
changes: "mod+s",
save: "mod+alt+s",
delete: "backspace",
group: "mod+g",
cut: "mod+x",

View file

@ -435,6 +435,7 @@ export type ConfirmationModalType = {
title: string;
titleHeader?: string;
destructive?: boolean;
destructiveCancel?: boolean;
modalContentTitle?: string;
cancelText: string;
confirmationText: string;
@ -446,7 +447,7 @@ export type ConfirmationModalType = {
index?: number;
onConfirm: (index, data) => void;
open?: boolean;
onClose?: (close: boolean) => void;
onClose?: () => void;
size?:
| "x-small"
| "smaller"

View file

@ -38,6 +38,7 @@ export type shortcutsStoreType = {
duplicate: string;
component: string;
docs: string;
changes: string;
save: string;
delete: string;
update: string;

View file

@ -1,3 +1,4 @@
import { FlowType } from "@/types/flow";
import {
Connection,
Edge,
@ -53,6 +54,7 @@ export type FlowPoolType = {
};
export type FlowStoreType = {
autoSaveFlow: (() => void) | undefined;
componentsToUpdate: boolean;
updateComponentsToUpdate: (nodes: Node[]) => void;
onFlowPage: boolean;
@ -83,11 +85,7 @@ export type FlowStoreType = {
isPending: boolean;
setIsBuilding: (isBuilding: boolean) => void;
setPending: (isPending: boolean) => void;
resetFlow: (flow: {
nodes: Node[];
edges: Edge[];
viewport: Viewport;
}) => void;
resetFlow: (flow: FlowType | undefined) => void;
reactFlowInstance: ReactFlowInstance | null;
setReactFlowInstance: (newState: ReactFlowInstance) => void;
flowState: FlowState | undefined;
@ -101,14 +99,8 @@ export type FlowStoreType = {
edges: Edge[];
onNodesChange: OnNodesChange;
onEdgesChange: OnEdgesChange;
setNodes: (
update: Node[] | ((oldState: Node[]) => Node[]),
skipSave?: boolean,
) => void;
setEdges: (
update: Edge[] | ((oldState: Edge[]) => Edge[]),
skipSave?: boolean,
) => void;
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void;
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void;
setNode: (
id: string,
update: Node | ((oldState: Node) => Node),
@ -177,4 +169,15 @@ export type FlowStoreType = {
setLockChat: (lock: boolean) => void;
lockChat: boolean;
updateFreezeStatus: (nodeIds: string[], freeze: boolean) => void;
currentFlow: FlowType | undefined;
setCurrentFlow: (flow: FlowType | undefined) => void;
updateCurrentFlow: ({
nodes,
edges,
viewport,
}: {
nodes?: Node[];
edges?: Edge[];
viewport?: Viewport;
}) => void;
};

View file

@ -1,41 +1,24 @@
import { Edge, Node, Viewport } from "reactflow";
import { FlowType } from "../../flow";
export type FlowsManagerStoreType = {
getFlowById: (id: string) => FlowType | undefined;
flows: Array<FlowType>;
flows: Array<FlowType> | undefined;
allFlows: Array<FlowType>;
setAllFlows: (flows: FlowType[]) => void;
setFlows: (flows: FlowType[]) => void;
currentFlow: FlowType | undefined;
currentFlowId: string;
setCurrentFlowId: (currentFlowId: string) => void;
saveLoading: boolean;
setSaveLoading: (saveLoading: boolean) => void;
isLoading: boolean;
setIsLoading: (isLoading: boolean) => void;
refreshFlows: () => Promise<void>;
saveFlow: (
flow: FlowType,
silent?: boolean,
folderId?: string,
) => Promise<void> | undefined;
saveFlowDebounce: (
flow: FlowType,
silent?: boolean,
folderId?: string,
) => Promise<void> | undefined;
autoSaveCurrentFlow: (
nodes: Node[],
edges: Edge[],
viewport: Viewport,
) => void;
undo: () => void;
redo: () => void;
takeSnapshot: () => void;
examples: Array<FlowType>;
setExamples: (examples: FlowType[]) => void;
setCurrentFlow: (flow: FlowType) => void;
setCurrentFlow: (flow?: FlowType) => void;
setSearchFlowsComponents: (search: string) => void;
searchFlowsComponents: string;
selectedFlowsComponentsCards: string[];

View file

@ -30,6 +30,7 @@ type BuildVerticesParams = {
onValidateNodes?: (nodes: string[]) => void;
nodes?: Node[];
edges?: Edge[];
logBuilds?: boolean;
};
function getInactiveVertexData(vertexId: string): VertexBuildTypeAPI {
@ -146,6 +147,7 @@ export async function buildFlowVertices({
onValidateNodes,
nodes,
edges,
logBuilds,
setLockChat,
}: BuildVerticesParams) {
let url = `${BASE_URL_API}build/${flowId}/flow?`;
@ -155,6 +157,9 @@ export async function buildFlowVertices({
if (stopNodeId) {
url = `${url}&stop_component_id=${stopNodeId}`;
}
if (logBuilds !== undefined) {
url = `${url}&log_builds=${logBuilds}`;
}
const postData = {};
if (typeof input_value !== "undefined") {
postData["inputs"] = { input_value: input_value };

View file

@ -47,10 +47,6 @@ test("flowSettings", async ({ page }) => {
await page.getByTestId("save-flow-settings").click();
await page.getByText("Close").last().click();
await page.waitForTimeout(1000);
await page.getByText("Changes saved successfully").isVisible();
await page.getByTestId("flow_name").click();

View file

@ -1,4 +1,4 @@
import { expect, test } from "@playwright/test";
import { test } from "@playwright/test";
import * as dotenv from "dotenv";
import path from "path";
@ -111,7 +111,6 @@ test("should share component with share button", async ({ page }) => {
.inputValue();
await page.getByPlaceholder("Flow name").fill(randomName);
await page.getByText("Save").last().click();
await page.getByText("Close").last().click();
await page.waitForSelector('[data-testid="shared-button-flow"]', {
timeout: 100000,

View file

@ -31,6 +31,9 @@ export default defineConfig(({ mode }) => {
outDir: "build",
},
define: {
"process.env.LANGFLOW_AUTO_SAVE": JSON.stringify(
process.env.LANGFLOW_AUTO_SAVE,
),
"process.env.BACKEND_URL": JSON.stringify(process.env.BACKEND_URL),
"process.env.ACCESS_TOKEN_EXPIRE_SECONDS": JSON.stringify(
process.env.ACCESS_TOKEN_EXPIRE_SECONDS,