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 <gabriel@langflow.org> * 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 <gabriel@langflow.org> * 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 <gabriel@langflow.org>
This commit is contained in:
parent
56fc11a99c
commit
7d67f36000
9 changed files with 95 additions and 20 deletions
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -316,3 +316,7 @@ class FlowDataRequest(BaseModel):
|
|||
nodes: List[dict]
|
||||
edges: List[dict]
|
||||
viewport: Optional[dict] = None
|
||||
|
||||
|
||||
class ConfigResponse(BaseModel):
|
||||
frontend_timeout: int
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<void>(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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
32
src/frontend/src/controllers/API/utils.tsx
Normal file
32
src/frontend/src/controllers/API/utils.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import axios from "axios";
|
||||
import { BASE_URL_API } from "../../constants/constants";
|
||||
|
||||
/**
|
||||
* Fetches the configuration data from the API.
|
||||
* @returns {Promise<any>} 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;
|
||||
}
|
||||
|
|
@ -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!);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue