diff --git a/Makefile b/Makefile index baf4220ff..3a00218fc 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,12 @@ backend: make install_backend poetry run uvicorn langflow.main:app --port 7860 --reload --log-level debug +build_and_run: + echo 'Removing dist folder' + rm -rf dist + make build && poetry run pip install dist/*.tar.gz && poetry run langflow + + build_frontend: cd src/frontend && CI='' npm run build cp -r src/frontend/build src/backend/langflow/frontend diff --git a/poetry.lock b/poetry.lock index 43a305b1a..6f77c6b19 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2955,6 +2955,33 @@ files = [ {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, ] +[[package]] +name = "multiprocess" +version = "0.70.14" +description = "better multiprocessing and multithreading in python" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multiprocess-0.70.14-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:560a27540daef4ce8b24ed3cc2496a3c670df66c96d02461a4da67473685adf3"}, + {file = "multiprocess-0.70.14-pp37-pypy37_pp73-manylinux_2_24_i686.whl", hash = "sha256:bfbbfa36f400b81d1978c940616bc77776424e5e34cb0c94974b178d727cfcd5"}, + {file = "multiprocess-0.70.14-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:89fed99553a04ec4f9067031f83a886d7fdec5952005551a896a4b6a59575bb9"}, + {file = "multiprocess-0.70.14-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:40a5e3685462079e5fdee7c6789e3ef270595e1755199f0d50685e72523e1d2a"}, + {file = "multiprocess-0.70.14-pp38-pypy38_pp73-manylinux_2_24_i686.whl", hash = "sha256:44936b2978d3f2648727b3eaeab6d7fa0bedf072dc5207bf35a96d5ee7c004cf"}, + {file = "multiprocess-0.70.14-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:e628503187b5d494bf29ffc52d3e1e57bb770ce7ce05d67c4bbdb3a0c7d3b05f"}, + {file = "multiprocess-0.70.14-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0d5da0fc84aacb0e4bd69c41b31edbf71b39fe2fb32a54eaedcaea241050855c"}, + {file = "multiprocess-0.70.14-pp39-pypy39_pp73-manylinux_2_24_i686.whl", hash = "sha256:6a7b03a5b98e911a7785b9116805bd782815c5e2bd6c91c6a320f26fd3e7b7ad"}, + {file = "multiprocess-0.70.14-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:cea5bdedd10aace3c660fedeac8b087136b4366d4ee49a30f1ebf7409bce00ae"}, + {file = "multiprocess-0.70.14-py310-none-any.whl", hash = "sha256:7dc1f2f6a1d34894c8a9a013fbc807971e336e7cc3f3ff233e61b9dc679b3b5c"}, + {file = "multiprocess-0.70.14-py37-none-any.whl", hash = "sha256:93a8208ca0926d05cdbb5b9250a604c401bed677579e96c14da3090beb798193"}, + {file = "multiprocess-0.70.14-py38-none-any.whl", hash = "sha256:6725bc79666bbd29a73ca148a0fb5f4ea22eed4a8f22fce58296492a02d18a7b"}, + {file = "multiprocess-0.70.14-py39-none-any.whl", hash = "sha256:63cee628b74a2c0631ef15da5534c8aedbc10c38910b9c8b18dcd327528d1ec7"}, + {file = "multiprocess-0.70.14.tar.gz", hash = "sha256:3eddafc12f2260d27ae03fe6069b12570ab4764ab59a75e81624fac453fbf46a"}, +] + +[package.dependencies] +dill = ">=0.3.6" + [[package]] name = "mypy" version = "1.3.0" @@ -6296,4 +6323,4 @@ deploy = ["langchain-serve"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "94e4a4ca5ec150ef6f673d4a3ab4964a876ce795ae398b78c76719695c63758e" +content-hash = "3cefd738bb8466d22b1403d9b40c92420ec7f248705a2604b21d96e58d5db990" diff --git a/pyproject.toml b/pyproject.toml index 436c0684c..522941f31 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,7 @@ ctransformers = "^0.2.2" cohere = "^4.6.0" faiss-cpu = "^1.7.4" anthropic = "^0.2.9" +multiprocess = "^0.70.14" [tool.poetry.group.dev.dependencies] diff --git a/src/backend/langflow/__main__.py b/src/backend/langflow/__main__.py index af57ed381..4a00802c9 100644 --- a/src/backend/langflow/__main__.py +++ b/src/backend/langflow/__main__.py @@ -1,20 +1,28 @@ -import multiprocessing +import sys +import time +import httpx +from multiprocess import Process, cpu_count # type: ignore import platform from pathlib import Path +from rich.panel import Panel +from rich import box +from rich import print as rprint import typer from fastapi.staticfiles import StaticFiles from langflow.main import create_app from langflow.settings import settings -from langflow.utils.logger import configure +from langflow.utils.logger import configure, logger +import webbrowser + app = typer.Typer() def get_number_of_workers(workers=None): if workers == -1: - workers = (multiprocessing.cpu_count() * 2) + 1 + workers = (cpu_count() * 2) + 1 return workers @@ -77,10 +85,17 @@ def serve( timeout: int = typer.Option(60, help="Worker timeout in seconds."), port: int = typer.Option(7860, help="Port to listen on."), config: str = typer.Option("config.yaml", help="Path to the configuration file."), - log_level: str = typer.Option("info", help="Logging level."), + log_level: str = typer.Option("critical", help="Logging level."), log_file: Path = typer.Option("logs/langflow.log", help="Path to the log file."), jcloud: bool = typer.Option(False, help="Deploy on Jina AI Cloud"), dev: bool = typer.Option(False, help="Run in development mode (may contain bugs)"), + path: str = typer.Option( + None, + help="Path to the frontend directory containing build files. This is for development purposes only.", + ), + open_browser: bool = typer.Option( + True, help="Open the browser after starting the server." + ), ): """ Run the Langflow server. @@ -93,8 +108,11 @@ def serve( update_settings(config, dev=dev) app = create_app() # get the directory of the current file - path = Path(__file__).parent - static_files_dir = path / "frontend" + if not path: + frontend_path = Path(__file__).parent + static_files_dir = frontend_path / "frontend" + else: + static_files_dir = Path(path) app.mount( "/", StaticFiles(directory=static_files_dir, html=True), @@ -107,17 +125,74 @@ def serve( "timeout": timeout, } - if platform.system() in ["Darwin", "Windows"]: - # Run using uvicorn on MacOS and Windows - # Windows doesn't support gunicorn - # MacOS requires an env variable to be set to use gunicorn - import uvicorn + webapp_process = Process( + target=run_langflow, args=(host, port, log_level, options, app) + ) + webapp_process.start() + status_code = 0 + while status_code != 200: + try: + status_code = httpx.get(f"http://{host}:{port}").status_code + except Exception: + time.sleep(1) - uvicorn.run(app, host=host, port=port, log_level=log_level) - else: - from langflow.server import LangflowApplication + print_banner(host, port) + if open_browser: + webbrowser.open(f"http://{host}:{port}") - LangflowApplication(app, options).run() + +def print_banner(host, port): + # console = Console() + + word = "LangFlow" + colors = ["#690080", "#660099", "#4d00b3", "#3300cc", "#1a00e6", "#0000ff"] + + styled_word = "" + + for i, char in enumerate(word): + color = colors[i % len(colors)] + styled_word += f"[{color}]{char}[/]" + + # Title with emojis and gradient text + title = ( + f"[bold]Welcome to :chains: {styled_word} [/bold]\n\n" + f"Access [link=http://{host}:{port}]http://{host}:{port}[/link]" + ) + info_text = ( + "Collaborate, and contribute at our " + "[bold][link=https://github.com/logspace-ai/langflow]GitHub Repo[/link][/bold] :rocket:" + ) + + # Create a panel with the title and the info text, and a border around it + panel = Panel( + f"{title}\n{info_text}", box=box.ROUNDED, border_style="blue", expand=False + ) + + # Print the banner with a separator line before and after + rprint(panel) + + +def run_langflow(host, port, log_level, options, app): + """ + Run Langflow server on localhost + """ + try: + if platform.system() in ["Darwin", "Windows"]: + # Run using uvicorn on MacOS and Windows + # Windows doesn't support gunicorn + # MacOS requires an env variable to be set to use gunicorn + import uvicorn + + uvicorn.run(app, host=host, port=port, log_level=log_level) + else: + from langflow.server import LangflowApplication + + LangflowApplication(app, options).run() + except KeyboardInterrupt: + pass + except Exception as e: + logger.error(e) + sys.exit(1) def main(): diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 9e84a6430..f7edaf899 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -49,7 +49,7 @@ export default function App() { // Initialize state variable for the version const [version, setVersion] = useState(""); useEffect(() => { - fetch("/version") + fetch("api/v1/version") .then((res) => res.json()) .then((data) => { setVersion(data.version); diff --git a/src/frontend/src/contexts/tabsContext.tsx b/src/frontend/src/contexts/tabsContext.tsx index 7950d8e6a..1907a5590 100644 --- a/src/frontend/src/contexts/tabsContext.tsx +++ b/src/frontend/src/contexts/tabsContext.tsx @@ -89,42 +89,47 @@ export function TabsProvider({ children }: { children: ReactNode }) { let cookie = window.localStorage.getItem("tabsData"); if (cookie && Object.keys(templates).length > 0) { let cookieObject: LangFlowState = JSON.parse(cookie); - cookieObject.flows.forEach((flow) => { - if (!flow.data) { - return; - } - flow.data.edges.forEach((edge) => { - edge.className = ""; - edge.style = { stroke: "#555555" }; - }); - flow.data.nodes.forEach((node) => { - const template = templates[node.data.type]; - if (!template) { - setErrorData({ title: `Unknown node type: ${node.data.type}` }); + try { + cookieObject.flows.forEach((flow) => { + if (!flow.data) { return; } - if (Object.keys(template["template"]).length > 0) { - node.data.node.base_classes = template["base_classes"]; - flow.data.edges.forEach((edge) => { - if (edge.source === node.id) { - edge.sourceHandle = edge.sourceHandle - .split("|") - .slice(0, 2) - .concat(template["base_classes"]) - .join("|"); - } - }); - node.data.node.description = template["description"]; - node.data.node.template = updateTemplate( - template["template"] as unknown as APITemplateType, - node.data.node.template as APITemplateType - ); - } + flow.data.edges.forEach((edge) => { + edge.className = ""; + edge.style = { stroke: "#555555" }; + }); + + flow.data.nodes.forEach((node) => { + const template = templates[node.data.type]; + if (!template) { + setErrorData({ title: `Unknown node type: ${node.data.type}` }); + return; + } + if (Object.keys(template["template"]).length > 0) { + node.data.node.base_classes = template["base_classes"]; + flow.data.edges.forEach((edge) => { + if (edge.source === node.id) { + edge.sourceHandle = edge.sourceHandle + .split("|") + .slice(0, 2) + .concat(template["base_classes"]) + .join("|"); + } + }); + node.data.node.description = template["description"]; + node.data.node.template = updateTemplate( + template["template"] as unknown as APITemplateType, + node.data.node.template as APITemplateType + ); + } + }); }); - }); - setTabIndex(cookieObject.tabIndex); - setFlows(cookieObject.flows); - setId(cookieObject.id); + setTabIndex(cookieObject.tabIndex); + setFlows(cookieObject.flows); + setId(cookieObject.id); + } catch (e) { + console.log(e); + } } }, [templates]); diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index 0cffd04bf..ffa9eef72 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -14,13 +14,13 @@ export async function sendAll(data: sendAllProps) { export async function checkCode( code: string ): Promise> { - return await axios.post("api/v1/validate/code", { code }); + return await axios.post("/validate/code", { code }); } export async function checkPrompt( template: string ): Promise> { - return await axios.post("api/v1/validate/prompt", { template }); + return await axios.post("/validate/prompt", { template }); } export async function getExamples(): Promise { @@ -37,7 +37,5 @@ export async function getExamples(): Promise { return contentResponse.data; }); - const contents = await Promise.all(contentsPromises); - - return contents; + return await Promise.all(contentsPromises); }