Fix Global Variables (#2430)

* chore: update linting workflows to include dev branch in merge_group

* Update README.md

Add 1.0 banner

* Update README.md

* chore: update package versions in pyproject.toml files

* Added db value to every onChange on parameters

* Refactored global variables to find variables on the first render, and to execute just one onChange to update both values and db

* Changed group recursion function to include check for global variables already applied or outdated

* Removed already inserted component check for default fields on global variables

* Fixed import error

* Added required parameters to update node on drop

* Removed check for unused hardcoded name

* Added global variables handling on nodes when adding a flow

* Fixed maximum update depth when deleting used global variable

* Fixed type error on addNewVariableButton modal

* Fixed openai api key on starter flows

* Fixed values to get the .env values by default on the starter projects

* Formatted flows

* fix erros reported by mypy

* [autofix.ci] apply automated fixes

* 🐛 (prototypes/__init__.py): fix missing comma in the list of imported components to prevent syntax error

* chore: Fix type hinting in ConditionalRouterComponent

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
Co-authored-by: Rodrigo Nader <rodrigosilvanader@gmail.com>
Co-authored-by: italojohnny <italojohnnydosanjos@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Lucas Oliveira 2024-07-01 13:31:06 -03:00 • committed by Gabriel Luiz Freitas Almeida
commit b31c5e6b4a
26 changed files with 1036 additions and 885 deletions

View file

@ -174,9 +174,10 @@ export default function ParameterComponent({
const handleOnNewValue = async (
newValue: string | string[] | boolean | Object[],
dbValue?: boolean,
skipSnapshot: boolean | undefined = false,
): Promise<void> => {
handleOnNewValueHook(newValue, skipSnapshot);
handleOnNewValueHook(newValue, dbValue, skipSnapshot);
};
const handleNodeClass = (newNodeClass: APIClassType, code?: string): void => {
@ -470,16 +471,6 @@ export default function ParameterComponent({
<InputGlobalComponent
disabled={disabled}
onChange={handleOnNewValue}
setDb={(value) => {
setNode(data.id, (oldNode) => {
let newNode = cloneDeep(oldNode);
newNode.data = {
...newNode.data,
};
newNode.data.node.template[name].load_from_db = value;
return newNode;
});
}}
name={name}
data={data.node?.template[name]!}
/>

View file

@ -18,7 +18,7 @@ const useHandleOnNewValue = (
) => {
const setErrorData = useAlertStore((state) => state.setErrorData);
const handleOnNewValue = async (newValue, skipSnapshot = false) => {
const handleOnNewValue = async (newValue, dbValue, skipSnapshot = false) => {
const nodeTemplate = data.node!.template[name];
const currentValue = nodeTemplate.value;
@ -63,6 +63,10 @@ const useHandleOnNewValue = (
...newNode.data,
};
if (dbValue) {
newNode.data.node.template[name].load_from_db = dbValue;
}
if (data.node?.template[name].real_time_refresh && newTemplate) {
newNode.data.node.template = newTemplate;
} else {

View file

@ -29,7 +29,9 @@ export default function AddNewVariableButton({
const setErrorData = useAlertStore((state) => state.setErrorData);
const componentFields = useTypesStore((state) => state.ComponentFields);
const unavaliableFields = new Set(
Object.keys(useGlobalVariablesStore((state) => state.unavaliableFields)),
Object.keys(
useGlobalVariablesStore((state) => state.unavaliableFields) ?? {},
),
);
const availableFields = () => {

View file

@ -23,7 +23,7 @@ export default function CodeAreaComponent({
useEffect(() => {
if (disabled && myValue !== "") {
setMyValue("");
onChange("", true);
onChange("", undefined, true);
}
}, [disabled]);

View file

@ -16,7 +16,7 @@ export default function FloatComponent({
// Clear component state
useEffect(() => {
if (disabled && value !== "") {
onChange("", true);
onChange("", undefined, true);
}
}, [disabled]);

View file

@ -27,7 +27,7 @@ export default function InputFileComponent({
useEffect(() => {
if (disabled && value !== "") {
setMyValue("");
onChange("", true);
onChange("", undefined, true);
onFileChange("");
}
}, [disabled, onChange]);

View file

@ -1,4 +1,5 @@
import { useEffect } from "react";
import { Controller } from "react-hook-form";
import { deleteGlobalVariable } from "../../controllers/API";
import DeleteConfirmationModal from "../../modals/deleteConfirmationModal";
import useAlertStore from "../../stores/alertStore";
@ -13,7 +14,6 @@ import { CommandItem } from "../ui/command";
export default function InputGlobalComponent({
disabled,
onChange,
setDb,
name,
data,
editNode = false,
@ -23,39 +23,17 @@ export default function InputGlobalComponent({
);
const getVariableId = useGlobalVariablesStore((state) => state.getVariableId);
const unavaliableFields = useGlobalVariablesStore(
(state) => state.unavaliableFields,
);
const removeGlobalVariable = useGlobalVariablesStore(
(state) => state.removeGlobalVariable,
);
const setErrorData = useAlertStore((state) => state.setErrorData);
useEffect(() => {
if (data)
if (
((globalVariablesEntries &&
!globalVariablesEntries.includes(data.value)) ||
!globalVariablesEntries) &&
data.load_from_db
) {
setTimeout(() => {
onChange("", true);
setDb(false);
}, 100);
if (data && globalVariablesEntries)
if (data.load_from_db && !globalVariablesEntries.includes(data.value)) {
onChange("", false, true);
}
}, [globalVariablesEntries, data]);
useEffect(() => {
if (!data.value && data.display_name) {
if (unavaliableFields[data.display_name!] && !disabled) {
setTimeout(() => {
setDb(true);
onChange(unavaliableFields[data.display_name!]);
}, 100);
}
}
}, [unavaliableFields]);
}, [globalVariablesEntries]);
async function handleDelete(key: string) {
const id = getVariableId(key);
@ -64,8 +42,7 @@ export default function InputGlobalComponent({
.then(() => {
removeGlobalVariable(key);
if (data?.value === key && data?.load_from_db) {
onChange("");
setDb(false);
onChange("", false);
}
})
.catch(() => {
@ -137,12 +114,10 @@ export default function InputGlobalComponent({
: ""
}
setSelectedOption={(value) => {
onChange(value);
setDb(value !== "" ? true : false);
onChange(value, value !== "" ? true : false);
}}
onChange={(value, skipSnapshot) => {
onChange(value, skipSnapshot);
setDb(false);
onChange(value, false, skipSnapshot);
}}
/>
);

View file

@ -19,7 +19,7 @@ export default function IntComponent({
// Clear component state
useEffect(() => {
if (disabled && value !== "") {
onChange("", true);
onChange("", undefined, true);
}
}, [disabled, onChange]);

View file

@ -19,7 +19,7 @@ export default function PromptAreaComponent({
}: PromptAreaComponentType): JSX.Element {
useEffect(() => {
if (disabled && value !== "") {
onChange("", true);
onChange("", undefined, true);
}
}, [disabled]);

View file

@ -24,32 +24,21 @@ import ToggleShadComponent from "../../../toggleShadComponent";
export default function TableNodeCellRender({
node: { data },
value: {
value,
nodeClass,
handleOnNewValue: handleOnNewValueNode,
handleOnChangeDb: handleOnChangeDbNode,
},
value: { value, nodeClass, handleOnNewValue: handleOnNewValueNode },
}: CustomCellRendererProps) {
const handleOnNewValue = (newValue: any, name: string) => {
handleOnNewValueNode(newValue, name);
const handleOnNewValue = (newValue: any, name: string, dbValue?: boolean) => {
handleOnNewValueNode(newValue, name, dbValue);
setTemplateData((old) => {
let newData = cloneDeep(old);
newData.value = newValue;
if (dbValue) {
newData.load_from_db = newValue;
}
return newData;
});
setTemplateValue(newValue);
};
const handleOnChangeDb = (newValue: boolean, name: string) => {
handleOnChangeDbNode(newValue, name);
setTemplateData((old) => {
let newData = cloneDeep(old);
newData.load_from_db = newValue;
return newData;
});
};
const [templateValue, setTemplateValue] = useState(value);
const [templateData, setTemplateData] = useState(data);
@ -106,10 +95,9 @@ export default function TableNodeCellRender({
<InputGlobalComponent
disabled={disabled}
editNode={true}
onChange={(value) => handleOnNewValue(value, templateData.key)}
setDb={(value) => {
handleOnChangeDb(value, templateData.key);
}}
onChange={(value, dbValue, snapshot) =>
handleOnNewValue(value, templateData.key, dbValue)
}
name={templateData.key}
data={templateData}
/>

View file

@ -18,7 +18,7 @@ export default function TextAreaComponent({
// Clear text area
useEffect(() => {
if (disabled && value !== "") {
onChange("", true);
onChange("", undefined, true);
}
}, [disabled]);

View file

@ -6,8 +6,7 @@ import { NodeDataType } from "../../../types/flow";
const useColumnDefs = (
myData: NodeDataType,
handleOnNewValue: (newValue: any, name: string) => void,
handleOnChangeDb: (value: boolean, key: string) => void,
handleOnNewValue: (newValue: any, name: string, setDb?: boolean) => void,
changeAdvanced: (n: string) => void,
open: boolean,
) => {
@ -49,7 +48,6 @@ const useColumnDefs = (
value: params.data.value,
nodeClass: myData.node,
handleOnNewValue: handleOnNewValue,
handleOnChangeDb: handleOnChangeDb,
};
},
minWidth: 340,

View file

@ -36,12 +36,11 @@ const EditNodeModal = forwardRef(
!myData.current.node!.template[n]?.advanced;
}
const handleOnNewValue = (newValue: any, key: string) => {
const handleOnNewValue = (newValue: any, key: string, setDb?: boolean) => {
myData.current.node!.template[key].value = newValue;
};
const handleOnChangeDb = (newValue: boolean, key: string) => {
myData.current.node!.template[key].load_from_db = newValue;
if (setDb) {
myData.current.node!.template[key].load_from_db = newValue;
}
};
const rowData = useRowData(data, open);
@ -49,7 +48,6 @@ const EditNodeModal = forwardRef(
const columnDefs: ColDef[] = useColumnDefs(
data,
handleOnNewValue,
handleOnChangeDb,
changeAdvanced,
open,
);

View file

@ -10,12 +10,6 @@ export default function NewFlowModal({
}: newFlowModalPropsType): JSX.Element {
const examples = useFlowsManagerStore((state) => state.examples);
examples?.forEach((example) => {
if (example.name === "Blog Writter") {
example.name = "Blog Writer";
}
});
return (
<BaseModal size="three-cards" open={open} setOpen={setOpen}>
<BaseModal.Header description={"Select a template below"}>

View file

@ -40,6 +40,7 @@ import { getInputsAndOutputs } from "../utils/storeUtils";
import useAlertStore from "./alertStore";
import { useDarkStore } from "./darkStore";
import useFlowsManagerStore from "./flowsManagerStore";
import { useGlobalVariablesStore } from "./globalVariablesStore/globalVariables";
// this is our useStore hook that we can use in our components to get parts of the store and call actions
const useFlowStore = create<FlowStoreType>((set, get) => ({
@ -288,7 +289,12 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
id: newId,
},
};
updateGroupRecursion(newNode, selection.edges);
updateGroupRecursion(
newNode,
selection.edges,
useGlobalVariablesStore.getState().unavaliableFields,
useGlobalVariablesStore.getState().globalVariablesEntries,
);
// Add the new node to the list of nodes in state
newNodes = newNodes

View file

@ -1,3 +1,4 @@
import { AxiosError } from "axios";
import { cloneDeep } from "lodash";
import pDebounce from "p-debounce";
import { Edge, Node, Viewport, XYPosition } from "reactflow";
@ -23,11 +24,13 @@ import {
extractFieldsFromComponenents,
processDataFromFlow,
processFlows,
updateGroupRecursion,
} from "../utils/reactflowUtils";
import useAlertStore from "./alertStore";
import { useDarkStore } from "./darkStore";
import useFlowStore from "./flowStore";
import { useFolderStore } from "./foldersStore";
import { useGlobalVariablesStore } from "./globalVariablesStore/globalVariables";
import { useTypesStore } from "./typesStore";
let saveTimeoutId: NodeJS.Timeout | null = null;
@ -202,6 +205,14 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
let flowData = flow
? processDataFromFlow(flow)
: { nodes: [], edges: [], viewport: { zoom: 1, x: 0, y: 0 } };
flowData?.nodes.forEach((node) => {
updateGroupRecursion(
node,
flowData?.edges,
useGlobalVariablesStore.getState().unavaliableFields,
useGlobalVariablesStore.getState().globalVariablesEntries,
);
});
if (newProject) {
// Create a new flow with a default name if no flow is provided.
const folder_id = useFolderStore.getState().folderUrl;

View file

@ -4,12 +4,12 @@ import getUnavailableFields from "./utils/get-unavailable-fields";
export const useGlobalVariablesStore = create<GlobalVariablesStore>(
(set, get) => ({
unavaliableFields: {},
unavaliableFields: undefined,
setUnavaliableFields: (fields) => {
set({ unavaliableFields: fields });
},
removeUnavaliableField: (field) => {
const newFields = get().unavaliableFields;
const newFields = get().unavaliableFields || {};
delete newFields[field];
set({ unavaliableFields: newFields });
},
@ -18,7 +18,7 @@ export const useGlobalVariablesStore = create<GlobalVariablesStore>(
setGlobalVariables: (variables) => {
set({
globalVariables: variables,
globalVariablesEntries: Object.keys(variables),
globalVariablesEntries: Object.keys(variables) || [],
unavaliableFields: getUnavailableFields(variables),
});
},
@ -27,7 +27,7 @@ export const useGlobalVariablesStore = create<GlobalVariablesStore>(
const newVariables = { ...get().globalVariables, [name]: data };
set({
globalVariables: newVariables,
globalVariablesEntries: Object.keys(newVariables),
globalVariablesEntries: Object.keys(newVariables) || [],
unavaliableFields: getUnavailableFields(newVariables),
});
},
@ -38,7 +38,7 @@ export const useGlobalVariablesStore = create<GlobalVariablesStore>(
delete newVariables[name];
set({
globalVariables: newVariables,
globalVariablesEntries: Object.keys(newVariables),
globalVariablesEntries: Object.keys(newVariables) || [],
unavaliableFields: getUnavailableFields(newVariables),
});
},

View file

@ -91,8 +91,7 @@ export type InputListComponentType = {
export type InputGlobalComponentType = {
disabled: boolean;
onChange: (value: string, snapshot?: boolean) => void;
setDb: (value: boolean) => void;
onChange: (value: string, dbValue: boolean, snapshot?: boolean) => void;
name: string;
data: InputFieldType;
editNode?: boolean;
@ -124,7 +123,11 @@ export type TextAreaComponentType = {
nodeClass?: APIClassType;
setNodeClass?: (value: APIClassType) => void;
disabled: boolean;
onChange: (value: string[] | string, skipSnapshot?: boolean) => void;
onChange: (
value: string[] | string,
dbValue?: boolean,
skipSnapshot?: boolean,
) => void;
value: string;
editNode?: boolean;
id?: string;
@ -146,7 +149,11 @@ export type PromptAreaComponentType = {
nodeClass?: APIClassType;
setNodeClass?: (value: APIClassType, code?: string) => void;
disabled: boolean;
onChange: (value: string[] | string, skipSnapshot?: boolean) => void;
onChange: (
value: string[] | string,
dbValue?: boolean,
skipSnapshot?: boolean,
) => void;
value: string;
readonly?: boolean;
editNode?: boolean;
@ -156,7 +163,11 @@ export type PromptAreaComponentType = {
export type CodeAreaComponentType = {
setOpenModal?: (bool: boolean) => void;
disabled: boolean;
onChange: (value: string[] | string, skipSnapshot?: boolean) => void;
onChange: (
value: string[] | string,
dbValue?: boolean,
skipSnapshot?: boolean,
) => void;
value: string;
editNode?: boolean;
nodeClass?: APIClassType;
@ -171,7 +182,11 @@ export type CodeAreaComponentType = {
export type FileComponentType = {
IOInputProps?;
disabled: boolean;
onChange: (value: string[] | string, skipSnapshot?: boolean) => void;
onChange: (
value: string[] | string,
dbValue?: boolean,
skipSnapshot?: boolean,
) => void;
value: string;
fileTypes: Array<string>;
onFileChange: (value: string) => void;
@ -204,7 +219,7 @@ export type IntComponentType = {
value: string;
disabled?: boolean;
rangeSpec: RangeSpecType;
onChange: (value: string, skipSnapshot?: boolean) => void;
onChange: (value: string, dbValue?: boolean, skipSnapshot?: boolean) => void;
editNode?: boolean;
id?: string;
};
@ -212,7 +227,7 @@ export type IntComponentType = {
export type FloatComponentType = {
value: string;
disabled?: boolean;
onChange: (value: string, skipSnapshot?: boolean) => void;
onChange: (value: string, dbValue?: boolean, skipSnapshot?: boolean) => void;
rangeSpec: RangeSpecType;
editNode?: boolean;
id?: string;

View file

@ -25,7 +25,7 @@ export type GlobalVariablesStore = {
) => void;
removeGlobalVariable: (name: string) => Promise<void>;
getVariableId: (name: string) => string | undefined;
unavaliableFields: { [name: string]: string };
unavaliableFields: { [name: string]: string } | undefined;
setUnavaliableFields: (fields: { [name: string]: string }) => void;
removeUnavaliableField: (field: string) => void;
};

View file

@ -1487,11 +1487,30 @@ export function isOutputType(type: string): boolean {
return OUTPUT_TYPES.has(type);
}
export function updateGroupRecursion(groupNode: NodeType, edges: Edge[]) {
export function updateGroupRecursion(
groupNode: NodeType,
edges: Edge[],
unavailableFields:
| {
[name: string]: string;
}
| undefined,
globalVariablesEntries: string[] | undefined,
) {
updateGlobalVariables(
groupNode.data.node,
unavailableFields,
globalVariablesEntries,
);
if (groupNode.data.node?.flow) {
groupNode.data.node.flow.data!.nodes.forEach((node) => {
if (node.data.node?.flow) {
updateGroupRecursion(node, node.data.node.flow.data!.edges);
updateGroupRecursion(
node,
node.data.node.flow.data!.edges,
unavailableFields,
globalVariablesEntries,
);
}
});
let newFlow = groupNode.data.node!.flow;
@ -1503,6 +1522,41 @@ export function updateGroupRecursion(groupNode: NodeType, edges: Edge[]) {
}
}
export function updateGlobalVariables(
node: APIClassType | undefined,
unavailableFields:
| {
[name: string]: string;
}
| undefined,
globalVariablesEntries: string[] | undefined,
) {
if (node && node.template) {
Object.keys(node.template).forEach((field) => {
if (
globalVariablesEntries &&
node!.template[field].load_from_db &&
!globalVariablesEntries.includes(node!.template[field].value)
) {
node!.template[field].value = "";
node!.template[field].load_from_db = false;
}
if (
!node!.template[field].load_from_db &&
node!.template[field].value === "" &&
unavailableFields &&
Object.keys(unavailableFields).includes(
node!.template[field].display_name ?? "",
)
) {
node!.template[field].value =
unavailableFields[node!.template[field].display_name ?? ""];
node!.template[field].load_from_db = true;
}
});
}
}
export function getGroupOutputNodeId(
flow: FlowType,
p_name: string,