mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-10 07:38:04 -05:00
refactor(frontend): synchronize hardcoded values with handle IDs in FlowEditor
### Changes - Implemented `syncHardcodedValuesWithHandleIds` in `useNodeStore` to ensure hardcoded values remain consistent with handle IDs. - Updated `useFlow` to call the new synchronization method for each custom node when nodes are added. ### Impact These changes enhance data integrity within the FlowEditor by preventing inconsistencies between hardcoded values and their corresponding handle IDs, improving overall functionality and user experience.
This commit is contained in:
@@ -121,6 +121,14 @@ export const useFlow = () => {
|
||||
if (customNodes.length > 0) {
|
||||
useNodeStore.getState().setNodes([]);
|
||||
addNodes(customNodes);
|
||||
|
||||
// Sync hardcoded values with handle IDs.
|
||||
// If a key–value field has a key without a value, the backend omits it from hardcoded values.
|
||||
// But if a handleId exists for that key, it causes inconsistency.
|
||||
// This ensures hardcoded values stay in sync with handle IDs.
|
||||
customNodes.forEach((node) => {
|
||||
useNodeStore.getState().syncHardcodedValuesWithHandleIds(node.id);
|
||||
});
|
||||
}
|
||||
}, [customNodes, addNodes]);
|
||||
|
||||
|
||||
@@ -13,6 +13,10 @@ import { useHistoryStore } from "./historyStore";
|
||||
import { useEdgeStore } from "./edgeStore";
|
||||
import { BlockUIType } from "../components/types";
|
||||
import { pruneEmptyValues } from "@/lib/utils";
|
||||
import {
|
||||
ensurePathExists,
|
||||
parseHandleIdToPath,
|
||||
} from "@/components/renderers/input-renderer-2/helpers";
|
||||
|
||||
// Minimum movement (in pixels) required before logging position change to history
|
||||
// Prevents spamming history with small movements when clicking on inputs inside blocks
|
||||
@@ -62,6 +66,8 @@ type NodeStore = {
|
||||
errors: { [key: string]: string },
|
||||
) => void;
|
||||
clearAllNodeErrors: () => void; // Add this
|
||||
|
||||
syncHardcodedValuesWithHandleIds: (nodeId: string) => void;
|
||||
};
|
||||
|
||||
export const useNodeStore = create<NodeStore>((set, get) => ({
|
||||
@@ -305,4 +311,35 @@ export const useNodeStore = create<NodeStore>((set, get) => ({
|
||||
})),
|
||||
}));
|
||||
},
|
||||
|
||||
syncHardcodedValuesWithHandleIds: (nodeId: string) => {
|
||||
const node = get().nodes.find((n) => n.id === nodeId);
|
||||
if (!node) return;
|
||||
|
||||
const handleIds = useEdgeStore.getState().getAllHandleIdsOfANode(nodeId);
|
||||
const additionalHandles = handleIds.filter((h) => h.includes("_#_"));
|
||||
|
||||
if (additionalHandles.length === 0) return;
|
||||
|
||||
const hardcodedValues = JSON.parse(
|
||||
JSON.stringify(node.data.hardcodedValues || {}),
|
||||
);
|
||||
|
||||
let modified = false;
|
||||
|
||||
additionalHandles.forEach((handleId) => {
|
||||
const segments = parseHandleIdToPath(handleId);
|
||||
if (ensurePathExists(hardcodedValues, segments)) {
|
||||
modified = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (modified) {
|
||||
set((state) => ({
|
||||
nodes: state.nodes.map((n) =>
|
||||
n.id === nodeId ? { ...n, data: { ...n.data, hardcodedValues } } : n,
|
||||
),
|
||||
}));
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
KEY_PAIR_FLAG,
|
||||
OBJECT_FLAG,
|
||||
} from "./constants";
|
||||
import { PathSegment } from "./types";
|
||||
|
||||
export function updateUiOption<T extends Record<string, any>>(
|
||||
uiSchema: T | undefined,
|
||||
@@ -176,3 +177,108 @@ export function isCredentialFieldSchema(schema: any): boolean {
|
||||
"credentials_provider" in schema
|
||||
);
|
||||
}
|
||||
|
||||
export function parseHandleIdToPath(handleId: string): PathSegment[] {
|
||||
let cleanedId = cleanUpHandleId(handleId);
|
||||
const segments: PathSegment[] = [];
|
||||
const parts = cleanedId.split(/(_#_|_@_|_\$_|\.)/);
|
||||
|
||||
let currentType: "property" | "item" | "additional" | "normal" = "normal";
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i];
|
||||
|
||||
if (part === "_#_") {
|
||||
currentType = "additional";
|
||||
} else if (part === "_@_") {
|
||||
currentType = "property";
|
||||
} else if (part === "_$_") {
|
||||
currentType = "item";
|
||||
} else if (part === ".") {
|
||||
currentType = "normal";
|
||||
} else if (part) {
|
||||
const isNumeric = /^\d+$/.test(part);
|
||||
if (currentType === "item" && isNumeric) {
|
||||
segments.push({
|
||||
key: part,
|
||||
type: "item",
|
||||
index: parseInt(part, 10),
|
||||
});
|
||||
} else {
|
||||
segments.push({
|
||||
key: part,
|
||||
type: currentType,
|
||||
});
|
||||
}
|
||||
currentType = "normal";
|
||||
}
|
||||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure a path exists in an object, creating intermediate objects/arrays as needed
|
||||
* Returns true if any modifications were made
|
||||
*/
|
||||
export function ensurePathExists(
|
||||
obj: Record<string, any>,
|
||||
segments: PathSegment[],
|
||||
): boolean {
|
||||
if (segments.length === 0) return false;
|
||||
|
||||
let current = obj;
|
||||
let modified = false;
|
||||
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
const segment = segments[i];
|
||||
const isLast = i === segments.length - 1;
|
||||
const nextSegment = segments[i + 1];
|
||||
|
||||
const getDefaultValue = () => {
|
||||
if (isLast) {
|
||||
return "";
|
||||
}
|
||||
if (nextSegment?.type === "item") {
|
||||
return [];
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
if (segment.type === "item" && segment.index !== undefined) {
|
||||
if (!Array.isArray(current)) {
|
||||
return modified;
|
||||
}
|
||||
|
||||
while (current.length <= segment.index) {
|
||||
current.push(isLast ? "" : {});
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (!isLast) {
|
||||
if (
|
||||
current[segment.index] === undefined ||
|
||||
current[segment.index] === null
|
||||
) {
|
||||
current[segment.index] = getDefaultValue();
|
||||
modified = true;
|
||||
}
|
||||
current = current[segment.index];
|
||||
}
|
||||
} else {
|
||||
if (!(segment.key in current)) {
|
||||
current[segment.key] = getDefaultValue();
|
||||
modified = true;
|
||||
} else if (!isLast && current[segment.key] === undefined) {
|
||||
current[segment.key] = getDefaultValue();
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (!isLast) {
|
||||
current = current[segment.key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
@@ -7,3 +7,9 @@ export interface ExtendedFormContextType extends FormContextType {
|
||||
showHandles?: boolean;
|
||||
size?: "small" | "medium" | "large";
|
||||
}
|
||||
|
||||
export type PathSegment = {
|
||||
key: string;
|
||||
type: "property" | "item" | "additional" | "normal";
|
||||
index?: number;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user