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:
parent
7a93a708ee
commit
7264028e41
62 changed files with 734 additions and 790 deletions
1
src/frontend/package-lock.json
generated
1
src/frontend/package-lock.json
generated
|
|
@ -1079,7 +1079,6 @@
|
||||||
},
|
},
|
||||||
"node_modules/@clack/prompts/node_modules/is-unicode-supported": {
|
"node_modules/@clack/prompts/node_modules/is-unicode-supported": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"extraneous": true,
|
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useContext, useEffect } from "react";
|
import { useContext, useEffect } from "react";
|
||||||
import { Cookies } from "react-cookie";
|
import { Cookies } from "react-cookie";
|
||||||
import { ErrorBoundary } from "react-error-boundary";
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
|
import { Outlet } from "react-router-dom";
|
||||||
import "reactflow/dist/style.css";
|
import "reactflow/dist/style.css";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import AlertDisplayArea from "./alerts/displayArea";
|
import AlertDisplayArea from "./alerts/displayArea";
|
||||||
|
|
@ -22,7 +23,6 @@ import { useGetHealthQuery } from "./controllers/API/queries/health";
|
||||||
import { useGetVersionQuery } from "./controllers/API/queries/version";
|
import { useGetVersionQuery } from "./controllers/API/queries/version";
|
||||||
import { setupAxiosDefaults } from "./controllers/API/utils";
|
import { setupAxiosDefaults } from "./controllers/API/utils";
|
||||||
import useTrackLastVisitedPath from "./hooks/use-track-last-visited-path";
|
import useTrackLastVisitedPath from "./hooks/use-track-last-visited-path";
|
||||||
import Router from "./routes";
|
|
||||||
import useAlertStore from "./stores/alertStore";
|
import useAlertStore from "./stores/alertStore";
|
||||||
import useAuthStore from "./stores/authStore";
|
import useAuthStore from "./stores/authStore";
|
||||||
import { useDarkStore } from "./stores/darkStore";
|
import { useDarkStore } from "./stores/darkStore";
|
||||||
|
|
@ -172,8 +172,7 @@ export default function App() {
|
||||||
>
|
>
|
||||||
<LoadingComponent remSize={50} />
|
<LoadingComponent remSize={50} />
|
||||||
</div>
|
</div>
|
||||||
|
<Outlet />
|
||||||
<Router />
|
|
||||||
</>
|
</>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
<div></div>
|
<div></div>
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,5 @@
|
||||||
import { cloneDeep } from "lodash";
|
|
||||||
import { useUpdateNodeInternals } from "reactflow";
|
|
||||||
import ForwardedIconComponent from "../../../../components/genericIconComponent";
|
|
||||||
import ShadTooltip from "../../../../components/shadTooltipComponent";
|
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 { outputComponentType } from "../../../../types/components";
|
||||||
import { NodeDataType } from "../../../../types/flow";
|
|
||||||
import { cn } from "../../../../utils/utils";
|
import { cn } from "../../../../utils/utils";
|
||||||
|
|
||||||
export default function OutputComponent({
|
export default function OutputComponent({
|
||||||
|
|
@ -23,9 +11,6 @@ export default function OutputComponent({
|
||||||
name,
|
name,
|
||||||
proxy,
|
proxy,
|
||||||
}: outputComponentType) {
|
}: outputComponentType) {
|
||||||
const setNode = useFlowStore((state) => state.setNode);
|
|
||||||
const updateNodeInternals = useUpdateNodeInternals();
|
|
||||||
|
|
||||||
const displayProxy = (children) => {
|
const displayProxy = (children) => {
|
||||||
if (proxy) {
|
if (proxy) {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -289,7 +289,6 @@ export default function GenericNode({
|
||||||
function handlePlayWShortcut() {
|
function handlePlayWShortcut() {
|
||||||
if (buildStatus === BuildStatus.BUILDING || isBuilding || !selected) return;
|
if (buildStatus === BuildStatus.BUILDING || isBuilding || !selected) return;
|
||||||
setValidationStatus(null);
|
setValidationStatus(null);
|
||||||
console.log(data.node?.display_name);
|
|
||||||
buildFlow({ stopNodeId: data.id });
|
buildFlow({ stopNodeId: data.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
19
src/frontend/src/components/authSettingsGuard/index.tsx
Normal file
19
src/frontend/src/components/authSettingsGuard/index.tsx
Normal 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" />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
import { usePostLikeComponent } from "@/controllers/API/queries/store";
|
import { usePostLikeComponent } from "@/controllers/API/queries/store";
|
||||||
import { useEffect, useState } from "react";
|
import { useState } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
|
||||||
import { Control } from "react-hook-form";
|
import { Control } from "react-hook-form";
|
||||||
import { getComponent, postLikeComponent } from "../../controllers/API";
|
import { getComponent } from "../../controllers/API";
|
||||||
import IOModal from "../../modals/IOModal";
|
import IOModal from "../../modals/IOModal";
|
||||||
import DeleteConfirmationModal from "../../modals/deleteConfirmationModal";
|
import DeleteConfirmationModal from "../../modals/deleteConfirmationModal";
|
||||||
import useAlertStore from "../../stores/alertStore";
|
import useAlertStore from "../../stores/alertStore";
|
||||||
import useFlowStore from "../../stores/flowStore";
|
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
import { useStoreStore } from "../../stores/storeStore";
|
import { useStoreStore } from "../../stores/storeStore";
|
||||||
import { FlowType } from "../../types/flow";
|
import { FlowType } from "../../types/flow";
|
||||||
|
|
@ -31,7 +29,6 @@ import Loading from "../ui/loading";
|
||||||
import useDataEffect from "./hooks/use-data-effect";
|
import useDataEffect from "./hooks/use-data-effect";
|
||||||
import useInstallComponent from "./hooks/use-handle-install";
|
import useInstallComponent from "./hooks/use-handle-install";
|
||||||
import useDragStart from "./hooks/use-on-drag-start";
|
import useDragStart from "./hooks/use-on-drag-start";
|
||||||
import usePlaygroundEffect from "./hooks/use-playground-effect";
|
|
||||||
import { convertTestName } from "./utils/convert-test-name";
|
import { convertTestName } from "./utils/convert-test-name";
|
||||||
|
|
||||||
export default function CollectionCardComponent({
|
export default function CollectionCardComponent({
|
||||||
|
|
@ -56,7 +53,6 @@ export default function CollectionCardComponent({
|
||||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const setValidApiKey = useStoreStore((state) => state.updateValidApiKey);
|
const setValidApiKey = useStoreStore((state) => state.updateValidApiKey);
|
||||||
const cleanFlowPool = useFlowStore((state) => state.CleanFlowPool);
|
|
||||||
const isStore = false;
|
const isStore = false;
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [likedByUser, setLikedByUser] = useState(data?.liked_by_user ?? false);
|
const [likedByUser, setLikedByUser] = useState(data?.liked_by_user ?? false);
|
||||||
|
|
@ -64,16 +60,9 @@ export default function CollectionCardComponent({
|
||||||
const [downloadsCount, setDownloadsCount] = useState(
|
const [downloadsCount, setDownloadsCount] = useState(
|
||||||
data?.downloads_count ?? 0,
|
data?.downloads_count ?? 0,
|
||||||
);
|
);
|
||||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
|
||||||
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
|
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
|
||||||
const getFlowById = useFlowsManagerStore((state) => state.getFlowById);
|
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 [openPlayground, setOpenPlayground] = useState(false);
|
||||||
const setCurrentFlowId = useFlowsManagerStore(
|
|
||||||
(state) => state.setCurrentFlowId,
|
|
||||||
);
|
|
||||||
const [loadingPlayground, setLoadingPlayground] = useState(false);
|
const [loadingPlayground, setLoadingPlayground] = useState(false);
|
||||||
|
|
||||||
const selectedFlowsComponentsCards = useFlowsManagerStore(
|
const selectedFlowsComponentsCards = useFlowsManagerStore(
|
||||||
|
|
@ -96,16 +85,6 @@ export default function CollectionCardComponent({
|
||||||
return inputs.length > 0 || outputs.length > 0;
|
return inputs.length > 0 || outputs.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
usePlaygroundEffect(
|
|
||||||
currentFlowId,
|
|
||||||
playground!,
|
|
||||||
openPlayground,
|
|
||||||
currentFlow,
|
|
||||||
setNodes,
|
|
||||||
setEdges,
|
|
||||||
cleanFlowPool,
|
|
||||||
);
|
|
||||||
|
|
||||||
useDataEffect(data, setLikedByUser, setLikesCount, setDownloadsCount);
|
useDataEffect(data, setLikedByUser, setLikesCount, setDownloadsCount);
|
||||||
|
|
||||||
const { handleInstall } = useInstallComponent(
|
const { handleInstall } = useInstallComponent(
|
||||||
|
|
@ -317,7 +296,7 @@ export default function CollectionCardComponent({
|
||||||
setLoadingPlayground(false);
|
setLoadingPlayground(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setCurrentFlowId(data.id);
|
setCurrentFlow(flow);
|
||||||
setOpenPlayground(true);
|
setOpenPlayground(true);
|
||||||
setLoadingPlayground(false);
|
setLoadingPlayground(false);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -473,7 +452,7 @@ export default function CollectionCardComponent({
|
||||||
setLoadingPlayground(false);
|
setLoadingPlayground(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setCurrentFlowId(data.id);
|
setCurrentFlow(flow);
|
||||||
setOpenPlayground(true);
|
setOpenPlayground(true);
|
||||||
setLoadingPlayground(false);
|
setLoadingPlayground(false);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -510,6 +489,7 @@ export default function CollectionCardComponent({
|
||||||
</Card>
|
</Card>
|
||||||
{openPlayground && (
|
{openPlayground && (
|
||||||
<IOModal
|
<IOModal
|
||||||
|
key={data.id}
|
||||||
cleanOnClose={true}
|
cleanOnClose={true}
|
||||||
open={openPlayground}
|
open={openPlayground}
|
||||||
setOpen={setOpenPlayground}
|
setOpen={setOpenPlayground}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import FeatureFlags from "@/../feature-config.json";
|
import FeatureFlags from "@/../feature-config.json";
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
import { useMemo, useRef, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { useHotkeys } from "react-hotkeys-hook";
|
import { useHotkeys } from "react-hotkeys-hook";
|
||||||
import IOModal from "../../modals/IOModal";
|
import IOModal from "../../modals/IOModal";
|
||||||
import ApiModal from "../../modals/apiModal";
|
import ApiModal from "../../modals/apiModal";
|
||||||
import ShareModal from "../../modals/shareModal";
|
import ShareModal from "../../modals/shareModal";
|
||||||
import useFlowStore from "../../stores/flowStore";
|
import useFlowStore from "../../stores/flowStore";
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
|
||||||
import { useShortcutsStore } from "../../stores/shortcuts";
|
import { useShortcutsStore } from "../../stores/shortcuts";
|
||||||
import { useStoreStore } from "../../stores/storeStore";
|
import { useStoreStore } from "../../stores/storeStore";
|
||||||
import { classNames, isThereModal } from "../../utils/utils";
|
import { classNames, isThereModal } from "../../utils/utils";
|
||||||
|
|
@ -47,9 +46,7 @@ export default function FlowToolbar(): JSX.Element {
|
||||||
const hasStore = useStoreStore((state) => state.hasStore);
|
const hasStore = useStoreStore((state) => state.hasStore);
|
||||||
const validApiKey = useStoreStore((state) => state.validApiKey);
|
const validApiKey = useStoreStore((state) => state.validApiKey);
|
||||||
const hasApiKey = useStoreStore((state) => state.hasApiKey);
|
const hasApiKey = useStoreStore((state) => state.hasApiKey);
|
||||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
const currentFlow = useFlowStore((state) => state.currentFlow);
|
||||||
|
|
||||||
const prevNodesRef = useRef<any[] | undefined>();
|
|
||||||
|
|
||||||
const ModalMemo = useMemo(
|
const ModalMemo = useMemo(
|
||||||
() => (
|
() => (
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,7 @@ export default function CodeTabsComponent({
|
||||||
}}
|
}}
|
||||||
id="tweaks-switch"
|
id="tweaks-switch"
|
||||||
onCheckedChange={setActiveTweaks}
|
onCheckedChange={setActiveTweaks}
|
||||||
|
checked={activeTweaks}
|
||||||
autoFocus={false}
|
autoFocus={false}
|
||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import React, { ChangeEvent, useState } from "react";
|
||||||
import { Input } from "../../components/ui/input";
|
import { Input } from "../../components/ui/input";
|
||||||
import { Label } from "../../components/ui/label";
|
import { Label } from "../../components/ui/label";
|
||||||
import { Textarea } from "../../components/ui/textarea";
|
import { Textarea } from "../../components/ui/textarea";
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
|
||||||
import { InputProps } from "../../types/components";
|
import { InputProps } from "../../types/components";
|
||||||
import { cn, isEndpointNameValid } from "../../utils/utils";
|
import { cn, isEndpointNameValid } from "../../utils/utils";
|
||||||
|
|
||||||
|
|
@ -19,7 +18,6 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
||||||
const [isMaxLength, setIsMaxLength] = useState(false);
|
const [isMaxLength, setIsMaxLength] = useState(false);
|
||||||
const [validEndpointName, setValidEndpointName] = useState(true);
|
const [validEndpointName, setValidEndpointName] = useState(true);
|
||||||
const [isInvalidName, setIsInvalidName] = useState(false);
|
const [isInvalidName, setIsInvalidName] = useState(false);
|
||||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
|
||||||
|
|
||||||
const handleNameChange = (event: ChangeEvent<HTMLInputElement>) => {
|
const handleNameChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
const { value } = event.target;
|
const { value } = event.target;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,10 @@ import {
|
||||||
} from "../../../ui/dropdown-menu";
|
} from "../../../ui/dropdown-menu";
|
||||||
|
|
||||||
import useAddFlow from "@/hooks/flows/use-add-flow";
|
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 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 { useNavigate } from "react-router-dom";
|
||||||
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
|
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
|
||||||
import { SAVED_HOVER } from "../../../../constants/constants";
|
import { SAVED_HOVER } from "../../../../constants/constants";
|
||||||
|
|
@ -29,7 +32,6 @@ import { Button } from "../../../ui/button";
|
||||||
export const MenuBar = ({}: {}): JSX.Element => {
|
export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
||||||
const addFlow = useAddFlow();
|
const addFlow = useAddFlow();
|
||||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
const setLockChat = useFlowStore((state) => state.setLockChat);
|
const setLockChat = useFlowStore((state) => state.setLockChat);
|
||||||
|
|
@ -46,6 +48,24 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const isBuilding = useFlowStore((state) => state.isBuilding);
|
const isBuilding = useFlowStore((state) => state.isBuilding);
|
||||||
const getTypes = useTypesStore((state) => state.getTypes);
|
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() {
|
function handleAddFlow() {
|
||||||
try {
|
try {
|
||||||
|
|
@ -69,10 +89,19 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
} else if (saveLoading) {
|
} else if (saveLoading) {
|
||||||
return "Saving...";
|
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="round-button-div">
|
||||||
<div className="header-menu-bar">
|
<div className="header-menu-bar">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
|
|
@ -112,6 +141,20 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
<IconComponent name="Settings2" className="header-menu-options" />
|
<IconComponent name="Settings2" className="header-menu-options" />
|
||||||
Settings
|
Settings
|
||||||
</DropdownMenuItem>
|
</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
|
<DropdownMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpenLogs(true);
|
setOpenLogs(true);
|
||||||
|
|
@ -205,11 +248,11 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
></FlowSettingsModal>
|
></FlowSettingsModal>
|
||||||
<FlowLogsModal open={openLogs} setOpen={setOpenLogs}></FlowLogsModal>
|
<FlowLogsModal open={openLogs} setOpen={setOpenLogs}></FlowLogsModal>
|
||||||
</div>
|
</div>
|
||||||
{(currentFlow.updated_at || saveLoading) && (
|
{(updatedAt || saveLoading) && (
|
||||||
<ShadTooltip
|
<ShadTooltip
|
||||||
content={
|
content={
|
||||||
SAVED_HOVER +
|
SAVED_HOVER +
|
||||||
new Date(currentFlow.updated_at ?? "").toLocaleString("en-US", {
|
new Date(updatedAt ?? "").toLocaleString("en-US", {
|
||||||
hour: "numeric",
|
hour: "numeric",
|
||||||
minute: "numeric",
|
minute: "numeric",
|
||||||
second: "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-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">
|
<div className="flex cursor-default items-center gap-1.5 text-sm text-muted-foreground transition-all">
|
||||||
<IconComponent
|
<Button
|
||||||
name={isBuilding || saveLoading ? "Loader2" : "CheckCircle2"}
|
unstyled
|
||||||
|
disabled={shouldAutosave || !changesNotSaved}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-4 w-4",
|
!shouldAutosave && changesNotSaved
|
||||||
isBuilding || saveLoading ? "animate-spin" : "animate-wiggle",
|
? "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>{printByBuildStatus()}</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { FaDiscord, FaGithub } from "react-icons/fa";
|
import { FaDiscord, FaGithub } from "react-icons/fa";
|
||||||
import { RiTwitterXFill } from "react-icons/ri";
|
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 AlertDropdown from "../../alerts/alertDropDown";
|
||||||
import {
|
import {
|
||||||
BASE_URL_API,
|
BASE_URL_API,
|
||||||
|
|
@ -12,11 +12,9 @@ import { AuthContext } from "../../contexts/authContext";
|
||||||
|
|
||||||
import FeatureFlags from "@/../feature-config.json";
|
import FeatureFlags from "@/../feature-config.json";
|
||||||
import { useLogout } from "@/controllers/API/queries/auth";
|
import { useLogout } from "@/controllers/API/queries/auth";
|
||||||
import useDeleteFlow from "@/hooks/flows/use-delete-flow";
|
|
||||||
import useAuthStore from "@/stores/authStore";
|
import useAuthStore from "@/stores/authStore";
|
||||||
import useAlertStore from "../../stores/alertStore";
|
import useAlertStore from "../../stores/alertStore";
|
||||||
import { useDarkStore } from "../../stores/darkStore";
|
import { useDarkStore } from "../../stores/darkStore";
|
||||||
import useFlowStore from "../../stores/flowStore";
|
|
||||||
import { useLocationStore } from "../../stores/locationStore";
|
import { useLocationStore } from "../../stores/locationStore";
|
||||||
import { useStoreStore } from "../../stores/storeStore";
|
import { useStoreStore } from "../../stores/storeStore";
|
||||||
import IconComponent, { ForwardedIconComponent } from "../genericIconComponent";
|
import IconComponent, { ForwardedIconComponent } from "../genericIconComponent";
|
||||||
|
|
@ -44,10 +42,7 @@ export default function Header(): JSX.Element {
|
||||||
const logout = useAuthStore((state) => state.logout);
|
const logout = useAuthStore((state) => state.logout);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const deleteFlow = useDeleteFlow();
|
|
||||||
const hasStore = useStoreStore((state) => state.hasStore);
|
const hasStore = useStoreStore((state) => state.hasStore);
|
||||||
const { id } = useParams();
|
|
||||||
const nodes = useFlowStore((state) => state.nodes);
|
|
||||||
|
|
||||||
const dark = useDarkStore((state) => state.dark);
|
const dark = useDarkStore((state) => state.dark);
|
||||||
const setDark = useDarkStore((state) => state.setDark);
|
const setDark = useDarkStore((state) => state.setDark);
|
||||||
|
|
@ -58,11 +53,6 @@ export default function Header(): JSX.Element {
|
||||||
const profileImageUrl = `${BASE_URL_API}files/profile_pictures/${
|
const profileImageUrl = `${BASE_URL_API}files/profile_pictures/${
|
||||||
userData?.profile_image ?? "Space/046-rocket.svg"
|
userData?.profile_image ?? "Space/046-rocket.svg"
|
||||||
}`;
|
}`;
|
||||||
async function checkForChanges(): Promise<void> {
|
|
||||||
if (nodes.length === 0 && id) {
|
|
||||||
await deleteFlow({ id });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const redirectToLastLocation = () => {
|
const redirectToLastLocation = () => {
|
||||||
const lastFlowVisitedIndex = routeHistory
|
const lastFlowVisitedIndex = routeHistory
|
||||||
|
|
@ -100,14 +90,13 @@ export default function Header(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className="header-arrangement">
|
<div className="header-arrangement">
|
||||||
<div className="header-start-display lg:w-[407px]">
|
<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>
|
<span className="ml-4 text-2xl">⛓️</span>
|
||||||
</Link>
|
</Link>
|
||||||
{showArrowReturnIcon && (
|
{showArrowReturnIcon && (
|
||||||
<Button
|
<Button
|
||||||
unstyled
|
unstyled
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
checkForChanges();
|
|
||||||
redirectToLastLocation();
|
redirectToLastLocation();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -129,7 +118,6 @@ export default function Header(): JSX.Element {
|
||||||
: "secondary"
|
: "secondary"
|
||||||
}
|
}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={checkForChanges}
|
|
||||||
>
|
>
|
||||||
<IconComponent name="Home" className="h-4 w-4" />
|
<IconComponent name="Home" className="h-4 w-4" />
|
||||||
<div className="hidden flex-1 md:block">{USER_PROJECTS_HEADER}</div>
|
<div className="hidden flex-1 md:block">{USER_PROJECTS_HEADER}</div>
|
||||||
|
|
@ -142,7 +130,6 @@ export default function Header(): JSX.Element {
|
||||||
className="gap-2"
|
className="gap-2"
|
||||||
variant={location.pathname === "/store" ? "primary" : "secondary"}
|
variant={location.pathname === "/store" ? "primary" : "secondary"}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={checkForChanges}
|
|
||||||
data-testid="button-store"
|
data-testid="button-store"
|
||||||
>
|
>
|
||||||
<IconComponent name="Store" className="h-4 w-4" />
|
<IconComponent name="Store" className="h-4 w-4" />
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
|
import useSaveFlow from "@/hooks/flows/use-save-flow";
|
||||||
import {
|
import {
|
||||||
UPLOAD_ALERT_LIST,
|
UPLOAD_ALERT_LIST,
|
||||||
WRONG_FILE_ERROR_ALERT,
|
WRONG_FILE_ERROR_ALERT,
|
||||||
} from "../../../constants/alerts_constants";
|
} from "../../../constants/alerts_constants";
|
||||||
import { updateFlowInDatabase } from "../../../controllers/API";
|
|
||||||
import { uploadFlowToFolder } from "../../../pages/MainPage/services";
|
import { uploadFlowToFolder } from "../../../pages/MainPage/services";
|
||||||
import useAlertStore from "../../../stores/alertStore";
|
import useAlertStore from "../../../stores/alertStore";
|
||||||
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
|
||||||
|
|
@ -21,6 +21,7 @@ const useFileDrop = (
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const refreshFolders = useFolderStore((state) => state.refreshFolders);
|
const refreshFolders = useFolderStore((state) => state.refreshFolders);
|
||||||
const flows = useFlowsManagerStore((state) => state.flows);
|
const flows = useFlowsManagerStore((state) => state.flows);
|
||||||
|
const saveFlow = useSaveFlow();
|
||||||
|
|
||||||
const triggerFolderChange = (folderId) => {
|
const triggerFolderChange = (folderId) => {
|
||||||
if (folderChangeCallback) {
|
if (folderChangeCallback) {
|
||||||
|
|
@ -106,21 +107,21 @@ const useFileDrop = (
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadFromDragCard = (flowId, folderId) => {
|
const uploadFromDragCard = (flowId, folderId) => {
|
||||||
const selectedFlow = flows.find((flow) => flow.id === flowId);
|
const selectedFlow = flows?.find((flow) => flow.id === flowId);
|
||||||
|
|
||||||
if (!selectedFlow) {
|
if (!selectedFlow) {
|
||||||
throw new Error("Flow not found");
|
throw new Error("Flow not found");
|
||||||
}
|
}
|
||||||
const updatedFlow = { ...selectedFlow, folder_id: folderId };
|
const updatedFlow = { ...selectedFlow, folder_id: folderId };
|
||||||
|
|
||||||
const newName = addVersionToDuplicates(updatedFlow, flows);
|
const newName = addVersionToDuplicates(updatedFlow, flows ?? []);
|
||||||
|
|
||||||
updatedFlow.name = newName;
|
updatedFlow.name = newName;
|
||||||
|
|
||||||
setFolderDragging(false);
|
setFolderDragging(false);
|
||||||
setFolderIdDragging("");
|
setFolderIdDragging("");
|
||||||
|
|
||||||
updateFlowInDatabase(updatedFlow).then(() => {
|
saveFlow(updatedFlow).then(() => {
|
||||||
refreshFolders();
|
refreshFolders();
|
||||||
triggerFolderChange(folderId);
|
triggerFolderChange(folderId);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -770,9 +770,13 @@ export const defaultShortcuts = [
|
||||||
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Shift + D`,
|
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Shift + D`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Save",
|
name: "Changes Save",
|
||||||
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + S`,
|
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + S`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Save Component",
|
||||||
|
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Alt + S`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Delete",
|
name: "Delete",
|
||||||
shortcut: "Backspace",
|
shortcut: "Backspace",
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,8 @@ import {
|
||||||
} from "@/constants/constants";
|
} from "@/constants/constants";
|
||||||
import { useGetUserData } from "@/controllers/API/queries/auth";
|
import { useGetUserData } from "@/controllers/API/queries/auth";
|
||||||
import useAuthStore from "@/stores/authStore";
|
import useAuthStore from "@/stores/authStore";
|
||||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
|
||||||
import { createContext, useEffect, useState } from "react";
|
import { createContext, useEffect, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import Cookies from "universal-cookie";
|
import Cookies from "universal-cookie";
|
||||||
import { getLoggedUser, requestLogout } from "../controllers/API";
|
|
||||||
import useAlertStore from "../stores/alertStore";
|
import useAlertStore from "../stores/alertStore";
|
||||||
import { useFolderStore } from "../stores/foldersStore";
|
import { useFolderStore } from "../stores/foldersStore";
|
||||||
import { useStoreStore } from "../stores/storeStore";
|
import { useStoreStore } from "../stores/storeStore";
|
||||||
|
|
@ -31,7 +28,6 @@ const initialValue: AuthContextType = {
|
||||||
export const AuthContext = createContext<AuthContextType>(initialValue);
|
export const AuthContext = createContext<AuthContextType>(initialValue);
|
||||||
|
|
||||||
export function AuthProvider({ children }): React.ReactElement {
|
export function AuthProvider({ children }): React.ReactElement {
|
||||||
const navigate = useNavigate();
|
|
||||||
const cookies = new Cookies();
|
const cookies = new Cookies();
|
||||||
const [accessToken, setAccessToken] = useState<string | null>(
|
const [accessToken, setAccessToken] = useState<string | null>(
|
||||||
cookies.get(LANGFLOW_ACCESS_TOKEN) ?? null,
|
cookies.get(LANGFLOW_ACCESS_TOKEN) ?? null,
|
||||||
|
|
@ -46,12 +42,7 @@ export function AuthProvider({ children }): React.ReactElement {
|
||||||
|
|
||||||
const checkHasStore = useStoreStore((state) => state.checkHasStore);
|
const checkHasStore = useStoreStore((state) => state.checkHasStore);
|
||||||
const fetchApiData = useStoreStore((state) => state.fetchApiData);
|
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 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();
|
const { mutate: mutateLoggedUser } = useGetUserData();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { BrowserRouter } from "react-router-dom";
|
|
||||||
import { ReactFlowProvider } from "reactflow";
|
import { ReactFlowProvider } from "reactflow";
|
||||||
import { TooltipProvider } from "../components/ui/tooltip";
|
import { TooltipProvider } from "../components/ui/tooltip";
|
||||||
import { ApiInterceptor } from "../controllers/API/api";
|
import { ApiInterceptor } from "../controllers/API/api";
|
||||||
|
|
@ -11,18 +10,16 @@ export default function ContextWrapper({ children }: { children: ReactNode }) {
|
||||||
//element to wrap all context
|
//element to wrap all context
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<BrowserRouter>
|
<QueryClientProvider client={queryClient}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<AuthProvider>
|
||||||
<AuthProvider>
|
<TooltipProvider skipDelayDuration={0}>
|
||||||
<TooltipProvider skipDelayDuration={0}>
|
<ReactFlowProvider>
|
||||||
<ReactFlowProvider>
|
<ApiInterceptor />
|
||||||
<ApiInterceptor />
|
{children}
|
||||||
{children}
|
</ReactFlowProvider>
|
||||||
</ReactFlowProvider>
|
</TooltipProvider>
|
||||||
</TooltipProvider>
|
</AuthProvider>
|
||||||
</AuthProvider>
|
</QueryClientProvider>
|
||||||
</QueryClientProvider>
|
|
||||||
</BrowserRouter>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { LANGFLOW_ACCESS_TOKEN } from "@/constants/constants";
|
import { LANGFLOW_ACCESS_TOKEN } from "@/constants/constants";
|
||||||
import useAuthStore from "@/stores/authStore";
|
import useAuthStore from "@/stores/authStore";
|
||||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
|
||||||
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig } from "axios";
|
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig } from "axios";
|
||||||
import { useContext, useEffect } from "react";
|
import { useContext, useEffect } from "react";
|
||||||
import { Cookies } from "react-cookie";
|
import { Cookies } from "react-cookie";
|
||||||
|
|
@ -21,8 +20,6 @@ function ApiInterceptor() {
|
||||||
const autoLogin = useAuthStore((state) => state.autoLogin);
|
const autoLogin = useAuthStore((state) => state.autoLogin);
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
let { accessToken, authenticationErrorCount } = useContext(AuthContext);
|
let { accessToken, authenticationErrorCount } = useContext(AuthContext);
|
||||||
|
|
||||||
const setSaveLoading = useFlowsManagerStore((state) => state.setSaveLoading);
|
|
||||||
const { mutate: mutationLogout } = useLogout();
|
const { mutate: mutationLogout } = useLogout();
|
||||||
const { mutate: mutationRenewAccessToken } = useRefreshAccessToken();
|
const { mutate: mutationRenewAccessToken } = useRefreshAccessToken();
|
||||||
const logout = useAuthStore((state) => state.logout);
|
const logout = useAuthStore((state) => state.logout);
|
||||||
|
|
@ -152,7 +149,6 @@ function ApiInterceptor() {
|
||||||
onSuccess: async (data) => {
|
onSuccess: async (data) => {
|
||||||
authenticationErrorCount = 0;
|
authenticationErrorCount = 0;
|
||||||
await remakeRequest(error);
|
await remakeRequest(error);
|
||||||
setSaveLoading(false);
|
|
||||||
authenticationErrorCount = 0;
|
authenticationErrorCount = 0;
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
|
|
|
||||||
|
|
@ -141,34 +141,6 @@ export async function saveFlowToDatabase(newFlow: {
|
||||||
throw error;
|
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.
|
* Reads all flows from the database.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import useFlowStore from "@/stores/flowStore";
|
import useFlowStore from "@/stores/flowStore";
|
||||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
|
||||||
import { FlowPoolType } from "@/types/zustand/flow";
|
import { FlowPoolType } from "@/types/zustand/flow";
|
||||||
import { keepPreviousData } from "@tanstack/react-query";
|
import { keepPreviousData } from "@tanstack/react-query";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
|
|
@ -20,7 +19,7 @@ export const useGetBuildsQuery: useQueryFunctionType<
|
||||||
const { query } = UseRequestProcessor();
|
const { query } = UseRequestProcessor();
|
||||||
|
|
||||||
const setFlowPool = useFlowStore((state) => state.setFlowPool);
|
const setFlowPool = useFlowStore((state) => state.setFlowPool);
|
||||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
const currentFlow = useFlowStore((state) => state.currentFlow);
|
||||||
|
|
||||||
const getBuildsFn = async (
|
const getBuildsFn = async (
|
||||||
params: BuildsQueryParams,
|
params: BuildsQueryParams,
|
||||||
|
|
|
||||||
|
|
@ -6,41 +6,34 @@ import { getURL } from "../../helpers/constants";
|
||||||
import { UseRequestProcessor } from "../../services/request-processor";
|
import { UseRequestProcessor } from "../../services/request-processor";
|
||||||
|
|
||||||
interface IPatchUpdateFlow {
|
interface IPatchUpdateFlow {
|
||||||
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
data: ReactFlowJsonObject;
|
data: ReactFlowJsonObject;
|
||||||
description: string;
|
description: string;
|
||||||
folder_id: string;
|
folder_id: string | null | undefined;
|
||||||
endpoint_name: string;
|
endpoint_name: string | null | undefined;
|
||||||
}
|
|
||||||
|
|
||||||
interface IPatchUpdateFlowParams {
|
|
||||||
id: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const usePatchUpdateFlow: useMutationFunctionType<
|
export const usePatchUpdateFlow: useMutationFunctionType<
|
||||||
IPatchUpdateFlowParams,
|
undefined,
|
||||||
IPatchUpdateFlow
|
IPatchUpdateFlow
|
||||||
> = (params, options?) => {
|
> = (options?) => {
|
||||||
const { mutate } = UseRequestProcessor();
|
const { mutate } = UseRequestProcessor();
|
||||||
|
|
||||||
const PatchUpdateFlowFn = async (payload: IPatchUpdateFlow): Promise<any> => {
|
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,
|
name: payload.name,
|
||||||
data: payload.data,
|
data: payload.data,
|
||||||
description: payload.description,
|
description: payload.description,
|
||||||
folder_id: payload.folder_id === "" ? null : payload.folder_id,
|
folder_id: payload.folder_id || null,
|
||||||
endpoint_name: payload.endpoint_name,
|
endpoint_name: payload.endpoint_name || null,
|
||||||
});
|
});
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mutation: UseMutationResult<IPatchUpdateFlow, any, IPatchUpdateFlow> =
|
const mutation: UseMutationResult<IPatchUpdateFlow, any, IPatchUpdateFlow> =
|
||||||
mutate(
|
mutate(["usePatchUpdateFlow"], PatchUpdateFlowFn, options);
|
||||||
["usePatchUpdateFlow", { id: params.id }],
|
|
||||||
PatchUpdateFlowFn,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
|
|
||||||
return mutation;
|
return mutation;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ const useAddFlow = () => {
|
||||||
const my_collection_id = useFolderStore.getState().myCollectionId;
|
const my_collection_id = useFolderStore.getState().myCollectionId;
|
||||||
|
|
||||||
if (params?.override && flow) {
|
if (params?.override && flow) {
|
||||||
const flowId = flows.find((f) => f.name === flow.name);
|
const flowId = flows?.find((f) => f.name === flow.name);
|
||||||
if (flowId) {
|
if (flowId) {
|
||||||
await deleteFlow({ id: flowId.id });
|
await deleteFlow({ id: flowId.id });
|
||||||
}
|
}
|
||||||
|
|
@ -60,7 +60,7 @@ const useAddFlow = () => {
|
||||||
flow,
|
flow,
|
||||||
);
|
);
|
||||||
|
|
||||||
const newName = addVersionToDuplicates(newFlow, flows);
|
const newName = addVersionToDuplicates(newFlow, flows ?? []);
|
||||||
newFlow.name = newName;
|
newFlow.name = newName;
|
||||||
newFlow.folder_id = useFolderStore.getState().folderUrl;
|
newFlow.folder_id = useFolderStore.getState().folderUrl;
|
||||||
|
|
||||||
|
|
@ -68,7 +68,10 @@ const useAddFlow = () => {
|
||||||
onSuccess: ({ id }) => {
|
onSuccess: ({ id }) => {
|
||||||
newFlow.id = id;
|
newFlow.id = id;
|
||||||
// Add the new flow to the list of flows.
|
// 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);
|
setFlows(myFlows);
|
||||||
useTypesStore.setState((state) => ({
|
useTypesStore.setState((state) => ({
|
||||||
data: { ...state.data, ["saved_components"]: data },
|
data: { ...state.data, ["saved_components"]: data },
|
||||||
|
|
|
||||||
19
src/frontend/src/hooks/flows/use-autosave-flow.ts
Normal file
19
src/frontend/src/hooks/flows/use-autosave-flow.ts
Normal 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;
|
||||||
|
|
@ -27,7 +27,7 @@ const useDeleteFlow = () => {
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
const { data, flows: myFlows } = processFlows(
|
const { data, flows: myFlows } = processFlows(
|
||||||
flows.filter((flow) => !id.includes(flow.id)),
|
(flows ?? []).filter((flow) => !id.includes(flow.id)),
|
||||||
);
|
);
|
||||||
setFlows(myFlows);
|
setFlows(myFlows);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|
|
||||||
100
src/frontend/src/hooks/flows/use-save-flow.ts
Normal file
100
src/frontend/src/hooks/flows/use-save-flow.ts
Normal 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;
|
||||||
13
src/frontend/src/hooks/use-debounce.ts
Normal file
13
src/frontend/src/hooks/use-debounce.ts
Normal 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],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import App from "./App";
|
|
||||||
import ContextWrapper from "./contexts";
|
import ContextWrapper from "./contexts";
|
||||||
import reportWebVitals from "./reportWebVitals";
|
import reportWebVitals from "./reportWebVitals";
|
||||||
|
|
||||||
|
|
@ -8,6 +7,10 @@ import "./style/index.css";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import "./style/applies.css";
|
import "./style/applies.css";
|
||||||
// @ts-ignore
|
// @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";
|
import "./style/classes.css";
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(
|
const root = ReactDOM.createRoot(
|
||||||
|
|
@ -15,7 +18,15 @@ const root = ReactDOM.createRoot(
|
||||||
);
|
);
|
||||||
root.render(
|
root.render(
|
||||||
<ContextWrapper>
|
<ContextWrapper>
|
||||||
<App />
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<div className="loading-page-panel">
|
||||||
|
<LoadingComponent remSize={50} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</Suspense>
|
||||||
</ContextWrapper>,
|
</ContextWrapper>,
|
||||||
);
|
);
|
||||||
reportWebVitals();
|
reportWebVitals();
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import {
|
||||||
FS_ERROR_TEXT,
|
FS_ERROR_TEXT,
|
||||||
SN_ERROR_TEXT,
|
SN_ERROR_TEXT,
|
||||||
} from "../../../../constants/constants";
|
} from "../../../../constants/constants";
|
||||||
import { deleteFlowPool } from "../../../../controllers/API";
|
|
||||||
import useAlertStore from "../../../../stores/alertStore";
|
import useAlertStore from "../../../../stores/alertStore";
|
||||||
import useFlowStore from "../../../../stores/flowStore";
|
import useFlowStore from "../../../../stores/flowStore";
|
||||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ export default function IOModal({
|
||||||
open,
|
open,
|
||||||
setOpen,
|
setOpen,
|
||||||
disable,
|
disable,
|
||||||
|
isPlayground,
|
||||||
}: IOModalPropsType): JSX.Element {
|
}: IOModalPropsType): JSX.Element {
|
||||||
const allNodes = useFlowStore((state) => state.nodes);
|
const allNodes = useFlowStore((state) => state.nodes);
|
||||||
const inputs = useFlowStore((state) => state.inputs).filter(
|
const inputs = useFlowStore((state) => state.inputs).filter(
|
||||||
|
|
@ -107,7 +108,7 @@ export default function IOModal({
|
||||||
const setLockChat = useFlowStore((state) => state.setLockChat);
|
const setLockChat = useFlowStore((state) => state.setLockChat);
|
||||||
const [chatValue, setChatValue] = useState("");
|
const [chatValue, setChatValue] = useState("");
|
||||||
const isBuilding = useFlowStore((state) => state.isBuilding);
|
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 setNode = useFlowStore((state) => state.setNode);
|
||||||
const [sessions, setSessions] = useState<string[]>([]);
|
const [sessions, setSessions] = useState<string[]>([]);
|
||||||
const messages = useMessagesStore((state) => state.messages);
|
const messages = useMessagesStore((state) => state.messages);
|
||||||
|
|
@ -115,7 +116,7 @@ export default function IOModal({
|
||||||
|
|
||||||
const { refetch } = useGetMessagesQuery({
|
const { refetch } = useGetMessagesQuery({
|
||||||
mode: "union",
|
mode: "union",
|
||||||
id: currentFlow?.id,
|
id: currentFlowId,
|
||||||
});
|
});
|
||||||
|
|
||||||
async function sendMessage({
|
async function sendMessage({
|
||||||
|
|
@ -164,7 +165,7 @@ export default function IOModal({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const sessions = new Set<string>();
|
const sessions = new Set<string>();
|
||||||
messages
|
messages
|
||||||
.filter((message) => message.flow_id === currentFlow!.id)
|
.filter((message) => message.flow_id === currentFlowId)
|
||||||
.forEach((row) => {
|
.forEach((row) => {
|
||||||
sessions.add(row.session_id);
|
sessions.add(row.session_id);
|
||||||
});
|
});
|
||||||
|
|
@ -178,6 +179,7 @@ export default function IOModal({
|
||||||
open={open}
|
open={open}
|
||||||
setOpen={setOpen}
|
setOpen={setOpen}
|
||||||
disable={disable}
|
disable={disable}
|
||||||
|
type={isPlayground ? "modal" : undefined}
|
||||||
onSubmit={() => sendMessage({ repeat: 1 })}
|
onSubmit={() => sendMessage({ repeat: 1 })}
|
||||||
>
|
>
|
||||||
<BaseModal.Trigger>{children}</BaseModal.Trigger>
|
<BaseModal.Trigger>{children}</BaseModal.Trigger>
|
||||||
|
|
@ -375,7 +377,7 @@ export default function IOModal({
|
||||||
size="md"
|
size="md"
|
||||||
className="block truncate"
|
className="block truncate"
|
||||||
>
|
>
|
||||||
{session === currentFlow?.id
|
{session === currentFlowId
|
||||||
? "Default Session"
|
? "Default Session"
|
||||||
: session}
|
: session}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
@ -491,7 +493,7 @@ export default function IOModal({
|
||||||
) && (
|
) && (
|
||||||
<SessionView
|
<SessionView
|
||||||
session={selectedViewField.id}
|
session={selectedViewField.id}
|
||||||
id={currentFlow!.id}
|
id={currentFlowId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -35,9 +35,6 @@ export default function ApiModal({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) initialSetup(autoLogin, flow);
|
if (open) initialSetup(autoLogin, flow);
|
||||||
}, [open, flow?.data?.nodes]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setActiveTab("0");
|
setActiveTab("0");
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ function ConfirmationModal({
|
||||||
confirmationText,
|
confirmationText,
|
||||||
children,
|
children,
|
||||||
destructive = false,
|
destructive = false,
|
||||||
|
destructiveCancel = false,
|
||||||
icon,
|
icon,
|
||||||
data,
|
data,
|
||||||
index,
|
index,
|
||||||
|
|
@ -44,13 +45,18 @@ function ConfirmationModal({
|
||||||
}: ConfirmationModalType) {
|
}: ConfirmationModalType) {
|
||||||
const Icon: any = nodeIconsLucide[icon];
|
const Icon: any = nodeIconsLucide[icon];
|
||||||
const [modalOpen, setModalOpen] = useState(open ?? false);
|
const [modalOpen, setModalOpen] = useState(open ?? false);
|
||||||
|
const [flag, setFlag] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) setModalOpen(open);
|
if (open) setModalOpen(open);
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (onClose) onClose!(modalOpen);
|
if (onClose && modalOpen === false && !flag) {
|
||||||
|
onClose();
|
||||||
|
} else if (flag) {
|
||||||
|
setFlag(false);
|
||||||
|
}
|
||||||
}, [modalOpen]);
|
}, [modalOpen]);
|
||||||
|
|
||||||
const triggerChild = React.Children.toArray(children).find(
|
const triggerChild = React.Children.toArray(children).find(
|
||||||
|
|
@ -61,7 +67,7 @@ function ConfirmationModal({
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseModal size={size} open={open} setOpen={setModalOpen}>
|
<BaseModal size={size} open={modalOpen} setOpen={setModalOpen}>
|
||||||
<BaseModal.Trigger>{triggerChild}</BaseModal.Trigger>
|
<BaseModal.Trigger>{triggerChild}</BaseModal.Trigger>
|
||||||
<BaseModal.Header description={titleHeader ?? null}>
|
<BaseModal.Header description={titleHeader ?? null}>
|
||||||
<span className="pr-2">{title}</span>
|
<span className="pr-2">{title}</span>
|
||||||
|
|
@ -86,6 +92,7 @@ function ConfirmationModal({
|
||||||
className="ml-3"
|
className="ml-3"
|
||||||
variant={destructive ? "destructive" : "default"}
|
variant={destructive ? "destructive" : "default"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
setFlag(true);
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
onConfirm(index, data);
|
onConfirm(index, data);
|
||||||
}}
|
}}
|
||||||
|
|
@ -96,8 +103,9 @@ function ConfirmationModal({
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
className=""
|
className=""
|
||||||
variant="outline"
|
variant={destructiveCancel ? "destructive" : "outline"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
setFlag(true);
|
||||||
if (onCancel) onCancel();
|
if (onCancel) onCancel();
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import useFlowStore from "@/stores/flowStore";
|
||||||
import { ReactNode, forwardRef, useEffect, useState } from "react";
|
import { ReactNode, forwardRef, useEffect, useState } from "react";
|
||||||
import EditFlowSettings from "../../components/editFlowSettingsComponent";
|
import EditFlowSettings from "../../components/editFlowSettingsComponent";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
|
|
@ -10,7 +11,6 @@ import {
|
||||||
} from "../../constants/constants";
|
} from "../../constants/constants";
|
||||||
import useAlertStore from "../../stores/alertStore";
|
import useAlertStore from "../../stores/alertStore";
|
||||||
import { useDarkStore } from "../../stores/darkStore";
|
import { useDarkStore } from "../../stores/darkStore";
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
|
||||||
import { downloadFlow, removeApiKeys } from "../../utils/reactflowUtils";
|
import { downloadFlow, removeApiKeys } from "../../utils/reactflowUtils";
|
||||||
import BaseModal from "../baseModal";
|
import BaseModal from "../baseModal";
|
||||||
|
|
||||||
|
|
@ -19,7 +19,7 @@ const ExportModal = forwardRef(
|
||||||
const version = useDarkStore((state) => state.version);
|
const version = useDarkStore((state) => state.version);
|
||||||
const setNoticeData = useAlertStore((state) => state.setNoticeData);
|
const setNoticeData = useAlertStore((state) => state.setNoticeData);
|
||||||
const [checked, setChecked] = useState(false);
|
const [checked, setChecked] = useState(false);
|
||||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
const currentFlow = useFlowStore((state) => state.currentFlow);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setName(currentFlow!.name);
|
setName(currentFlow!.name);
|
||||||
setDescription(currentFlow!.description);
|
setDescription(currentFlow!.description);
|
||||||
|
|
|
||||||
|
|
@ -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 { useEffect, useState } from "react";
|
||||||
import EditFlowSettings from "../../components/editFlowSettingsComponent";
|
import EditFlowSettings from "../../components/editFlowSettingsComponent";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
import { SETTINGS_DIALOG_SUBTITLE } from "../../constants/constants";
|
import { SETTINGS_DIALOG_SUBTITLE } from "../../constants/constants";
|
||||||
import useAlertStore from "../../stores/alertStore";
|
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
import { FlowSettingsPropsType } from "../../types/components";
|
import { FlowSettingsPropsType } from "../../types/components";
|
||||||
import { FlowType } from "../../types/flow";
|
import { FlowType } from "../../types/flow";
|
||||||
|
|
@ -13,8 +16,10 @@ export default function FlowSettingsModal({
|
||||||
open,
|
open,
|
||||||
setOpen,
|
setOpen,
|
||||||
}: FlowSettingsPropsType): JSX.Element {
|
}: FlowSettingsPropsType): JSX.Element {
|
||||||
const saveFlow = useFlowsManagerStore((state) => state.saveFlow);
|
const saveFlow = useSaveFlow();
|
||||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
const currentFlow = useFlowStore((state) => state.currentFlow);
|
||||||
|
const setCurrentFlow = useFlowStore((state) => state.setCurrentFlow);
|
||||||
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
const flows = useFlowsManagerStore((state) => state.flows);
|
const flows = useFlowsManagerStore((state) => state.flows);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setName(currentFlow!.name);
|
setName(currentFlow!.name);
|
||||||
|
|
@ -28,35 +33,43 @@ export default function FlowSettingsModal({
|
||||||
);
|
);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const [disableSave, setDisableSave] = useState(true);
|
const [disableSave, setDisableSave] = useState(true);
|
||||||
|
const shouldAutosave = process.env.LANGFLOW_AUTO_SAVE !== "false";
|
||||||
function handleClick(): void {
|
function handleClick(): void {
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
currentFlow!.name = name;
|
if (!currentFlow) return;
|
||||||
currentFlow!.description = description;
|
const newFlow = cloneDeep(currentFlow);
|
||||||
currentFlow!.endpoint_name =
|
newFlow.name = name;
|
||||||
|
newFlow.description = description;
|
||||||
|
newFlow.endpoint_name =
|
||||||
endpoint_name && endpoint_name.length > 0 ? endpoint_name : null;
|
endpoint_name && endpoint_name.length > 0 ? endpoint_name : null;
|
||||||
saveFlow(currentFlow!)
|
if (shouldAutosave) {
|
||||||
?.then(() => {
|
saveFlow(newFlow)
|
||||||
setOpen(false);
|
?.then(() => {
|
||||||
setIsSaving(false);
|
setOpen(false);
|
||||||
})
|
setIsSaving(false);
|
||||||
.catch((err) => {
|
setSuccessData({ title: "Changes saved successfully" });
|
||||||
useAlertStore.getState().setErrorData({
|
})
|
||||||
title: "Error while saving changes",
|
.catch(() => {
|
||||||
list: [err?.response?.data.detail ?? ""],
|
setIsSaving(false);
|
||||||
});
|
});
|
||||||
console.error(err);
|
} else {
|
||||||
setIsSaving(false);
|
setCurrentFlow(newFlow);
|
||||||
});
|
setOpen(false);
|
||||||
|
setIsSaving(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const [nameLists, setNameList] = useState<string[]>([]);
|
const [nameLists, setNameList] = useState<string[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const tempNameList: string[] = [];
|
if (flows) {
|
||||||
flows.forEach((flow: FlowType) => {
|
const tempNameList: string[] = [];
|
||||||
if ((flow.is_component ?? false) === false) tempNameList.push(flow.name);
|
flows.forEach((flow: FlowType) => {
|
||||||
});
|
if ((flow.is_component ?? false) === false)
|
||||||
setNameList(tempNameList.filter((name) => name !== currentFlow!.name));
|
tempNameList.push(flow.name);
|
||||||
|
});
|
||||||
|
setNameList(tempNameList.filter((name) => name !== currentFlow!.name));
|
||||||
|
}
|
||||||
}, [flows]);
|
}, [flows]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
22
src/frontend/src/modals/saveChangesModal/index.tsx
Normal file
22
src/frontend/src/modals/saveChangesModal/index.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { useGetTagsQuery } from "@/controllers/API/queries/store";
|
import { useGetTagsQuery } from "@/controllers/API/queries/store";
|
||||||
|
import useSaveFlow from "@/hooks/flows/use-save-flow";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import { ReactNode, useEffect, useMemo, useState } from "react";
|
import { ReactNode, useEffect, useMemo, useState } from "react";
|
||||||
import EditFlowSettings from "../../components/editFlowSettingsComponent";
|
import EditFlowSettings from "../../components/editFlowSettingsComponent";
|
||||||
|
|
@ -8,13 +9,11 @@ import { Button } from "../../components/ui/button";
|
||||||
import { Checkbox } from "../../components/ui/checkbox";
|
import { Checkbox } from "../../components/ui/checkbox";
|
||||||
import {
|
import {
|
||||||
getStoreComponents,
|
getStoreComponents,
|
||||||
getStoreTags,
|
|
||||||
saveFlowStore,
|
saveFlowStore,
|
||||||
updateFlowStore,
|
updateFlowStore,
|
||||||
} from "../../controllers/API";
|
} from "../../controllers/API";
|
||||||
import useAlertStore from "../../stores/alertStore";
|
import useAlertStore from "../../stores/alertStore";
|
||||||
import { useDarkStore } from "../../stores/darkStore";
|
import { useDarkStore } from "../../stores/darkStore";
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
|
||||||
import { useStoreStore } from "../../stores/storeStore";
|
import { useStoreStore } from "../../stores/storeStore";
|
||||||
import { FlowType } from "../../types/flow";
|
import { FlowType } from "../../types/flow";
|
||||||
import {
|
import {
|
||||||
|
|
@ -59,7 +58,7 @@ export default function ShareModal({
|
||||||
const [unavaliableNames, setUnavaliableNames] = useState<
|
const [unavaliableNames, setUnavaliableNames] = useState<
|
||||||
{ id: string; name: string }[]
|
{ id: string; name: string }[]
|
||||||
>([]);
|
>([]);
|
||||||
const saveFlow = useFlowsManagerStore((state) => state.saveFlow);
|
const saveFlow = useSaveFlow();
|
||||||
const { data, isFetching } = useGetTagsQuery();
|
const { data, isFetching } = useGetTagsQuery();
|
||||||
|
|
||||||
const [loadingNames, setLoadingNames] = useState(false);
|
const [loadingNames, setLoadingNames] = useState(false);
|
||||||
|
|
@ -105,7 +104,7 @@ export default function ShareModal({
|
||||||
|
|
||||||
function successShare() {
|
function successShare() {
|
||||||
if (!is_component) {
|
if (!is_component) {
|
||||||
saveFlow(flow!, true);
|
saveFlow(flow);
|
||||||
}
|
}
|
||||||
setSuccessData({
|
setSuccessData({
|
||||||
title: `${is_component ? "Component" : "Flow"} shared successfully!`,
|
title: `${is_component ? "Component" : "Flow"} shared successfully!`,
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ import { AuthContext } from "../../contexts/authContext";
|
||||||
import ConfirmationModal from "../../modals/confirmationModal";
|
import ConfirmationModal from "../../modals/confirmationModal";
|
||||||
import UserManagementModal from "../../modals/userManagementModal";
|
import UserManagementModal from "../../modals/userManagementModal";
|
||||||
import useAlertStore from "../../stores/alertStore";
|
import useAlertStore from "../../stores/alertStore";
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
|
||||||
import { Users } from "../../types/api";
|
import { Users } from "../../types/api";
|
||||||
import { UserInputType } from "../../types/components";
|
import { UserInputType } from "../../types/components";
|
||||||
|
|
||||||
|
|
@ -51,19 +50,11 @@ export default function AdminPage() {
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const { userData } = useContext(AuthContext);
|
const { userData } = useContext(AuthContext);
|
||||||
const [totalRowsCount, setTotalRowsCount] = useState(0);
|
const [totalRowsCount, setTotalRowsCount] = useState(0);
|
||||||
const setCurrentFlowId = useFlowsManagerStore(
|
|
||||||
(state) => state.setCurrentFlowId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { mutate: mutateDeleteUser } = useDeleteUsers();
|
const { mutate: mutateDeleteUser } = useDeleteUsers();
|
||||||
const { mutate: mutateUpdateUser } = useUpdateUser();
|
const { mutate: mutateUpdateUser } = useUpdateUser();
|
||||||
const { mutate: mutateAddUser } = useAddUser();
|
const { mutate: mutateAddUser } = useAddUser();
|
||||||
|
|
||||||
// set null id
|
|
||||||
useEffect(() => {
|
|
||||||
setCurrentFlowId("");
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const userList = useRef([]);
|
const userList = useRef([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import LoadingComponent from "@/components/loadingComponent";
|
import LoadingComponent from "@/components/loadingComponent";
|
||||||
import { useGetBuildsQuery } from "@/controllers/API/queries/_builds";
|
import { useGetBuildsQuery } from "@/controllers/API/queries/_builds";
|
||||||
|
import useAutoSaveFlow from "@/hooks/flows/use-autosave-flow";
|
||||||
import useUploadFlow from "@/hooks/flows/use-upload-flow";
|
import useUploadFlow from "@/hooks/flows/use-upload-flow";
|
||||||
import { getInputsAndOutputs } from "@/utils/storeUtils";
|
|
||||||
import _, { cloneDeep } from "lodash";
|
import _, { cloneDeep } from "lodash";
|
||||||
import {
|
import {
|
||||||
KeyboardEvent,
|
KeyboardEvent,
|
||||||
|
|
@ -36,10 +36,9 @@ import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||||
import { useShortcutsStore } from "../../../../stores/shortcuts";
|
import { useShortcutsStore } from "../../../../stores/shortcuts";
|
||||||
import { useTypesStore } from "../../../../stores/typesStore";
|
import { useTypesStore } from "../../../../stores/typesStore";
|
||||||
import { APIClassType } from "../../../../types/api";
|
import { APIClassType } from "../../../../types/api";
|
||||||
import { FlowType, NodeType } from "../../../../types/flow";
|
import { NodeType } from "../../../../types/flow";
|
||||||
import {
|
import {
|
||||||
checkOldComponents,
|
checkOldComponents,
|
||||||
cleanEdges,
|
|
||||||
generateFlow,
|
generateFlow,
|
||||||
generateNodeFromFlow,
|
generateNodeFromFlow,
|
||||||
getNodeId,
|
getNodeId,
|
||||||
|
|
@ -57,17 +56,9 @@ const nodeTypes = {
|
||||||
genericNode: GenericNode,
|
genericNode: GenericNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Page({
|
export default function Page({ view }: { view?: boolean }): JSX.Element {
|
||||||
flow,
|
|
||||||
view,
|
|
||||||
}: {
|
|
||||||
flow: FlowType;
|
|
||||||
view?: boolean;
|
|
||||||
}): JSX.Element {
|
|
||||||
const uploadFlow = useUploadFlow();
|
const uploadFlow = useUploadFlow();
|
||||||
const autoSaveCurrentFlow = useFlowsManagerStore(
|
const autoSaveFlow = useAutoSaveFlow();
|
||||||
(state) => state.autoSaveCurrentFlow,
|
|
||||||
);
|
|
||||||
const types = useTypesStore((state) => state.types);
|
const types = useTypesStore((state) => state.types);
|
||||||
const templates = useTypesStore((state) => state.templates);
|
const templates = useTypesStore((state) => state.templates);
|
||||||
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
|
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
|
||||||
|
|
@ -83,7 +74,6 @@ export default function Page({
|
||||||
const onEdgesChange = useFlowStore((state) => state.onEdgesChange);
|
const onEdgesChange = useFlowStore((state) => state.onEdgesChange);
|
||||||
const setNodes = useFlowStore((state) => state.setNodes);
|
const setNodes = useFlowStore((state) => state.setNodes);
|
||||||
const setEdges = useFlowStore((state) => state.setEdges);
|
const setEdges = useFlowStore((state) => state.setEdges);
|
||||||
const cleanFlow = useFlowStore((state) => state.cleanFlow);
|
|
||||||
const deleteNode = useFlowStore((state) => state.deleteNode);
|
const deleteNode = useFlowStore((state) => state.deleteNode);
|
||||||
const deleteEdge = useFlowStore((state) => state.deleteEdge);
|
const deleteEdge = useFlowStore((state) => state.deleteEdge);
|
||||||
const undo = useFlowsManagerStore((state) => state.undo);
|
const undo = useFlowsManagerStore((state) => state.undo);
|
||||||
|
|
@ -97,22 +87,17 @@ export default function Page({
|
||||||
(state) => state.setLastCopiedSelection,
|
(state) => state.setLastCopiedSelection,
|
||||||
);
|
);
|
||||||
const onConnect = useFlowStore((state) => state.onConnect);
|
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 setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const setNoticeData = useAlertStore((state) => state.setNoticeData);
|
const setNoticeData = useAlertStore((state) => state.setNoticeData);
|
||||||
|
const updateCurrentFlow = useFlowStore((state) => state.updateCurrentFlow);
|
||||||
const [selectionMenuVisible, setSelectionMenuVisible] = useState(false);
|
const [selectionMenuVisible, setSelectionMenuVisible] = useState(false);
|
||||||
const edgeUpdateSuccessful = useRef(true);
|
const edgeUpdateSuccessful = useRef(true);
|
||||||
|
|
||||||
const position = useRef({ x: 0, y: 0 });
|
const position = useRef({ x: 0, y: 0 });
|
||||||
const [lastSelection, setLastSelection] =
|
const [lastSelection, setLastSelection] =
|
||||||
useState<OnSelectionChangeParams | null>(null);
|
useState<OnSelectionChangeParams | null>(null);
|
||||||
|
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
||||||
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 };
|
|
||||||
|
|
||||||
function handleGroupNode() {
|
function handleGroupNode() {
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
|
|
@ -159,16 +144,6 @@ export default function Page({
|
||||||
};
|
};
|
||||||
}, [lastCopiedSelection, lastSelection, takeSnapshot, selectionMenuVisible]);
|
}, [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 { isFetching, refetch } = useGetBuildsQuery({});
|
||||||
|
|
||||||
const showCanvas =
|
const showCanvas =
|
||||||
|
|
@ -177,19 +152,9 @@ export default function Page({
|
||||||
!isFetching;
|
!isFetching;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isFetching) {
|
refetch();
|
||||||
let newEdges = cleanEdges(flow.data!.nodes, flow.data!.edges);
|
useFlowStore.setState({ autoSaveFlow });
|
||||||
setNodes(flow.data!.nodes);
|
if (checkOldComponents({ nodes })) {
|
||||||
setEdges(newEdges);
|
|
||||||
setFlowState(undefined);
|
|
||||||
setInputs(inputs);
|
|
||||||
setOutputs(outputs);
|
|
||||||
setHasIO(inputs.length > 0 || outputs.length > 0);
|
|
||||||
}
|
|
||||||
}, [isFetching]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (checkOldComponents({ nodes: flow?.data?.nodes ?? [] })) {
|
|
||||||
setNoticeData({
|
setNoticeData({
|
||||||
title:
|
title:
|
||||||
"Components created before Langflow 1.0 may be unstable. Ensure components are up to date.",
|
"Components created before Langflow 1.0 may be unstable. Ensure components are up to date.",
|
||||||
|
|
@ -197,13 +162,6 @@ export default function Page({
|
||||||
}
|
}
|
||||||
}, [currentFlowId]);
|
}, [currentFlowId]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
refetch();
|
|
||||||
return () => {
|
|
||||||
cleanFlow();
|
|
||||||
};
|
|
||||||
}, [currentFlowId]);
|
|
||||||
|
|
||||||
function handleUndo(e: KeyboardEvent) {
|
function handleUndo(e: KeyboardEvent) {
|
||||||
if (!isWrappedWithClass(e, "noflow")) {
|
if (!isWrappedWithClass(e, "noflow")) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -338,15 +296,17 @@ export default function Page({
|
||||||
// 👉 you can place your event handlers here
|
// 👉 you can place your event handlers here
|
||||||
}, [takeSnapshot]);
|
}, [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(() => {
|
const onMoveEnd: OnMove = useCallback(() => {
|
||||||
// 👇 make moving the canvas undoable
|
// 👇 make moving the canvas undoable
|
||||||
autoSaveCurrentFlow(nodes, edges, reactFlowInstance?.getViewport()!);
|
autoSaveFlow();
|
||||||
}, [takeSnapshot, autoSaveCurrentFlow, nodes, edges, reactFlowInstance]);
|
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(() => {
|
const onSelectionDragStart: SelectionDragHandler = useCallback(() => {
|
||||||
// 👇 make dragging a selection undoable
|
// 👇 make dragging a selection undoable
|
||||||
|
|
@ -497,13 +457,13 @@ export default function Page({
|
||||||
onEdgeUpdateStart={onEdgeUpdateStart}
|
onEdgeUpdateStart={onEdgeUpdateStart}
|
||||||
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
||||||
onNodeDragStart={onNodeDragStart}
|
onNodeDragStart={onNodeDragStart}
|
||||||
onNodeDragStop={onNodeDragStop}
|
|
||||||
onSelectionDragStart={onSelectionDragStart}
|
onSelectionDragStart={onSelectionDragStart}
|
||||||
onSelectionEnd={onSelectionEnd}
|
onSelectionEnd={onSelectionEnd}
|
||||||
onSelectionStart={onSelectionStart}
|
onSelectionStart={onSelectionStart}
|
||||||
connectionLineComponent={ConnectionLineComponent}
|
connectionLineComponent={ConnectionLineComponent}
|
||||||
onDragOver={onDragOver}
|
onDragOver={onDragOver}
|
||||||
onMoveEnd={onMoveEnd}
|
onMoveEnd={onMoveEnd}
|
||||||
|
onNodeDragStop={onNodeDragStop}
|
||||||
onDrop={onDrop}
|
onDrop={onDrop}
|
||||||
onSelectionChange={onSelectionChange}
|
onSelectionChange={onSelectionChange}
|
||||||
deleteKeyCode={[]}
|
deleteKeyCode={[]}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,13 @@
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import { LinkIcon, SparklesIcon } from "lucide-react";
|
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 IconComponent from "../../../../components/genericIconComponent";
|
||||||
import ShadTooltip from "../../../../components/shadTooltipComponent";
|
import ShadTooltip from "../../../../components/shadTooltipComponent";
|
||||||
import { Input } from "../../../../components/ui/input";
|
import { Input } from "../../../../components/ui/input";
|
||||||
import { Separator } from "../../../../components/ui/separator";
|
import { Separator } from "../../../../components/ui/separator";
|
||||||
import { PRIORITY_SIDEBAR_ORDER } from "../../../../constants/constants";
|
import { PRIORITY_SIDEBAR_ORDER } from "../../../../constants/constants";
|
||||||
import ExportModal from "../../../../modals/exportModal";
|
|
||||||
import ShareModal from "../../../../modals/shareModal";
|
|
||||||
import useAlertStore from "../../../../stores/alertStore";
|
import useAlertStore from "../../../../stores/alertStore";
|
||||||
import useFlowStore from "../../../../stores/flowStore";
|
import useFlowStore from "../../../../stores/flowStore";
|
||||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
|
||||||
import { useStoreStore } from "../../../../stores/storeStore";
|
|
||||||
import { useTypesStore } from "../../../../stores/typesStore";
|
import { useTypesStore } from "../../../../stores/typesStore";
|
||||||
import { APIClassType, APIObjectType } from "../../../../types/api";
|
import { APIClassType, APIObjectType } from "../../../../types/api";
|
||||||
import {
|
import {
|
||||||
|
|
@ -19,7 +15,7 @@ import {
|
||||||
nodeIconsLucide,
|
nodeIconsLucide,
|
||||||
nodeNames,
|
nodeNames,
|
||||||
} from "../../../../utils/styleUtils";
|
} from "../../../../utils/styleUtils";
|
||||||
import { classNames, removeCountFromString } from "../../../../utils/utils";
|
import { removeCountFromString } from "../../../../utils/utils";
|
||||||
import DisclosureComponent from "../DisclosureComponent";
|
import DisclosureComponent from "../DisclosureComponent";
|
||||||
import ParentDisclosureComponent from "../ParentDisclosureComponent";
|
import ParentDisclosureComponent from "../ParentDisclosureComponent";
|
||||||
import SidebarDraggableComponent from "./sideBarDraggableComponent";
|
import SidebarDraggableComponent from "./sideBarDraggableComponent";
|
||||||
|
|
@ -31,10 +27,6 @@ export default function ExtraSidebar(): JSX.Element {
|
||||||
const templates = useTypesStore((state) => state.templates);
|
const templates = useTypesStore((state) => state.templates);
|
||||||
const getFilterEdge = useFlowStore((state) => state.getFilterEdge);
|
const getFilterEdge = useFlowStore((state) => state.getFilterEdge);
|
||||||
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
|
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 setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const [dataFilter, setFilterData] = useState(data);
|
const [dataFilter, setFilterData] = useState(data);
|
||||||
|
|
@ -187,63 +179,6 @@ export default function ExtraSidebar(): JSX.Element {
|
||||||
}
|
}
|
||||||
}, [getFilterEdge, data]);
|
}, [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 (
|
return (
|
||||||
<div className="side-bar-arrangement">
|
<div className="side-bar-arrangement">
|
||||||
<div className="side-bar-search-div-placement">
|
<div className="side-bar-search-div-placement">
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ export const SidebarDraggableComponent = forwardRef(
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "delete":
|
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 });
|
if (flowId) deleteFlow({ id: flowId.id });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ export default function NodeToolbarComponent({
|
||||||
const validApiKey = useStoreStore((state) => state.validApiKey);
|
const validApiKey = useStoreStore((state) => state.validApiKey);
|
||||||
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
||||||
const unselectAll = useFlowStore((state) => state.unselectAll);
|
const unselectAll = useFlowStore((state) => state.unselectAll);
|
||||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
||||||
const addFlow = useAddFlow();
|
const addFlow = useAddFlow();
|
||||||
|
|
||||||
function handleMinimizeWShortcut(e: KeyboardEvent) {
|
function handleMinimizeWShortcut(e: KeyboardEvent) {
|
||||||
|
|
@ -179,7 +179,7 @@ export default function NodeToolbarComponent({
|
||||||
function handleFreezeAll(e: KeyboardEvent) {
|
function handleFreezeAll(e: KeyboardEvent) {
|
||||||
if (isWrappedWithClass(e, "noflow")) return;
|
if (isWrappedWithClass(e, "noflow")) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
FreezeAllVertices({ flowId: currentFlow!.id, stopNodeId: data.id });
|
FreezeAllVertices({ flowId: currentFlowId, stopNodeId: data.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
const advanced = useShortcutsStore((state) => state.advanced);
|
const advanced = useShortcutsStore((state) => state.advanced);
|
||||||
|
|
@ -280,7 +280,7 @@ export default function NodeToolbarComponent({
|
||||||
}));
|
}));
|
||||||
break;
|
break;
|
||||||
case "freezeAll":
|
case "freezeAll":
|
||||||
FreezeAllVertices({ flowId: currentFlow!.id, stopNodeId: data.id });
|
FreezeAllVertices({ flowId: currentFlowId, stopNodeId: data.id });
|
||||||
break;
|
break;
|
||||||
case "code":
|
case "code":
|
||||||
setOpenModal(!openModal);
|
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!),
|
Object.values(flow).includes(data.node?.display_name!),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -480,7 +480,7 @@ export default function NodeToolbarComponent({
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
FreezeAllVertices({
|
FreezeAllVertices({
|
||||||
flowId: currentFlow!.id,
|
flowId: currentFlowId,
|
||||||
stopNodeId: data.id,
|
stopNodeId: data.id,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|
@ -543,7 +543,8 @@ export default function NodeToolbarComponent({
|
||||||
<SelectItem value={"save"}>
|
<SelectItem value={"save"}>
|
||||||
<ToolbarSelectItem
|
<ToolbarSelectItem
|
||||||
shortcut={
|
shortcut={
|
||||||
shortcuts.find((obj) => obj.name === "Save")?.shortcut!
|
shortcuts.find((obj) => obj.name === "Save Component")
|
||||||
|
?.shortcut!
|
||||||
}
|
}
|
||||||
value={"Save"}
|
value={"Save"}
|
||||||
icon={"SaveAll"}
|
icon={"SaveAll"}
|
||||||
|
|
@ -712,7 +713,7 @@ export default function NodeToolbarComponent({
|
||||||
});
|
});
|
||||||
setSuccessData({ title: `${data.id} successfully overridden!` });
|
setSuccessData({ title: `${data.id} successfully overridden!` });
|
||||||
}}
|
}}
|
||||||
onClose={setShowOverrideModal}
|
onClose={() => setShowOverrideModal(false)}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
addFlow({
|
addFlow({
|
||||||
flow: flowComponent,
|
flow: flowComponent,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
import FeatureFlags from "@/../feature-config.json";
|
import FeatureFlags from "@/../feature-config.json";
|
||||||
import { useGetGlobalVariables } from "@/controllers/API/queries/variables";
|
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 { useEffect } from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useBlocker, useNavigate, useParams } from "react-router-dom";
|
||||||
import FlowToolbar from "../../components/chatComponent";
|
import FlowToolbar from "../../components/chatComponent";
|
||||||
import Header from "../../components/headerComponent";
|
import Header from "../../components/headerComponent";
|
||||||
import { useDarkStore } from "../../stores/darkStore";
|
import { useDarkStore } from "../../stores/darkStore";
|
||||||
|
|
@ -11,34 +14,66 @@ import Page from "./components/PageComponent";
|
||||||
import ExtraSidebar from "./components/extraSidebarComponent";
|
import ExtraSidebar from "./components/extraSidebarComponent";
|
||||||
|
|
||||||
export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
|
export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
|
||||||
const setCurrentFlowId = useFlowsManagerStore(
|
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
|
||||||
(state) => state.setCurrentFlowId,
|
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 version = useDarkStore((state) => state.version);
|
||||||
const setOnFlowPage = useFlowStore((state) => state.setOnFlowPage);
|
const setOnFlowPage = useFlowStore((state) => state.setOnFlowPage);
|
||||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
useGetGlobalVariables();
|
useGetGlobalVariables();
|
||||||
|
const saveFlow = useSaveFlow();
|
||||||
|
|
||||||
const flows = useFlowsManagerStore((state) => state.flows);
|
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
|
// Set flow tab id
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const isAnExistingFlow = flows.some((flow) => flow.id === id);
|
if (flows && currentFlowId === "") {
|
||||||
|
const isAnExistingFlow = flows.find((flow) => flow.id === id);
|
||||||
|
|
||||||
if (!isAnExistingFlow) {
|
if (!isAnExistingFlow) {
|
||||||
navigate("/all");
|
navigate("/all");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentFlow(isAnExistingFlow);
|
||||||
}
|
}
|
||||||
|
}, [id, flows]);
|
||||||
|
|
||||||
setCurrentFlowId(id!);
|
useEffect(() => {
|
||||||
setOnFlowPage(true);
|
setOnFlowPage(true);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
setOnFlowPage(false);
|
setOnFlowPage(false);
|
||||||
|
setCurrentFlow();
|
||||||
};
|
};
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header />
|
<Header />
|
||||||
|
|
@ -49,7 +84,7 @@ export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
|
||||||
<main className="flex flex-1">
|
<main className="flex flex-1">
|
||||||
{/* Primary column */}
|
{/* Primary column */}
|
||||||
<div className="h-full w-full">
|
<div className="h-full w-full">
|
||||||
<Page flow={currentFlow} />
|
<Page />
|
||||||
</div>
|
</div>
|
||||||
{!view && <FlowToolbar />}
|
{!view && <FlowToolbar />}
|
||||||
</main>
|
</main>
|
||||||
|
|
@ -66,6 +101,13 @@ export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
|
||||||
<div className={version ? "mt-2" : "mt-1"}>⛓️ v{version}</div>
|
<div className={version ? "mt-2" : "mt-1"}>⛓️ v{version}</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
{blocker.state === "blocked" && (
|
||||||
|
<SaveChangesModal
|
||||||
|
onSave={handleSave}
|
||||||
|
onCancel={() => (blocker.reset ? blocker.reset() : null)}
|
||||||
|
onProceed={() => (blocker.proceed ? blocker.proceed() : null)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ export default function ComponentsComponent({
|
||||||
handleSelectAll(false);
|
handleSelectAll(false);
|
||||||
setShouldSelectAll(true);
|
setShouldSelectAll(true);
|
||||||
getFolderById(folderId ? folderId : myCollectionId);
|
getFolderById(folderId ? folderId : myCollectionId);
|
||||||
}, [location]);
|
}, [location, folderId, myCollectionId]);
|
||||||
|
|
||||||
useFilteredFlows(flowsFromFolder!, searchFlowsComponents, setAllFlows);
|
useFilteredFlows(flowsFromFolder!, searchFlowsComponents, setAllFlows);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useDeleteFolders } from "@/controllers/API/queries/folders";
|
import { useDeleteFolders } from "@/controllers/API/queries/folders";
|
||||||
import useAlertStore from "@/stores/alertStore";
|
import useAlertStore from "@/stores/alertStore";
|
||||||
import { useEffect, useState } from "react";
|
import { useState } from "react";
|
||||||
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||||
import DropdownButton from "../../../../components/dropdownButtonComponent";
|
import DropdownButton from "../../../../components/dropdownButtonComponent";
|
||||||
import PageLayout from "../../../../components/pageLayout";
|
import PageLayout from "../../../../components/pageLayout";
|
||||||
|
|
@ -9,17 +9,12 @@ import {
|
||||||
MY_COLLECTION_DESC,
|
MY_COLLECTION_DESC,
|
||||||
USER_PROJECTS_HEADER,
|
USER_PROJECTS_HEADER,
|
||||||
} from "../../../../constants/constants";
|
} from "../../../../constants/constants";
|
||||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
|
||||||
import { useFolderStore } from "../../../../stores/foldersStore";
|
import { useFolderStore } from "../../../../stores/foldersStore";
|
||||||
import ModalsComponent from "../../components/modalsComponent";
|
import ModalsComponent from "../../components/modalsComponent";
|
||||||
import useDropdownOptions from "../../hooks/use-dropdown-options";
|
import useDropdownOptions from "../../hooks/use-dropdown-options";
|
||||||
import { getFolderById } from "../../services";
|
import { getFolderById } from "../../services";
|
||||||
|
|
||||||
export default function HomePage(): JSX.Element {
|
export default function HomePage(): JSX.Element {
|
||||||
const setCurrentFlowId = useFlowsManagerStore(
|
|
||||||
(state) => state.setCurrentFlowId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const pathname = location.pathname;
|
const pathname = location.pathname;
|
||||||
const [openModal, setOpenModal] = useState(false);
|
const [openModal, setOpenModal] = useState(false);
|
||||||
|
|
@ -35,10 +30,6 @@ export default function HomePage(): JSX.Element {
|
||||||
const myCollectionId = useFolderStore((state) => state.myCollectionId);
|
const myCollectionId = useFolderStore((state) => state.myCollectionId);
|
||||||
const getFoldersApi = useFolderStore((state) => state.getFoldersApi);
|
const getFoldersApi = useFolderStore((state) => state.getFoldersApi);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setCurrentFlowId("");
|
|
||||||
}, [pathname]);
|
|
||||||
|
|
||||||
const dropdownOptions = useDropdownOptions({
|
const dropdownOptions = useDropdownOptions({
|
||||||
navigate,
|
navigate,
|
||||||
is_component,
|
is_component,
|
||||||
|
|
|
||||||
|
|
@ -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 { useParams } from "react-router-dom";
|
||||||
import LoadingComponent from "../../components/loadingComponent";
|
import LoadingComponent from "../../components/loadingComponent";
|
||||||
import { getComponent } from "../../controllers/API";
|
import { getComponent } from "../../controllers/API";
|
||||||
import IOModal from "../../modals/IOModal";
|
import IOModal from "../../modals/IOModal";
|
||||||
import useFlowStore from "../../stores/flowStore";
|
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
import cloneFLowWithParent from "../../utils/storeUtils";
|
import cloneFLowWithParent from "../../utils/storeUtils";
|
||||||
|
|
||||||
export default function PlaygroundPage() {
|
export default function PlaygroundPage() {
|
||||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
|
||||||
const getFlowById = useFlowsManagerStore((state) => state.getFlowById);
|
const getFlowById = useFlowsManagerStore((state) => state.getFlowById);
|
||||||
const setCurrentFlowId = useFlowsManagerStore(
|
const flows = useFlowsManagerStore((state) => state.flows);
|
||||||
(state) => state.setCurrentFlowId,
|
|
||||||
);
|
|
||||||
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
|
||||||
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
|
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
|
||||||
const setNodes = useFlowStore((state) => state.setNodes);
|
const currentSavedFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||||
const setEdges = useFlowStore((state) => state.setEdges);
|
const validApiKey = useStoreStore((state) => state.validApiKey);
|
||||||
const cleanFlowPool = useFlowStore((state) => state.CleanFlowPool);
|
|
||||||
|
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
async function getFlowData() {
|
async function getFlowData() {
|
||||||
const res = await getComponent(id!);
|
const res = await getComponent(id!);
|
||||||
const newFlow = cloneFLowWithParent(res, res.id, false, true);
|
const newFlow = cloneFLowWithParent(res, res.id, false, true);
|
||||||
|
|
@ -29,32 +23,22 @@ export default function PlaygroundPage() {
|
||||||
|
|
||||||
// Set flow tab id
|
// Set flow tab id
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (getFlowById(id!)) {
|
if (flows) {
|
||||||
setCurrentFlowId(id!);
|
const flow = getFlowById(id!);
|
||||||
} else {
|
if (flow) {
|
||||||
getFlowData().then((flow) => {
|
|
||||||
setCurrentFlow(flow);
|
setCurrentFlow(flow);
|
||||||
});
|
} else {
|
||||||
|
if (validApiKey)
|
||||||
|
getFlowData().then((flow) => {
|
||||||
|
setCurrentFlow(flow);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [id]);
|
}, [id, flows, validApiKey]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (currentFlow) {
|
|
||||||
setNodes(currentFlow?.data?.nodes ?? [], true);
|
|
||||||
setEdges(currentFlow?.data?.edges ?? [], true);
|
|
||||||
cleanFlowPool();
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
setNodes([], true);
|
|
||||||
setEdges([], true);
|
|
||||||
cleanFlowPool();
|
|
||||||
};
|
|
||||||
}, [currentFlow]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center align-middle">
|
<div className="flex h-full w-full flex-col items-center justify-center align-middle">
|
||||||
{loading ? (
|
{!currentSavedFlow ? (
|
||||||
<div>
|
<div>
|
||||||
<LoadingComponent remSize={24}></LoadingComponent>
|
<LoadingComponent remSize={24}></LoadingComponent>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import {
|
||||||
} from "@/controllers/API/queries/auth";
|
} from "@/controllers/API/queries/auth";
|
||||||
import * as Form from "@radix-ui/react-form";
|
import * as Form from "@radix-ui/react-form";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
import Header from "../../components/headerComponent";
|
import Header from "../../components/headerComponent";
|
||||||
import InputComponent from "../../components/inputComponent";
|
import InputComponent from "../../components/inputComponent";
|
||||||
|
|
@ -18,7 +18,6 @@ import {
|
||||||
import { CONTROL_PATCH_USER_STATE } from "../../constants/constants";
|
import { CONTROL_PATCH_USER_STATE } from "../../constants/constants";
|
||||||
import { AuthContext } from "../../contexts/authContext";
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
import useAlertStore from "../../stores/alertStore";
|
import useAlertStore from "../../stores/alertStore";
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
|
||||||
import {
|
import {
|
||||||
inputHandlerEventType,
|
inputHandlerEventType,
|
||||||
patchUserInputStateType,
|
patchUserInputStateType,
|
||||||
|
|
@ -26,18 +25,9 @@ import {
|
||||||
import { gradients } from "../../utils/styleUtils";
|
import { gradients } from "../../utils/styleUtils";
|
||||||
import GradientChooserComponent from "../SettingsPage/pages/GeneralPage/components/ProfilePictureForm/components/profilePictureChooserComponent";
|
import GradientChooserComponent from "../SettingsPage/pages/GeneralPage/components/ProfilePictureForm/components/profilePictureChooserComponent";
|
||||||
export default function ProfileSettingsPage(): JSX.Element {
|
export default function ProfileSettingsPage(): JSX.Element {
|
||||||
const setCurrentFlowId = useFlowsManagerStore(
|
|
||||||
(state) => state.setCurrentFlowId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [inputState, setInputState] = useState<patchUserInputStateType>(
|
const [inputState, setInputState] = useState<patchUserInputStateType>(
|
||||||
CONTROL_PATCH_USER_STATE,
|
CONTROL_PATCH_USER_STATE,
|
||||||
);
|
);
|
||||||
|
|
||||||
// set null id
|
|
||||||
useEffect(() => {
|
|
||||||
setCurrentFlowId("");
|
|
||||||
}, []);
|
|
||||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const { userData, setUserData } = useContext(AuthContext);
|
const { userData, setUserData } = useContext(AuthContext);
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,12 @@
|
||||||
import FeatureFlags from "@/../feature-config.json";
|
import FeatureFlags from "@/../feature-config.json";
|
||||||
import useAuthStore from "@/stores/authStore";
|
import useAuthStore from "@/stores/authStore";
|
||||||
import { useStoreStore } from "@/stores/storeStore";
|
import { useStoreStore } from "@/stores/storeStore";
|
||||||
import { useEffect } from "react";
|
|
||||||
import { Outlet } from "react-router-dom";
|
import { Outlet } from "react-router-dom";
|
||||||
import ForwardedIconComponent from "../../components/genericIconComponent";
|
import ForwardedIconComponent from "../../components/genericIconComponent";
|
||||||
import PageLayout from "../../components/pageLayout";
|
import PageLayout from "../../components/pageLayout";
|
||||||
import SidebarNav from "../../components/sidebarComponent";
|
import SidebarNav from "../../components/sidebarComponent";
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
|
||||||
|
|
||||||
export default function SettingsPage(): JSX.Element {
|
export default function SettingsPage(): JSX.Element {
|
||||||
const pathname = location.pathname;
|
|
||||||
const setCurrentFlowId = useFlowsManagerStore(
|
|
||||||
(state) => state.setCurrentFlowId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const autoLogin = useAuthStore((state) => state.autoLogin);
|
const autoLogin = useAuthStore((state) => state.autoLogin);
|
||||||
const hasStore = useStoreStore((state) => state.hasStore);
|
const hasStore = useStoreStore((state) => state.hasStore);
|
||||||
|
|
||||||
|
|
@ -21,10 +14,6 @@ export default function SettingsPage(): JSX.Element {
|
||||||
const showGeneralSettings =
|
const showGeneralSettings =
|
||||||
FeatureFlags.ENABLE_PROFILE_ICONS || hasStore || !autoLogin;
|
FeatureFlags.ENABLE_PROFILE_ICONS || hasStore || !autoLogin;
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setCurrentFlowId("");
|
|
||||||
}, [pathname]);
|
|
||||||
|
|
||||||
const sidebarNavItems: {
|
const sidebarNavItems: {
|
||||||
href?: string;
|
href?: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import { useParams } from "react-router-dom";
|
||||||
import { CONTROL_PATCH_USER_STATE } from "../../../../constants/constants";
|
import { CONTROL_PATCH_USER_STATE } from "../../../../constants/constants";
|
||||||
import { AuthContext } from "../../../../contexts/authContext";
|
import { AuthContext } from "../../../../contexts/authContext";
|
||||||
import useAlertStore from "../../../../stores/alertStore";
|
import useAlertStore from "../../../../stores/alertStore";
|
||||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
|
||||||
import { useStoreStore } from "../../../../stores/storeStore";
|
import { useStoreStore } from "../../../../stores/storeStore";
|
||||||
import {
|
import {
|
||||||
inputHandlerEventType,
|
inputHandlerEventType,
|
||||||
|
|
@ -31,10 +30,6 @@ import ProfilePictureFormComponent from "./components/ProfilePictureForm";
|
||||||
import StoreApiKeyFormComponent from "./components/StoreApiKeyForm";
|
import StoreApiKeyFormComponent from "./components/StoreApiKeyForm";
|
||||||
|
|
||||||
export const GeneralPage = () => {
|
export const GeneralPage = () => {
|
||||||
const setCurrentFlowId = useFlowsManagerStore(
|
|
||||||
(state) => state.setCurrentFlowId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { scrollId } = useParams();
|
const { scrollId } = useParams();
|
||||||
|
|
||||||
const [inputState, setInputState] = useState<patchUserInputStateType>(
|
const [inputState, setInputState] = useState<patchUserInputStateType>(
|
||||||
|
|
@ -112,7 +107,7 @@ export const GeneralPage = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useScrollToElement(scrollId, setCurrentFlowId);
|
useScrollToElement(scrollId);
|
||||||
|
|
||||||
const { mutate } = usePostAddApiKey({
|
const { mutate } = usePostAddApiKey({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
const useScrollToElement = (
|
const useScrollToElement = (scrollId: string | null | undefined) => {
|
||||||
scrollId: string | null | undefined,
|
|
||||||
setCurrentFlowId: (currentFlowId: string) => void,
|
|
||||||
) => {
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const element = document.getElementById(scrollId ?? "null");
|
const element = document.getElementById(scrollId ?? "null");
|
||||||
if (element) {
|
if (element) {
|
||||||
|
|
@ -11,10 +8,6 @@ const useScrollToElement = (
|
||||||
element.scrollIntoView({ behavior: "smooth" });
|
element.scrollIntoView({ behavior: "smooth" });
|
||||||
}
|
}
|
||||||
}, [scrollId]);
|
}, [scrollId]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setCurrentFlowId("");
|
|
||||||
}, [setCurrentFlowId]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useScrollToElement;
|
export default useScrollToElement;
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import {
|
||||||
} from "../../constants/alerts_constants";
|
} from "../../constants/alerts_constants";
|
||||||
import { STORE_DESC, STORE_TITLE } from "../../constants/constants";
|
import { STORE_DESC, STORE_TITLE } from "../../constants/constants";
|
||||||
import { AuthContext } from "../../contexts/authContext";
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
import { getStoreComponents, getStoreTags } from "../../controllers/API";
|
import { getStoreComponents } from "../../controllers/API";
|
||||||
import useAlertStore from "../../stores/alertStore";
|
import useAlertStore from "../../stores/alertStore";
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
import { useStoreStore } from "../../stores/storeStore";
|
import { useStoreStore } from "../../stores/storeStore";
|
||||||
|
|
@ -46,9 +46,6 @@ export default function StorePage(): JSX.Element {
|
||||||
const { apiKey } = useContext(AuthContext);
|
const { apiKey } = useContext(AuthContext);
|
||||||
|
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const setCurrentFlowId = useFlowsManagerStore(
|
|
||||||
(state) => state.setCurrentFlowId,
|
|
||||||
);
|
|
||||||
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
@ -148,11 +145,6 @@ export default function StorePage(): JSX.Element {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a null id
|
|
||||||
useEffect(() => {
|
|
||||||
setCurrentFlowId("");
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
function resetPagination() {
|
function resetPagination() {
|
||||||
setPageIndex(1);
|
setPageIndex(1);
|
||||||
setPageSize(12);
|
setPageSize(12);
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,47 @@
|
||||||
|
import { useGetGlobalVariables } from "@/controllers/API/queries/variables";
|
||||||
|
import useFlowStore from "@/stores/flowStore";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
import Page from "../FlowPage/components/PageComponent";
|
import Page from "../FlowPage/components/PageComponent";
|
||||||
|
|
||||||
export default function ViewPage() {
|
export default function ViewPage() {
|
||||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
|
||||||
const setCurrentFlowId = useFlowsManagerStore(
|
|
||||||
(state) => state.setCurrentFlowId,
|
const setOnFlowPage = useFlowStore((state) => state.setOnFlowPage);
|
||||||
);
|
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
useGetGlobalVariables();
|
||||||
|
|
||||||
|
const flows = useFlowsManagerStore((state) => state.flows);
|
||||||
|
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
||||||
|
|
||||||
// Set flow tab id
|
// Set flow tab id
|
||||||
useEffect(() => {
|
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]);
|
}, [id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flow-page-positioning">
|
<div className="flow-page-positioning">
|
||||||
{currentFlow && <Page view flow={currentFlow} />}
|
<Page view />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,20 @@
|
||||||
import FeatureFlags from "@/../feature-config.json";
|
import { lazy } from "react";
|
||||||
import useAuthStore from "@/stores/authStore";
|
import {
|
||||||
import { useStoreStore } from "@/stores/storeStore";
|
createBrowserRouter,
|
||||||
import { Suspense, lazy } from "react";
|
createRoutesFromElements,
|
||||||
import { Navigate, Route, Routes } from "react-router-dom";
|
Navigate,
|
||||||
|
Route,
|
||||||
|
} from "react-router-dom";
|
||||||
|
import App from "./App";
|
||||||
import { ProtectedAdminRoute } from "./components/authAdminGuard";
|
import { ProtectedAdminRoute } from "./components/authAdminGuard";
|
||||||
import { ProtectedRoute } from "./components/authGuard";
|
import { ProtectedRoute } from "./components/authGuard";
|
||||||
import { ProtectedLoginRoute } from "./components/authLoginGuard";
|
import { ProtectedLoginRoute } from "./components/authLoginGuard";
|
||||||
|
import { AuthSettingsGuard } from "./components/authSettingsGuard";
|
||||||
import { CatchAllRoute } from "./components/catchAllRoutes";
|
import { CatchAllRoute } from "./components/catchAllRoutes";
|
||||||
import LoadingComponent from "./components/loadingComponent";
|
|
||||||
import { StoreGuard } from "./components/storeGuard";
|
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 AdminPage = lazy(() => import("./pages/AdminPage"));
|
||||||
const LoginAdminPage = lazy(() => import("./pages/AdminPage/LoginPage"));
|
const LoginAdminPage = lazy(() => import("./pages/AdminPage/LoginPage"));
|
||||||
const ApiKeysPage = lazy(
|
const ApiKeysPage = lazy(
|
||||||
|
|
@ -38,184 +42,161 @@ const SignUp = lazy(() => import("./pages/SignUpPage"));
|
||||||
const StorePage = lazy(() => import("./pages/StorePage"));
|
const StorePage = lazy(() => import("./pages/StorePage"));
|
||||||
const ViewPage = lazy(() => import("./pages/ViewPage"));
|
const ViewPage = lazy(() => import("./pages/ViewPage"));
|
||||||
|
|
||||||
const Router = () => {
|
const router = createBrowserRouter(
|
||||||
const autoLogin = useAuthStore((state) => state.autoLogin);
|
createRoutesFromElements([
|
||||||
const hasStore = useStoreStore((state) => state.hasStore);
|
<Route path="" element={<App />}>
|
||||||
|
<Route
|
||||||
// Hides the General settings if there is nothing to show
|
path="/"
|
||||||
const showGeneralSettings =
|
element={
|
||||||
FeatureFlags.ENABLE_PROFILE_ICONS || hasStore || !autoLogin;
|
<ProtectedRoute>
|
||||||
|
<HomePage />
|
||||||
return (
|
</ProtectedRoute>
|
||||||
<Suspense
|
}
|
||||||
fallback={
|
>
|
||||||
<div className="loading-page-panel">
|
<Route index element={<Navigate replace to={"all"} />} />
|
||||||
<LoadingComponent remSize={50} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Routes>
|
|
||||||
<Route
|
<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={
|
element={
|
||||||
<ProtectedRoute>
|
<AuthSettingsGuard>
|
||||||
<HomePage />
|
<GeneralPage />
|
||||||
</ProtectedRoute>
|
</AuthSettingsGuard>
|
||||||
}
|
}
|
||||||
>
|
/>
|
||||||
<Route index element={<Navigate replace to={"all"} />} />
|
<Route path="shortcuts" element={<ShortcutsPage />} />
|
||||||
<Route
|
<Route path="messages" element={<MessagesPage />} />
|
||||||
path="flows/*"
|
</Route>
|
||||||
element={<MyCollectionComponent key="flows" type="flow" />}
|
<Route
|
||||||
/>
|
path="/store"
|
||||||
<Route
|
element={
|
||||||
path="components/*"
|
<ProtectedRoute>
|
||||||
element={
|
<StoreGuard>
|
||||||
<MyCollectionComponent key="components" type="component" />
|
<StorePage />
|
||||||
}
|
</StoreGuard>
|
||||||
/>
|
</ProtectedRoute>
|
||||||
<Route
|
}
|
||||||
path="all/*"
|
/>
|
||||||
element={<MyCollectionComponent key="all" type="all" />}
|
<Route
|
||||||
/>
|
path="/store/:id/"
|
||||||
</Route>
|
element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<StoreGuard>
|
||||||
|
<StorePage />
|
||||||
|
</StoreGuard>
|
||||||
|
</ProtectedRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route path="/playground/:id/">
|
||||||
<Route
|
<Route
|
||||||
path="/settings"
|
path=""
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
<SettingsPage />
|
<PlaygroundPage />
|
||||||
</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>
|
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
</Route>
|
||||||
path="/store/:id/"
|
<Route path="/flow/: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="*"
|
path="*"
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
<CatchAllRoute />
|
<FlowPage />
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/login"
|
path=""
|
||||||
element={
|
element={
|
||||||
<ProtectedLoginRoute>
|
<ProtectedRoute>
|
||||||
<LoginPage />
|
<FlowPage />
|
||||||
</ProtectedLoginRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/signup"
|
path="view"
|
||||||
element={
|
element={
|
||||||
<ProtectedLoginRoute>
|
<ProtectedRoute>
|
||||||
<SignUp />
|
<ViewPage />
|
||||||
</ProtectedLoginRoute>
|
</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
|
<Route
|
||||||
path="/login/admin"
|
path="delete"
|
||||||
element={
|
element={
|
||||||
<ProtectedLoginRoute>
|
<ProtectedRoute>
|
||||||
<LoginAdminPage />
|
<DeleteAccountPage />
|
||||||
</ProtectedLoginRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
/>
|
></Route>
|
||||||
|
</Route>
|
||||||
|
</Route>,
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
<Route
|
export default router;
|
||||||
path="/admin"
|
|
||||||
element={
|
|
||||||
<ProtectedAdminRoute>
|
|
||||||
<AdminPage />
|
|
||||||
</ProtectedAdminRoute>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route path="/account">
|
|
||||||
<Route
|
|
||||||
path="delete"
|
|
||||||
element={
|
|
||||||
<ProtectedRoute>
|
|
||||||
<DeleteAccountPage />
|
|
||||||
</ProtectedRoute>
|
|
||||||
}
|
|
||||||
></Route>
|
|
||||||
</Route>
|
|
||||||
</Routes>
|
|
||||||
</Suspense>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Router;
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { getRepoStars, getVersion } from "../controllers/API";
|
import { getRepoStars } from "../controllers/API";
|
||||||
import { DarkStoreType } from "../types/zustand/dark";
|
import { DarkStoreType } from "../types/zustand/dark";
|
||||||
|
|
||||||
const startedStars = Number(window.localStorage.getItem("githubStars")) ?? 0;
|
const startedStars = Number(window.localStorage.getItem("githubStars")) ?? 0;
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import {
|
||||||
MISSED_ERROR_ALERT,
|
MISSED_ERROR_ALERT,
|
||||||
} from "../constants/alerts_constants";
|
} from "../constants/alerts_constants";
|
||||||
import { BuildStatus } from "../constants/enums";
|
import { BuildStatus } from "../constants/enums";
|
||||||
import { getFlowPool } from "../controllers/API";
|
|
||||||
import { VertexBuildTypeAPI } from "../types/api";
|
import { VertexBuildTypeAPI } from "../types/api";
|
||||||
import { ChatInputType, ChatOutputType } from "../types/chat";
|
import { ChatInputType, ChatOutputType } from "../types/chat";
|
||||||
import {
|
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
|
// 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) => ({
|
const useFlowStore = create<FlowStoreType>((set, get) => ({
|
||||||
|
autoSaveFlow: undefined,
|
||||||
componentsToUpdate: false,
|
componentsToUpdate: false,
|
||||||
updateComponentsToUpdate: (nodes) => {
|
updateComponentsToUpdate: (nodes) => {
|
||||||
let outdatedNodes = false;
|
let outdatedNodes = false;
|
||||||
|
|
@ -153,8 +153,10 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
||||||
setPending: (isPending) => {
|
setPending: (isPending) => {
|
||||||
set({ isPending });
|
set({ isPending });
|
||||||
},
|
},
|
||||||
resetFlow: ({ nodes, edges, viewport }) => {
|
resetFlow: (flow) => {
|
||||||
const currentFlow = useFlowsManagerStore.getState().currentFlow;
|
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);
|
let brokenEdges = detectBrokenEdgesEdges(nodes, edges);
|
||||||
if (brokenEdges.length > 0) {
|
if (brokenEdges.length > 0) {
|
||||||
useAlertStore.getState().setErrorData({
|
useAlertStore.getState().setErrorData({
|
||||||
|
|
@ -172,13 +174,10 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs,
|
||||||
hasIO: inputs.length > 0 || outputs.length > 0,
|
hasIO: inputs.length > 0 || outputs.length > 0,
|
||||||
|
flowPool: {},
|
||||||
|
currentFlow: flow,
|
||||||
});
|
});
|
||||||
get().reactFlowInstance!.setViewport(viewport);
|
get().reactFlowInstance?.setViewport(viewport);
|
||||||
if (currentFlow) {
|
|
||||||
getFlowPool({ flowId: currentFlow.id }).then((flowPool) => {
|
|
||||||
set({ flowPool: flowPool.data.vertex_builds });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
setIsBuilding: (isBuilding) => {
|
setIsBuilding: (isBuilding) => {
|
||||||
set({ isBuilding });
|
set({ isBuilding });
|
||||||
|
|
@ -195,6 +194,16 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
||||||
},
|
},
|
||||||
setReactFlowInstance: (newState) => {
|
setReactFlowInstance: (newState) => {
|
||||||
set({ reactFlowInstance: 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[]) => {
|
onNodesChange: (changes: NodeChange[]) => {
|
||||||
set({
|
set({
|
||||||
|
|
@ -206,7 +215,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
||||||
edges: applyEdgeChanges(changes, get().edges),
|
edges: applyEdgeChanges(changes, get().edges),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setNodes: (change, skipSave = false) => {
|
setNodes: (change) => {
|
||||||
let newChange = typeof change === "function" ? change(get().nodes) : change;
|
let newChange = typeof change === "function" ? change(get().nodes) : change;
|
||||||
let newEdges = cleanEdges(newChange, get().edges);
|
let newEdges = cleanEdges(newChange, get().edges);
|
||||||
const { inputs, outputs } = getInputsAndOutputs(newChange);
|
const { inputs, outputs } = getInputsAndOutputs(newChange);
|
||||||
|
|
@ -219,30 +228,18 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
||||||
outputs,
|
outputs,
|
||||||
hasIO: inputs.length > 0 || outputs.length > 0,
|
hasIO: inputs.length > 0 || outputs.length > 0,
|
||||||
});
|
});
|
||||||
|
if (get().autoSaveFlow) {
|
||||||
const flowsManager = useFlowsManagerStore.getState();
|
get().autoSaveFlow!();
|
||||||
if (!get().isBuilding && !skipSave && get().onFlowPage) {
|
|
||||||
flowsManager.autoSaveCurrentFlow(
|
|
||||||
newChange,
|
|
||||||
newEdges,
|
|
||||||
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setEdges: (change, skipSave = false) => {
|
setEdges: (change) => {
|
||||||
let newChange = typeof change === "function" ? change(get().edges) : change;
|
let newChange = typeof change === "function" ? change(get().edges) : change;
|
||||||
set({
|
set({
|
||||||
edges: newChange,
|
edges: newChange,
|
||||||
flowState: undefined,
|
flowState: undefined,
|
||||||
});
|
});
|
||||||
|
if (get().autoSaveFlow) {
|
||||||
const flowsManager = useFlowsManagerStore.getState();
|
get().autoSaveFlow!();
|
||||||
if (!get().isBuilding && !skipSave && get().onFlowPage) {
|
|
||||||
flowsManager.autoSaveCurrentFlow(
|
|
||||||
get().nodes,
|
|
||||||
newChange,
|
|
||||||
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setNode: (
|
setNode: (
|
||||||
|
|
@ -485,13 +482,6 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
||||||
|
|
||||||
return newEdges;
|
return newEdges;
|
||||||
});
|
});
|
||||||
useFlowsManagerStore
|
|
||||||
.getState()
|
|
||||||
.autoSaveCurrentFlow(
|
|
||||||
get().nodes,
|
|
||||||
newEdges,
|
|
||||||
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 },
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
unselectAll: () => {
|
unselectAll: () => {
|
||||||
let newNodes = cloneDeep(get().nodes);
|
let newNodes = cloneDeep(get().nodes);
|
||||||
|
|
@ -672,8 +662,9 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
||||||
useFlowStore.getState().updateBuildStatus(idList, BuildStatus.BUILDING);
|
useFlowStore.getState().updateBuildStatus(idList, BuildStatus.BUILDING);
|
||||||
},
|
},
|
||||||
onValidateNodes: validateSubgraph,
|
onValidateNodes: validateSubgraph,
|
||||||
nodes: get().onFlowPage ? get().nodes : undefined,
|
nodes: get().nodes || undefined,
|
||||||
edges: get().onFlowPage ? get().edges : undefined,
|
edges: get().edges || undefined,
|
||||||
|
logBuilds: get().onFlowPage,
|
||||||
});
|
});
|
||||||
get().setIsBuilding(false);
|
get().setIsBuilding(false);
|
||||||
get().setLockChat(false);
|
get().setLockChat(false);
|
||||||
|
|
@ -744,6 +735,27 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
||||||
});
|
});
|
||||||
set({ flowBuildStatus: newFlowBuildStatus });
|
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;
|
export default useFlowStore;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,6 @@
|
||||||
import { AxiosError } from "axios";
|
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import pDebounce from "p-debounce";
|
|
||||||
import { Edge, Node, Viewport } from "reactflow";
|
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { SAVE_DEBOUNCE_TIME } from "../constants/constants";
|
import { readFlowsFromDatabase } from "../controllers/API";
|
||||||
import {
|
|
||||||
readFlowsFromDatabase,
|
|
||||||
updateFlowInDatabase,
|
|
||||||
} from "../controllers/API";
|
|
||||||
import { FlowType } from "../types/flow";
|
import { FlowType } from "../types/flow";
|
||||||
import {
|
import {
|
||||||
FlowsManagerStoreType,
|
FlowsManagerStoreType,
|
||||||
|
|
@ -36,22 +29,17 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
||||||
set({ examples });
|
set({ examples });
|
||||||
},
|
},
|
||||||
currentFlowId: "",
|
currentFlowId: "",
|
||||||
setCurrentFlow: (flow: FlowType) => {
|
setCurrentFlow: (flow: FlowType | undefined) => {
|
||||||
set((state) => ({
|
set({
|
||||||
currentFlow: flow,
|
currentFlow: flow,
|
||||||
currentFlowId: flow.id,
|
currentFlowId: flow?.id ?? "",
|
||||||
}));
|
});
|
||||||
|
useFlowStore.getState().resetFlow(flow);
|
||||||
},
|
},
|
||||||
getFlowById: (id: string) => {
|
getFlowById: (id: string) => {
|
||||||
return get().flows.find((flow) => flow.id === id);
|
return get().flows?.find((flow) => flow.id === id);
|
||||||
},
|
},
|
||||||
setCurrentFlowId: (currentFlowId: string) => {
|
flows: undefined,
|
||||||
set((state) => ({
|
|
||||||
currentFlowId,
|
|
||||||
currentFlow: state.flows.find((flow) => flow.id === currentFlowId),
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
flows: [],
|
|
||||||
allFlows: [],
|
allFlows: [],
|
||||||
setAllFlows: (allFlows: FlowType[]) => {
|
setAllFlows: (allFlows: FlowType[]) => {
|
||||||
set({ allFlows });
|
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: () => {
|
takeSnapshot: () => {
|
||||||
const currentFlowId = get().currentFlowId;
|
const currentFlowId = get().currentFlowId;
|
||||||
// push the current graph to the past state
|
// push the current graph to the past state
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ export const useShortcutsStore = create<shortcutsStoreType>((set, get) => ({
|
||||||
duplicate: "mod+d",
|
duplicate: "mod+d",
|
||||||
component: "mod+shift+s",
|
component: "mod+shift+s",
|
||||||
docs: "mod+shift+d",
|
docs: "mod+shift+d",
|
||||||
save: "mod+s",
|
changes: "mod+s",
|
||||||
|
save: "mod+alt+s",
|
||||||
delete: "backspace",
|
delete: "backspace",
|
||||||
group: "mod+g",
|
group: "mod+g",
|
||||||
cut: "mod+x",
|
cut: "mod+x",
|
||||||
|
|
|
||||||
|
|
@ -435,6 +435,7 @@ export type ConfirmationModalType = {
|
||||||
title: string;
|
title: string;
|
||||||
titleHeader?: string;
|
titleHeader?: string;
|
||||||
destructive?: boolean;
|
destructive?: boolean;
|
||||||
|
destructiveCancel?: boolean;
|
||||||
modalContentTitle?: string;
|
modalContentTitle?: string;
|
||||||
cancelText: string;
|
cancelText: string;
|
||||||
confirmationText: string;
|
confirmationText: string;
|
||||||
|
|
@ -446,7 +447,7 @@ export type ConfirmationModalType = {
|
||||||
index?: number;
|
index?: number;
|
||||||
onConfirm: (index, data) => void;
|
onConfirm: (index, data) => void;
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
onClose?: (close: boolean) => void;
|
onClose?: () => void;
|
||||||
size?:
|
size?:
|
||||||
| "x-small"
|
| "x-small"
|
||||||
| "smaller"
|
| "smaller"
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ export type shortcutsStoreType = {
|
||||||
duplicate: string;
|
duplicate: string;
|
||||||
component: string;
|
component: string;
|
||||||
docs: string;
|
docs: string;
|
||||||
|
changes: string;
|
||||||
save: string;
|
save: string;
|
||||||
delete: string;
|
delete: string;
|
||||||
update: string;
|
update: string;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { FlowType } from "@/types/flow";
|
||||||
import {
|
import {
|
||||||
Connection,
|
Connection,
|
||||||
Edge,
|
Edge,
|
||||||
|
|
@ -53,6 +54,7 @@ export type FlowPoolType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FlowStoreType = {
|
export type FlowStoreType = {
|
||||||
|
autoSaveFlow: (() => void) | undefined;
|
||||||
componentsToUpdate: boolean;
|
componentsToUpdate: boolean;
|
||||||
updateComponentsToUpdate: (nodes: Node[]) => void;
|
updateComponentsToUpdate: (nodes: Node[]) => void;
|
||||||
onFlowPage: boolean;
|
onFlowPage: boolean;
|
||||||
|
|
@ -83,11 +85,7 @@ export type FlowStoreType = {
|
||||||
isPending: boolean;
|
isPending: boolean;
|
||||||
setIsBuilding: (isBuilding: boolean) => void;
|
setIsBuilding: (isBuilding: boolean) => void;
|
||||||
setPending: (isPending: boolean) => void;
|
setPending: (isPending: boolean) => void;
|
||||||
resetFlow: (flow: {
|
resetFlow: (flow: FlowType | undefined) => void;
|
||||||
nodes: Node[];
|
|
||||||
edges: Edge[];
|
|
||||||
viewport: Viewport;
|
|
||||||
}) => void;
|
|
||||||
reactFlowInstance: ReactFlowInstance | null;
|
reactFlowInstance: ReactFlowInstance | null;
|
||||||
setReactFlowInstance: (newState: ReactFlowInstance) => void;
|
setReactFlowInstance: (newState: ReactFlowInstance) => void;
|
||||||
flowState: FlowState | undefined;
|
flowState: FlowState | undefined;
|
||||||
|
|
@ -101,14 +99,8 @@ export type FlowStoreType = {
|
||||||
edges: Edge[];
|
edges: Edge[];
|
||||||
onNodesChange: OnNodesChange;
|
onNodesChange: OnNodesChange;
|
||||||
onEdgesChange: OnEdgesChange;
|
onEdgesChange: OnEdgesChange;
|
||||||
setNodes: (
|
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void;
|
||||||
update: Node[] | ((oldState: Node[]) => Node[]),
|
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void;
|
||||||
skipSave?: boolean,
|
|
||||||
) => void;
|
|
||||||
setEdges: (
|
|
||||||
update: Edge[] | ((oldState: Edge[]) => Edge[]),
|
|
||||||
skipSave?: boolean,
|
|
||||||
) => void;
|
|
||||||
setNode: (
|
setNode: (
|
||||||
id: string,
|
id: string,
|
||||||
update: Node | ((oldState: Node) => Node),
|
update: Node | ((oldState: Node) => Node),
|
||||||
|
|
@ -177,4 +169,15 @@ export type FlowStoreType = {
|
||||||
setLockChat: (lock: boolean) => void;
|
setLockChat: (lock: boolean) => void;
|
||||||
lockChat: boolean;
|
lockChat: boolean;
|
||||||
updateFreezeStatus: (nodeIds: string[], freeze: boolean) => void;
|
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;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,24 @@
|
||||||
import { Edge, Node, Viewport } from "reactflow";
|
|
||||||
import { FlowType } from "../../flow";
|
import { FlowType } from "../../flow";
|
||||||
|
|
||||||
export type FlowsManagerStoreType = {
|
export type FlowsManagerStoreType = {
|
||||||
getFlowById: (id: string) => FlowType | undefined;
|
getFlowById: (id: string) => FlowType | undefined;
|
||||||
flows: Array<FlowType>;
|
flows: Array<FlowType> | undefined;
|
||||||
allFlows: Array<FlowType>;
|
allFlows: Array<FlowType>;
|
||||||
setAllFlows: (flows: FlowType[]) => void;
|
setAllFlows: (flows: FlowType[]) => void;
|
||||||
setFlows: (flows: FlowType[]) => void;
|
setFlows: (flows: FlowType[]) => void;
|
||||||
currentFlow: FlowType | undefined;
|
currentFlow: FlowType | undefined;
|
||||||
currentFlowId: string;
|
currentFlowId: string;
|
||||||
setCurrentFlowId: (currentFlowId: string) => void;
|
|
||||||
saveLoading: boolean;
|
saveLoading: boolean;
|
||||||
setSaveLoading: (saveLoading: boolean) => void;
|
setSaveLoading: (saveLoading: boolean) => void;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
setIsLoading: (isLoading: boolean) => void;
|
setIsLoading: (isLoading: boolean) => void;
|
||||||
refreshFlows: () => Promise<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;
|
undo: () => void;
|
||||||
redo: () => void;
|
redo: () => void;
|
||||||
takeSnapshot: () => void;
|
takeSnapshot: () => void;
|
||||||
examples: Array<FlowType>;
|
examples: Array<FlowType>;
|
||||||
setExamples: (examples: FlowType[]) => void;
|
setExamples: (examples: FlowType[]) => void;
|
||||||
setCurrentFlow: (flow: FlowType) => void;
|
setCurrentFlow: (flow?: FlowType) => void;
|
||||||
setSearchFlowsComponents: (search: string) => void;
|
setSearchFlowsComponents: (search: string) => void;
|
||||||
searchFlowsComponents: string;
|
searchFlowsComponents: string;
|
||||||
selectedFlowsComponentsCards: string[];
|
selectedFlowsComponentsCards: string[];
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ type BuildVerticesParams = {
|
||||||
onValidateNodes?: (nodes: string[]) => void;
|
onValidateNodes?: (nodes: string[]) => void;
|
||||||
nodes?: Node[];
|
nodes?: Node[];
|
||||||
edges?: Edge[];
|
edges?: Edge[];
|
||||||
|
logBuilds?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getInactiveVertexData(vertexId: string): VertexBuildTypeAPI {
|
function getInactiveVertexData(vertexId: string): VertexBuildTypeAPI {
|
||||||
|
|
@ -146,6 +147,7 @@ export async function buildFlowVertices({
|
||||||
onValidateNodes,
|
onValidateNodes,
|
||||||
nodes,
|
nodes,
|
||||||
edges,
|
edges,
|
||||||
|
logBuilds,
|
||||||
setLockChat,
|
setLockChat,
|
||||||
}: BuildVerticesParams) {
|
}: BuildVerticesParams) {
|
||||||
let url = `${BASE_URL_API}build/${flowId}/flow?`;
|
let url = `${BASE_URL_API}build/${flowId}/flow?`;
|
||||||
|
|
@ -155,6 +157,9 @@ export async function buildFlowVertices({
|
||||||
if (stopNodeId) {
|
if (stopNodeId) {
|
||||||
url = `${url}&stop_component_id=${stopNodeId}`;
|
url = `${url}&stop_component_id=${stopNodeId}`;
|
||||||
}
|
}
|
||||||
|
if (logBuilds !== undefined) {
|
||||||
|
url = `${url}&log_builds=${logBuilds}`;
|
||||||
|
}
|
||||||
const postData = {};
|
const postData = {};
|
||||||
if (typeof input_value !== "undefined") {
|
if (typeof input_value !== "undefined") {
|
||||||
postData["inputs"] = { input_value: input_value };
|
postData["inputs"] = { input_value: input_value };
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,6 @@ test("flowSettings", async ({ page }) => {
|
||||||
|
|
||||||
await page.getByTestId("save-flow-settings").click();
|
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.getByText("Changes saved successfully").isVisible();
|
||||||
|
|
||||||
await page.getByTestId("flow_name").click();
|
await page.getByTestId("flow_name").click();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { expect, test } from "@playwright/test";
|
import { test } from "@playwright/test";
|
||||||
import * as dotenv from "dotenv";
|
import * as dotenv from "dotenv";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
|
|
@ -111,7 +111,6 @@ test("should share component with share button", async ({ page }) => {
|
||||||
.inputValue();
|
.inputValue();
|
||||||
await page.getByPlaceholder("Flow name").fill(randomName);
|
await page.getByPlaceholder("Flow name").fill(randomName);
|
||||||
await page.getByText("Save").last().click();
|
await page.getByText("Save").last().click();
|
||||||
await page.getByText("Close").last().click();
|
|
||||||
|
|
||||||
await page.waitForSelector('[data-testid="shared-button-flow"]', {
|
await page.waitForSelector('[data-testid="shared-button-flow"]', {
|
||||||
timeout: 100000,
|
timeout: 100000,
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,9 @@ export default defineConfig(({ mode }) => {
|
||||||
outDir: "build",
|
outDir: "build",
|
||||||
},
|
},
|
||||||
define: {
|
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.BACKEND_URL": JSON.stringify(process.env.BACKEND_URL),
|
||||||
"process.env.ACCESS_TOKEN_EXPIRE_SECONDS": JSON.stringify(
|
"process.env.ACCESS_TOKEN_EXPIRE_SECONDS": JSON.stringify(
|
||||||
process.env.ACCESS_TOKEN_EXPIRE_SECONDS,
|
process.env.ACCESS_TOKEN_EXPIRE_SECONDS,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue