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,
}: 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;
}

View file

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

View file

@ -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<Boolean>(false);
const [data, setData] = useState(flow ? flow["data"]!["nodes"] : null);
const [openAccordion, setOpenAccordion] = useState<string[]>([]);
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 (
<Tabs
value={activeTab}
@ -132,9 +118,6 @@ export default function CodeTabsComponent({
}
onValueChange={(value) => {
setActiveTab(value);
if (value === "3") {
openAccordions();
}
}}
>
<div className="api-modal-tablist-div">
@ -155,27 +138,58 @@ export default function CodeTabsComponent({
) : (
<div></div>
)}
{Number(activeTab) < 4 && (
<div className="float-right mx-1 mb-1 mt-2 flex gap-2">
<button
className="flex items-center gap-1.5 rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"
onClick={copyToClipboard}
<div className="float-right mx-1 mb-1 mt-2 flex gap-2">
<div
className={
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 ? (
<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>
Tweaks
</Label>
</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>
{tabs.map((tab, idx) => (
@ -205,14 +219,12 @@ export default function CodeTabsComponent({
<div className="api-modal-according-display">
<div
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) => (
<div className="px-3" key={i}>
{tweaks?.tweaksList!.current.includes(
node["data"]["id"]
) && (
{tweaks?.tweaksList!.includes(node["data"]["id"]) && (
<AccordionComponent
trigger={
<ShadTooltip
@ -223,7 +235,6 @@ export default function CodeTabsComponent({
<div>{node["data"]["node"]["display_name"]}</div>
</ShadTooltip>
}
open={openAccordion}
keyValue={node["data"]["id"]}
>
<div className="api-modal-table-arrangement">
@ -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({
</AccordionComponent>
)}
{tweaks?.tweaksList!.current.length === 0 && (
{tweaks?.tweaksList!.length === 0 && (
<>
<div className="pt-3">
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?: (
newNodeClass: APIClassType,
code: string,
name: string
name: string,
) => void;
setShowState: (show: boolean | SetStateAction<boolean>) => 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<string> };
tweak?: tweakType;
tweaksList?: Array<string>;
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<string | void>;
};
activeTweaks?: boolean;
setActiveTweaks?: (value: boolean) => void;
};
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(
{ 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<string> {
if (!node.data?.node?.template || !Object.keys(node.data.node.template)) {
return [
@ -297,11 +290,11 @@ export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
(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<string> {
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<string> {
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<string> }> {
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<HTMLInputElement>
| React.KeyboardEvent<HTMLTextAreaElement>,
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<HTMLInputElement>
event: React.KeyboardEvent<HTMLInputElement>,
) {
if (
event.key === "." ||
@ -461,7 +454,7 @@ export function handleOnlyIntegerInput(
export function getConnectedNodes(
edge: Edge,
nodes: Array<NodeType>
nodes: Array<NodeType>,
): Array<NodeType> {
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<string> {
//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(),

View file

@ -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>): 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 `<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

View file

@ -24,7 +24,7 @@ test("curl_api_generation", async ({ page, context }) => {
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();
});