diff --git a/src/frontend/src/components/core/chatComponents/ContentDisplay.tsx b/src/frontend/src/components/core/chatComponents/ContentDisplay.tsx index 323320943..d875e4b71 100644 --- a/src/frontend/src/components/core/chatComponents/ContentDisplay.tsx +++ b/src/frontend/src/components/core/chatComponents/ContentDisplay.tsx @@ -4,7 +4,7 @@ import Markdown from "react-markdown"; import rehypeMathjax from "rehype-mathjax"; import remarkGfm from "remark-gfm"; import ForwardedIconComponent from "../../common/genericIconComponent"; -import SimplifiedCodeTabComponent from "../codeTabsComponent/ChatCodeTabComponent"; +import SimplifiedCodeTabComponent from "../codeTabsComponent"; import DurationDisplay from "./DurationDisplay"; export default function ContentDisplay({ diff --git a/src/frontend/src/components/core/codeTabsComponent/ChatCodeTabComponent.tsx b/src/frontend/src/components/core/codeTabsComponent/ChatCodeTabComponent.tsx deleted file mode 100644 index 7f9ea7665..000000000 --- a/src/frontend/src/components/core/codeTabsComponent/ChatCodeTabComponent.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { useState } from "react"; -import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; -import { tomorrow } from "react-syntax-highlighter/dist/cjs/styles/prism"; -import IconComponent from "../../common/genericIconComponent"; -import { Button } from "../../ui/button"; - -type SimplifiedCodeTabProps = { - code: string; - language: string; -}; - -export default function SimplifiedCodeTabComponent({ - code, - language, -}: SimplifiedCodeTabProps) { - const [isCopied, setIsCopied] = useState(false); - - const copyToClipboard = () => { - if (!navigator.clipboard || !navigator.clipboard.writeText) { - return; - } - - navigator.clipboard.writeText(code).then(() => { - setIsCopied(true); - - setTimeout(() => { - setIsCopied(false); - }, 2000); - }); - }; - - return ( -
-
- {language} - -
- - {code} - -
- ); -} diff --git a/src/frontend/src/components/core/codeTabsComponent/components/tweakComponent/index.tsx b/src/frontend/src/components/core/codeTabsComponent/components/tweakComponent/index.tsx index efc62cc3c..2c743fcea 100644 --- a/src/frontend/src/components/core/codeTabsComponent/components/tweakComponent/index.tsx +++ b/src/frontend/src/components/core/codeTabsComponent/components/tweakComponent/index.tsx @@ -37,7 +37,6 @@ export function TweakComponent({ (false); - const dark = useDarkStore((state) => state.dark); - const nodes = useTweaksStore((state) => state.nodes); +type SimplifiedCodeTabProps = { + code: string; + language: string; +}; + +export default function SimplifiedCodeTabComponent({ + code, + language, +}: SimplifiedCodeTabProps) { + const [isCopied, setIsCopied] = useState(false); const copyToClipboard = () => { if (!navigator.clipboard || !navigator.clipboard.writeText) { return; } - navigator.clipboard.writeText(tabs[activeTab].code).then(() => { + navigator.clipboard.writeText(code).then(() => { setIsCopied(true); setTimeout(() => { @@ -40,121 +30,33 @@ export default function CodeTabsComponent({ }; return ( - { - setActiveTab(value); - }} +
-
- {tabs.length > 0 && tabs[0].name !== "" ? ( - - {tabs.map((tab, index) => ( - - {tab.name} - - ))} - - ) : ( -
- )} -
- - {tabs.map((tab, idx) => ( - + {language} + - - )} -
- - {tab.code} - - - ) : tab.name.toLowerCase() === "tweaks" ? ( - <> - - - ) : null} - - ))} -
+ {isCopied ? ( + + ) : ( + + )} + + + + {code} + + ); } diff --git a/src/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsx b/src/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsx index 88d24087f..cefd32d0a 100644 --- a/src/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsx +++ b/src/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsx @@ -12,7 +12,7 @@ import { usePatchUpdateFlow } from "@/controllers/API/queries/flows/use-patch-up import { CustomLink } from "@/customization/components/custom-link"; import { ENABLE_PUBLISH, ENABLE_WIDGET } from "@/customization/feature-flags"; import { customMcpOpen } from "@/customization/utils/custom-mcp-open"; -import ApiModal from "@/modals/apiModal/new-api-modal"; +import ApiModal from "@/modals/apiModal"; import EmbedModal from "@/modals/EmbedModal/embed-modal"; import useAlertStore from "@/stores/alertStore"; import useAuthStore from "@/stores/authStore"; @@ -87,7 +87,7 @@ export default function PublishDropdown() { + + + )} + + + {open && ( + <> + + + + )} + + + + + + + API access requires an API key. You can{" "} + + {" "} + create an API key + {" "} + in settings. + + + ) + } + > + + Input Schema + + + {true && ( + + )} +
+ +
+
+
+ ); } diff --git a/src/frontend/src/modals/apiModal/new-api-modal.tsx b/src/frontend/src/modals/apiModal/new-api-modal.tsx deleted file mode 100644 index 37fb43548..000000000 --- a/src/frontend/src/modals/apiModal/new-api-modal.tsx +++ /dev/null @@ -1,225 +0,0 @@ -import { TweaksComponent } from "@/components/core/codeTabsComponent/components/tweaksComponent"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Separator } from "@/components/ui/separator"; -import { CustomAPIGenerator } from "@/customization/components/custom-api-generator"; -import useSaveFlow from "@/hooks/flows/use-save-flow"; -import useAuthStore from "@/stores/authStore"; -import useFlowsManagerStore from "@/stores/flowsManagerStore"; -import useFlowStore from "@/stores/flowStore"; -import { isEndpointNameValid } from "@/utils/utils"; -import "ace-builds/src-noconflict/ext-language_tools"; -import "ace-builds/src-noconflict/mode-python"; -import "ace-builds/src-noconflict/theme-github"; -import "ace-builds/src-noconflict/theme-twilight"; -import { cloneDeep } from "lodash"; -import { ChangeEvent, ReactNode, useEffect, useState } from "react"; -import { useShallow } from "zustand/react/shallow"; -import IconComponent from "../../components/common/genericIconComponent"; -import { useTweaksStore } from "../../stores/tweaksStore"; -import BaseModal from "../baseModal"; -import APITabsComponent from "./codeTabs/code-tabs"; - -const MAX_LENGTH = 20; -const MIN_LENGTH = 1; - -export default function ApiModal({ - children, - open: myOpen, - setOpen: mySetOpen, -}: { - children: ReactNode; - open?: boolean; - setOpen?: (a: boolean | ((o?: boolean) => boolean)) => void; -}) { - const autoLogin = useAuthStore((state) => state.autoLogin); - const nodes = useFlowStore((state) => state.nodes); - const [openTweaks, setOpenTweaks] = useState(false); - const tweaks = useTweaksStore((state) => state.tweaks); - const [open, setOpen] = - mySetOpen !== undefined && myOpen !== undefined - ? [myOpen, mySetOpen] - : useState(false); - const newInitialSetup = useTweaksStore((state) => state.newInitialSetup); - - const flowEndpointName = useFlowStore( - useShallow((state) => state.currentFlow?.endpoint_name), - ); - - const [endpointName, setEndpointName] = useState(flowEndpointName ?? ""); - const [validEndpointName, setValidEndpointName] = useState(true); - - const handleEndpointNameChange = (event: ChangeEvent) => { - const { value } = event.target; - // Validate the endpoint name - // use this regex r'^[a-zA-Z0-9_-]+$' - const isValid = isEndpointNameValid(event.target.value, MAX_LENGTH); - setValidEndpointName(isValid); - - // Only update if valid and meets minimum length (if set) - if (isValid && value.length >= MIN_LENGTH) { - setEndpointName!(value); - } else if (value.length === 0) { - // Always allow empty endpoint name (it's optional) - setEndpointName!(""); - } - }; - - useEffect(() => { - if (open) newInitialSetup(nodes); - }, [open]); - - const autoSaving = useFlowsManagerStore((state) => state.autoSaving); - const saveFlow = useSaveFlow(); - const setCurrentFlow = useFlowStore((state) => state.setCurrentFlow); - - function handleSave(): void { - const newFlow = cloneDeep(useFlowStore.getState().currentFlow); - if (!newFlow) return; - newFlow.endpoint_name = - endpointName && endpointName.length > 0 ? endpointName : null; - - if (autoSaving) { - saveFlow(newFlow); - } else { - setCurrentFlow(newFlow); - } - } - - useEffect(() => { - if (!openTweaks && endpointName !== flowEndpointName) handleSave(); - else if (openTweaks) { - setEndpointName(flowEndpointName ?? ""); - } - }, [openTweaks]); - - return ( - <> - - {children} - - - API access requires an API key. You can{" "} - - {" "} - create an API key - {" "} - in settings. - - - ) - } - > - - - {open && ( - <> - - - - )} - - - - - - - API access requires an API key. You can{" "} - - {" "} - create an API key - {" "} - in settings. - - - ) - } - > - - Tweaks - - - {true && ( - - )} -
- -
-
-
- - ); -} diff --git a/src/frontend/src/modals/apiModal/utils/get-curl-code.tsx b/src/frontend/src/modals/apiModal/utils/get-curl-code.tsx index c478188f3..0d93739de 100644 --- a/src/frontend/src/modals/apiModal/utils/get-curl-code.tsx +++ b/src/frontend/src/modals/apiModal/utils/get-curl-code.tsx @@ -1,44 +1,7 @@ import { ENABLE_DATASTAX_LANGFLOW } from "@/customization/feature-flags"; import { customGetHostProtocol } from "@/customization/utils/custom-get-host-protocol"; -import useFlowStore from "@/stores/flowStore"; import { GetCodeType } from "@/types/tweaks"; -/** - * Function to get the curl code for the API - * @param {string} flowId - The id of the flow - * @param {boolean} isAuth - If the API is authenticated - * @returns {string} - The curl code - */ -export function getCurlRunCode({ - flowId, - isAuth, - tweaksBuildedObject, - endpointName, - activeTweaks, -}: GetCodeType): string { - let tweaksString = "{}"; - const inputs = useFlowStore.getState().inputs; - const outputs = useFlowStore.getState().outputs; - const hasChatInput = inputs.some((input) => input.type === "ChatInput"); - const hasChatOutput = outputs.some((output) => output.type === "ChatOutput"); - if (tweaksBuildedObject) - tweaksString = JSON.stringify(tweaksBuildedObject, null, 2); - // show the endpoint name in the curl command if it exists - - const { protocol, host } = customGetHostProtocol(); - - return `curl -X POST \\ - "${protocol}//${host}/api/v1/run/${endpointName || flowId}?stream=false" \\ - -H 'Content-Type: application/json'\\${ - !isAuth ? `\n -H 'x-api-key: '\\` : "" - } - -d '{${!activeTweaks ? `"input_value": "message",` : ""} - "output_type": ${hasChatOutput ? '"chat"' : '"text"'}, - "input_type": ${hasChatInput ? '"chat"' : '"text"'}, - "tweaks": ${tweaksString}}' - `; -} - /** * Generates a cURL command for making a POST request to a webhook endpoint. * diff --git a/src/frontend/src/modals/apiModal/utils/get-js-api-code.tsx b/src/frontend/src/modals/apiModal/utils/get-js-api-code.tsx index b37b536f6..f755dc893 100644 --- a/src/frontend/src/modals/apiModal/utils/get-js-api-code.tsx +++ b/src/frontend/src/modals/apiModal/utils/get-js-api-code.tsx @@ -1,52 +1,5 @@ import { customGetHostProtocol } from "@/customization/utils/custom-get-host-protocol"; -import useFlowStore from "@/stores/flowStore"; -import { GetCodeType } from "@/types/tweaks"; -/** - * Function to generate JavaScript code for interfacing with an API using the LangflowClient class. - * @param {string} flowId - The id of the flow. - * @param {boolean} isAuth - Whether the API requires authentication. - * @param {any[]} tweaksBuildedObject - Customizations applied to the flow. - * @param {string} [endpointName] - Optional endpoint name. - * @returns {string} - The JavaScript code as a string. - */ -export default function getJsApiCode({ - flowId, - isAuth, - tweaksBuildedObject, - endpointName, - activeTweaks, -}: GetCodeType): string { - let tweaksString = "{}"; - if (tweaksBuildedObject) - tweaksString = JSON.stringify(tweaksBuildedObject, null, 8); - const inputs = useFlowStore.getState().inputs; - const outputs = useFlowStore.getState().outputs; - const hasChatInput = inputs.some((input) => input.type === "ChatInput"); - const hasChatOutput = outputs.some((output) => output.type === "ChatOutput"); - - const { protocol, host } = customGetHostProtocol(); - - return `${activeTweaks ? "" : 'let inputValue = ""; // Insert input value here\n\n'}fetch( - "${protocol}//${host}/api/v1/run/${endpointName || flowId}?stream=false", - { - method: "POST", - headers: { - "Authorization": "Bearer ", - "Content-Type": "application/json",${isAuth ? '\n\t\t\t"x-api-key": ' : ""} - }, - body: JSON.stringify({${activeTweaks ? "" : "\n\t\t\tinput_value: inputValue, "} - output_type: ${hasChatOutput ? '"chat"' : '"text"'}, - input_type: ${hasChatInput ? '"chat"' : '"text"'}, - tweaks: ${tweaksString} - }), - }, -) - .then(res => res.json()) - .then(data => console.log(data)) - .catch(error => console.error('Error:', error)); -`; -} /** * Generates JavaScript code for making API calls to a Langflow endpoint. * diff --git a/src/frontend/src/modals/apiModal/utils/get-nodes-with-default-value.ts b/src/frontend/src/modals/apiModal/utils/get-nodes-with-default-value.ts index 92f0041ab..445742d0c 100644 --- a/src/frontend/src/modals/apiModal/utils/get-nodes-with-default-value.ts +++ b/src/frontend/src/modals/apiModal/utils/get-nodes-with-default-value.ts @@ -2,7 +2,14 @@ import { AllNodeType } from "@/types/flow"; import { cloneDeep } from "lodash"; import { LANGFLOW_SUPPORTED_TYPES } from "../../../constants/constants"; -export const getNodesWithDefaultValue = (nodes: AllNodeType[]) => { +export const getNodesWithDefaultValue = ( + nodes: AllNodeType[], + oldTweaks: { + [key: string]: { + [key: string]: any; + }; + }, +) => { const filteredNodes: AllNodeType[] = []; nodes.forEach((node) => { @@ -20,6 +27,8 @@ export const getNodesWithDefaultValue = (nodes: AllNodeType[]) => { if (newNode?.data?.node?.template) { newNode.data.node.template = templateKeys.reduce((acc, key) => { acc[key] = cloneDeep(node?.data?.node?.template[key]); + acc[key].advanced = + node.id in oldTweaks && key in oldTweaks[node.id] ? false : true; return acc; }, {}); } diff --git a/src/frontend/src/modals/apiModal/utils/get-python-api-code.tsx b/src/frontend/src/modals/apiModal/utils/get-python-api-code.tsx index 29eaf283b..46d3057d1 100644 --- a/src/frontend/src/modals/apiModal/utils/get-python-api-code.tsx +++ b/src/frontend/src/modals/apiModal/utils/get-python-api-code.tsx @@ -1,120 +1,4 @@ import { customGetHostProtocol } from "@/customization/utils/custom-get-host-protocol"; -import { GetCodeType } from "@/types/tweaks"; - -/** - * Function to get the python code for the API - * @param {string} flowId - The id of the flow - * @param {boolean} isAuth - If the API is authenticated - * @param {any[]} tweaksBuildedObject - The tweaks - * @param {string} [endpointName] - The optional endpoint name - * @returns {string} - The python code - */ -export default function getPythonApiCode({ - flowId, - tweaksBuildedObject, - endpointName, - activeTweaks, -}: GetCodeType): string { - let tweaksString = "{}"; - if (tweaksBuildedObject) - tweaksString = JSON.stringify(tweaksBuildedObject, null, 2) - .replace(/true/g, "True") - .replace(/false/g, "False") - .replace(/null|undefined/g, "None"); - - return `import argparse -import json -from argparse import RawTextHelpFormatter -import requests -from typing import Optional -import warnings -try: - from langflow.load import upload_file -except ImportError: - warnings.warn("Langflow provides a function to help you upload files to the flow. Please install langflow to use it.") - upload_file = None - -BASE_API_URL = "${window.location.protocol}//${window.location.host}" -FLOW_ID = "${flowId}" -ENDPOINT = "${endpointName || ""}" ${ - endpointName - ? `# The endpoint name of the flow` - : `# You can set a specific endpoint name in the flow settings` - } - -# You can tweak the flow by adding a tweaks dictionary -# e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}} -TWEAKS = ${tweaksString} - -def run_flow(message: str, - endpoint: str, - output_type: str = "chat", - input_type: str = "chat", - tweaks: Optional[dict] = None, - api_key: Optional[str] = None) -> dict: - """ - Run a flow with a given message and optional tweaks. - - :param message: The message to send to the flow - :param endpoint: The ID or the endpoint name of the flow - :param tweaks: Optional tweaks to customize the flow - :return: The JSON response from the flow - """ - api_url = f"{BASE_API_URL}/api/v1/run/{endpoint}" - - payload = { - ${!activeTweaks ? `"input_value": message,` : ""} - "output_type": output_type, - "input_type": input_type, - } - headers = None - if tweaks: - payload["tweaks"] = tweaks - if api_key: - headers = {"x-api-key": api_key} - response = requests.post(api_url, json=payload, headers=headers) - return response.json() - -def main(): - parser = argparse.ArgumentParser(description="""Run a flow with a given message and optional tweaks.\nRun it like: python .py "your message here" --endpoint "your_endpoint" --tweaks '{"key": "value"}'""", - formatter_class=RawTextHelpFormatter) - parser.add_argument("message", type=str, help="The message to send to the flow") - parser.add_argument("--endpoint", type=str, default=ENDPOINT or FLOW_ID, help="The ID or the endpoint name of the flow") - parser.add_argument("--tweaks", type=str, help="JSON string representing the tweaks to customize the flow", default=json.dumps(TWEAKS)) - parser.add_argument("--api_key", type=str, help="API key for authentication", default=None) - parser.add_argument("--output_type", type=str, default="chat", help="The output type") - parser.add_argument("--input_type", type=str, default="chat", help="The input type") - parser.add_argument("--upload_file", type=str, help="Path to the file to upload", default=None) - parser.add_argument("--components", type=str, help="Components to upload the file to", default=None) - - args = parser.parse_args() - try: - tweaks = json.loads(args.tweaks) - except json.JSONDecodeError: - raise ValueError("Invalid tweaks JSON string") - - if args.upload_file: - if not upload_file: - raise ImportError("Langflow is not installed. Please install it to use the upload_file function.") - elif not args.components: - raise ValueError("You need to provide the components to upload the file to.") - tweaks = upload_file(file_path=args.upload_file, host=BASE_API_URL, flow_id=args.endpoint, components=[args.components], tweaks=tweaks) - - response = run_flow( - message=args.message, - endpoint=args.endpoint, - output_type=args.output_type, - input_type=args.input_type, - tweaks=tweaks, - api_key=args.api_key - ) - - print(json.dumps(response, indent=2)) - -if __name__ == "__main__": - main() -`; -} export function getNewPythonApiCode({ flowId, diff --git a/src/frontend/src/modals/apiModal/utils/get-python-code.tsx b/src/frontend/src/modals/apiModal/utils/get-python-code.tsx deleted file mode 100644 index 5432d693d..000000000 --- a/src/frontend/src/modals/apiModal/utils/get-python-code.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { GetCodeType } from "@/types/tweaks"; - -/** - * Function to get the python code for the API - * @param {string} flow - The current flow - * @param {any[]} tweaksBuildedObject - The tweaks - * @returns {string} - The python code - */ -export default function getPythonCode({ - flowName, - tweaksBuildedObject, - activeTweaks, -}: GetCodeType): string { - let tweaksString = "{}"; - if (tweaksBuildedObject) - tweaksString = JSON.stringify(tweaksBuildedObject, null, 2) - .replace(/true/g, "True") - .replace(/false/g, "False") - .replace(/null|undefined/g, "None"); - - return `from langflow.load import run_flow_from_json -TWEAKS = ${tweaksString} - -result = run_flow_from_json(flow="${flowName}.json", - ${!activeTweaks ? `input_value="message",\n ` : ""}session_id="", # provide a session id if you want to use session state - fallback_to_env_vars=True, # False by default - tweaks=TWEAKS)`; -} diff --git a/src/frontend/src/modals/apiModal/utils/tabs-array.tsx b/src/frontend/src/modals/apiModal/utils/tabs-array.tsx deleted file mode 100644 index b4aa25a08..000000000 --- a/src/frontend/src/modals/apiModal/utils/tabs-array.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { tabsArrayType } from "@/types/components"; - -export function createTabsArray( - codes: { [key: string]: string }, - includeTweaks = false, -) { - const tabs: tabsArrayType[] = []; - if (codes.runCurlCode) { - tabs.push({ - name: "cURL", - mode: "bash", - image: "https://curl.se/logo/curl-symbol-transparent.png", - language: "sh", - code: codes.runCurlCode, - hasTweaks: includeTweaks, - }); - } - if (codes.webhookCurlCode) { - tabs.push({ - name: "Webhook cURL", - mode: "bash", - image: "https://curl.se/logo/curl-symbol-transparent.png", - language: "sh", - code: codes.webhookCurlCode, - }); - } - if (codes.pythonApiCode) { - tabs.push({ - name: "Python API", - mode: "python", - image: - "https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w", - language: "py", - code: codes.pythonApiCode, - hasTweaks: includeTweaks, - }); - } - if (codes.jsApiCode) { - tabs.push({ - name: "JS API", - mode: "javascript", - image: "https://cdn-icons-png.flaticon.com/512/136/136530.png", - language: "js", - code: codes.jsApiCode, - hasTweaks: includeTweaks, - }); - } - if (codes.pythonCode) { - tabs.push({ - name: "Python Code", - mode: "python", - image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png", - language: "py", - code: codes.pythonCode, - hasTweaks: includeTweaks, - }); - } - if (codes.widgetCode) { - tabs.push({ - name: "Chat Widget HTML", - description: - "Insert this code anywhere in your <body> tag. To use with react and other libs, check our documentation.", - mode: "html", - image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png", - language: "html", - code: codes.widgetCode, - }); - } - if (includeTweaks) { - tabs.push({ - name: "Tweaks", - mode: "python", - image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png", - language: "py", - code: codes.tweaksCode, - }); - } - - return tabs; -} diff --git a/src/frontend/src/modals/baseModal/helpers/switch-case-size.ts b/src/frontend/src/modals/baseModal/helpers/switch-case-size.ts index b36622ea8..bd2eb9e6e 100644 --- a/src/frontend/src/modals/baseModal/helpers/switch-case-size.ts +++ b/src/frontend/src/modals/baseModal/helpers/switch-case-size.ts @@ -31,7 +31,7 @@ export const switchCaseModalSize = (size: string) => { height = "h-fit"; break; case "medium-small-tall": - minWidth = "min-w-[50vw]"; + minWidth = "w-[900px] max-w-[98vw]"; height = "h-[70vh]"; break; case "small-h-full": diff --git a/src/frontend/src/modals/editNodeModal/hooks/use-column-defs.ts b/src/frontend/src/modals/editNodeModal/hooks/use-column-defs.ts index 0755a0161..f2920d0bd 100644 --- a/src/frontend/src/modals/editNodeModal/hooks/use-column-defs.ts +++ b/src/frontend/src/modals/editNodeModal/hooks/use-column-defs.ts @@ -26,7 +26,7 @@ const useColumnDefs = ( autoHeight: true, flex: 1, resizable: false, - cellClass: "no-border", + cellClass: "no-border cursor-default", }, { headerName: "Description", @@ -36,7 +36,7 @@ const useColumnDefs = ( autoHeight: true, flex: 2, resizable: false, - cellClass: "no-border", + cellClass: "no-border cursor-default", }, { headerName: "Value", @@ -61,24 +61,26 @@ const useColumnDefs = ( autoHeight: true, flex: 1, resizable: false, - cellClass: "no-border", + cellClass: "no-border cursor-default", }, ]; if (!hideVisibility) { colDefs.push({ - headerName: "Show", + headerName: isTweaks ? "Enable Input" : "Show", field: "advanced", cellRenderer: TableAdvancedToggleCellRender, valueGetter: (params: ValueGetterParams) => { return { nodeId, parameterId: params.data.key, + isTweaks, }; }, editable: false, - maxWidth: 80, + maxWidth: !isTweaks ? 80 : 120, + minWidth: !isTweaks ? 80 : 120, resizable: false, - cellClass: "no-border", + cellClass: "no-border cursor-default", }); } return colDefs; diff --git a/src/frontend/src/stores/flowStore.ts b/src/frontend/src/stores/flowStore.ts index 459026fb4..23234e770 100644 --- a/src/frontend/src/stores/flowStore.ts +++ b/src/frontend/src/stores/flowStore.ts @@ -53,6 +53,7 @@ import useAlertStore from "./alertStore"; import { useDarkStore } from "./darkStore"; import useFlowsManagerStore from "./flowsManagerStore"; import { useGlobalVariablesStore } from "./globalVariablesStore/globalVariables"; +import { useTweaksStore } from "./tweaksStore"; import { useTypesStore } from "./typesStore"; // this is our useStore hook that we can use in our components to get parts of the store and call actions @@ -226,6 +227,9 @@ const useFlowStore = create((set, get) => ({ ) as string[], }); unselectAllNodesEdges(nodes, edges); + if (flow?.id) { + useTweaksStore.getState().initialSetup(nodes, flow?.id); + } set({ nodes, edges: newEdges, diff --git a/src/frontend/src/stores/tweaksStore.ts b/src/frontend/src/stores/tweaksStore.ts index df36f29a6..6ba726c97 100644 --- a/src/frontend/src/stores/tweaksStore.ts +++ b/src/frontend/src/stores/tweaksStore.ts @@ -1,19 +1,13 @@ import { getChangesType } from "@/modals/apiModal/utils/get-changes-types"; import { getNodesWithDefaultValue } from "@/modals/apiModal/utils/get-nodes-with-default-value"; -import { createTabsArray } from "@/modals/apiModal/utils/tabs-array"; -import { AllNodeType, FlowType, NodeDataType } from "@/types/flow"; -import { GetCodesType } from "@/types/tweaks"; -import { customStringify } from "@/utils/reactflowUtils"; +import { AllNodeType, NodeDataType } from "@/types/flow"; +import { getLocalStorage, setLocalStorage } from "@/utils/local-storage-util"; import { create } from "zustand"; import { TweaksStoreType } from "../types/zustand/tweaks"; import useFlowStore from "./flowStore"; export const useTweaksStore = create((set, get) => ({ - activeTweaks: false, tweaks: {}, - setActiveTweaks: (activeTweaks: boolean) => { - set({ activeTweaks }), get().refreshTabs(); - }, nodes: [], setNodes: (change) => { let newChange = typeof change === "function" ? change(get().nodes) : change; @@ -21,7 +15,6 @@ export const useTweaksStore = create((set, get) => ({ set({ nodes: newChange, }); - get().refreshTabs(); get().updateTweaks(); }, setNode: (id, change) => { @@ -44,112 +37,28 @@ export const useTweaksStore = create((set, get) => ({ getNode: (id: string) => { return get().nodes.find((node) => node.id === id); }, - autoLogin: false, - flow: null, - getCodes: {}, - initialSetup: ( - autoLogin: boolean, - flow: FlowType, - getCodes: GetCodesType, - ) => { + currentFlowId: "", + initialSetup: (nodes: AllNodeType[], flowId: string) => { useFlowStore.getState().unselectAll(); set({ - nodes: getNodesWithDefaultValue(flow?.data?.nodes ?? []), - autoLogin, - flow, - getCodes, + currentFlowId: flowId, }); - get().refreshTabs(); - }, - newInitialSetup: (nodes: AllNodeType[]) => { - useFlowStore.getState().unselectAll(); + const tweaks = JSON.parse(getLocalStorage(`lf_tweaks_${flowId}`) || "{}"); set({ - nodes: getNodesWithDefaultValue(nodes), + nodes: getNodesWithDefaultValue(nodes, tweaks), }); get().updateTweaks(); }, - tabs: [], - refreshTabs: () => { - const autoLogin = get().autoLogin; - const flow = get().flow; - const tweak = {}; - const nodes = get().nodes; - const originalNodes = flow?.data?.nodes; - if (!flow) return; - - nodes.forEach((node) => { - const originalNodeTemplate = originalNodes?.find((n) => n.id === node.id) - ?.data?.node?.template; - const nodeTemplate = node.data?.node?.template; - if (originalNodeTemplate && nodeTemplate && node.type === "genericNode") { - const currentTweak = {}; - Object.keys(nodeTemplate).forEach((name) => { - if ( - customStringify(nodeTemplate[name]) !== - customStringify(originalNodeTemplate[name]) || - get().activeTweaks - ) { - currentTweak[name] = getChangesType( - nodeTemplate[name].value, - nodeTemplate[name], - ); - } - }); - tweak[node.id] = currentTweak; - } - }); - const codesObj = {}; - const getCodes = get().getCodes; - - const props = { - flowId: flow?.id, - flowName: flow?.name, - isAuth: autoLogin, - tweaksBuildedObject: tweak, - endpointName: flow?.endpoint_name, - activeTweaks: get().activeTweaks, - }; - - if (getCodes) { - if (getCodes.getCurlRunCode) { - codesObj["runCurlCode"] = getCodes.getCurlRunCode(props); - } - if (getCodes.getCurlWebhookCode && !!flow.webhook) { - codesObj["webhookCurlCode"] = getCodes.getCurlWebhookCode(props); - } - if (getCodes.getJsApiCode) { - codesObj["jsApiCode"] = getCodes.getJsApiCode(props); - } - if (getCodes.getPythonApiCode) { - codesObj["pythonApiCode"] = getCodes.getPythonApiCode(props); - } - if (getCodes.getPythonCode) { - codesObj["pythonCode"] = getCodes.getPythonCode(props); - } - if (getCodes.getWidgetCode) { - codesObj["widgetCode"] = getCodes.getWidgetCode(props); - } - } - - set({ - tabs: createTabsArray(codesObj, nodes.length > 0), - }); - }, updateTweaks: () => { const nodes = get().nodes; - const originalNodes = useFlowStore.getState().nodes; const tweak = {}; + const flowId = get().currentFlowId; nodes.forEach((node) => { - const originalNodeTemplate = originalNodes?.find((n) => n.id === node.id) - ?.data?.node?.template; const nodeTemplate = node.data?.node?.template; - if (originalNodeTemplate && nodeTemplate && node.type === "genericNode") { + if (nodeTemplate && node.type === "genericNode") { const currentTweak = {}; Object.keys(nodeTemplate).forEach((name) => { - if ( - customStringify(nodeTemplate[name]) !== - customStringify(originalNodeTemplate[name]) - ) { + if (!nodeTemplate[name].advanced) { currentTweak[name] = getChangesType( nodeTemplate[name].value, nodeTemplate[name], @@ -161,6 +70,7 @@ export const useTweaksStore = create((set, get) => ({ } } }); + setLocalStorage(`lf_tweaks_${flowId}`, JSON.stringify(tweak)); set({ tweaks: tweak, }); diff --git a/src/frontend/src/style/applies.css b/src/frontend/src/style/applies.css index 2b138ee33..24bd7b2b1 100644 --- a/src/frontend/src/style/applies.css +++ b/src/frontend/src/style/applies.css @@ -1240,7 +1240,7 @@ } .playground-btn-flow-toolbar { - @apply relative inline-flex h-8 w-full items-center justify-center gap-1.5 rounded px-3 py-1.5 text-sm font-semibold transition-all duration-500 ease-in-out; + @apply relative inline-flex h-8 w-full items-center justify-center gap-1.5 rounded px-3 py-1.5 text-sm font-normal transition-all duration-500 ease-in-out; } .input-slider-text { diff --git a/src/frontend/src/types/zustand/tweaks/index.ts b/src/frontend/src/types/zustand/tweaks/index.ts index 78feb71dd..d8189a72a 100644 --- a/src/frontend/src/types/zustand/tweaks/index.ts +++ b/src/frontend/src/types/zustand/tweaks/index.ts @@ -1,11 +1,8 @@ -import { AllNodeType, FlowType, TweaksType } from "@/types/flow"; -import { GetCodesType } from "@/types/tweaks"; -import { tabsArrayType } from "../../components"; +import { AllNodeType } from "@/types/flow"; export type TweaksStoreType = { - activeTweaks: boolean; - setActiveTweaks: (activeTweaks: boolean) => void; nodes: AllNodeType[]; + currentFlowId: string; setNodes: ( update: AllNodeType[] | ((oldState: AllNodeType[]) => AllNodeType[]), skipSave?: boolean, @@ -14,18 +11,8 @@ export type TweaksStoreType = { id: string, update: AllNodeType | ((oldState: AllNodeType) => AllNodeType), ) => void; - getCodes: GetCodesType; getNode: (id: string) => AllNodeType | undefined; - tabs: tabsArrayType[]; - initialSetup: ( - autoLogin: boolean, - flow: FlowType, - getCodes: GetCodesType, - ) => void; - newInitialSetup: (nodes: AllNodeType[]) => void; - refreshTabs: () => void; - autoLogin: boolean; - flow: FlowType | null; + initialSetup: (nodes: AllNodeType[], flowId: string) => void; updateTweaks: () => void; tweaks: { [key: string]: { diff --git a/src/frontend/tests/core/features/tweaksTest.spec.ts b/src/frontend/tests/core/features/tweaksTest.spec.ts index fd26c1013..f628b1d55 100644 --- a/src/frontend/tests/core/features/tweaksTest.spec.ts +++ b/src/frontend/tests/core/features/tweaksTest.spec.ts @@ -27,17 +27,11 @@ test( .first() .click(); - await page.waitForSelector( - '[data-testid="popover-anchor-input-openai_api_base-edit"]', - { - timeout: 1000, - }, - ); + await page.waitForSelector('[data-testid="showstream"]', { + timeout: 1000, + }); - await page - .getByTestId("popover-anchor-input-openai_api_base-edit") - .first() - .fill("teste"); + await page.getByTestId("showstream").first().click(); await page.getByText("Close").last().click(); @@ -50,6 +44,13 @@ test( const newValue = clipboardContent2; expect(oldValue).not.toBe(newValue); expect(clipboardContent2.length).toBeGreaterThan(clipboardContent.length); + await awaitBootstrapTest(page, { skipModal: true }); + await page.getByText("Basic Prompting").first().click(); + await page.getByTestId("publish-button").click(); + await page.getByTestId("api-access-item").click(); + expect( + await page.getByText("Input Schema (1)", { exact: true }).isVisible(), + ); }, ); @@ -131,5 +132,5 @@ test("check if tweaks are updating when someothing on the flow changes", async ( await page.getByText("collection_name_test_123123123!@#$&*(&%$@").isVisible(); await page.getByText("persist_directory_123123123!@#$&*(&%$@").isVisible(); - expect(await page.getByText("Tweaks (2)", { exact: true }).isVisible()); + expect(await page.getByText("Input Schema (2)", { exact: true }).isVisible()); });