diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 9d46908cf..348559c0f 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -818,7 +818,7 @@ class Graph: # All vertices that do not have edges are invalid return len(self.get_vertex_edges(vertex.id)) > 0 - def get_vertex(self, vertex_id: str) -> Vertex: + def get_vertex(self, vertex_id: str, silent: bool = False) -> Vertex: """Returns a vertex by id.""" try: return self.vertex_map[vertex_id] @@ -869,7 +869,8 @@ class Graph: self.run_manager.add_to_vertices_being_run(vertex_id) try: params = "" - if vertex.frozen: + parent_vertex = self.get_vertex(vertex.parent_node_id) if vertex.parent_node_id else None + if vertex.frozen or (parent_vertex and parent_vertex.frozen): # Check the cache for the vertex cached_result = await chat_service.get_cache(key=vertex.id) if isinstance(cached_result, CacheMiss): diff --git a/src/backend/base/langflow/graph/graph/utils.py b/src/backend/base/langflow/graph/graph/utils.py index 9a46f828f..0addab0b6 100644 --- a/src/backend/base/langflow/graph/graph/utils.py +++ b/src/backend/base/langflow/graph/graph/utils.py @@ -1,7 +1,6 @@ -from typing import List, Dict import copy from collections import deque - +from typing import Dict, List PRIORITY_LIST_OF_INPUTS = ["webhook", "chat"] @@ -38,14 +37,25 @@ def add_parent_node_id(nodes, parent_node_id): node["parent_node_id"] = parent_node_id +def add_frozen(nodes, frozen): + """ + This function receives a list of nodes and adds a frozen to each node. + """ + for node in nodes: + node["frozen"] = frozen + + def ungroup_node(group_node_data, base_flow): - template, flow = ( + template, flow, frozen = ( group_node_data["node"]["template"], group_node_data["node"]["flow"], + group_node_data["node"].get("frozen", False), ) parent_node_id = group_node_data["id"] + g_nodes = flow["data"]["nodes"] add_parent_node_id(g_nodes, parent_node_id) + add_frozen(g_nodes, frozen) g_edges = flow["data"]["edges"] # Redirect edges to the correct proxy node diff --git a/src/frontend/src/constants/constants.ts b/src/frontend/src/constants/constants.ts index 9642859f0..9ca0122f4 100644 --- a/src/frontend/src/constants/constants.ts +++ b/src/frontend/src/constants/constants.ts @@ -815,6 +815,10 @@ export const defaultShortcuts = [ name: "Freeze", shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + F`, }, + { + name: "Freeze Path", + shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Shift + F`, + }, { name: "Flow Share", shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + B`, diff --git a/src/frontend/src/controllers/API/helpers/constants.ts b/src/frontend/src/controllers/API/helpers/constants.ts index 02e5a67ff..0276e73c6 100644 --- a/src/frontend/src/controllers/API/helpers/constants.ts +++ b/src/frontend/src/controllers/API/helpers/constants.ts @@ -7,6 +7,7 @@ export const URLs = { VERSION: `version`, MESSAGES: `monitor/messages`, STORE: `store`, + BUILD: `build`, } as const; export function getURL(key: keyof typeof URLs, params: any = {}) { diff --git a/src/frontend/src/controllers/API/queries/vertex/index.tsx b/src/frontend/src/controllers/API/queries/vertex/index.tsx new file mode 100644 index 000000000..e8b098070 --- /dev/null +++ b/src/frontend/src/controllers/API/queries/vertex/index.tsx @@ -0,0 +1 @@ +export * from "./use-post-retrieve-vertex-order"; diff --git a/src/frontend/src/controllers/API/queries/vertex/use-post-retrieve-vertex-order.tsx b/src/frontend/src/controllers/API/queries/vertex/use-post-retrieve-vertex-order.tsx new file mode 100644 index 000000000..2b90daea2 --- /dev/null +++ b/src/frontend/src/controllers/API/queries/vertex/use-post-retrieve-vertex-order.tsx @@ -0,0 +1,68 @@ +import { useMutationFunctionType } from "@/types/api"; +import { AxiosRequestConfig } from "axios"; +import { ReactFlowJsonObject } from "reactflow"; +import { api } from "../../api"; +import { getURL } from "../../helpers/constants"; +import { UseRequestProcessor } from "../../services/request-processor"; + +interface retrieveGetVerticesOrder { + flowId: string; + data?: ReactFlowJsonObject; + stopNodeId?: string; + startNodeId?: string; +} + +interface retrieveGetVerticesOrderResponse { + ids: string[]; + rund_id: string; + vertices_to_run: string[]; +} + +// add types for error handling and success +export const usePostRetrieveVertexOrder: useMutationFunctionType< + retrieveGetVerticesOrder, + retrieveGetVerticesOrderResponse +> = (options) => { + const { mutate } = UseRequestProcessor(); + + const postRetrieveVertexOrder = async ({ + flowId, + data: flow, + startNodeId, + stopNodeId, + }: retrieveGetVerticesOrder): Promise => { + // nodeId is optional and is a query parameter + // if nodeId is not provided, the API will return all vertices + const config: AxiosRequestConfig = {}; + if (stopNodeId) { + config["params"] = { stop_component_id: decodeURIComponent(stopNodeId) }; + } else if (startNodeId) { + config["params"] = { + start_component_id: decodeURIComponent(startNodeId), + }; + } + const data = { + data: {}, + }; + if (flow && flow.nodes && flow.edges) { + const { nodes, edges } = flow; + data["data"]["nodes"] = nodes; + data["data"]["edges"] = edges; + } + const response = await api.post( + `${getURL("BUILD")}/${flowId}/vertices`, + data, + config, + ); + + return response.data; + }; + + const mutation = mutate( + ["usePostRetrieveVertexOrder"], + postRetrieveVertexOrder, + options, + ); + + return mutation; +}; diff --git a/src/frontend/src/icons/freezeAll/freezeAll.jsx b/src/frontend/src/icons/freezeAll/freezeAll.jsx new file mode 100644 index 000000000..4c2b22bc3 --- /dev/null +++ b/src/frontend/src/icons/freezeAll/freezeAll.jsx @@ -0,0 +1,46 @@ +const FreezeAllSvg = (props) => { + return ( + + snowflake-svg + + + + + + + + + + ); +}; + +export default FreezeAllSvg; diff --git a/src/frontend/src/icons/freezeAll/index.tsx b/src/frontend/src/icons/freezeAll/index.tsx new file mode 100644 index 000000000..62f5b53de --- /dev/null +++ b/src/frontend/src/icons/freezeAll/index.tsx @@ -0,0 +1,10 @@ +import React, { forwardRef } from "react"; +import SvgFreezeAll from "./freezeAll"; +("./freezeAll.jsx"); + +export const freezeAllIcon = forwardRef< + SVGSVGElement, + React.PropsWithChildren<{}> +>((props, ref) => { + return ; +}); diff --git a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx index d27ad4ef7..684298f77 100644 --- a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx @@ -1,3 +1,4 @@ +import { usePostRetrieveVertexOrder } from "@/controllers/API/queries/vertex"; import _, { cloneDeep } from "lodash"; import { useEffect, useState } from "react"; import { useHotkeys } from "react-hotkeys-hook"; @@ -71,12 +72,14 @@ export default function NodeToolbarComponent({ data.node.template[templateField]?.type === "dict" || data.node.template[templateField]?.type === "NestedDict"), ).length; + const updateFreezeStatus = useFlowStore((state) => state.updateFreezeStatus); const hasStore = useStoreStore((state) => state.hasStore); const hasApiKey = useStoreStore((state) => state.hasApiKey); const validApiKey = useStoreStore((state) => state.validApiKey); const shortcuts = useShortcutsStore((state) => state.shortcuts); const unselectAll = useFlowStore((state) => state.unselectAll); + const currentFlow = useFlowsManagerStore((state) => state.currentFlow); function handleMinimizeWShortcut(e: KeyboardEvent) { if (isWrappedWithClass(e, "noflow")) return; e.preventDefault(); @@ -153,7 +156,6 @@ export default function NodeToolbarComponent({ function handleFreeze(e: KeyboardEvent) { if (isWrappedWithClass(e, "noflow")) return; e.preventDefault(); - if (data.node?.flow) return; setNode(data.id, (old) => ({ ...old, data: { @@ -166,6 +168,12 @@ export default function NodeToolbarComponent({ })); } + function handleFreezeAll(e: KeyboardEvent) { + if (isWrappedWithClass(e, "noflow")) return; + e.preventDefault(); + FreezeAllVertices({ flowId: currentFlow!.id, stopNodeId: data.id }); + } + const advanced = useShortcutsStore((state) => state.advanced); const minimize = useShortcutsStore((state) => state.minimize); const component = useShortcutsStore((state) => state.component); @@ -175,6 +183,7 @@ export default function NodeToolbarComponent({ const group = useShortcutsStore((state) => state.group); const download = useShortcutsStore((state) => state.download); const freeze = useShortcutsStore((state) => state.freeze); + const freezeAll = useShortcutsStore((state) => state.FreezePath); useHotkeys(minimize, handleMinimizeWShortcut, { preventDefault }); useHotkeys(group, handleGroupWShortcut, { preventDefault }); @@ -185,6 +194,7 @@ export default function NodeToolbarComponent({ useHotkeys(docs, handleDocsWShortcut, { preventDefault }); useHotkeys(download, handleDownloadWShortcut, { preventDefault }); useHotkeys(freeze, handleFreeze); + useHotkeys(freezeAll, handleFreezeAll); const isMinimal = numberOfHandles <= 1 && numberOfOutputHandles <= 1; const isGroup = data.node?.flow ? true : false; @@ -200,6 +210,14 @@ export default function NodeToolbarComponent({ const getNodePosition = useFlowStore((state) => state.getNodePosition); const flows = useFlowsManagerStore((state) => state.flows); const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot); + const { mutate: FreezeAllVertices } = usePostRetrieveVertexOrder({ + onSuccess: ({ vertices_to_run }) => { + updateFreezeStatus(vertices_to_run, !data.node?.frozen); + vertices_to_run.forEach((vertex) => { + updateNodeInternals(vertex); + }); + }, + }); // useEffect(() => { // if (openWDoubleClick) setShowModalAdvanced(true); @@ -244,7 +262,6 @@ export default function NodeToolbarComponent({ saveComponent(cloneDeep(data), false); break; case "freeze": - if (data.node?.flow) return; setNode(data.id, (old) => ({ ...old, data: { @@ -256,6 +273,9 @@ export default function NodeToolbarComponent({ }, })); break; + case "freezeAll": + FreezeAllVertices({ flowId: currentFlow!.id, stopNodeId: data.id }); + break; case "code": setOpenModal(!openModal); break; @@ -490,44 +510,37 @@ export default function NodeToolbarComponent({ */} - {!data.node?.flow && ( - name.split(" ")[0].toLowerCase() === "freeze", - )!, + name.toLowerCase() === "freeze path", + )!, + )} + side="top" + > + - - )} + /> + + {/*