From 3e5bcf42fff77d8229df6a704db1062d40af2212 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Wed, 15 May 2024 18:37:25 +0200 Subject: [PATCH 01/11] Made Output Types dropdown work --- .../components/parameterComponent/index.tsx | 58 ++++++++++++++++--- .../src/customNodes/genericNode/index.tsx | 34 +++++------ src/frontend/src/types/flow/index.ts | 1 + 3 files changed, 68 insertions(+), 25 deletions(-) diff --git a/src/frontend/src/customNodes/genericNode/components/parameterComponent/index.tsx b/src/frontend/src/customNodes/genericNode/components/parameterComponent/index.tsx index dbcb1e3dc..fee39fce4 100644 --- a/src/frontend/src/customNodes/genericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/customNodes/genericNode/components/parameterComponent/index.tsx @@ -5,7 +5,9 @@ import CodeAreaComponent from "../../../../components/codeAreaComponent"; import DictComponent from "../../../../components/dictComponent"; import Dropdown from "../../../../components/dropdownComponent"; import FloatComponent from "../../../../components/floatComponent"; -import { default as IconComponent } from "../../../../components/genericIconComponent"; +import ForwardedIconComponent, { + default as IconComponent, +} from "../../../../components/genericIconComponent"; import InputFileComponent from "../../../../components/inputFileComponent"; import InputGlobalComponent from "../../../../components/inputGlobalComponent"; import InputListComponent from "../../../../components/inputListComponent"; @@ -16,6 +18,12 @@ import ShadTooltip from "../../../../components/shadTooltipComponent"; import TextAreaComponent from "../../../../components/textAreaComponent"; import ToggleShadComponent from "../../../../components/toggleShadComponent"; import { Button } from "../../../../components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "../../../../components/ui/dropdown-menu"; import { RefreshButton } from "../../../../components/ui/refreshButton"; import { INPUT_HANDLER_HOVER, @@ -49,7 +57,7 @@ import { nodeIconsLucide, nodeNames, } from "../../../../utils/styleUtils"; -import { classNames, groupByFamily } from "../../../../utils/utils"; +import { classNames, cn, groupByFamily } from "../../../../utils/utils"; export default function ParameterComponent({ left, @@ -257,6 +265,44 @@ export default function ParameterComponent({ ); }, [info]); + function renderTitle() { + const output_types = title.split("|"); + const displayTitle = data.selected_output_type ?? output_types[0]; + return !left && output_types.length > 1 ? ( + + + + {displayTitle} + + + + + {output_types.map((type) => ( + { + setNode(data.id, (node) => ({ + ...node, + data: { ...node.data, selected_output_type: type }, + })); + }} + > + {type} + + ))} + + + ) : ( + + {title} + + ); + } + function renderTooltips() { let groupedObj: any = groupByFamily(myData, tooltipTitle!, left, flow!); groupedEdge.current = groupedObj; @@ -434,14 +480,10 @@ export default function ParameterComponent({ )} {proxy ? ( {proxy.id}}> - - {title} - + {renderTitle()} ) : ( - - {title} - + renderTitle() )} {required ? "*" : ""} diff --git a/src/frontend/src/customNodes/genericNode/index.tsx b/src/frontend/src/customNodes/genericNode/index.tsx index 044d8cca4..f4059120a 100644 --- a/src/frontend/src/customNodes/genericNode/index.tsx +++ b/src/frontend/src/customNodes/genericNode/index.tsx @@ -54,14 +54,14 @@ export default function GenericNode({ const [nodeName, setNodeName] = useState(data.node!.display_name); const [inputDescription, setInputDescription] = useState(false); const [nodeDescription, setNodeDescription] = useState( - data.node?.description! + data.node?.description!, ); const [isOutdated, setIsOutdated] = useState(false); const buildStatus = useFlowStore( - (state) => state.flowBuildStatus[data.id]?.status + (state) => state.flowBuildStatus[data.id]?.status, ); const lastRunTime = useFlowStore( - (state) => state.flowBuildStatus[data.id]?.timestamp + (state) => state.flowBuildStatus[data.id]?.timestamp, ); const [validationStatus, setValidationStatus] = useState(null); @@ -118,7 +118,7 @@ export default function GenericNode({ updateNodeInternals(data.id); }, - [data.id, data.node, setNode, setIsOutdated] + [data.id, data.node, setNode, setIsOutdated], ); if (!data.node!.template) { @@ -258,7 +258,7 @@ export default function GenericNode({ const isDark = useDarkStore((state) => state.dark); const renderIconStatus = ( buildStatus: BuildStatus | undefined, - validationStatus: validationStatusType | null + validationStatus: validationStatusType | null, ) => { if (buildStatus === BuildStatus.BUILDING) { return ; @@ -299,7 +299,7 @@ export default function GenericNode({ }; const getSpecificClassFromBuildStatus = ( buildStatus: BuildStatus | undefined, - validationStatus: validationStatusType | null + validationStatus: validationStatusType | null, ) => { let isInvalid = validationStatus && !validationStatus.valid; @@ -323,11 +323,11 @@ export default function GenericNode({ selected: boolean, showNode: boolean, buildStatus: BuildStatus | undefined, - validationStatus: validationStatusType | null + validationStatus: validationStatusType | null, ) => { const specificClassFromBuildStatus = getSpecificClassFromBuildStatus( buildStatus, - validationStatus + validationStatus, ); const baseBorderClass = getBaseBorderClass(selected); const nodeSizeClass = getNodeSizeClass(showNode); @@ -335,7 +335,7 @@ export default function GenericNode({ baseBorderClass, nodeSizeClass, "generic-node-div", - specificClassFromBuildStatus + specificClassFromBuildStatus, ); }; @@ -392,7 +392,7 @@ export default function GenericNode({ selected, showNode, buildStatus, - validationStatus + validationStatus, )} > {data.node?.beta && showNode && ( @@ -537,7 +537,7 @@ export default function GenericNode({ } title={getFieldTitle( data.node?.template!, - templateField + templateField, )} info={data.node?.template[templateField].info} name={templateField} @@ -565,7 +565,7 @@ export default function GenericNode({ proxy={data.node?.template[templateField].proxy} showNode={showNode} /> - ) + ), )} 0 - ? data.node.output_types.join(" | ") + ? data.node.output_types.join("|") : data.type } tooltipTitle={data.node?.base_classes.join("\n")} @@ -722,7 +722,7 @@ export default function GenericNode({ !data.node?.description) && nameEditable ? "font-light italic" - : "" + : "", )} onDoubleClick={(e) => { setInputDescription(true); @@ -784,13 +784,13 @@ export default function GenericNode({ } title={getFieldTitle( data.node?.template!, - templateField + templateField, )} info={data.node?.template[templateField].info} name={templateField} tooltipTitle={ data.node?.template[templateField].input_types?.join( - "\n" + "\n", ) ?? data.node?.template[templateField].type } required={data.node!.template[templateField].required} @@ -817,7 +817,7 @@ export default function GenericNode({
{" "} diff --git a/src/frontend/src/types/flow/index.ts b/src/frontend/src/types/flow/index.ts index 8b259186a..0d453b4b2 100644 --- a/src/frontend/src/types/flow/index.ts +++ b/src/frontend/src/types/flow/index.ts @@ -33,6 +33,7 @@ export type NodeDataType = { node?: APIClassType; id: string; output_types?: string[]; + selected_output_type?: string; buildStatus?: BuildStatus; }; // FlowStyleType is the type of the style object that is used to style the From 64c42a9280189852967105ea5d6569fa110294b9 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Thu, 16 May 2024 15:48:49 -0700 Subject: [PATCH 02/11] feat: Update selected_output_type handling in Vertex class This commit updates the handling of the `selected_output_type` attribute in the `Vertex` class. Previously, the attribute was assigned directly from the `data` dictionary, which could result in unexpected behavior. Now, the attribute is properly stripped and converted to a string before assignment, ensuring consistent behavior. This change improves the reliability and accuracy of the `selected_output_type` attribute in the `Vertex` class. --- src/backend/base/langflow/graph/vertex/base.py | 4 +++- src/backend/base/langflow/interface/initialize/loading.py | 1 - src/backend/base/langflow/utils/validate.py | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/backend/base/langflow/graph/vertex/base.py b/src/backend/base/langflow/graph/vertex/base.py index e250e9419..27110a510 100644 --- a/src/backend/base/langflow/graph/vertex/base.py +++ b/src/backend/base/langflow/graph/vertex/base.py @@ -201,7 +201,9 @@ class Vertex: self.description = self.data["node"].get("description", "") self.frozen = self.data["node"].get("frozen", False) - self.selected_output_type = self.data["node"].get("selected_output_type") + self.selected_output_type = ( + str(self.data.get("selected_output_type")).strip() if self.data.get("selected_output_type") else None + ) self.is_input = self.data["node"].get("is_input") or self.is_input self.is_output = self.data["node"].get("is_output") or self.is_output template_dicts = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)} diff --git a/src/backend/base/langflow/interface/initialize/loading.py b/src/backend/base/langflow/interface/initialize/loading.py index c1014ef90..96fe8832f 100644 --- a/src/backend/base/langflow/interface/initialize/loading.py +++ b/src/backend/base/langflow/interface/initialize/loading.py @@ -2,7 +2,6 @@ import inspect import json from typing import TYPE_CHECKING, Any, Callable, Dict, Sequence, Type - import orjson from langchain.agents import agent as agent_module from langchain.agents.agent import AgentExecutor diff --git a/src/backend/base/langflow/utils/validate.py b/src/backend/base/langflow/utils/validate.py index 0871dbd82..bd7827199 100644 --- a/src/backend/base/langflow/utils/validate.py +++ b/src/backend/base/langflow/utils/validate.py @@ -253,6 +253,9 @@ def build_class_constructor(compiled_class, exec_globals, class_name): globals()[module_name] = module instance = exec_globals[class_name](*args, **kwargs) + # Get selected type from global scope + if instance.selected_output_type in exec_globals: + instance.selected_output_type = exec_globals[instance.selected_output_type] return instance build_custom_class.__globals__.update(exec_globals) From 6595702e73ea9f62102b17b1c45ceb662ecfaaa0 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Fri, 24 May 2024 17:42:31 -0300 Subject: [PATCH 03/11] feat(frontend): add ComponentOutput component to display output options for generic nodes feat(types): add outputComponentType to define props for ComponentOutput component --- .../components/ComponentOutput/index.tsx | 54 +++++++++++++++++++ src/frontend/src/types/components/index.ts | 7 +++ 2 files changed, 61 insertions(+) create mode 100644 src/frontend/src/customNodes/genericNode/components/ComponentOutput/index.tsx diff --git a/src/frontend/src/customNodes/genericNode/components/ComponentOutput/index.tsx b/src/frontend/src/customNodes/genericNode/components/ComponentOutput/index.tsx new file mode 100644 index 000000000..6f872afe2 --- /dev/null +++ b/src/frontend/src/customNodes/genericNode/components/ComponentOutput/index.tsx @@ -0,0 +1,54 @@ +import { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, +} from "@radix-ui/react-dropdown-menu"; +import ForwardedIconComponent from "../../../../components/genericIconComponent"; +import { outputComponentType } from "../../../../types/components"; +import { cn } from "../../../../utils/utils"; +import useFlowStore from "../../../../stores/flowStore"; + +export default function ComponentOutput({ + selected, + types, + frozen = false, + nodeId, +}: outputComponentType) { + const setNode = useFlowStore((state) => state.setNode); + let displayTitle = selected ?? types[0]; + + if (types.length < 2) { + return ( + {displayTitle} + ); + } + + return ( + + + + {displayTitle} + + + + + {types.map((type) => ( + { + // TODO: UDPDATE SET NODE TO NEW NODE FORM + setNode(nodeId, (node) => ({ + ...node, + data: { ...node.data, selected: type }, + })); + }} + > + {type} + + ))} + + + ); +} diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index e23557a84..758b13510 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -111,6 +111,13 @@ export type TextAreaComponentType = { readonly?: boolean; }; +export type outputComponentType = { + types: string[]; + selected: string; + nodeId: string; + frozen?: boolean; +}; + export type PromptAreaComponentType = { field_name?: string; nodeClass?: APIClassType; From 70a43c394752897e0b5ab1ab0ba12342e1115fb3 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Mon, 27 May 2024 11:44:47 -0300 Subject: [PATCH 04/11] refactor handle tooltip to it's own component --- .../HandleTooltipComponent/index.tsx | 31 +++++++++ .../components/componentOutputs/index.tsx | 17 +++++ .../components/parameterComponent/index.tsx | 64 ++++++++----------- .../src/customNodes/genericNode/index.tsx | 4 ++ .../hooks/use-fetch-data-on-mount.tsx | 2 - .../hooks/use-handle-new-value.tsx | 3 - .../hooks/use-handle-node-class.tsx | 3 - .../hooks/use-handle-refresh-buttons.tsx | 3 +- src/frontend/src/types/api/index.ts | 4 +- 9 files changed, 81 insertions(+), 50 deletions(-) create mode 100644 src/frontend/src/customNodes/genericNode/components/HandleTooltipComponent/index.tsx create mode 100644 src/frontend/src/customNodes/genericNode/components/componentOutputs/index.tsx diff --git a/src/frontend/src/customNodes/genericNode/components/HandleTooltipComponent/index.tsx b/src/frontend/src/customNodes/genericNode/components/HandleTooltipComponent/index.tsx new file mode 100644 index 000000000..2dddabbb5 --- /dev/null +++ b/src/frontend/src/customNodes/genericNode/components/HandleTooltipComponent/index.tsx @@ -0,0 +1,31 @@ +import { useRef } from "react"; +import { TOOLTIP_EMPTY } from "../../../../constants/constants"; +import { groupByFamily } from "../../../../utils/utils"; +import TooltipRenderComponent from "../tooltipRenderComponent"; +import { useTypesStore } from "../../../../stores/typesStore"; +import { NodeType } from "../../../../types/flow"; +import useFlowStore from "../../../../stores/flowStore"; + +export default function HandleTooltips({ + left, + tooltipTitle, +}: { + left: boolean; + nodes: NodeType[]; + tooltipTitle: string; +}) { + const myData = useTypesStore((state) => state.data); + const nodes = useFlowStore((state) => state.nodes); + + let groupedObj: any = groupByFamily(myData, tooltipTitle!, left, nodes!); + + if (groupedObj && groupedObj.length > 0) { + //@ts-ignore + return groupedObj.map((item, index) => { + return ; + }); + } else { + //@ts-ignore + return {TOOLTIP_EMPTY}; + } +} diff --git a/src/frontend/src/customNodes/genericNode/components/componentOutputs/index.tsx b/src/frontend/src/customNodes/genericNode/components/componentOutputs/index.tsx new file mode 100644 index 000000000..6cdc72839 --- /dev/null +++ b/src/frontend/src/customNodes/genericNode/components/componentOutputs/index.tsx @@ -0,0 +1,17 @@ +import { NodeDataType } from "../../../../types/flow"; +import ComponentOutput from "../ComponentOutput"; + +export default function ComponentOutputs({ data }: { data: NodeDataType }) { + return ( +
+ {data.node?.outputs?.map((output) => ( + + ))} +
+ ); +} diff --git a/src/frontend/src/customNodes/genericNode/components/parameterComponent/index.tsx b/src/frontend/src/customNodes/genericNode/components/parameterComponent/index.tsx index 2503567fa..e1f737ad0 100644 --- a/src/frontend/src/customNodes/genericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/customNodes/genericNode/components/parameterComponent/index.tsx @@ -54,6 +54,7 @@ import useHandleOnNewValue from "../../../hooks/use-handle-new-value"; import useHandleNodeClass from "../../../hooks/use-handle-node-class"; import useHandleRefreshButtonPress from "../../../hooks/use-handle-refresh-buttons"; import TooltipRenderComponent from "../tooltipRenderComponent"; +import HandleTooltips from "../HandleTooltipComponent"; export default function ParameterComponent({ left, @@ -72,7 +73,6 @@ export default function ParameterComponent({ index = "", }: ParameterComponentType): JSX.Element { const ref = useRef(null); - const refHtml = useRef(null); const infoHtml = useRef(null); const setErrorData = useAlertStore((state) => state.setErrorData); const currentFlow = useFlowsManagerStore((state) => state.currentFlow); @@ -84,8 +84,6 @@ export default function ParameterComponent({ const [isLoading, setIsLoading] = useState(false); const updateNodeInternals = useUpdateNodeInternals(); const [errorDuplicateKey, setErrorDuplicateKey] = useState(false); - const flow = currentFlow?.data?.nodes ?? null; - const groupedEdge = useRef(null); const setFilterEdge = useFlowStore((state) => state.setFilterEdge); const { handleOnNewValue: handleOnNewValueHook } = useHandleOnNewValue( @@ -95,7 +93,6 @@ export default function ParameterComponent({ handleUpdateValues, debouncedHandleUpdateValues, setNode, - renderTooltips, isLoading, setIsLoading, ); @@ -106,11 +103,10 @@ export default function ParameterComponent({ takeSnapshot, setNode, updateNodeInternals, - renderTooltips, ); const { handleRefreshButtonPress: handleRefreshButtonPressHook } = - useHandleRefreshButtonPress(setIsLoading, setNode, renderTooltips); + useHandleRefreshButtonPress(setIsLoading, setNode); let disabled = edges.some( @@ -122,14 +118,7 @@ export default function ParameterComponent({ handleRefreshButtonPressHook(name, data); }; - useFetchDataOnMount( - data, - name, - handleUpdateValues, - setNode, - renderTooltips, - setIsLoading, - ); + useFetchDataOnMount(data, name, handleUpdateValues, setNode, setIsLoading); const handleOnNewValue = async ( newValue: string | string[] | boolean | Object[], @@ -193,32 +182,11 @@ export default function ParameterComponent({ ); } - function renderTooltips() { - let groupedObj: any = groupByFamily(myData, tooltipTitle!, left, flow!); - groupedEdge.current = groupedObj; - - if (groupedObj && groupedObj.length > 0) { - //@ts-ignore - refHtml.current = groupedObj.map((item, index) => { - return ; - }); - } else { - //@ts-ignore - refHtml.current = ( - {TOOLTIP_EMPTY} - ); - } - } - // If optionalHandle is an empty list, then it is not an optional handle if (optionalHandle && optionalHandle.length === 0) { optionalHandle = null; } - useEffect(() => { - renderTooltips(); - }, [tooltipTitle, flow]); - return !showNode ? ( left && LANGFLOW_SUPPORTED_TYPES.has(type ?? "") && !optionalHandle ? ( <> @@ -228,7 +196,13 @@ export default function ParameterComponent({ + } side={left ? "left" : "right"} > { - setFilterEdge(groupedEdge.current); + setFilterEdge( + groupByFamily(myData, tooltipTitle!, left, nodes!), + ); }} > @@ -322,7 +298,13 @@ export default function ParameterComponent({ + } side={left ? "left" : "right"} > setFilterEdge(groupedEdge.current)} + onClick={() => { + setFilterEdge( + groupByFamily(myData, tooltipTitle!, left, nodes!), + ); + }} />
diff --git a/src/frontend/src/customNodes/genericNode/index.tsx b/src/frontend/src/customNodes/genericNode/index.tsx index ec7bbd9f5..baf5948f4 100644 --- a/src/frontend/src/customNodes/genericNode/index.tsx +++ b/src/frontend/src/customNodes/genericNode/index.tsx @@ -31,6 +31,7 @@ import { classNames, cn } from "../../utils/utils"; import ParameterComponent from "./components/parameterComponent"; import getFieldTitle from "../utils/get-field-title"; import sortFields from "../utils/sort-fields"; +import ComponentOutputs from "./components/componentOutputs"; export default function GenericNode({ data, @@ -824,6 +825,9 @@ export default function GenericNode({ > {" "} + {data.node!.outputs && data.node!.outputs.length > 0 && ( + + )} {data.node!.base_classes.length > 0 && ( { const setErrorData = useAlertStore((state) => state.setErrorData); @@ -44,7 +43,6 @@ const useFetchDataOnMount = ( }); } setIsLoading(false); - renderTooltips(); } } fetchData(); diff --git a/src/frontend/src/customNodes/hooks/use-handle-new-value.tsx b/src/frontend/src/customNodes/hooks/use-handle-new-value.tsx index 7de830eda..c6f026f51 100644 --- a/src/frontend/src/customNodes/hooks/use-handle-new-value.tsx +++ b/src/frontend/src/customNodes/hooks/use-handle-new-value.tsx @@ -9,7 +9,6 @@ const useHandleOnNewValue = ( handleUpdateValues, debouncedHandleUpdateValues, setNode, - renderTooltips, isLoading, setIsLoading, ) => { @@ -65,8 +64,6 @@ const useHandleOnNewValue = ( return newNode; }); - - renderTooltips(); }; return { handleOnNewValue }; diff --git a/src/frontend/src/customNodes/hooks/use-handle-node-class.tsx b/src/frontend/src/customNodes/hooks/use-handle-node-class.tsx index 412658d77..6fb78ce10 100644 --- a/src/frontend/src/customNodes/hooks/use-handle-node-class.tsx +++ b/src/frontend/src/customNodes/hooks/use-handle-node-class.tsx @@ -6,7 +6,6 @@ const useHandleNodeClass = ( takeSnapshot, setNode, updateNodeInternals, - renderTooltips, ) => { const handleNodeClass = (newNodeClass, code) => { if (!data.node) return; @@ -30,8 +29,6 @@ const useHandleNodeClass = ( }); updateNodeInternals(data.id); - - renderTooltips(); }; return { handleNodeClass }; diff --git a/src/frontend/src/customNodes/hooks/use-handle-refresh-buttons.tsx b/src/frontend/src/customNodes/hooks/use-handle-refresh-buttons.tsx index 19f2a3c29..0101e8ee0 100644 --- a/src/frontend/src/customNodes/hooks/use-handle-refresh-buttons.tsx +++ b/src/frontend/src/customNodes/hooks/use-handle-refresh-buttons.tsx @@ -3,7 +3,7 @@ import useAlertStore from "../../stores/alertStore"; import { ResponseErrorDetailAPI } from "../../types/api"; import { handleUpdateValues } from "../../utils/parameterUtils"; -const useHandleRefreshButtonPress = (setIsLoading, setNode, renderTooltips) => { +const useHandleRefreshButtonPress = (setIsLoading, setNode) => { const setErrorData = useAlertStore((state) => state.setErrorData); const handleRefreshButtonPress = async (name, data) => { @@ -30,7 +30,6 @@ const useHandleRefreshButtonPress = (setIsLoading, setNode, renderTooltips) => { }); } setIsLoading(false); - renderTooltips(); }; return { handleRefreshButtonPress }; diff --git a/src/frontend/src/types/api/index.ts b/src/frontend/src/types/api/index.ts index faef230a5..c9f09bd40 100644 --- a/src/frontend/src/types/api/index.ts +++ b/src/frontend/src/types/api/index.ts @@ -27,6 +27,7 @@ export type APIClassType = { documentation: string; error?: string; official?: boolean; + outputs?: Array<{ types: Array; selected?: string }>; frozen?: boolean; flow?: FlowType; field_order?: string[]; @@ -38,7 +39,8 @@ export type APIClassType = { | FlowType | CustomFieldsType | boolean - | undefined; + | undefined + | Array<{ types: Array; selected?: string }>; }; export type TemplateVariableType = { From 596ab2f282fc375229488a68ebf0c62fdcc8c51a Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Mon, 27 May 2024 16:08:34 -0300 Subject: [PATCH 05/11] feat(frontend): add new OutputComponent to handle dropdown menu for selecting output types in generic nodes feat(frontend): remove ComponentOutputs component and integrate its functionality into ParameterComponent feat(frontend): update ParameterComponent to use OutputComponent for displaying output types feat(frontend): update GenericNode to use OutputComponent for displaying output types and remove ComponentOutputs feat(types): update ParameterComponentType to use number type for index instead of string --- .../index.tsx | 22 +++--- .../components/componentOutputs/index.tsx | 17 ----- .../components/parameterComponent/index.tsx | 49 +++---------- .../src/customNodes/genericNode/index.tsx | 69 +++++++++---------- src/frontend/src/types/components/index.ts | 3 +- 5 files changed, 57 insertions(+), 103 deletions(-) rename src/frontend/src/customNodes/genericNode/components/{ComponentOutput => OutputComponent}/index.tsx (70%) delete mode 100644 src/frontend/src/customNodes/genericNode/components/componentOutputs/index.tsx diff --git a/src/frontend/src/customNodes/genericNode/components/ComponentOutput/index.tsx b/src/frontend/src/customNodes/genericNode/components/OutputComponent/index.tsx similarity index 70% rename from src/frontend/src/customNodes/genericNode/components/ComponentOutput/index.tsx rename to src/frontend/src/customNodes/genericNode/components/OutputComponent/index.tsx index 6f872afe2..2dbdc7167 100644 --- a/src/frontend/src/customNodes/genericNode/components/ComponentOutput/index.tsx +++ b/src/frontend/src/customNodes/genericNode/components/OutputComponent/index.tsx @@ -8,20 +8,20 @@ import ForwardedIconComponent from "../../../../components/genericIconComponent" import { outputComponentType } from "../../../../types/components"; import { cn } from "../../../../utils/utils"; import useFlowStore from "../../../../stores/flowStore"; +import { NodeDataType } from "../../../../types/flow"; +import { cloneDeep } from "lodash"; -export default function ComponentOutput({ +export default function OutputComponent({ selected, types, frozen = false, nodeId, + idx, }: outputComponentType) { const setNode = useFlowStore((state) => state.setNode); - let displayTitle = selected ?? types[0]; if (types.length < 2) { - return ( - {displayTitle} - ); + return {selected}; } return ( @@ -30,7 +30,7 @@ export default function ComponentOutput({ - {displayTitle} + {selected} @@ -39,10 +39,12 @@ export default function ComponentOutput({ { // TODO: UDPDATE SET NODE TO NEW NODE FORM - setNode(nodeId, (node) => ({ - ...node, - data: { ...node.data, selected: type }, - })); + setNode(nodeId, (node) => { + const newNode = cloneDeep(node); + (newNode.data as NodeDataType).node!.outputs![idx].selected = + type; + return newNode; + }); }} > {type} diff --git a/src/frontend/src/customNodes/genericNode/components/componentOutputs/index.tsx b/src/frontend/src/customNodes/genericNode/components/componentOutputs/index.tsx deleted file mode 100644 index 6cdc72839..000000000 --- a/src/frontend/src/customNodes/genericNode/components/componentOutputs/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { NodeDataType } from "../../../../types/flow"; -import ComponentOutput from "../ComponentOutput"; - -export default function ComponentOutputs({ data }: { data: NodeDataType }) { - return ( -
- {data.node?.outputs?.map((output) => ( - - ))} -
- ); -} diff --git a/src/frontend/src/customNodes/genericNode/components/parameterComponent/index.tsx b/src/frontend/src/customNodes/genericNode/components/parameterComponent/index.tsx index e1f737ad0..fe84645a6 100644 --- a/src/frontend/src/customNodes/genericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/customNodes/genericNode/components/parameterComponent/index.tsx @@ -55,6 +55,7 @@ import useHandleNodeClass from "../../../hooks/use-handle-node-class"; import useHandleRefreshButtonPress from "../../../hooks/use-handle-refresh-buttons"; import TooltipRenderComponent from "../tooltipRenderComponent"; import HandleTooltips from "../HandleTooltipComponent"; +import OutputComponent from "../OutputComponent"; export default function ParameterComponent({ left, @@ -70,12 +71,9 @@ export default function ParameterComponent({ info = "", proxy, showNode, - index = "", + index, }: ParameterComponentType): JSX.Element { - const ref = useRef(null); const infoHtml = useRef(null); - const setErrorData = useAlertStore((state) => state.setErrorData); - const currentFlow = useFlowsManagerStore((state) => state.currentFlow); const nodes = useFlowStore((state) => state.nodes); const edges = useFlowStore((state) => state.edges); const setNode = useFlowStore((state) => state.setNode); @@ -145,40 +143,16 @@ export default function ParameterComponent({ }, [info]); function renderTitle() { - const output_types = title.split("|"); - const displayTitle = data.selected_output_type ?? output_types[0]; - return !left && output_types.length > 1 ? ( - - - - {displayTitle} - - - - - {output_types.map((type) => ( - { - setNode(data.id, (node) => ({ - ...node, - data: { ...node.data, selected_output_type: type }, - })); - }} - > - {type} - - ))} - - + return !left ? ( + ) : ( - - {title} - + {title} ); } @@ -244,7 +218,6 @@ export default function ParameterComponent({ ) ) : (
{" "}
- {data.node!.outputs && data.node!.outputs.length > 0 && ( - - )} - {data.node!.base_classes.length > 0 && ( - 0 - ? nodeColors[data.node.output_types[0]] ?? - nodeColors[types[data.node.output_types[0]]] - : nodeColors[types[data.type]]) ?? nodeColors.unknown - } - title={ - data.node?.output_types && data.node.output_types.length > 0 - ? data.node.output_types.join(" | ") - : data.type - } - tooltipTitle={data.node?.base_classes.join("\n")} - id={{ - baseClasses: data.node!.base_classes, - id: data.id, - dataType: data.type, - }} - type={data.node?.base_classes.join("|")} - left={false} - showNode={showNode} - /> - )} + {data.node!.outputs && + data.node!.outputs.length > 0 && + data.node!.outputs.map((output, idx) => ( + + ))} )} diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index 758b13510..bc4f65ed7 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -67,7 +67,7 @@ export type ParameterComponentType = { info?: string; proxy?: { field: string; id: string }; showNode?: boolean; - index?: string; + index: number; onCloseModal?: (close: boolean) => void; }; export type InputListComponentType = { @@ -116,6 +116,7 @@ export type outputComponentType = { selected: string; nodeId: string; frozen?: boolean; + idx: number; }; export type PromptAreaComponentType = { From 354e1bc126492b319dca359013bf7e89e3d768d8 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Mon, 27 May 2024 17:57:22 -0300 Subject: [PATCH 06/11] =?UTF-8?q?funcionando=20mas=20ainda=20n=C3=A3o=20id?= =?UTF-8?q?eal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/src/stores/flowsManagerStore.ts | 9 ++-- src/frontend/src/utils/reactflowUtils.ts | 45 +++++++++++++++++++- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/frontend/src/stores/flowsManagerStore.ts b/src/frontend/src/stores/flowsManagerStore.ts index 81637fba0..90bf82169 100644 --- a/src/frontend/src/stores/flowsManagerStore.ts +++ b/src/frontend/src/stores/flowsManagerStore.ts @@ -199,11 +199,10 @@ const useFlowsManagerStore = create((set, get) => ({ position?: XYPosition, fromDragAndDrop?: boolean, ): Promise => { + let flowData = flow + ? processDataFromFlow(flow) + : { nodes: [], edges: [], viewport: { zoom: 1, x: 0, y: 0 } }; if (newProject) { - let flowData = flow - ? processDataFromFlow(flow) - : { nodes: [], edges: [], viewport: { zoom: 1, x: 0, y: 0 } }; - // Create a new flow with a default name if no flow is provided. const folder_id = useFolderStore.getState().folderUrl; const my_collection_id = useFolderStore.getState().myCollectionId; @@ -272,7 +271,7 @@ const useFlowsManagerStore = create((set, get) => ({ useFlowStore .getState() .paste( - { nodes: flow!.data!.nodes, edges: flow!.data!.edges }, + { nodes: flowData?.nodes, edges: flowData?.edges }, position ?? { x: 10, y: 10 }, ); } diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index b379b445e..97384e714 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -191,8 +191,8 @@ export const processDataFromFlow = (flow: FlowType, refreshIds = true) => { let data = flow?.data ? flow.data : null; if (data) { processFlowEdges(flow); - //prevent node update for now - // processFlowNodes(flow); + //add dropdown option to nodeOutputs + processFlowNodes(flow); //add animation to text type edges updateEdges(data.edges); // updateNodes(data.nodes, data.edges); @@ -410,6 +410,34 @@ export function updateEdgesHandleIds({ return newEdges; } +export function updateNewOutput({ nodes, edges }: updateEdgesHandleIdsType) { + let newEdges = cloneDeep(edges); + let newNodes = cloneDeep(nodes); + newNodes.forEach((node) => { + if ( + !node.data.node?.outputs && + (node.data.node?.base_classes ?? []).length > 0 + ) { + const selected = node.data.node?.base_classes[0]!; + node.data.node!.outputs = [ + { types: node.data.node!.base_classes, selected: selected }, + ]; + newEdges.forEach((edge) => { + if (edge.source === node.id && edge.sourceHandle) { + let newSourceHandle: sourceHandleType = scapeJSONParse( + edge.sourceHandle, + ); + newSourceHandle.baseClasses = [selected]; + edge.sourceHandle = scapedJSONStringfy(newSourceHandle); + edge.data.sourceHandle = newSourceHandle; + } + }); + } + }); + + return { nodes: newNodes, edges: newEdges }; +} + export function handleKeyDown( e: | React.KeyboardEvent @@ -561,6 +589,10 @@ export function checkOldEdgesHandles(edges: Edge[]): boolean { ); } +export function checkOldNodesOutput(nodes: NodeType[]): boolean { + return nodes.some((node) => !node.data.node?.outputs); +} + export function customStringify(obj: any): string { if (typeof obj === "undefined") { return "null"; @@ -1017,6 +1049,15 @@ export function processFlowEdges(flow: FlowType) { }); } +export function processFlowNodes(flow: FlowType) { + if (!flow.data || !flow.data.nodes) return; + if (checkOldNodesOutput(flow.data.nodes)) { + const { nodes, edges } = updateNewOutput(flow.data); + flow.data.nodes = nodes; + flow.data.edges = edges; + } +} + export function expandGroupNode( id: string, flow: FlowType, From 9d38ccc15db7febfef48f6c8cf5306e4b0344794 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Mon, 27 May 2024 19:00:26 -0300 Subject: [PATCH 07/11] feat(frontend): refactor OutputComponent to use external dropdown menu component --- .../genericNode/components/OutputComponent/index.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/frontend/src/customNodes/genericNode/components/OutputComponent/index.tsx b/src/frontend/src/customNodes/genericNode/components/OutputComponent/index.tsx index 2dbdc7167..d76f30f63 100644 --- a/src/frontend/src/customNodes/genericNode/components/OutputComponent/index.tsx +++ b/src/frontend/src/customNodes/genericNode/components/OutputComponent/index.tsx @@ -1,15 +1,15 @@ -import { - DropdownMenu, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuItem, -} from "@radix-ui/react-dropdown-menu"; import ForwardedIconComponent from "../../../../components/genericIconComponent"; import { outputComponentType } from "../../../../types/components"; import { cn } from "../../../../utils/utils"; import useFlowStore from "../../../../stores/flowStore"; import { NodeDataType } from "../../../../types/flow"; import { cloneDeep } from "lodash"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "../../../../components/ui/dropdown-menu"; export default function OutputComponent({ selected, From 3f43958f089909beb8e9d38065139b5c58ad5f48 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Tue, 28 May 2024 08:43:48 -0300 Subject: [PATCH 08/11] fix dropdown bug --- .../genericNode/components/OutputComponent/index.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/frontend/src/customNodes/genericNode/components/OutputComponent/index.tsx b/src/frontend/src/customNodes/genericNode/components/OutputComponent/index.tsx index d76f30f63..69ca3965c 100644 --- a/src/frontend/src/customNodes/genericNode/components/OutputComponent/index.tsx +++ b/src/frontend/src/customNodes/genericNode/components/OutputComponent/index.tsx @@ -10,6 +10,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "../../../../components/ui/dropdown-menu"; +import { useUpdateNodeInternals } from "reactflow"; export default function OutputComponent({ selected, @@ -19,6 +20,7 @@ export default function OutputComponent({ idx, }: outputComponentType) { const setNode = useFlowStore((state) => state.setNode); + const updateNodeInternals = useUpdateNodeInternals(); if (types.length < 2) { return {selected}; @@ -45,6 +47,7 @@ export default function OutputComponent({ type; return newNode; }); + updateNodeInternals(nodeId); }} > {type} From 77cb85b7f27dc946db2742960bc206ae32abd829 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Tue, 28 May 2024 15:35:38 -0300 Subject: [PATCH 09/11] feat(frontend): refactor OutputComponent to use external dropdown menu component --- src/frontend/src/utils/reactflowUtils.ts | 53 +++++++++++++++--------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 97384e714..b3e172be2 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -16,6 +16,8 @@ import { specialCharsRegex, } from "../constants/constants"; import { downloadFlowsFromDatabase } from "../controllers/API"; +import getFieldTitle from "../customNodes/utils/get-field-title"; +import { DESCRIPTIONS } from "../flow_constants"; import { APIClassType, APIKindType, @@ -37,8 +39,6 @@ import { updateEdgesHandleIdsType, } from "../types/utils/reactflowUtils"; import { createRandomKey, toTitleCase } from "./utils"; -import { DESCRIPTIONS } from "../flow_constants"; -import getFieldTitle from "../customNodes/utils/get-field-title"; const uid = new ShortUniqueId({ length: 5 }); export function checkChatInput(nodes: Node[]) { @@ -413,25 +413,38 @@ export function updateEdgesHandleIds({ export function updateNewOutput({ nodes, edges }: updateEdgesHandleIdsType) { let newEdges = cloneDeep(edges); let newNodes = cloneDeep(nodes); - newNodes.forEach((node) => { - if ( - !node.data.node?.outputs && - (node.data.node?.base_classes ?? []).length > 0 - ) { - const selected = node.data.node?.base_classes[0]!; - node.data.node!.outputs = [ - { types: node.data.node!.base_classes, selected: selected }, - ]; - newEdges.forEach((edge) => { - if (edge.source === node.id && edge.sourceHandle) { - let newSourceHandle: sourceHandleType = scapeJSONParse( - edge.sourceHandle, - ); - newSourceHandle.baseClasses = [selected]; - edge.sourceHandle = scapedJSONStringfy(newSourceHandle); - edge.data.sourceHandle = newSourceHandle; + newEdges.forEach((edge) => { + if (edge.sourceHandle && edge.targetHandle) { + let newSourceHandle: sourceHandleType = scapeJSONParse(edge.sourceHandle); + let newTargetHandle: targetHandleType = scapeJSONParse(edge.targetHandle); + let intersection; + if (newTargetHandle.inputTypes && newTargetHandle.inputTypes.length > 0) { + //conjuction subtraction + intersection = newSourceHandle.baseClasses.filter((type) => + newTargetHandle.inputTypes!.includes(type), + ); + } else { + intersection = newSourceHandle.baseClasses.filter( + (type) => type === newTargetHandle.type, + ); + } + const selected = intersection[0]; + newSourceHandle.baseClasses = [selected]; + const id = newSourceHandle.id; + const sourceNodeIndex = newNodes.findIndex((node) => node.id === id); + if (sourceNodeIndex > -1) { + const sourceNode = newNodes[sourceNodeIndex]; + if ( + !sourceNode.data.node?.outputs || + sourceNode.data.node!.outputs!.length === 0 + ) { + sourceNode.data.node!.outputs = [ + { types: sourceNode.data.node!.base_classes, selected: selected }, + ]; } - }); + } + edge.sourceHandle = scapedJSONStringfy(newSourceHandle); + edge.data.sourceHandle = newSourceHandle; } }); From 45011e8fda8fc6b70bd78db5c56873f6bcd077ed Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Tue, 28 May 2024 17:57:52 -0300 Subject: [PATCH 10/11] refactor: add idx property to handle types in GenericNode and reactflowUtils --- src/frontend/src/customNodes/genericNode/index.tsx | 2 ++ src/frontend/src/types/flow/index.ts | 1 + src/frontend/src/utils/reactflowUtils.ts | 3 +++ 3 files changed, 6 insertions(+) diff --git a/src/frontend/src/customNodes/genericNode/index.tsx b/src/frontend/src/customNodes/genericNode/index.tsx index 5eb2c9c39..febf12b39 100644 --- a/src/frontend/src/customNodes/genericNode/index.tsx +++ b/src/frontend/src/customNodes/genericNode/index.tsx @@ -589,6 +589,7 @@ export default function GenericNode({ baseClasses: data.node!.base_classes, id: data.id, dataType: data.type, + idx: 0, }} type={data.node?.base_classes.join("|")} left={false} @@ -848,6 +849,7 @@ export default function GenericNode({ baseClasses: [output.selected ?? output.types[0]], id: data.id, dataType: data.type, + idx: idx, }} type={output.types.join("|")} left={false} diff --git a/src/frontend/src/types/flow/index.ts b/src/frontend/src/types/flow/index.ts index d50f8def6..005bb6197 100644 --- a/src/frontend/src/types/flow/index.ts +++ b/src/frontend/src/types/flow/index.ts @@ -58,6 +58,7 @@ export type sourceHandleType = { dataType: string; id: string; baseClasses: string[]; + idx: number; }; //left side export type targetHandleType = { diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index b3e172be2..758c60d6d 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -79,6 +79,8 @@ export function cleanEdges(nodes: Node[], edges: Edge[]) { id: sourceNode.data.id, baseClasses: sourceNode.data.node!.base_classes, dataType: sourceNode.data.type, + idx: + sourceNode.data.node!.outputs[scapeJSONParse(sourceHandle).idx] ?? 0, }; if (scapedJSONStringfy(id) !== sourceHandle) { newEdges = newEdges.filter((e) => e.id !== edge.id); @@ -397,6 +399,7 @@ export function updateEdgesHandleIds({ id: sourceNode.data.id, baseClasses: sourceNode.data.node!.base_classes, dataType: sourceNode.data.type, + idx: 0, }; } edge.sourceHandle = scapedJSONStringfy(newSource!); From 02a1624bf4df61cbc1e633497136dad455775a72 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Tue, 28 May 2024 18:29:07 -0300 Subject: [PATCH 11/11] update cleanEdges and fix updateNewOutput --- src/frontend/src/utils/reactflowUtils.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 758c60d6d..a51baee23 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -46,6 +46,7 @@ export function checkChatInput(nodes: Node[]) { } export function cleanEdges(nodes: Node[], edges: Edge[]) { + console.log("cleanEdges"); let newEdges = cloneDeep(edges); edges.forEach((edge) => { // check if the source and target node still exists @@ -75,12 +76,12 @@ export function cleanEdges(nodes: Node[], edges: Edge[]) { } } if (sourceHandle) { + const index = scapeJSONParse(sourceHandle).idx ?? 0; const id: sourceHandleType = { id: sourceNode.data.id, - baseClasses: sourceNode.data.node!.base_classes, + baseClasses: [sourceNode.data.node.outputs[index].selected], dataType: sourceNode.data.type, - idx: - sourceNode.data.node!.outputs[scapeJSONParse(sourceHandle).idx] ?? 0, + idx: index, }; if (scapedJSONStringfy(id) !== sourceHandle) { newEdges = newEdges.filter((e) => e.id !== edge.id); @@ -414,6 +415,7 @@ export function updateEdgesHandleIds({ } export function updateNewOutput({ nodes, edges }: updateEdgesHandleIdsType) { + console.log("updateNewOutput"); let newEdges = cloneDeep(edges); let newNodes = cloneDeep(nodes); newEdges.forEach((edge) => { @@ -434,6 +436,7 @@ export function updateNewOutput({ nodes, edges }: updateEdgesHandleIdsType) { const selected = intersection[0]; newSourceHandle.baseClasses = [selected]; const id = newSourceHandle.id; + newSourceHandle.idx = 0; const sourceNodeIndex = newNodes.findIndex((node) => node.id === id); if (sourceNodeIndex > -1) { const sourceNode = newNodes[sourceNodeIndex];