diff --git a/src/frontend/src/customNodes/genericNode/components/outputModal/components/switchOutputView/index.tsx b/src/frontend/src/customNodes/genericNode/components/outputModal/components/switchOutputView/index.tsx new file mode 100644 index 000000000..ee09a9983 --- /dev/null +++ b/src/frontend/src/customNodes/genericNode/components/outputModal/components/switchOutputView/index.tsx @@ -0,0 +1,38 @@ +import RecordsOutputComponent from "../../../../../../components/recordsOutputComponent"; +import { Case } from "../../../../../../shared/components/caseComponent"; +import TextOutputView from "../../../../../../shared/components/textOutputView"; +import useFlowStore from "../../../../../../stores/flowStore"; + +export default function SwitchOutputView(nodeId): JSX.Element { + const nodes = useFlowStore((state) => state.nodes); + const setNode = useFlowStore((state) => state.setNode); + const flowPool = useFlowStore((state) => state.flowPool); + const node = nodes.find((node) => node?.id === nodeId?.nodeId); + + const flowPoolNode = (flowPool[node!.id] ?? [])[ + (flowPool[node!.id]?.length ?? 1) - 1 + ]; + + const results = flowPoolNode?.data?.logs[0] ?? ""; + + const checkType = () => { + const typeOutput = typeof results; + return typeOutput; + }; + + return ( + <> + + NO OUTPUT + + + + + + + + + + > + ); +} diff --git a/src/frontend/src/customNodes/genericNode/components/outputModal/index.tsx b/src/frontend/src/customNodes/genericNode/components/outputModal/index.tsx new file mode 100644 index 000000000..fbddc0275 --- /dev/null +++ b/src/frontend/src/customNodes/genericNode/components/outputModal/index.tsx @@ -0,0 +1,25 @@ +import { Button } from "../../../../components/ui/button"; +import BaseModal from "../../../../modals/baseModal"; +import SwitchOutputView from "./components/switchOutputView"; + +export default function OutputModal({ open, setOpen, nodeId }): JSX.Element { + return ( + + + + Output View + + + + + + + + setOpen(false)}> + Ok + + + + + ); +} diff --git a/src/frontend/src/customNodes/genericNode/index.tsx b/src/frontend/src/customNodes/genericNode/index.tsx index afaefb530..d17da3063 100644 --- a/src/frontend/src/customNodes/genericNode/index.tsx +++ b/src/frontend/src/customNodes/genericNode/index.tsx @@ -1,16 +1,11 @@ -import { cloneDeep } from "lodash"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { NodeToolbar, useUpdateNodeInternals } from "reactflow"; import IconComponent from "../../components/genericIconComponent"; import InputComponent from "../../components/inputComponent"; import ShadTooltip from "../../components/shadTooltipComponent"; import { Button } from "../../components/ui/button"; -import Checkmark from "../../components/ui/checkmark"; -import Loading from "../../components/ui/loading"; import { Textarea } from "../../components/ui/textarea"; -import Xmark from "../../components/ui/xmark"; import { - NATIVE_CATEGORIES, RUN_TIMESTAMP_PREFIX, STATUS_BUILD, STATUS_BUILDING, @@ -22,26 +17,33 @@ import { useDarkStore } from "../../stores/darkStore"; import useFlowStore from "../../stores/flowStore"; import useFlowsManagerStore from "../../stores/flowsManagerStore"; import { useTypesStore } from "../../stores/typesStore"; -import { APIClassType, VertexBuildTypeAPI } from "../../types/api"; -import { validationStatusType } from "../../types/components"; +import { VertexBuildTypeAPI } from "../../types/api"; import { NodeDataType } from "../../types/flow"; import { handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils"; import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils"; import { classNames, cn } from "../../utils/utils"; -import ParameterComponent from "./components/parameterComponent"; +import { countHandlesFn } from "../helpers/count-handles"; +import { getSpecificClassFromBuildStatus } from "../helpers/get-class-from-build-status"; +import useCheckCodeValidity from "../hooks/use-check-code-validity"; +import useIconNodeRender from "../hooks/use-icon-render"; +import useIconStatus from "../hooks/use-icons-status"; +import useUpdateNodeCode from "../hooks/use-update-node-code"; +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 OutputModal from "./components/outputModal"; +import ParameterComponent from "./components/parameterComponent"; export default function GenericNode({ data, - xPos, - yPos, + selected, }: { data: NodeDataType; selected: boolean; - xPos: number; - yPos: number; + xPos?: number; + yPos?: number; }): JSX.Element { const types = useTypesStore((state) => state.types); const templates = useTypesStore((state) => state.templates); @@ -51,203 +53,42 @@ export default function GenericNode({ const setNode = useFlowStore((state) => state.setNode); const updateNodeInternals = useUpdateNodeInternals(); const setErrorData = useAlertStore((state) => state.setErrorData); - const name = nodeIconsLucide[data.type] ? data.type : types[data.type]; - const [inputName, setInputName] = useState(false); - const [nodeName, setNodeName] = useState(data.node!.display_name); - const [inputDescription, setInputDescription] = useState(false); - const [nodeDescription, setNodeDescription] = useState( - data.node?.description!, - ); - const [isOutdated, setIsOutdated] = useState(false); + const isDark = useDarkStore((state) => state.dark); const buildStatus = useFlowStore( (state) => state.flowBuildStatus[data.id]?.status, ); const lastRunTime = useFlowStore( (state) => state.flowBuildStatus[data.id]?.timestamp, ); + const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot); + + const [inputName, setInputName] = useState(false); + const [nodeName, setNodeName] = useState(data.node!.display_name); + const [inputDescription, setInputDescription] = useState(false); + const [nodeDescription, setNodeDescription] = useState( + data.node?.description!, + ); + const [openOutputModal, setOpenOutputModal] = useState(false); + const [isOutdated, setIsOutdated] = useState(false); const [validationStatus, setValidationStatus] = useState(null); const [handles, setHandles] = useState(0); - const [validationString, setValidationString] = useState(""); - const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot); - - useEffect(() => { - // This one should run only once - // first check if data.type in NATIVE_CATEGORIES - // if not return - if ( - !NATIVE_CATEGORIES.includes(types[data.type]) || - !data.node?.template?.code?.value - ) - return; - const thisNodeTemplate = templates[data.type].template; - // if the template does not have a code key - // return - if (!thisNodeTemplate.code) return; - const currentCode = thisNodeTemplate.code?.value; - const thisNodesCode = data.node!.template?.code?.value; - const componentsToIgnore = ["Custom Component", "Prompt"]; - if ( - currentCode !== thisNodesCode && - !componentsToIgnore.includes(data.node!.display_name) - ) { - setIsOutdated(true); - } else { - setIsOutdated(false); - } - // template.code can be undefined - }, [data.node?.template?.code?.value]); - - const updateNodeCode = useCallback( - (newNodeClass: APIClassType, code: string, name: string) => { - setNode(data.id, (oldNode) => { - let newNode = cloneDeep(oldNode); - - newNode.data = { - ...newNode.data, - node: newNodeClass, - description: newNodeClass.description ?? data.node!.description, - display_name: newNodeClass.display_name ?? data.node!.display_name, - }; - - newNode.data.node.template[name].value = code; - setIsOutdated(false); - - return newNode; - }); - - updateNodeInternals(data.id); - }, - [data.id, data.node, setNode, setIsOutdated], - ); - - if (!data.node!.template) { - setErrorData({ - title: `Error in component ${data.node!.display_name}`, - list: [ - `The component ${data.node!.display_name} has no template.`, - `Please contact the developer of the component to fix this issue.`, - ], - }); - takeSnapshot(); - deleteNode(data.id); - } - - function countHandles(): void { - let count = Object.keys(data.node!.template) - .filter((templateField) => templateField.charAt(0) !== "_") - .map((templateCamp) => { - const { template } = data.node!; - if (template[templateCamp].input_types) return true; - if (!template[templateCamp].show) return false; - switch (template[templateCamp].type) { - case "str": - case "bool": - case "float": - case "code": - case "prompt": - case "file": - case "int": - return false; - default: - return true; - } - }) - .reduce((total, value) => total + (value ? 1 : 0), 0); - - setHandles(count); - } - useEffect(() => { - countHandles(); - }, [data, data.node]); - - useEffect(() => { - if (!selected) { - setInputName(false); - setInputDescription(false); - } - }, [selected]); - + const iconStatus = useIconStatus(buildStatus, validationStatus); + const [showNode, setShowNode] = useState(data.showNode ?? true); // State for outline color const isBuilding = useFlowStore((state) => state.isBuilding); - // should be empty string if no duration - // else should be `Duration: ${duration}` - const getDurationString = (duration: number | undefined|string): string => { - if (duration === undefined) { - return ""; - } else { - return `${duration}`; - } - }; - const durationString = getDurationString(validationStatus?.data.duration); + const updateNodeCode = useUpdateNodeCode( + data?.id, + data.node!, + setNode, + setIsOutdated, + updateNodeInternals, + ); - useEffect(() => { - setNodeDescription(data.node!.description); - }, [data.node!.description]); - - useEffect(() => { - setNodeName(data.node!.display_name); - }, [data.node!.display_name]); - - useEffect(() => { - const relevantData = - flowPool[data.id] && flowPool[data.id]?.length > 0 - ? flowPool[data.id][flowPool[data.id].length - 1] - : null; - if (relevantData) { - console.log(relevantData) - // Extract validation information from relevantData and update the validationStatus state - setValidationStatus(relevantData); - } else { - setValidationStatus(null); - } - }, [flowPool[data.id], data.id]); - - useEffect(() => { - if (validationStatus?.data.logs) { - // if it is not a string turn it into a string - let newValidationString = ""; - if (Array.isArray(validationStatus.data.logs)) { - newValidationString = validationStatus.data.logs - .map((log) => log.message) - .join("\n"); - } - if (typeof newValidationString !== "string") { - newValidationString = JSON.stringify(validationStatus.data.logs); - } - - setValidationString(newValidationString); - } - }, [validationStatus, validationStatus?.data.logs]); - - const [showNode, setShowNode] = useState(data.showNode ?? true); - - useEffect(() => { - setShowNode(data.showNode ?? true); - }, [data.showNode]); - - const nameEditable = true; - - const emojiRegex = /\p{Emoji}/u; - const isEmoji = emojiRegex.test(data?.node?.icon!); - - const iconNodeRender = useCallback(() => { - const iconElement = data?.node?.icon; - const iconColor = nodeColors[types[data.type]]; - const iconName = - iconElement || (data.node?.flow ? "group_components" : name); - const iconClassName = `generic-node-icon ${ - !showNode ? " absolute inset-x-6 h-12 w-12 " : "" - }`; - if (iconElement && isEmoji) { - return nodeIconFragment(iconElement); - } else { - return checkNodeIconFragment(iconColor, iconName, iconClassName); - } - }, [data, isEmoji, name, showNode]); + const name = nodeIconsLucide[data.type] ? data.type : types[data.type]; const nodeIconFragment = (icon) => { return {icon}; @@ -263,79 +104,24 @@ export default function GenericNode({ ); }; - const isDark = useDarkStore((state) => state.dark); - const renderIconStatus = ( - buildStatus: BuildStatus | undefined, - validationStatus: VertexBuildTypeAPI | null - ) => { - if (buildStatus === BuildStatus.BUILDING) { - return ; - } else { - return ( - <> - - {validationStatus && validationStatus.valid ? ( - - ) : validationStatus && - !validationStatus.valid && - buildStatus === BuildStatus.INACTIVE ? ( - - ) : buildStatus === BuildStatus.ERROR || - (validationStatus && !validationStatus.valid) ? ( - - ) : ( - - )} - > - ); - } - }; - const getSpecificClassFromBuildStatus = ( - buildStatus: BuildStatus | undefined, - validationStatus: VertexBuildTypeAPI | null - ) => { - let isInvalid = validationStatus && !validationStatus.valid; - - if (buildStatus === BuildStatus.INACTIVE) { - // INACTIVE should have its own class - return "inactive-status"; - } - if ( - (buildStatus === BuildStatus.BUILT && isInvalid) || - buildStatus === BuildStatus.ERROR - ) { - return isDark ? "built-invalid-status-dark" : "built-invalid-status"; - } else if (buildStatus === BuildStatus.BUILDING) { - return "building-status"; - } else { - return ""; - } + const renderIconStatus = () => { + return ( + + {iconStatus} + + ); }; const getNodeBorderClassName = ( selected: boolean, showNode: boolean, buildStatus: BuildStatus | undefined, - validationStatus: VertexBuildTypeAPI | null + validationStatus: VertexBuildTypeAPI | null, ) => { const specificClassFromBuildStatus = getSpecificClassFromBuildStatus( buildStatus, validationStatus, + isDark, ); const baseBorderClass = getBaseBorderClass(selected); @@ -354,6 +140,65 @@ export default function GenericNode({ const getNodeSizeClass = (showNode) => showNode ? "w-96 rounded-lg" : "w-26 h-26 rounded-full"; + const nameEditable = true; + const emojiRegex = /\p{Emoji}/u; + const isEmoji = emojiRegex.test(data?.node?.icon!); + + if (!data.node!.template) { + setErrorData({ + title: `Error in component ${data.node!.display_name}`, + list: [ + `The component ${data.node!.display_name} has no template.`, + `Please contact the developer of the component to fix this issue.`, + ], + }); + takeSnapshot(); + deleteNode(data.id); + } + + useCheckCodeValidity(data, templates, setIsOutdated, types); + useValidationStatusString(validationStatus, setValidationString); + useUpdateValidationStatus(data?.id, flowPool, setValidationStatus); + + const iconNodeRender = useIconNodeRender( + data, + types, + nodeColors, + name, + showNode, + isEmoji, + nodeIconFragment, + checkNodeIconFragment, + ); + + function countHandles(): void { + const count = countHandlesFn(data); + setHandles(count); + } + + useEffect(() => { + countHandles(); + }, [data, data.node]); + + useEffect(() => { + if (!selected) { + setInputName(false); + setInputDescription(false); + } + }, [selected]); + + useEffect(() => { + setNodeDescription(data.node!.description); + }, [data.node!.description]); + + useEffect(() => { + setNodeName(data.node!.display_name); + }, [data.node!.display_name]); + + useEffect(() => { + setShowNode(data.showNode ?? true); + }, [data.showNode]); + const memoizedNodeToolbarComponent = useMemo(() => { return ( @@ -603,67 +448,63 @@ export default function GenericNode({ )} {showNode && ( - {STATUS_BUILDING} - ) : !validationStatus ? ( - {STATUS_BUILD} - ) : ( - - - {lastRunTime && ( - - {RUN_TIMESTAMP_PREFIX} - - {lastRunTime} - - - )} - - - Duration: - - {validationStatus?.data.duration} - - - - - Output - - - {validationString.split("\n").map((line, index) => ( - - {line} - - ))} - - - ) - } - side="bottom" - > + <> { - if (buildStatus === BuildStatus.BUILDING || isBuilding) - return; - setValidationStatus(null); - buildFlow({ stopNodeId: data.id }); + setOpenOutputModal(true); }} - variant="secondary" - className={"group h-9 px-1.5"} > - - - {renderIconStatus(buildStatus, validationStatus)} - - + STATUS## - + {STATUS_BUILDING} + ) : !validationStatus ? ( + {STATUS_BUILD} + ) : ( + + + {lastRunTime && ( + + {RUN_TIMESTAMP_PREFIX} + + {lastRunTime} + + + )} + + + Duration: + + {validationStatus?.data.duration} + + + + ) + } + side="bottom" + > + { + if (buildStatus === BuildStatus.BUILDING || isBuilding) + return; + setValidationStatus(null); + buildFlow({ stopNodeId: data.id }); + }} + variant="secondary" + className={"group h-9 px-1.5"} + > + + {renderIconStatus()} + + + + > )} @@ -865,6 +706,14 @@ export default function GenericNode({ )} + + {openOutputModal && ( + + )} > ); }