diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeInputField/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeInputField/index.tsx new file mode 100644 index 000000000..061a5bcce --- /dev/null +++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeInputField/index.tsx @@ -0,0 +1,175 @@ +import useHandleNodeClass from "@/CustomNodes/hooks/use-handle-node-class"; +import { ParameterRenderComponent } from "@/components/parameterRenderComponent"; +import { usePostTemplateValue } from "@/controllers/API/queries/nodes/use-post-template-value"; +import { ReactNode, useEffect, useRef, useState } from "react"; +import { default as IconComponent } from "../../../../components/genericIconComponent"; +import ShadTooltip from "../../../../components/shadTooltipComponent"; +import { LANGFLOW_SUPPORTED_TYPES } from "../../../../constants/constants"; +import useFlowStore from "../../../../stores/flowStore"; +import { useTypesStore } from "../../../../stores/typesStore"; +import { + NodeInputFieldComponentType, + ParameterComponentType, +} from "../../../../types/components"; +import { scapedJSONStringfy } from "../../../../utils/reactflowUtils"; +import useFetchDataOnMount from "../../../hooks/use-fetch-data-on-mount"; +import useHandleOnNewValue from "../../../hooks/use-handle-new-value"; +import HandleRenderComponent from "../handleRenderComponent"; + +export default function NodeInputField({ + id, + data, + tooltipTitle, + title, + colors, + type, + name = "", + required = false, + optionalHandle = null, + info = "", + proxy, + showNode, +}: NodeInputFieldComponentType): JSX.Element { + const ref = useRef(null); + const infoHtml = useRef(null); + const nodes = useFlowStore((state) => state.nodes); + const edges = useFlowStore((state) => state.edges); + const myData = useTypesStore((state) => state.data); + const postTemplateValue = usePostTemplateValue({ + node: data.node!, + nodeId: data.id, + parameterId: name, + }); + const setFilterEdge = useFlowStore((state) => state.setFilterEdge); + const { handleNodeClass } = useHandleNodeClass(data.id); + + let disabled = + edges.some( + (edge) => + edge.targetHandle === scapedJSONStringfy(proxy ? { ...id, proxy } : id), + ) ?? false; + + const { handleOnNewValue } = useHandleOnNewValue({ + node: data.node!, + nodeId: data.id, + name, + }); + + useFetchDataOnMount(data.node!, handleNodeClass, name, postTemplateValue); + + useEffect(() => { + // @ts-ignore + infoHtml.current = ( +
+ {info.split("\n").map((line, index) => ( +

+ {line} +

+ ))} +
+ ); + }, [info]); + + useEffect(() => { + if (optionalHandle && optionalHandle.length === 0) { + optionalHandle = null; + } + }, [optionalHandle]); + + const displayHandle = + !LANGFLOW_SUPPORTED_TYPES.has(type ?? "") || optionalHandle; + + return !showNode ? ( + displayHandle ? ( + + ) : ( + <> + ) + ) : ( +
+ <> +
+ {proxy ? ( + {proxy.id}}> + {{title}} + + ) : ( +
+ {{title}} +
+ )} + + {required ? "*" : ""} + +
+ {info !== "" && ( + + {/* put div to avoid bug that does not display tooltip */} +
+ +
+
+ )} +
+
+ + {displayHandle && ( + + )} + {data.node?.template[name] !== undefined && ( +
+ +
+ )} + +
+ ); +} diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputfield/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputfield/index.tsx new file mode 100644 index 000000000..bb12627b8 --- /dev/null +++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputfield/index.tsx @@ -0,0 +1,245 @@ +import { cloneDeep } from "lodash"; +import { useEffect, useRef, useState } from "react"; +import { useHotkeys } from "react-hotkeys-hook"; +import { useUpdateNodeInternals } from "reactflow"; +import { default as IconComponent } from "../../../../components/genericIconComponent"; +import ShadTooltip from "../../../../components/shadTooltipComponent"; +import { Button } from "../../../../components/ui/button"; +import { Case } from "../../../../shared/components/caseComponent"; +import useFlowStore from "../../../../stores/flowStore"; +import { useShortcutsStore } from "../../../../stores/shortcuts"; +import { useTypesStore } from "../../../../stores/typesStore"; +import { + NodeOutputFieldComponentType, + ParameterComponentType, +} from "../../../../types/components"; +import { + getGroupOutputNodeId, + scapedJSONStringfy, +} from "../../../../utils/reactflowUtils"; +import { + classNames, + cn, + isThereModal, + logHasMessage, + logTypeIsError, + logTypeIsUnknown, +} from "../../../../utils/utils"; +import OutputComponent from "../OutputComponent"; +import HandleRenderComponent from "../handleRenderComponent"; +import OutputModal from "../outputModal"; + +export default function NodeOutputField({ + selected, + data, + title, + id, + colors, + tooltipTitle, + showNode, + index, + type, + outputName, + outputProxy, +}: NodeOutputFieldComponentType): JSX.Element { + const ref = useRef(null); + const nodes = useFlowStore((state) => state.nodes); + const edges = useFlowStore((state) => state.edges); + const setNode = useFlowStore((state) => state.setNode); + const myData = useTypesStore((state) => state.data); + const updateNodeInternals = useUpdateNodeInternals(); + const setFilterEdge = useFlowStore((state) => state.setFilterEdge); + const [openOutputModal, setOpenOutputModal] = useState(false); + const flowPool = useFlowStore((state) => state.flowPool); + + let flowPoolId = data.id; + let internalOutputName = outputName; + + if (data.node?.flow && outputProxy) { + const realOutput = getGroupOutputNodeId( + data.node.flow, + outputProxy.name, + outputProxy.id, + ); + if (realOutput) { + flowPoolId = realOutput.id; + internalOutputName = realOutput.outputName; + } + } + + const flowPoolNode = (flowPool[flowPoolId] ?? [])[ + (flowPool[flowPoolId]?.length ?? 1) - 1 + ]; + + const displayOutputPreview = + !!flowPool[flowPoolId] && + logHasMessage(flowPoolNode?.data, internalOutputName); + + const unknownOutput = logTypeIsUnknown( + flowPoolNode?.data, + internalOutputName, + ); + const errorOutput = logTypeIsError(flowPoolNode?.data, internalOutputName); + + const preventDefault = true; + + function handleOutputWShortcut() { + if (!displayOutputPreview || unknownOutput) return; + if (isThereModal() && !openOutputModal) return; + if (selected) { + setOpenOutputModal((state) => !state); + } + } + + const output = useShortcutsStore((state) => state.output); + useHotkeys(output, handleOutputWShortcut, { preventDefault }); + + let disabledOutput = + edges.some((edge) => edge.sourceHandle === scapedJSONStringfy(id)) ?? false; + + const handleUpdateOutputHide = (value?: boolean) => { + setNode(data.id, (oldNode) => { + let newNode = cloneDeep(oldNode); + newNode.data = { + ...newNode.data, + node: { + ...newNode.data.node, + outputs: newNode.data.node.outputs?.map((output, i) => { + if (i === index) { + output.hidden = value ?? !output.hidden; + } + return output; + }), + }, + }; + return newNode; + }); + updateNodeInternals(data.id); + }; + + useEffect(() => { + if (disabledOutput && data.node?.outputs![index].hidden) { + handleUpdateOutputHide(false); + } + }, [disabledOutput]); + + return !showNode ? ( + + ) : ( +
+ <> +
+
+ +
+ +
+ +
+
+ +
+ + + + + + +
+
+ + {openOutputModal && ( + + )} + +
+ ); +} diff --git a/src/frontend/src/CustomNodes/GenericNode/components/handleRenderComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/handleRenderComponent/index.tsx index 51b50332e..fbf2232de 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/handleRenderComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/handleRenderComponent/index.tsx @@ -24,7 +24,7 @@ export default function HandleRenderComponent({ left: boolean; nodes: any; tooltipTitle?: string; - proxy: any; + proxy?: any; id: any; title: string; edges: any; diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/constants.ts b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/constants.ts deleted file mode 100644 index 58cb45587..000000000 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const TEXT_FIELD_TYPES: string[] = ["str", "SecretStr"]; diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx deleted file mode 100644 index 611fd79be..000000000 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ /dev/null @@ -1,378 +0,0 @@ -import useHandleNodeClass from "@/CustomNodes/hooks/use-handle-node-class"; -import { ParameterRenderComponent } from "@/components/parameterRenderComponent"; -import { usePostTemplateValue } from "@/controllers/API/queries/nodes/use-post-template-value"; -import useAlertStore from "@/stores/alertStore"; -import { cloneDeep } from "lodash"; -import { ReactNode, useEffect, useRef, useState } from "react"; -import { useHotkeys } from "react-hotkeys-hook"; -import { useUpdateNodeInternals } from "reactflow"; -import { default as IconComponent } from "../../../../components/genericIconComponent"; -import ShadTooltip from "../../../../components/shadTooltipComponent"; -import { Button } from "../../../../components/ui/button"; -import { LANGFLOW_SUPPORTED_TYPES } from "../../../../constants/constants"; -import { Case } from "../../../../shared/components/caseComponent"; -import useFlowStore from "../../../../stores/flowStore"; -import { useShortcutsStore } from "../../../../stores/shortcuts"; -import { useTypesStore } from "../../../../stores/typesStore"; -import { ParameterComponentType } from "../../../../types/components"; -import { - getGroupOutputNodeId, - scapedJSONStringfy, -} from "../../../../utils/reactflowUtils"; -import { - classNames, - cn, - isThereModal, - logHasMessage, - logTypeIsError, - logTypeIsUnknown, -} from "../../../../utils/utils"; -import useFetchDataOnMount from "../../../hooks/use-fetch-data-on-mount"; -import useHandleOnNewValue from "../../../hooks/use-handle-new-value"; -import OutputComponent from "../OutputComponent"; -import HandleRenderComponent from "../handleRenderComponent"; -import OutputModal from "../outputModal"; - -export default function ParameterComponent({ - left, - id, - data, - tooltipTitle, - title, - colors, - type, - name = "", - required = false, - optionalHandle = null, - info = "", - proxy, - showNode, - index, - outputName, - selected, - outputProxy, -}: ParameterComponentType): JSX.Element { - const ref = useRef(null); - const infoHtml = useRef(null); - const nodes = useFlowStore((state) => state.nodes); - const edges = useFlowStore((state) => state.edges); - const setNode = useFlowStore((state) => state.setNode); - const myData = useTypesStore((state) => state.data); - const postTemplateValue = usePostTemplateValue({ - node: data.node!, - nodeId: data.id, - parameterId: name, - }); - const updateNodeInternals = useUpdateNodeInternals(); - const setFilterEdge = useFlowStore((state) => state.setFilterEdge); - const [openOutputModal, setOpenOutputModal] = useState(false); - const flowPool = useFlowStore((state) => state.flowPool); - - let flowPoolId = data.id; - let internalOutputName = outputName; - - if (data.node?.flow && outputProxy) { - const realOutput = getGroupOutputNodeId( - data.node.flow, - outputProxy.name, - outputProxy.id, - ); - if (realOutput) { - flowPoolId = realOutput.id; - internalOutputName = realOutput.outputName; - } - } - - const flowPoolNode = (flowPool[flowPoolId] ?? [])[ - (flowPool[flowPoolId]?.length ?? 1) - 1 - ]; - - const displayOutputPreview = - !!flowPool[flowPoolId] && - logHasMessage(flowPoolNode?.data, internalOutputName); - - const unknownOutput = logTypeIsUnknown( - flowPoolNode?.data, - internalOutputName, - ); - const errorOutput = logTypeIsError(flowPoolNode?.data, internalOutputName); - - if (outputProxy) { - console.log(logHasMessage(flowPoolNode?.data, internalOutputName)); - } - - const preventDefault = true; - - function handleOutputWShortcut() { - if (!displayOutputPreview || unknownOutput) return; - if (isThereModal() && !openOutputModal) return; - if (selected && !left) { - setOpenOutputModal((state) => !state); - } - } - - const setErrorData = useAlertStore((state) => state.setErrorData); - - const output = useShortcutsStore((state) => state.output); - useHotkeys(output, handleOutputWShortcut, { preventDefault }); - - const { handleNodeClass } = useHandleNodeClass(data.id); - - let disabled = - edges.some( - (edge) => - edge.targetHandle === scapedJSONStringfy(proxy ? { ...id, proxy } : id), - ) ?? false; - - let disabledOutput = - edges.some( - (edge) => - edge.sourceHandle === scapedJSONStringfy(proxy ? { ...id, proxy } : id), - ) ?? false; - - const { handleOnNewValue } = useHandleOnNewValue({ - node: data.node!, - nodeId: data.id, - name, - }); - - useFetchDataOnMount(data.node!, handleNodeClass, name, postTemplateValue); - - useEffect(() => { - // @ts-ignore - infoHtml.current = ( -
- {info.split("\n").map((line, index) => ( -

- {line} -

- ))} -
- ); - }, [info]); - - function renderTitle() { - return !left ? ( - - ) : ( - {title} - ); - } - - useEffect(() => { - if (optionalHandle && optionalHandle.length === 0) { - optionalHandle = null; - } - }, [optionalHandle]); - - const handleUpdateOutputHide = (value?: boolean) => { - setNode(data.id, (oldNode) => { - let newNode = cloneDeep(oldNode); - newNode.data = { - ...newNode.data, - node: { - ...newNode.data.node, - outputs: newNode.data.node.outputs?.map((output, i) => { - if (i === index) { - output.hidden = value ?? !output.hidden; - } - return output; - }), - }, - }; - return newNode; - }); - updateNodeInternals(data.id); - }; - - useEffect(() => { - if (disabledOutput && data.node?.outputs![index].hidden) { - handleUpdateOutputHide(false); - } - }, [disabledOutput]); - - return !showNode ? ( - left && LANGFLOW_SUPPORTED_TYPES.has(type ?? "") && !optionalHandle ? ( - <> - ) : ( - - ) - ) : ( -
- <> -
- {!left && ( -
- -
- )} - -
- -
-
- - {proxy ? ( - {proxy.id}}> - {renderTitle()} - - ) : ( -
- - {renderTitle()} - - {!left && ( - - - - )} -
- )} - - {required ? "*" : ""} - -
- {info !== "" && ( - - {/* put div to avoid bug that does not display tooltip */} -
- -
-
- )} -
-
- - {left && LANGFLOW_SUPPORTED_TYPES.has(type ?? "") && !optionalHandle ? ( - <> - ) : ( - - )} - {data.node?.template[name] !== undefined && ( -
- -
- )} - {openOutputModal && ( - - )} - -
- ); -} diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index f4b0314a7..e0a582d44 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -43,7 +43,8 @@ import useUpdateValidationStatus from "../hooks/use-update-validation-status"; import useValidationStatusString from "../hooks/use-validation-status-string"; import getFieldTitle from "../utils/get-field-title"; import sortFields from "../utils/sort-fields"; -import ParameterComponent from "./components/parameterComponent"; +import NodeInputField from "./components/NodeInputField"; +import NodeOutputField from "./components/NodeOutputfield"; export default function GenericNode({ data, @@ -302,8 +303,9 @@ export default function GenericNode({ const renderOutputParameter = (output: OutputFieldType, idx: number) => { return ( - @@ -494,9 +495,7 @@ export default function GenericNode({ (templateField: string, idx) => data.node!.template[templateField]?.show && !data.node!.template[templateField]?.advanced && ( - {data.node!.template[templateField]?.show && !data.node!.template[templateField]?.advanced ? ( - | undefined | null; + info: string; + proxy: { field: string; id: string } | undefined; + showNode: boolean; +}; + export type InputListComponentType = { value: string[]; onChange: (value: string[], dbValue?: boolean, snapshot?: boolean) => void; diff --git a/src/frontend/src/types/zustand/flow/index.ts b/src/frontend/src/types/zustand/flow/index.ts index 927638b65..59ea979fa 100644 --- a/src/frontend/src/types/zustand/flow/index.ts +++ b/src/frontend/src/types/zustand/flow/index.ts @@ -112,7 +112,7 @@ export type FlowStoreType = { setNode: ( id: string, update: Node | ((oldState: Node) => Node), - isUserChange: boolean, + isUserChange?: boolean, ) => void; getNode: (id: string) => Node | undefined; deleteNode: (nodeId: string | Array) => void;