diff --git a/src/backend/langflow/components/llms/OllamaLLM.py b/src/backend/langflow/components/llms/OllamaLLM.py index 9bf00ad5d..fae8e1917 100644 --- a/src/backend/langflow/components/llms/OllamaLLM.py +++ b/src/backend/langflow/components/llms/OllamaLLM.py @@ -150,10 +150,9 @@ class OllamaLLM(CustomComponent): "top_k": top_k, "top_p": top_p, } - - # None Value remove - llm_params = {k: v for k, v in llm_params.items() if v is not None} + # None Value remove + llm_params = {k: v for k, v in llm_params.items() if v is not None} try: llm = Ollama(**llm_params) diff --git a/src/backend/langflow/components/retrievers/VectaraSelfQueryRetriver.py b/src/backend/langflow/components/retrievers/VectaraSelfQueryRetriver.py index bc8d78909..26afd765c 100644 --- a/src/backend/langflow/components/retrievers/VectaraSelfQueryRetriver.py +++ b/src/backend/langflow/components/retrievers/VectaraSelfQueryRetriver.py @@ -15,29 +15,21 @@ class VectaraSelfQueryRetriverComponent(CustomComponent): display_name: str = "Vectara Self Query Retriever for Vectara Vector Store" description: str = "Implementation of Vectara Self Query Retriever" - documentation = ( - "https://python.langchain.com/docs/integrations/retrievers/self_query/vectara_self_query" - ) + documentation = "https://python.langchain.com/docs/integrations/retrievers/self_query/vectara_self_query" beta = True field_config = { "code": {"show": True}, - "vectorstore": { - "display_name": "Vector Store", - "info": "Input Vectara Vectore Store" - }, - "llm": { - "display_name": "LLM", - "info": "For self query retriever" - }, - "document_content_description":{ - "display_name": "Document Content Description", + "vectorstore": {"display_name": "Vector Store", "info": "Input Vectara Vectore Store"}, + "llm": {"display_name": "LLM", "info": "For self query retriever"}, + "document_content_description": { + "display_name": "Document Content Description", "info": "For self query retriever", - }, + }, "metadata_field_info": { - "display_name": "Metadata Field Info", - "info": "Each metadata field info is a string in the form of key value pair dictionary containing additional search metadata.\nExample input: {\"name\":\"speech\",\"description\":\"what name of the speech\",\"type\":\"string or list[string]\"}.\nThe keys should remain constant(name, description, type)", - }, + "display_name": "Metadata Field Info", + "info": 'Each metadata field info is a string in the form of key value pair dictionary containing additional search metadata.\nExample input: {"name":"speech","description":"what name of the speech","type":"string or list[string]"}.\nThe keys should remain constant(name, description, type)', + }, } def build( @@ -47,24 +39,19 @@ class VectaraSelfQueryRetriverComponent(CustomComponent): llm: BaseLanguageModel, metadata_field_info: List[str], ) -> BaseRetriever: - metadata_field_obj = [] for meta in metadata_field_info: meta_obj = json.loads(meta) - if 'name' not in meta_obj or 'description' not in meta_obj or 'type' not in meta_obj : - raise Exception('Incorrect metadata field info format.') + if "name" not in meta_obj or "description" not in meta_obj or "type" not in meta_obj: + raise Exception("Incorrect metadata field info format.") attribute_info = AttributeInfo( - name = meta_obj['name'], - description = meta_obj['description'], - type = meta_obj['type'], + name=meta_obj["name"], + description=meta_obj["description"], + type=meta_obj["type"], ) metadata_field_obj.append(attribute_info) return SelfQueryRetriever.from_llm( - llm, - vectorstore, - document_content_description, - metadata_field_obj, - verbose=True - ) \ No newline at end of file + llm, vectorstore, document_content_description, metadata_field_obj, verbose=True + ) diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 0898ad58d..7ccb9d1c7 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -16,7 +16,6 @@ import { FETCH_ERROR_MESSAGE, } from "./constants/constants"; import { alertContext } from "./contexts/alertContext"; -import { FlowsContext } from "./contexts/flowsContext"; import { locationContext } from "./contexts/locationContext"; import { typesContext } from "./contexts/typesContext"; import Router from "./routes"; @@ -30,7 +29,6 @@ export default function App() { setShowSideBar(true); setIsStackedOpen(true); }, [location.pathname, setCurrent, setIsStackedOpen, setShowSideBar]); - const { hardReset } = useContext(FlowsContext); const { errorData, @@ -136,10 +134,7 @@ export default function App() {
{ - window.localStorage.removeItem("tabsData"); - window.localStorage.clear(); - hardReset(); - window.location.href = window.location.href; + // any reset function }} FallbackComponent={CrashErrorComponent} > diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index 6208faff5..fc0ed31a9 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -31,6 +31,7 @@ import { FlowsContext } from "../../../../contexts/flowsContext"; import { typesContext } from "../../../../contexts/typesContext"; import { undoRedoContext } from "../../../../contexts/undoRedoContext"; import { postCustomComponentUpdate } from "../../../../controllers/API"; +import useFlow from "../../../../stores/flowManagerStore"; import { APIClassType } from "../../../../types/api"; import { ParameterComponentType } from "../../../../types/components"; import { NodeDataType } from "../../../../types/flow"; @@ -70,13 +71,8 @@ export default function ParameterComponent({ const { setErrorData, modalContextOpen } = useContext(alertContext); const updateNodeInternals = useUpdateNodeInternals(); const [position, setPosition] = useState(0); - const { - tabId, - flows, - nodes, - edges, - setNode, - } = useContext(FlowsContext); + const { tabId, flows } = useContext(FlowsContext); + const { nodes, edges, setNode } = useFlow(); const flow = flows.find((flow) => flow.id === tabId)?.data?.nodes ?? null; @@ -133,8 +129,8 @@ export default function ParameterComponent({ let newNode = cloneDeep(oldNode); newNode.data = { - ...newNode.data - } + ...newNode.data, + }; newNode.data.node.template[name].value = newValue; diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index eb6d27137..6d343f9cd 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -7,7 +7,6 @@ import InputComponent from "../../components/inputComponent"; import { Textarea } from "../../components/ui/textarea"; import { priorityFields } from "../../constants/constants"; import { useSSE } from "../../contexts/SSEContext"; -import { FlowsContext } from "../../contexts/flowsContext"; import { typesContext } from "../../contexts/typesContext"; import { undoRedoContext } from "../../contexts/undoRedoContext"; import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent"; @@ -17,6 +16,7 @@ import { handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils"; import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils"; import { classNames, cn, getFieldTitle } from "../../utils/utils"; import ParameterComponent from "./components/parameterComponent"; +import useFlow from "../../stores/flowManagerStore"; export default function GenericNode({ data, @@ -30,7 +30,7 @@ export default function GenericNode({ yPos: number; }): JSX.Element { const { types } = useContext(typesContext); - const { deleteNode, setNode } = useContext(FlowsContext); + const { deleteNode, setNode } = useFlow(); const name = nodeIconsLucide[data.type] ? data.type : types[data.type]; const [inputName, setInputName] = useState(false); const [nodeName, setNodeName] = useState(data.node!.display_name); diff --git a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx index eb8f281a6..65531e995 100644 --- a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx +++ b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx @@ -3,7 +3,6 @@ import { useContext, useState } from "react"; import Loading from "../../../components/ui/loading"; import { useSSE } from "../../../contexts/SSEContext"; import { alertContext } from "../../../contexts/alertContext"; -import { typesContext } from "../../../contexts/typesContext"; import { postBuildInit } from "../../../controllers/API"; import { FlowType } from "../../../types/flow"; @@ -13,6 +12,7 @@ import { FlowsState } from "../../../types/tabs"; import { validateNodes } from "../../../utils/reactflowUtils"; import RadialProgressComponent from "../../RadialProgress"; import IconComponent from "../../genericIconComponent"; +import useFlow from "../../../stores/flowManagerStore"; export default function BuildTrigger({ open, @@ -25,7 +25,8 @@ export default function BuildTrigger({ isBuilt: boolean; }): JSX.Element { const { updateSSEData, isBuilding, setIsBuilding, sseData } = useSSE(); - const { setTabsState, saveFlow, nodes, edges } = useContext(FlowsContext); + const { setTabsState, saveFlow } = useContext(FlowsContext); + const { nodes, edges } = useFlow(); const { setErrorData, setSuccessData } = useContext(alertContext); const [isIconTouched, setIsIconTouched] = useState(false); const eventClick = isBuilding ? "pointer-events-none" : ""; @@ -36,10 +37,7 @@ export default function BuildTrigger({ if (isBuilding) { return; } - const errors = validateNodes( - nodes, - edges - ); + const errors = validateNodes(nodes, edges); if (errors.length > 0) { setErrorData({ title: "Oops! Looks like you missed something", diff --git a/src/frontend/src/components/chatComponent/index.tsx b/src/frontend/src/components/chatComponent/index.tsx index 129e1f55c..b07732a64 100644 --- a/src/frontend/src/components/chatComponent/index.tsx +++ b/src/frontend/src/components/chatComponent/index.tsx @@ -9,11 +9,14 @@ import { FlowsContext } from "../../contexts/flowsContext"; import { getBuildStatus } from "../../controllers/API"; import FormModal from "../../modals/formModal"; import { NodeType } from "../../types/flow"; +import useFlow from "../../stores/flowManagerStore"; export default function Chat({ flow }: ChatType): JSX.Element { const [open, setOpen] = useState(false); const [canOpen, setCanOpen] = useState(false); - const { tabsState, isBuilt, setIsBuilt, isPending } = useContext(FlowsContext); + const { isBuilt, setIsBuilt, isPending } = useFlow(); + const { tabsState } = + useContext(FlowsContext); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { diff --git a/src/frontend/src/components/codeTabsComponent/index.tsx b/src/frontend/src/components/codeTabsComponent/index.tsx index 6a9228f2d..e4e4bbb60 100644 --- a/src/frontend/src/components/codeTabsComponent/index.tsx +++ b/src/frontend/src/components/codeTabsComponent/index.tsx @@ -28,7 +28,6 @@ import { TabsTrigger, } from "../../components/ui/tabs"; import { LANGFLOW_SUPPORTED_TYPES } from "../../constants/constants"; -import { FlowsContext } from "../../contexts/flowsContext"; import { useDarkStore } from "../../stores/darkStore"; import { codeTabsPropsType } from "../../types/components"; import { @@ -41,6 +40,7 @@ import { classNames } from "../../utils/utils"; import DictComponent from "../dictComponent"; import IconComponent from "../genericIconComponent"; import KeypairListComponent from "../keypairListComponent"; +import useFlow from "../../stores/flowManagerStore"; export default function CodeTabsComponent({ flow, @@ -53,9 +53,9 @@ export default function CodeTabsComponent({ const [isCopied, setIsCopied] = useState(false); const [data, setData] = useState(flow ? flow["data"]!["nodes"] : null); const [openAccordion, setOpenAccordion] = useState([]); - const dark = useDarkStore((state) => state.dark); + const {dark} = useDarkStore(); - const { setNodes } = useContext(FlowsContext); + const { setNodes } = useFlow(); const [errorDuplicateKey, setErrorDuplicateKey] = useState(false); useEffect(() => { diff --git a/src/frontend/src/components/headerComponent/index.tsx b/src/frontend/src/components/headerComponent/index.tsx index 3aa7e6340..d330500cd 100644 --- a/src/frontend/src/components/headerComponent/index.tsx +++ b/src/frontend/src/components/headerComponent/index.tsx @@ -8,7 +8,6 @@ import { AuthContext } from "../../contexts/authContext"; import { FlowsContext } from "../../contexts/flowsContext"; import { useDarkStore } from "../../stores/darkStore"; -import { useStoreStore } from "../../stores/storeStore"; import { gradients } from "../../utils/styleUtils"; import IconComponent from "../genericIconComponent"; import { Button } from "../ui/button"; @@ -30,12 +29,7 @@ export default function Header(): JSX.Element { const { logout, autoLogin, isAdmin, userData } = useContext(AuthContext); const navigate = useNavigate(); - const hasStore = useStoreStore((state) => state.hasStore); - - const dark = useDarkStore((state) => state.dark); - const setDark = useDarkStore((state) => state.updateDark); - const stars = useDarkStore((state) => state.stars); - const gradientIndex = useDarkStore((state) => state.gradientIndex); + const { dark, setDark, stars, gradientIndex } = useDarkStore(); useEffect(() => { if (dark) { diff --git a/src/frontend/src/components/pageLayout/index.tsx b/src/frontend/src/components/pageLayout/index.tsx index 9af894730..fb10f9550 100644 --- a/src/frontend/src/components/pageLayout/index.tsx +++ b/src/frontend/src/components/pageLayout/index.tsx @@ -12,7 +12,7 @@ export default function PageLayout({ description: string; children: React.ReactNode; button?: React.ReactNode; - betaIcon: boolean; + betaIcon?: boolean; }) { return (
diff --git a/src/frontend/src/components/tagsSelectorComponent/index.tsx b/src/frontend/src/components/tagsSelectorComponent/index.tsx index ce9da74ab..a6ffd953a 100644 --- a/src/frontend/src/components/tagsSelectorComponent/index.tsx +++ b/src/frontend/src/components/tagsSelectorComponent/index.tsx @@ -1,5 +1,4 @@ import { useEffect, useRef, useState } from "react"; -import { useDarkStore } from "../../stores/darkStore"; import { cn } from "../../utils/utils"; import { Badge } from "../ui/badge"; @@ -24,7 +23,6 @@ export function TagsSelector({ : selectedTags.filter((_, i) => i !== index); setSelectedTags(newArray); }; - const dark = useDarkStore((state) => state.dark); const scrollContainerRef = useRef(null); const fadeContainerRef = useRef(null); diff --git a/src/frontend/src/contexts/flowsContext.tsx b/src/frontend/src/contexts/flowsContext.tsx index 6487ad334..97cdfc77b 100644 --- a/src/frontend/src/contexts/flowsContext.tsx +++ b/src/frontend/src/contexts/flowsContext.tsx @@ -31,7 +31,7 @@ import { updateFlowInDatabase, uploadFlowsToDatabase, } from "../controllers/API"; -import { APIClassType, APITemplateType } from "../types/api"; +import { APIClassType } from "../types/api"; import { tweakType } from "../types/components"; import { FlowType, @@ -46,12 +46,13 @@ import { checkOldEdgesHandles, cleanEdges, createFlowComponent, + processFlowEdges, removeFileNameFromComponents, scapeJSONParse, scapedJSONStringfy, + updateEdges, updateEdgesHandleIds, updateIds, - updateTemplate, } from "../utils/reactflowUtils"; import { createRandomKey, @@ -61,10 +62,12 @@ import { import { alertContext } from "./alertContext"; import { AuthContext } from "./authContext"; import { typesContext } from "./typesContext"; +import useFlow from "../stores/flowManagerStore"; const uid = new ShortUniqueId({ length: 5 }); const FlowsContextInitialValue: FlowsContextType = { + //Remove tab id and get current id from url tabId: "", setTabId: (index: string) => {}, isLoading: true, @@ -75,41 +78,16 @@ const FlowsContextInitialValue: FlowsContextType = { flowData?: FlowType, override?: boolean ) => "", - deleteNode: () => {}, - deleteEdge: () => {}, - incrementNodeId: () => uid(), downloadFlow: (flow: FlowType) => {}, downloadFlows: () => {}, uploadFlows: () => {}, uploadFlow: async () => "", - isBuilt: false, - setIsBuilt: (state: boolean) => {}, - hardReset: () => {}, saveFlow: async (flow?: FlowType, silent?: boolean) => {}, - lastCopiedSelection: null, - setLastCopiedSelection: (selection: any) => {}, - isPending: false, - setPending: (pending: boolean) => {}, tabsState: {}, setTabsState: () => {}, - getNodeId: (nodeType: string) => "", - setTweak: (tweak: any) => {}, - getTweak: [], - paste: ( - selection: { nodes: any; edges: any }, - position: { x: number; y: number; paneX?: number; paneY?: number } - ) => {}, saveComponent: async (component: NodeDataType, override: boolean) => "", deleteComponent: (key: string) => {}, version: "", - nodes: [], - setNodes: () => {}, - setNode: () => {}, - getNode: () => undefined, - onNodesChange: () => {}, - edges: [], - setEdges: () => {}, - onEdgesChange: () => {}, }; export const FlowsContext = createContext( @@ -123,107 +101,10 @@ export function FlowsProvider({ children }: { children: ReactNode }) { const [tabId, setTabId] = useState(""); const [isLoading, setIsLoading] = useState(false); const [flows, setFlows] = useState>([]); - const [id, setId] = useState(uid()); - const { reactFlowInstance, setData } = useContext(typesContext); - const [lastCopiedSelection, setLastCopiedSelection] = useState<{ - nodes: any; - edges: any; - } | null>(null); + const { setData } = useContext(typesContext); const [tabsState, setTabsState] = useState({}); - const [getTweak, setTweak] = useState([]); - const [nodes, setNodesInternal, onNodesChangeInternal] = useNodesState([]); - - const [edges, setEdgesInternal, onEdgesChangeInternal] = useEdgesState([]); - - const setPending = (pending: boolean) => { - setTabsState((prev: FlowsState) => { - return { - ...prev, - [tabId]: { - ...prev[tabId], - isPending: pending, - }, - }; - }); - }; - - const isPending = tabsState[tabId]?.isPending ?? false; - - const onNodesChange = useCallback( - (change: NodeChange[]) => { - onNodesChangeInternal(change); - if(!isPending) - setPending(true); - }, - [onNodesChangeInternal, setPending, isPending] - ); - - const onEdgesChange = useCallback( - (edges: EdgeChange[]) => { - onEdgesChangeInternal(edges); - if(!isPending) - setPending(true); - }, - [onEdgesChangeInternal, setPending, isPending] - ); - - const setNodes = (change: Node[] | ((oldState: Node[]) => Node[])) => { - let newChange = typeof change === "function" ? change(nodes) : change; - let newEdges = cleanEdges(newChange, edges); - - saveCurrentFlow( - newChange, - newEdges, - reactFlowInstance?.getViewport() ?? { zoom: 1, x: 0, y: 0 } - ); - setEdgesInternal(newEdges); - setNodesInternal(newChange); - }; - - const setNode = (id: string, change: Node | ((oldState: Node) => Node)) => { - let newChange = - typeof change === "function" - ? change(nodes.find((node) => node.id === id)!) - : change; - - setNodes((oldNodes) => - oldNodes.map((node) => { - if (node.id === id) { - return newChange; - } - return node; - }) - ); - }; - - const getNode = (id: string) => { - return nodes.find((node) => node.id === id); - }; - - const setEdges = (change: Edge[] | ((oldState: Edge[]) => Edge[])) => { - let newChange = typeof change === "function" ? change(edges) : change; - - saveCurrentFlow( - nodes, - newChange, - reactFlowInstance?.getViewport() ?? { zoom: 1, x: 0, y: 0 } - ); - setEdgesInternal(newChange); - }; - - useEffect(() => { - if (!isAuthenticated) { - hardReset(); - } - }, [isAuthenticated]); - - const newNodeId = useRef(uid()); - - function incrementNodeId() { - newNodeId.current = uid(); - return newNodeId.current; - } + const {nodes, edges, paste, setPending, reactFlowInstance} = useFlow(); function refreshFlows() { setIsLoading(true); @@ -231,7 +112,7 @@ export function FlowsProvider({ children }: { children: ReactNode }) { if (DbData) { try { processFlows(DbData, false); - updateStateWithDbData(DbData); + setFlows(DbData); setIsLoading(false); } catch (e) {} } @@ -281,31 +162,6 @@ export function FlowsProvider({ children }: { children: ReactNode }) { }); } - function processFlowEdges(flow: FlowType) { - if (!flow.data || !flow.data.edges) return; - if (checkOldEdgesHandles(flow.data.edges)) { - const newEdges = updateEdgesHandleIds(flow.data); - flow.data.edges = newEdges; - } - //update edges colors - flow.data.edges.forEach((edge) => { - edge.className = ""; - edge.style = { stroke: "#555" }; - }); - } - - function updateStateWithDbData(tabsData: FlowType[]) { - setFlows(tabsData); - } - - function hardReset() { - newNodeId.current = uid(); - setTabId(""); - setFlows([]); - setIsLoading(true); - setId(uid()); - } - /** * Downloads the current flow as a JSON file */ @@ -353,11 +209,6 @@ export function FlowsProvider({ children }: { children: ReactNode }) { link.click(); }); } - - function getNodeId(nodeType: string) { - return nodeType + "-" + incrementNodeId(); - } - /** * Creates a file input and listens to a change event to upload a JSON flow file. * If the file type is application/json, the file is read and parsed into a JSON object. @@ -466,111 +317,6 @@ export function FlowsProvider({ children }: { children: ReactNode }) { processFlows(flows.filter((flow) => flow.id !== id)); } } - /** - * Add a new flow to the list of flows. - * @param flow Optional flow to add. - */ - function paste( - selectionInstance: { nodes: Node[]; edges: Edge[] }, - position: { x: number; y: number; paneX?: number; paneY?: number } - ) { - let minimumX = Infinity; - let minimumY = Infinity; - let idsMap = {}; - let newNodes: Node[] = nodes; - let newEdges = edges; - selectionInstance.nodes.forEach((node: Node) => { - if (node.position.y < minimumY) { - minimumY = node.position.y; - } - if (node.position.x < minimumX) { - minimumX = node.position.x; - } - }); - - const insidePosition = position.paneX - ? { x: position.paneX + position.x, y: position.paneY! + position.y } - : reactFlowInstance!.screenToFlowPosition({ - x: position.x, - y: position.y, - }); - - selectionInstance.nodes.forEach((node: NodeType) => { - // Generate a unique node ID - let newId = getNodeId(node.data.type); - idsMap[node.id] = newId; - - // Create a new node object - const newNode: NodeType = { - id: newId, - type: "genericNode", - position: { - x: insidePosition.x + node.position!.x - minimumX, - y: insidePosition.y + node.position!.y - minimumY, - }, - data: { - ..._.cloneDeep(node.data), - id: newId, - }, - }; - - // Add the new node to the list of nodes in state - newNodes = newNodes - .map((node) => ({ ...node, selected: false })) - .concat({ ...newNode, selected: false }); - }); - setNodes(newNodes); - - selectionInstance.edges.forEach((edge: Edge) => { - let source = idsMap[edge.source]; - let target = idsMap[edge.target]; - const sourceHandleObject: sourceHandleType = scapeJSONParse( - edge.sourceHandle! - ); - let sourceHandle = scapedJSONStringfy({ - ...sourceHandleObject, - id: source, - }); - sourceHandleObject.id = source; - - edge.data.sourceHandle = sourceHandleObject; - const targetHandleObject: targetHandleType = scapeJSONParse( - edge.targetHandle! - ); - let targetHandle = scapedJSONStringfy({ - ...targetHandleObject, - id: target, - }); - targetHandleObject.id = target; - edge.data.targetHandle = targetHandleObject; - let id = - "reactflow__edge-" + - source + - sourceHandle + - "-" + - target + - targetHandle; - newEdges = addEdge( - { - source, - target, - sourceHandle, - targetHandle, - id, - data: cloneDeep(edge.data), - style: { stroke: "#555" }, - className: - targetHandleObject.type === "Text" - ? "stroke-gray-800 " - : "stroke-gray-900 ", - animated: targetHandleObject.type === "Text", - selected: false, - }, - newEdges.map((edge) => ({ ...edge, selected: false })) - ); - }); - setEdges(newEdges); - } const addFlow = async ( newProject: Boolean, @@ -634,26 +380,12 @@ export function FlowsProvider({ children }: { children: ReactNode }) { //add animation to text type edges updateEdges(data.edges); // updateNodes(data.nodes, data.edges); - if (refreshIds) updateIds(data, getNodeId); // Assuming updateIds is defined elsewhere + if (refreshIds) updateIds(data); // Assuming updateIds is defined elsewhere } return data; }; - const updateEdges = (edges: Edge[]) => { - if (edges) - edges.forEach((edge) => { - const targetHandleObject: targetHandleType = scapeJSONParse( - edge.targetHandle! - ); - edge.className = - (targetHandleObject.type === "Text" - ? "stroke-gray-800 " - : "stroke-gray-900 ") + " stroke-connection"; - edge.animated = targetHandleObject.type === "Text"; - }); - }; - const createNewFlow = ( flowData: ReactFlowJsonObject | null, flow: FlowType @@ -697,7 +429,11 @@ export function FlowsProvider({ children }: { children: ReactNode }) { const saveTimeoutId = useRef(null); - const saveCurrentFlow = (nodes: Node[], edges: Edge[], viewport: Viewport) => { + const saveCurrentFlow = ( + nodes: Node[], + edges: Edge[], + viewport: Viewport + ) => { // Clear the previous timeout if it exists. if (saveTimeoutId.current) { clearTimeout(saveTimeoutId.current); @@ -710,8 +446,7 @@ export function FlowsProvider({ children }: { children: ReactNode }) { saveFlow({ ...currentFlow, data: { nodes, edges, viewport } }, true); } }, 300); // Delay of 300ms. - } - + }; async function saveFlow(flow?: FlowType, silent?: boolean) { let newFlow; @@ -765,7 +500,6 @@ export function FlowsProvider({ children }: { children: ReactNode }) { } } - const [isBuilt, setIsBuilt] = useState(false); // Initialize state variable for the version const [version, setVersion] = useState(""); useEffect(() => { @@ -774,63 +508,26 @@ export function FlowsProvider({ children }: { children: ReactNode }) { }); }, []); - function deleteNode(idx: string | Array) { - setNodes((oldNodes) => - oldNodes.filter((node) => - typeof idx === "string" ? node.id !== idx : !idx.includes(node.id) - ) - ); - } - - function deleteEdge(idx: string | Array) { - setEdges((oldEdges) => - oldEdges.filter((edge) => - typeof idx === "string" ? edge.id !== idx : !idx.includes(edge.id) - ) - ); - } return ( {children} diff --git a/src/frontend/src/contexts/typesContext.tsx b/src/frontend/src/contexts/typesContext.tsx index 5db197e20..2abaf5275 100644 --- a/src/frontend/src/contexts/typesContext.tsx +++ b/src/frontend/src/contexts/typesContext.tsx @@ -16,8 +16,6 @@ import { AuthContext } from "./authContext"; //context to share types adn functions from nodes to flow const initialValue: typesContextType = { - reactFlowInstance: null, - setReactFlowInstance: (newState: ReactFlowInstance) => {}, types: {}, setTypes: () => {}, templates: {}, @@ -34,8 +32,6 @@ export const typesContext = createContext(initialValue); export function TypesProvider({ children }: { children: ReactNode }) { const [types, setTypes] = useState({}); - const [reactFlowInstance, setReactFlowInstance] = - useState(null); const [templates, setTemplates] = useState({}); const [data, setData] = useState({}); const [fetchError, setFetchError] = useState(false); @@ -95,14 +91,11 @@ export function TypesProvider({ children }: { children: ReactNode }) { } } - return ( {}, @@ -29,7 +29,10 @@ const defaultOptions: UseUndoRedoOptions = { export const undoRedoContext = createContext(initialValue); export function UndoRedoProvider({ children }) { - const { tabId, flows, setNodes, setEdges, nodes, edges } = useContext(FlowsContext); + const { tabId, flows } = + useContext(FlowsContext); + + const {setNodes, setEdges, nodes, edges} = useFlow(); const [past, setPast] = useState(flows.map(() => [])); const [future, setFuture] = useState(flows.map(() => [])); diff --git a/src/frontend/src/modals/ApiModal/index.tsx b/src/frontend/src/modals/ApiModal/index.tsx index 93d85549d..97a476363 100644 --- a/src/frontend/src/modals/ApiModal/index.tsx +++ b/src/frontend/src/modals/ApiModal/index.tsx @@ -48,7 +48,8 @@ const ApiModal = forwardRef( const [activeTab, setActiveTab] = useState("0"); const tweak = useRef([]); const tweaksList = useRef([]); - const { setTweak, getTweak, tabsState } = useContext(FlowsContext); + const { tabsState } = useContext(FlowsContext); + const [getTweak, setTweak] = useState([]); const pythonApiCode = getPythonApiCode( flow, autoLogin, diff --git a/src/frontend/src/modals/EditNodeModal/index.tsx b/src/frontend/src/modals/EditNodeModal/index.tsx index 0b6ab5a39..d2e4a9e77 100644 --- a/src/frontend/src/modals/EditNodeModal/index.tsx +++ b/src/frontend/src/modals/EditNodeModal/index.tsx @@ -29,10 +29,7 @@ import { limitScrollFieldsModal, } from "../../constants/constants"; import { alertContext } from "../../contexts/alertContext"; -import { FlowsContext } from "../../contexts/flowsContext"; -import { typesContext } from "../../contexts/typesContext"; import { NodeDataType } from "../../types/flow"; -import { FlowsState } from "../../types/tabs"; import { convertObjToArray, convertValuesToNumbers, @@ -41,6 +38,7 @@ import { } from "../../utils/reactflowUtils"; import { classNames } from "../../utils/utils"; import BaseModal from "../baseModal"; +import useFlow from "../../stores/flowManagerStore"; const EditNodeModal = forwardRef( ( @@ -59,7 +57,7 @@ const EditNodeModal = forwardRef( ) => { const [myData, setMyData] = useState(data); - const { setPending, edges, setNode } = useContext(FlowsContext); + const { setPending, edges, setNode } = useFlow(); const { setModalContextOpen } = useContext(alertContext); function changeAdvanced(n) { diff --git a/src/frontend/src/modals/codeAreaModal/index.tsx b/src/frontend/src/modals/codeAreaModal/index.tsx index cba033acd..880bbe4cb 100644 --- a/src/frontend/src/modals/codeAreaModal/index.tsx +++ b/src/frontend/src/modals/codeAreaModal/index.tsx @@ -26,7 +26,7 @@ export default function CodeAreaModal({ readonly = false, }: codeAreaModalPropsType): JSX.Element { const [code, setCode] = useState(value); - const dark = useDarkStore((state) => state.dark); + const {dark} = useDarkStore(); const [height, setHeight] = useState(null); const { setErrorData, setSuccessData } = useContext(alertContext); diff --git a/src/frontend/src/modals/exportModal/index.tsx b/src/frontend/src/modals/exportModal/index.tsx index f2d2424a4..89bd0593a 100644 --- a/src/frontend/src/modals/exportModal/index.tsx +++ b/src/frontend/src/modals/exportModal/index.tsx @@ -6,7 +6,6 @@ import { Checkbox } from "../../components/ui/checkbox"; import { EXPORT_DIALOG_SUBTITLE } from "../../constants/constants"; import { alertContext } from "../../contexts/alertContext"; import { FlowsContext } from "../../contexts/flowsContext"; -import { typesContext } from "../../contexts/typesContext"; import { removeApiKeys } from "../../utils/reactflowUtils"; import BaseModal from "../baseModal"; diff --git a/src/frontend/src/modals/formModal/index.tsx b/src/frontend/src/modals/formModal/index.tsx index 99ea3a465..71115aef8 100644 --- a/src/frontend/src/modals/formModal/index.tsx +++ b/src/frontend/src/modals/formModal/index.tsx @@ -1,6 +1,5 @@ import { useContext, useEffect, useRef, useState } from "react"; import { alertContext } from "../../contexts/alertContext"; -import { typesContext } from "../../contexts/typesContext"; import { sendAllProps } from "../../types/api"; import { ChatMessageType } from "../../types/chat"; import { FlowType } from "../../types/flow"; @@ -26,6 +25,7 @@ import { CHAT_FORM_DIALOG_SUBTITLE } from "../../constants/constants"; import { AuthContext } from "../../contexts/authContext"; import { FlowsContext } from "../../contexts/flowsContext"; import { getBuildStatus } from "../../controllers/API"; +import useFlow from "../../stores/flowManagerStore"; import { FlowsState } from "../../types/tabs"; import { validateNodes } from "../../utils/reactflowUtils"; @@ -38,7 +38,8 @@ export default function FormModal({ setOpen: (open: boolean) => void; flow: FlowType; }): JSX.Element { - const { tabsState, setTabsState, nodes, edges } = useContext(FlowsContext); + const { tabsState, setTabsState } = useContext(FlowsContext); + const { nodes, edges } = useFlow(); const [chatValue, setChatValue] = useState(() => { try { const { formKeysData } = tabsState[flow.id]; @@ -383,10 +384,7 @@ export default function FormModal({ }, [open]); function sendMessage(): void { - let nodeValidationErrors = validateNodes( - nodes, - edges - ); + let nodeValidationErrors = validateNodes(nodes, edges); if (nodeValidationErrors.length === 0) { setLockChat(true); let inputs = tabsState[id.current].formKeysData.input_keys; diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index ce537084b..17777adcd 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -17,7 +17,6 @@ import ReactFlow, { SelectionDragHandler, addEdge, updateEdge, - useReactFlow, } from "reactflow"; import GenericNode from "../../../../CustomNodes/GenericNode"; import Chat from "../../../../components/chatComponent"; @@ -27,14 +26,14 @@ import { FlowsContext } from "../../../../contexts/flowsContext"; import { locationContext } from "../../../../contexts/locationContext"; import { typesContext } from "../../../../contexts/typesContext"; import { undoRedoContext } from "../../../../contexts/undoRedoContext"; +import useFlow from "../../../../stores/flowManagerStore"; import { APIClassType } from "../../../../types/api"; -import { FlowType, NodeType, targetHandleType } from "../../../../types/flow"; -import { FlowsState } from "../../../../types/tabs"; +import { FlowType, NodeType } from "../../../../types/flow"; import { generateFlow, generateNodeFromFlow, + getNodeId, isValidConnection, - scapeJSONParse, validateSelection, } from "../../../../utils/reactflowUtils"; import { cn, getRandomName, isWrappedWithClass } from "../../../../utils/utils"; @@ -53,26 +52,33 @@ export default function Page({ flow: FlowType; view?: boolean; }): JSX.Element { - let { - uploadFlow, - getNodeId, - paste, - lastCopiedSelection, - setLastCopiedSelection, - deleteNode, - deleteEdge, - } = useContext(FlowsContext); - const { - types, - reactFlowInstance, - setReactFlowInstance, - templates, - setFilterEdge, - } = useContext(typesContext); + let { uploadFlow, saveFlow } = useContext(FlowsContext); + const { types, templates, setFilterEdge } = useContext(typesContext); const reactFlowWrapper = useRef(null); + const [lastCopiedSelection, setLastCopiedSelection] = useState<{ + nodes: any; + edges: any; + } | null>(null); + const { takeSnapshot } = useContext(undoRedoContext); - const { nodes, edges, setNodes, setEdges, onNodesChange, onEdgesChange, setPending, saveFlow, isPending } = useContext(FlowsContext); + + const { + reactFlowInstance, + setReactFlowInstance, + nodes, + edges, + onNodesChange, + onEdgesChange, + onConnect, + setNodes, + setEdges, + deleteNode, + deleteEdge, + setPending, + isPending, + paste, + } = useFlow(); const position = useRef({ x: 0, y: 0 }); const [lastSelection, setLastSelection] = @@ -136,11 +142,7 @@ export default function Page({ document.removeEventListener("keydown", onKeyDown); document.removeEventListener("mousemove", handleMouseMove); }; - }, [ - lastCopiedSelection, - lastSelection, - takeSnapshot, - ]); + }, [lastCopiedSelection, lastSelection, takeSnapshot]); const [selectionMenuVisible, setSelectionMenuVisible] = useState(false); @@ -155,10 +157,12 @@ export default function Page({ useEffect(() => { setLoading(true); - if(reactFlowInstance){ + if (reactFlowInstance) { reactFlowInstance.setNodes(flow?.data?.nodes ?? []); reactFlowInstance.setEdges(flow?.data?.edges ?? []); - reactFlowInstance.setViewport(flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 }); + reactFlowInstance.setViewport( + flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 } + ); } // Clear the previous timeout @@ -177,30 +181,10 @@ export default function Page({ }; }, [flow, reactFlowInstance]); - const onConnect = useCallback( + const onConnectMod = useCallback( (params: Connection) => { takeSnapshot(); - setEdges((eds) => - addEdge( - { - ...params, - data: { - targetHandle: scapeJSONParse(params.targetHandle!), - sourceHandle: scapeJSONParse(params.sourceHandle!), - }, - style: { stroke: "#555" }, - className: - ((scapeJSONParse(params.targetHandle!) as targetHandleType) - .type === "Text" - ? "stroke-foreground " - : "stroke-foreground ") + " stroke-connection", - animated: - (scapeJSONParse(params.targetHandle!) as targetHandleType) - .type === "Text", - }, - eds - ) - ); + onConnect(params); }, [setEdges, takeSnapshot, addEdge] ); @@ -237,10 +221,6 @@ export default function Page({ if (event.dataTransfer.types.some((types) => types === "nodedata")) { takeSnapshot(); - // Get the current bounds of the ReactFlow wrapper element - const reactflowBounds = - reactFlowWrapper.current?.getBoundingClientRect(); - // Extract the data from the drag event and parse it as a JSON object let data: { type: string; node?: APIClassType } = JSON.parse( event.dataTransfer.getData("nodedata") @@ -370,10 +350,9 @@ export default function Page({ }, []); const onMove = useCallback(() => { - if(!isPending) - setPending(true); + if (!isPending) setPending(true); }, [setPending]); - + return (
{!view && } @@ -399,7 +378,7 @@ export default function Page({ edges={edges} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} - onConnect={onConnect} + onConnect={onConnectMod} disableKeyboardA11y={true} onInit={setReactFlowInstance} nodeTypes={nodeTypes} @@ -479,9 +458,7 @@ export default function Page({ }} /> - {!view && ( - - )} + {!view && }
) : ( <> diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx index 3e5342422..bb250e2aa 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx @@ -10,6 +10,7 @@ import { typesContext } from "../../../../contexts/typesContext"; import ApiModal from "../../../../modals/ApiModal"; import ExportModal from "../../../../modals/exportModal"; import ShareModal from "../../../../modals/shareModal"; +import useFlow from "../../../../stores/flowManagerStore"; import { useStoreStore } from "../../../../stores/storeStore"; import { APIClassType, APIObjectType } from "../../../../types/api"; import { @@ -28,13 +29,11 @@ import SidebarDraggableComponent from "./sideBarDraggableComponent"; export default function ExtraSidebar(): JSX.Element { const { data, templates, getFilterEdge, setFilterEdge } = useContext(typesContext); - const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt, isPending } = - useContext(FlowsContext); + const { flows, tabId, uploadFlow, saveFlow } = useContext(FlowsContext); - const hasStore = useStoreStore((state) => state.hasStore); - const hasApiKey = useStoreStore((state) => state.hasApiKey); - const validApiKey = useStoreStore((state) => state.validApiKey); + const { hasStore, hasApiKey, validApiKey } = useStoreStore(); + const { isBuilt, isPending } = useFlow(); const { setErrorData } = useContext(alertContext); const [dataFilter, setFilterData] = useState(data); const [search, setSearch] = useState(""); @@ -292,12 +291,9 @@ export default function ExtraSidebar(): JSX.Element { {flow && flow.data && ( diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarDraggableComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarDraggableComponent/index.tsx index 07aacaefc..0d1bcdddb 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarDraggableComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarDraggableComponent/index.tsx @@ -12,6 +12,7 @@ import { APIClassType } from "../../../../../types/api"; import { createFlowComponent, downloadNode, + getNodeId, } from "../../../../../utils/reactflowUtils"; import { removeCountFromString } from "../../../../../utils/utils"; @@ -35,7 +36,7 @@ export default function SidebarDraggableComponent({ official: boolean; }) { const [open, setOpen] = useState(false); - const { getNodeId, deleteComponent, version } = useContext(FlowsContext); + const { deleteComponent, version } = useContext(FlowsContext); const { autoLogin, userData } = useContext(AuthContext); const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 }); const popoverRef = useRef(null); diff --git a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx index ab6015e54..38fc175ae 100644 --- a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx @@ -13,6 +13,7 @@ import { undoRedoContext } from "../../../../contexts/undoRedoContext"; import ConfirmationModal from "../../../../modals/ConfirmationModal"; import EditNodeModal from "../../../../modals/EditNodeModal"; import ShareModal from "../../../../modals/shareModal"; +import useFlow from "../../../../stores/flowManagerStore"; import { useStoreStore } from "../../../../stores/storeStore"; import { nodeToolbarPropsType } from "../../../../types/components"; import { FlowType } from "../../../../types/flow"; @@ -49,11 +50,8 @@ export default function NodeToolbarComponent({ data.node.template[templateField].type === "NestedDict") ).length ); - const { getNodeId } = useContext(FlowsContext); - const hasStore = useStoreStore((state) => state.hasStore); - const hasApiKey = useStoreStore((state) => state.hasApiKey); - const validApiKey = useStoreStore((state) => state.validApiKey); + const { hasStore, hasApiKey, validApiKey } = useStoreStore(); function canMinimize() { let countHandles: number = 0; @@ -66,16 +64,9 @@ export default function NodeToolbarComponent({ const isMinimal = canMinimize(); const isGroup = data.node?.flow ? true : false; - const { - paste, - saveComponent, - version, - flows, - nodes, - edges, - setNodes, - setEdges, - } = useContext(FlowsContext); + const { paste, nodes, edges, setNodes, setEdges } = useFlow(); + + const { saveComponent, flows, version } = useContext(FlowsContext); const { takeSnapshot } = useContext(undoRedoContext); const [showModalAdvanced, setShowModalAdvanced] = useState(false); const [showconfirmShare, setShowconfirmShare] = useState(false); @@ -123,7 +114,7 @@ export default function NodeToolbarComponent({ case "ungroup": takeSnapshot(); updateFlowPosition(position, data.node?.flow!); - expandGroupNode(data, getNodeId, nodes, edges, setNodes, setEdges); + expandGroupNode(data, nodes, edges, setNodes, setEdges); break; case "override": setShowOverrideModal(true); diff --git a/src/frontend/src/stores/darkStore.tsx b/src/frontend/src/stores/darkStore.tsx index 1acf37ead..3eb9a55ee 100644 --- a/src/frontend/src/stores/darkStore.tsx +++ b/src/frontend/src/stores/darkStore.tsx @@ -8,9 +8,9 @@ type State = { }; type Action = { - updateDark: (dark: State["dark"]) => void; - updateStars: (starts: State["stars"]) => void; - updateGradientIndex: (gradientIndex: State["gradientIndex"]) => void; + setDark: (dark: State["dark"]) => void; + setStars: (starts: State["stars"]) => void; + setGradientIndex: (gradientIndex: State["gradientIndex"]) => void; }; function gradientIndexInitialState() { @@ -23,9 +23,9 @@ export const useDarkStore = create((set) => ({ dark: JSON.parse(window.localStorage.getItem("isDark")!) ?? false, stars: 0, gradientIndex: gradientIndexInitialState(), - updateDark: (dark) => set(() => ({ dark: dark })), - updateStars: (starts) => set(() => ({ stars: starts })), - updateGradientIndex: (gradientIndex) => + setDark: (dark) => set(() => ({ dark: dark })), + setStars: (starts) => set(() => ({ stars: starts })), + setGradientIndex: (gradientIndex) => set(() => ({ gradientIndex: gradientIndex })), })); diff --git a/src/frontend/src/stores/flowManagerStore.ts b/src/frontend/src/stores/flowManagerStore.ts new file mode 100644 index 000000000..db6501460 --- /dev/null +++ b/src/frontend/src/stores/flowManagerStore.ts @@ -0,0 +1,248 @@ +import { cloneDeep } from "lodash"; +import { + Connection, + Edge, + EdgeChange, + Node, + NodeChange, + OnConnect, + OnEdgesChange, + OnNodesChange, + ReactFlowInstance, + addEdge, + applyEdgeChanges, + applyNodeChanges, +} from "reactflow"; +import { create } from "zustand"; +import { + NodeDataType, + NodeType, + sourceHandleType, + targetHandleType, +} from "../types/flow"; +import { + cleanEdges, + getHandleId, + getNodeId, + scapeJSONParse, + scapedJSONStringfy, +} from "../utils/reactflowUtils"; + +type RFState = { + reactFlowInstance: ReactFlowInstance | null; + setReactFlowInstance: (newState: ReactFlowInstance) => void; + nodes: Node[]; + edges: Edge[]; + onNodesChange: OnNodesChange; + onEdgesChange: OnEdgesChange; + setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void; + setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void; + setNode: (id: string, update: Node | ((oldState: Node) => Node)) => void; + getNode: (id: string) => Node | undefined; + onConnect: OnConnect; + deleteNode: (nodeId: string | Array) => void; + deleteEdge: (edgeId: string | Array) => void; + paste: ( + selection: { nodes: any; edges: any }, + position: { x: number; y: number; paneX?: number; paneY?: number } + ) => void; + isBuilt: boolean; + setIsBuilt: (isBuilt: boolean) => void; + isPending: boolean; + setPending: (pending: boolean) => void; +}; + +// this is our useStore hook that we can use in our components to get parts of the store and call actions +const useFlow = create((set, get) => ({ + reactFlowInstance: null, + setReactFlowInstance: (newState) => { + set({ reactFlowInstance: newState }); + }, + nodes: [], + edges: [], + isBuilt: false, + setIsBuilt: (isBuilt) => { + set({ isBuilt }); + }, + onNodesChange: (changes: NodeChange[]) => { + set({ + nodes: applyNodeChanges(changes, get().nodes), + }); + if (!get().isPending) set({ isPending: true }); + }, + onEdgesChange: (changes: EdgeChange[]) => { + set({ + edges: applyEdgeChanges(changes, get().edges), + }); + if (!get().isPending) set({ isPending: true }); + }, + setNodes: (change) => { + let newChange = typeof change === "function" ? change(get().nodes) : change; + let newEdges = cleanEdges(newChange, get().edges); + + set({ edges: newEdges }); + set({ nodes: newChange }); + }, + setEdges: (change) => { + let newChange = typeof change === "function" ? change(get().edges) : change; + + set({ edges: newChange }); + }, + setNode: (id: string, change: Node | ((oldState: Node) => Node)) => { + let newChange = + typeof change === "function" + ? change(get().nodes.find((node) => node.id === id)!) + : change; + + get().setNodes((oldNodes) => + oldNodes.map((node) => { + if (node.id === id) { + return newChange; + } + return node; + }) + ); + }, + getNode: (id: string) => { + return get().nodes.find((node) => node.id === id); + }, + onConnect: (connection: Connection) => { + set({ + edges: addEdge( + { + ...connection, + data: { + targetHandle: scapeJSONParse(connection.targetHandle!), + sourceHandle: scapeJSONParse(connection.sourceHandle!), + }, + style: { stroke: "#555" }, + className: + ((scapeJSONParse(connection.targetHandle!) as targetHandleType) + .type === "Text" + ? "stroke-foreground " + : "stroke-foreground ") + " stroke-connection", + animated: + (scapeJSONParse(connection.targetHandle!) as targetHandleType) + .type === "Text", + }, + get().edges + ), + }); + }, + deleteNode: (nodeId) => { + get().setNodes( + get().nodes.filter((node) => + typeof nodeId === "string" + ? node.id !== nodeId + : !nodeId.includes(node.id) + ) + ); + }, + deleteEdge: (edgeId) => { + get().setEdges( + get().edges.filter((edge) => + typeof edgeId === "string" + ? edge.id !== edgeId + : !edgeId.includes(edge.id) + ) + ); + }, + paste: (selection, position) => { + let minimumX = Infinity; + let minimumY = Infinity; + let idsMap = {}; + let newNodes: Node[] = get().nodes; + let newEdges = get().edges; + selection.nodes.forEach((node: Node) => { + if (node.position.y < minimumY) { + minimumY = node.position.y; + } + if (node.position.x < minimumX) { + minimumX = node.position.x; + } + }); + + const insidePosition = position.paneX + ? { x: position.paneX + position.x, y: position.paneY! + position.y } + : get().reactFlowInstance!.screenToFlowPosition({ + x: position.x, + y: position.y, + }); + + selection.nodes.forEach((node: NodeType) => { + // Generate a unique node ID + let newId = getNodeId(node.data.type); + idsMap[node.id] = newId; + + // Create a new node object + const newNode: NodeType = { + id: newId, + type: "genericNode", + position: { + x: insidePosition.x + node.position!.x - minimumX, + y: insidePosition.y + node.position!.y - minimumY, + }, + data: { + ...cloneDeep(node.data), + id: newId, + }, + }; + + // Add the new node to the list of nodes in state + newNodes = newNodes + .map((node) => ({ ...node, selected: false })) + .concat({ ...newNode, selected: false }); + }); + set({ nodes: newNodes }); + + selection.edges.forEach((edge: Edge) => { + let source = idsMap[edge.source]; + let target = idsMap[edge.target]; + const sourceHandleObject: sourceHandleType = scapeJSONParse( + edge.sourceHandle! + ); + let sourceHandle = scapedJSONStringfy({ + ...sourceHandleObject, + id: source, + }); + sourceHandleObject.id = source; + + edge.data.sourceHandle = sourceHandleObject; + const targetHandleObject: targetHandleType = scapeJSONParse( + edge.targetHandle! + ); + let targetHandle = scapedJSONStringfy({ + ...targetHandleObject, + id: target, + }); + targetHandleObject.id = target; + edge.data.targetHandle = targetHandleObject; + let id = getHandleId(source, sourceHandle, target, targetHandle); + newEdges = addEdge( + { + source, + target, + sourceHandle, + targetHandle, + id, + data: cloneDeep(edge.data), + style: { stroke: "#555" }, + className: + targetHandleObject.type === "Text" + ? "stroke-gray-800 " + : "stroke-gray-900 ", + animated: targetHandleObject.type === "Text", + selected: false, + }, + newEdges.map((edge) => ({ ...edge, selected: false })) + ); + }); + set({ edges: newEdges }); + }, + isPending: false, + setPending: (pending: boolean) => { + set({ isPending: pending }); + }, +})); + +export default useFlow; diff --git a/src/frontend/src/types/chat/index.ts b/src/frontend/src/types/chat/index.ts index 829f612c1..62cb4eb78 100644 --- a/src/frontend/src/types/chat/index.ts +++ b/src/frontend/src/types/chat/index.ts @@ -1,6 +1,6 @@ import { FlowType } from "../flow"; -export type ChatType = { flow: FlowType; }; +export type ChatType = { flow: FlowType }; export type ChatMessageType = { message: string | Object; template?: string; diff --git a/src/frontend/src/types/tabs/index.ts b/src/frontend/src/types/tabs/index.ts index 18619cfe3..c5c0e680b 100644 --- a/src/frontend/src/types/tabs/index.ts +++ b/src/frontend/src/types/tabs/index.ts @@ -1,35 +1,34 @@ -import { XYPosition, Node, NodeChange, Edge, EdgeChange } from "reactflow"; +import { Edge, EdgeChange, Node, NodeChange, XYPosition } from "reactflow"; import { tweakType } from "../components"; import { FlowType, NodeDataType } from "../flow"; -import { Dispatch, SetStateAction } from "react"; type OnChange = (changes: ChangesType[]) => void; export type FlowsContextType = { + //keep saveFlow: (flow?: FlowType, silent?: boolean) => Promise; tabId: string; + //keep isLoading: boolean; setTabId: (index: string) => void; - flows: Array; - deleteNode: (idx: string | Array) => void; - deleteEdge: (idx: string | Array) => void; + //keep removeFlow: (id: string) => void; + //keep addFlow: ( newProject: boolean, flow?: FlowType, override?: boolean, position?: XYPosition ) => Promise; - incrementNodeId: () => string; downloadFlow: ( flow: FlowType, flowName: string, flowDescription?: string ) => void; + //keep downloadFlows: () => void; + //keep uploadFlows: () => void; - isBuilt: boolean; - setIsBuilt: (state: boolean) => void; uploadFlow: ({ newProject, file, @@ -41,34 +40,17 @@ export type FlowsContextType = { isComponent?: boolean; position?: XYPosition; }) => Promise; - hardReset: () => void; - getNodeId: (nodeType: string) => string; - isPending: boolean; - setPending: (pending: boolean) => void; tabsState: FlowsState; - setTabsState: (update: FlowsState | ((oldState: FlowsState) => FlowsState)) => void; - paste: ( - selection: { nodes: any; edges: any }, - position: { x: number; y: number; paneX?: number; paneY?: number } + setTabsState: ( + update: FlowsState | ((oldState: FlowsState) => FlowsState) ) => void; - lastCopiedSelection: { nodes: any; edges: any } | null; - setLastCopiedSelection: (selection: { nodes: any; edges: any }) => void; - setTweak: (tweak: tweakType) => tweakType | void; - getTweak: tweakType; saveComponent: ( component: NodeDataType, override: boolean ) => Promise; deleteComponent: (key: string) => void; version: string; - nodes: Array; - setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void; - setNode: (id: string, update: Node | ((oldState: Node) => Node)) => void; - getNode: (id: string) => Node | undefined; - onNodesChange: OnChange; - edges: Array; - setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void; - onEdgesChange: OnChange; + flows: Array; }; export type FlowsState = { diff --git a/src/frontend/src/types/typesContext/index.ts b/src/frontend/src/types/typesContext/index.ts index cfe206694..e04c40ce7 100644 --- a/src/frontend/src/types/typesContext/index.ts +++ b/src/frontend/src/types/typesContext/index.ts @@ -7,8 +7,6 @@ const template: { [char: string]: APIClassType } = {}; const data: { [char: string]: string } = {}; export type typesContextType = { - reactFlowInstance: ReactFlowInstance | null; - setReactFlowInstance: (newState: ReactFlowInstance) => void; types: typeof types; setTypes: (newState: {}) => void; templates: typeof template; diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 9a05f8d2e..0f3af8a55 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -27,6 +27,7 @@ import { updateEdgesHandleIdsType, } from "../types/utils/reactflowUtils"; import { getFieldTitle, toTitleCase } from "./utils"; +const uid = new ShortUniqueId({ length: 5 }); export function cleanEdges(nodes: Node[], edges: Edge[]) { let newEdges = _.cloneDeep(edges); @@ -82,7 +83,7 @@ export function unselectAllNodes({ updateNodes, data }: unselectAllNodesType) { export function isValidConnection( { source, target, sourceHandle, targetHandle }: Connection, nodes: Node[], - edges: Edge[], + edges: Edge[] ) { const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!); const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!); @@ -98,16 +99,12 @@ export function isValidConnection( ) { let targetNode = nodes.find((node) => node.id === target!)?.data?.node; if (!targetNode) { - if ( - !edges - .find((e) => e.targetHandle === targetHandle) - ) { + if (!edges.find((e) => e.targetHandle === targetHandle)) { return true; } } else if ( (!targetNode.template[targetHandleObject.fieldName].list && - !edges - .find((e) => e.targetHandle === targetHandle)) || + !edges.find((e) => e.targetHandle === targetHandle)) || targetNode.template[targetHandleObject.fieldName].list ) { return true; @@ -153,7 +150,6 @@ export function updateTemplate( export function updateIds( newFlow: ReactFlowJsonObject, - getNodeId: (type: string) => string ) { let idsMap = {}; @@ -272,6 +268,20 @@ export function validateNodes(nodes: Node[], edges: Edge[]) { return nodes.flatMap((n: NodeType) => validateNode(n, edges)); } +export function updateEdges(edges: Edge[]) { + if (edges) + edges.forEach((edge) => { + const targetHandleObject: targetHandleType = scapeJSONParse( + edge.targetHandle! + ); + edge.className = + (targetHandleObject.type === "Text" + ? "stroke-gray-800 " + : "stroke-gray-900 ") + " stroke-connection"; + edge.animated = targetHandleObject.type === "Text"; + }); +}; + export function addVersionToDuplicates(flow: FlowType, flows: FlowType[]) { const existingNames = flows.map((item) => item.name); let newName = flow.name; @@ -499,13 +509,26 @@ export function getMiddlePoint(nodes: Node[]) { return { x: averageX, y: averageY }; } +export function getNodeId(nodeType: string) { + return nodeType + "-" + uid(); +} + +export function getHandleId(source: string, sourceHandle: string, target: string, targetHandle: string){ + return "reactflow__edge-" + + source + + sourceHandle + + "-" + + target + + targetHandle; +} + export function generateFlow( selection: OnSelectionChangeParams, nodes: Node[], edges: Edge[], name: string ): generateFlowType { - const newFlowData = {nodes, edges, viewport: { zoom: 1, x: 0, y: 0 }}; + const newFlowData = { nodes, edges, viewport: { zoom: 1, x: 0, y: 0 } }; const uid = new ShortUniqueId({ length: 5 }); /* remove edges that are not connected to selected nodes on both ends in future we can save this edges to when ungrouping reconect to the old nodes @@ -539,14 +562,10 @@ export function generateFlow( export function filterFlow( selection: OnSelectionChangeParams, setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void, - setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void, + setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void ) { - setNodes((nodes) => - nodes.filter((node) => !selection.nodes.includes(node)) - ); - setEdges((edges) => - edges.filter((edge) => !selection.edges.includes(edge)) - ); + setNodes((nodes) => nodes.filter((node) => !selection.nodes.includes(node))); + setEdges((edges) => edges.filter((edge) => !selection.edges.includes(edge))); } export function findLastNode({ nodes, edges }: findLastNodeType) { @@ -572,7 +591,7 @@ export function updateFlowPosition(NewPosition: XYPosition, flow: FlowType) { export function concatFlows( flow: FlowType, setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void, - setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void, + setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void ) { const { nodes, edges } = flow.data!; setNodes((old) => [...old, ...nodes]); @@ -916,16 +935,28 @@ function updateEdgesIds(edges: Edge[], idsMap: { [key: string]: string }) { }); } +export function processFlowEdges(flow: FlowType) { + if (!flow.data || !flow.data.edges) return; + if (checkOldEdgesHandles(flow.data.edges)) { + const newEdges = updateEdgesHandleIds(flow.data); + flow.data.edges = newEdges; + } + //update edges colors + flow.data.edges.forEach((edge) => { + edge.className = ""; + edge.style = { stroke: "#555" }; + }); +} + export function expandGroupNode( groupNode: NodeDataType, - getNodeId: (type: string) => string, nodes: Node[], edges: Edge[], setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void, setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void ) { const { template, flow } = _.cloneDeep(groupNode.node!); - const idsMap = updateIds(flow!.data!, getNodeId); + const idsMap = updateIds(flow!.data!); updateProxyIdsOnTemplate(template, idsMap); let flowEdges = edges; updateEdgesIds(flowEdges, idsMap); diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py index 2e2a65b64..338224004 100644 --- a/tests/test_endpoints.py +++ b/tests/test_endpoints.py @@ -578,4 +578,4 @@ def test_async_task_processing_vector_store(client, added_vector_store, created_ # Validate that the task completed successfully and the result is as expected assert "result" in task_status_json, task_status_json assert "output" in task_status_json["result"], task_status_json["result"] - assert "Langflow" in task_status_json["result"]["output"], task_status_json["result"] \ No newline at end of file + assert "Langflow" in task_status_json["result"]["output"], task_status_json["result"]