Revert "fix(frontend): improve history tracking and error handling in flow ed…"

This reverts commit 11d5ef2f43.
This commit is contained in:
Abhimanyu Yadav
2026-01-16 18:50:00 +05:30
committed by GitHub
parent 11d5ef2f43
commit 88e92a36a6
8 changed files with 63 additions and 161 deletions

View File

@@ -48,29 +48,17 @@ export const useRunInputDialog = ({
},
onError: (error) => {
if (error instanceof ApiError && error.isGraphValidationError()) {
const errorData = error.response?.detail || {
node_errors: {},
message: undefined,
};
const nodeErrors = errorData.node_errors || {};
if (Object.keys(nodeErrors).length > 0) {
Object.entries(nodeErrors).forEach(
([nodeId, nodeErrorsForNode]) => {
useNodeStore
.getState()
.updateNodeErrors(
nodeId,
nodeErrorsForNode as { [key: string]: string },
);
},
);
} else {
useNodeStore.getState().nodes.forEach((node) => {
useNodeStore.getState().updateNodeErrors(node.id, {});
});
}
const errorData = error.response?.detail;
Object.entries(errorData.node_errors).forEach(
([nodeId, nodeErrors]) => {
useNodeStore
.getState()
.updateNodeErrors(
nodeId,
nodeErrors as { [key: string]: string },
);
},
);
toast({
title: errorData?.message || "Graph validation failed",
description:
@@ -79,7 +67,7 @@ export const useRunInputDialog = ({
});
setIsOpen(false);
const firstBackendId = Object.keys(nodeErrors)[0];
const firstBackendId = Object.keys(errorData.node_errors)[0];
if (firstBackendId) {
const firstErrorNode = useNodeStore

View File

@@ -55,16 +55,14 @@ export const Flow = () => {
const edgeTypes = useMemo(() => ({ custom: CustomEdge }), []);
const onNodeDragStop = useCallback(() => {
const currentNodes = useNodeStore.getState().nodes;
setNodes(
resolveCollisions(currentNodes, {
resolveCollisions(nodes, {
maxIterations: Infinity,
overlapThreshold: 0.5,
margin: 15,
}),
);
}, [setNodes]);
}, [setNodes, nodes]);
const { edges, onConnect, onEdgesChange } = useCustomEdge();
// for loading purpose

View File

@@ -6,7 +6,6 @@ import {
import { useEdgeStore } from "@/app/(platform)/build/stores/edgeStore";
import { useCallback } from "react";
import { useNodeStore } from "../../../stores/nodeStore";
import { useHistoryStore } from "../../../stores/historyStore";
import { CustomEdge } from "./CustomEdge";
export const useCustomEdge = () => {
@@ -52,20 +51,7 @@ export const useCustomEdge = () => {
const onEdgesChange = useCallback(
(changes: EdgeChange<CustomEdge>[]) => {
const hasRemoval = changes.some((change) => change.type === "remove");
const prevState = hasRemoval
? {
nodes: useNodeStore.getState().nodes,
edges: edges,
}
: null;
setEdges(applyEdgeChanges(changes, edges));
if (prevState) {
useHistoryStore.getState().pushState(prevState);
}
},
[edges, setEdges],
);

View File

@@ -22,7 +22,7 @@ export const NodeHeader = ({ data, nodeId }: Props) => {
const updateNodeData = useNodeStore((state) => state.updateNodeData);
const title =
(data.metadata?.customized_name as string) ||
data.hardcodedValues?.agent_name ||
data.hardcodedValues.agent_name ||
data.title;
const [isEditingTitle, setIsEditingTitle] = useState(false);

View File

@@ -5,8 +5,6 @@ import { customEdgeToLink, linkToCustomEdge } from "../components/helper";
import { MarkerType } from "@xyflow/react";
import { NodeExecutionResult } from "@/app/api/__generated__/models/nodeExecutionResult";
import { cleanUpHandleId } from "@/components/renderers/InputRenderer/helpers";
import { useHistoryStore } from "./historyStore";
import { useNodeStore } from "./nodeStore";
type EdgeStore = {
edges: CustomEdge[];
@@ -55,36 +53,25 @@ export const useEdgeStore = create<EdgeStore>((set, get) => ({
id,
};
const exists = get().edges.some(
(e) =>
e.source === newEdge.source &&
e.target === newEdge.target &&
e.sourceHandle === newEdge.sourceHandle &&
e.targetHandle === newEdge.targetHandle,
);
if (exists) return newEdge;
const prevState = {
nodes: useNodeStore.getState().nodes,
edges: get().edges,
};
set((state) => ({ edges: [...state.edges, newEdge] }));
useHistoryStore.getState().pushState(prevState);
set((state) => {
const exists = state.edges.some(
(e) =>
e.source === newEdge.source &&
e.target === newEdge.target &&
e.sourceHandle === newEdge.sourceHandle &&
e.targetHandle === newEdge.targetHandle,
);
if (exists) return state;
return { edges: [...state.edges, newEdge] };
});
return newEdge;
},
removeEdge: (edgeId) => {
const prevState = {
nodes: useNodeStore.getState().nodes,
edges: get().edges,
};
removeEdge: (edgeId) =>
set((state) => ({
edges: state.edges.filter((e) => e.id !== edgeId),
}));
useHistoryStore.getState().pushState(prevState);
},
})),
upsertMany: (edges) =>
set((state) => {

View File

@@ -37,15 +37,6 @@ export const useHistoryStore = create<HistoryStore>((set, get) => ({
return;
}
const actualCurrentState = {
nodes: useNodeStore.getState().nodes,
edges: useEdgeStore.getState().edges,
};
if (isEqual(state, actualCurrentState)) {
return;
}
set((prev) => ({
past: [...prev.past.slice(-MAX_HISTORY + 1), state],
future: [],
@@ -64,25 +55,18 @@ export const useHistoryStore = create<HistoryStore>((set, get) => ({
undo: () => {
const { past, future } = get();
if (past.length === 0) return;
if (past.length <= 1) return;
const actualCurrentState = {
nodes: useNodeStore.getState().nodes,
edges: useEdgeStore.getState().edges,
};
const currentState = past[past.length - 1];
const previousState = past[past.length - 1];
if (isEqual(actualCurrentState, previousState)) {
return;
}
const previousState = past[past.length - 2];
useNodeStore.getState().setNodes(previousState.nodes);
useEdgeStore.getState().setEdges(previousState.edges);
set({
past: past.length > 1 ? past.slice(0, -1) : past,
future: [actualCurrentState, ...future],
past: past.slice(0, -1),
future: [currentState, ...future],
});
},
@@ -90,36 +74,18 @@ export const useHistoryStore = create<HistoryStore>((set, get) => ({
const { past, future } = get();
if (future.length === 0) return;
const actualCurrentState = {
nodes: useNodeStore.getState().nodes,
edges: useEdgeStore.getState().edges,
};
const nextState = future[0];
useNodeStore.getState().setNodes(nextState.nodes);
useEdgeStore.getState().setEdges(nextState.edges);
const lastPast = past[past.length - 1];
const shouldPushToPast =
!lastPast || !isEqual(actualCurrentState, lastPast);
set({
past: shouldPushToPast ? [...past, actualCurrentState] : past,
past: [...past, nextState],
future: future.slice(1),
});
},
canUndo: () => {
const { past } = get();
if (past.length === 0) return false;
const actualCurrentState = {
nodes: useNodeStore.getState().nodes,
edges: useEdgeStore.getState().edges,
};
return !isEqual(actualCurrentState, past[past.length - 1]);
},
canUndo: () => get().past.length > 1,
canRedo: () => get().future.length > 0,
clear: () => set({ past: [{ nodes: [], edges: [] }], future: [] }),

View File

@@ -1,7 +1,6 @@
import { create } from "zustand";
import { NodeChange, XYPosition, applyNodeChanges } from "@xyflow/react";
import { CustomNode } from "../components/FlowEditor/nodes/CustomNode/CustomNode";
import { CustomEdge } from "../components/FlowEditor/edges/CustomEdge";
import { BlockInfo } from "@/app/api/__generated__/models/blockInfo";
import {
convertBlockInfoIntoCustomNodeData,
@@ -45,8 +44,6 @@ const MINIMUM_MOVE_BEFORE_LOG = 50;
// Track initial positions when drag starts (outside store to avoid re-renders)
const dragStartPositions: Record<string, XYPosition> = {};
let dragStartState: { nodes: CustomNode[]; edges: CustomEdge[] } | null = null;
type NodeStore = {
nodes: CustomNode[];
nodeCounter: number;
@@ -127,20 +124,14 @@ export const useNodeStore = create<NodeStore>((set, get) => ({
nodeCounter: state.nodeCounter + 1,
})),
onNodesChange: (changes) => {
const prevState = {
nodes: get().nodes,
edges: useEdgeStore.getState().edges,
};
// Track initial positions when drag starts
changes.forEach((change) => {
if (change.type === "position" && change.dragging === true) {
if (!dragStartState) {
const currentNodes = get().nodes;
const currentEdges = useEdgeStore.getState().edges;
dragStartState = {
nodes: currentNodes.map((n) => ({
...n,
position: { ...n.position },
data: { ...n.data },
})),
edges: currentEdges.map((e) => ({ ...e })),
};
}
if (!dragStartPositions[change.id]) {
const node = get().nodes.find((n) => n.id === change.id);
if (node) {
@@ -150,17 +141,12 @@ export const useNodeStore = create<NodeStore>((set, get) => ({
}
});
let shouldTrack = changes.some((change) => change.type === "remove");
let stateToTrack: { nodes: CustomNode[]; edges: CustomEdge[] } | null =
null;
if (shouldTrack) {
stateToTrack = {
nodes: get().nodes,
edges: useEdgeStore.getState().edges,
};
}
// Check if we should track this change in history
let shouldTrack = changes.some(
(change) => change.type === "remove" || change.type === "add",
);
// For position changes, only track if movement exceeds threshold
if (!shouldTrack) {
changes.forEach((change) => {
if (change.type === "position" && change.dragging === false) {
@@ -172,23 +158,20 @@ export const useNodeStore = create<NodeStore>((set, get) => ({
);
if (distanceMoved > MINIMUM_MOVE_BEFORE_LOG) {
shouldTrack = true;
stateToTrack = dragStartState;
}
}
// Clean up tracked position after drag ends
delete dragStartPositions[change.id];
}
});
if (Object.keys(dragStartPositions).length === 0) {
dragStartState = null;
}
}
set((state) => ({
nodes: applyNodeChanges(changes, state.nodes),
}));
if (shouldTrack && stateToTrack) {
useHistoryStore.getState().pushState(stateToTrack);
if (shouldTrack) {
useHistoryStore.getState().pushState(prevState);
}
},
@@ -202,11 +185,6 @@ export const useNodeStore = create<NodeStore>((set, get) => ({
hardcodedValues?: Record<string, any>,
position?: XYPosition,
) => {
const prevState = {
nodes: get().nodes,
edges: useEdgeStore.getState().edges,
};
const customNodeData = convertBlockInfoIntoCustomNodeData(
block,
hardcodedValues,
@@ -240,24 +218,21 @@ export const useNodeStore = create<NodeStore>((set, get) => ({
set((state) => ({
nodes: [...state.nodes, customNode],
}));
useHistoryStore.getState().pushState(prevState);
return customNode;
},
updateNodeData: (nodeId, data) => {
const prevState = {
nodes: get().nodes,
edges: useEdgeStore.getState().edges,
};
set((state) => ({
nodes: state.nodes.map((n) =>
n.id === nodeId ? { ...n, data: { ...n.data, ...data } } : n,
),
}));
useHistoryStore.getState().pushState(prevState);
const newState = {
nodes: get().nodes,
edges: useEdgeStore.getState().edges,
};
useHistoryStore.getState().pushState(newState);
},
toggleAdvanced: (nodeId: string) =>
set((state) => ({
@@ -416,11 +391,6 @@ export const useNodeStore = create<NodeStore>((set, get) => ({
},
setCredentialsOptional: (nodeId: string, optional: boolean) => {
const prevState = {
nodes: get().nodes,
edges: useEdgeStore.getState().edges,
};
set((state) => ({
nodes: state.nodes.map((n) =>
n.id === nodeId
@@ -438,7 +408,12 @@ export const useNodeStore = create<NodeStore>((set, get) => ({
),
}));
useHistoryStore.getState().pushState(prevState);
const newState = {
nodes: get().nodes,
edges: useEdgeStore.getState().edges,
};
useHistoryStore.getState().pushState(newState);
},
// Sub-agent resolution mode state

View File

@@ -30,6 +30,8 @@ export const FormRenderer = ({
return generateUiSchemaForCustomFields(preprocessedSchema, uiSchema);
}, [preprocessedSchema, uiSchema]);
console.log("preprocessedSchema", preprocessedSchema);
return (
<div className={"mb-6 mt-4"} data-tutorial-id="input-handles">
<Form