From 7d67f360003371d797c4fb3adfeb5a5f0228df07 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira <62335616+lucaseduoli@users.noreply.github.com> Date: Tue, 28 May 2024 17:18:36 +0200 Subject: [PATCH] Fix Building timeout and macbook shortcuts (#1964) * Fixed shortcuts not working on mac * fixed top level vertices and added timeout * revert flowStore changes * chore: Update worker timeout setting * feat: Add endpoint to retrieve config settings The commit adds a new endpoint `/config` to retrieve the configuration settings. This endpoint returns the `ConfigResponse` model, which includes the `timeout` value. The implementation handles any exceptions and logs them appropriately. * feat: Add fetchConfig function to retrieve configuration settings This commit adds a new function fetchConfig to the API utils module. The function makes an HTTP GET request to the /config endpoint and returns the configuration data. Any errors that occur during the request are logged and rethrown. This function will be used to initialize the application with the fetched configuration. Co-authored-by: Gabriel Luiz Freitas Almeida * revert changes * feat: Add setupAxiosDefaults function to initialize Axios configurations This commit adds a new function setupAxiosDefaults to the API utils module. The function fetches the configuration data using the fetchConfig function and sets up default configurations for Axios. It sets the base URL and timeout for Axios requests based on the fetched configuration. This function will be used to initialize Axios with the correct configurations. Co-authored-by: Gabriel Luiz Freitas Almeida * fix(langflow): rename 'timeout' setting to 'worker_timeout' for clarity and consistency feat(langflow): add 'frontend_timeout' setting to control frontend API call timeout chore(langflow): reorganize imports and constants in settings module for better readability * Set frontend_timeout to 0 --------- Co-authored-by: ogabrielluiz --- src/backend/base/langflow/__main__.py | 6 +++- src/backend/base/langflow/api/v1/endpoints.py | 19 +++++++++-- src/backend/base/langflow/api/v1/schemas.py | 4 +++ .../base/langflow/services/settings/base.py | 4 +++ .../langflow/services/settings/service.py | 4 +++ src/frontend/src/App.tsx | 10 ++++-- src/frontend/src/controllers/API/api.tsx | 10 +++--- src/frontend/src/controllers/API/utils.tsx | 32 +++++++++++++++++++ .../components/nodeToolbarComponent/index.tsx | 26 +++++++++------ 9 files changed, 95 insertions(+), 20 deletions(-) create mode 100644 src/frontend/src/controllers/API/utils.tsx diff --git a/src/backend/base/langflow/__main__.py b/src/backend/base/langflow/__main__.py index 4d59b3149..4162629dd 100644 --- a/src/backend/base/langflow/__main__.py +++ b/src/backend/base/langflow/__main__.py @@ -77,7 +77,7 @@ def set_var_for_macos_issue(): def run( host: str = typer.Option("127.0.0.1", help="Host to bind the server to.", envvar="LANGFLOW_HOST"), workers: int = typer.Option(1, help="Number of worker processes.", envvar="LANGFLOW_WORKERS"), - timeout: int = typer.Option(300, help="Worker timeout in seconds."), + timeout: int = typer.Option(300, help="Worker timeout in seconds.", envvar="LANGFLOW_WORKER_TIMEOUT"), port: int = typer.Option(7860, help="Port to listen on.", envvar="LANGFLOW_PORT"), components_path: Optional[Path] = typer.Option( Path(__file__).parent / "components", @@ -145,6 +145,10 @@ def run( if is_port_in_use(port, host): port = get_free_port(port) + settings_service = get_settings_service() + + settings_service.set("worker_timeout", timeout) + options = { "bind": f"{host}:{port}", "workers": get_number_of_workers(workers), diff --git a/src/backend/base/langflow/api/v1/endpoints.py b/src/backend/base/langflow/api/v1/endpoints.py index b47774c8b..006099b15 100644 --- a/src/backend/base/langflow/api/v1/endpoints.py +++ b/src/backend/base/langflow/api/v1/endpoints.py @@ -1,5 +1,5 @@ from http import HTTPStatus -from typing import Annotated, List, Optional, Union +from typing import TYPE_CHECKING, Annotated, List, Optional, Union from uuid import UUID import sqlalchemy as sa @@ -9,6 +9,7 @@ from sqlmodel import Session, select from langflow.api.utils import update_frontend_node_with_template_values from langflow.api.v1.schemas import ( + ConfigResponse, CustomComponentRequest, InputValueRequest, ProcessResponse, @@ -31,7 +32,9 @@ from langflow.services.deps import get_session, get_session_service, get_setting from langflow.services.session.service import SessionService from langflow.services.task.service import TaskService -# build router +if TYPE_CHECKING: + from langflow.services.settings.manager import SettingsService + router = APIRouter(tags=["Base"]) @@ -440,3 +443,15 @@ async def custom_component_update( except Exception as exc: logger.exception(exc) raise HTTPException(status_code=400, detail=str(exc)) from exc + + +@router.get("/config", response_model=ConfigResponse) +def get_config(): + try: + from langflow.services.deps import get_settings_service + + settings_service: "SettingsService" = get_settings_service() + return settings_service.settings.model_dump() + except Exception as exc: + logger.exception(exc) + raise HTTPException(status_code=500, detail=str(exc)) from exc diff --git a/src/backend/base/langflow/api/v1/schemas.py b/src/backend/base/langflow/api/v1/schemas.py index 2b9eff312..419c3d1f9 100644 --- a/src/backend/base/langflow/api/v1/schemas.py +++ b/src/backend/base/langflow/api/v1/schemas.py @@ -316,3 +316,7 @@ class FlowDataRequest(BaseModel): nodes: List[dict] edges: List[dict] viewport: Optional[dict] = None + + +class ConfigResponse(BaseModel): + frontend_timeout: int diff --git a/src/backend/base/langflow/services/settings/base.py b/src/backend/base/langflow/services/settings/base.py index 05a368c9b..f62ccacd4 100644 --- a/src/backend/base/langflow/services/settings/base.py +++ b/src/backend/base/langflow/services/settings/base.py @@ -104,6 +104,10 @@ class Settings(BaseSettings): """Whether to store environment variables as Global Variables in the database.""" variables_to_get_from_environment: list[str] = VARIABLES_TO_GET_FROM_ENVIRONMENT """List of environment variables to get from the environment and store in the database.""" + worker_timeout: int = 300 + """Timeout for the API calls in seconds.""" + frontend_timeout: int = 0 + """Timeout for the frontend API calls in seconds.""" @field_validator("config_dir", mode="before") def set_langflow_dir(cls, value): diff --git a/src/backend/base/langflow/services/settings/service.py b/src/backend/base/langflow/services/settings/service.py index 0d9d63bc4..f7ef2980d 100644 --- a/src/backend/base/langflow/services/settings/service.py +++ b/src/backend/base/langflow/services/settings/service.py @@ -42,3 +42,7 @@ class SettingsService(Service): CONFIG_DIR=settings.config_dir, ) return cls(settings, auth_settings) + + def set(self, key, value): + setattr(self.settings, key, value) + return self.settings diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 922ec9994..510500e2b 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -1,3 +1,4 @@ +import axios from "axios"; import { useContext, useEffect, useState } from "react"; import { ErrorBoundary } from "react-error-boundary"; import { useNavigate } from "react-router-dom"; @@ -15,6 +16,7 @@ import { } from "./constants/constants"; import { AuthContext } from "./contexts/authContext"; import { autoLogin, getGlobalVariables, getHealth } from "./controllers/API"; +import { setupAxiosDefaults } from "./controllers/API/utils"; import useTrackLastVisitedPath from "./hooks/use-track-last-visited-path"; import Router from "./routes"; import useAlertStore from "./stores/alertStore"; @@ -28,10 +30,10 @@ export default function App() { useTrackLastVisitedPath(); const removeFromTempNotificationList = useAlertStore( - (state) => state.removeFromTempNotificationList, + (state) => state.removeFromTempNotificationList ); const tempNotificationList = useAlertStore( - (state) => state.tempNotificationList, + (state) => state.tempNotificationList ); const [fetchError, setFetchError] = useState(false); const isLoading = useFlowsManagerStore((state) => state.isLoading); @@ -49,7 +51,7 @@ export default function App() { const refreshVersion = useDarkStore((state) => state.refreshVersion); const refreshStars = useDarkStore((state) => state.refreshStars); const setGlobalVariables = useGlobalVariablesStore( - (state) => state.setGlobalVariables, + (state) => state.setGlobalVariables ); const checkHasStore = useStoreStore((state) => state.checkHasStore); const navigate = useNavigate(); @@ -114,9 +116,11 @@ export default function App() { return new Promise(async (resolve, reject) => { if (isAuthenticated) { try { + await setupAxiosDefaults(); await getFoldersApi(); await getTypes(); await refreshFlows(); + console.log(axios.defaults); const res = await getGlobalVariables(); setGlobalVariables(res); checkHasStore(); diff --git a/src/frontend/src/controllers/API/api.tsx b/src/frontend/src/controllers/API/api.tsx index 363576ae0..d630c474f 100644 --- a/src/frontend/src/controllers/API/api.tsx +++ b/src/frontend/src/controllers/API/api.tsx @@ -48,7 +48,7 @@ function ApiInterceptor() { } await clearBuildVerticesState(error); return Promise.reject(error); - }, + } ); const isAuthorizedURL = (url) => { @@ -65,10 +65,10 @@ function ApiInterceptor() { const parsedURL = new URL(url); const isDomainAllowed = authorizedDomains.some( - (domain) => parsedURL.origin === new URL(domain).origin, + (domain) => parsedURL.origin === new URL(domain).origin ); const isEndpointAllowed = authorizedEndpoints.some((endpoint) => - parsedURL.pathname.includes(endpoint), + parsedURL.pathname.includes(endpoint) ); return isDomainAllowed || isEndpointAllowed; @@ -112,7 +112,7 @@ function ApiInterceptor() { }, (error) => { return Promise.reject(error); - }, + } ); return () => { @@ -144,7 +144,7 @@ function ApiInterceptor() { if (error?.config?.headers) { delete error.config.headers["Authorization"]; error.config.headers["Authorization"] = `Bearer ${cookies.get( - "access_token_lf", + "access_token_lf" )}`; const response = await axios.request(error.config); return response; diff --git a/src/frontend/src/controllers/API/utils.tsx b/src/frontend/src/controllers/API/utils.tsx new file mode 100644 index 000000000..3991caa2e --- /dev/null +++ b/src/frontend/src/controllers/API/utils.tsx @@ -0,0 +1,32 @@ +import axios from "axios"; +import { BASE_URL_API } from "../../constants/constants"; + +/** + * Fetches the configuration data from the API. + * @returns {Promise} A promise that resolves to the configuration data. + * @throws {Error} If there was an error fetching the configuration data. + */ +export async function fetchConfig() { + try { + const response = await axios.get(`${BASE_URL_API}config`); + return response.data; + } catch (error) { + console.error("Failed to fetch configuration:", error); + throw error; + } +} + +/** + * Sets up default configurations for Axios. + * Fetches the timeout configuration and sets it as the default timeout for Axios requests. + */ +export async function setupAxiosDefaults() { + const config = await fetchConfig(); + // Create Axios instance with the fetched timeout configuration + + const timeoutInMilliseconds = config.frontend_timeout + ? config.frontend_timeout * 1000 + : 30000; + axios.defaults.baseURL = ""; + axios.defaults.timeout = timeoutInMilliseconds; +} diff --git a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx index 1203aab65..cbf941fbb 100644 --- a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx @@ -271,7 +271,7 @@ export default function NodeToolbarComponent({ selected && (hasApiKey || hasStore) && (event.ctrlKey || event.metaKey) && - event.key === "u" + event.key.toUpperCase() === "U" ) { event.preventDefault(); handleSelectChange("update"); @@ -280,7 +280,7 @@ export default function NodeToolbarComponent({ selected && isGroup && (event.ctrlKey || event.metaKey) && - event.key === "g" + event.key.toUpperCase() === "G" ) { event.preventDefault(); handleSelectChange("ungroup"); @@ -290,7 +290,7 @@ export default function NodeToolbarComponent({ (hasApiKey || hasStore) && (event.ctrlKey || event.metaKey) && event.shiftKey && - event.key === "S" + event.key.toUpperCase() === "S" ) { event.preventDefault(); setShowconfirmShare((state) => !state); @@ -300,7 +300,7 @@ export default function NodeToolbarComponent({ selected && (event.ctrlKey || event.metaKey) && event.shiftKey && - event.key === "Q" + event.key.toUpperCase() === "Q" ) { event.preventDefault(); if (isMinimal) { @@ -317,7 +317,7 @@ export default function NodeToolbarComponent({ selected && (event.ctrlKey || event.metaKey) && event.shiftKey && - event.key === "U" + event.key.toUpperCase() === "U" ) { event.preventDefault(); if (hasCode) return setOpenModal((state) => !state); @@ -327,12 +327,16 @@ export default function NodeToolbarComponent({ selected && (event.ctrlKey || event.metaKey) && event.shiftKey && - event.key === "A" + event.key.toUpperCase() === "A" ) { event.preventDefault(); setShowModalAdvanced((state) => !state); } - if (selected && (event.ctrlKey || event.metaKey) && event.key === "s") { + if ( + selected && + (event.ctrlKey || event.metaKey) && + event.key.toUpperCase() === "S" + ) { if (isSaved) { event.preventDefault(); return setShowOverrideModal((state) => !state); @@ -347,7 +351,7 @@ export default function NodeToolbarComponent({ selected && (event.ctrlKey || event.metaKey) && event.shiftKey && - event.key === "D" + event.key.toUpperCase() === "D" ) { event.preventDefault(); if (data.node?.documentation) { @@ -357,7 +361,11 @@ export default function NodeToolbarComponent({ title: `${data.id} docs is not available at the moment.`, }); } - if (selected && (event.ctrlKey || event.metaKey) && event.key === "j") { + if ( + selected && + (event.ctrlKey || event.metaKey) && + event.key.toUpperCase() === "J" + ) { event.preventDefault(); downloadNode(flowComponent!); }