feat(validate): add debounced validation for nodes in GenericNode component
fix(validate): set validation status to "success" or "error" based on response status feat(validate): add state for validation status and outline color in GenericNode component refactor(validate): use useDebouncedCallback hook for debouncing validation function refactor(validate): simplify useEffect dependencies in GenericNode component
This commit is contained in:
parent
f122151b12
commit
7af5f68861
2 changed files with 180 additions and 163 deletions
|
|
@ -46,7 +46,7 @@ def post_validate_node(node_id: str, data: dict):
|
||||||
node = graph.get_node(node_id)
|
node = graph.get_node(node_id)
|
||||||
if node is not None:
|
if node is not None:
|
||||||
_ = node.build()
|
_ = node.build()
|
||||||
return node.params
|
return str(node.params)
|
||||||
raise Exception(f"Node {node_id} not found")
|
raise Exception(f"Node {node_id} not found")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
|
|
|
||||||
|
|
@ -1,186 +1,203 @@
|
||||||
import { TrashIcon } from "@heroicons/react/24/outline";
|
import { TrashIcon } from "@heroicons/react/24/outline";
|
||||||
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
import {
|
import {
|
||||||
classNames,
|
classNames,
|
||||||
nodeColors,
|
nodeColors,
|
||||||
nodeIcons,
|
nodeIcons,
|
||||||
snakeToNormalCase,
|
snakeToNormalCase,
|
||||||
} from "../../utils";
|
} from "../../utils";
|
||||||
import ParameterComponent from "./components/parameterComponent";
|
import ParameterComponent from "./components/parameterComponent";
|
||||||
import { typesContext } from "../../contexts/typesContext";
|
import { typesContext } from "../../contexts/typesContext";
|
||||||
import { useContext, useState, useEffect, useRef } from "react";
|
import { useContext, useState, useEffect, useRef } from "react";
|
||||||
import { NodeDataType } from "../../types/flow";
|
import { NodeDataType } from "../../types/flow";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from "react";
|
||||||
|
|
||||||
export default function GenericNode({
|
export default function GenericNode({
|
||||||
data,
|
data,
|
||||||
selected,
|
selected,
|
||||||
}: {
|
}: {
|
||||||
data: NodeDataType;
|
data: NodeDataType;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { setErrorData } = useContext(alertContext);
|
const { setErrorData } = useContext(alertContext);
|
||||||
const showError = useRef(true);
|
const showError = useRef(true);
|
||||||
const { types, deleteNode } = useContext(typesContext);
|
const { types, deleteNode } = useContext(typesContext);
|
||||||
const Icon = nodeIcons[types[data.type]];
|
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 debouncedValidateNode = useDebouncedCallback(async () => {
|
||||||
const [isGreenOutline, setIsGreenOutline] = useState(false);
|
// Check if the validationStatus is "success"
|
||||||
const [isRedOutline, setIsRedOutline] = useState(false);
|
if (validationStatus === "success") return;
|
||||||
const { reactFlowInstance } = useContext(typesContext);
|
|
||||||
|
|
||||||
const validateNode = useCallback(async () => {
|
try {
|
||||||
try {
|
const response = await fetch(`/validate/node/${data.id}`, {
|
||||||
const response = await fetch(`/validate/node/${data.id}`, {
|
method: "POST",
|
||||||
method: 'POST',
|
headers: {
|
||||||
headers: {
|
"Content-Type": "application/json",
|
||||||
'Content-Type': 'application/json',
|
},
|
||||||
},
|
body: JSON.stringify(reactFlowInstance.toObject()),
|
||||||
body: JSON.stringify(reactFlowInstance.toObject()),
|
});
|
||||||
});
|
|
||||||
|
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
setIsGreenOutline(true);
|
setValidationStatus("success");
|
||||||
setIsRedOutline(false);
|
} else if (response.status === 500) {
|
||||||
} else if (response.status === 500) {
|
setValidationStatus("error");
|
||||||
setIsRedOutline(true);
|
}
|
||||||
setIsGreenOutline(false);
|
} catch (error) {
|
||||||
}
|
console.error("Error validating node:", error);
|
||||||
} catch (error) {
|
setValidationStatus("error");
|
||||||
console.error('Error validating node:', error);
|
}
|
||||||
setIsRedOutline(true);
|
}, 1000);
|
||||||
}
|
|
||||||
}, [data.id, reactFlowInstance]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const validateNode = useCallback(() => {
|
||||||
validateNode();
|
debouncedValidateNode();
|
||||||
}, [
|
}, [debouncedValidateNode]);
|
||||||
validateNode,
|
|
||||||
...Object.values(data.node.template).flatMap((t) => Object.values(t)),
|
|
||||||
|
|
||||||
]);
|
useEffect(() => {
|
||||||
|
validateNode();
|
||||||
|
}, [
|
||||||
|
validateNode,
|
||||||
|
...Object.values(data.node.template).flatMap((t) => Object.values(t)),
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isGreenOutline) {
|
if (validationStatus === "success") {
|
||||||
setTimeout(() => {
|
setIsGreenOutline(true);
|
||||||
setIsGreenOutline(false);
|
setIsRedOutline(false);
|
||||||
}, 1000);
|
setTimeout(() => {
|
||||||
}
|
setIsGreenOutline(false);
|
||||||
}, [isGreenOutline]);
|
}, 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 (!Icon) {
|
||||||
if (showError.current) {
|
if (showError.current) {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: data.type
|
title: data.type
|
||||||
? `The ${data.type} node could not be rendered, please review your json file`
|
? `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",
|
: "There was a node that can't be rendered, please review your json file",
|
||||||
});
|
});
|
||||||
showError.current = false;
|
showError.current = false;
|
||||||
}
|
}
|
||||||
deleteNode(data.id);
|
deleteNode(data.id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
outlineColor,
|
outlineColor,
|
||||||
selected ? "border border-blue-500" : "border dark:border-gray-700",
|
selected ? "border border-blue-500" : "border dark:border-gray-700",
|
||||||
"prompt-node relative bg-white dark:bg-gray-900 w-96 rounded-lg flex flex-col justify-center"
|
"prompt-node relative bg-white dark:bg-gray-900 w-96 rounded-lg flex flex-col justify-center"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="w-full dark:text-white flex items-center justify-between p-4 gap-8 bg-gray-50 rounded-t-lg dark:bg-gray-800 border-b dark:border-b-gray-700 ">
|
<div className="w-full dark:text-white flex items-center justify-between p-4 gap-8 bg-gray-50 rounded-t-lg dark:bg-gray-800 border-b dark:border-b-gray-700 ">
|
||||||
<div className="w-full flex items-center truncate gap-4 text-lg">
|
<div className="w-full flex items-center truncate gap-4 text-lg">
|
||||||
<Icon
|
<Icon
|
||||||
className="w-10 h-10 p-1 rounded"
|
className="w-10 h-10 p-1 rounded"
|
||||||
style={{
|
style={{
|
||||||
color: nodeColors[types[data.type]] ?? nodeColors.unknown,
|
color: nodeColors[types[data.type]] ?? nodeColors.unknown,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="truncate">{data.type}</div>
|
<div className="truncate">{data.type}</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteNode(data.id);
|
deleteNode(data.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TrashIcon className="w-6 h-6 hover:text-red-500 dark:text-gray-500 dark:hover:text-red-500"></TrashIcon>
|
<TrashIcon className="w-6 h-6 hover:text-red-500 dark:text-gray-500 dark:hover:text-red-500"></TrashIcon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full h-full py-5">
|
<div className="w-full h-full py-5">
|
||||||
<div className="w-full text-gray-500 px-5 text-sm">
|
<div className="w-full text-gray-500 px-5 text-sm">
|
||||||
{data.node.description}
|
{data.node.description}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<>
|
<>
|
||||||
{Object.keys(data.node.template)
|
{Object.keys(data.node.template)
|
||||||
.filter((t) => t.charAt(0) !== "_")
|
.filter((t) => t.charAt(0) !== "_")
|
||||||
.map((t: string, idx) => (
|
.map((t: string, idx) => (
|
||||||
<div key={idx}>
|
<div key={idx}>
|
||||||
{idx === 0 ? (
|
{idx === 0 ? (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"px-5 py-2 mt-2 dark:text-white text-center",
|
"px-5 py-2 mt-2 dark:text-white text-center",
|
||||||
Object.keys(data.node.template).filter(
|
Object.keys(data.node.template).filter(
|
||||||
(key) =>
|
(key) =>
|
||||||
!key.startsWith("_") && data.node.template[key].show
|
!key.startsWith("_") && data.node.template[key].show
|
||||||
).length === 0
|
).length === 0
|
||||||
? "hidden"
|
? "hidden"
|
||||||
: ""
|
: ""
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Inputs
|
Inputs
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
{data.node.template[t].show ? (
|
{data.node.template[t].show ? (
|
||||||
<ParameterComponent
|
<ParameterComponent
|
||||||
data={data}
|
data={data}
|
||||||
color={
|
color={
|
||||||
nodeColors[types[data.node.template[t].type]] ??
|
nodeColors[types[data.node.template[t].type]] ??
|
||||||
nodeColors.unknown
|
nodeColors.unknown
|
||||||
}
|
}
|
||||||
title={
|
title={
|
||||||
data.node.template[t].display_name
|
data.node.template[t].display_name
|
||||||
? data.node.template[t].display_name
|
? data.node.template[t].display_name
|
||||||
: data.node.template[t].name
|
: data.node.template[t].name
|
||||||
? snakeToNormalCase(data.node.template[t].name)
|
? snakeToNormalCase(data.node.template[t].name)
|
||||||
: snakeToNormalCase(t)
|
: snakeToNormalCase(t)
|
||||||
}
|
}
|
||||||
name={t}
|
name={t}
|
||||||
tooltipTitle={
|
tooltipTitle={
|
||||||
"Type: " +
|
"Type: " +
|
||||||
data.node.template[t].type +
|
data.node.template[t].type +
|
||||||
(data.node.template[t].list ? " list" : "")
|
(data.node.template[t].list ? " list" : "")
|
||||||
}
|
}
|
||||||
required={data.node.template[t].required}
|
required={data.node.template[t].required}
|
||||||
id={data.node.template[t].type + "|" + t + "|" + data.id}
|
id={data.node.template[t].type + "|" + t + "|" + data.id}
|
||||||
left={true}
|
left={true}
|
||||||
type={data.node.template[t].type}
|
type={data.node.template[t].type}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div className="px-5 py-2 mt-2 dark:text-white text-center">
|
<div className="px-5 py-2 mt-2 dark:text-white text-center">
|
||||||
Output
|
Output
|
||||||
</div>
|
</div>
|
||||||
<ParameterComponent
|
<ParameterComponent
|
||||||
data={data}
|
data={data}
|
||||||
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
|
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
|
||||||
title={data.type}
|
title={data.type}
|
||||||
tooltipTitle={`Type: ${data.node.base_classes.join(" | ")}`}
|
tooltipTitle={`Type: ${data.node.base_classes.join(" | ")}`}
|
||||||
id={[data.type, data.id, ...data.node.base_classes].join("|")}
|
id={[data.type, data.id, ...data.node.base_classes].join("|")}
|
||||||
type={data.node.base_classes.join("|")}
|
type={data.node.base_classes.join("|")}
|
||||||
left={false}
|
left={false}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue