refactor: streamline UI server startup and port availability checks
This commit is contained in:
parent
a68401ee70
commit
c518f149f2
4 changed files with 89 additions and 35 deletions
|
|
@ -1 +1 @@
|
||||||
from .ui import start_ui, ui
|
from .ui import start_ui
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
@ -7,7 +8,7 @@ import webbrowser
|
||||||
import zipfile
|
import zipfile
|
||||||
import requests
|
import requests
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable, Optional, Tuple
|
from typing import Callable, Optional, Tuple, List
|
||||||
import tempfile
|
import tempfile
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
|
@ -17,6 +18,40 @@ from cognee.version import get_cognee_version
|
||||||
logger = get_logger()
|
logger = get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
def _is_port_available(port: int) -> bool:
|
||||||
|
"""
|
||||||
|
Check if a port is available on localhost.
|
||||||
|
Returns True if the port is available, False otherwise.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||||
|
sock.settimeout(1) # 1 second timeout
|
||||||
|
result = sock.connect_ex(("localhost", port))
|
||||||
|
return result != 0 # Port is available if connection fails
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _check_required_ports(ports_to_check: List[Tuple[int, str]]) -> Tuple[bool, List[str]]:
|
||||||
|
"""
|
||||||
|
Check if all required ports are available on localhost.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ports_to_check: List of (port, service_name) tuples
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (all_available: bool, unavailable_services: List[str])
|
||||||
|
"""
|
||||||
|
unavailable = []
|
||||||
|
|
||||||
|
for port, service_name in ports_to_check:
|
||||||
|
if not _is_port_available(port):
|
||||||
|
unavailable.append(f"{service_name} (port {port})")
|
||||||
|
logger.error(f"Port {port} is already in use for {service_name}")
|
||||||
|
|
||||||
|
return len(unavailable) == 0, unavailable
|
||||||
|
|
||||||
|
|
||||||
def normalize_version_for_comparison(version: str) -> str:
|
def normalize_version_for_comparison(version: str) -> str:
|
||||||
"""
|
"""
|
||||||
Normalize version string for comparison.
|
Normalize version string for comparison.
|
||||||
|
|
@ -327,14 +362,13 @@ def prompt_user_for_download() -> bool:
|
||||||
|
|
||||||
def start_ui(
|
def start_ui(
|
||||||
pid_callback: Callable[[int], None],
|
pid_callback: Callable[[int], None],
|
||||||
host: str = "localhost",
|
|
||||||
port: int = 3000,
|
port: int = 3000,
|
||||||
open_browser: bool = True,
|
open_browser: bool = True,
|
||||||
auto_download: bool = False,
|
auto_download: bool = False,
|
||||||
start_backend: bool = False,
|
start_backend: bool = False,
|
||||||
backend_host: str = "localhost",
|
|
||||||
backend_port: int = 8000,
|
backend_port: int = 8000,
|
||||||
start_mcp: bool = False,
|
start_mcp: bool = False,
|
||||||
|
mcp_port: int = 8001,
|
||||||
) -> Optional[subprocess.Popen]:
|
) -> Optional[subprocess.Popen]:
|
||||||
"""
|
"""
|
||||||
Start the cognee frontend UI server, optionally with the backend API server and MCP server.
|
Start the cognee frontend UI server, optionally with the backend API server and MCP server.
|
||||||
|
|
@ -350,14 +384,13 @@ def start_ui(
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
pid_callback: Callback to notify with PID of each spawned process
|
pid_callback: Callback to notify with PID of each spawned process
|
||||||
host: Host to bind the frontend server to (default: localhost)
|
|
||||||
port: Port to run the frontend server on (default: 3000)
|
port: Port to run the frontend server on (default: 3000)
|
||||||
open_browser: Whether to open the browser automatically (default: True)
|
open_browser: Whether to open the browser automatically (default: True)
|
||||||
auto_download: If True, download frontend without prompting (default: False)
|
auto_download: If True, download frontend without prompting (default: False)
|
||||||
start_backend: If True, also start the cognee API backend server (default: False)
|
start_backend: If True, also start the cognee API backend server (default: False)
|
||||||
backend_host: Host to bind the backend server to (default: localhost)
|
|
||||||
backend_port: Port to run the backend server on (default: 8000)
|
backend_port: Port to run the backend server on (default: 8000)
|
||||||
start_mcp: If True, also start the cognee MCP server on port 8001 (default: False)
|
start_mcp: If True, also start the cognee MCP server (default: False)
|
||||||
|
mcp_port: Port to run the MCP server on (default: 8001)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
subprocess.Popen object representing the running frontend server, or None if failed
|
subprocess.Popen object representing the running frontend server, or None if failed
|
||||||
|
|
@ -366,22 +399,42 @@ def start_ui(
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
>>> import cognee
|
>>> import cognee
|
||||||
|
>>> def dummy_callback(pid): pass
|
||||||
>>> # Start just the frontend
|
>>> # Start just the frontend
|
||||||
>>> server = cognee.start_ui()
|
>>> server = cognee.start_ui(dummy_callback)
|
||||||
>>>
|
>>>
|
||||||
>>> # Start both frontend and backend
|
>>> # Start both frontend and backend
|
||||||
>>> server = cognee.start_ui(start_backend=True)
|
>>> server = cognee.start_ui(dummy_callback, start_backend=True)
|
||||||
>>> # UI will be available at http://localhost:3000
|
>>> # UI will be available at http://localhost:3000
|
||||||
>>> # API will be available at http://localhost:8000
|
>>> # API will be available at http://localhost:8000
|
||||||
>>>
|
>>>
|
||||||
>>> # Start frontend with MCP server
|
>>> # Start frontend with MCP server
|
||||||
>>> server = cognee.start_ui(start_mcp=True)
|
>>> server = cognee.start_ui(dummy_callback, start_mcp=True)
|
||||||
>>> # UI will be available at http://localhost:3000
|
>>> # UI will be available at http://localhost:3000
|
||||||
>>> # MCP server will be available at http://127.0.0.1:8001/sse
|
>>> # MCP server will be available at http://127.0.0.1:8001/sse
|
||||||
>>> # To stop all servers later:
|
>>> # To stop all servers later:
|
||||||
>>> server.terminate()
|
>>> server.terminate()
|
||||||
"""
|
"""
|
||||||
logger.info("Starting cognee UI...")
|
logger.info("Starting cognee UI...")
|
||||||
|
|
||||||
|
ports_to_check = [(port, "Frontend UI")]
|
||||||
|
|
||||||
|
if start_backend:
|
||||||
|
ports_to_check.append((backend_port, "Backend API"))
|
||||||
|
|
||||||
|
if start_mcp:
|
||||||
|
ports_to_check.append((mcp_port, "MCP Server"))
|
||||||
|
|
||||||
|
logger.info("Checking port availability...")
|
||||||
|
all_ports_available, unavailable_services = _check_required_ports(ports_to_check)
|
||||||
|
|
||||||
|
if not all_ports_available:
|
||||||
|
error_msg = f"Cannot start cognee UI: The following services have ports already in use: {', '.join(unavailable_services)}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
logger.error("Please stop the conflicting services or change the port configuration.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
logger.info("✓ All required ports are available")
|
||||||
backend_process = None
|
backend_process = None
|
||||||
|
|
||||||
if start_mcp:
|
if start_mcp:
|
||||||
|
|
@ -394,7 +447,7 @@ def start_ui(
|
||||||
"docker",
|
"docker",
|
||||||
"run",
|
"run",
|
||||||
"-p",
|
"-p",
|
||||||
"8001:8000",
|
f"{mcp_port}:8000",
|
||||||
"--rm",
|
"--rm",
|
||||||
"--env-file",
|
"--env-file",
|
||||||
env_file,
|
env_file,
|
||||||
|
|
@ -405,7 +458,7 @@ def start_ui(
|
||||||
preexec_fn=os.setsid if hasattr(os, "setsid") else None,
|
preexec_fn=os.setsid if hasattr(os, "setsid") else None,
|
||||||
)
|
)
|
||||||
pid_callback(mcp_process.pid)
|
pid_callback(mcp_process.pid)
|
||||||
logger.info("✓ Cognee MCP server starting on http://127.0.0.1:8001/sse")
|
logger.info(f"✓ Cognee MCP server starting on http://127.0.0.1:{mcp_port}/sse")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to start MCP server with Docker: {str(e)}")
|
logger.error(f"Failed to start MCP server with Docker: {str(e)}")
|
||||||
# Start backend server if requested
|
# Start backend server if requested
|
||||||
|
|
@ -421,7 +474,7 @@ def start_ui(
|
||||||
"uvicorn",
|
"uvicorn",
|
||||||
"cognee.api.client:app",
|
"cognee.api.client:app",
|
||||||
"--host",
|
"--host",
|
||||||
backend_host,
|
"localhost",
|
||||||
"--port",
|
"--port",
|
||||||
str(backend_port),
|
str(backend_port),
|
||||||
],
|
],
|
||||||
|
|
@ -440,7 +493,7 @@ def start_ui(
|
||||||
logger.error("Backend server failed to start - process exited early")
|
logger.error("Backend server failed to start - process exited early")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
logger.info(f"✓ Backend API started at http://{backend_host}:{backend_port}")
|
logger.info(f"✓ Backend API started at http://localhost:{backend_port}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to start backend server: {str(e)}")
|
logger.error(f"Failed to start backend server: {str(e)}")
|
||||||
|
|
@ -485,11 +538,11 @@ def start_ui(
|
||||||
|
|
||||||
# Prepare environment variables
|
# Prepare environment variables
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env["HOST"] = host
|
env["HOST"] = "localhost"
|
||||||
env["PORT"] = str(port)
|
env["PORT"] = str(port)
|
||||||
|
|
||||||
# Start the development server
|
# Start the development server
|
||||||
logger.info(f"Starting frontend server at http://{host}:{port}")
|
logger.info(f"Starting frontend server at http://localhost:{port}")
|
||||||
logger.info("This may take a moment to compile and start...")
|
logger.info("This may take a moment to compile and start...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -523,7 +576,7 @@ def start_ui(
|
||||||
def open_browser_delayed():
|
def open_browser_delayed():
|
||||||
time.sleep(5) # Give Next.js time to fully start
|
time.sleep(5) # Give Next.js time to fully start
|
||||||
try:
|
try:
|
||||||
webbrowser.open(f"http://{host}:{port}") # TODO: use dashboard url?
|
webbrowser.open(f"http://localhost:{port}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Could not open browser automatically: {e}")
|
logger.warning(f"Could not open browser automatically: {e}")
|
||||||
|
|
||||||
|
|
@ -531,7 +584,7 @@ def start_ui(
|
||||||
browser_thread.start()
|
browser_thread.start()
|
||||||
|
|
||||||
logger.info("✓ Cognee UI is starting up...")
|
logger.info("✓ Cognee UI is starting up...")
|
||||||
logger.info(f"✓ Open your browser to: http://{host}:{port}")
|
logger.info(f"✓ Open your browser to: http://localhost:{port}")
|
||||||
logger.info("✓ The UI will be available once Next.js finishes compiling")
|
logger.info("✓ The UI will be available once Next.js finishes compiling")
|
||||||
|
|
||||||
return process
|
return process
|
||||||
|
|
@ -551,12 +604,3 @@ def start_ui(
|
||||||
except (OSError, ProcessLookupError):
|
except (OSError, ProcessLookupError):
|
||||||
pass
|
pass
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
# Convenience function similar to DuckDB's approach
|
|
||||||
def ui() -> Optional[subprocess.Popen]:
|
|
||||||
"""
|
|
||||||
Convenient alias for start_ui() with default parameters.
|
|
||||||
Similar to how DuckDB provides simple ui() function.
|
|
||||||
"""
|
|
||||||
return start_ui()
|
|
||||||
|
|
|
||||||
|
|
@ -204,20 +204,27 @@ def main() -> int:
|
||||||
nonlocal spawned_pids
|
nonlocal spawned_pids
|
||||||
spawned_pids.append(pid)
|
spawned_pids.append(pid)
|
||||||
|
|
||||||
|
frontend_port = 3000
|
||||||
|
start_backend, backend_port = True, 8000
|
||||||
|
start_mcp, mcp_port = True, 8001
|
||||||
server_process = start_ui(
|
server_process = start_ui(
|
||||||
host="localhost",
|
|
||||||
port=3000,
|
|
||||||
open_browser=True,
|
|
||||||
start_backend=True,
|
|
||||||
start_mcp=True,
|
|
||||||
auto_download=True,
|
|
||||||
pid_callback=pid_callback,
|
pid_callback=pid_callback,
|
||||||
|
port=frontend_port,
|
||||||
|
open_browser=True,
|
||||||
|
auto_download=True,
|
||||||
|
start_backend=start_backend,
|
||||||
|
backend_port=backend_port,
|
||||||
|
start_mcp=start_mcp,
|
||||||
|
mcp_port=mcp_port,
|
||||||
)
|
)
|
||||||
|
|
||||||
if server_process:
|
if server_process:
|
||||||
fmt.success("UI server started successfully!")
|
fmt.success("UI server started successfully!")
|
||||||
fmt.echo("The interface is available at: http://localhost:3000")
|
fmt.echo("The interface is available at: http://localhost:3000")
|
||||||
fmt.echo("The API backend is available at: http://localhost:8000")
|
if start_backend:
|
||||||
|
fmt.echo(f"The API backend is available at: http://localhost:{backend_port}")
|
||||||
|
if start_mcp:
|
||||||
|
fmt.echo(f"The MCP server is available at: http://localhost:{mcp_port}")
|
||||||
fmt.note("Press Ctrl+C to stop the server...")
|
fmt.note("Press Ctrl+C to stop the server...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,11 @@ async def main():
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
|
|
||||||
# Start the UI server
|
# Start the UI server
|
||||||
|
def dummy_callback(pid):
|
||||||
|
pass
|
||||||
|
|
||||||
server = cognee.start_ui(
|
server = cognee.start_ui(
|
||||||
host="localhost",
|
pid_callback=dummy_callback,
|
||||||
port=3000,
|
port=3000,
|
||||||
open_browser=True, # This will automatically open your browser
|
open_browser=True, # This will automatically open your browser
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue