Migrate Tweaks Module to Zustand Library and Add All Flows (#1847)

* ✨ (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
This commit is contained in:
Cristhian Zanforlin Lousa 2024-05-10 12:30:45 -03:00 • committed by GitHub
commit c69f950cba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 575 additions and 435 deletions

View file

@ -15,17 +15,16 @@ export default function AccordionComponent({
sideBar, sideBar,
}: AccordionComponentType): JSX.Element { }: AccordionComponentType): JSX.Element {
const [value, setValue] = useState( const [value, setValue] = useState(
open.length === 0 ? "" : getOpenAccordion() open.length === 0 ? "" : getOpenAccordion(),
); );
function getOpenAccordion(): string { function getOpenAccordion(): string {
let value = ""; let value = "";
open.forEach((el) => { open.forEach((el) => {
if (el == trigger) { if (el == keyValue) {
value = trigger; value = keyValue;
} }
}); });
return value; return value;
} }

View file

@ -1,7 +1,7 @@
import { Transition } from "@headlessui/react"; import { Transition } from "@headlessui/react";
import { useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import IOModal from "../../modals/IOModal"; import IOModal from "../../modals/IOModal";
import ApiModal from "../../modals/apiModal"; import ApiModal from "../../modals/apiModal/views";
import ShareModal from "../../modals/shareModal"; import ShareModal from "../../modals/shareModal";
import useFlowStore from "../../stores/flowStore"; import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../stores/flowsManagerStore";

View file

@ -41,6 +41,8 @@ import IconComponent from "../genericIconComponent";
import InputComponent from "../inputComponent"; import InputComponent from "../inputComponent";
import KeypairListComponent from "../keypairListComponent"; import KeypairListComponent from "../keypairListComponent";
import ShadTooltip from "../shadTooltipComponent"; import ShadTooltip from "../shadTooltipComponent";
import { Label } from "../ui/label";
import { Switch } from "../ui/switch";
export default function CodeTabsComponent({ export default function CodeTabsComponent({
flow, flow,
@ -49,14 +51,13 @@ export default function CodeTabsComponent({
setActiveTab, setActiveTab,
isMessage, isMessage,
tweaks, tweaks,
setActiveTweaks,
activeTweaks,
}: codeTabsPropsType) { }: codeTabsPropsType) {
const [isCopied, setIsCopied] = useState<Boolean>(false); const [isCopied, setIsCopied] = useState<Boolean>(false);
const [data, setData] = useState(flow ? flow["data"]!["nodes"] : null); const [data, setData] = useState(flow ? flow["data"]!["nodes"] : null);
const [openAccordion, setOpenAccordion] = useState<string[]>([]);
const dark = useDarkStore((state) => state.dark); const dark = useDarkStore((state) => state.dark);
const unselectAll = useFlowStore((state) => state.unselectAll); const unselectAll = useFlowStore((state) => state.unselectAll);
const setNode = useFlowStore((state) => state.setNode);
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false); const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
useEffect(() => { useEffect(() => {
@ -107,21 +108,6 @@ export default function CodeTabsComponent({
URL.revokeObjectURL(url); 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 ( return (
<Tabs <Tabs
value={activeTab} value={activeTab}
@ -132,9 +118,6 @@ export default function CodeTabsComponent({
} }
onValueChange={(value) => { onValueChange={(value) => {
setActiveTab(value); setActiveTab(value);
if (value === "3") {
openAccordions();
}
}} }}
> >
<div className="api-modal-tablist-div"> <div className="api-modal-tablist-div">
@ -155,27 +138,58 @@ export default function CodeTabsComponent({
) : ( ) : (
<div></div> <div></div>
)} )}
{Number(activeTab) < 4 && (
<div className="float-right mx-1 mb-1 mt-2 flex gap-2"> <div className="float-right mx-1 mb-1 mt-2 flex gap-2">
<button <div
className="flex items-center gap-1.5 rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300" className={
onClick={copyToClipboard} Number(activeTab) > 2
? "hidden"
: "relative top-[2.5px] flex gap-2"
}
>
<Switch
style={{
transform: `scaleX(${0.7}) scaleY(${0.7})`,
}}
id="tweaks-switch"
onCheckedChange={setActiveTweaks}
autoFocus={false}
/>
<Label
className={
"relative right-1 top-[4px] text-xs font-medium text-gray-500 dark:text-gray-300 " +
(activeTweaks
? "font-bold text-black dark:text-white"
: "font-medium")
}
htmlFor="tweaks-switch"
> >
{isCopied ? ( Tweaks
<IconComponent name="Check" className="h-4 w-4" /> </Label>
) : (
<IconComponent name="Clipboard" className="h-4 w-4" />
)}
{isCopied ? "Copied!" : "Copy Code"}
</button>
<button
className="flex items-center gap-1.5 rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"
onClick={downloadAsFile}
>
<IconComponent name="Download" className="h-5 w-5" />
</button>
</div> </div>
)}
{Number(activeTab) < 4 && (
<>
<button
className="flex items-center gap-1.5 rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"
onClick={copyToClipboard}
>
{isCopied ? (
<IconComponent name="Check" className="h-4 w-4" />
) : (
<IconComponent name="Clipboard" className="h-4 w-4" />
)}
{isCopied ? "Copied!" : "Copy Code"}
</button>
<button
className="flex items-center gap-1.5 rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"
onClick={downloadAsFile}
>
<IconComponent name="Download" className="h-5 w-5" />
</button>
</>
)}
</div>
</div> </div>
{tabs.map((tab, idx) => ( {tabs.map((tab, idx) => (
@ -205,14 +219,12 @@ export default function CodeTabsComponent({
<div className="api-modal-according-display"> <div className="api-modal-according-display">
<div <div
className={classNames( className={classNames(
"h-[70vh] w-full overflow-y-auto overflow-x-hidden rounded-lg bg-muted custom-scroll" "h-[70vh] w-full overflow-y-auto overflow-x-hidden rounded-lg bg-muted custom-scroll",
)} )}
> >
{data?.map((node: any, i) => ( {data?.map((node: any, i) => (
<div className="px-3" key={i}> <div className="px-3" key={i}>
{tweaks?.tweaksList!.current.includes( {tweaks?.tweaksList!.includes(node["data"]["id"]) && (
node["data"]["id"]
) && (
<AccordionComponent <AccordionComponent
trigger={ trigger={
<ShadTooltip <ShadTooltip
@ -223,7 +235,6 @@ export default function CodeTabsComponent({
<div>{node["data"]["node"]["display_name"]}</div> <div>{node["data"]["node"]["display_name"]}</div>
</ShadTooltip> </ShadTooltip>
} }
open={openAccordion}
keyValue={node["data"]["id"]} keyValue={node["data"]["id"]}
> >
<div className="api-modal-table-arrangement"> <div className="api-modal-table-arrangement">
@ -247,8 +258,8 @@ export default function CodeTabsComponent({
.show && .show &&
LANGFLOW_SUPPORTED_TYPES.has( LANGFLOW_SUPPORTED_TYPES.has(
node.data.node.template[templateField] node.data.node.template[templateField]
.type .type,
) ),
) )
.map((templateField, indx) => { .map((templateField, indx) => {
return ( return (
@ -298,12 +309,12 @@ export default function CodeTabsComponent({
].value = target; ].value = target;
return newInputList; return newInputList;
}); });
tweaks.buildTweakObject!( tweaks?.buildTweakObject!(
node["data"]["id"], node["data"]["id"],
target, target,
node.data.node.template[ node.data.node.template[
templateField templateField
] ],
); );
}} }}
/> />
@ -339,13 +350,13 @@ export default function CodeTabsComponent({
].value = target; ].value = target;
return newInputList; return newInputList;
}); });
tweaks.buildTweakObject!( tweaks?.buildTweakObject!(
node["data"]["id"], node["data"]["id"],
target, target,
node.data.node node.data.node
.template[ .template[
templateField templateField
] ],
); );
}} }}
/> />
@ -383,12 +394,12 @@ export default function CodeTabsComponent({
].value = target; ].value = target;
return newInputList; return newInputList;
}); });
tweaks.buildTweakObject!( tweaks?.buildTweakObject!(
node["data"]["id"], node["data"]["id"],
target, target,
node.data.node.template[ node.data.node.template[
templateField templateField
] ],
); );
}} }}
/> />
@ -416,12 +427,12 @@ export default function CodeTabsComponent({
].value = e; ].value = e;
return newInputList; return newInputList;
}); });
tweaks.buildTweakObject!( tweaks?.buildTweakObject!(
node["data"]["id"], node["data"]["id"],
e, e,
node.data.node.template[ node.data.node.template[
templateField templateField
] ],
); );
}} }}
size="small" size="small"
@ -447,7 +458,7 @@ export default function CodeTabsComponent({
].fileTypes ].fileTypes
} }
onFileChange={( onFileChange={(
value: any value: any,
) => { ) => {
node.data.node.template[ node.data.node.template[
templateField templateField
@ -490,12 +501,12 @@ export default function CodeTabsComponent({
].value = target; ].value = target;
return newInputList; return newInputList;
}); });
tweaks.buildTweakObject!( tweaks?.buildTweakObject!(
node["data"]["id"], node["data"]["id"],
target, target,
node.data.node.template[ node.data.node.template[
templateField templateField
] ],
); );
}} }}
/> />
@ -525,12 +536,12 @@ export default function CodeTabsComponent({
].value = target; ].value = target;
return newInputList; return newInputList;
}); });
tweaks.buildTweakObject!( tweaks?.buildTweakObject!(
node["data"]["id"], node["data"]["id"],
target, target,
node.data.node.template[ node.data.node.template[
templateField templateField
] ],
); );
}} }}
value={ value={
@ -582,12 +593,12 @@ export default function CodeTabsComponent({
].value = target; ].value = target;
return newInputList; return newInputList;
}); });
tweaks.buildTweakObject!( tweaks?.buildTweakObject!(
node["data"]["id"], node["data"]["id"],
target, target,
node.data.node.template[ node.data.node.template[
templateField templateField
] ],
); );
}} }}
/> />
@ -623,12 +634,12 @@ export default function CodeTabsComponent({
].value = target; ].value = target;
return newInputList; return newInputList;
}); });
tweaks.buildTweakObject!( tweaks?.buildTweakObject!(
node["data"]["id"], node["data"]["id"],
target, target,
node.data.node.template[ node.data.node.template[
templateField templateField
] ],
); );
}} }}
/> />
@ -664,12 +675,12 @@ export default function CodeTabsComponent({
].value = target; ].value = target;
return newInputList; return newInputList;
}); });
tweaks.buildTweakObject!( tweaks?.buildTweakObject!(
node["data"]["id"], node["data"]["id"],
target, target,
node.data.node.template[ node.data.node.template[
templateField templateField
] ],
); );
}} }}
/> />
@ -693,7 +704,7 @@ export default function CodeTabsComponent({
node.data.node! node.data.node!
.template[ .template[
templateField templateField
].value ].value,
) )
} }
duplicateKey={ duplicateKey={
@ -702,15 +713,15 @@ export default function CodeTabsComponent({
onChange={(target) => { onChange={(target) => {
const valueToNumbers = const valueToNumbers =
convertValuesToNumbers( convertValuesToNumbers(
target target,
); );
node.data.node!.template[ node.data.node!.template[
templateField templateField
].value = valueToNumbers; ].value = valueToNumbers;
setErrorDuplicateKey( setErrorDuplicateKey(
hasDuplicateKeys( hasDuplicateKeys(
valueToNumbers valueToNumbers,
) ),
); );
setData((old) => { setData((old) => {
let newInputList = let newInputList =
@ -722,12 +733,12 @@ export default function CodeTabsComponent({
].value = target; ].value = target;
return newInputList; return newInputList;
}); });
tweaks.buildTweakObject!( tweaks?.buildTweakObject!(
node["data"]["id"], node["data"]["id"],
target, target,
node.data.node.template[ node.data.node.template[
templateField templateField
] ],
); );
}} }}
isList={ isList={
@ -767,12 +778,12 @@ export default function CodeTabsComponent({
].value = target; ].value = target;
return newInputList; return newInputList;
}); });
tweaks.buildTweakObject!( tweaks?.buildTweakObject!(
node["data"]["id"], node["data"]["id"],
target, target,
node.data.node.template[ node.data.node.template[
templateField templateField
] ],
); );
}} }}
/> />
@ -795,7 +806,7 @@ export default function CodeTabsComponent({
</AccordionComponent> </AccordionComponent>
)} )}
{tweaks?.tweaksList!.current.length === 0 && ( {tweaks?.tweaksList!.length === 0 && (
<> <>
<div className="pt-3"> <div className="pt-3">
No tweaks are available for this flow. No tweaks are available for this flow.

View file

@ -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<tweakType>([]);
const tweaksList = useRef<string[]>([]);
const [getTweak, setTweak] = useState<tweakType>([]);
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 = (
<div className="w-[200px]">
<span>{value != null && value != "" ? value : "None"}</span>
</div>
);
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 (
<BaseModal open={open} setOpen={setOpen}>
<BaseModal.Trigger asChild>{children}</BaseModal.Trigger>
<BaseModal.Header description={EXPORT_CODE_DIALOG}>
<span className="pr-2">API</span>
<IconComponent
name="Code2"
className="h-6 w-6 pl-1 text-gray-800 dark:text-white"
aria-hidden="true"
/>
</BaseModal.Header>
<BaseModal.Content>
<CodeTabsComponent
flow={flow}
tabs={tabs!}
activeTab={activeTab}
setActiveTab={setActiveTab}
tweaks={{
tweak,
tweaksList,
buildContent,
buildTweakObject,
getValue,
}}
/>
</BaseModal.Content>
</BaseModal>
);
}
);
export default ApiModal;

View file

@ -0,0 +1,10 @@
export function buildContent(value: string) {
const htmlContent = (
<div className="w-[200px]">
<span>{value != null && value != "" ? value : "None"}</span>
</div>
);
return htmlContent;
}
export default buildContent;

View file

@ -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;
}, {});
}

View file

@ -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"
);
};

View file

@ -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;
};

View file

@ -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;
};

View file

@ -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;
};

View file

@ -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 (
<BaseModal open={open} setOpen={setOpen}>
<BaseModal.Trigger asChild>{children}</BaseModal.Trigger>
<BaseModal.Header description={EXPORT_CODE_DIALOG}>
<span className="pr-2">API</span>
<IconComponent
name="Code2"
className="h-6 w-6 pl-1 text-gray-800 dark:text-white"
aria-hidden="true"
/>
</BaseModal.Header>
<BaseModal.Content>
<CodeTabsComponent
flow={flow}
tabs={tabs!}
activeTab={activeTab}
setActiveTab={setActiveTab}
tweaks={{
tweak,
tweaksList,
buildContent,
buildTweakObject,
getValue,
}}
activeTweaks={activeTweaks}
setActiveTweaks={setActiveTweaks}
/>
</BaseModal.Content>
</BaseModal>
);
},
);
export default ApiModal;

View file

@ -0,0 +1,9 @@
import { create } from "zustand";
import { TweaksStoreType } from "../types/zustand/tweaks";
export const useTweaksStore = create<TweaksStoreType>((set, get) => ({
tweak: [],
setTweak: (tweak) => set({ tweak }),
tweaksList: [],
setTweaksList: (tweaksList) => set({ tweaksList }),
}));

View file

@ -511,7 +511,7 @@ export type nodeToolbarPropsType = {
updateNodeCode?: ( updateNodeCode?: (
newNodeClass: APIClassType, newNodeClass: APIClassType,
code: string, code: string,
name: string name: string,
) => void; ) => void;
setShowState: (show: boolean | SetStateAction<boolean>) => void; setShowState: (show: boolean | SetStateAction<boolean>) => void;
isOutdated?: boolean; isOutdated?: boolean;
@ -561,7 +561,7 @@ export type chatMessagePropsType = {
updateChat: ( updateChat: (
chat: ChatMessageType, chat: ChatMessageType,
message: string, message: string,
stream_url?: string stream_url?: string,
) => void; ) => void;
}; };
@ -646,20 +646,23 @@ export type codeTabsPropsType = {
setActiveTab: (value: string) => void; setActiveTab: (value: string) => void;
isMessage?: boolean; isMessage?: boolean;
tweaks?: { tweaks?: {
tweak?: { current: tweakType }; tweak?: tweakType;
tweaksList?: { current: Array<string> }; tweaksList?: Array<string>;
buildContent?: (value: string) => ReactNode; buildContent?: (value: string) => ReactNode;
getValue?: ( getValue?: (
value: string, value: string,
node: NodeType, node: NodeType,
template: TemplateVariableType template: TemplateVariableType,
tweak: tweakType,
) => string; ) => string;
buildTweakObject?: ( buildTweakObject?: (
tw: string, tw: string,
changes: string | string[] | boolean | number | Object[] | Object, changes: string | string[] | boolean | number | Object[] | Object,
template: TemplateVariableType template: TemplateVariableType,
) => string | void; ) => Promise<string | void>;
}; };
activeTweaks?: boolean;
setActiveTweaks?: (value: boolean) => void;
}; };
export type crashComponentPropsType = { export type crashComponentPropsType = {

View file

@ -0,0 +1,8 @@
import { tweakType } from "../../components";
export type TweaksStoreType = {
tweak: tweakType;
setTweak: (tweak: tweakType) => void;
tweaksList: string[];
setTweaksList: (tweaksList: string[]) => void;
};

View file

@ -102,18 +102,18 @@ export function unselectAllNodes({ updateNodes, data }: unselectAllNodesType) {
export function isValidConnection( export function isValidConnection(
{ source, target, sourceHandle, targetHandle }: Connection, { source, target, sourceHandle, targetHandle }: Connection,
nodes: Node[], nodes: Node[],
edges: Edge[] edges: Edge[],
) { ) {
const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!); const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!);
const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!); const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!);
if ( if (
targetHandleObject.inputTypes?.some( targetHandleObject.inputTypes?.some(
(n) => n === sourceHandleObject.dataType (n) => n === sourceHandleObject.dataType,
) || ) ||
sourceHandleObject.baseClasses.some( sourceHandleObject.baseClasses.some(
(t) => (t) =>
targetHandleObject.inputTypes?.some((n) => n === t) || targetHandleObject.inputTypes?.some((n) => n === t) ||
t === targetHandleObject.type t === targetHandleObject.type,
) )
) { ) {
let targetNode = nodes.find((node) => node.id === target!)?.data?.node; let targetNode = nodes.find((node) => node.id === target!)?.data?.node;
@ -146,7 +146,7 @@ export function removeApiKeys(flow: FlowType): FlowType {
export function updateTemplate( export function updateTemplate(
reference: APITemplateType, reference: APITemplateType,
objectToUpdate: APITemplateType objectToUpdate: APITemplateType,
): APITemplateType { ): APITemplateType {
let clonedObject: APITemplateType = cloneDeep(reference); let clonedObject: APITemplateType = cloneDeep(reference);
@ -206,7 +206,7 @@ export const processDataFromFlow = (flow: FlowType, refreshIds = true) => {
export function updateIds( export function updateIds(
{ edges, nodes }: { edges: Edge[]; nodes: Node[] }, { edges, nodes }: { edges: Edge[]; nodes: Node[] },
selection?: { edges: Edge[]; nodes: Node[] } selection?: { edges: Edge[]; nodes: Node[] },
) { ) {
let idsMap = {}; let idsMap = {};
const selectionIds = selection?.nodes.map((n) => n.id); const selectionIds = selection?.nodes.map((n) => n.id);
@ -234,7 +234,7 @@ export function updateIds(
edge.source = idsMap[edge.source]; edge.source = idsMap[edge.source];
edge.target = idsMap[edge.target]; edge.target = idsMap[edge.target];
const sourceHandleObject: sourceHandleType = scapeJSONParse( const sourceHandleObject: sourceHandleType = scapeJSONParse(
edge.sourceHandle! edge.sourceHandle!,
); );
edge.sourceHandle = scapedJSONStringfy({ edge.sourceHandle = scapedJSONStringfy({
...sourceHandleObject, ...sourceHandleObject,
@ -244,7 +244,7 @@ export function updateIds(
edge.data.sourceHandle.id = edge.source; edge.data.sourceHandle.id = edge.source;
} }
const targetHandleObject: targetHandleType = scapeJSONParse( const targetHandleObject: targetHandleType = scapeJSONParse(
edge.targetHandle! edge.targetHandle!,
); );
edge.targetHandle = scapedJSONStringfy({ edge.targetHandle = scapedJSONStringfy({
...targetHandleObject, ...targetHandleObject,
@ -264,13 +264,6 @@ export function updateIds(
return idsMap; 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<string> { export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
if (!node.data?.node?.template || !Object.keys(node.data.node.template)) { if (!node.data?.node?.template || !Object.keys(node.data.node.template)) {
return [ return [
@ -297,11 +290,11 @@ export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
(scapeJSONParse(edge.targetHandle!) as targetHandleType).fieldName === (scapeJSONParse(edge.targetHandle!) as targetHandleType).fieldName ===
t && t &&
(scapeJSONParse(edge.targetHandle!) as targetHandleType).id === (scapeJSONParse(edge.targetHandle!) as targetHandleType).id ===
node.id node.id,
) )
) { ) {
errors.push( errors.push(
`${displayName || type} is missing ${getFieldTitle(template, t)}.` `${displayName || type} is missing ${getFieldTitle(template, t)}.`,
); );
} else if ( } else if (
template[t].type === "dict" && template[t].type === "dict" &&
@ -315,15 +308,15 @@ export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
errors.push( errors.push(
`${displayName || type} (${getFieldTitle( `${displayName || type} (${getFieldTitle(
template, template,
t t,
)}) contains duplicate keys with the same values.` )}) contains duplicate keys with the same values.`,
); );
if (hasEmptyKey(template[t].value)) if (hasEmptyKey(template[t].value))
errors.push( errors.push(
`${displayName || type} (${getFieldTitle( `${displayName || type} (${getFieldTitle(
template, template,
t t,
)}) field must not be empty.` )}) field must not be empty.`,
); );
} }
return errors; return errors;
@ -332,7 +325,7 @@ export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
export function validateNodes( export function validateNodes(
nodes: Node[], nodes: Node[],
edges: Edge[] edges: Edge[],
): // this returns an array of tuples with the node id and the errors ): // this returns an array of tuples with the node id and the errors
Array<{ id: string; errors: Array<string> }> { Array<{ id: string; errors: Array<string> }> {
if (nodes.length === 0) { if (nodes.length === 0) {
@ -353,7 +346,7 @@ export function updateEdges(edges: Edge[]) {
if (edges) if (edges)
edges.forEach((edge) => { edges.forEach((edge) => {
const targetHandleObject: targetHandleType = scapeJSONParse( const targetHandleObject: targetHandleType = scapeJSONParse(
edge.targetHandle! edge.targetHandle!,
); );
edge.className = "stroke-gray-900 stroke-connection"; edge.className = "stroke-gray-900 stroke-connection";
}); });
@ -420,7 +413,7 @@ export function handleKeyDown(
| React.KeyboardEvent<HTMLInputElement> | React.KeyboardEvent<HTMLInputElement>
| React.KeyboardEvent<HTMLTextAreaElement>, | React.KeyboardEvent<HTMLTextAreaElement>,
inputValue: string | string[] | null, inputValue: string | string[] | null,
block: string block: string,
) { ) {
//condition to fix bug control+backspace on Windows/Linux //condition to fix bug control+backspace on Windows/Linux
if ( if (
@ -445,7 +438,7 @@ export function handleKeyDown(
} }
export function handleOnlyIntegerInput( export function handleOnlyIntegerInput(
event: React.KeyboardEvent<HTMLInputElement> event: React.KeyboardEvent<HTMLInputElement>,
) { ) {
if ( if (
event.key === "." || event.key === "." ||
@ -461,7 +454,7 @@ export function handleOnlyIntegerInput(
export function getConnectedNodes( export function getConnectedNodes(
edge: Edge, edge: Edge,
nodes: Array<NodeType> nodes: Array<NodeType>,
): Array<NodeType> { ): Array<NodeType> {
const sourceId = edge.source; const sourceId = edge.source;
const targetId = edge.target; const targetId = edge.target;
@ -561,7 +554,7 @@ export function checkOldEdgesHandles(edges: Edge[]): boolean {
!edge.sourceHandle || !edge.sourceHandle ||
!edge.targetHandle || !edge.targetHandle ||
!edge.sourceHandle.includes("{") || !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 keys = Object.keys(obj).sort();
const keyValuePairs = keys.map( const keyValuePairs = keys.map(
(key) => `"${key}":${customStringify(obj[key])}` (key) => `"${key}":${customStringify(obj[key])}`,
); );
return `{${keyValuePairs.join(",")}}`; return `{${keyValuePairs.join(",")}}`;
} }
@ -613,7 +606,7 @@ export function getHandleId(
source: string, source: string,
sourceHandle: string, sourceHandle: string,
target: string, target: string,
targetHandle: string targetHandle: string,
) { ) {
return ( return (
"reactflow__edge-" + source + sourceHandle + "-" + target + targetHandle "reactflow__edge-" + source + sourceHandle + "-" + target + targetHandle
@ -624,7 +617,7 @@ export function generateFlow(
selection: OnSelectionChangeParams, selection: OnSelectionChangeParams,
nodes: Node[], nodes: Node[],
edges: Edge[], edges: Edge[],
name: string name: string,
): generateFlowType { ): generateFlowType {
const newFlowData = { nodes, edges, viewport: { zoom: 1, x: 0, y: 0 } }; const newFlowData = { nodes, edges, viewport: { zoom: 1, x: 0, y: 0 } };
const uid = new ShortUniqueId({ length: 5 }); const uid = new ShortUniqueId({ length: 5 });
@ -633,7 +626,7 @@ export function generateFlow(
newFlowData.edges = selection.edges.filter( newFlowData.edges = selection.edges.filter(
(edge) => (edge) =>
selection.nodes.some((node) => node.id === edge.target) && 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; newFlowData.nodes = selection.nodes;
@ -654,7 +647,7 @@ export function generateFlow(
(edge) => (edge) =>
(selection.nodes.some((node) => node.id === edge.target) || (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.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 { nodes, edges } = groupNode.data.node!.flow!.data!;
const lastNode = findLastNode(groupNode.data.node!.flow!.data!); const lastNode = findLastNode(groupNode.data.node!.flow!.data!);
newEdges = newEdges.filter( 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) => { newEdges.forEach((edge) => {
if (lastNode && edge.source === lastNode.id) { if (lastNode && edge.source === lastNode.id) {
edge.source = groupNode.id; edge.source = groupNode.id;
let newSourceHandle: sourceHandleType = scapeJSONParse( let newSourceHandle: sourceHandleType = scapeJSONParse(
edge.sourceHandle! edge.sourceHandle!,
); );
newSourceHandle.id = groupNode.id; newSourceHandle.id = groupNode.id;
edge.sourceHandle = scapedJSONStringfy(newSourceHandle); edge.sourceHandle = scapedJSONStringfy(newSourceHandle);
@ -698,7 +691,7 @@ export function reconnectEdges(groupNode: NodeType, excludedEdges: Edge[]) {
export function filterFlow( export function filterFlow(
selection: OnSelectionChangeParams, selection: OnSelectionChangeParams,
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void, 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))); setNodes((nodes) => nodes.filter((node) => !selection.nodes.includes(node)));
setEdges((edges) => edges.filter((edge) => !selection.edges.includes(edge))); setEdges((edges) => edges.filter((edge) => !selection.edges.includes(edge)));
@ -736,7 +729,7 @@ export function updateFlowPosition(NewPosition: XYPosition, flow: FlowType) {
export function concatFlows( export function concatFlows(
flow: FlowType, flow: FlowType,
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void, 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!; const { nodes, edges } = flow.data!;
setNodes((old) => [...old, ...nodes]); setNodes((old) => [...old, ...nodes]);
@ -745,7 +738,7 @@ export function concatFlows(
export function validateSelection( export function validateSelection(
selection: OnSelectionChangeParams, selection: OnSelectionChangeParams,
edges: Edge[] edges: Edge[],
): Array<string> { ): Array<string> {
//add edges to selection if selection mode selected only nodes //add edges to selection if selection mode selected only nodes
if (selection.edges.length === 0) { if (selection.edges.length === 0) {
@ -757,7 +750,7 @@ export function validateSelection(
let nodesSet = new Set(selection.nodes.map((n) => n.id)); let nodesSet = new Set(selection.nodes.map((n) => n.id));
// then filter the edges that are connected to the nodes in the set // then filter the edges that are connected to the nodes in the set
let connectedEdges = selection.edges.filter( 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 // add the edges to the selection
selection.edges = connectedEdges; selection.edges = connectedEdges;
@ -771,17 +764,17 @@ export function validateSelection(
selection.nodes.some( selection.nodes.some(
(node) => (node) =>
isInputNode(node.data as NodeDataType) || isInputNode(node.data as NodeDataType) ||
isOutputNode(node.data as NodeDataType) isOutputNode(node.data as NodeDataType),
) )
) { ) {
errorsArray.push( 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 //check if there are two or more nodes with free outputs
if ( if (
selection.nodes.filter( selection.nodes.filter(
(n) => !selection.edges.some((e) => e.source === n.id) (n) => !selection.edges.some((e) => e.source === n.id),
).length > 1 ).length > 1
) { ) {
errorsArray.push("Please select only one node with free outputs"); errorsArray.push("Please select only one node with free outputs");
@ -792,7 +785,7 @@ export function validateSelection(
selection.nodes.some( selection.nodes.some(
(node) => (node) =>
!selection.edges.some((edge) => edge.target === node.id) && !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"); 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].display_name ? nodeTemplate[key].display_name
: nodeTemplate[key].name : nodeTemplate[key].name
? toTitleCase(nodeTemplate[key].name) ? toTitleCase(nodeTemplate[key].name)
: toTitleCase(key); : toTitleCase(key);
} }
} }
}); });
@ -861,7 +854,7 @@ function isHandleConnected(
edges: Edge[], edges: Edge[],
key: string, key: string,
field: TemplateVariableType, field: TemplateVariableType,
nodeId: string nodeId: string,
) { ) {
/* /*
this function receives a flow and a handleId and check if there is a connection with this handle 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, id: nodeId,
proxy: { id: field.proxy!.id, field: field.proxy!.field }, proxy: { id: field.proxy!.id, field: field.proxy!.field },
inputTypes: field.input_types, inputTypes: field.input_types,
} as targetHandleType) } as targetHandleType),
) )
) { ) {
return true; return true;
@ -892,7 +885,7 @@ function isHandleConnected(
fieldName: key, fieldName: key,
id: nodeId, id: nodeId,
inputTypes: field.input_types, inputTypes: field.input_types,
} as targetHandleType) } as targetHandleType),
) )
) { ) {
return true; return true;
@ -915,7 +908,7 @@ export function generateNodeTemplate(Flow: FlowType) {
export function generateNodeFromFlow( export function generateNodeFromFlow(
flow: FlowType, flow: FlowType,
getNodeId: (type: string) => string getNodeId: (type: string) => string,
): NodeType { ): NodeType {
const { nodes } = flow.data!; const { nodes } = flow.data!;
const outputNode = cloneDeep(findLastNode(flow.data!)); const outputNode = cloneDeep(findLastNode(flow.data!));
@ -946,7 +939,7 @@ export function generateNodeFromFlow(
export function connectedInputNodesOnHandle( export function connectedInputNodesOnHandle(
nodeId: string, nodeId: string,
handleId: string, handleId: string,
{ nodes, edges }: { nodes: NodeType[]; edges: Edge[] } { nodes, edges }: { nodes: NodeType[]; edges: Edge[] },
) { ) {
const connectedNodes: Array<{ name: string; id: string; isGroup: boolean }> = const connectedNodes: Array<{ name: string; id: string; isGroup: boolean }> =
[]; [];
@ -983,7 +976,7 @@ export function connectedInputNodesOnHandle(
export function updateProxyIdsOnTemplate( export function updateProxyIdsOnTemplate(
template: APITemplateType, template: APITemplateType,
idsMap: { [key: string]: string } idsMap: { [key: string]: string },
) { ) {
Object.keys(template).forEach((key) => { Object.keys(template).forEach((key) => {
if (template[key].proxy && idsMap[template[key].proxy!.id]) { if (template[key].proxy && idsMap[template[key].proxy!.id]) {
@ -994,7 +987,7 @@ export function updateProxyIdsOnTemplate(
export function updateEdgesIds( export function updateEdgesIds(
edges: Edge[], edges: Edge[],
idsMap: { [key: string]: string } idsMap: { [key: string]: string },
) { ) {
edges.forEach((edge) => { edges.forEach((edge) => {
let targetHandle: targetHandleType = edge.data.targetHandle; let targetHandle: targetHandleType = edge.data.targetHandle;
@ -1027,7 +1020,7 @@ export function expandGroupNode(
nodes: Node[], nodes: Node[],
edges: Edge[], edges: Edge[],
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void, 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!); const idsMap = updateIds(flow!.data!);
updateProxyIdsOnTemplate(template, idsMap); updateProxyIdsOnTemplate(template, idsMap);
@ -1070,7 +1063,7 @@ export function expandGroupNode(
const lastNode = cloneDeep(findLastNode(flow!.data!)); const lastNode = cloneDeep(findLastNode(flow!.data!));
newEdge.source = lastNode!.id; newEdge.source = lastNode!.id;
let newSourceHandle: sourceHandleType = scapeJSONParse( let newSourceHandle: sourceHandleType = scapeJSONParse(
newEdge.sourceHandle! newEdge.sourceHandle!,
); );
newSourceHandle.id = lastNode!.id; newSourceHandle.id = lastNode!.id;
newEdge.data.sourceHandle = newSourceHandle; newEdge.data.sourceHandle = newSourceHandle;
@ -1124,7 +1117,7 @@ export function expandGroupNode(
export function getGroupStatus( export function getGroupStatus(
flow: FlowType, flow: FlowType,
ssData: { [key: string]: { valid: boolean; params: string } } ssData: { [key: string]: { valid: boolean; params: string } },
) { ) {
let status = { valid: true, params: SUCCESS_BUILD }; let status = { valid: true, params: SUCCESS_BUILD };
const { nodes } = flow.data!; const { nodes } = flow.data!;
@ -1143,7 +1136,7 @@ export function getGroupStatus(
export function createFlowComponent( export function createFlowComponent(
nodeData: NodeDataType, nodeData: NodeDataType,
version: string version: string,
): FlowType { ): FlowType {
const flowNode: FlowType = { const flowNode: FlowType = {
data: { data: {
@ -1179,7 +1172,7 @@ export function downloadNode(NodeFLow: FlowType) {
export function updateComponentNameAndType( export function updateComponentNameAndType(
data: any, data: any,
component: NodeDataType component: NodeDataType,
) {} ) {}
export function removeFileNameFromComponents(flow: FlowType) { export function removeFileNameFromComponents(flow: FlowType) {
@ -1253,7 +1246,7 @@ export function extractFieldsFromComponenents(data: APIObjectType) {
export function downloadFlow( export function downloadFlow(
flow: FlowType, flow: FlowType,
flowName: string, flowName: string,
flowDescription?: string flowDescription?: string,
) { ) {
let clonedFlow = cloneDeep(flow); let clonedFlow = cloneDeep(flow);
removeFileNameFromComponents(clonedFlow); removeFileNameFromComponents(clonedFlow);
@ -1263,7 +1256,7 @@ export function downloadFlow(
...clonedFlow, ...clonedFlow,
name: flowName, name: flowName,
description: flowDescription, description: flowDescription,
}) }),
)}`; )}`;
// create a link element and set its properties // create a link element and set its properties
@ -1278,7 +1271,7 @@ export function downloadFlow(
export function downloadFlows() { export function downloadFlows() {
downloadFlowsFromDatabase().then((flows) => { downloadFlowsFromDatabase().then((flows) => {
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent( const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
JSON.stringify(flows) JSON.stringify(flows),
)}`; )}`;
// create a link element and set its properties // create a link element and set its properties
@ -1293,7 +1286,7 @@ export function downloadFlows() {
export const createNewFlow = ( export const createNewFlow = (
flowData: ReactFlowJsonObject, flowData: ReactFlowJsonObject,
flow: FlowType flow: FlowType,
) => { ) => {
return { return {
description: flow?.description ?? getRandomDescription(), description: flow?.description ?? getRandomDescription(),

View file

@ -13,9 +13,8 @@ import {
nodeGroupedObjType, nodeGroupedObjType,
tweakType, tweakType,
} from "../types/components"; } from "../types/components";
import { FlowType, NodeType } from "../types/flow"; import { NodeType } from "../types/flow";
import { FlowState } from "../types/tabs"; import { FlowState } from "../types/tabs";
import { buildTweaks } from "./reactflowUtils";
export function classNames(...classes: Array<string>): string { export function classNames(...classes: Array<string>): string {
return classes.filter(Boolean).join(" "); return classes.filter(Boolean).join(" ");
@ -323,17 +322,10 @@ export function getChatInputField(flowState?: FlowState) {
* @returns {string} - The python code * @returns {string} - The python code
*/ */
export function getPythonApiCode( export function getPythonApiCode(
flow: FlowType, flowId: string,
isAuth: boolean, isAuth: boolean,
tweak?: any[], tweaksBuildedObject,
): string { ): 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 return `import requests
from typing import Optional from typing import Optional
@ -341,11 +333,7 @@ BASE_API_URL = "${window.location.protocol}//${window.location.host}/api/v1/run"
FLOW_ID = "${flowId}" FLOW_ID = "${flowId}"
# You can tweak the flow by adding a tweaks dictionary # You can tweak the flow by adding a tweaks dictionary
# e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}} # e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}}
TWEAKS = ${ TWEAKS = ${JSON.stringify(tweaksBuildedObject, null, 2)}
tweak && tweak.length > 0
? buildTweakObject(tweak)
: JSON.stringify(tweaks, null, 2)
}
def run_flow(message: str, def run_flow(message: str,
flow_id: str, flow_id: str,
@ -391,13 +379,10 @@ print(run_flow(message=message, flow_id=FLOW_ID, tweaks=TWEAKS${
* @returns {string} - The curl code * @returns {string} - The curl code
*/ */
export function getCurlCode( export function getCurlCode(
flow: FlowType, flowId: string,
isAuth: boolean, isAuth: boolean,
tweak?: any[], tweaksBuildedObject,
): string { ): string {
const flowId = flow.id;
const tweaks = buildTweaks(flow);
return `curl -X POST \\ return `curl -X POST \\
${window.location.protocol}//${ ${window.location.protocol}//${
window.location.host window.location.host
@ -408,11 +393,7 @@ export function getCurlCode(
-d '{"input_value": "message", -d '{"input_value": "message",
"output_type": "chat", "output_type": "chat",
"input_type": "chat", "input_type": "chat",
"tweaks": ${ "tweaks": ${JSON.stringify(tweaksBuildedObject, null, 2)}'
tweak && tweak.length > 0
? buildTweakObject(tweak)
: JSON.stringify(tweaks, null, 2)
}}'
`; `;
} }
@ -439,16 +420,9 @@ export function getOutputIds(flow) {
* @param {any[]} tweak - The tweaks * @param {any[]} tweak - The tweaks
* @returns {string} - The python code * @returns {string} - The python code
*/ */
export function getPythonCode(flow: FlowType, tweak?: any[]): string { export function getPythonCode(flowName: string, tweaksBuildedObject): string {
const flowName = flow.name;
const tweaks = buildTweaks(flow);
return `from langflow.load import run_flow_from_json return `from langflow.load import run_flow_from_json
TWEAKS = ${ TWEAKS = ${JSON.stringify(tweaksBuildedObject, null, 2)}
tweak && tweak.length > 0
? buildTweakObject(tweak)
: JSON.stringify(tweaks, null, 2)
}
result = run_flow_from_json(flow="${flowName}.json", result = run_flow_from_json(flow="${flowName}.json",
input_value="message", input_value="message",
@ -461,15 +435,10 @@ result = run_flow_from_json(flow="${flowName}.json",
* @returns {string} - The widget code * @returns {string} - The widget code
*/ */
export function getWidgetCode( export function getWidgetCode(
flow: FlowType, flowId: string,
flowName: string,
isAuth: boolean, isAuth: boolean,
flowState?: FlowState,
): string { ): string {
const flowId = flow.id;
const flowName = flow.name;
const inputs = buildInputs();
let chat_input_field = getChatInputField(flowState);
return `<script src="https://cdn.jsdelivr.net/gh/langflow-ai/langflow-embedded-chat@1.0_alpha/dist/build/static/js/bundle.min.js"></script> return `<script src="https://cdn.jsdelivr.net/gh/langflow-ai/langflow-embedded-chat@1.0_alpha/dist/build/static/js/bundle.min.js"></script>
<langflow-chat <langflow-chat

View file

@ -24,7 +24,7 @@ test("curl_api_generation", async ({ page, context }) => {
await page.getByRole("tab", { name: "cURL" }).click(); await page.getByRole("tab", { name: "cURL" }).click();
await page.getByRole("button", { name: "Copy Code" }).click(); await page.getByRole("button", { name: "Copy Code" }).click();
const handle = await page.evaluateHandle(() => const handle = await page.evaluateHandle(() =>
navigator.clipboard.readText() navigator.clipboard.readText(),
); );
const clipboardContent = await handle.jsonValue(); const clipboardContent = await handle.jsonValue();
const oldValue = clipboardContent; const oldValue = clipboardContent;
@ -50,10 +50,78 @@ test("curl_api_generation", async ({ page, context }) => {
await page.getByRole("tab", { name: "cURL" }).click(); await page.getByRole("tab", { name: "cURL" }).click();
await page.getByRole("button", { name: "Copy Code" }).click(); await page.getByRole("button", { name: "Copy Code" }).click();
const handle2 = await page.evaluateHandle(() => const handle2 = await page.evaluateHandle(() =>
navigator.clipboard.readText() navigator.clipboard.readText(),
); );
const clipboardContent2 = await handle2.jsonValue(); const clipboardContent2 = await handle2.jsonValue();
const newValue = clipboardContent2; const newValue = clipboardContent2;
expect(oldValue).not.toBe(newValue); expect(oldValue).not.toBe(newValue);
expect(clipboardContent2.length).toBeGreaterThan(clipboardContent.length); 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();
});