diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 2d8fc3caa..88d990c93 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -19,15 +19,15 @@ import Router from "./routes"; import useAlertStore from "./stores/alertStore"; import { useDarkStore } from "./stores/darkStore"; import useFlowsManagerStore from "./stores/flowsManagerStore"; -import { useGlobalVariablesStore } from "./stores/globalVariables"; +import { useGlobalVariablesStore } from "./stores/globalVariablesStore/globalVariables"; import { useStoreStore } from "./stores/storeStore"; import { useTypesStore } from "./stores/typesStore"; export default function App() { const removeFromTempNotificationList = useAlertStore( - (state) => state.removeFromTempNotificationList + (state) => state.removeFromTempNotificationList, ); const tempNotificationList = useAlertStore( - (state) => state.tempNotificationList + (state) => state.tempNotificationList, ); const [fetchError, setFetchError] = useState(false); const isLoading = useFlowsManagerStore((state) => state.isLoading); @@ -45,10 +45,10 @@ export default function App() { const refreshVersion = useDarkStore((state) => state.refreshVersion); const refreshStars = useDarkStore((state) => state.refreshStars); const setGlobalVariables = useGlobalVariablesStore( - (state) => state.setGlobalVariables + (state) => state.setGlobalVariables, ); const setUnavailableFields = useGlobalVariablesStore( - (state) => state.setUnavaliableFields + (state) => state.setUnavaliableFields, ); const checkHasStore = useStoreStore((state) => state.checkHasStore); const navigate = useNavigate(); @@ -92,8 +92,8 @@ export default function App() { } }); - /* - Abort the request as it isn't needed anymore, the component being + /* + Abort the request as it isn't needed anymore, the component being unmounted. It helps avoid, among other things, the well-known "can't perform a React state update on an unmounted component" warning. */ diff --git a/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx b/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx index 2745ba1bf..1ea7142ab 100644 --- a/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx +++ b/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { registerGlobalVariable } from "../../controllers/API"; import BaseModal from "../../modals/baseModal"; import useAlertStore from "../../stores/alertStore"; -import { useGlobalVariablesStore } from "../../stores/globalVariables"; +import { useGlobalVariablesStore } from "../../stores/globalVariablesStore/globalVariables"; import { useTypesStore } from "../../stores/typesStore"; import { ResponseErrorDetailAPI } from "../../types/api"; import { sortByName } from "../../utils/utils"; diff --git a/src/frontend/src/components/inputGlobalComponent/index.tsx b/src/frontend/src/components/inputGlobalComponent/index.tsx index 2ca1aed22..7ab3cae33 100644 --- a/src/frontend/src/components/inputGlobalComponent/index.tsx +++ b/src/frontend/src/components/inputGlobalComponent/index.tsx @@ -2,7 +2,7 @@ import { useEffect } from "react"; import { deleteGlobalVariable } from "../../controllers/API"; import DeleteConfirmationModal from "../../modals/deleteConfirmationModal"; import useAlertStore from "../../stores/alertStore"; -import { useGlobalVariablesStore } from "../../stores/globalVariables"; +import { useGlobalVariablesStore } from "../../stores/globalVariablesStore/globalVariables"; import { InputGlobalComponentType } from "../../types/components"; import { cn } from "../../utils/utils"; import AddNewVariableButton from "../addNewVariableButtonComponent/addNewVariableButton"; diff --git a/src/frontend/src/customNodes/genericNode/components/parameterComponent/index.tsx b/src/frontend/src/customNodes/genericNode/components/parameterComponent/index.tsx index dbcb1e3dc..c77d7308c 100644 --- a/src/frontend/src/customNodes/genericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/customNodes/genericNode/components/parameterComponent/index.tsx @@ -49,7 +49,8 @@ import { nodeIconsLucide, nodeNames, } from "../../../../utils/styleUtils"; -import { classNames, groupByFamily } from "../../../../utils/utils"; +import { classNames } from "../../../../utils/utils"; +import groupByFamily from "./utils/group-by-family"; export default function ParameterComponent({ left, diff --git a/src/frontend/src/customNodes/genericNode/components/parameterComponent/utils/group-by-family.tsx b/src/frontend/src/customNodes/genericNode/components/parameterComponent/utils/group-by-family.tsx new file mode 100644 index 000000000..a38a31b59 --- /dev/null +++ b/src/frontend/src/customNodes/genericNode/components/parameterComponent/utils/group-by-family.tsx @@ -0,0 +1,119 @@ +import { APIDataType, TemplateVariableType } from "../../../../../types/api"; +import { + groupedObjType, + nodeGroupedObjType, +} from "../../../../../types/components"; +import { NodeType } from "../../../../../types/flow"; + +export default function groupByFamily( + data: APIDataType, + baseClasses: string, + left: boolean, + flow?: NodeType[], +): groupedObjType[] { + const baseClassesSet = new Set(baseClasses.split("\n")); + let arrOfPossibleInputs: Array<{ + category: string; + nodes: nodeGroupedObjType[]; + full: boolean; + display_name?: string; + }> = []; + let arrOfPossibleOutputs: Array<{ + category: string; + nodes: nodeGroupedObjType[]; + full: boolean; + display_name?: string; + }> = []; + let checkedNodes = new Map(); + const excludeTypes = new Set(["bool", "float", "code", "file", "int"]); + + const checkBaseClass = (template: TemplateVariableType) => { + return ( + template.type && + template.show && + ((!excludeTypes.has(template.type) && + baseClassesSet.has(template.type)) || + (template.input_types && + template.input_types.some((inputType) => + baseClassesSet.has(inputType), + ))) + ); + }; + + if (flow) { + // se existir o flow + for (const node of flow) { + // para cada node do flow + if (node!.data!.node!.flow || !node!.data!.node!.template) break; // não faz nada se o node for um group + const nodeData = node.data; + + const foundNode = checkedNodes.get(nodeData.type); // verifica se o tipo do node já foi checado + checkedNodes.set(nodeData.type, { + hasBaseClassInTemplate: + foundNode?.hasBaseClassInTemplate || + Object.values(nodeData.node!.template).some(checkBaseClass), + hasBaseClassInBaseClasses: + foundNode?.hasBaseClassInBaseClasses || + nodeData.node!.base_classes.some((baseClass) => + baseClassesSet.has(baseClass), + ), //seta como anterior ou verifica se o node tem base class + displayName: nodeData.node?.display_name, + }); + } + } + + for (const [d, nodes] of Object.entries(data)) { + let tempInputs: nodeGroupedObjType[] = [], + tempOutputs: nodeGroupedObjType[] = []; + + for (const [n, node] of Object.entries(nodes!)) { + let foundNode = checkedNodes.get(n); + + if (!foundNode) { + foundNode = { + hasBaseClassInTemplate: Object.values(node!.template).some( + checkBaseClass, + ), + hasBaseClassInBaseClasses: node!.base_classes.some((baseClass) => + baseClassesSet.has(baseClass), + ), + displayName: node?.display_name, + }; + } + + if (foundNode.hasBaseClassInTemplate) + tempInputs.push({ node: n, displayName: foundNode.displayName }); + if (foundNode.hasBaseClassInBaseClasses) + tempOutputs.push({ node: n, displayName: foundNode.displayName }); + } + + const totalNodes = Object.keys(nodes!).length; + + if (tempInputs.length) + arrOfPossibleInputs.push({ + category: d, + nodes: tempInputs, + full: tempInputs.length === totalNodes, + }); + if (tempOutputs.length) + arrOfPossibleOutputs.push({ + category: d, + nodes: tempOutputs, + full: tempOutputs.length === totalNodes, + }); + } + + return left + ? arrOfPossibleOutputs.map((output) => ({ + family: output.category, + type: output.full + ? "" + : output.nodes.map((item) => item.node).join(", "), + display_name: "", + })) + : arrOfPossibleInputs.map((input) => ({ + family: input.category, + type: input.full ? "" : input.nodes.map((item) => item.node).join(", "), + display_name: input.nodes.map((item) => item.displayName).join(", "), + })); +} diff --git a/src/frontend/src/modals/genericModal/index.tsx b/src/frontend/src/modals/genericModal/index.tsx index 02000df4d..0e111c548 100644 --- a/src/frontend/src/modals/genericModal/index.tsx +++ b/src/frontend/src/modals/genericModal/index.tsx @@ -24,8 +24,9 @@ import { postValidatePrompt } from "../../controllers/API"; import useAlertStore from "../../stores/alertStore"; import { genericModalPropsType } from "../../types/components"; import { handleKeyDown } from "../../utils/reactflowUtils"; -import { classNames, varHighlightHTML } from "../../utils/utils"; +import { classNames } from "../../utils/utils"; import BaseModal from "../baseModal"; +import varHighlightHTML from "./utils/var-highlight-html"; export default function GenericModal({ field_name = "", @@ -82,7 +83,7 @@ export default function GenericModal({ } const filteredWordsHighlight = matches.filter( - (word) => !invalid_chars.includes(word) + (word) => !invalid_chars.includes(word), ); setWordsHighlight(filteredWordsHighlight); @@ -133,7 +134,7 @@ export default function GenericModal({ // to the first key of the custom_fields object if (field_name === "") { field_name = Array.isArray( - apiReturn.data?.frontend_node?.custom_fields?.[""] + apiReturn.data?.frontend_node?.custom_fields?.[""], ) ? apiReturn.data?.frontend_node?.custom_fields?.[""][0] ?? "" : apiReturn.data?.frontend_node?.custom_fields?.[""] ?? ""; @@ -209,7 +210,7 @@ export default function GenericModal({
{type === TypeModal.PROMPT && isEdit && !readonly ? ( diff --git a/src/frontend/src/modals/genericModal/utils/var-highlight-html.tsx b/src/frontend/src/modals/genericModal/utils/var-highlight-html.tsx new file mode 100644 index 000000000..d88be4965 --- /dev/null +++ b/src/frontend/src/modals/genericModal/utils/var-highlight-html.tsx @@ -0,0 +1,6 @@ +import { IVarHighlightType } from "../../../types/components"; + +export default function varHighlightHTML({ name }: IVarHighlightType): string { + const html = `{${name}}`; + return html; +} diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index bc0a0e362..3a456f540 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -34,9 +34,10 @@ import { updateIds, validateSelection, } from "../../../../utils/reactflowUtils"; -import { getRandomName, isWrappedWithClass } from "../../../../utils/utils"; +import { getRandomName } from "../../../../utils/utils"; import ConnectionLineComponent from "../ConnectionLineComponent"; import SelectionMenu from "../SelectionMenuComponent"; +import isWrappedWithClass from "./utils/is-wrapped-with-class"; const nodeTypes = { genericNode: GenericNode, diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/utils/is-wrapped-with-class.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/utils/is-wrapped-with-class.tsx new file mode 100644 index 000000000..eff34d330 --- /dev/null +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/utils/is-wrapped-with-class.tsx @@ -0,0 +1,4 @@ +const isWrappedWithClass = (event: any, className: string | undefined) => + event.target.closest(`.${className}`); + +export default isWrappedWithClass; diff --git a/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx b/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx index 0ecdd588b..148f4359f 100644 --- a/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx +++ b/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx @@ -11,7 +11,7 @@ import { Badge } from "../../../../components/ui/badge"; import { Card, CardContent } from "../../../../components/ui/card"; import { deleteGlobalVariable } from "../../../../controllers/API"; import useAlertStore from "../../../../stores/alertStore"; -import { useGlobalVariablesStore } from "../../../../stores/globalVariables"; +import { useGlobalVariablesStore } from "../../../../stores/globalVariablesStore/globalVariables"; import { cn } from "../../../../utils/utils"; export default function GlobalVariablesPage() { diff --git a/src/frontend/src/stores/globalVariables.ts b/src/frontend/src/stores/globalVariablesStore/globalVariables.ts similarity index 91% rename from src/frontend/src/stores/globalVariables.ts rename to src/frontend/src/stores/globalVariablesStore/globalVariables.ts index 3adf8cbf8..873e6e899 100644 --- a/src/frontend/src/stores/globalVariables.ts +++ b/src/frontend/src/stores/globalVariablesStore/globalVariables.ts @@ -1,6 +1,6 @@ import { create } from "zustand"; -import { GlobalVariablesStore } from "../types/zustand/globalVariables"; -import { getUnavailableFields } from "../utils/utils"; +import { GlobalVariablesStore } from "../../types/zustand/globalVariables"; +import getUnavailableFields from "./utils/get-unavailable-fields"; export const useGlobalVariablesStore = create( (set, get) => ({ @@ -45,5 +45,5 @@ export const useGlobalVariablesStore = create( getVariableId: (name) => { return get().globalVariables[name]?.id; }, - }) + }), ); diff --git a/src/frontend/src/stores/globalVariablesStore/utils/get-unavailable-fields.tsx b/src/frontend/src/stores/globalVariablesStore/utils/get-unavailable-fields.tsx new file mode 100644 index 000000000..63da1dbe2 --- /dev/null +++ b/src/frontend/src/stores/globalVariablesStore/utils/get-unavailable-fields.tsx @@ -0,0 +1,13 @@ +export default function getUnavailableFields(variables: { + [key: string]: { default_fields?: string[] }; +}): { [name: string]: string } { + const unVariables: { [name: string]: string } = {}; + Object.keys(variables).forEach((key) => { + if (variables[key].default_fields) { + variables[key].default_fields!.forEach((field) => { + unVariables[field] = key; + }); + } + }); + return unVariables; +} diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 65a85960b..64008aa68 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -36,12 +36,8 @@ import { unselectAllNodesType, updateEdgesHandleIdsType, } from "../types/utils/reactflowUtils"; -import { - createRandomKey, - getFieldTitle, - getRandomDescription, - toTitleCase, -} from "./utils"; +import { createRandomKey, getFieldTitle, toTitleCase } from "./utils"; +import { DESCRIPTIONS } from "../flow_constants"; const uid = new ShortUniqueId({ length: 5 }); export function checkChatInput(nodes: Node[]) { @@ -1286,6 +1282,14 @@ export function downloadFlows() { }); } +export function getRandomElement(array: T[]): T { + return array[Math.floor(Math.random() * array.length)]; +} + +export function getRandomDescription(): string { + return getRandomElement(DESCRIPTIONS); +} + export const createNewFlow = ( flowData: ReactFlowJsonObject, flow: FlowType, diff --git a/src/frontend/src/utils/utils.ts b/src/frontend/src/utils/utils.ts index 5e1786404..23bd275ea 100644 --- a/src/frontend/src/utils/utils.ts +++ b/src/frontend/src/utils/utils.ts @@ -15,6 +15,7 @@ import { } from "../types/components"; import { NodeType } from "../types/flow"; import { FlowState } from "../types/tabs"; +import { getRandomElement } from "./reactflowUtils"; export function classNames(...classes: Array): string { return classes.filter(Boolean).join(" "); @@ -90,20 +91,6 @@ export function toTitleCase( .join(" "); } -export function getUnavailableFields(variables: { - [key: string]: { default_fields?: string[] }; -}): { [name: string]: string } { - const unVariables: { [name: string]: string } = {}; - Object.keys(variables).forEach((key) => { - if (variables[key].default_fields) { - variables[key].default_fields!.forEach((field) => { - unVariables[field] = key; - }); - } - }); - return unVariables; -} - export const upperCaseWords: string[] = ["llm", "uri"]; export function checkUpperWords(str: string): string { const words = str.split(" ").map((word) => { @@ -115,133 +102,10 @@ export function checkUpperWords(str: string): string { return words.join(" "); } -export const isWrappedWithClass = (event: any, className: string | undefined) => - event.target.closest(`.${className}`); - -export function groupByFamily( - data: APIDataType, - baseClasses: string, - left: boolean, - flow?: NodeType[], -): groupedObjType[] { - const baseClassesSet = new Set(baseClasses.split("\n")); - let arrOfPossibleInputs: Array<{ - category: string; - nodes: nodeGroupedObjType[]; - full: boolean; - display_name?: string; - }> = []; - let arrOfPossibleOutputs: Array<{ - category: string; - nodes: nodeGroupedObjType[]; - full: boolean; - display_name?: string; - }> = []; - let checkedNodes = new Map(); - const excludeTypes = new Set(["bool", "float", "code", "file", "int"]); - - const checkBaseClass = (template: TemplateVariableType) => { - return ( - template.type && - template.show && - ((!excludeTypes.has(template.type) && - baseClassesSet.has(template.type)) || - (template.input_types && - template.input_types.some((inputType) => - baseClassesSet.has(inputType), - ))) - ); - }; - - if (flow) { - // se existir o flow - for (const node of flow) { - // para cada node do flow - if (node!.data!.node!.flow || !node!.data!.node!.template) break; // não faz nada se o node for um group - const nodeData = node.data; - - const foundNode = checkedNodes.get(nodeData.type); // verifica se o tipo do node já foi checado - checkedNodes.set(nodeData.type, { - hasBaseClassInTemplate: - foundNode?.hasBaseClassInTemplate || - Object.values(nodeData.node!.template).some(checkBaseClass), - hasBaseClassInBaseClasses: - foundNode?.hasBaseClassInBaseClasses || - nodeData.node!.base_classes.some((baseClass) => - baseClassesSet.has(baseClass), - ), //seta como anterior ou verifica se o node tem base class - displayName: nodeData.node?.display_name, - }); - } - } - - for (const [d, nodes] of Object.entries(data)) { - let tempInputs: nodeGroupedObjType[] = [], - tempOutputs: nodeGroupedObjType[] = []; - - for (const [n, node] of Object.entries(nodes!)) { - let foundNode = checkedNodes.get(n); - - if (!foundNode) { - foundNode = { - hasBaseClassInTemplate: Object.values(node!.template).some( - checkBaseClass, - ), - hasBaseClassInBaseClasses: node!.base_classes.some((baseClass) => - baseClassesSet.has(baseClass), - ), - displayName: node?.display_name, - }; - } - - if (foundNode.hasBaseClassInTemplate) - tempInputs.push({ node: n, displayName: foundNode.displayName }); - if (foundNode.hasBaseClassInBaseClasses) - tempOutputs.push({ node: n, displayName: foundNode.displayName }); - } - - const totalNodes = Object.keys(nodes!).length; - - if (tempInputs.length) - arrOfPossibleInputs.push({ - category: d, - nodes: tempInputs, - full: tempInputs.length === totalNodes, - }); - if (tempOutputs.length) - arrOfPossibleOutputs.push({ - category: d, - nodes: tempOutputs, - full: tempOutputs.length === totalNodes, - }); - } - - return left - ? arrOfPossibleOutputs.map((output) => ({ - family: output.category, - type: output.full - ? "" - : output.nodes.map((item) => item.node).join(", "), - display_name: "", - })) - : arrOfPossibleInputs.map((input) => ({ - family: input.category, - type: input.full ? "" : input.nodes.map((item) => item.node).join(", "), - display_name: input.nodes.map((item) => item.displayName).join(", "), - })); -} - export function buildInputs(): string { return '{"input_value": "message"}'; } -export function getRandomElement(array: T[]): T { - return array[Math.floor(Math.random() * array.length)]; -} -export function getRandomDescription(): string { - return getRandomElement(DESCRIPTIONS); -} - export function getRandomName( retry: number = 0, noSpace: boolean = false,