diff --git a/src/frontend/src/constants/constants.ts b/src/frontend/src/constants/constants.ts index f1026fec2..95c8625b8 100644 --- a/src/frontend/src/constants/constants.ts +++ b/src/frontend/src/constants/constants.ts @@ -677,3 +677,6 @@ export const LANGFLOW_SUPPORTED_TYPES = new Set([ ]); export const priorityFields = new Set(["code", "template"]); + +export const INPUT_TYPES = new Set(["ChatInput","TextInput"]); +export const OUTPUT_TYPES = new Set(["ChatOutput"]); diff --git a/src/frontend/src/stores/flowsIOStore.ts b/src/frontend/src/stores/flowsIOStore.ts new file mode 100644 index 000000000..cc6efb5f2 --- /dev/null +++ b/src/frontend/src/stores/flowsIOStore.ts @@ -0,0 +1,255 @@ +import { cloneDeep } from "lodash"; +import { create } from "zustand"; +import { FlowType, NodeDataType, NodeType } from "../types/flow"; +import useFlowStore from "./flowStore"; +import { isInputNode, isOutputNode } from "../utils/reactflowUtils"; +import useFlowsManagerStore from "./flowsManagerStore"; +import useAlertStore from "./alertStore"; +import { flowIOStoreType } from "../types/zustand/flowIOStore"; +/* const { getNodeId, saveFlow } = useContext(FlowsContext); +const { setErrorData, setNoticeData } = useContext(alertContext); */ + +const { reactFlowInstance, paste } = useFlowStore(); +const { saveFlow } = useFlowsManagerStore(); +const { setErrorData, setNoticeData } = useAlertStore(); + +const useFlowIOStore = create((set, get) => ({ + flowPool: {}, + getFilterEdge: [], + isBuilt: false, + outputTypes: [], + inputTypes: [], + inputIds: [], + outputIds: [], + isBuilding: true, + + setFilterEdge: (filterEdge) => { set({getFilterEdge: filterEdge}) }, + setFlowPool: (flowPool) => { set({flowPool}) }, + setIsBuilt: (isBuilt) => { set({isBuilt}) }, + setOutputTypes: (outputTypes) => { set({outputTypes}) }, + setInputTypes: (inputTypes) => { set({inputTypes}) }, + setInputIds: (inputIds) => { set({ inputIds }) }, + setOutputIds: (outputIds) => { set({outputIds}) }, + setIsBuilding: (isBuilding) => { set({isBuilding}) }, + + updateFlowPoolNodes: (nodes: NodeType[]) => { + //this function will update the removing the old ones + const nodeIdsSet = new Set(nodes.map((node) => node.id)); + set((state) => { + let newFlowPool = cloneDeep({ ...state.flowPool }); + Object.keys(newFlowPool).forEach((nodeId) => { + if (!nodeIdsSet.has(nodeId)) { + delete newFlowPool[nodeId]; + } + }); + return { flowPool: newFlowPool }; + }); + }, + addDataToFlowPool: (data: any, nodeId: string) => { + set((state) => { + let newFlowPool = cloneDeep({ ...state.flowPool }); + if (!newFlowPool[nodeId]) newFlowPool[nodeId] = [data]; + else { + newFlowPool[nodeId].push(data); + } + return { flowPool: newFlowPool }; + }); + }, + CleanFlowPool: () => { + set({ flowPool: {} }); + }, + updateNodeFlowData: (nodeId: string, newData: NodeDataType) => { + let oldNodes = reactFlowInstance?.getNodes(); + let targetNode = oldNodes?.find((node) => node.id === nodeId); + if (targetNode) { + targetNode.data = cloneDeep(newData); + reactFlowInstance?.setNodes([...oldNodes!]); + console.log( + "after reactflow update", + JSON.parse(JSON.stringify(reactFlowInstance?.toObject())) + ); + } + }, + checkInputandOutput: (flow?: FlowType) => { + let has_input = false; + let has_output = false; + if (!flow && !reactFlowInstance) { + return false; + } + const nodes = flow?.data?.nodes + ? flow.data.nodes + : reactFlowInstance!.getNodes(); + nodes.forEach((node) => { + const nodeData: NodeDataType = node.data as NodeDataType; + if (isInputNode(nodeData)) { + has_input = true; + } + if (isOutputNode(nodeData)) { + has_output = true; + } + }); + return has_input && has_output; + }, + getInputTypes: (flow?: FlowType) => { + let inputType: string[] = []; + if (!flow && !reactFlowInstance) { + return []; + } + const nodes = flow?.data?.nodes + ? flow.data.nodes + : reactFlowInstance!.getNodes(); + nodes.forEach((node) => { + const nodeData: NodeDataType = node.data as NodeDataType; + if (isInputNode(nodeData)) { + // TODO remove count and ramdom key from type before pushing + inputType.push(nodeData.type); + } + }); + set({ inputTypes: inputType }); + return inputType; + }, + getOutputTypes: (flow?: FlowType) => { + let outputType: string[] = []; + if (!flow && !reactFlowInstance) { + return []; + } + const nodes = flow?.data?.nodes + ? flow.data.nodes + : reactFlowInstance!.getNodes(); + nodes.forEach((node) => { + const nodeData: NodeDataType = node.data as NodeDataType; + if (isOutputNode(nodeData)) { + // TODO remove count and ramdom key from type before pushing + outputType.push(nodeData.type); + } + }); + set({ outputTypes: outputType }); + return outputType; + }, + getInputIds: (flow?: FlowType) => { + let inputIds: string[] = []; + if (!flow && !reactFlowInstance) { + return []; + } + const nodes = flow?.data?.nodes + ? flow.data.nodes + : reactFlowInstance!.getNodes(); + nodes.forEach((node) => { + const nodeData: NodeDataType = node.data as NodeDataType; + if (isInputNode(nodeData)) { + inputIds.push(nodeData.id); + } + }); + set({ inputIds }); + return inputIds; + }, + getOutputIds: (flow?: FlowType) => { + let outputIds: string[] = []; + if (!flow && !reactFlowInstance) { + return []; + } + const nodes = flow?.data?.nodes + ? flow.data.nodes + : reactFlowInstance!.getNodes(); + + nodes.forEach((node) => { + const nodeData: NodeDataType = node.data as NodeDataType; + if (isOutputNode(nodeData)) { + outputIds.push(nodeData.id); + } + }); + set({ outputIds }); + return outputIds; + }, + pasteFileOnFLow: async (file?: File) => { + if (file) { + const text = await file.text(); + let fileData = JSON.parse(text); + if (fileData.flows) { + fileData.flows.forEach((flow: FlowType) => { + paste( + { nodes: flow!.data!.nodes, edges: flow!.data!.edges }, + { x: 10, y: 10 } + ); + }); + } else { + let flow: FlowType = JSON.parse(text); + paste( + { nodes: flow.data!.nodes, edges: flow.data!.edges }, + { x: 10, y: 10 } + ); + } + } else { + const input = document.createElement("input"); + input.type = "file"; + input.accept = ".json"; + // add a change event listener to the file input + input.onchange = async (e: Event) => { + if ( + (e.target as HTMLInputElement).files![0].type === "application/json" + ) { + const currentfile = (e.target as HTMLInputElement).files![0]; + get().pasteFileOnFLow(currentfile); + } + }; + // trigger the file input click event to open the file dialog + input.click(); + } + }, + downloadFlow: ( + flow: FlowType, + flowName: string, + flowDescription?: string + ) => { + // create a data URI with the current flow data + const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent( + JSON.stringify({ ...flow, name: flowName, description: flowDescription }) + )}`; + + // create a link element and set its properties + const link = document.createElement("a"); + link.href = jsonString; + link.download = `${flowName && flowName != "" ? flowName : "flow"}.json`; + + // simulate a click on the link element to trigger the download + link.click(); + }, + /* buildFlow: async (nodeId?: string) => { + function handleBuildUpdate(data: any) { + get().addDataToFlowPool(data.data[data.id], data.id); + } + console.log( + "building flow before save", + JSON.parse(JSON.stringify(actualFlow)) + ); + console.log(saveFlow); + await saveFlow( + { ...actualFlow!, data: reactFlowInstance!.toObject()! }, + true + ); + console.log( + "building flow AFTER save", + JSON.parse(JSON.stringify(actualFlow)) + ); + return buildVertices({ + flow: { + data: reactFlowInstance?.toObject()!, + description: actualFlow!.description, + id: actualFlow!.id, + name: actualFlow!.name, + }, + nodeId, + onBuildComplete: () => { + if (nodeId) { + setNoticeData({ title: `${nodeId} built successfully` }); + } + }, + onBuildUpdate: handleBuildUpdate, + onBuildError: (title, list) => { + setErrorData({ list, title }); + }, + }); + }, */ +})); + +export default useFlowIOStore; diff --git a/src/frontend/src/types/zustand/flowIOStore/index.ts b/src/frontend/src/types/zustand/flowIOStore/index.ts new file mode 100644 index 000000000..00fbcd6ed --- /dev/null +++ b/src/frontend/src/types/zustand/flowIOStore/index.ts @@ -0,0 +1,61 @@ +import { ReactFlowInstance } from "reactflow"; +import { tweakType } from "../../components"; +import { FlowType, NodeDataType, NodeType } from "../../flow"; + +export type chatInputType = { + result: string; +}; + +export type ChatOutputType = { + message: string; + sender: string; + sender_name: string; +}; + +export type FlowPoolObjectType = { + timestamp: string; + valid: boolean; + params: any; + data: { artifacts: any; results: any | ChatOutputType | chatInputType }; + id: string; +}; + +export type FlowPoolType = { + [key: string]: Array; +}; + +export type flowIOStoreType = { + flowPool: FlowPoolType; + getFilterEdge: any[]; + isBuilt: boolean; + inputTypes: string[]; + outputTypes: string[]; + inputIds: string[]; + outputIds: string[]; + isBuilding: boolean; + setFlowPool: (flowPool: FlowPoolType) => void; + setIsBuilding: (state: boolean) => void; + setIsBuilt: (state: boolean) => void; + setOutputTypes: (outputTypes: string[]) => void; + setInputTypes: (inputTypes: string[]) => void; + setInputIds: (inputIds: string[]) => void; + setOutputIds: (outputIds: string[]) => void; + setFilterEdge: (newState) => void; + updateFlowPoolNodes: (nodes: NodeType[]) => void; + addDataToFlowPool: (data: any, nodeId: string) => void; + CleanFlowPool: () => void; + updateNodeFlowData: (nodeId: string, newData: NodeDataType) => void; + checkInputandOutput: (flow?: FlowType) => boolean; + getInputTypes: (flow?: FlowType) => string[]; + getOutputTypes: (flow?: FlowType) => string[]; + getInputIds: (flow?: FlowType) => string[]; + getOutputIds: (flow?: FlowType) => string[]; + pasteFileOnFLow: (file?: File) => Promise; + downloadFlow: ( + flow: FlowType, + flowName: string, + flowDescription?: string + ) => void; + + /* buildFlow: (nodeId?:string) => Promise; */ +}; \ No newline at end of file diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 8d222349a..350e24987 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -9,7 +9,9 @@ import { } from "reactflow"; import ShortUniqueId from "short-unique-id"; import { + INPUT_TYPES, LANGFLOW_SUPPORTED_TYPES, + OUTPUT_TYPES, specialCharsRegex, } from "../constants/constants"; import { downloadFlowsFromDatabase } from "../controllers/API"; @@ -1286,3 +1288,11 @@ export const createNewFlow = ( is_component: flow?.is_component ?? false, }; }; + +export function isInputNode(nodeData: NodeDataType): boolean { + return INPUT_TYPES.has(nodeData.type); +} + +export function isOutputNode(nodeData: NodeDataType): boolean { + return OUTPUT_TYPES.has(nodeData.type); +}