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