Removed SSE Store and fixed node building not appearing at node

This commit is contained in:
Lucas Oliveira 2024-01-08 15:31:18 -03:00
commit 221776c2d4
14 changed files with 138 additions and 161 deletions

View file

@ -16,7 +16,6 @@ import { classNames, cn, getFieldTitle } from "../../utils/utils";
import ParameterComponent from "./components/parameterComponent";
import { useTypesStore } from "../../stores/typesStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { useSSEStore } from "../../stores/sseStore";
export default function GenericNode({
data,
@ -80,8 +79,8 @@ export default function GenericNode({
}, [data, data.node]);
// State for outline color
const sseData = useSSEStore((state) => state.sseData);
const isBuilding = useSSEStore((state) => state.isBuilding);
const sseData = useFlowStore((state) => state.sseData);
const isBuilding = useFlowStore((state) => state.isBuilding);
useEffect(() => {
setNodeDescription(data.node!.description);

View file

@ -1,18 +1,16 @@
import { Transition } from "@headlessui/react";
import { useContext, useState } from "react";
import { useState } from "react";
import Loading from "../../../components/ui/loading";
import { postBuildInit } from "../../../controllers/API";
import { FlowType } from "../../../types/flow";
import useAlertStore from "../../../stores/alertStore";
import useFlowStore from "../../../stores/flowStore";
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
import { parsedDataType } from "../../../types/components";
import { FlowState } from "../../../types/tabs";
import { validateNodes } from "../../../utils/reactflowUtils";
import RadialProgressComponent from "../../RadialProgress";
import IconComponent from "../../genericIconComponent";
import { useSSEStore } from "../../../stores/sseStore";
import { stat } from "fs";
export default function BuildTrigger({
open,
@ -24,20 +22,17 @@ export default function BuildTrigger({
setIsBuilt: any;
isBuilt: boolean;
}): JSX.Element {
const updateSSEData = useSSEStore((state) => state.updateSSEData);
const isBuilding = useSSEStore((state) => state.isBuilding);
const setIsBuilding = useSSEStore((state) => state.setIsBuilding);
const sseData = useSSEStore((state) => state.sseData);
const updateSSEData = useFlowStore((state) => state.updateSSEData);
const isBuilding = useFlowStore((state) => state.isBuilding);
const setIsBuilding = useFlowStore((state) => state.setIsBuilding);
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const setErrorData = useAlertStore((state) => state.setErrorData);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setCurrentFlowState = useFlowsManagerStore(
(state) => state.setCurrentFlowState
const setFlowState = useFlowStore(
(state) => state.setFlowState
);
const saveFlow = useFlowsManagerStore((state) => state.saveFlow);
const [isIconTouched, setIsIconTouched] = useState(false);
const eventClick = isBuilding ? "pointer-events-none" : "";
const [progress, setProgress] = useState(0);
@ -82,7 +77,6 @@ export default function BuildTrigger({
}
async function streamNodeData(flow: FlowType) {
// Step 1: Make a POST request to send the flow data and receive a unique session ID
const id = saveFlow(flow, true);
const response = await postBuildInit(flow);
const { flowId } = response.data;
// Step 2: Use the session ID to establish an SSE connection using EventSource
@ -105,7 +99,7 @@ export default function BuildTrigger({
// If the event is a log, log it
setSuccessData({ title: parsedData.log });
} else if (parsedData.input_keys !== undefined) {
setCurrentFlowState(parsedData);
setFlowState(parsedData);
} else {
// Otherwise, process the data
const isValid = processStreamResult(parsedData);
@ -151,14 +145,6 @@ export default function BuildTrigger({
}
}
const handleMouseEnter = () => {
setIsIconTouched(true);
};
const handleMouseLeave = () => {
setIsIconTouched(false);
};
return (
<Transition
show={!open}
@ -176,8 +162,6 @@ export default function BuildTrigger({
onClick={() => {
handleBuild(flow);
}}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<button>
<div className="round-button-div">

View file

@ -13,10 +13,9 @@ import useFlowsManagerStore from "../../stores/flowsManagerStore";
export default function Chat({ flow }: ChatType): JSX.Element {
const [open, setOpen] = useState(false);
const [canOpen, setCanOpen] = useState(false);
const isBuilt = useFlowStore((state) => state.isBuilt);
const setIsBuilt = useFlowStore((state) => state.setIsBuilt);
const currentFlowState = useFlowsManagerStore((state) => state.currentFlowState);
const flowState = useFlowStore((state) => state.flowState);
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (
@ -57,17 +56,8 @@ export default function Chat({ flow }: ChatType): JSX.Element {
) {
setIsBuilt(false);
}
if (
currentFlowState &&
currentFlowState.input_keys !== null
) {
setCanOpen(true);
} else {
setCanOpen(false);
}
prevNodesRef.current = currentNodes;
}, [currentFlowState, flow.id]);
}, [flowState, flow.id]);
return (
<>
@ -79,8 +69,8 @@ export default function Chat({ flow }: ChatType): JSX.Element {
isBuilt={isBuilt}
/>
{isBuilt &&
currentFlowState &&
canOpen && (
flowState &&
!!flowState?.input_keys && (
<FormModal
key={flow.id}
flow={flow}
@ -89,7 +79,7 @@ export default function Chat({ flow }: ChatType): JSX.Element {
/>
)}
<ChatTrigger
canOpen={canOpen}
canOpen={!!flowState?.input_keys}
open={open}
setOpen={setOpen}
isBuilt={isBuilt}

View file

@ -31,6 +31,7 @@ import {
tabsArray,
} from "../../utils/utils";
import BaseModal from "../baseModal";
import useFlowStore from "../../stores/flowStore";
const ApiModal = forwardRef(
(
@ -49,18 +50,18 @@ const ApiModal = forwardRef(
const tweak = useRef<tweakType>([]);
const tweaksList = useRef<string[]>([]);
const [getTweak, setTweak] = useState<tweakType>([]);
const currentFlowState = useFlowsManagerStore(
(state) => state.currentFlowState
const flowState = useFlowStore(
(state) => state.flowState
);
const pythonApiCode = getPythonApiCode(
flow,
autoLogin,
tweak.current,
currentFlowState
flowState
);
const curl_code = getCurlCode(flow, autoLogin, tweak.current, currentFlowState);
const pythonCode = getPythonCode(flow, tweak.current, currentFlowState);
const widgetCode = getWidgetCode(flow, autoLogin, currentFlowState);
const curl_code = getCurlCode(flow, autoLogin, tweak.current, flowState);
const pythonCode = getPythonCode(flow, tweak.current, flowState);
const widgetCode = getWidgetCode(flow, autoLogin, flowState);
const tweaksCode = buildTweaks(flow);
const codesArray = [
curl_code,
@ -171,11 +172,11 @@ const ApiModal = forwardRef(
flow,
autoLogin,
tweak.current,
currentFlowState
flowState
);
const curl_code = getCurlCode(flow, autoLogin, tweak.current, currentFlowState);
const pythonCode = getPythonCode(flow, tweak.current, currentFlowState);
const widgetCode = getWidgetCode(flow, autoLogin, currentFlowState);
const curl_code = getCurlCode(flow, autoLogin, tweak.current, flowState);
const pythonCode = getPythonCode(flow, tweak.current, flowState);
const widgetCode = getWidgetCode(flow, autoLogin, flowState);
tabs![0].code = curl_code;
tabs![1].code = pythonApiCode;

View file

@ -40,19 +40,19 @@ export default function FormModal({
}): JSX.Element {
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const currentFlowState = useFlowsManagerStore(
(state) => state.currentFlowState
const flowState = useFlowStore(
(state) => state.flowState
);
const setCurrentFlowState = useFlowsManagerStore(
(state) => state.setCurrentFlowState
const setFlowState = useFlowStore(
(state) => state.setFlowState
);
const [chatValue, setChatValue] = useState(() => {
try {
if (!currentFlowState) {
throw new Error("currentFlowState is undefined");
if (!flowState) {
throw new Error("flowState is undefined");
}
const inputKeys = currentFlowState.input_keys;
const handleKeys = currentFlowState.handle_keys;
const inputKeys = flowState.input_keys;
const handleKeys = flowState.handle_keys;
const keyToUse = Object.keys(inputKeys!).find(
(key) => !handleKeys?.some((j) => j === key) && inputKeys![key] === ""
@ -67,7 +67,7 @@ export default function FormModal({
});
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
const template = useRef(currentFlowState?.template ?? undefined);
const template = useRef(flowState?.template ?? undefined);
const { accessToken } = useContext(AuthContext);
const setErrorData = useAlertStore((state) => state.setErrorData);
const ws = useRef<WebSocket | null>(null);
@ -76,11 +76,11 @@ export default function FormModal({
const messagesRef = useRef<HTMLDivElement | null>(null);
const [chatKey, setChatKey] = useState(() => {
if (currentFlowState?.input_keys) {
return Object.keys(currentFlowState.input_keys!).find(
if (flowState?.input_keys) {
return Object.keys(flowState.input_keys!).find(
(key) =>
!currentFlowState.handle_keys!.some((j) => j === key) &&
currentFlowState.input_keys![key] === ""
!flowState.handle_keys!.some((j) => j === key) &&
flowState.input_keys![key] === ""
);
}
// TODO: return a sensible default
@ -386,7 +386,7 @@ export default function FormModal({
let nodeValidationErrors = validateNodes(nodes, edges);
if (nodeValidationErrors.length === 0) {
setLockChat(true);
let inputs = currentFlowState?.input_keys;
let inputs = flowState?.input_keys;
setChatValue("");
const message = inputs;
addChatHistory(message!, true, chatKey!, template.current);
@ -398,8 +398,8 @@ export default function FormModal({
description: flow.description,
chatKey: chatKey!,
});
if (currentFlowState && chatKey) {
setCurrentFlowState((old: FlowState | undefined) => {
if (flowState && chatKey) {
setFlowState((old: FlowState | undefined) => {
let newFlowState = cloneDeep(old!);
newFlowState.input_keys![chatKey] = "";
return newFlowState;
@ -414,7 +414,7 @@ export default function FormModal({
}
function clearChat(): void {
setChatHistory([]);
template.current = currentFlowState?.template;
template.current = flowState?.template;
ws.current?.send(JSON.stringify({ clear_history: true }));
if (lockChat) setLockChat(false);
}
@ -422,7 +422,7 @@ export default function FormModal({
function handleOnCheckedChange(checked: boolean, i: string) {
if (checked === true) {
setChatKey(i);
setChatValue(currentFlowState?.input_keys![i] ?? "");
setChatValue(flowState?.input_keys![i] ?? "");
} else {
setChatKey(null!);
setChatValue("");
@ -431,7 +431,7 @@ export default function FormModal({
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger hidden></DialogTrigger>
{currentFlowState && currentFlowState && (
{flowState && flowState && (
<DialogContent className="min-w-[80vw]">
<DialogHeader>
<DialogTitle className="flex items-center">
@ -467,8 +467,8 @@ export default function FormModal({
</div>
</div>
{currentFlowState?.input_keys
? Object.keys(currentFlowState?.input_keys!).map(
{flowState?.input_keys
? Object.keys(flowState?.input_keys!).map(
(key, index) => (
<div className="file-component-accordion-div" key={index}>
<AccordionComponent
@ -490,7 +490,7 @@ export default function FormModal({
handleOnCheckedChange(value, key)
}
size="small"
disabled={currentFlowState.handle_keys!.some(
disabled={flowState.handle_keys!.some(
(t) => t === key
)}
/>
@ -501,7 +501,7 @@ export default function FormModal({
keyValue={key}
>
<div className="file-component-tab-column">
{currentFlowState?.handle_keys!.some(
{flowState?.handle_keys!.some(
(t) => t === key
) && (
<div className="font-normal text-muted-foreground ">
@ -511,11 +511,11 @@ export default function FormModal({
<Textarea
className="custom-scroll"
value={
currentFlowState?.input_keys![key]
flowState?.input_keys![key]
}
onChange={(e) => {
if (currentFlowState) {
setCurrentFlowState(
if (flowState) {
setFlowState(
(old: FlowState | undefined) => {
let newFlowState = cloneDeep(old!);
newFlowState.input_keys![
@ -535,7 +535,7 @@ export default function FormModal({
)
)
: null}
{currentFlowState?.memory_keys!.map((key, index) => (
{flowState?.memory_keys!.map((key, index) => (
<div className="file-component-accordion-div" key={index}>
<AccordionComponent
trigger={
@ -627,8 +627,8 @@ export default function FormModal({
sendMessage={sendMessage}
setChatValue={(value) => {
setChatValue(value);
if (currentFlowState && chatKey) {
setCurrentFlowState(
if (flowState && chatKey) {
setFlowState(
(old: FlowState | undefined) => {
let newFlowState = cloneDeep(old!);
newFlowState.input_keys![

View file

@ -72,6 +72,7 @@ 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
);
@ -174,13 +175,11 @@ export default function Page({
useEffect(() => {
setLoading(true);
if (reactFlowInstance) {
useFlowStore.setState({
resetFlow({
nodes: flow?.data?.nodes ?? [],
edges: flow?.data?.edges ?? [],
});
reactFlowInstance.setViewport(
flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 }
);
viewport: flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 },
})
}
// Clear the previous timeout

View file

@ -28,16 +28,51 @@ import useFlowsManagerStore from "./flowsManagerStore";
// 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) => ({
reactFlowInstance: null,
setReactFlowInstance: (newState) => {
set({ reactFlowInstance: newState });
},
sseData: {},
flowState: undefined,
nodes: [],
edges: [],
isBuilt: false,
isBuilding: false,
isBuilt: false,
reactFlowInstance: null,
lastCopiedSelection: null,
resetFlow: ({ nodes, edges, viewport }) => {
set({
nodes,
edges,
flowState: undefined,
sseData: {},
isBuilt: false,
});
get().reactFlowInstance!.setViewport(viewport);
},
updateSSEData: (sseData) => {
set((state) => ({ sseData: { ...state.sseData, ...sseData } }));
},
setIsBuilding: (isBuilding) => {
set({ isBuilding });
},
setIsBuilt: (isBuilt) => {
set({ isBuilt });
},
setFlowState: (flowState) => {
const newFlowState =
typeof flowState === "function"
? flowState(get().flowState)
: flowState;
if(newFlowState !== get().flowState){
set(() => ({
flowState: newFlowState,
}));
}
},
setReactFlowInstance: (newState) => {
set({ reactFlowInstance: newState });
},
onNodesChange: (changes: NodeChange[]) => {
set({
nodes: applyNodeChanges(changes, get().nodes),
@ -52,29 +87,30 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
let newChange = typeof change === "function" ? change(get().nodes) : change;
let newEdges = cleanEdges(newChange, get().edges);
set({ edges: newEdges });
set({ nodes: newChange });
set({ edges: newEdges, nodes: newChange, flowState: undefined });
useFlowsManagerStore
.getState()
.autoSaveCurrentFlow(
const flowsManager = useFlowsManagerStore.getState()
flowsManager.autoSaveCurrentFlow(
newChange,
newEdges,
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }
);
},
setEdges: (change) => {
let newChange = typeof change === "function" ? change(get().edges) : change;
set({ edges: newChange });
set({ edges: newChange, flowState: undefined });
useFlowsManagerStore
.getState()
.autoSaveCurrentFlow(
const flowsManager = useFlowsManagerStore.getState()
flowsManager.autoSaveCurrentFlow(
get().nodes,
newChange,
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }
);
},
setNode: (id: string, change: Node | ((oldState: Node) => Node)) => {
let newChange =
@ -204,7 +240,6 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
});
set({ edges: newEdges });
},
lastCopiedSelection: null,
setLastCopiedSelection: (newSelection) => {
set({ lastCopiedSelection: newSelection });
},

View file

@ -38,7 +38,6 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
setCurrentFlowId: (currentFlowId: string) => {
set((state) => ({
currentFlowId,
currentFlowState: state.flowsState[state.currentFlowId],
currentFlow: state.flows.find((flow) => flow.id === currentFlowId),
}));
},
@ -52,23 +51,6 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
currentFlow: undefined,
isLoading: true,
setIsLoading: (isLoading: boolean) => set({ isLoading }),
flowsState: {},
currentFlowState: undefined,
setCurrentFlowState: (
flowState: FlowState | ((oldState: FlowState | undefined) => FlowState)
) => {
const newFlowState =
typeof flowState === "function"
? flowState(get().currentFlowState)
: flowState;
set((state) => ({
flowsState: {
...state.flowsState,
[state.currentFlowId]: newFlowState,
},
currentFlowState: newFlowState,
}));
},
refreshFlows: () => {
return new Promise<void>((resolve, reject) => {
set({ isLoading: true });

View file

@ -1,13 +0,0 @@
import { create } from "zustand";
import { SSEStoreType } from "../types/zustand/sse";
export const useSSEStore = create<SSEStoreType>((set) => ({
updateSSEData: (sseData) => {
set({ sseData });
},
sseData: {},
isBuilding: false,
setIsBuilding: (isBuilding) => {
set({ isBuilding });
},
}));

View file

@ -55,7 +55,7 @@ export type FlowsContextType = {
};
export type FlowsState = {
[key: string]: FlowState;
[key: string]: FlowState | undefined;
};
export type FlowState = {

View file

@ -5,11 +5,20 @@ import {
OnEdgesChange,
OnNodesChange,
ReactFlowInstance,
Viewport,
} from "reactflow";
import { FlowState } from "../../tabs";
export type FlowStoreType = {
updateSSEData: (sseData: object) => void;
sseData: object;
isBuilding: boolean;
setIsBuilding: (isBuilding: boolean) => void;
resetFlow: (flow: {nodes: Node[], edges: Edge[], viewport: Viewport}) => void;
reactFlowInstance: ReactFlowInstance | null;
setReactFlowInstance: (newState: ReactFlowInstance) => void;
flowState: FlowState | undefined;
setFlowState: (state: FlowState | undefined | ((oldState: FlowState | undefined) => FlowState)) => void;
nodes: Node[];
edges: Edge[];
onNodesChange: OnNodesChange;

View file

@ -10,9 +10,6 @@ export type FlowsManagerStoreType = {
setCurrentFlowId: (currentFlowId: string) => void;
isLoading: boolean;
setIsLoading: (isLoading: boolean) => void;
flowsState: FlowsState;
currentFlowState: FlowState | undefined;
setCurrentFlowState: (state: FlowState | ((oldState: FlowState | undefined) => FlowState)) => void;
refreshFlows: () => Promise<void>;
saveFlow: (flow: FlowType, silent?: boolean) => Promise<void>;
autoSaveCurrentFlow: (nodes: Node[], edges: Edge[], viewport: Viewport) => void;

View file

@ -1,6 +0,0 @@
export type SSEStoreType = {
updateSSEData: (sseData: object) => void,
sseData: object,
isBuilding: boolean,
setIsBuilding: (isBuilding: boolean) => void,
}

View file

@ -217,11 +217,11 @@ export function groupByFamily(
}));
}
export function buildInputs(currentFlowState?: FlowState): string {
return currentFlowState &&
currentFlowState.input_keys &&
Object.keys(currentFlowState.input_keys!).length > 0
? JSON.stringify(currentFlowState.input_keys)
export function buildInputs(flowState?: FlowState): string {
return flowState &&
flowState.input_keys &&
Object.keys(flowState.input_keys!).length > 0
? JSON.stringify(flowState.input_keys)
: '{"input": "message"}';
}
@ -296,15 +296,15 @@ export function buildTweakObject(tweak: tweakType) {
* @param {FlowsState} tabsState - The current tabs state.
* @returns {string} - The chat input field
*/
export function getChatInputField(flow: FlowType, currentFlowState?: FlowState) {
export function getChatInputField(flow: FlowType, flowState?: FlowState) {
let chat_input_field = "text";
if (
currentFlowState &&
currentFlowState.input_keys
flowState &&
flowState.input_keys
) {
chat_input_field = Object.keys(
currentFlowState.input_keys!
flowState.input_keys!
)[0];
}
return chat_input_field;
@ -319,7 +319,7 @@ export function getPythonApiCode(
flow: FlowType,
isAuth: boolean,
tweak?: any[],
currentFlowState?: FlowState
flowState?: FlowState
): string {
const flowId = flow.id;
@ -328,7 +328,7 @@ export function getPythonApiCode(
// node.data.id
// }
const tweaks = buildTweaks(flow);
const inputs = buildInputs(currentFlowState);
const inputs = buildInputs(flowState);
return `import requests
from typing import Optional
@ -383,11 +383,11 @@ export function getCurlCode(
flow: FlowType,
isAuth: boolean,
tweak?: any[],
currentFlowState?: FlowState
flowState?: FlowState
): string {
const flowId = flow.id;
const tweaks = buildTweaks(flow);
const inputs = buildInputs(currentFlowState);
const inputs = buildInputs(flowState);
return `curl -X POST \\
${window.location.protocol}//${
@ -411,11 +411,11 @@ export function getCurlCode(
export function getPythonCode(
flow: FlowType,
tweak?: any[],
currentFlowState?: FlowState
flowState?: FlowState
): string {
const flowName = flow.name;
const tweaks = buildTweaks(flow);
const inputs = buildInputs(currentFlowState);
const inputs = buildInputs(flowState);
return `from langflow import load_flow_from_json
TWEAKS = ${
tweak && tweak.length > 0
@ -436,12 +436,12 @@ flow(inputs)`;
export function getWidgetCode(
flow: FlowType,
isAuth: boolean,
currentFlowState?: FlowState
flowState?: FlowState
): string {
const flowId = flow.id;
const flowName = flow.name;
const inputs = buildInputs(currentFlowState);
let chat_input_field = getChatInputField(flow, currentFlowState);
const inputs = buildInputs(flowState);
let chat_input_field = getChatInputField(flow, flowState);
return `<script src="https://cdn.jsdelivr.net/gh/logspace-ai/langflow-embedded-chat@main/dist/build/static/js/bundle.min.js"></script>
@ -452,7 +452,7 @@ chat_input_field: Input key that you want the chat to send the user message with
window_title="${flowName}"
flow_id="${flowId}"
${
currentFlowState
flowState
? `chat_inputs='${inputs}'
chat_input_field="${chat_input_field}"
`