diff --git a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx index ba89279a8..6cee73615 100644 --- a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx +++ b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx @@ -8,8 +8,9 @@ import { FlowType } from "../../../types/flow"; import { FlowsContext } from "../../../contexts/flowsContext"; import useAlertStore from "../../../stores/alertStore"; import useFlowStore from "../../../stores/flowStore"; +import useFlowsManagerStore from "../../../stores/flowsManagerStore"; import { parsedDataType } from "../../../types/components"; -import { FlowsState } from "../../../types/tabs"; +import { FlowState } from "../../../types/tabs"; import { validateNodes } from "../../../utils/reactflowUtils"; import RadialProgressComponent from "../../RadialProgress"; import IconComponent from "../../genericIconComponent"; @@ -25,11 +26,14 @@ export default function BuildTrigger({ isBuilt: boolean; }): JSX.Element { const { updateSSEData, isBuilding, setIsBuilding, sseData } = useSSE(); - const { setTabsState, saveFlow } = useContext(FlowsContext); + const { saveFlow } = useContext(FlowsContext); 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 [isIconTouched, setIsIconTouched] = useState(false); const eventClick = isBuilding ? "pointer-events-none" : ""; @@ -99,15 +103,7 @@ export default function BuildTrigger({ // If the event is a log, log it setSuccessData({ title: parsedData.log }); } else if (parsedData.input_keys !== undefined) { - setTabsState((old: FlowsState) => { - return { - ...old, - [flowId]: { - ...old[flowId], - formKeysData: parsedData, - }, - }; - }); + setCurrentFlowState(parsedData); } else { // Otherwise, process the data const isValid = processStreamResult(parsedData); diff --git a/src/frontend/src/components/chatComponent/index.tsx b/src/frontend/src/components/chatComponent/index.tsx index 1bf8e6ab9..22362bca5 100644 --- a/src/frontend/src/components/chatComponent/index.tsx +++ b/src/frontend/src/components/chatComponent/index.tsx @@ -10,6 +10,7 @@ import { getBuildStatus } from "../../controllers/API"; import FormModal from "../../modals/formModal"; import useFlowStore from "../../stores/flowStore"; import { NodeType } from "../../types/flow"; +import useFlowsManagerStore from "../../stores/flowsManagerStore"; export default function Chat({ flow }: ChatType): JSX.Element { const [open, setOpen] = useState(false); @@ -17,7 +18,7 @@ export default function Chat({ flow }: ChatType): JSX.Element { const isBuilt = useFlowStore((state) => state.isBuilt); const setIsBuilt = useFlowStore((state) => state.setIsBuilt); const isPending = useFlowStore((state) => state.isPending); - const { tabsState } = useContext(FlowsContext); + const currentFlowState = useFlowsManagerStore((state) => state.currentFlowState); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { @@ -61,10 +62,9 @@ export default function Chat({ flow }: ChatType): JSX.Element { setIsBuilt(false); } if ( - tabsState && - tabsState[flow.id] && - tabsState[flow.id].formKeysData && - tabsState[flow.id].formKeysData.input_keys !== null + currentFlowState && + currentFlowState.formKeysData && + currentFlowState.formKeysData.input_keys !== null ) { setCanOpen(true); } else { @@ -72,7 +72,7 @@ export default function Chat({ flow }: ChatType): JSX.Element { } prevNodesRef.current = currentNodes; - }, [tabsState, flow.id]); + }, [currentFlowState, flow.id]); return ( <> @@ -84,8 +84,8 @@ export default function Chat({ flow }: ChatType): JSX.Element { isBuilt={isBuilt} /> {isBuilt && - tabsState[flow.id] && - tabsState[flow.id].formKeysData && + currentFlowState && + currentFlowState.formKeysData && canOpen && ( ([]); const tweaksList = useRef([]); - const { tabsState } = useContext(FlowsContext); const [getTweak, setTweak] = useState([]); + const currentFlowState = useFlowsManagerStore( + (state) => state.currentFlowState + ); const pythonApiCode = getPythonApiCode( flow, autoLogin, tweak.current, - tabsState + currentFlowState ); - const curl_code = getCurlCode(flow, autoLogin, tweak.current, tabsState); - const pythonCode = getPythonCode(flow, tweak.current, tabsState); - const widgetCode = getWidgetCode(flow, autoLogin, tabsState); + const curl_code = getCurlCode(flow, autoLogin, tweak.current, currentFlowState); + const pythonCode = getPythonCode(flow, tweak.current, currentFlowState); + const widgetCode = getWidgetCode(flow, autoLogin, currentFlowState); const tweaksCode = buildTweaks(flow); const codesArray = [ curl_code, @@ -169,11 +171,11 @@ const ApiModal = forwardRef( flow, autoLogin, tweak.current, - tabsState + currentFlowState ); - const curl_code = getCurlCode(flow, autoLogin, tweak.current, tabsState); - const pythonCode = getPythonCode(flow, tweak.current, tabsState); - const widgetCode = getWidgetCode(flow, autoLogin, tabsState); + const curl_code = getCurlCode(flow, autoLogin, tweak.current, currentFlowState); + const pythonCode = getPythonCode(flow, tweak.current, currentFlowState); + const widgetCode = getWidgetCode(flow, autoLogin, currentFlowState); tabs![0].code = curl_code; tabs![1].code = pythonApiCode; diff --git a/src/frontend/src/modals/formModal/index.tsx b/src/frontend/src/modals/formModal/index.tsx index bbf1d79ec..b14d7a9d0 100644 --- a/src/frontend/src/modals/formModal/index.tsx +++ b/src/frontend/src/modals/formModal/index.tsx @@ -6,7 +6,7 @@ import { classNames } from "../../utils/utils"; import ChatInput from "./chatInput"; import ChatMessage from "./chatMessage"; -import _ from "lodash"; +import _, { cloneDeep } from "lodash"; import AccordionComponent from "../../components/AccordionComponent"; import IconComponent from "../../components/genericIconComponent"; import ToggleShadComponent from "../../components/toggleShadComponent"; @@ -22,11 +22,11 @@ import { import { Textarea } from "../../components/ui/textarea"; import { CHAT_FORM_DIALOG_SUBTITLE } from "../../constants/constants"; import { AuthContext } from "../../contexts/authContext"; -import { FlowsContext } from "../../contexts/flowsContext"; import { getBuildStatus } from "../../controllers/API"; import useAlertStore from "../../stores/alertStore"; import useFlowStore from "../../stores/flowStore"; -import { FlowsState } from "../../types/tabs"; +import useFlowsManagerStore from "../../stores/flowsManagerStore"; +import { FlowState, FlowsState } from "../../types/tabs"; import { validateNodes } from "../../utils/reactflowUtils"; export default function FormModal({ @@ -38,12 +38,17 @@ export default function FormModal({ setOpen: (open: boolean) => void; flow: FlowType; }): JSX.Element { - const { tabsState, setTabsState } = useContext(FlowsContext); const nodes = useFlowStore((state) => state.nodes); const edges = useFlowStore((state) => state.edges); + const currentFlowState = useFlowsManagerStore( + (state) => state.currentFlowState + ); + const setCurrentFlowState = useFlowsManagerStore( + (state) => state.setCurrentFlowState + ); const [chatValue, setChatValue] = useState(() => { try { - const { formKeysData } = tabsState[flow.id]; + const formKeysData = currentFlowState?.formKeysData; if (!formKeysData) { throw new Error("formKeysData is undefined"); } @@ -63,23 +68,20 @@ export default function FormModal({ }); const [chatHistory, setChatHistory] = useState([]); - const template = useRef(tabsState[flow.id].formKeysData.template); + const template = useRef(currentFlowState?.formKeysData.template ?? undefined); const { accessToken } = useContext(AuthContext); const setErrorData = useAlertStore((state) => state.setErrorData); const ws = useRef(null); const [lockChat, setLockChat] = useState(false); const isOpen = useRef(open); const messagesRef = useRef(null); - const id = useRef(flow.id); - const tabsStateFlowId = tabsState[flow.id]; - const tabsStateFlowIdFormKeysData = tabsStateFlowId.formKeysData; + const [chatKey, setChatKey] = useState(() => { - if (tabsState[flow.id]?.formKeysData?.input_keys) { - return Object.keys(tabsState[flow.id].formKeysData.input_keys!).find( + if (currentFlowState?.formKeysData?.input_keys) { + return Object.keys(currentFlowState.formKeysData.input_keys!).find( (key) => - !tabsState[flow.id].formKeysData.handle_keys!.some( - (j) => j === key - ) && tabsState[flow.id].formKeysData.input_keys![key] === "" + !currentFlowState.formKeysData.handle_keys!.some((j) => j === key) && + currentFlowState.formKeysData.input_keys![key] === "" ); } // TODO: return a sensible default @@ -94,9 +96,6 @@ export default function FormModal({ useEffect(() => { isOpen.current = open; }, [open]); - useEffect(() => { - id.current = flow.id; - }, [flow.id, tabsStateFlowId, tabsStateFlowIdFormKeysData]); var isStream = false; @@ -297,7 +296,7 @@ export default function FormModal({ function connectWS(): void { try { const urlWs = getWebSocketUrl( - id.current, + flow.id, process.env.NODE_ENV === "development" ); const newWs = new WebSocket(urlWs); @@ -388,7 +387,7 @@ export default function FormModal({ let nodeValidationErrors = validateNodes(nodes, edges); if (nodeValidationErrors.length === 0) { setLockChat(true); - let inputs = tabsState[id.current].formKeysData.input_keys; + let inputs = currentFlowState?.formKeysData.input_keys; setChatValue(""); const message = inputs; addChatHistory(message!, true, chatKey!, template.current); @@ -400,12 +399,13 @@ export default function FormModal({ description: flow.description, chatKey: chatKey!, }); - setTabsState((old: FlowsState) => { - if (!chatKey) return old; - let newTabsState = _.cloneDeep(old); - newTabsState[id.current].formKeysData.input_keys![chatKey] = ""; - return newTabsState; - }); + if (currentFlowState && chatKey) { + setCurrentFlowState((old: FlowState | undefined) => { + let newFlowState = cloneDeep(old!); + newFlowState.formKeysData.input_keys![chatKey] = ""; + return newFlowState; + }); + } } else { setErrorData({ title: "Oops! Looks like you missed some required information:", @@ -415,7 +415,7 @@ export default function FormModal({ } function clearChat(): void { setChatHistory([]); - template.current = tabsState[id.current].formKeysData.template; + template.current = currentFlowState?.formKeysData.template; ws.current?.send(JSON.stringify({ clear_history: true })); if (lockChat) setLockChat(false); } @@ -423,7 +423,7 @@ export default function FormModal({ function handleOnCheckedChange(checked: boolean, i: string) { if (checked === true) { setChatKey(i); - setChatValue(tabsState[flow.id].formKeysData.input_keys![i]); + setChatValue(currentFlowState?.formKeysData.input_keys![i] ?? ""); } else { setChatKey(null!); setChatValue(""); @@ -432,7 +432,7 @@ export default function FormModal({ return ( - {tabsState[flow.id].formKeysData && ( + {currentFlowState && currentFlowState.formKeysData && ( @@ -468,106 +468,103 @@ export default function FormModal({ - {tabsState[id.current]?.formKeysData?.input_keys - ? Object.keys( - tabsState[id.current].formKeysData.input_keys! - ).map((key, index) => ( -
- - - {key} - + {currentFlowState?.formKeysData?.input_keys + ? Object.keys(currentFlowState?.formKeysData.input_keys!).map( + (key, index) => ( +
+ + + {key} + -
{ - event.stopPropagation(); - }} - > - - handleOnCheckedChange(value, key) +
{ + event.stopPropagation(); + }} + > + + handleOnCheckedChange(value, key) + } + size="small" + disabled={currentFlowState.formKeysData.handle_keys!.some( + (t) => t === key + )} + /> +
+
+ } + key={index} + keyValue={key} + > +
+ {currentFlowState?.formKeysData.handle_keys!.some( + (t) => t === key + ) && ( +
+ Source: Component +
+ )} +
- } - key={index} - keyValue={key} - > -
- {tabsState[id.current].formKeysData.handle_keys!.some( - (t) => t === key - ) && ( -
- Source: Component -
- )} - -
-
-
- )) +
+
+ ) + ) : null} - {tabsState[id.current].formKeysData.memory_keys!.map( - (key, index) => ( -
- - - {key} - -
- {}} - size="small" - disabled={true} - /> -
-
- } - key={index} - keyValue={key} - > -
-
- Source: Memory + {currentFlowState?.formKeysData.memory_keys!.map((key, index) => ( +
+ + + {key} + +
+ {}} + size="small" + disabled={true} + />
- -
- ) - )} + } + key={index} + keyValue={key} + > +
+
+ Source: Memory +
+
+ +
+ ))}
@@ -631,13 +628,17 @@ export default function FormModal({ sendMessage={sendMessage} setChatValue={(value) => { setChatValue(value); - setTabsState((old: FlowsState) => { - let newTabsState = _.cloneDeep(old); - newTabsState[id.current].formKeysData.input_keys![ - chatKey! - ] = value; - return newTabsState; - }); + if (currentFlowState && chatKey) { + setCurrentFlowState( + (old: FlowState | undefined) => { + let newFlowState = cloneDeep(old!); + newFlowState.formKeysData.input_keys![ + chatKey + ] = value; + return newFlowState; + } + ); + } }} inputRef={ref} /> diff --git a/src/frontend/src/stores/flowsManagerStore.ts b/src/frontend/src/stores/flowsManagerStore.ts index e61d85548..bc0d20d3c 100644 --- a/src/frontend/src/stores/flowsManagerStore.ts +++ b/src/frontend/src/stores/flowsManagerStore.ts @@ -1,20 +1,29 @@ import { create } from "zustand"; -import { FlowsManagerStoreType } from "../types/zustand/flowsManager"; import { FlowState } from "../types/tabs"; +import { FlowsManagerStoreType } from "../types/zustand/flowsManager"; const useFlowsManagerStore = create((set, get) => ({ currentFlowId: "", setCurrentFlowId: (currentFlowId: string) => { - set({ currentFlowId, currentFlow: get().flows.find((flow) => flow.id === currentFlowId) }); -}, + set((state) => ({ + currentFlowId, + currentFlowState: state.flowsState[state.currentFlowId], + currentFlow: state.flows.find((flow) => flow.id === currentFlowId), + })); + }, flows: [], 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; + setCurrentFlowState: ( + flowState: FlowState | ((oldState: FlowState | undefined) => FlowState) + ) => { + const newFlowState = + typeof flowState === "function" + ? flowState(get().currentFlowState) + : flowState; set((state) => ({ flowsState: { ...state.flowsState, @@ -23,7 +32,6 @@ const useFlowsManagerStore = create((set, get) => ({ currentFlowState: newFlowState, })); }, - })); export default useFlowsManagerStore; diff --git a/src/frontend/src/types/tabs/index.ts b/src/frontend/src/types/tabs/index.ts index 4196cecdc..f5815c62c 100644 --- a/src/frontend/src/types/tabs/index.ts +++ b/src/frontend/src/types/tabs/index.ts @@ -59,7 +59,6 @@ export type FlowsState = { }; export type FlowState = { - isPending: boolean; formKeysData: { template?: string; input_keys?: Object; diff --git a/src/frontend/src/utils/utils.ts b/src/frontend/src/utils/utils.ts index 478028bca..006d29435 100644 --- a/src/frontend/src/utils/utils.ts +++ b/src/frontend/src/utils/utils.ts @@ -13,7 +13,7 @@ import { tweakType, } from "../types/components"; import { FlowType, NodeType } from "../types/flow"; -import { FlowsState } from "../types/tabs"; +import { FlowState, FlowsState } from "../types/tabs"; import { buildTweaks } from "./reactflowUtils"; export function classNames(...classes: Array): string { @@ -217,13 +217,12 @@ export function groupByFamily( })); } -export function buildInputs(tabsState: FlowsState, id: string): string { - return tabsState && - tabsState[id] && - tabsState[id].formKeysData && - tabsState[id].formKeysData.input_keys && - Object.keys(tabsState[id].formKeysData.input_keys!).length > 0 - ? JSON.stringify(tabsState[id].formKeysData.input_keys) +export function buildInputs(currentFlowState?: FlowState): string { + return currentFlowState && + currentFlowState.formKeysData && + currentFlowState.formKeysData.input_keys && + Object.keys(currentFlowState.formKeysData.input_keys!).length > 0 + ? JSON.stringify(currentFlowState.formKeysData.input_keys) : '{"input": "message"}'; } @@ -298,17 +297,16 @@ export function buildTweakObject(tweak: tweakType) { * @param {FlowsState} tabsState - The current tabs state. * @returns {string} - The chat input field */ -export function getChatInputField(flow: FlowType, tabsState?: FlowsState) { +export function getChatInputField(flow: FlowType, currentFlowState?: FlowState) { let chat_input_field = "text"; if ( - tabsState && - tabsState[flow.id] && - tabsState[flow.id].formKeysData && - tabsState[flow.id].formKeysData.input_keys + currentFlowState && + currentFlowState.formKeysData && + currentFlowState.formKeysData.input_keys ) { chat_input_field = Object.keys( - tabsState[flow.id].formKeysData.input_keys! + currentFlowState.formKeysData.input_keys! )[0]; } return chat_input_field; @@ -323,7 +321,7 @@ export function getPythonApiCode( flow: FlowType, isAuth: boolean, tweak?: any[], - tabsState?: FlowsState + currentFlowState?: FlowState ): string { const flowId = flow.id; @@ -332,7 +330,7 @@ export function getPythonApiCode( // node.data.id // } const tweaks = buildTweaks(flow); - const inputs = buildInputs(tabsState!, flow.id); + const inputs = buildInputs(currentFlowState); return `import requests from typing import Optional @@ -387,11 +385,11 @@ export function getCurlCode( flow: FlowType, isAuth: boolean, tweak?: any[], - tabsState?: FlowsState + currentFlowState?: FlowState ): string { const flowId = flow.id; const tweaks = buildTweaks(flow); - const inputs = buildInputs(tabsState!, flow.id); + const inputs = buildInputs(currentFlowState); return `curl -X POST \\ ${window.location.protocol}//${ @@ -415,11 +413,11 @@ export function getCurlCode( export function getPythonCode( flow: FlowType, tweak?: any[], - tabsState?: FlowsState + currentFlowState?: FlowState ): string { const flowName = flow.name; const tweaks = buildTweaks(flow); - const inputs = buildInputs(tabsState!, flow.id); + const inputs = buildInputs(currentFlowState); return `from langflow import load_flow_from_json TWEAKS = ${ tweak && tweak.length > 0 @@ -440,12 +438,12 @@ flow(inputs)`; export function getWidgetCode( flow: FlowType, isAuth: boolean, - tabsState?: FlowsState + currentFlowState?: FlowState ): string { const flowId = flow.id; const flowName = flow.name; - const inputs = buildInputs(tabsState!, flow.id); - let chat_input_field = getChatInputField(flow, tabsState); + const inputs = buildInputs(currentFlowState); + let chat_input_field = getChatInputField(flow, currentFlowState); return ` @@ -456,7 +454,7 @@ chat_input_field: Input key that you want the chat to send the user message with window_title="${flowName}" flow_id="${flowId}" ${ - tabsState![flow.id] && tabsState![flow.id].formKeysData + currentFlowState && currentFlowState.formKeysData ? `chat_inputs='${inputs}' chat_input_field="${chat_input_field}" `