From c69f950cbab1afe8c0f0e898cc71458028d5afd3 Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa <72977554+Cristhianzl@users.noreply.github.com> Date: Fri, 10 May 2024 12:30:45 -0300 Subject: [PATCH] Migrate Tweaks Module to Zustand Library and Add All Flows (#1847) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ (codeTabsComponent/index.tsx): refactor code to remove unnecessary code and improve readability 📝 (codeTabsComponent/index.tsx): update comments in source code to provide better understanding of the code logic ♻️ (apiModal/index.tsx): refactor code to remove unnecessary code and improve readability 📝 (apiModal/index.tsx): update comments in source code to provide better understanding of the code logic 🔧 (tweaksStore.ts): add new store for managing tweaks data 📝 (components/index.ts): update codeTabsPropsType to use array syntax for tweak and tweaksList properties for better readability and consistency ♻️ (utils/utils.ts): refactor toTitleCase function to remove unnecessary comma and improve code readability ♻️ (utils/utils.ts): refactor groupByFamily function to remove unnecessary comma and improve code readability ♻️ (utils/utils.ts): refactor getPythonApiCode function to remove unnecessary comma and improve code readability ♻️ (utils/utils.ts): refactor getCurlCode function to remove unnecessary comma and improve code readability ♻️ (utils/utils.ts): refactor getPythonCode function to remove unnecessary comma and improve code readability ♻️ (utils/utils.ts): refactor getWidgetCode function to remove unnecessary comma and improve code readability ♻️ (utils/utils.ts): refactor getFieldTitle function to remove unnecessary comma and improve code readability ♻️ (utils/utils.ts): refactor IncrementObjectKey function to remove unnecessary comma and improve code readability ♻️ (utils/utils.ts): refactor getSetFromObject function to remove unnecessary comma and improve code readability ♻️ (utils/utils.ts): refactor getRandomName function to remove unnecessary comma and improve code readability * 📝 (codeTabsComponent/index.tsx): remove unnecessary whitespace in className ♻️ (codeTabsComponent/index.tsx): refactor code to improve readability and remove unnecessary code ♻️ (apiModal/index.tsx): refactor code to improve readability and remove unnecessary code ♻️ (tweaksStore.ts): refactor code to improve readability and remove unnecessary code ✨ (zustand/tweaks/index.ts): add types for tweaks store * ✨ (codeTabsComponent/index.tsx): refactor code to improve readability and remove unnecessary code ♻️ (codeTabsComponent/index.tsx): refactor code to improve readability and remove unnecessary code ♻️ (utils/utils.ts): refactor code to improve readability and remove unnecessary code ✅ (tweaks_test.spec.ts): add end-to-end test to check if tweaks are updating when something on the flow changes * 🐛 (accordionComponent/index.tsx): fix variable name from 'trigger' to 'keyValue' for better clarity and readability ♻️ (accordionComponent/index.tsx): refactor code to use 'defaultValue' prop instead of manually setting the value in the AccordionItem component 🐛 (codeTabsComponent/index.tsx): remove unused 'openAccordions' function ♻️ (codeTabsComponent/index.tsx): refactor code to remove unnecessary condition for opening accordions in the onValueChange event handler ♻️ (codeTabsComponent/index.tsx): refactor code to remove unused 'open' prop in AccordionItem component ♻️ (codeTabsComponent/index.tsx): refactor code to remove unused 'openAccordion' state variable ♻️ (codeTabsComponent/index.tsx): refactor code to remove unused 'openAccordions' function call in the onValueChange event handler * 🐛 (apiModal/index.tsx): remove unnecessary comma after ref parameter in ApiModal component ♻️ (apiModal/index.tsx): refactor code to improve readability and remove unnecessary commas in function parameters ✨ (apiModal/index.tsx): add conditional check before assigning values to tabs array to prevent errors when tabs is undefined or empty * 🐛 (index.tsx): remove unnecessary defaultValue prop from Accordion component ♻️ (index.tsx): refactor AccordionItem component to remove defaultValue prop and simplify code * 🐛 (apiModal/index.tsx): remove unnecessary comma after ref parameter in function signature ♻️ (apiModal/index.tsx): refactor code to improve readability and remove unnecessary comma after ref parameter in function signature * refactor tweaks * ✨ (codeTabsComponent/index.tsx): add autoFocus prop to the tweaks switch to prevent it from automatically gaining focus when rendered ♻️ (codeTabsComponent/index.tsx): refactor classNames in the api-modal-according-display div to remove unnecessary whitespace ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api-modal-according-display div to improve readability and remove unnecessary parentheses ♻️ (codeTabsComponent/index.tsx): refactor mapping of templateFields in the api * fix tests on tweaks --- .../components/accordionComponent/index.tsx | 7 +- .../src/components/chatComponent/index.tsx | 2 +- .../components/codeTabsComponent/index.tsx | 161 ++++++------ src/frontend/src/modals/apiModal/index.tsx | 245 ------------------ .../modals/apiModal/utils/build-content.tsx | 10 + .../src/modals/apiModal/utils/build-tweaks.ts | 8 + .../utils/check-can-build-tweak-object.ts | 13 + .../apiModal/utils/get-changes-types.ts | 26 ++ .../utils/get-nodes-with-default-value.ts | 28 ++ .../src/modals/apiModal/utils/get-value.ts | 29 +++ .../src/modals/apiModal/views/index.tsx | 211 +++++++++++++++ src/frontend/src/stores/tweaksStore.ts | 9 + src/frontend/src/types/components/index.ts | 17 +- .../src/types/zustand/tweaks/index.ts | 8 + src/frontend/src/utils/reactflowUtils.ts | 111 ++++---- src/frontend/src/utils/utils.ts | 53 +--- .../tests/end-to-end/tweaks_test.spec.ts | 72 ++++- 17 files changed, 575 insertions(+), 435 deletions(-) delete mode 100644 src/frontend/src/modals/apiModal/index.tsx create mode 100644 src/frontend/src/modals/apiModal/utils/build-content.tsx create mode 100644 src/frontend/src/modals/apiModal/utils/build-tweaks.ts create mode 100644 src/frontend/src/modals/apiModal/utils/check-can-build-tweak-object.ts create mode 100644 src/frontend/src/modals/apiModal/utils/get-changes-types.ts create mode 100644 src/frontend/src/modals/apiModal/utils/get-nodes-with-default-value.ts create mode 100644 src/frontend/src/modals/apiModal/utils/get-value.ts create mode 100644 src/frontend/src/modals/apiModal/views/index.tsx create mode 100644 src/frontend/src/stores/tweaksStore.ts create mode 100644 src/frontend/src/types/zustand/tweaks/index.ts diff --git a/src/frontend/src/components/accordionComponent/index.tsx b/src/frontend/src/components/accordionComponent/index.tsx index fd9e42580..fdcf8b96c 100644 --- a/src/frontend/src/components/accordionComponent/index.tsx +++ b/src/frontend/src/components/accordionComponent/index.tsx @@ -15,17 +15,16 @@ export default function AccordionComponent({ sideBar, }: AccordionComponentType): JSX.Element { const [value, setValue] = useState( - open.length === 0 ? "" : getOpenAccordion() + open.length === 0 ? "" : getOpenAccordion(), ); function getOpenAccordion(): string { let value = ""; open.forEach((el) => { - if (el == trigger) { - value = trigger; + if (el == keyValue) { + value = keyValue; } }); - return value; } diff --git a/src/frontend/src/components/chatComponent/index.tsx b/src/frontend/src/components/chatComponent/index.tsx index 7d55f9229..5de2198a0 100644 --- a/src/frontend/src/components/chatComponent/index.tsx +++ b/src/frontend/src/components/chatComponent/index.tsx @@ -1,7 +1,7 @@ import { Transition } from "@headlessui/react"; import { useEffect, useMemo, useRef, useState } from "react"; import IOModal from "../../modals/IOModal"; -import ApiModal from "../../modals/apiModal"; +import ApiModal from "../../modals/apiModal/views"; import ShareModal from "../../modals/shareModal"; import useFlowStore from "../../stores/flowStore"; import useFlowsManagerStore from "../../stores/flowsManagerStore"; diff --git a/src/frontend/src/components/codeTabsComponent/index.tsx b/src/frontend/src/components/codeTabsComponent/index.tsx index 355ddb1ec..c40c11439 100644 --- a/src/frontend/src/components/codeTabsComponent/index.tsx +++ b/src/frontend/src/components/codeTabsComponent/index.tsx @@ -41,6 +41,8 @@ import IconComponent from "../genericIconComponent"; import InputComponent from "../inputComponent"; import KeypairListComponent from "../keypairListComponent"; import ShadTooltip from "../shadTooltipComponent"; +import { Label } from "../ui/label"; +import { Switch } from "../ui/switch"; export default function CodeTabsComponent({ flow, @@ -49,14 +51,13 @@ export default function CodeTabsComponent({ setActiveTab, isMessage, tweaks, + setActiveTweaks, + activeTweaks, }: codeTabsPropsType) { const [isCopied, setIsCopied] = useState(false); const [data, setData] = useState(flow ? flow["data"]!["nodes"] : null); - const [openAccordion, setOpenAccordion] = useState([]); const dark = useDarkStore((state) => state.dark); const unselectAll = useFlowStore((state) => state.unselectAll); - const setNode = useFlowStore((state) => state.setNode); - const [errorDuplicateKey, setErrorDuplicateKey] = useState(false); useEffect(() => { @@ -107,21 +108,6 @@ export default function CodeTabsComponent({ URL.revokeObjectURL(url); }; - function openAccordions() { - let accordionsToOpen: string[] = []; - tweaks?.tweak!.current.forEach((el) => { - Object.keys(el).forEach((key) => { - if (Object.keys(el[key]).length > 0) { - accordionsToOpen.push(key); - setOpenAccordion(accordionsToOpen); - } - }); - }); - - if (accordionsToOpen.length == 0) { - setOpenAccordion([]); - } - } return ( { setActiveTab(value); - if (value === "3") { - openAccordions(); - } }} >
@@ -155,27 +138,58 @@ export default function CodeTabsComponent({ ) : (
)} - {Number(activeTab) < 4 && ( -
- - + Tweaks +
- )} + + {Number(activeTab) < 4 && ( + <> + + + + )} +
{tabs.map((tab, idx) => ( @@ -205,14 +219,12 @@ export default function CodeTabsComponent({
{data?.map((node: any, i) => (
- {tweaks?.tweaksList!.current.includes( - node["data"]["id"] - ) && ( + {tweaks?.tweaksList!.includes(node["data"]["id"]) && ( {node["data"]["node"]["display_name"]}
} - open={openAccordion} keyValue={node["data"]["id"]} >
@@ -247,8 +258,8 @@ export default function CodeTabsComponent({ .show && LANGFLOW_SUPPORTED_TYPES.has( node.data.node.template[templateField] - .type - ) + .type, + ), ) .map((templateField, indx) => { return ( @@ -298,12 +309,12 @@ export default function CodeTabsComponent({ ].value = target; return newInputList; }); - tweaks.buildTweakObject!( + tweaks?.buildTweakObject!( node["data"]["id"], target, node.data.node.template[ templateField - ] + ], ); }} /> @@ -339,13 +350,13 @@ export default function CodeTabsComponent({ ].value = target; return newInputList; }); - tweaks.buildTweakObject!( + tweaks?.buildTweakObject!( node["data"]["id"], target, node.data.node .template[ templateField - ] + ], ); }} /> @@ -383,12 +394,12 @@ export default function CodeTabsComponent({ ].value = target; return newInputList; }); - tweaks.buildTweakObject!( + tweaks?.buildTweakObject!( node["data"]["id"], target, node.data.node.template[ templateField - ] + ], ); }} /> @@ -416,12 +427,12 @@ export default function CodeTabsComponent({ ].value = e; return newInputList; }); - tweaks.buildTweakObject!( + tweaks?.buildTweakObject!( node["data"]["id"], e, node.data.node.template[ templateField - ] + ], ); }} size="small" @@ -447,7 +458,7 @@ export default function CodeTabsComponent({ ].fileTypes } onFileChange={( - value: any + value: any, ) => { node.data.node.template[ templateField @@ -490,12 +501,12 @@ export default function CodeTabsComponent({ ].value = target; return newInputList; }); - tweaks.buildTweakObject!( + tweaks?.buildTweakObject!( node["data"]["id"], target, node.data.node.template[ templateField - ] + ], ); }} /> @@ -525,12 +536,12 @@ export default function CodeTabsComponent({ ].value = target; return newInputList; }); - tweaks.buildTweakObject!( + tweaks?.buildTweakObject!( node["data"]["id"], target, node.data.node.template[ templateField - ] + ], ); }} value={ @@ -582,12 +593,12 @@ export default function CodeTabsComponent({ ].value = target; return newInputList; }); - tweaks.buildTweakObject!( + tweaks?.buildTweakObject!( node["data"]["id"], target, node.data.node.template[ templateField - ] + ], ); }} /> @@ -623,12 +634,12 @@ export default function CodeTabsComponent({ ].value = target; return newInputList; }); - tweaks.buildTweakObject!( + tweaks?.buildTweakObject!( node["data"]["id"], target, node.data.node.template[ templateField - ] + ], ); }} /> @@ -664,12 +675,12 @@ export default function CodeTabsComponent({ ].value = target; return newInputList; }); - tweaks.buildTweakObject!( + tweaks?.buildTweakObject!( node["data"]["id"], target, node.data.node.template[ templateField - ] + ], ); }} /> @@ -693,7 +704,7 @@ export default function CodeTabsComponent({ node.data.node! .template[ templateField - ].value + ].value, ) } duplicateKey={ @@ -702,15 +713,15 @@ export default function CodeTabsComponent({ onChange={(target) => { const valueToNumbers = convertValuesToNumbers( - target + target, ); node.data.node!.template[ templateField ].value = valueToNumbers; setErrorDuplicateKey( hasDuplicateKeys( - valueToNumbers - ) + valueToNumbers, + ), ); setData((old) => { let newInputList = @@ -722,12 +733,12 @@ export default function CodeTabsComponent({ ].value = target; return newInputList; }); - tweaks.buildTweakObject!( + tweaks?.buildTweakObject!( node["data"]["id"], target, node.data.node.template[ templateField - ] + ], ); }} isList={ @@ -767,12 +778,12 @@ export default function CodeTabsComponent({ ].value = target; return newInputList; }); - tweaks.buildTweakObject!( + tweaks?.buildTweakObject!( node["data"]["id"], target, node.data.node.template[ templateField - ] + ], ); }} /> @@ -795,7 +806,7 @@ export default function CodeTabsComponent({ )} - {tweaks?.tweaksList!.current.length === 0 && ( + {tweaks?.tweaksList!.length === 0 && ( <>
No tweaks are available for this flow. diff --git a/src/frontend/src/modals/apiModal/index.tsx b/src/frontend/src/modals/apiModal/index.tsx deleted file mode 100644 index 9e847aae5..000000000 --- a/src/frontend/src/modals/apiModal/index.tsx +++ /dev/null @@ -1,245 +0,0 @@ -import "ace-builds/src-noconflict/ext-language_tools"; -import "ace-builds/src-noconflict/mode-python"; -import "ace-builds/src-noconflict/theme-github"; -import "ace-builds/src-noconflict/theme-twilight"; -import { - ReactNode, - forwardRef, - useContext, - useEffect, - useRef, - useState, -} from "react"; -// import "ace-builds/webpack-resolver"; -import CodeTabsComponent from "../../components/codeTabsComponent"; -import IconComponent from "../../components/genericIconComponent"; -import { - EXPORT_CODE_DIALOG, - LANGFLOW_SUPPORTED_TYPES, -} from "../../constants/constants"; -import { AuthContext } from "../../contexts/authContext"; -import useFlowStore from "../../stores/flowStore"; -import { TemplateVariableType } from "../../types/api"; -import { tweakType, uniqueTweakType } from "../../types/components"; -import { FlowType, NodeType } from "../../types/flow/index"; -import { buildTweaks, convertArrayToObj } from "../../utils/reactflowUtils"; -import { - getCurlCode, - getPythonApiCode, - getPythonCode, - getWidgetCode, - tabsArray, -} from "../../utils/utils"; -import BaseModal from "../baseModal"; - -const ApiModal = forwardRef( - ( - { - flow, - children, - }: { - flow: FlowType; - children: ReactNode; - }, - ref - ) => { - const { autoLogin } = useContext(AuthContext); - const [open, setOpen] = useState(false); - const [activeTab, setActiveTab] = useState("0"); - const tweak = useRef([]); - const tweaksList = useRef([]); - const [getTweak, setTweak] = useState([]); - const flowState = useFlowStore((state) => state.flowState); - const pythonApiCode = getPythonApiCode(flow, autoLogin, tweak.current); - const curl_code = getCurlCode(flow, autoLogin, tweak.current); - const pythonCode = getPythonCode(flow, tweak.current); - const widgetCode = getWidgetCode(flow, autoLogin, flowState); - const tweaksCode = buildTweaks(flow); - const codesArray = [ - curl_code, - pythonApiCode, - pythonCode, - widgetCode, - pythonCode, - ]; - const [tabs, setTabs] = useState(tabsArray(codesArray, 0)); - - function startState() { - tweak.current = []; - setTweak([]); - tweaksList.current = []; - } - - useEffect(() => { - if (flow["data"]!["nodes"].length == 0) { - startState(); - } else { - tweak.current = []; - const t = buildTweaks(flow); - tweak.current.push(t); - } - - filterNodes(); - - if (Object.keys(tweaksCode).length > 0) { - setActiveTab("0"); - setTabs(tabsArray(codesArray, 1)); - } else { - setTabs(tabsArray(codesArray, 1)); - } - }, [flow["data"]!["nodes"], open]); - - function filterNodes() { - let arrNodesWithValues: string[] = []; - - flow["data"]!["nodes"].forEach((node) => { - if (!node["data"]["node"]["template"]) { - return; - } - Object.keys(node["data"]["node"]["template"]) - .filter( - (templateField) => - templateField.charAt(0) !== "_" && - node.data.node.template[templateField].show && - LANGFLOW_SUPPORTED_TYPES.has( - node.data.node.template[templateField].type - ) - ) - .map((n, i) => { - arrNodesWithValues.push(node["id"]); - }); - }); - - tweaksList.current = arrNodesWithValues.filter((value, index, self) => { - return self.indexOf(value) === index; - }); - } - function buildTweakObject( - tw: string, - changes: string | string[] | boolean | number | Object[] | Object, - template: TemplateVariableType - ) { - if (typeof changes === "string" && template.type === "float") { - changes = parseFloat(changes); - } - if (typeof changes === "string" && template.type === "int") { - changes = parseInt(changes); - } - if (template.list === true && Array.isArray(changes)) { - changes = changes?.filter((x) => x !== ""); - } - - if (template.type === "dict" && Array.isArray(changes)) { - changes = convertArrayToObj(changes); - } - - if (template.type === "NestedDict") { - changes = JSON.stringify(changes); - } - - const existingTweak = tweak.current.find((element) => - element.hasOwnProperty(tw) - ); - - if (existingTweak) { - existingTweak[tw][template["name"]!] = changes as string; - - if (existingTweak[tw][template["name"]!] == template.value) { - tweak.current.forEach((element) => { - if (element[tw] && Object.keys(element[tw])?.length === 0) { - tweak.current = tweak.current.filter((obj) => { - const prop = obj[Object.keys(obj)[0]].prop; - return prop !== undefined && prop !== null && prop !== ""; - }); - } - }); - } - } else { - const newTweak = { - [tw]: { - [template["name"]!]: changes, - }, - } as uniqueTweakType; - tweak.current.push(newTweak); - } - - const pythonApiCode = getPythonApiCode(flow, autoLogin, tweak.current); - const curl_code = getCurlCode(flow, autoLogin, tweak.current); - const pythonCode = getPythonCode(flow, tweak.current); - const widgetCode = getWidgetCode(flow, autoLogin, flowState); - - tabs![0].code = curl_code; - tabs![1].code = pythonApiCode; - tabs![2].code = pythonCode; - tabs![3].code = widgetCode; - - setTweak(tweak.current); - } - - function buildContent(value: string) { - const htmlContent = ( -
- {value != null && value != "" ? value : "None"} -
- ); - return htmlContent; - } - - function getValue( - value: string, - node: NodeType, - template: TemplateVariableType - ) { - let returnValue = value ?? ""; - - if (getTweak.length > 0) { - for (const obj of getTweak) { - Object.keys(obj).forEach((key) => { - const value = obj[key]; - if (key == node["id"]) { - Object.keys(value).forEach((key) => { - if (key == template["name"]) { - returnValue = value[key]; - } - }); - } - }); - } - } else { - return value ?? ""; - } - return returnValue; - } - - return ( - - {children} - - API - - - - - - ); - } -); - -export default ApiModal; diff --git a/src/frontend/src/modals/apiModal/utils/build-content.tsx b/src/frontend/src/modals/apiModal/utils/build-content.tsx new file mode 100644 index 000000000..80468fe71 --- /dev/null +++ b/src/frontend/src/modals/apiModal/utils/build-content.tsx @@ -0,0 +1,10 @@ +export function buildContent(value: string) { + const htmlContent = ( +
+ {value != null && value != "" ? value : "None"} +
+ ); + return htmlContent; +} + +export default buildContent; diff --git a/src/frontend/src/modals/apiModal/utils/build-tweaks.ts b/src/frontend/src/modals/apiModal/utils/build-tweaks.ts new file mode 100644 index 000000000..82d35302a --- /dev/null +++ b/src/frontend/src/modals/apiModal/utils/build-tweaks.ts @@ -0,0 +1,8 @@ +import { FlowType } from "../../../types/flow"; + +export function buildTweaks(flow: FlowType) { + return flow.data!.nodes.reduce((acc, node) => { + acc[node.data.id] = {}; + return acc; + }, {}); +} diff --git a/src/frontend/src/modals/apiModal/utils/check-can-build-tweak-object.ts b/src/frontend/src/modals/apiModal/utils/check-can-build-tweak-object.ts new file mode 100644 index 000000000..5a859cae8 --- /dev/null +++ b/src/frontend/src/modals/apiModal/utils/check-can-build-tweak-object.ts @@ -0,0 +1,13 @@ +import { LANGFLOW_SUPPORTED_TYPES } from "../../../constants/constants"; + +export const checkCanBuildTweakObject = (element, templateField) => { + return ( + element.data.node.template[templateField] && + templateField.charAt(0) !== "_" && + element.data.node.template[templateField].show && + LANGFLOW_SUPPORTED_TYPES.has( + element.data.node.template[templateField].type, + ) && + templateField !== "code" + ); +}; diff --git a/src/frontend/src/modals/apiModal/utils/get-changes-types.ts b/src/frontend/src/modals/apiModal/utils/get-changes-types.ts new file mode 100644 index 000000000..e8e912ff3 --- /dev/null +++ b/src/frontend/src/modals/apiModal/utils/get-changes-types.ts @@ -0,0 +1,26 @@ +import { TemplateVariableType } from "../../../types/api"; +import { convertArrayToObj } from "../../../utils/reactflowUtils"; + +export const getChangesType = ( + changes: string | string[] | boolean | number | Object[] | Object, + template: TemplateVariableType, +) => { + if (typeof changes === "string" && template.type === "float") { + changes = parseFloat(changes); + } + if (typeof changes === "string" && template.type === "int") { + changes = parseInt(changes); + } + if (template.list === true && Array.isArray(changes)) { + changes = changes?.filter((x) => x !== ""); + } + + if (template.type === "dict" && Array.isArray(changes)) { + changes = convertArrayToObj(changes); + } + + if (template.type === "NestedDict") { + changes = JSON.stringify(changes); + } + return changes; +}; diff --git a/src/frontend/src/modals/apiModal/utils/get-nodes-with-default-value.ts b/src/frontend/src/modals/apiModal/utils/get-nodes-with-default-value.ts new file mode 100644 index 000000000..fb30a57a1 --- /dev/null +++ b/src/frontend/src/modals/apiModal/utils/get-nodes-with-default-value.ts @@ -0,0 +1,28 @@ +import { LANGFLOW_SUPPORTED_TYPES } from "../../../constants/constants"; + +export const getNodesWithDefaultValue = (flow) => { + let arrNodesWithValues: string[] = []; + + flow["data"]!["nodes"].forEach((node) => { + if (!node["data"]["node"]["template"]) { + return; + } + Object.keys(node["data"]["node"]["template"]) + .filter( + (templateField) => + templateField.charAt(0) !== "_" && + node.data.node.template[templateField].show && + LANGFLOW_SUPPORTED_TYPES.has( + node.data.node.template[templateField].type, + ), + ) + .map((n, i) => { + arrNodesWithValues.push(node["id"]); + }); + }); + + const tweaksListFiltered = arrNodesWithValues.filter((value, index, self) => { + return self.indexOf(value) === index; + }); + return tweaksListFiltered; +}; diff --git a/src/frontend/src/modals/apiModal/utils/get-value.ts b/src/frontend/src/modals/apiModal/utils/get-value.ts new file mode 100644 index 000000000..df8e5bdde --- /dev/null +++ b/src/frontend/src/modals/apiModal/utils/get-value.ts @@ -0,0 +1,29 @@ +import { TemplateVariableType } from "../../../types/api"; +import { NodeType } from "../../../types/flow"; + +export const getValue = ( + value: string, + node: NodeType, + template: TemplateVariableType, + tweak: Object[], +) => { + let returnValue = value ?? ""; + + if (tweak.length > 0) { + for (const obj of tweak) { + Object.keys(obj).forEach((key) => { + const value = obj[key]; + if (key == node["id"]) { + Object.keys(value).forEach((key) => { + if (key == template["name"]) { + returnValue = value[key]; + } + }); + } + }); + } + } else { + return value ?? ""; + } + return returnValue; +}; diff --git a/src/frontend/src/modals/apiModal/views/index.tsx b/src/frontend/src/modals/apiModal/views/index.tsx new file mode 100644 index 000000000..6aebb7618 --- /dev/null +++ b/src/frontend/src/modals/apiModal/views/index.tsx @@ -0,0 +1,211 @@ +import "ace-builds/src-noconflict/ext-language_tools"; +import "ace-builds/src-noconflict/mode-python"; +import "ace-builds/src-noconflict/theme-github"; +import "ace-builds/src-noconflict/theme-twilight"; +import { ReactNode, forwardRef, useContext, useEffect, useState } from "react"; +// import "ace-builds/webpack-resolver"; +import { cloneDeep } from "lodash"; +import CodeTabsComponent from "../../../components/codeTabsComponent"; +import IconComponent from "../../../components/genericIconComponent"; +import { EXPORT_CODE_DIALOG } from "../../../constants/constants"; +import { AuthContext } from "../../../contexts/authContext"; +import { useTweaksStore } from "../../../stores/tweaksStore"; +import { TemplateVariableType } from "../../../types/api"; +import { uniqueTweakType } from "../../../types/components"; +import { FlowType } from "../../../types/flow/index"; +import { + getCurlCode, + getPythonApiCode, + getPythonCode, + getWidgetCode, + tabsArray, +} from "../../../utils/utils"; +import BaseModal from "../../baseModal"; +import { buildContent } from "../utils/build-content"; +import { buildTweaks } from "../utils/build-tweaks"; +import { checkCanBuildTweakObject } from "../utils/check-can-build-tweak-object"; +import { getChangesType } from "../utils/get-changes-types"; +import { getNodesWithDefaultValue } from "../utils/get-nodes-with-default-value"; +import { getValue } from "../utils/get-value"; + +const ApiModal = forwardRef( + ( + { + flow, + children, + }: { + flow: FlowType; + children: ReactNode; + }, + ref, + ) => { + const tweak = useTweaksStore((state) => state.tweak); + const addTweaks = useTweaksStore((state) => state.setTweak); + const setTweaksList = useTweaksStore((state) => state.setTweaksList); + const tweaksList = useTweaksStore((state) => state.tweaksList); + + const [activeTweaks, setActiveTweaks] = useState(false); + const { autoLogin } = useContext(AuthContext); + const [open, setOpen] = useState(false); + const [activeTab, setActiveTab] = useState("0"); + const pythonApiCode = getPythonApiCode(flow?.id, autoLogin, tweak); + const curl_code = getCurlCode(flow?.id, autoLogin, tweak); + const pythonCode = getPythonCode(flow?.name, tweak); + const widgetCode = getWidgetCode(flow?.id, flow?.name, autoLogin); + const tweaksCode = buildTweaks(flow); + const codesArray = [ + curl_code, + pythonApiCode, + pythonCode, + widgetCode, + pythonCode, + ]; + const [tabs, setTabs] = useState(tabsArray(codesArray, 0)); + + const canShowTweaks = + flow && + flow["data"] && + flow["data"]!["nodes"] && + tweak && + tweak?.length > 0 && + activeTweaks === true; + + const buildTweaksInitialState = () => { + const newTweak: any = []; + const t = buildTweaks(flow); + newTweak.push(t); + addTweaks(newTweak); + addCodes(newTweak); + }; + + useEffect(() => { + if (flow["data"]!["nodes"].length == 0) { + addTweaks([]); + setTweaksList([]); + } else { + buildTweaksInitialState(); + } + + filterNodes(); + + if (Object.keys(tweaksCode).length > 0) { + setActiveTab("0"); + setTabs(tabsArray(codesArray, 1)); + } else { + setTabs(tabsArray(codesArray, 1)); + } + }, [flow["data"]!["nodes"], open]); + + useEffect(() => { + if (canShowTweaks) { + const nodes = flow["data"]!["nodes"]; + nodes.forEach((element) => { + const nodeId = element["id"]; + const template = element["data"]["node"]["template"]; + + Object.keys(template).forEach((templateField) => { + if (checkCanBuildTweakObject(element, templateField)) { + buildTweakObject( + nodeId, + element.data.node.template[templateField].value, + element.data.node.template[templateField], + ); + } + }); + }); + } else { + buildTweaksInitialState(); + } + }, [activeTweaks]); + + const filterNodes = () => { + setTweaksList(getNodesWithDefaultValue(flow)); + }; + + async function buildTweakObject( + tw: string, + changes: string | string[] | boolean | number | Object[] | Object, + template: TemplateVariableType, + ) { + changes = getChangesType(changes, template); + + const existingTweak = tweak.find((element) => element.hasOwnProperty(tw)); + + if (existingTweak) { + existingTweak[tw][template["name"]!] = changes as string; + + if (existingTweak[tw][template["name"]!] == template.value) { + tweak.forEach((element) => { + if (element[tw] && Object.keys(element[tw])?.length === 0) { + const filteredTweaks = tweak.filter((obj) => { + const prop = obj[Object.keys(obj)[0]].prop; + return prop !== undefined && prop !== null && prop !== ""; + }); + addTweaks(filteredTweaks); + } + }); + } + } else { + const newTweak = { + [tw]: { + [template["name"]!]: changes, + }, + } as uniqueTweakType; + tweak.push(newTweak); + } + + if (tweak && tweak.length > 0) { + const cloneTweak = cloneDeep(tweak); + addCodes(cloneTweak); + addTweaks(cloneTweak); + } + } + + const addCodes = (cloneTweak) => { + const pythonApiCode = getPythonApiCode(flow?.id, autoLogin, cloneTweak); + const curl_code = getCurlCode(flow?.id, autoLogin, cloneTweak); + const pythonCode = getPythonCode(flow?.name, cloneTweak); + const widgetCode = getWidgetCode(flow?.id, flow?.name, autoLogin); + + if (tabs && tabs?.length > 0) { + tabs![0].code = curl_code; + tabs![1].code = pythonApiCode; + tabs![2].code = pythonCode; + tabs![3].code = widgetCode; + } + }; + + return ( + + {children} + + API + + + + + + ); + }, +); + +export default ApiModal; diff --git a/src/frontend/src/stores/tweaksStore.ts b/src/frontend/src/stores/tweaksStore.ts new file mode 100644 index 000000000..cbf035b6f --- /dev/null +++ b/src/frontend/src/stores/tweaksStore.ts @@ -0,0 +1,9 @@ +import { create } from "zustand"; +import { TweaksStoreType } from "../types/zustand/tweaks"; + +export const useTweaksStore = create((set, get) => ({ + tweak: [], + setTweak: (tweak) => set({ tweak }), + tweaksList: [], + setTweaksList: (tweaksList) => set({ tweaksList }), +})); diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index 2a3ee29b7..d883b7014 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -511,7 +511,7 @@ export type nodeToolbarPropsType = { updateNodeCode?: ( newNodeClass: APIClassType, code: string, - name: string + name: string, ) => void; setShowState: (show: boolean | SetStateAction) => void; isOutdated?: boolean; @@ -561,7 +561,7 @@ export type chatMessagePropsType = { updateChat: ( chat: ChatMessageType, message: string, - stream_url?: string + stream_url?: string, ) => void; }; @@ -646,20 +646,23 @@ export type codeTabsPropsType = { setActiveTab: (value: string) => void; isMessage?: boolean; tweaks?: { - tweak?: { current: tweakType }; - tweaksList?: { current: Array }; + tweak?: tweakType; + tweaksList?: Array; buildContent?: (value: string) => ReactNode; getValue?: ( value: string, node: NodeType, - template: TemplateVariableType + template: TemplateVariableType, + tweak: tweakType, ) => string; buildTweakObject?: ( tw: string, changes: string | string[] | boolean | number | Object[] | Object, - template: TemplateVariableType - ) => string | void; + template: TemplateVariableType, + ) => Promise; }; + activeTweaks?: boolean; + setActiveTweaks?: (value: boolean) => void; }; export type crashComponentPropsType = { diff --git a/src/frontend/src/types/zustand/tweaks/index.ts b/src/frontend/src/types/zustand/tweaks/index.ts new file mode 100644 index 000000000..3eb0a5f73 --- /dev/null +++ b/src/frontend/src/types/zustand/tweaks/index.ts @@ -0,0 +1,8 @@ +import { tweakType } from "../../components"; + +export type TweaksStoreType = { + tweak: tweakType; + setTweak: (tweak: tweakType) => void; + tweaksList: string[]; + setTweaksList: (tweaksList: string[]) => void; +}; diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index deb470f80..83772ee0d 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -102,18 +102,18 @@ export function unselectAllNodes({ updateNodes, data }: unselectAllNodesType) { export function isValidConnection( { source, target, sourceHandle, targetHandle }: Connection, nodes: Node[], - edges: Edge[] + edges: Edge[], ) { const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!); const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!); if ( targetHandleObject.inputTypes?.some( - (n) => n === sourceHandleObject.dataType + (n) => n === sourceHandleObject.dataType, ) || sourceHandleObject.baseClasses.some( (t) => targetHandleObject.inputTypes?.some((n) => n === t) || - t === targetHandleObject.type + t === targetHandleObject.type, ) ) { let targetNode = nodes.find((node) => node.id === target!)?.data?.node; @@ -146,7 +146,7 @@ export function removeApiKeys(flow: FlowType): FlowType { export function updateTemplate( reference: APITemplateType, - objectToUpdate: APITemplateType + objectToUpdate: APITemplateType, ): APITemplateType { let clonedObject: APITemplateType = cloneDeep(reference); @@ -206,7 +206,7 @@ export const processDataFromFlow = (flow: FlowType, refreshIds = true) => { export function updateIds( { edges, nodes }: { edges: Edge[]; nodes: Node[] }, - selection?: { edges: Edge[]; nodes: Node[] } + selection?: { edges: Edge[]; nodes: Node[] }, ) { let idsMap = {}; const selectionIds = selection?.nodes.map((n) => n.id); @@ -234,7 +234,7 @@ export function updateIds( edge.source = idsMap[edge.source]; edge.target = idsMap[edge.target]; const sourceHandleObject: sourceHandleType = scapeJSONParse( - edge.sourceHandle! + edge.sourceHandle!, ); edge.sourceHandle = scapedJSONStringfy({ ...sourceHandleObject, @@ -244,7 +244,7 @@ export function updateIds( edge.data.sourceHandle.id = edge.source; } const targetHandleObject: targetHandleType = scapeJSONParse( - edge.targetHandle! + edge.targetHandle!, ); edge.targetHandle = scapedJSONStringfy({ ...targetHandleObject, @@ -264,13 +264,6 @@ export function updateIds( return idsMap; } -export function buildTweaks(flow: FlowType) { - return flow.data!.nodes.reduce((acc, node) => { - acc[node.data.id] = {}; - return acc; - }, {}); -} - export function validateNode(node: NodeType, edges: Edge[]): Array { if (!node.data?.node?.template || !Object.keys(node.data.node.template)) { return [ @@ -297,11 +290,11 @@ export function validateNode(node: NodeType, edges: Edge[]): Array { (scapeJSONParse(edge.targetHandle!) as targetHandleType).fieldName === t && (scapeJSONParse(edge.targetHandle!) as targetHandleType).id === - node.id + node.id, ) ) { errors.push( - `${displayName || type} is missing ${getFieldTitle(template, t)}.` + `${displayName || type} is missing ${getFieldTitle(template, t)}.`, ); } else if ( template[t].type === "dict" && @@ -315,15 +308,15 @@ export function validateNode(node: NodeType, edges: Edge[]): Array { errors.push( `${displayName || type} (${getFieldTitle( template, - t - )}) contains duplicate keys with the same values.` + t, + )}) contains duplicate keys with the same values.`, ); if (hasEmptyKey(template[t].value)) errors.push( `${displayName || type} (${getFieldTitle( template, - t - )}) field must not be empty.` + t, + )}) field must not be empty.`, ); } return errors; @@ -332,7 +325,7 @@ export function validateNode(node: NodeType, edges: Edge[]): Array { export function validateNodes( nodes: Node[], - edges: Edge[] + edges: Edge[], ): // this returns an array of tuples with the node id and the errors Array<{ id: string; errors: Array }> { if (nodes.length === 0) { @@ -353,7 +346,7 @@ export function updateEdges(edges: Edge[]) { if (edges) edges.forEach((edge) => { const targetHandleObject: targetHandleType = scapeJSONParse( - edge.targetHandle! + edge.targetHandle!, ); edge.className = "stroke-gray-900 stroke-connection"; }); @@ -420,7 +413,7 @@ export function handleKeyDown( | React.KeyboardEvent | React.KeyboardEvent, inputValue: string | string[] | null, - block: string + block: string, ) { //condition to fix bug control+backspace on Windows/Linux if ( @@ -445,7 +438,7 @@ export function handleKeyDown( } export function handleOnlyIntegerInput( - event: React.KeyboardEvent + event: React.KeyboardEvent, ) { if ( event.key === "." || @@ -461,7 +454,7 @@ export function handleOnlyIntegerInput( export function getConnectedNodes( edge: Edge, - nodes: Array + nodes: Array, ): Array { const sourceId = edge.source; const targetId = edge.target; @@ -561,7 +554,7 @@ export function checkOldEdgesHandles(edges: Edge[]): boolean { !edge.sourceHandle || !edge.targetHandle || !edge.sourceHandle.includes("{") || - !edge.targetHandle.includes("{") + !edge.targetHandle.includes("{"), ); } @@ -584,7 +577,7 @@ export function customStringify(obj: any): string { const keys = Object.keys(obj).sort(); const keyValuePairs = keys.map( - (key) => `"${key}":${customStringify(obj[key])}` + (key) => `"${key}":${customStringify(obj[key])}`, ); return `{${keyValuePairs.join(",")}}`; } @@ -613,7 +606,7 @@ export function getHandleId( source: string, sourceHandle: string, target: string, - targetHandle: string + targetHandle: string, ) { return ( "reactflow__edge-" + source + sourceHandle + "-" + target + targetHandle @@ -624,7 +617,7 @@ export function generateFlow( selection: OnSelectionChangeParams, nodes: Node[], edges: Edge[], - name: string + name: string, ): generateFlowType { const newFlowData = { nodes, edges, viewport: { zoom: 1, x: 0, y: 0 } }; const uid = new ShortUniqueId({ length: 5 }); @@ -633,7 +626,7 @@ export function generateFlow( newFlowData.edges = selection.edges.filter( (edge) => selection.nodes.some((node) => node.id === edge.target) && - selection.nodes.some((node) => node.id === edge.source) + selection.nodes.some((node) => node.id === edge.source), ); newFlowData.nodes = selection.nodes; @@ -654,7 +647,7 @@ export function generateFlow( (edge) => (selection.nodes.some((node) => node.id === edge.target) || selection.nodes.some((node) => node.id === edge.source)) && - newFlowData.edges.every((e) => e.id !== edge.id) + newFlowData.edges.every((e) => e.id !== edge.id), ), }; } @@ -665,13 +658,13 @@ export function reconnectEdges(groupNode: NodeType, excludedEdges: Edge[]) { const { nodes, edges } = groupNode.data.node!.flow!.data!; const lastNode = findLastNode(groupNode.data.node!.flow!.data!); newEdges = newEdges.filter( - (e) => !(nodes.some((n) => n.id === e.source) && e.source !== lastNode?.id) + (e) => !(nodes.some((n) => n.id === e.source) && e.source !== lastNode?.id), ); newEdges.forEach((edge) => { if (lastNode && edge.source === lastNode.id) { edge.source = groupNode.id; let newSourceHandle: sourceHandleType = scapeJSONParse( - edge.sourceHandle! + edge.sourceHandle!, ); newSourceHandle.id = groupNode.id; edge.sourceHandle = scapedJSONStringfy(newSourceHandle); @@ -698,7 +691,7 @@ export function reconnectEdges(groupNode: NodeType, excludedEdges: Edge[]) { export function filterFlow( selection: OnSelectionChangeParams, setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void, - setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void + setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void, ) { setNodes((nodes) => nodes.filter((node) => !selection.nodes.includes(node))); setEdges((edges) => edges.filter((edge) => !selection.edges.includes(edge))); @@ -736,7 +729,7 @@ export function updateFlowPosition(NewPosition: XYPosition, flow: FlowType) { export function concatFlows( flow: FlowType, setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void, - setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void + setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void, ) { const { nodes, edges } = flow.data!; setNodes((old) => [...old, ...nodes]); @@ -745,7 +738,7 @@ export function concatFlows( export function validateSelection( selection: OnSelectionChangeParams, - edges: Edge[] + edges: Edge[], ): Array { //add edges to selection if selection mode selected only nodes if (selection.edges.length === 0) { @@ -757,7 +750,7 @@ export function validateSelection( let nodesSet = new Set(selection.nodes.map((n) => n.id)); // then filter the edges that are connected to the nodes in the set let connectedEdges = selection.edges.filter( - (e) => nodesSet.has(e.source) && nodesSet.has(e.target) + (e) => nodesSet.has(e.source) && nodesSet.has(e.target), ); // add the edges to the selection selection.edges = connectedEdges; @@ -771,17 +764,17 @@ export function validateSelection( selection.nodes.some( (node) => isInputNode(node.data as NodeDataType) || - isOutputNode(node.data as NodeDataType) + isOutputNode(node.data as NodeDataType), ) ) { errorsArray.push( - "Please select only nodes that are not input or output nodes" + "Please select only nodes that are not input or output nodes", ); } //check if there are two or more nodes with free outputs if ( selection.nodes.filter( - (n) => !selection.edges.some((e) => e.source === n.id) + (n) => !selection.edges.some((e) => e.source === n.id), ).length > 1 ) { errorsArray.push("Please select only one node with free outputs"); @@ -792,7 +785,7 @@ export function validateSelection( selection.nodes.some( (node) => !selection.edges.some((edge) => edge.target === node.id) && - !selection.edges.some((edge) => edge.source === node.id) + !selection.edges.some((edge) => edge.source === node.id), ) ) { errorsArray.push("Please select only nodes that are connected"); @@ -849,8 +842,8 @@ export function mergeNodeTemplates({ nodeTemplate[key].display_name ? nodeTemplate[key].display_name : nodeTemplate[key].name - ? toTitleCase(nodeTemplate[key].name) - : toTitleCase(key); + ? toTitleCase(nodeTemplate[key].name) + : toTitleCase(key); } } }); @@ -861,7 +854,7 @@ function isHandleConnected( edges: Edge[], key: string, field: TemplateVariableType, - nodeId: string + nodeId: string, ) { /* this function receives a flow and a handleId and check if there is a connection with this handle @@ -877,7 +870,7 @@ function isHandleConnected( id: nodeId, proxy: { id: field.proxy!.id, field: field.proxy!.field }, inputTypes: field.input_types, - } as targetHandleType) + } as targetHandleType), ) ) { return true; @@ -892,7 +885,7 @@ function isHandleConnected( fieldName: key, id: nodeId, inputTypes: field.input_types, - } as targetHandleType) + } as targetHandleType), ) ) { return true; @@ -915,7 +908,7 @@ export function generateNodeTemplate(Flow: FlowType) { export function generateNodeFromFlow( flow: FlowType, - getNodeId: (type: string) => string + getNodeId: (type: string) => string, ): NodeType { const { nodes } = flow.data!; const outputNode = cloneDeep(findLastNode(flow.data!)); @@ -946,7 +939,7 @@ export function generateNodeFromFlow( export function connectedInputNodesOnHandle( nodeId: string, handleId: string, - { nodes, edges }: { nodes: NodeType[]; edges: Edge[] } + { nodes, edges }: { nodes: NodeType[]; edges: Edge[] }, ) { const connectedNodes: Array<{ name: string; id: string; isGroup: boolean }> = []; @@ -983,7 +976,7 @@ export function connectedInputNodesOnHandle( export function updateProxyIdsOnTemplate( template: APITemplateType, - idsMap: { [key: string]: string } + idsMap: { [key: string]: string }, ) { Object.keys(template).forEach((key) => { if (template[key].proxy && idsMap[template[key].proxy!.id]) { @@ -994,7 +987,7 @@ export function updateProxyIdsOnTemplate( export function updateEdgesIds( edges: Edge[], - idsMap: { [key: string]: string } + idsMap: { [key: string]: string }, ) { edges.forEach((edge) => { let targetHandle: targetHandleType = edge.data.targetHandle; @@ -1027,7 +1020,7 @@ export function expandGroupNode( nodes: Node[], edges: Edge[], setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void, - setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void + setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void, ) { const idsMap = updateIds(flow!.data!); updateProxyIdsOnTemplate(template, idsMap); @@ -1070,7 +1063,7 @@ export function expandGroupNode( const lastNode = cloneDeep(findLastNode(flow!.data!)); newEdge.source = lastNode!.id; let newSourceHandle: sourceHandleType = scapeJSONParse( - newEdge.sourceHandle! + newEdge.sourceHandle!, ); newSourceHandle.id = lastNode!.id; newEdge.data.sourceHandle = newSourceHandle; @@ -1124,7 +1117,7 @@ export function expandGroupNode( export function getGroupStatus( flow: FlowType, - ssData: { [key: string]: { valid: boolean; params: string } } + ssData: { [key: string]: { valid: boolean; params: string } }, ) { let status = { valid: true, params: SUCCESS_BUILD }; const { nodes } = flow.data!; @@ -1143,7 +1136,7 @@ export function getGroupStatus( export function createFlowComponent( nodeData: NodeDataType, - version: string + version: string, ): FlowType { const flowNode: FlowType = { data: { @@ -1179,7 +1172,7 @@ export function downloadNode(NodeFLow: FlowType) { export function updateComponentNameAndType( data: any, - component: NodeDataType + component: NodeDataType, ) {} export function removeFileNameFromComponents(flow: FlowType) { @@ -1253,7 +1246,7 @@ export function extractFieldsFromComponenents(data: APIObjectType) { export function downloadFlow( flow: FlowType, flowName: string, - flowDescription?: string + flowDescription?: string, ) { let clonedFlow = cloneDeep(flow); removeFileNameFromComponents(clonedFlow); @@ -1263,7 +1256,7 @@ export function downloadFlow( ...clonedFlow, name: flowName, description: flowDescription, - }) + }), )}`; // create a link element and set its properties @@ -1278,7 +1271,7 @@ export function downloadFlow( export function downloadFlows() { downloadFlowsFromDatabase().then((flows) => { const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent( - JSON.stringify(flows) + JSON.stringify(flows), )}`; // create a link element and set its properties @@ -1293,7 +1286,7 @@ export function downloadFlows() { export const createNewFlow = ( flowData: ReactFlowJsonObject, - flow: FlowType + flow: FlowType, ) => { return { description: flow?.description ?? getRandomDescription(), diff --git a/src/frontend/src/utils/utils.ts b/src/frontend/src/utils/utils.ts index 7c269d8e4..5e1786404 100644 --- a/src/frontend/src/utils/utils.ts +++ b/src/frontend/src/utils/utils.ts @@ -13,9 +13,8 @@ import { nodeGroupedObjType, tweakType, } from "../types/components"; -import { FlowType, NodeType } from "../types/flow"; +import { NodeType } from "../types/flow"; import { FlowState } from "../types/tabs"; -import { buildTweaks } from "./reactflowUtils"; export function classNames(...classes: Array): string { return classes.filter(Boolean).join(" "); @@ -323,17 +322,10 @@ export function getChatInputField(flowState?: FlowState) { * @returns {string} - The python code */ export function getPythonApiCode( - flow: FlowType, + flowId: string, isAuth: boolean, - tweak?: any[], + tweaksBuildedObject, ): string { - const flowId = flow.id; - - // create a dictionary of node ids and the values is an empty dictionary - // flow.data.nodes.forEach((node) => { - // node.data.id - // } - const tweaks = buildTweaks(flow); return `import requests from typing import Optional @@ -341,11 +333,7 @@ BASE_API_URL = "${window.location.protocol}//${window.location.host}/api/v1/run" FLOW_ID = "${flowId}" # You can tweak the flow by adding a tweaks dictionary # e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}} -TWEAKS = ${ - tweak && tweak.length > 0 - ? buildTweakObject(tweak) - : JSON.stringify(tweaks, null, 2) - } +TWEAKS = ${JSON.stringify(tweaksBuildedObject, null, 2)} def run_flow(message: str, flow_id: str, @@ -391,13 +379,10 @@ print(run_flow(message=message, flow_id=FLOW_ID, tweaks=TWEAKS${ * @returns {string} - The curl code */ export function getCurlCode( - flow: FlowType, + flowId: string, isAuth: boolean, - tweak?: any[], + tweaksBuildedObject, ): string { - const flowId = flow.id; - const tweaks = buildTweaks(flow); - return `curl -X POST \\ ${window.location.protocol}//${ window.location.host @@ -408,11 +393,7 @@ export function getCurlCode( -d '{"input_value": "message", "output_type": "chat", "input_type": "chat", - "tweaks": ${ - tweak && tweak.length > 0 - ? buildTweakObject(tweak) - : JSON.stringify(tweaks, null, 2) - }}' + "tweaks": ${JSON.stringify(tweaksBuildedObject, null, 2)}' `; } @@ -439,16 +420,9 @@ export function getOutputIds(flow) { * @param {any[]} tweak - The tweaks * @returns {string} - The python code */ -export function getPythonCode(flow: FlowType, tweak?: any[]): string { - const flowName = flow.name; - const tweaks = buildTweaks(flow); - +export function getPythonCode(flowName: string, tweaksBuildedObject): string { return `from langflow.load import run_flow_from_json -TWEAKS = ${ - tweak && tweak.length > 0 - ? buildTweakObject(tweak) - : JSON.stringify(tweaks, null, 2) - } +TWEAKS = ${JSON.stringify(tweaksBuildedObject, null, 2)} result = run_flow_from_json(flow="${flowName}.json", input_value="message", @@ -461,15 +435,10 @@ result = run_flow_from_json(flow="${flowName}.json", * @returns {string} - The widget code */ export function getWidgetCode( - flow: FlowType, + flowId: string, + flowName: string, isAuth: boolean, - flowState?: FlowState, ): string { - const flowId = flow.id; - const flowName = flow.name; - const inputs = buildInputs(); - let chat_input_field = getChatInputField(flowState); - return ` { await page.getByRole("tab", { name: "cURL" }).click(); await page.getByRole("button", { name: "Copy Code" }).click(); const handle = await page.evaluateHandle(() => - navigator.clipboard.readText() + navigator.clipboard.readText(), ); const clipboardContent = await handle.jsonValue(); const oldValue = clipboardContent; @@ -50,10 +50,78 @@ test("curl_api_generation", async ({ page, context }) => { await page.getByRole("tab", { name: "cURL" }).click(); await page.getByRole("button", { name: "Copy Code" }).click(); const handle2 = await page.evaluateHandle(() => - navigator.clipboard.readText() + navigator.clipboard.readText(), ); const clipboardContent2 = await handle2.jsonValue(); const newValue = clipboardContent2; expect(oldValue).not.toBe(newValue); expect(clipboardContent2.length).toBeGreaterThan(clipboardContent.length); }); + +test("check if tweaks are updating when someothing on the flow changes", async ({ + page, +}) => { + await page.goto("/"); + await page.waitForTimeout(2000); + + let modalCount = 0; + try { + const modalTitleElement = await page?.getByTestId("modal-title"); + if (modalTitleElement) { + modalCount = await modalTitleElement.count(); + } + } catch (error) { + modalCount = 0; + } + + while (modalCount === 0) { + await page.locator('//*[@id="new-project-btn"]').click(); + await page.waitForTimeout(5000); + modalCount = await page.getByTestId("modal-title")?.count(); + } + await page.waitForTimeout(1000); + + await page.getByTestId("blank-flow").click(); + await page.waitForTimeout(1000); + await page.getByTestId("extended-disclosure").click(); + await page.getByPlaceholder("Search").click(); + await page.getByPlaceholder("Search").fill("Chroma"); + + await page.waitForTimeout(1000); + + await page + .getByTestId("vectorstoresChroma") + .dragTo(page.locator('//*[@id="react-flow-id"]')); + await page.mouse.up(); + await page.mouse.down(); + await page.getByTitle("fit view").click(); + await page.getByTitle("zoom out").click(); + await page.getByTitle("zoom out").click(); + await page.getByTitle("zoom out").click(); + await page.getByTestId("input-collection_name").click(); + await page + .getByTestId("input-collection_name") + .fill("collection_name_test_123123123!@#$&*(&%$@"); + + await page.getByTestId("input-index_directory").click(); + await page + .getByTestId("input-index_directory") + .fill("index_directory_123123123!@#$&*(&%$@"); + + await page.getByText("API", { exact: true }).first().click(); + + await page.getByText("Tweaks").nth(1).click(); + + await page.getByText("collection_name_test_123123123!@#$&*(&%$@").isVisible(); + await page.getByText("index_directory_123123123!@#$&*(&%$@").isVisible(); + + await page.getByText("Python API", { exact: true }).click(); + + await page.getByText("collection_name_test_123123123!@#$&*(&%$@").isVisible(); + await page.getByText("index_directory_123123123!@#$&*(&%$@").isVisible(); + + await page.getByText("Python Code", { exact: true }).click(); + + await page.getByText("collection_name_test_123123123!@#$&*(&%$@").isVisible(); + await page.getByText("index_directory_123123123!@#$&*(&%$@").isVisible(); +});