start backend alongside with frontend on cognee -ui (#1378)
<!-- .github/pull_request_template.md --> ## Description <!-- Please provide a clear, human-generated description of the changes in this PR. DO NOT use AI-generated descriptions. We want to understand your thought process and reasoning. --> ## Demo https://github.com/user-attachments/assets/3873cc5e-f5c7-445d-8b95-49007ca86437 1. Run `cognee -ui`, verify cognee frontend and backend started 2. Run in separate port, verify port is in use ### Test backend integration https://github.com/user-attachments/assets/f74de72a-335b-4cec-a565-2c36c9f503c1 1. Add new file to existing dataset 2. Create new dataset ## Type of Change <!-- Please check the relevant option --> - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Code refactoring - [ ] Performance improvement - [ ] Other (please specify): ## Changes Made <!-- List the specific changes made in this PR --> - - - ## Testing <!-- Describe how you tested your changes --> ## Screenshots/Videos (if applicable) <!-- Add screenshots or videos to help explain your changes --> ## Pre-submission Checklist <!-- Please check all boxes that apply before submitting your PR --> - [ ] **I have tested my changes thoroughly before submitting this PR** - [ ] **This PR contains minimal changes necessary to address the issue/feature** - [ ] My code follows the project's coding standards and style guidelines - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] I have added necessary documentation (if applicable) - [ ] All new and existing tests pass - [ ] I have searched existing PRs to ensure this change hasn't been submitted already - [ ] I have linked any relevant issues in the description - [ ] My commits have clear and descriptive messages ## Related Issues <!-- Link any related issues using "Fixes #issue_number" or "Relates to #issue_number" --> ## Additional Notes <!-- Add any additional notes, concerns, or context for reviewers --> ## DCO Affirmation I affirm that all code in every commit of this pull request conforms to the terms of the Topoteretes Developer Certificate of Origin.
This commit is contained in:
commit
545eee96e1
2 changed files with 115 additions and 17 deletions
|
|
@ -330,34 +330,88 @@ def start_ui(
|
||||||
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,
|
||||||
|
backend_host: str = "localhost",
|
||||||
|
backend_port: int = 8000,
|
||||||
) -> Optional[subprocess.Popen]:
|
) -> Optional[subprocess.Popen]:
|
||||||
"""
|
"""
|
||||||
Start the cognee frontend UI server.
|
Start the cognee frontend UI server, optionally with the backend API server.
|
||||||
|
|
||||||
This function will:
|
This function will:
|
||||||
1. Find the cognee-frontend directory (development) or download it (pip install)
|
1. Optionally start the cognee backend API server
|
||||||
2. Check if Node.js and npm are available (for development mode)
|
2. Find the cognee-frontend directory (development) or download it (pip install)
|
||||||
3. Install dependencies if needed (development mode)
|
3. Check if Node.js and npm are available (for development mode)
|
||||||
4. Start the appropriate server
|
4. Install dependencies if needed (development mode)
|
||||||
5. Optionally open the browser
|
5. Start the frontend server
|
||||||
|
6. Optionally open the browser
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
host: Host to bind the server to (default: localhost)
|
host: Host to bind the frontend server to (default: localhost)
|
||||||
port: Port to run the 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)
|
||||||
|
backend_host: Host to bind the backend server to (default: localhost)
|
||||||
|
backend_port: Port to run the backend server on (default: 8000)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
subprocess.Popen object representing the running server, or None if failed
|
subprocess.Popen object representing the running frontend server, or None if failed
|
||||||
|
Note: If backend is started, it runs in a separate process that will be cleaned up
|
||||||
|
when the frontend process is terminated.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
>>> import cognee
|
>>> import cognee
|
||||||
|
>>> # Start just the frontend
|
||||||
>>> server = cognee.start_ui()
|
>>> server = cognee.start_ui()
|
||||||
|
>>>
|
||||||
|
>>> # Start both frontend and backend
|
||||||
|
>>> server = cognee.start_ui(start_backend=True)
|
||||||
>>> # UI will be available at http://localhost:3000
|
>>> # UI will be available at http://localhost:3000
|
||||||
>>> # To stop the server later:
|
>>> # API will be available at http://localhost:8000
|
||||||
|
>>> # To stop both servers later:
|
||||||
>>> server.terminate()
|
>>> server.terminate()
|
||||||
"""
|
"""
|
||||||
logger.info("Starting cognee UI...")
|
logger.info("Starting cognee UI...")
|
||||||
|
backend_process = None
|
||||||
|
|
||||||
|
# Start backend server if requested
|
||||||
|
if start_backend:
|
||||||
|
logger.info("Starting cognee backend API server...")
|
||||||
|
try:
|
||||||
|
import sys
|
||||||
|
|
||||||
|
backend_process = subprocess.Popen(
|
||||||
|
[
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"uvicorn",
|
||||||
|
"cognee.api.client:app",
|
||||||
|
"--host",
|
||||||
|
backend_host,
|
||||||
|
"--port",
|
||||||
|
str(backend_port),
|
||||||
|
],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
text=True,
|
||||||
|
preexec_fn=os.setsid if hasattr(os, "setsid") else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Give the backend a moment to start
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
if backend_process.poll() is not None:
|
||||||
|
stdout, stderr = backend_process.communicate()
|
||||||
|
logger.error("Backend server failed to start:")
|
||||||
|
logger.error(f"stdout: {stdout}")
|
||||||
|
logger.error(f"stderr: {stderr}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
logger.info(f"✓ Backend API started at http://{backend_host}:{backend_port}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to start backend server: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
# Find frontend directory
|
# Find frontend directory
|
||||||
frontend_path = find_frontend_path()
|
frontend_path = find_frontend_path()
|
||||||
|
|
@ -447,16 +501,32 @@ def start_ui(
|
||||||
logger.info(f"✓ Open your browser to: http://{host}:{port}")
|
logger.info(f"✓ Open your browser to: http://{host}:{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")
|
||||||
|
|
||||||
|
# Store backend process reference in the frontend process for cleanup
|
||||||
|
if backend_process:
|
||||||
|
process._cognee_backend_process = backend_process
|
||||||
|
|
||||||
return process
|
return process
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to start frontend server: {str(e)}")
|
logger.error(f"Failed to start frontend server: {str(e)}")
|
||||||
|
# Clean up backend process if it was started
|
||||||
|
if backend_process:
|
||||||
|
logger.info("Cleaning up backend process due to frontend failure...")
|
||||||
|
try:
|
||||||
|
backend_process.terminate()
|
||||||
|
backend_process.wait(timeout=5)
|
||||||
|
except (subprocess.TimeoutExpired, OSError, ProcessLookupError):
|
||||||
|
try:
|
||||||
|
backend_process.kill()
|
||||||
|
backend_process.wait()
|
||||||
|
except (OSError, ProcessLookupError):
|
||||||
|
pass
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def stop_ui(process: subprocess.Popen) -> bool:
|
def stop_ui(process: subprocess.Popen) -> bool:
|
||||||
"""
|
"""
|
||||||
Stop a running UI server process and all its children.
|
Stop a running UI server process and backend process (if started), along with all their children.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
process: The subprocess.Popen object returned by start_ui()
|
process: The subprocess.Popen object returned by start_ui()
|
||||||
|
|
@ -467,7 +537,29 @@ def stop_ui(process: subprocess.Popen) -> bool:
|
||||||
if not process:
|
if not process:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
success = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# First, stop the backend process if it exists
|
||||||
|
backend_process = getattr(process, "_cognee_backend_process", None)
|
||||||
|
if backend_process:
|
||||||
|
logger.info("Stopping backend server...")
|
||||||
|
try:
|
||||||
|
backend_process.terminate()
|
||||||
|
try:
|
||||||
|
backend_process.wait(timeout=5)
|
||||||
|
logger.info("Backend server stopped gracefully")
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
logger.warning("Backend didn't terminate gracefully, forcing kill")
|
||||||
|
backend_process.kill()
|
||||||
|
backend_process.wait()
|
||||||
|
logger.info("Backend server stopped")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error stopping backend server: {str(e)}")
|
||||||
|
success = False
|
||||||
|
|
||||||
|
# Now stop the frontend process
|
||||||
|
logger.info("Stopping frontend server...")
|
||||||
# Try to terminate the process group (includes child processes like Next.js)
|
# Try to terminate the process group (includes child processes like Next.js)
|
||||||
if hasattr(os, "killpg"):
|
if hasattr(os, "killpg"):
|
||||||
try:
|
try:
|
||||||
|
|
@ -484,9 +576,9 @@ def stop_ui(process: subprocess.Popen) -> bool:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
process.wait(timeout=10)
|
process.wait(timeout=10)
|
||||||
logger.info("UI server stopped gracefully")
|
logger.info("Frontend server stopped gracefully")
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
logger.warning("Process didn't terminate gracefully, forcing kill")
|
logger.warning("Frontend didn't terminate gracefully, forcing kill")
|
||||||
|
|
||||||
# Force kill the process group
|
# Force kill the process group
|
||||||
if hasattr(os, "killpg"):
|
if hasattr(os, "killpg"):
|
||||||
|
|
@ -502,11 +594,13 @@ def stop_ui(process: subprocess.Popen) -> bool:
|
||||||
|
|
||||||
process.wait()
|
process.wait()
|
||||||
|
|
||||||
logger.info("UI server stopped")
|
if success:
|
||||||
return True
|
logger.info("UI servers stopped successfully")
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error stopping UI server: {str(e)}")
|
logger.error(f"Error stopping UI servers: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -205,11 +205,15 @@ def main() -> int:
|
||||||
from cognee import start_ui
|
from cognee import start_ui
|
||||||
|
|
||||||
fmt.echo("Starting cognee UI...")
|
fmt.echo("Starting cognee UI...")
|
||||||
server_process = start_ui(host="localhost", port=3000, open_browser=True)
|
|
||||||
|
server_process = start_ui(
|
||||||
|
host="localhost", port=3000, open_browser=True, start_backend=True
|
||||||
|
)
|
||||||
|
|
||||||
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")
|
||||||
fmt.note("Press Ctrl+C to stop the server...")
|
fmt.note("Press Ctrl+C to stop the server...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue