{" "}
diff --git a/src/frontend/src/customNodes/hooks/use-fetch-data-on-mount.tsx b/src/frontend/src/customNodes/hooks/use-fetch-data-on-mount.tsx
new file mode 100644
index 000000000..3fc3fbe72
--- /dev/null
+++ b/src/frontend/src/customNodes/hooks/use-fetch-data-on-mount.tsx
@@ -0,0 +1,54 @@
+import { cloneDeep } from "lodash";
+import { useEffect } from "react";
+import useAlertStore from "../../stores/alertStore";
+import { ResponseErrorDetailAPI } from "../../types/api";
+
+const useFetchDataOnMount = (
+ data,
+ name,
+ handleUpdateValues,
+ setNode,
+ renderTooltips,
+ setIsLoading,
+) => {
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+
+ useEffect(() => {
+ async function fetchData() {
+ if (
+ (data.node?.template[name]?.real_time_refresh ||
+ data.node?.template[name]?.refresh_button) &&
+ // options can be undefined but not an empty array
+ (data.node?.template[name]?.options?.length ?? 0) === 0
+ ) {
+ setIsLoading(true);
+ try {
+ let newTemplate = await handleUpdateValues(name, data);
+
+ if (newTemplate) {
+ setNode(data.id, (oldNode) => {
+ let newNode = cloneDeep(oldNode);
+ newNode.data = {
+ ...newNode.data,
+ };
+ newNode.data.node.template = newTemplate;
+ return newNode;
+ });
+ }
+ } catch (error) {
+ let responseError = error as ResponseErrorDetailAPI;
+
+ setErrorData({
+ title: "Error while updating the Component",
+ list: [responseError.response.data.detail ?? "Unknown error"],
+ });
+ }
+ setIsLoading(false);
+ renderTooltips();
+ }
+ }
+ fetchData();
+ }, []); // Empty dependency array ensures that this effect runs only once, on mount
+};
+
+export default useFetchDataOnMount;
diff --git a/src/frontend/src/customNodes/hooks/use-handle-new-value.tsx b/src/frontend/src/customNodes/hooks/use-handle-new-value.tsx
new file mode 100644
index 000000000..7de830eda
--- /dev/null
+++ b/src/frontend/src/customNodes/hooks/use-handle-new-value.tsx
@@ -0,0 +1,75 @@
+import { cloneDeep } from "lodash";
+import useAlertStore from "../../stores/alertStore";
+import { ResponseErrorTypeAPI } from "../../types/api";
+
+const useHandleOnNewValue = (
+ data,
+ name,
+ takeSnapshot,
+ handleUpdateValues,
+ debouncedHandleUpdateValues,
+ setNode,
+ renderTooltips,
+ isLoading,
+ setIsLoading,
+) => {
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+
+ const handleOnNewValue = async (newValue, skipSnapshot = false) => {
+ const nodeTemplate = data.node!.template[name];
+ const currentValue = nodeTemplate.value;
+
+ if (currentValue !== newValue && !skipSnapshot) {
+ takeSnapshot();
+ }
+
+ const shouldUpdate =
+ data.node?.template[name].real_time_refresh &&
+ !data.node?.template[name].refresh_button &&
+ currentValue !== newValue;
+
+ const typeToDebounce = nodeTemplate.type;
+
+ nodeTemplate.value = newValue;
+
+ let newTemplate;
+ if (shouldUpdate) {
+ setIsLoading(true);
+ try {
+ if (["int"].includes(typeToDebounce)) {
+ newTemplate = await handleUpdateValues(name, data);
+ } else {
+ newTemplate = await debouncedHandleUpdateValues(name, data);
+ }
+ } catch (error) {
+ let responseError = error as ResponseErrorTypeAPI;
+ setErrorData({
+ title: "Error while updating the Component",
+ list: [responseError.response.data.detail.error ?? "Unknown error"],
+ });
+ }
+ setIsLoading(false);
+ }
+
+ setNode(data.id, (oldNode) => {
+ const newNode = cloneDeep(oldNode);
+ newNode.data = {
+ ...newNode.data,
+ };
+
+ if (data.node?.template[name].real_time_refresh && newTemplate) {
+ newNode.data.node.template = newTemplate;
+ } else {
+ newNode.data.node.template[name].value = newValue;
+ }
+
+ return newNode;
+ });
+
+ renderTooltips();
+ };
+
+ return { handleOnNewValue };
+};
+
+export default useHandleOnNewValue;
diff --git a/src/frontend/src/customNodes/hooks/use-handle-node-class.tsx b/src/frontend/src/customNodes/hooks/use-handle-node-class.tsx
new file mode 100644
index 000000000..412658d77
--- /dev/null
+++ b/src/frontend/src/customNodes/hooks/use-handle-node-class.tsx
@@ -0,0 +1,40 @@
+import { cloneDeep } from "lodash";
+
+const useHandleNodeClass = (
+ data,
+ name,
+ takeSnapshot,
+ setNode,
+ updateNodeInternals,
+ renderTooltips,
+) => {
+ const handleNodeClass = (newNodeClass, code) => {
+ if (!data.node) return;
+ if (data.node!.template[name].value !== code) {
+ takeSnapshot();
+ }
+
+ setNode(data.id, (oldNode) => {
+ let newNode = cloneDeep(oldNode);
+
+ newNode.data = {
+ ...newNode.data,
+ node: newNodeClass,
+ description: newNodeClass.description ?? data.node!.description,
+ display_name: newNodeClass.display_name ?? data.node!.display_name,
+ };
+
+ newNode.data.node.template[name].value = code;
+
+ return newNode;
+ });
+
+ updateNodeInternals(data.id);
+
+ renderTooltips();
+ };
+
+ return { handleNodeClass };
+};
+
+export default useHandleNodeClass;
diff --git a/src/frontend/src/customNodes/hooks/use-handle-refresh-buttons.tsx b/src/frontend/src/customNodes/hooks/use-handle-refresh-buttons.tsx
new file mode 100644
index 000000000..19f2a3c29
--- /dev/null
+++ b/src/frontend/src/customNodes/hooks/use-handle-refresh-buttons.tsx
@@ -0,0 +1,39 @@
+import { cloneDeep } from "lodash";
+import useAlertStore from "../../stores/alertStore";
+import { ResponseErrorDetailAPI } from "../../types/api";
+import { handleUpdateValues } from "../../utils/parameterUtils";
+
+const useHandleRefreshButtonPress = (setIsLoading, setNode, renderTooltips) => {
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+
+ const handleRefreshButtonPress = async (name, data) => {
+ setIsLoading(true);
+ try {
+ let newTemplate = await handleUpdateValues(name, data);
+
+ if (newTemplate) {
+ setNode(data.id, (oldNode) => {
+ let newNode = cloneDeep(oldNode);
+ newNode.data = {
+ ...newNode.data,
+ };
+ newNode.data.node.template = newTemplate;
+ return newNode;
+ });
+ }
+ } catch (error) {
+ let responseError = error as ResponseErrorDetailAPI;
+
+ setErrorData({
+ title: "Error while updating the Component",
+ list: [responseError.response.data.detail ?? "Unknown error"],
+ });
+ }
+ setIsLoading(false);
+ renderTooltips();
+ };
+
+ return { handleRefreshButtonPress };
+};
+
+export default useHandleRefreshButtonPress;
diff --git a/src/frontend/src/modals/IOModal/components/chatView/chatMessage/index.tsx b/src/frontend/src/modals/IOModal/components/chatView/chatMessage/index.tsx
index ee56f3a8f..086d87a45 100644
--- a/src/frontend/src/modals/IOModal/components/chatView/chatMessage/index.tsx
+++ b/src/frontend/src/modals/IOModal/components/chatView/chatMessage/index.tsx
@@ -114,19 +114,19 @@ export default function ChatMessage({
),
- [chat.message, chatMessage]
+ [chat.message, chatMessage],
)}
{chat.files && (
@@ -306,7 +306,7 @@ dark:prose-invert"
parts.push(
{chat.message[match[1]]}
-
+ ,
);
}
@@ -324,7 +324,7 @@ dark:prose-invert"
>
) : (
diff --git a/src/frontend/src/modals/editNodeModal/index.tsx b/src/frontend/src/modals/editNodeModal/index.tsx
index b853ecfab..0db4bdcf5 100644
--- a/src/frontend/src/modals/editNodeModal/index.tsx
+++ b/src/frontend/src/modals/editNodeModal/index.tsx
@@ -28,6 +28,7 @@ import {
LANGFLOW_SUPPORTED_TYPES,
limitScrollFieldsModal,
} from "../../constants/constants";
+import { Case } from "../../shared/components/caseComponent";
import useFlowStore from "../../stores/flowStore";
import { NodeDataType } from "../../types/flow";
import {
@@ -52,7 +53,7 @@ const EditNodeModal = forwardRef(
open: boolean;
setOpen: (open: boolean) => void;
},
- ref
+ ref,
) => {
const [myData, setMyData] = useState(data);
@@ -84,6 +85,10 @@ const EditNodeModal = forwardRef(
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
+ const type = (templateParam) => {
+ return myData.node?.template[templateParam].type;
+ };
+
return (
limitScrollFieldsModal
? "overflow-scroll overflow-x-hidden custom-scroll"
- : ""
+ : "",
)}
>
{nodeLength > 0 && (
@@ -138,8 +143,8 @@ const EditNodeModal = forwardRef(
templateParam.charAt(0) !== "_" &&
myData.node?.template[templateParam].show &&
LANGFLOW_SUPPORTED_TYPES.has(
- myData.node.template[templateParam].type
- )
+ myData.node!.template[templateParam].type,
+ ),
)
.map((templateParam, index) => {
let id = {
@@ -161,8 +166,8 @@ const EditNodeModal = forwardRef(
myData.node?.template[templateParam]
.proxy,
}
- : id
- )
+ : id,
+ ),
) ?? false;
return (
{myData.node?.template[templateParam]
.display_name
- ? myData.node.template[templateParam]
+ ? myData.node!.template[templateParam]
.display_name
: myData.node?.template[templateParam]
.name}
@@ -198,58 +202,62 @@ const EditNodeModal = forwardRef(
- {myData.node?.template[templateParam].type ===
- "str" &&
- !myData.node.template[templateParam].options ? (
+
- {myData.node.template[templateParam]
+ {myData.node!.template[templateParam]
?.list ? (
{
handleOnNewValue(
value,
- templateParam
+ templateParam,
);
}}
/>
- ) : myData.node.template[templateParam]
+ ) : myData.node!.template[templateParam]
.multiline ? (
{
handleOnNewValue(
value,
- templateParam
+ templateParam,
);
}}
/>
@@ -274,8 +282,13 @@ const EditNodeModal = forwardRef(
/>
)}
- ) : myData.node?.template[templateParam]
- .type === "NestedDict" ? (
+
+
+
- ) : myData.node?.template[templateParam]
- .type === "dict" ? (
+
+
+
1
? "my-3"
- : ""
+ : "",
)}
>
- ) : myData.node?.template[templateParam]
- .type === "bool" ? (
+
+
+
{" "}
{
handleOnNewValue(
isEnabled,
- templateParam
+ templateParam,
);
}}
size="small"
editNode={true}
/>
- ) : myData.node?.template[templateParam]
- .type === "float" ? (
+
+
+
{
@@ -392,32 +416,38 @@ const EditNodeModal = forwardRef(
}}
/>
- ) : myData.node?.template[templateParam]
- .type === "str" &&
- myData.node.template[templateParam]
- .options ? (
+
+
+
handleOnNewValue(value, templateParam)
}
value={
- myData.node.template[templateParam]
+ myData.node!.template[templateParam]
.value ?? "Choose an option"
}
id={
"dropdown-edit-" +
- myData.node.template[templateParam].name
+ myData.node!.template[templateParam]
+ .name
}
>
- ) : myData.node?.template[templateParam]
- .type === "int" ? (
+
+
+
{
@@ -439,21 +470,24 @@ const EditNodeModal = forwardRef(
}}
/>
- ) : myData.node?.template[templateParam]
- .type === "file" ? (
+
+
+
{
handleOnNewValue(value, templateParam);
}}
fileTypes={
- myData.node.template[templateParam]
+ myData.node!.template[templateParam]
.fileTypes
}
onFileChange={(filePath: string) => {
@@ -463,8 +497,11 @@ const EditNodeModal = forwardRef(
}}
>
- ) : myData.node?.template[templateParam]
- .type === "prompt" ? (
+
+
+
{
@@ -486,21 +523,26 @@ const EditNodeModal = forwardRef(
}}
id={
"prompt-area-edit-" +
- myData.node.template[templateParam].name
+ myData.node!.template[templateParam]
+ .name
}
data-testid={
"modal-prompt-input-" +
- myData.node.template[templateParam].name
+ myData.node!.template[templateParam]
+ .name
}
/>
- ) : myData.node?.template[templateParam]
- .type === "code" ? (
+
+
+
{
@@ -524,16 +566,16 @@ const EditNodeModal = forwardRef(
}}
id={
"code-area-edit" +
- myData.node.template[templateParam].name
+ myData.node!.template[templateParam]
+ .name
}
/>
- ) : myData.node?.template[templateParam]
- .type === "Any" ? (
- "-"
- ) : (
-
- )}
+
+
+
+ <>->
+
@@ -588,7 +630,7 @@ const EditNodeModal = forwardRef(
);
- }
+ },
);
export default EditNodeModal;
diff --git a/src/frontend/src/pages/FlowPage/components/SelectionMenuComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/SelectionMenuComponent/index.tsx
index 949e22fb6..a6d83153c 100644
--- a/src/frontend/src/pages/FlowPage/components/SelectionMenuComponent/index.tsx
+++ b/src/frontend/src/pages/FlowPage/components/SelectionMenuComponent/index.tsx
@@ -1,8 +1,8 @@
import { useEffect, useState } from "react";
import { NodeToolbar } from "reactflow";
import { GradientGroup } from "../../../../icons/GradientSparkles";
-import { validateSelection } from "../../../../utils/reactflowUtils";
import useFlowStore from "../../../../stores/flowStore";
+import { validateSelection } from "../../../../utils/reactflowUtils";
export default function SelectionMenu({
onClick,
nodes,
@@ -62,7 +62,11 @@ export default function SelectionMenu({
}
>
diff --git a/src/frontend/src/pages/FlowPage/index.tsx b/src/frontend/src/pages/FlowPage/index.tsx
index b69015395..886ab5150 100644
--- a/src/frontend/src/pages/FlowPage/index.tsx
+++ b/src/frontend/src/pages/FlowPage/index.tsx
@@ -3,14 +3,14 @@ import { useParams } from "react-router-dom";
import FlowToolbar from "../../components/chatComponent";
import Header from "../../components/headerComponent";
import { useDarkStore } from "../../stores/darkStore";
+import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import Page from "./components/PageComponent";
import ExtraSidebar from "./components/extraSidebarComponent";
-import useFlowStore from "../../stores/flowStore";
export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
const setCurrentFlowId = useFlowsManagerStore(
- (state) => state.setCurrentFlowId
+ (state) => state.setCurrentFlowId,
);
const version = useDarkStore((state) => state.version);
const setOnFlowPage = useFlowStore((state) => state.setOnFlowPage);
@@ -21,7 +21,7 @@ export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
useEffect(() => {
setCurrentFlowId(id!);
setOnFlowPage(true);
-
+
return () => {
setOnFlowPage(false);
};
diff --git a/src/frontend/src/pages/Playground/index.tsx b/src/frontend/src/pages/Playground/index.tsx
index 9d538494b..b65c64c66 100644
--- a/src/frontend/src/pages/Playground/index.tsx
+++ b/src/frontend/src/pages/Playground/index.tsx
@@ -1,64 +1,69 @@
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
-import useFlowsManagerStore from "../../stores/flowsManagerStore";
-import { getComponent } from "../../controllers/API";
-import cloneFLowWithParent from "../../utils/storeUtils";
import LoadingComponent from "../../components/loadingComponent";
-import useFlowStore from "../../stores/flowStore";
+import { getComponent } from "../../controllers/API";
import IOModal from "../../modals/IOModal";
+import useFlowStore from "../../stores/flowStore";
+import useFlowsManagerStore from "../../stores/flowsManagerStore";
+import cloneFLowWithParent from "../../utils/storeUtils";
export default function PlaygroundPage() {
- const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
- const getFlowById = useFlowsManagerStore((state) => state.getFlowById);
- const setCurrentFlowId = useFlowsManagerStore((state) => state.setCurrentFlowId);
- const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
- const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
- const setNodes = useFlowStore((state) => state.setNodes);
- const setEdges = useFlowStore((state) => state.setEdges);
- const cleanFlowPool = useFlowStore((state) => state.CleanFlowPool);
+ const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
+ const getFlowById = useFlowsManagerStore((state) => state.getFlowById);
+ const setCurrentFlowId = useFlowsManagerStore(
+ (state) => state.setCurrentFlowId,
+ );
+ const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
+ const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
+ const setNodes = useFlowStore((state) => state.setNodes);
+ const setEdges = useFlowStore((state) => state.setEdges);
+ const cleanFlowPool = useFlowStore((state) => state.CleanFlowPool);
- const { id } = useParams();
- const [loading, setLoading] = useState(true);
- async function getFlowData() {
- const res = await getComponent(id!);
- const newFlow = cloneFLowWithParent(res, res.id, false, true);
- return newFlow;
+ const { id } = useParams();
+ const [loading, setLoading] = useState(true);
+ async function getFlowData() {
+ const res = await getComponent(id!);
+ const newFlow = cloneFLowWithParent(res, res.id, false, true);
+ return newFlow;
+ }
+
+ // Set flow tab id
+ useEffect(() => {
+ console.log("id", id);
+ if (getFlowById(id!)) {
+ setCurrentFlowId(id!);
+ } else {
+ getFlowData().then((flow) => {
+ setCurrentFlow(flow);
+ });
}
+ }, [id]);
- // Set flow tab id
- useEffect(() => {
- console.log("id", id);
- if (getFlowById(id!)) {
- setCurrentFlowId(id!);
- }
- else {
- getFlowData().then((flow) => {
- setCurrentFlow(flow);
- });
- }
- }, [id]);
+ useEffect(() => {
+ if (currentFlow) {
+ setNodes(currentFlow?.data?.nodes ?? [], true);
+ setEdges(currentFlow?.data?.edges ?? [], true);
+ cleanFlowPool();
+ setLoading(false);
+ }
+ return () => {
+ setNodes([], true);
+ setEdges([], true);
+ cleanFlowPool();
+ };
+ }, [currentFlow]);
- useEffect(() => {
- if (currentFlow) {
- setNodes(currentFlow?.data?.nodes ?? [], true);
- setEdges(currentFlow?.data?.edges ?? [], true);
- cleanFlowPool();
- setLoading(false);
- }
- return () => {
- setNodes([], true);
- setEdges([], true);
- cleanFlowPool();
- };
- }, [currentFlow]);
-
-
- return (
-
- {loading ?
:
-
{}} isPlayground>
- <>>
- }
+ return (
+
+ {loading ? (
+
+
- )
-}
\ No newline at end of file
+ ) : (
+
{}} isPlayground>
+ <>>
+
+ )}
+
+ );
+}
diff --git a/src/frontend/src/pages/SettingsPage/pages/GeneralPage/index.tsx b/src/frontend/src/pages/SettingsPage/pages/GeneralPage/index.tsx
index 7ece3678b..5c53e0aa8 100644
--- a/src/frontend/src/pages/SettingsPage/pages/GeneralPage/index.tsx
+++ b/src/frontend/src/pages/SettingsPage/pages/GeneralPage/index.tsx
@@ -32,11 +32,11 @@ import { gradients } from "../../../../utils/styleUtils";
export default function GeneralPage() {
const setCurrentFlowId = useFlowsManagerStore(
- (state) => state.setCurrentFlowId
+ (state) => state.setCurrentFlowId,
);
const [inputState, setInputState] = useState
(
- CONTROL_PATCH_USER_STATE
+ CONTROL_PATCH_USER_STATE,
);
const { autoLogin } = useContext(AuthContext);
@@ -113,110 +113,109 @@ export default function GeneralPage() {
-
-
{
- handlePatchGradient();
- event.preventDefault();
- }}
- >
-
-
- Profile Gradient
-
- Choose the gradient that appears as your profile picture.
-
-
-
-
-
{
+ handlePatchGradient();
+ event.preventDefault();
+ }}
+ >
+
+
+ Profile Gradient
+
+ Choose the gradient that appears as your profile picture.
+
+
+
+
+ {
+ handleInput({ target: { name: "gradient", value } });
+ }}
+ />
+
+
+
+
+ Save
+
+
+
+
+ {!autoLogin && (
+ {
+ handlePatchPassword();
+ event.preventDefault();
+ }}
+ >
+
+
+ Password
+
+ Type your new password and confirm it.
+
+
+
+
+
+ {
- handleInput({ target: { name: "gradient", value } });
+ handleInput({ target: { name: "password", value } });
}}
+ value={password}
+ isForm
+ password={true}
+ placeholder="Password"
+ className="w-full"
+ />
+
+ Please enter your password
+
+
+
+ {
+ handleInput({
+ target: { name: "cnfPassword", value },
+ });
+ }}
+ value={cnfPassword}
+ isForm
+ password={true}
+ placeholder="Confirm Password"
+ className="w-full"
/>
-
-
-
-
- Save
-
-
-
- {!autoLogin && (
- {
- handlePatchPassword();
- event.preventDefault();
- }}
- >
-
-
- Password
-
- Type your new password and confirm it.
-
-
-
-
-
- {
- handleInput({ target: { name: "password", value } });
- }}
- value={password}
- isForm
- password={true}
- placeholder="Password"
- className="w-full"
- />
-
- Please enter your password
-
-
-
- {
- handleInput({
- target: { name: "cnfPassword", value },
- });
- }}
- value={cnfPassword}
- isForm
- password={true}
- placeholder="Confirm Password"
- className="w-full"
- />
-
- Please confirm your password
-
-
-
-
-
-
- Save
-
-
-
-
+
+ Please confirm your password
+
+
+
+
+
+
+ Save
+
+
+
+
)}
diff --git a/src/frontend/src/shared/components/caseComponent/index.tsx b/src/frontend/src/shared/components/caseComponent/index.tsx
new file mode 100644
index 000000000..c5330af3d
--- /dev/null
+++ b/src/frontend/src/shared/components/caseComponent/index.tsx
@@ -0,0 +1,15 @@
+import { memo } from "react";
+
+type BooleanLike = boolean | string | number | null | undefined;
+
+type Props = {
+ condition: (() => BooleanLike) | BooleanLike;
+ children: React.ReactNode | any;
+};
+
+export const Case = memo(({ condition, children }: Props) => {
+ const conditionResult =
+ typeof condition === "function" ? condition() : condition;
+
+ return conditionResult ? children : null;
+});
diff --git a/src/frontend/src/stores/flowStore.ts b/src/frontend/src/stores/flowStore.ts
index 44879aba8..8554d2f55 100644
--- a/src/frontend/src/stores/flowStore.ts
+++ b/src/frontend/src/stores/flowStore.ts
@@ -44,7 +44,6 @@ import { getInputsAndOutputs } from "../utils/storeUtils";
import useAlertStore from "./alertStore";
import { useDarkStore } from "./darkStore";
import useFlowsManagerStore from "./flowsManagerStore";
-import FlowPage from "../pages/FlowPage";
// this is our useStore hook that we can use in our components to get parts of the store and call actions
const useFlowStore = create((set, get) => ({
@@ -465,8 +464,13 @@ const useFlowStore = create((set, get) => ({
status: BuildStatus,
runId: string,
) {
+ console.log("handleBuildUpdate", vertexBuildData, status, runId);
if (vertexBuildData && vertexBuildData.inactivated_vertices) {
get().removeFromVerticesBuild(vertexBuildData.inactivated_vertices);
+ get().updateBuildStatus(
+ vertexBuildData.inactivated_vertices,
+ BuildStatus.INACTIVE,
+ );
}
if (vertexBuildData.next_vertices_ids) {
@@ -483,9 +487,13 @@ const useFlowStore = create((set, get) => ({
const next_vertices_ids = vertexBuildData.next_vertices_ids.filter(
(id) => !vertexBuildData.inactivated_vertices?.includes(id),
);
+ const top_level_vertices = vertexBuildData.top_level_vertices.filter(
+ (vertex) =>
+ !vertexBuildData.inactivated_vertices?.includes(vertex.id),
+ );
const nextVertices: VertexLayerElementType[] = zip(
next_vertices_ids,
- vertexBuildData.top_level_vertices,
+ top_level_vertices,
).map(([id, reference]) => ({ id: id!, reference }));
const newLayers = [
@@ -502,10 +510,7 @@ const useFlowStore = create((set, get) => ({
runId: runId,
verticesToRun: get().verticesBuild!.verticesToRun,
});
- get().updateBuildStatus(
- vertexBuildData.top_level_vertices,
- BuildStatus.TO_BUILD,
- );
+ get().updateBuildStatus(top_level_vertices, BuildStatus.TO_BUILD);
}
get().addDataToFlowPool(
diff --git a/src/frontend/src/stores/flowsManagerStore.ts b/src/frontend/src/stores/flowsManagerStore.ts
index b9bc69e52..0ef07b7c2 100644
--- a/src/frontend/src/stores/flowsManagerStore.ts
+++ b/src/frontend/src/stores/flowsManagerStore.ts
@@ -52,7 +52,6 @@ const useFlowsManagerStore = create((set, get) => ({
currentFlow: flow,
currentFlowId: flow.id,
}));
-
},
getFlowById: (id: string) => {
return get().flows.find((flow) => flow.id === id);
@@ -83,13 +82,13 @@ const useFlowsManagerStore = create((set, get) => ({
const { data, flows } = processFlows(dbData, false);
get().setExamples(
flows.filter(
- (f) => f.folder === STARTER_FOLDER_NAME && !f.user_id
- )
+ (f) => f.folder === STARTER_FOLDER_NAME && !f.user_id,
+ ),
);
get().setFlows(
flows.filter(
- (f) => !(f.folder === STARTER_FOLDER_NAME && !f.user_id)
- )
+ (f) => !(f.folder === STARTER_FOLDER_NAME && !f.user_id),
+ ),
);
useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data },
@@ -115,7 +114,7 @@ const useFlowsManagerStore = create((set, get) => ({
if (get().currentFlow) {
get().saveFlow(
{ ...get().currentFlow!, data: { nodes, edges, viewport } },
- true
+ true,
);
}
},
@@ -141,7 +140,7 @@ const useFlowsManagerStore = create((set, get) => ({
return updatedFlow;
}
return flow;
- })
+ }),
);
//update tabs state
@@ -191,7 +190,7 @@ const useFlowsManagerStore = create((set, get) => ({
newProject: Boolean,
flow?: FlowType,
override?: boolean,
- position?: XYPosition
+ position?: XYPosition,
): Promise => {
if (newProject) {
let flowData = flow
@@ -255,7 +254,7 @@ const useFlowsManagerStore = create((set, get) => ({
.getState()
.paste(
{ nodes: flow!.data!.nodes, edges: flow!.data!.edges },
- position ?? { x: 10, y: 10 }
+ position ?? { x: 10, y: 10 },
);
}
},
@@ -265,7 +264,7 @@ const useFlowsManagerStore = create((set, get) => ({
if (index >= 0) {
deleteFlowFromDatabase(id).then(() => {
const { data, flows } = processFlows(
- get().flows.filter((flow) => flow.id !== id)
+ get().flows.filter((flow) => flow.id !== id),
);
get().setFlows(flows);
set({ isLoading: false });
@@ -285,7 +284,7 @@ const useFlowsManagerStore = create((set, get) => ({
return new Promise((resolve) => {
let componentFlow = get().flows.find(
(componentFlow) =>
- componentFlow.is_component && componentFlow.name === key
+ componentFlow.is_component && componentFlow.name === key,
);
if (componentFlow) {
@@ -367,7 +366,7 @@ const useFlowsManagerStore = create((set, get) => ({
return get().addFlow(
true,
createFlowComponent(component, useDarkStore.getState().version),
- override
+ override,
);
},
takeSnapshot: () => {
@@ -388,7 +387,7 @@ const useFlowsManagerStore = create((set, get) => ({
if (pastLength > 0) {
past[currentFlowId] = past[currentFlowId].slice(
pastLength - defaultOptions.maxHistorySize + 1,
- pastLength
+ pastLength,
);
past[currentFlowId].push(newState);
diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts
index 6b3f9b224..e277775a7 100644
--- a/src/frontend/src/types/components/index.ts
+++ b/src/frontend/src/types/components/index.ts
@@ -400,6 +400,7 @@ export type StoreApiKeyType = {
export type groupedObjType = {
family: string;
type: string;
+ display_name?: string;
};
export type nodeGroupedObjType = {
@@ -663,6 +664,7 @@ export type codeTabsPropsType = {
};
activeTweaks?: boolean;
setActiveTweaks?: (value: boolean) => void;
+ allowExport?: boolean;
};
export type crashComponentPropsType = {
diff --git a/src/frontend/src/types/zustand/flow/index.ts b/src/frontend/src/types/zustand/flow/index.ts
index c975eec8d..71ff76f55 100644
--- a/src/frontend/src/types/zustand/flow/index.ts
+++ b/src/frontend/src/types/zustand/flow/index.ts
@@ -70,26 +70,32 @@ export type FlowStoreType = {
state:
| FlowState
| undefined
- | ((oldState: FlowState | undefined) => FlowState)
+ | ((oldState: FlowState | undefined) => FlowState),
) => void;
nodes: Node[];
edges: Edge[];
onNodesChange: OnNodesChange;
onEdgesChange: OnEdgesChange;
- setNodes: (update: Node[] | ((oldState: Node[]) => Node[]),skipSave?:boolean) => void;
- setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[]),skipSave?:boolean) => void;
+ setNodes: (
+ update: Node[] | ((oldState: Node[]) => Node[]),
+ skipSave?: boolean,
+ ) => void;
+ setEdges: (
+ update: Edge[] | ((oldState: Edge[]) => Edge[]),
+ skipSave?: boolean,
+ ) => void;
setNode: (id: string, update: Node | ((oldState: Node) => Node)) => void;
getNode: (id: string) => Node | undefined;
deleteNode: (nodeId: string | Array) => void;
deleteEdge: (edgeId: string | Array) => void;
paste: (
selection: { nodes: any; edges: any },
- position: { x: number; y: number; paneX?: number; paneY?: number }
+ position: { x: number; y: number; paneX?: number; paneY?: number },
) => void;
lastCopiedSelection: { nodes: any; edges: any } | null;
setLastCopiedSelection: (
newSelection: { nodes: any; edges: any } | null,
- isCrop?: boolean
+ isCrop?: boolean,
) => void;
cleanFlow: () => void;
setFilterEdge: (newState) => void;
@@ -113,7 +119,7 @@ export type FlowStoreType = {
verticesLayers: VertexLayerElementType[][];
runId: string;
verticesToRun: string[];
- } | null
+ } | null,
) => void;
addToVerticesBuild: (vertices: string[]) => void;
removeFromVerticesBuild: (vertices: string[]) => void;
@@ -131,7 +137,7 @@ export type FlowStoreType = {
updateFlowPool: (
nodeId: string,
data: FlowPoolObjectType | ChatOutputType | chatInputType,
- buildId?: string
+ buildId?: string,
) => void;
getNodePosition: (nodeId: string) => { x: number; y: number };
};
diff --git a/src/frontend/src/types/zustand/globalVariables/index.ts b/src/frontend/src/types/zustand/globalVariables/index.ts
index 752336c19..d22170ed2 100644
--- a/src/frontend/src/types/zustand/globalVariables/index.ts
+++ b/src/frontend/src/types/zustand/globalVariables/index.ts
@@ -21,11 +21,11 @@ export type GlobalVariablesStore = {
id: string,
type?: string,
default_fields?: string[],
- value?: string
+ value?: string,
) => void;
removeGlobalVariable: (name: string) => Promise;
getVariableId: (name: string) => string | undefined;
- unavaliableFields: {[name: string]: string};
- setUnavaliableFields: (fields: {[name: string]: string}) => void;
+ unavaliableFields: { [name: string]: string };
+ setUnavaliableFields: (fields: { [name: string]: string }) => void;
removeUnavaliableField: (field: string) => void;
};
diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts
index 64008aa68..1e67fdf93 100644
--- a/src/frontend/src/utils/reactflowUtils.ts
+++ b/src/frontend/src/utils/reactflowUtils.ts
@@ -457,7 +457,8 @@ export function getConnectedNodes(
return nodes.filter((node) => node.id === targetId || node.id === sourceId);
}
-export function convertObjToArray(singleObject: object | string) {
+export function convertObjToArray(singleObject: object | string, type: string) {
+ if (type !== "dict") return [{ "": "" }];
if (typeof singleObject === "string") {
singleObject = JSON.parse(singleObject);
}
@@ -736,28 +737,30 @@ export function validateSelection(
selection: OnSelectionChangeParams,
edges: Edge[],
): Array {
+ const clonedSelection = cloneDeep(selection);
+ const clonedEdges = cloneDeep(edges);
//add edges to selection if selection mode selected only nodes
- if (selection.edges.length === 0) {
- selection.edges = edges;
+ if (clonedSelection.edges.length === 0) {
+ clonedSelection.edges = clonedEdges;
}
// get only edges that are connected to the nodes in the selection
// first creates a set of all the nodes ids
- let nodesSet = new Set(selection.nodes.map((n) => n.id));
+ let nodesSet = new Set(clonedSelection.nodes.map((n) => n.id));
// then filter the edges that are connected to the nodes in the set
- let connectedEdges = selection.edges.filter(
+ let connectedEdges = clonedSelection.edges.filter(
(e) => nodesSet.has(e.source) && nodesSet.has(e.target),
);
// add the edges to the selection
- selection.edges = connectedEdges;
+ clonedSelection.edges = connectedEdges;
let errorsArray: Array = [];
// check if there is more than one node
- if (selection.nodes.length < 2) {
+ if (clonedSelection.nodes.length < 2) {
errorsArray.push("Please select more than one node");
}
if (
- selection.nodes.some(
+ clonedSelection.nodes.some(
(node) =>
isInputNode(node.data as NodeDataType) ||
isOutputNode(node.data as NodeDataType),
@@ -769,8 +772,8 @@ export function validateSelection(
}
//check if there are two or more nodes with free outputs
if (
- selection.nodes.filter(
- (n) => !selection.edges.some((e) => e.source === n.id),
+ clonedSelection.nodes.filter(
+ (n) => !clonedSelection.edges.some((e) => e.source === n.id),
).length > 1
) {
errorsArray.push("Please select only one node with free outputs");
@@ -778,10 +781,10 @@ export function validateSelection(
// check if there is any node that does not have any connection
if (
- selection.nodes.some(
+ clonedSelection.nodes.some(
(node) =>
- !selection.edges.some((edge) => edge.target === node.id) &&
- !selection.edges.some((edge) => edge.source === node.id),
+ !clonedSelection.edges.some((edge) => edge.target === node.id) &&
+ !clonedSelection.edges.some((edge) => edge.source === node.id),
)
) {
errorsArray.push("Please select only nodes that are connected");
diff --git a/src/frontend/src/utils/storeUtils.ts b/src/frontend/src/utils/storeUtils.ts
index ad4d04529..c0a0fc440 100644
--- a/src/frontend/src/utils/storeUtils.ts
+++ b/src/frontend/src/utils/storeUtils.ts
@@ -7,15 +7,14 @@ export default function cloneFLowWithParent(
flow: FlowType,
parent: string,
is_component: boolean,
- keepId=false
+ keepId = false,
) {
let childFLow = cloneDeep(flow);
childFLow.parent = parent;
- if(!keepId){
+ if (!keepId) {
childFLow.id = "";
- }
- else{
- childFLow.id = uniqueId()+"-"+childFLow.id;
+ } else {
+ childFLow.id = uniqueId() + "-" + childFLow.id;
}
childFLow.is_component = is_component;
return childFLow;
@@ -23,7 +22,7 @@ export default function cloneFLowWithParent(
export function getTagsIds(
tags: string[],
- tagListId: { name: string; id: string }[]
+ tagListId: { name: string; id: string }[],
) {
return tags
.map((tag) => tagListId.find((tagObj) => tagObj.name === tag))!
diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts
index bb4011eac..15b15d282 100644
--- a/src/frontend/src/utils/styleUtils.ts
+++ b/src/frontend/src/utils/styleUtils.ts
@@ -143,6 +143,7 @@ import {
} from "lucide-react";
import { FaApple, FaGithub } from "react-icons/fa";
import { AWSIcon } from "../icons/AWS";
+import { FaDiscord } from "react-icons/fa";
import { AirbyteIcon } from "../icons/Airbyte";
import { AnthropicIcon } from "../icons/Anthropic";
import { AstraDBIcon } from "../icons/AstraDB";
@@ -517,4 +518,5 @@ export const nodeIconsLucide: iconsType = {
Command,
ArrowBigUp,
Dot,
+ Discord: FaDiscord,
};
diff --git a/src/frontend/src/utils/utils.ts b/src/frontend/src/utils/utils.ts
index 23bd275ea..c1ae07039 100644
--- a/src/frontend/src/utils/utils.ts
+++ b/src/frontend/src/utils/utils.ts
@@ -190,6 +190,7 @@ export function getPythonApiCode(
isAuth: boolean,
tweaksBuildedObject,
): string {
+ const tweaksObject = tweaksBuildedObject[0];
return `import requests
from typing import Optional
@@ -197,7 +198,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 = ${JSON.stringify(tweaksBuildedObject, null, 2)}
+TWEAKS = ${JSON.stringify(tweaksObject, null, 2)}
def run_flow(message: str,
flow_id: str,
@@ -247,6 +248,8 @@ export function getCurlCode(
isAuth: boolean,
tweaksBuildedObject,
): string {
+ const tweaksObject = tweaksBuildedObject[0];
+
return `curl -X POST \\
${window.location.protocol}//${
window.location.host
@@ -257,7 +260,7 @@ export function getCurlCode(
-d '{"input_value": "message",
"output_type": "chat",
"input_type": "chat",
- "tweaks": ${JSON.stringify(tweaksBuildedObject, null, 2)}'
+ "tweaks": ${JSON.stringify(tweaksObject, null, 2)}'
`;
}
@@ -285,11 +288,14 @@ export function getOutputIds(flow) {
* @returns {string} - The python code
*/
export function getPythonCode(flowName: string, tweaksBuildedObject): string {
+ const tweaksObject = tweaksBuildedObject[0];
+
return `from langflow.load import run_flow_from_json
-TWEAKS = ${JSON.stringify(tweaksBuildedObject, null, 2)}
+TWEAKS = ${JSON.stringify(tweaksObject, null, 2)}
result = run_flow_from_json(flow="${flowName}.json",
input_value="message",
+ fallback_to_env_vars=True, # False by default
tweaks=TWEAKS)`;
}
@@ -481,6 +487,120 @@ export function sensitiveSort(a: string, b: string): number {
return a.localeCompare(b);
}
}
+
+export function groupByFamily(
+ data: APIDataType,
+ baseClasses: string,
+ left: boolean,
+ flow?: NodeType[],
+): groupedObjType[] {
+ const baseClassesSet = new Set(baseClasses.split("\n"));
+ let arrOfPossibleInputs: Array<{
+ category: string;
+ nodes: nodeGroupedObjType[];
+ full: boolean;
+ display_name?: string;
+ }> = [];
+ let arrOfPossibleOutputs: Array<{
+ category: string;
+ nodes: nodeGroupedObjType[];
+ full: boolean;
+ display_name?: string;
+ }> = [];
+ let checkedNodes = new Map();
+ const excludeTypes = new Set(["bool", "float", "code", "file", "int"]);
+
+ const checkBaseClass = (template: TemplateVariableType) => {
+ return (
+ template.type &&
+ template.show &&
+ ((!excludeTypes.has(template.type) &&
+ baseClassesSet.has(template.type)) ||
+ (template.input_types &&
+ template.input_types.some((inputType) =>
+ baseClassesSet.has(inputType),
+ )))
+ );
+ };
+
+ if (flow) {
+ // se existir o flow
+ for (const node of flow) {
+ // para cada node do flow
+ if (node!.data!.node!.flow || !node!.data!.node!.template) break; // não faz nada se o node for um group
+ const nodeData = node.data;
+
+ const foundNode = checkedNodes.get(nodeData.type); // verifica se o tipo do node já foi checado
+ checkedNodes.set(nodeData.type, {
+ hasBaseClassInTemplate:
+ foundNode?.hasBaseClassInTemplate ||
+ Object.values(nodeData.node!.template).some(checkBaseClass),
+ hasBaseClassInBaseClasses:
+ foundNode?.hasBaseClassInBaseClasses ||
+ nodeData.node!.base_classes.some((baseClass) =>
+ baseClassesSet.has(baseClass),
+ ), //seta como anterior ou verifica se o node tem base class
+ displayName: nodeData.node?.display_name,
+ });
+ }
+ }
+
+ for (const [d, nodes] of Object.entries(data)) {
+ let tempInputs: nodeGroupedObjType[] = [],
+ tempOutputs: nodeGroupedObjType[] = [];
+
+ for (const [n, node] of Object.entries(nodes!)) {
+ let foundNode = checkedNodes.get(n);
+
+ if (!foundNode) {
+ foundNode = {
+ hasBaseClassInTemplate: Object.values(node!.template).some(
+ checkBaseClass,
+ ),
+ hasBaseClassInBaseClasses: node!.base_classes.some((baseClass) =>
+ baseClassesSet.has(baseClass),
+ ),
+ displayName: node?.display_name,
+ };
+ }
+
+ if (foundNode.hasBaseClassInTemplate)
+ tempInputs.push({ node: n, displayName: foundNode.displayName });
+ if (foundNode.hasBaseClassInBaseClasses)
+ tempOutputs.push({ node: n, displayName: foundNode.displayName });
+ }
+
+ const totalNodes = Object.keys(nodes!).length;
+
+ if (tempInputs.length)
+ arrOfPossibleInputs.push({
+ category: d,
+ nodes: tempInputs,
+ full: tempInputs.length === totalNodes,
+ });
+ if (tempOutputs.length)
+ arrOfPossibleOutputs.push({
+ category: d,
+ nodes: tempOutputs,
+ full: tempOutputs.length === totalNodes,
+ });
+ }
+
+ return left
+ ? arrOfPossibleOutputs.map((output) => ({
+ family: output.category,
+ type: output.full
+ ? ""
+ : output.nodes.map((item) => item.node).join(", "),
+ display_name: "",
+ }))
+ : arrOfPossibleInputs.map((input) => ({
+ family: input.category,
+ type: input.full ? "" : input.nodes.map((item) => item.node).join(", "),
+ display_name: input.nodes.map((item) => item.displayName).join(", "),
+ }));
+}
+
// this function is used to get the set of keys from an object
export function getSetFromObject(obj: object, key?: string): Set {
const set = new Set();
diff --git a/src/frontend/tests/end-to-end/deleteComponentFlows.spec.ts b/src/frontend/tests/end-to-end/deleteComponentFlows.spec.ts
index ecb590aa9..7ca9c482c 100644
--- a/src/frontend/tests/end-to-end/deleteComponentFlows.spec.ts
+++ b/src/frontend/tests/end-to-end/deleteComponentFlows.spec.ts
@@ -4,6 +4,14 @@ test("shoud delete a flow", async ({ page }) => {
await page.goto("/");
await page.waitForTimeout(2000);
await page.getByText("Store").nth(0).click();
+
+ await page.getByText("API Key", { exact: true }).click();
+ await page
+ .getByPlaceholder("Insert your API Key", { exact: true })
+ .fill(process.env.STORE_API_KEY ?? "");
+ await page.getByText("Save").last().click();
+ await page.waitForTimeout(8000);
+
await page.getByTestId("install-Website Content QA").click();
await page.waitForTimeout(5000);
await page.getByText("My Collection").nth(0).click();
@@ -16,7 +24,9 @@ test("shoud delete a flow", async ({ page }) => {
await page.getByTestId("icon-Trash2").first().click();
await page.waitForTimeout(2000);
});
- await page.getByText("Confirm deletion of component?").isVisible();
+ await page
+ .getByText("Are you sure you want to delete the selected component?")
+ .isVisible();
await page.getByText("Delete").nth(1).click();
await page.waitForTimeout(1000);
await page.getByText("Successfully").first().isVisible();
@@ -39,7 +49,9 @@ test("shoud delete a component", async ({ page }) => {
await page.getByTestId("icon-Trash2").first().click();
await page.waitForTimeout(2000);
});
- await page.getByText("Confirm deletion of component?").isVisible();
+ await page
+ .getByText("Are you sure you want to delete the selected component?")
+ .isVisible();
await page.getByText("Delete").nth(1).click();
await page.waitForTimeout(1000);
await page.getByText("Successfully").first().isVisible();
diff --git a/src/frontend/tests/end-to-end/nestedComponent.spec.ts b/src/frontend/tests/end-to-end/nestedComponent.spec.ts
index a6b358f71..60e1a9fb9 100644
--- a/src/frontend/tests/end-to-end/nestedComponent.spec.ts
+++ b/src/frontend/tests/end-to-end/nestedComponent.spec.ts
@@ -15,7 +15,7 @@ test("NestedComponent", async ({ page }) => {
}
while (modalCount === 0) {
- await page.locator('//*[@id="new-project-btn"]').click();
+ await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(5000);
modalCount = await page.getByTestId("modal-title")?.count();
}
@@ -41,7 +41,7 @@ test("NestedComponent", async ({ page }) => {
await page.locator('//*[@id="showpool_threads"]').click();
expect(
- await page.locator('//*[@id="showpool_threads"]').isChecked()
+ await page.locator('//*[@id="showpool_threads"]').isChecked(),
).toBeTruthy();
//showtext_key
@@ -53,182 +53,140 @@ test("NestedComponent", async ({ page }) => {
await page.locator('//*[@id="showindex_name"]').click();
expect(
- await page.locator('//*[@id="showindex_name"]').isChecked()
+ await page.locator('//*[@id="showindex_name"]').isChecked(),
).toBeFalsy();
// shownamespace
await page.locator('//*[@id="shownamespace"]').click();
expect(
- await page.locator('//*[@id="shownamespace"]').isChecked()
+ await page.locator('//*[@id="shownamespace"]').isChecked(),
).toBeFalsy();
// showpinecone_api_key
await page.locator('//*[@id="showpinecone_api_key"]').click();
expect(
- await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
- ).toBeFalsy();
-
- // showpinecone_env
- await page.locator('//*[@id="showpinecone_env"]').click();
-
- expect(
- await page.locator('//*[@id="showpinecone_env"]').isChecked()
+ await page.locator('//*[@id="showpinecone_api_key"]').isChecked(),
).toBeFalsy();
// showindex_name
await page.locator('//*[@id="showindex_name"]').click();
expect(
- await page.locator('//*[@id="showindex_name"]').isChecked()
+ await page.locator('//*[@id="showindex_name"]').isChecked(),
).toBeTruthy();
// shownamespace
await page.locator('//*[@id="shownamespace"]').click();
expect(
- await page.locator('//*[@id="shownamespace"]').isChecked()
+ await page.locator('//*[@id="shownamespace"]').isChecked(),
).toBeTruthy();
// showpinecone_api_key
await page.locator('//*[@id="showpinecone_api_key"]').click();
expect(
- await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
- ).toBeTruthy();
-
- // showpinecone_env
- await page.locator('//*[@id="showpinecone_env"]').click();
-
- expect(
- await page.locator('//*[@id="showpinecone_env"]').isChecked()
+ await page.locator('//*[@id="showpinecone_api_key"]').isChecked(),
).toBeTruthy();
// showindex_name
await page.locator('//*[@id="showindex_name"]').click();
expect(
- await page.locator('//*[@id="showindex_name"]').isChecked()
+ await page.locator('//*[@id="showindex_name"]').isChecked(),
).toBeFalsy();
// shownamespace
await page.locator('//*[@id="shownamespace"]').click();
expect(
- await page.locator('//*[@id="shownamespace"]').isChecked()
+ await page.locator('//*[@id="shownamespace"]').isChecked(),
).toBeFalsy();
// showpinecone_api_key
await page.locator('//*[@id="showpinecone_api_key"]').click();
expect(
- await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
- ).toBeFalsy();
-
- // showpinecone_env
- await page.locator('//*[@id="showpinecone_env"]').click();
-
- expect(
- await page.locator('//*[@id="showpinecone_env"]').isChecked()
+ await page.locator('//*[@id="showpinecone_api_key"]').isChecked(),
).toBeFalsy();
// showindex_name
await page.locator('//*[@id="showindex_name"]').click();
expect(
- await page.locator('//*[@id="showindex_name"]').isChecked()
+ await page.locator('//*[@id="showindex_name"]').isChecked(),
).toBeTruthy();
// shownamespace
await page.locator('//*[@id="shownamespace"]').click();
expect(
- await page.locator('//*[@id="shownamespace"]').isChecked()
+ await page.locator('//*[@id="shownamespace"]').isChecked(),
).toBeTruthy();
// showpinecone_api_key
await page.locator('//*[@id="showpinecone_api_key"]').click();
expect(
- await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
- ).toBeTruthy();
-
- // showpinecone_env
- await page.locator('//*[@id="showpinecone_env"]').click();
-
- expect(
- await page.locator('//*[@id="showpinecone_env"]').isChecked()
+ await page.locator('//*[@id="showpinecone_api_key"]').isChecked(),
).toBeTruthy();
// showindex_name
await page.locator('//*[@id="showindex_name"]').click();
expect(
- await page.locator('//*[@id="showindex_name"]').isChecked()
+ await page.locator('//*[@id="showindex_name"]').isChecked(),
).toBeFalsy();
// shownamespace
await page.locator('//*[@id="shownamespace"]').click();
expect(
- await page.locator('//*[@id="shownamespace"]').isChecked()
+ await page.locator('//*[@id="shownamespace"]').isChecked(),
).toBeFalsy();
// showpinecone_api_key
await page.locator('//*[@id="showpinecone_api_key"]').click();
expect(
- await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
- ).toBeFalsy();
-
- // showpinecone_env
- await page.locator('//*[@id="showpinecone_env"]').click();
-
- expect(
- await page.locator('//*[@id="showpinecone_env"]').isChecked()
+ await page.locator('//*[@id="showpinecone_api_key"]').isChecked(),
).toBeFalsy();
// showindex_name
await page.locator('//*[@id="showindex_name"]').click();
expect(
- await page.locator('//*[@id="showindex_name"]').isChecked()
+ await page.locator('//*[@id="showindex_name"]').isChecked(),
).toBeTruthy();
// shownamespace
await page.locator('//*[@id="shownamespace"]').click();
expect(
- await page.locator('//*[@id="shownamespace"]').isChecked()
+ await page.locator('//*[@id="shownamespace"]').isChecked(),
).toBeTruthy();
// showpinecone_api_key
await page.locator('//*[@id="showpinecone_api_key"]').click();
expect(
- await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
- ).toBeTruthy();
-
- // showpinecone_env
- await page.locator('//*[@id="showpinecone_env"]').click();
-
- expect(
- await page.locator('//*[@id="showpinecone_env"]').isChecked()
+ await page.locator('//*[@id="showpinecone_api_key"]').isChecked(),
).toBeTruthy();
//showpool_threads
await page.locator('//*[@id="showpool_threads"]').click();
expect(
- await page.locator('//*[@id="showpool_threads"]').isChecked()
+ await page.locator('//*[@id="showpool_threads"]').isChecked(),
).toBeFalsy();
//showtext_key
await page.locator('//*[@id="showtext_key"]').click();
expect(
- await page.locator('//*[@id="showtext_key"]').isChecked()
+ await page.locator('//*[@id="showtext_key"]').isChecked(),
).toBeTruthy();
await page.locator('//*[@id="saveChangesBtn"]').click();
diff --git a/src/frontend/tests/end-to-end/saveComponents.spec.ts b/src/frontend/tests/end-to-end/saveComponents.spec.ts
index b089abc9c..49ec8fd0b 100644
--- a/src/frontend/tests/end-to-end/saveComponents.spec.ts
+++ b/src/frontend/tests/end-to-end/saveComponents.spec.ts
@@ -16,7 +16,7 @@ test.describe("save component tests", () => {
}
while (modalCount === 0) {
- await page.locator('//*[@id="new-project-btn"]').click();
+ await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(5000);
modalCount = await page.getByTestId("modal-title")?.count();
}
@@ -27,7 +27,7 @@ test.describe("save component tests", () => {
// Read your file into a buffer.
const jsonContent = readFileSync(
"tests/end-to-end/assets/flow_group_test.json",
- "utf-8"
+ "utf-8",
);
// Create the DataTransfer and File
@@ -49,7 +49,7 @@ test.describe("save component tests", () => {
"drop",
{
dataTransfer,
- }
+ },
);
const genericNoda = page.getByTestId("div-generic-node");
@@ -61,9 +61,6 @@ test.describe("save component tests", () => {
.locator('//*[@id="react-flow-id"]/div[1]/div[2]/button[3]')
.click();
- await page.getByTestId("title-PythonFunctionTool").click({
- modifiers: ["Control"],
- });
await page.getByTestId("title-ChatOpenAI").click({
modifiers: ["Control"],
});
diff --git a/src/frontend/tests/end-to-end/store.spec.ts b/src/frontend/tests/end-to-end/store.spec.ts
index 6e372c5b0..ec526c0de 100644
--- a/src/frontend/tests/end-to-end/store.spec.ts
+++ b/src/frontend/tests/end-to-end/store.spec.ts
@@ -144,7 +144,7 @@ test("should add API-KEY", async ({ page }) => {
await page.getByTestId("api-key-button-store").click();
await page
.getByPlaceholder("Insert your API Key")
- .fill("x1fOKU0v2e5zL5d-BZW6CxZBZvoyuFgF");
+ .fill(process.env.STORE_API_KEY ?? "");
await page.getByTestId("api-key-save-button-store").click();
await page.waitForTimeout(2000);
@@ -165,7 +165,7 @@ test("should like and add components and flows", async ({ page }) => {
await page
.getByPlaceholder("Insert your API Key")
- .fill("x1fOKU0v2e5zL5d-BZW6CxZBZvoyuFgF");
+ .fill(process.env.STORE_API_KEY ?? "");
await page.getByTestId("api-key-save-button-store").click();
await page.waitForTimeout(2000);
@@ -174,6 +174,8 @@ test("should like and add components and flows", async ({ page }) => {
await page.waitForTimeout(2000);
await page.getByText("API Key Error").isHidden();
+ await page.waitForTimeout(5000);
+
const likedValue = await page
.getByTestId("likes-Website Content QA")
.innerText();
@@ -214,7 +216,7 @@ test("should like and add components and flows", async ({ page }) => {
await page.getByText("My Collection").click();
await page.getByText("Website Content QA").first().isVisible();
- await page.getByTestId("sidebar-nav-Components").click();
+ await page.getByText("Components").first().click();
await page.getByText("Basic RAG").first().isVisible();
});
@@ -233,7 +235,7 @@ test("should share component with share button", async ({ page }) => {
}
while (modalCount === 0) {
- await page.locator('//*[@id="new-project-btn"]').click();
+ await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(5000);
modalCount = await page.getByTestId("modal-title")?.count();
}
@@ -255,7 +257,7 @@ test("should share component with share button", async ({ page }) => {
await page.getByText("Set workflow status to public").isVisible();
await page
.getByText(
- "Attention: API keys in specified fields are automatically removed upon sharing."
+ "Attention: API keys in specified fields are automatically removed upon sharing.",
)
.isVisible();
await page.getByText("Export").first().isVisible();
diff --git a/src/frontend/tests/end-to-end/tweaks_test.spec.ts b/src/frontend/tests/end-to-end/tweaks_test.spec.ts
index 55386fcae..8bdd32ac0 100644
--- a/src/frontend/tests/end-to-end/tweaks_test.spec.ts
+++ b/src/frontend/tests/end-to-end/tweaks_test.spec.ts
@@ -13,7 +13,7 @@ test("curl_api_generation", async ({ page, context }) => {
}
while (modalCount === 0) {
- await page.locator('//*[@id="new-project-btn"]').click();
+ await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(5000);
modalCount = await page.getByTestId("modal-title")?.count();
}
@@ -75,7 +75,7 @@ test("check if tweaks are updating when someothing on the flow changes", async (
}
while (modalCount === 0) {
- await page.locator('//*[@id="new-project-btn"]').click();
+ await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(5000);
modalCount = await page.getByTestId("modal-title")?.count();
}
diff --git a/tests/conftest.py b/tests/conftest.py
index e3faa3ba5..739f12ffb 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -10,7 +10,6 @@ import orjson
import pytest
from fastapi.testclient import TestClient
from httpx import AsyncClient
-from sqlmodel import Session, SQLModel, create_engine, select
from langflow.graph.graph.base import Graph
from langflow.initial_setup.setup import STARTER_FOLDER_NAME
from langflow.services.auth.utils import get_password_hash
@@ -19,6 +18,7 @@ from langflow.services.database.models.flow.model import Flow, FlowCreate
from langflow.services.database.models.user.model import User, UserCreate
from langflow.services.database.utils import session_getter
from langflow.services.deps import get_db_service
+from sqlmodel import Session, SQLModel, create_engine, select
from sqlmodel.pool import StaticPool
from typer.testing import CliRunner
@@ -26,7 +26,8 @@ if TYPE_CHECKING:
from langflow.services.database.service import DatabaseService
-def pytest_configure():
+def pytest_configure(config):
+ config.addinivalue_line("markers", "noclient: don't create a client for this test")
data_path = Path(__file__).parent.absolute() / "data"
pytest.BASIC_EXAMPLE_PATH = data_path / "basic_example.json"
diff --git a/tests/test_graph.py b/tests/test_graph.py
index 9e5775f90..ec69051d2 100644
--- a/tests/test_graph.py
+++ b/tests/test_graph.py
@@ -407,7 +407,7 @@ def test_update_source_handle():
@pytest.mark.asyncio
-async def test_pickle_graph(json_vector_store):
+async def test_pickle_graph():
starter_projects = load_starter_projects()
data = starter_projects[0][1]["data"]
graph = Graph.from_payload(data)
diff --git a/tests/test_template.py b/tests/test_template.py
index d1f75e75e..95ea4f528 100644
--- a/tests/test_template.py
+++ b/tests/test_template.py
@@ -3,8 +3,7 @@ from typing import Dict, List, Optional
import pytest
from langflow.interface.utils import build_template_from_class
-from langflow.utils.constants import CHAT_OPENAI_MODELS, OPENAI_MODELS
-from langflow.utils.util import build_template_from_function, format_dict, get_base_classes, get_default_factory
+from langflow.utils.util import build_template_from_function, get_base_classes, get_default_factory
from pydantic import BaseModel
@@ -88,171 +87,6 @@ def test_build_template_from_class():
build_template_from_class("InvalidClass", type_to_cls_dict)
-# Test format_dict
-def test_format_dict():
- # Test 1: Optional type removal
- input_dict = {
- "field1": {"type": "Optional[str]", "required": False},
- }
- expected_output = {
- "field1": {
- "type": "str",
- "required": False,
- "list": False,
- "show": False,
- "password": False,
- "multiline": False,
- },
- }
- assert format_dict(input_dict) == expected_output
-
- # Test 2: List type processing
- input_dict = {
- "field1": {"type": "List[str]", "required": False},
- }
- expected_output = {
- "field1": {
- "type": "str",
- "required": False,
- "list": True,
- "show": False,
- "password": False,
- "multiline": False,
- },
- }
- assert format_dict(input_dict) == expected_output
-
- # Test 3: Mapping type replacement
- input_dict = {
- "field1": {"type": "Mapping[str, int]", "required": False},
- }
- expected_output = {
- "field1": {
- "type": "dict[str, int]", # Mapping type is replaced with dict which is replaced with code
- "required": False,
- "list": False,
- "show": False,
- "password": False,
- "multiline": False,
- },
- }
- assert format_dict(input_dict) == expected_output
-
- # Test 4: Replace default value with actual value
- input_dict = {
- "field1": {"type": "str", "required": False, "default": "test"},
- }
- expected_output = {
- "field1": {
- "type": "str",
- "required": False,
- "list": False,
- "show": False,
- "password": False,
- "multiline": False,
- "value": "test",
- },
- }
- assert format_dict(input_dict) == expected_output
-
- # Test 5: Add password field
- input_dict = {
- "field1": {"type": "str", "required": False},
- "api_key": {"type": "str", "required": False},
- }
- expected_output = {
- "field1": {
- "type": "str",
- "required": False,
- "list": False,
- "show": False,
- "password": False,
- "multiline": False,
- },
- "api_key": {
- "type": "str",
- "required": False,
- "list": False,
- "show": True,
- "password": True,
- "multiline": False,
- },
- }
- assert format_dict(input_dict) == expected_output
-
- # Test 6: Add multiline
- input_dict = {
- "field1": {"type": "str", "required": False},
- "prefix": {"type": "str", "required": False},
- }
- expected_output = {
- "field1": {
- "type": "str",
- "required": False,
- "list": False,
- "show": False,
- "password": False,
- "multiline": False,
- },
- "prefix": {
- "type": "str",
- "required": False,
- "list": False,
- "show": True,
- "password": False,
- "multiline": True,
- },
- }
- assert format_dict(input_dict) == expected_output
-
- # Test 7: Check class name-specific cases (OpenAI, ChatOpenAI)
- input_dict = {
- "model_name": {"type": "str", "required": False},
- }
- expected_output_openai = {
- "model_name": {
- "type": "str",
- "required": False,
- "list": True,
- "show": True,
- "password": False,
- "multiline": False,
- "options": OPENAI_MODELS,
- "value": "text-davinci-003",
- },
- }
- expected_output_openai_chat = {
- "model_name": {
- "type": "str",
- "required": False,
- "list": True,
- "show": True,
- "password": False,
- "multiline": False,
- "options": CHAT_OPENAI_MODELS,
- "value": "gpt-4-turbo-preview",
- },
- }
- assert format_dict(input_dict, "OpenAI") == expected_output_openai
- assert format_dict(input_dict, "ChatOpenAI") == expected_output_openai_chat
-
- # Test 8: Replace dict type with str
- input_dict = {
- "field1": {"type": "Dict[str, int]", "required": False},
- }
- expected_output = {
- "field1": {
- "type": "Dict[str, int]",
- "required": False,
- "list": False,
- "show": False,
- "password": False,
- "multiline": False,
- },
- }
- assert format_dict(input_dict) == expected_output
-
-
# Test get_base_classes
def test_get_base_classes():
base_classes_parent = get_base_classes(Parent)