diff --git a/src/frontend/src/components/chatComponent/index.tsx b/src/frontend/src/components/chatComponent/index.tsx
index a88bd9573..fd2c7a080 100644
--- a/src/frontend/src/components/chatComponent/index.tsx
+++ b/src/frontend/src/components/chatComponent/index.tsx
@@ -113,7 +113,10 @@ export default function FlowToolbar(): JSX.Element {
{hasIO ? (
-
+
= (
+ options,
+) => {
+ const { mutate } = UseRequestProcessor();
+
+ const deleteBuildsFn = async (payload: IDeleteBuilds): Promise => {
+ const config = {};
+ config["params"] = { flow_id: payload.flowId };
+ const res = await api.delete(`${getURL("BUILDS")}`, config);
+ return res.data;
+ };
+
+ const mutation = mutate(["useDeleteBuilds"], deleteBuildsFn, options);
+
+ return mutation;
+};
diff --git a/src/frontend/src/controllers/API/queries/_builds/use-get-builds.ts b/src/frontend/src/controllers/API/queries/_builds/use-get-builds.ts
new file mode 100644
index 000000000..eeaa34ef4
--- /dev/null
+++ b/src/frontend/src/controllers/API/queries/_builds/use-get-builds.ts
@@ -0,0 +1,58 @@
+import useFlowStore from "@/stores/flowStore";
+import useFlowsManagerStore from "@/stores/flowsManagerStore";
+import { FlowPoolType } from "@/types/zustand/flow";
+import { cleanEdges } from "@/utils/reactflowUtils";
+import { getInputsAndOutputs } from "@/utils/storeUtils";
+import { keepPreviousData } from "@tanstack/react-query";
+import { AxiosResponse } from "axios";
+import { useQueryFunctionType } from "../../../../types/api";
+import { api } from "../../api";
+import { getURL } from "../../helpers/constants";
+import { UseRequestProcessor } from "../../services/request-processor";
+
+interface BuildsQueryParams {
+ flowId?: string;
+ nodeId?: string;
+}
+
+export const useGetBuildsQuery: useQueryFunctionType<
+ BuildsQueryParams,
+ AxiosResponse<{ vertex_builds: FlowPoolType }>
+> = ({}) => {
+ const { query } = UseRequestProcessor();
+
+ const setFlowPool = useFlowStore((state) => state.setFlowPool);
+ const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
+
+ const getBuildsFn = async (
+ params: BuildsQueryParams,
+ ): Promise> => {
+ const config = {};
+ config["params"] = { flow_id: params.flowId };
+
+ if (params.nodeId) {
+ config["params"] = { nodeId: params.nodeId };
+ }
+
+ return await api.get(`${getURL("BUILDS")}`, config);
+ };
+
+ const responseFn = async () => {
+ const response = await getBuildsFn({
+ flowId: currentFlow!.id,
+ });
+
+ if (currentFlow) {
+ const flowPool = response.data.vertex_builds;
+ setFlowPool(flowPool);
+ }
+
+ return response;
+ };
+
+ const queryResult = query(["useGetBuildsQuery"], responseFn, {
+ placeholderData: keepPreviousData,
+ });
+
+ return queryResult;
+};
diff --git a/src/frontend/src/modals/IOModal/components/chatView/index.tsx b/src/frontend/src/modals/IOModal/components/chatView/index.tsx
index f9d771fea..511cb1085 100644
--- a/src/frontend/src/modals/IOModal/components/chatView/index.tsx
+++ b/src/frontend/src/modals/IOModal/components/chatView/index.tsx
@@ -1,3 +1,4 @@
+import { useDeleteBuilds } from "@/controllers/API/queries/_builds";
import { usePostUploadFile } from "@/controllers/API/queries/files/use-post-upload-file";
import { useEffect, useRef, useState } from "react";
import ShortUniqueId from "short-unique-id";
@@ -40,6 +41,7 @@ export default function ChatView({
const outputIds = outputs.map((obj) => obj.id);
const updateFlowPool = useFlowStore((state) => state.updateFlowPool);
const [id, setId] = useState("");
+ const { mutate: mutateDeleteFlowPool } = useDeleteBuilds();
//build chat history
useEffect(() => {
@@ -116,9 +118,15 @@ export default function ChatView({
function clearChat(): void {
setChatHistory([]);
- deleteFlowPool(currentFlowId).then((_) => {
- CleanFlowPool();
- });
+
+ mutateDeleteFlowPool(
+ { flowId: currentFlowId },
+ {
+ onSuccess: () => {
+ CleanFlowPool();
+ },
+ },
+ );
//TODO tell backend to clear chat session
if (lockChat) setLockChat(false);
}
diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx
index f0c97572f..70f546546 100644
--- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx
+++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx
@@ -1,3 +1,5 @@
+import { useGetBuildsQuery } from "@/controllers/API/queries/_builds";
+import { getInputsAndOutputs } from "@/utils/storeUtils";
import _, { cloneDeep } from "lodash";
import {
KeyboardEvent,
@@ -35,6 +37,7 @@ import { APIClassType } from "../../../../types/api";
import { FlowType, NodeType } from "../../../../types/flow";
import {
checkOldComponents,
+ cleanEdges,
generateFlow,
generateNodeFromFlow,
getNodeId,
@@ -88,7 +91,6 @@ export default function Page({
const redo = useFlowsManagerStore((state) => state.redo);
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
const paste = useFlowStore((state) => state.paste);
- const resetFlow = useFlowStore((state) => state.resetFlow);
const lastCopiedSelection = useFlowStore(
(state) => state.lastCopiedSelection,
);
@@ -106,6 +108,13 @@ export default function Page({
const [lastSelection, setLastSelection] =
useState(null);
+ const setFlowState = useFlowStore((state) => state.setFlowState);
+ const setInputs = useFlowStore((state) => state.setInputs);
+ const setOutputs = useFlowStore((state) => state.setOutputs);
+ const setHasIO = useFlowStore((state) => state.setHasIO);
+ const { inputs, outputs } = getInputsAndOutputs(flow.data!.nodes);
+ const viewport = flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 };
+
function handleGroupNode() {
takeSnapshot();
if (validateSelection(lastSelection!, edges).length === 0) {
@@ -113,14 +122,15 @@ export default function Page({
const clonedEdges = cloneDeep(edges);
const clonedSelection = cloneDeep(lastSelection);
updateIds({ nodes: clonedNodes, edges: clonedEdges }, clonedSelection!);
- const { newFlow, removedEdges } = generateFlow(
+ const { newFlow } = generateFlow(
clonedSelection!,
clonedNodes,
clonedEdges,
getRandomName(),
);
+
const newGroupNode = generateNodeFromFlow(newFlow, getNodeId);
- // const newEdges = reconnectEdges(newGroupNode, removedEdges);
+
setNodes([
...clonedNodes.filter(
(oldNodes) =>
@@ -130,17 +140,6 @@ export default function Page({
),
newGroupNode,
]);
- // setEdges([
- // ...clonedEdges.filter(
- // (oldEdge) =>
- // !clonedSelection!.nodes.some(
- // (selectionNode) =>
- // selectionNode.id === oldEdge.target ||
- // selectionNode.id === oldEdge.source,
- // ),
- // ),
- // ...newEdges,
- // ]);
} else {
setErrorData({
title: INVALID_SELECTION_ERROR_ALERT,
@@ -149,7 +148,6 @@ export default function Page({
}
}
- const setNode = useFlowStore((state) => state.setNode);
useEffect(() => {
const handleMouseMove = (event) => {
position.current = { x: event.clientX, y: event.clientY };
@@ -164,14 +162,24 @@ export default function Page({
useEffect(() => {
if (reactFlowInstance && currentFlowId) {
- resetFlow({
- nodes: flow?.data?.nodes ?? [],
- edges: flow?.data?.edges ?? [],
- viewport: flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 },
- });
+ reactFlowInstance!.setViewport(viewport);
}
}, [currentFlowId, reactFlowInstance]);
+ const { isFetching } = useGetBuildsQuery({});
+
+ useEffect(() => {
+ if (!isFetching) {
+ let newEdges = cleanEdges(flow.data!.nodes, flow.data!.edges);
+ setNodes(flow.data!.nodes);
+ setEdges(newEdges);
+ setFlowState(undefined);
+ setInputs(inputs);
+ setOutputs(outputs);
+ setHasIO(inputs.length > 0 || outputs.length > 0);
+ }
+ }, [isFetching]);
+
useEffect(() => {
if (checkOldComponents({ nodes: flow?.data?.nodes ?? [] })) {
setNoticeData({
diff --git a/src/frontend/src/stores/flowStore.ts b/src/frontend/src/stores/flowStore.ts
index 94216d244..408ead525 100644
--- a/src/frontend/src/stores/flowStore.ts
+++ b/src/frontend/src/stores/flowStore.ts
@@ -59,12 +59,21 @@ const useFlowStore = create((set, get) => ({
edges: [],
isBuilding: false,
isPending: true,
- hasIO: false,
+ setHasIO: (hasIO) => {
+ set({ hasIO });
+ },
reactFlowInstance: null,
lastCopiedSelection: null,
flowPool: {},
+ setInputs: (inputs) => {
+ set({ inputs });
+ },
+ setOutputs: (outputs) => {
+ set({ outputs });
+ },
inputs: [],
outputs: [],
+ hasIO: get()?.inputs?.length > 0 || get()?.outputs?.length > 0,
setFlowPool: (flowPool) => {
set({ flowPool });
},
diff --git a/src/frontend/src/types/zustand/flow/index.ts b/src/frontend/src/types/zustand/flow/index.ts
index c7a0f1f09..8d17aa34a 100644
--- a/src/frontend/src/types/zustand/flow/index.ts
+++ b/src/frontend/src/types/zustand/flow/index.ts
@@ -56,6 +56,13 @@ export type FlowStoreType = {
onFlowPage: boolean;
setOnFlowPage: (onFlowPage: boolean) => void;
flowPool: FlowPoolType;
+ setHasIO: (hasIO: boolean) => void;
+ setInputs: (
+ inputs: Array<{ type: string; id: string; displayName: string }>,
+ ) => void;
+ setOutputs: (
+ outputs: Array<{ type: string; id: string; displayName: string }>,
+ ) => void;
inputs: Array<{
type: string;
id: string;
diff --git a/src/frontend/tests/end-to-end/generalBugs-shard-3.spec.ts b/src/frontend/tests/end-to-end/generalBugs-shard-3.spec.ts
index cef4b1951..b8452569b 100644
--- a/src/frontend/tests/end-to-end/generalBugs-shard-3.spec.ts
+++ b/src/frontend/tests/end-to-end/generalBugs-shard-3.spec.ts
@@ -196,3 +196,58 @@ test("should copy code from playground modal", async ({ page }) => {
expect(clipboardContent.length).toBeGreaterThan(0);
expect(clipboardContent).toContain("Hello");
});
+
+test("playground button should be enabled or disabled", async ({ page }) => {
+ await page.goto("/");
+ await page.locator("span").filter({ hasText: "My Collection" }).isVisible();
+ 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.getByText("New Project", { exact: true }).click();
+ await page.waitForTimeout(5000);
+ modalCount = await page.getByTestId("modal-title")?.count();
+ }
+
+ await page.waitForSelector('[data-testid="blank-flow"]', {
+ timeout: 30000,
+ });
+
+ await page.getByTestId("blank-flow").click();
+ await page.waitForSelector('[data-testid="extended-disclosure"]', {
+ timeout: 30000,
+ });
+
+ await page.getByTestId("playground-btn-flow").click({ force: true });
+
+ expect(await page.getByText("Langflow Chat").isHidden());
+
+ await page.getByPlaceholder("Search").click();
+ await page.getByPlaceholder("Search").fill("chat output");
+
+ await page.waitForTimeout(2000);
+
+ await page
+ .locator('//*[@id="outputsChat Output"]')
+ .dragTo(page.locator('//*[@id="react-flow-id"]'));
+ await page.mouse.up();
+ await page.mouse.down();
+ await page.waitForSelector('[title="fit view"]', {
+ timeout: 100000,
+ });
+
+ await page.waitForTimeout(2000);
+
+ await page.getByTestId("playground-btn-flow-io").click({ force: true });
+
+ expect(await page.getByText("Langflow Chat").isVisible());
+});