diff --git a/src/backend/langflow/api/v1/chat.py b/src/backend/langflow/api/v1/chat.py
index 71214ea72d..8cdeb6f054 100644
--- a/src/backend/langflow/api/v1/chat.py
+++ b/src/backend/langflow/api/v1/chat.py
@@ -202,7 +202,7 @@ async def build_vertex_stream(
async def stream_vertex():
try:
if not session_id:
- cache = chat_service.get_cache(flow_id)
+ cache = await chat_service.get_cache(flow_id)
if not cache:
# If there's no cache
raise ValueError(f"No cache found for {flow_id}.")
@@ -252,7 +252,7 @@ async def stream_vertex():
raise ValueError(f"No result found for vertex {vertex_id}")
except Exception as exc:
- logger.error(f"Error building vertex: {exc}")
+ logger.exception(f"Error building vertex: {exc}")
yield str(StreamData(event="error", data={"error": str(exc)}))
finally:
logger.debug("Closing stream")
diff --git a/src/backend/langflow/graph/graph/base.py b/src/backend/langflow/graph/graph/base.py
index a918464424..025845b574 100644
--- a/src/backend/langflow/graph/graph/base.py
+++ b/src/backend/langflow/graph/graph/base.py
@@ -377,7 +377,10 @@ def update(self, other: "Graph") -> "Graph":
# Remove vertices that are not in the other graph
for vertex_id in removed_vertex_ids:
- self.remove_vertex(vertex_id)
+ try:
+ self.remove_vertex(vertex_id)
+ except ValueError:
+ pass
# The order here matters because adding the vertex is required
# if any of them have edges that point to any of the new vertices
@@ -741,8 +744,11 @@ def _build_vertices(self) -> List[Vertex]:
vertex_data = vertex["data"]
vertex_type: str = vertex_data["type"] # type: ignore
vertex_base_type: str = vertex_data["node"]["template"]["_type"] # type: ignore
+ if "id" not in vertex_data:
+ raise ValueError(f"Vertex data for {vertex_data['display_name']} does not contain an id")
VertexClass = self._get_vertex_class(vertex_type, vertex_base_type, vertex_data["id"])
+
vertex_instance = VertexClass(vertex, graph=self)
vertex_instance.set_top_level(self.top_level_vertices)
vertices.append(vertex_instance)
diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx
index d408f2f5b1..5493ed12a7 100644
--- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx
+++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx
@@ -1,4 +1,4 @@
-import _ from "lodash";
+import _, { cloneDeep, set } from "lodash";
import { MouseEvent, useCallback, useEffect, useRef, useState } from "react";
import ReactFlow, {
Background,
@@ -32,6 +32,7 @@ import {
isValidConnection,
reconnectEdges,
scapeJSONParse,
+ updateIds,
validateSelection,
} from "../../../../utils/reactflowUtils";
import { getRandomName, isWrappedWithClass } from "../../../../utils/utils";
@@ -104,35 +105,40 @@ export default function Page({
) {
event.preventDefault();
takeSnapshot();
- if (validateSelection(lastSelection!, edges).length === 0) {
+ if (
+ validateSelection(lastSelection!, edges).length === 0
+ ) {
+ const clonedNodes = cloneDeep(nodes)
+ const clonedEdges = cloneDeep(edges)
+ const clonedSelection = cloneDeep(lastSelection)
+ updateIds({ nodes: clonedNodes, edges: clonedEdges }, clonedSelection!)
const { newFlow, removedEdges } = generateFlow(
- lastSelection!,
- nodes,
- edges,
+ clonedSelection!,
+ clonedNodes,
+ clonedEdges,
getRandomName()
);
- const newGroupNode = generateNodeFromFlow(newFlow, getNodeId);
- const newEdges = reconnectEdges(newGroupNode, removedEdges);
- setNodes((oldNodes) => [
- ...oldNodes.filter(
- (oldNodes) =>
- !lastSelection?.nodes.some(
- (selectionNode) => selectionNode.id === oldNodes.id
- )
- ),
+ const newGroupNode = generateNodeFromFlow(
+ newFlow,
+ getNodeId
+ );
+ const newEdges = reconnectEdges(
newGroupNode,
- ]);
- setEdges((oldEdges) => [
- ...oldEdges.filter(
- (oldEdge) =>
- !lastSelection!.nodes.some(
- (selectionNode) =>
- selectionNode.id === oldEdge.target ||
- selectionNode.id === oldEdge.source
- )
- ),
- ...newEdges,
- ]);
+ removedEdges
+ );
+ setNodes([...clonedNodes.filter(
+ (oldNodes) =>
+ !clonedSelection?.nodes.some(
+ (selectionNode) =>
+ selectionNode.id === oldNodes.id
+ )), newGroupNode])
+ setEdges([...clonedEdges.filter(
+ (oldEdge) =>
+ !clonedSelection!.nodes.some(
+ (selectionNode) =>
+ selectionNode.id === oldEdge.target ||
+ selectionNode.id === oldEdge.source
+ )), ...newEdges])
} else {
setErrorData({
title: INVALID_SELECTION_ERROR_ALERT,
@@ -431,7 +437,7 @@ export default function Page({
{Object.keys(templates).length > 0 &&
- Object.keys(types).length > 0 ? (
+ Object.keys(types).length > 0 ? (
[
- ...oldNodes.filter(
- (oldNodes) =>
- !lastSelection?.nodes.some(
- (selectionNode) =>
- selectionNode.id === oldNodes.id
- )
- ),
- newGroupNode,
- ]);
- setEdges((oldEdges) => [
- ...oldEdges.filter(
- (oldEdge) =>
- !lastSelection!.nodes.some(
- (selectionNode) =>
- selectionNode.id === oldEdge.target ||
- selectionNode.id === oldEdge.source
- )
- ),
- ...newEdges,
- ]);
+ setNodes([...clonedNodes.filter(
+ (oldNodes) =>
+ !clonedSelection?.nodes.some(
+ (selectionNode) =>
+ selectionNode.id === oldNodes.id
+ )), newGroupNode])
+ setEdges([...clonedEdges.filter(
+ (oldEdge) =>
+ !clonedSelection!.nodes.some(
+ (selectionNode) =>
+ selectionNode.id === oldEdge.target ||
+ selectionNode.id === oldEdge.source
+ )), ...newEdges])
} else {
setErrorData({
title: INVALID_SELECTION_ERROR_ALERT,
diff --git a/src/frontend/src/stores/flowStore.ts b/src/frontend/src/stores/flowStore.ts
index 5ca1de1e02..5e7563974f 100644
--- a/src/frontend/src/stores/flowStore.ts
+++ b/src/frontend/src/stores/flowStore.ts
@@ -1,4 +1,4 @@
-import { cloneDeep } from "lodash";
+import { cloneDeep, zip } from "lodash";
import {
Edge,
EdgeChange,
@@ -26,6 +26,7 @@ import {
ChatOutputType,
FlowPoolObjectType,
FlowStoreType,
+ VertexLayerElementType,
chatInputType,
} from "../types/zustand/flow";
import { buildVertices } from "../utils/buildUtils";
@@ -36,6 +37,9 @@ import {
getNodeId,
scapeJSONParse,
scapedJSONStringfy,
+ updateEdgesIds,
+ updateIds,
+ updateProxyIdsOnTemplate,
validateNodes,
} from "../utils/reactflowUtils";
import { getInputsAndOutputs } from "../utils/storeUtils";
@@ -257,6 +261,14 @@ const useFlowStore = create((set, get) => ({
let newId = getNodeId(node.data.type);
idsMap[node.id] = newId;
+ if (node.data.node!.flow) {
+ let newFlow = node.data.node!.flow;
+ const idsMap = updateIds(newFlow.data!);
+ updateProxyIdsOnTemplate(node.data.node!.template, idsMap);
+ let flowEdges = selection.edges;
+ updateEdgesIds(flowEdges, idsMap);
+ }
+
// Create a new node object
const newNode: NodeType = {
id: newId,
@@ -459,9 +471,18 @@ const useFlowStore = create((set, get) => ({
// verticesLayers is a list of list of vertices ids, where each list is a layer of vertices
// we want to add a new layer (next_vertices_ids) to the list of layers (verticesLayers)
// and the values of next_vertices_ids to the list of vertices ids (verticesIds)
+
+ // const nextVertices will be the zip of vertexBuildData.next_vertices_ids and
+ // vertexBuildData.top_level_vertices
+ // the VertexLayerElementType as {id: next_vertices_id, layer: top_level_vertex}
+ const nextVertices: VertexLayerElementType[] = zip(
+ vertexBuildData.next_vertices_ids,
+ vertexBuildData.top_level_vertices
+ ).map(([id, reference]) => ({ id: id!, reference }));
+
const newLayers = [
...get().verticesBuild!.verticesLayers,
- vertexBuildData.next_vertices_ids,
+ nextVertices,
];
const newIds = [
...get().verticesBuild!.verticesIds,
@@ -508,12 +529,18 @@ const useFlowStore = create((set, get) => ({
get().setIsBuilding(false);
},
onBuildUpdate: handleBuildUpdate,
- onBuildError: (title, list, idList) => {
+ onBuildError: (title: string, list: string[], elementList) => {
+ const idList = elementList
+ .map((element) => element.id)
+ .filter(Boolean) as string[];
useFlowStore.getState().updateBuildStatus(idList, BuildStatus.BUILT);
setErrorData({ list, title });
get().setIsBuilding(false);
},
- onBuildStart: (idList) => {
+ onBuildStart: (elementList) => {
+ const idList = elementList
+ .map((element) => element.reference)
+ .filter(Boolean) as string[];
useFlowStore.getState().updateBuildStatus(idList, BuildStatus.BUILDING);
},
validateNodes: validateSubgraph,
@@ -531,7 +558,7 @@ const useFlowStore = create((set, get) => ({
updateVerticesBuild: (
vertices: {
verticesIds: string[];
- verticesLayers: string[][];
+ verticesLayers: VertexLayerElementType[][];
runId: string;
} | null
) => {
@@ -562,6 +589,7 @@ const useFlowStore = create((set, get) => ({
},
updateBuildStatus: (nodeIdList: string[], status: BuildStatus) => {
const newFlowBuildStatus = { ...get().flowBuildStatus };
+ console.log("newFlowBuildStatus", newFlowBuildStatus);
nodeIdList.forEach((id) => {
newFlowBuildStatus[id] = {
status,
diff --git a/src/frontend/src/types/zustand/flow/index.ts b/src/frontend/src/types/zustand/flow/index.ts
index 0f5fe6ecc6..0fa8c971db 100644
--- a/src/frontend/src/types/zustand/flow/index.ts
+++ b/src/frontend/src/types/zustand/flow/index.ts
@@ -35,6 +35,11 @@ export type FlowPoolObjectType = {
buildId: string;
};
+export type VertexLayerElementType = {
+ id: string;
+ reference?: string;
+};
+
export type FlowPoolType = {
[key: string]: Array;
};
@@ -103,7 +108,7 @@ export type FlowStoreType = {
updateVerticesBuild: (
vertices: {
verticesIds: string[];
- verticesLayers: string[][];
+ verticesLayers: VertexLayerElementType[][];
runId: string;
} | null
) => void;
@@ -111,7 +116,7 @@ export type FlowStoreType = {
removeFromVerticesBuild: (vertices: string[]) => void;
verticesBuild: {
verticesIds: string[];
- verticesLayers: string[][];
+ verticesLayers: VertexLayerElementType[][];
runId: string;
} | null;
updateBuildStatus: (nodeId: string[], status: BuildStatus) => void;
diff --git a/src/frontend/src/utils/buildUtils.ts b/src/frontend/src/utils/buildUtils.ts
index 765630849c..e702ec64be 100644
--- a/src/frontend/src/utils/buildUtils.ts
+++ b/src/frontend/src/utils/buildUtils.ts
@@ -4,6 +4,7 @@ import { getVerticesOrder, postBuildVertex } from "../controllers/API";
import useAlertStore from "../stores/alertStore";
import useFlowStore from "../stores/flowStore";
import { VertexBuildTypeAPI } from "../types/api";
+import { VertexLayerElementType } from "../types/zustand/flow";
type BuildVerticesParams = {
flowId: string; // Assuming FlowType is the type for your flow
@@ -17,8 +18,8 @@ type BuildVerticesParams = {
buildId: string
) => void; // Replace any with the actual type if it's not any
onBuildComplete?: (allNodesValid: boolean) => void;
- onBuildError?: (title, list, idList: string[]) => void;
- onBuildStart?: (idList: string[]) => void;
+ onBuildError?: (title, list, idList: VertexLayerElementType[]) => void;
+ onBuildStart?: (idList: VertexLayerElementType[]) => void;
validateNodes?: (nodes: string[]) => void;
};
@@ -49,7 +50,7 @@ export async function updateVerticesOrder(
startNodeId?: string | null,
stopNodeId?: string | null
): Promise<{
- verticesLayers: string[][];
+ verticesLayers: VertexLayerElementType[][];
verticesIds: string[];
runId: string;
}> {
@@ -67,7 +68,14 @@ export async function updateVerticesOrder(
useFlowStore.getState().setIsBuilding(false);
throw new Error("Invalid nodes");
}
- let verticesLayers: Array> = [orderResponse.data.ids];
+ // orderResponse.data.ids,
+ // for each id we need to build the VertexLayerElementType object as
+ // {id: id, reference: id}
+ let verticesLayers: Array> =
+ orderResponse.data.ids.map((id: string) => {
+ return [{ id: id, reference: id }];
+ });
+
const runId = orderResponse.data.run_id;
// if (nodeId) {
// for (let i = 0; i < verticesOrder.length; i += 1) {
@@ -161,17 +169,17 @@ export async function buildVertices({
if (onBuildStart) onBuildStart(currentLayer);
// Build each vertex in the current layer
await Promise.all(
- currentLayer.map(async (vertexId) => {
+ currentLayer.map(async (element) => {
// Check if id is in the list of inactive nodes
if (
!useFlowStore
.getState()
- .verticesBuild?.verticesIds.includes(vertexId) &&
+ .verticesBuild?.verticesIds.includes(element.id) &&
onBuildUpdate
) {
// If it is, skip building and set the state to inactive
onBuildUpdate(
- getInactiveVertexData(vertexId),
+ getInactiveVertexData(element.id),
BuildStatus.INACTIVE,
runId
);
@@ -182,7 +190,7 @@ export async function buildVertices({
// Build the vertex
await buildVertex({
flowId,
- id: vertexId,
+ id: element.id,
input_value,
onBuildUpdate: (data: VertexBuildTypeAPI, status: BuildStatus) => {
if (onBuildUpdate) onBuildUpdate(data, status, runId);
@@ -227,7 +235,7 @@ async function buildVertex({
id: string;
input_value: string;
onBuildUpdate?: (data: any, status: BuildStatus) => void;
- onBuildError?: (title, list, idList: string[]) => void;
+ onBuildError?: (title, list, idList: VertexLayerElementType[]) => void;
verticesIds: string[];
buildResults: boolean[];
stopBuild: () => void;
@@ -241,7 +249,7 @@ async function buildVertex({
onBuildError!(
"Error Building Component",
[buildData.params],
- verticesIds
+ verticesIds.map((id) => ({ id }))
);
stopBuild();
}
@@ -252,7 +260,7 @@ async function buildVertex({
onBuildError!(
"Error Building Component",
[(error as AxiosError).response?.data?.detail ?? "Unknown Error"],
- verticesIds
+ verticesIds.map((id) => ({ id }))
);
stopBuild();
}
diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts
index 2f3d6e81d8..62c522129a 100644
--- a/src/frontend/src/utils/reactflowUtils.ts
+++ b/src/frontend/src/utils/reactflowUtils.ts
@@ -204,23 +204,33 @@ export const processDataFromFlow = (flow: FlowType, refreshIds = true) => {
return data;
};
-export function updateIds(newFlow: ReactFlowJsonObject) {
+export function updateIds(
+ { edges, nodes }: { edges: Edge[]; nodes: Node[] },
+ selection?: { edges: Edge[]; nodes: Node[] }
+) {
let idsMap = {};
-
- if (newFlow.nodes)
- newFlow.nodes.forEach((node: NodeType) => {
+ const selectionIds = selection?.nodes.map((n) => n.id);
+ if (nodes) {
+ nodes.forEach((node: NodeType) => {
// Generate a unique node ID
- let newId = getNodeId(
- node.data.node?.flow ? "GroupNode" : node.data.type
- );
+ let newId = getNodeId(node.data.type);
+ if (selection && !selectionIds?.includes(node.id)) {
+ newId = node.id;
+ }
idsMap[node.id] = newId;
node.id = newId;
node.data.id = newId;
// Add the new node to the list of nodes in state
});
-
- if (newFlow.edges)
- newFlow.edges.forEach((edge: Edge) => {
+ selection?.nodes.forEach((sNode: NodeType) => {
+ let newId = idsMap[sNode.id];
+ sNode.id = newId;
+ sNode.data.id = newId;
+ });
+ }
+ const concatedEdges = [...edges, ...(selection?.edges ?? [])];
+ if (concatedEdges)
+ concatedEdges.forEach((edge: Edge) => {
edge.source = idsMap[edge.source];
edge.target = idsMap[edge.target];
const sourceHandleObject: sourceHandleType = scapeJSONParse(
@@ -955,7 +965,7 @@ export function connectedInputNodesOnHandle(
return connectedNodes;
}
-function updateProxyIdsOnTemplate(
+export function updateProxyIdsOnTemplate(
template: APITemplateType,
idsMap: { [key: string]: string }
) {
@@ -966,12 +976,16 @@ function updateProxyIdsOnTemplate(
});
}
-function updateEdgesIds(edges: Edge[], idsMap: { [key: string]: string }) {
+export function updateEdgesIds(
+ edges: Edge[],
+ idsMap: { [key: string]: string }
+) {
edges.forEach((edge) => {
let targetHandle: targetHandleType = edge.data.targetHandle;
if (targetHandle.proxy && idsMap[targetHandle.proxy!.id]) {
targetHandle.proxy!.id = idsMap[targetHandle.proxy!.id];
}
+ console.log("edge", edge);
edge.data.targetHandle = targetHandle;
edge.targetHandle = scapedJSONStringfy(targetHandle);
});
diff --git a/src/frontend/src/utils/utils.ts b/src/frontend/src/utils/utils.ts
index 35326a456d..3bb6218088 100644
--- a/src/frontend/src/utils/utils.ts
+++ b/src/frontend/src/utils/utils.ts
@@ -705,3 +705,8 @@ export function sortFields(a, b, fieldOrder) {
// You might want to sort them alphabetically or in another specific manner
return a.localeCompare(b);
}
+
+export function freezeObject(obj: any) {
+ if(!obj) return obj;
+ return(JSON.parse(JSON.stringify(obj)));
+}
\ No newline at end of file