diff --git a/src/backend/langflow/api/validate.py b/src/backend/langflow/api/validate.py index d80010be4..e1b5a3a1a 100644 --- a/src/backend/langflow/api/validate.py +++ b/src/backend/langflow/api/validate.py @@ -46,7 +46,7 @@ def post_validate_node(node_id: str, data: dict): node = graph.get_node(node_id) if node is not None: _ = node.build() - return node.params + return str(node.params) raise Exception(f"Node {node_id} not found") except Exception as e: logger.exception(e) diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index c96591cc9..93497c0fc 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -1,186 +1,203 @@ import { TrashIcon } from "@heroicons/react/24/outline"; +import { useDebouncedCallback } from "use-debounce"; import { - classNames, - nodeColors, - nodeIcons, - snakeToNormalCase, + classNames, + nodeColors, + nodeIcons, + snakeToNormalCase, } from "../../utils"; import ParameterComponent from "./components/parameterComponent"; import { typesContext } from "../../contexts/typesContext"; import { useContext, useState, useEffect, useRef } from "react"; import { NodeDataType } from "../../types/flow"; import { alertContext } from "../../contexts/alertContext"; -import { useCallback } from 'react'; +import { useCallback } from "react"; export default function GenericNode({ - data, - selected, + data, + selected, }: { - data: NodeDataType; - selected: boolean; + data: NodeDataType; + selected: boolean; }) { - const { setErrorData } = useContext(alertContext); - const showError = useRef(true); - const { types, deleteNode } = useContext(typesContext); - const Icon = nodeIcons[types[data.type]]; + const { setErrorData } = useContext(alertContext); + const showError = useRef(true); + const { types, deleteNode } = useContext(typesContext); + const Icon = nodeIcons[types[data.type]]; + const [validationStatus, setValidationStatus] = useState("idle"); + // State for outline color + const [isGreenOutline, setIsGreenOutline] = useState(false); + const [isRedOutline, setIsRedOutline] = useState(false); + const { reactFlowInstance } = useContext(typesContext); - // State for outline color - const [isGreenOutline, setIsGreenOutline] = useState(false); - const [isRedOutline, setIsRedOutline] = useState(false); - const { reactFlowInstance } = useContext(typesContext); + const debouncedValidateNode = useDebouncedCallback(async () => { + // Check if the validationStatus is "success" + if (validationStatus === "success") return; - const validateNode = useCallback(async () => { - try { - const response = await fetch(`/validate/node/${data.id}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(reactFlowInstance.toObject()), - }); + try { + const response = await fetch(`/validate/node/${data.id}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(reactFlowInstance.toObject()), + }); - if (response.status === 200) { - setIsGreenOutline(true); - setIsRedOutline(false); - } else if (response.status === 500) { - setIsRedOutline(true); - setIsGreenOutline(false); - } - } catch (error) { - console.error('Error validating node:', error); - setIsRedOutline(true); - } - }, [data.id, reactFlowInstance]); + if (response.status === 200) { + setValidationStatus("success"); + } else if (response.status === 500) { + setValidationStatus("error"); + } + } catch (error) { + console.error("Error validating node:", error); + setValidationStatus("error"); + } + }, 1000); - useEffect(() => { - validateNode(); - }, [ - validateNode, - ...Object.values(data.node.template).flatMap((t) => Object.values(t)), + const validateNode = useCallback(() => { + debouncedValidateNode(); + }, [debouncedValidateNode]); - ]); + useEffect(() => { + validateNode(); + }, [ + validateNode, + ...Object.values(data.node.template).flatMap((t) => Object.values(t)), + ]); - useEffect(() => { - if (isGreenOutline) { - setTimeout(() => { - setIsGreenOutline(false); - }, 1000); - } - }, [isGreenOutline]); + useEffect(() => { + if (validationStatus === "success") { + setIsGreenOutline(true); + setIsRedOutline(false); + setTimeout(() => { + setIsGreenOutline(false); + }, 1000); + } else if (validationStatus === "error") { + setIsRedOutline(true); + setIsGreenOutline(false); + } else { + setIsGreenOutline(false); + setIsRedOutline(false); + } + }, [validationStatus]); - const outlineColor = isGreenOutline ? 'animate-pulse-green' : isRedOutline ? 'border-red-outline' : ''; + const outlineColor = isGreenOutline + ? "animate-pulse-green" + : isRedOutline + ? "border-red-outline" + : ""; - if (!Icon) { - if (showError.current) { - setErrorData({ - title: data.type - ? `The ${data.type} node could not be rendered, please review your json file` - : "There was a node that can't be rendered, please review your json file", - }); - showError.current = false; - } - deleteNode(data.id); - return; - } + if (!Icon) { + if (showError.current) { + setErrorData({ + title: data.type + ? `The ${data.type} node could not be rendered, please review your json file` + : "There was a node that can't be rendered, please review your json file", + }); + showError.current = false; + } + deleteNode(data.id); + return; + } - return ( -