diff --git a/cognee-frontend/src/app/(graph)/GraphVisualization.tsx b/cognee-frontend/src/app/(graph)/GraphVisualization.tsx index 4e2d1e642..6f66264ca 100644 --- a/cognee-frontend/src/app/(graph)/GraphVisualization.tsx +++ b/cognee-frontend/src/app/(graph)/GraphVisualization.tsx @@ -3,10 +3,18 @@ import classNames from "classnames"; import { MutableRefObject, useEffect, useImperativeHandle, useRef, useState, useCallback } from "react"; import { forceCollide, forceManyBody } from "d3-force-3d"; -import ForceGraph, { ForceGraphMethods, GraphData, LinkObject, NodeObject } from "react-force-graph-2d"; +import dynamic from "next/dynamic"; import { GraphControlsAPI } from "./GraphControls"; import getColorForNodeType from "./getColorForNodeType"; +// Dynamically import ForceGraph to prevent SSR issues +const ForceGraph = dynamic(() => import("react-force-graph-2d"), { + ssr: false, + loading: () =>
Loading graph...
+}); + +import type { ForceGraphMethods, GraphData, LinkObject, NodeObject } from "react-force-graph-2d"; + interface GraphVisuzaliationProps { ref: MutableRefObject; data?: GraphData; @@ -200,7 +208,7 @@ export default function GraphVisualization({ ref, data, graphControls, className const graphRef = useRef(); useEffect(() => { - if (typeof window !== "undefined" && data && graphRef.current) { + if (data && graphRef.current) { // add collision force graphRef.current.d3Force("collision", forceCollide(nodeSize * 1.5)); graphRef.current.d3Force("charge", forceManyBody().strength(-10).distanceMin(10).distanceMax(50)); @@ -216,56 +224,34 @@ export default function GraphVisualization({ ref, data, graphControls, className return (
- {(data && typeof window !== "undefined") ? ( - "replace"} + nodeLabel="label" + nodeRelSize={data ? nodeSize : 20} + nodeCanvasObject={data ? renderNode : renderInitialNode} + nodeCanvasObjectMode={() => data ? "replace" : "after"} + nodeAutoColorBy={data ? undefined : "type"} - linkLabel="label" - linkCanvasObject={renderLink} - linkCanvasObjectMode={() => "after"} - linkDirectionalArrowLength={3.5} - linkDirectionalArrowRelPos={1} + linkLabel="label" + linkCanvasObject={renderLink} + linkCanvasObjectMode={() => "after"} + linkDirectionalArrowLength={3.5} + linkDirectionalArrowRelPos={1} - onNodeClick={handleNodeClick} - onBackgroundClick={handleBackgroundClick} - d3VelocityDecay={0.3} - /> - ) : ( - "after"} - nodeAutoColorBy="type" - - linkLabel="label" - linkCanvasObject={renderLink} - linkCanvasObjectMode={() => "after"} - linkDirectionalArrowLength={3.5} - linkDirectionalArrowRelPos={1} - /> - )} + onNodeClick={handleNodeClick} + onBackgroundClick={handleBackgroundClick} + d3VelocityDecay={data ? 0.3 : undefined} + />
); } diff --git a/cognee-frontend/src/utils/fetch.ts b/cognee-frontend/src/utils/fetch.ts index 246853fb9..355912eda 100644 --- a/cognee-frontend/src/utils/fetch.ts +++ b/cognee-frontend/src/utils/fetch.ts @@ -49,6 +49,13 @@ export default async function fetch(url: string, options: RequestInit = {}, useC ) .then((response) => handleServerErrors(response, retry, useCloud)) .catch((error) => { + // Handle network errors more gracefully + if (error.name === 'TypeError' && error.message.includes('fetch')) { + return Promise.reject( + new Error("Backend server is not responding. Please check if the server is running.") + ); + } + if (error.detail === undefined) { return Promise.reject( new Error("No connection to the server.") @@ -62,8 +69,27 @@ export default async function fetch(url: string, options: RequestInit = {}, useC }); } -fetch.checkHealth = () => { - return global.fetch(`${backendApiUrl.replace("/api", "")}/health`); +fetch.checkHealth = async () => { + const maxRetries = 5; + const retryDelay = 1000; // 1 second + + for (let i = 0; i < maxRetries; i++) { + try { + const response = await global.fetch(`${backendApiUrl.replace("/api", "")}/health`); + if (response.ok) { + return response; + } + } catch (error) { + // If this is the last retry, throw the error + if (i === maxRetries - 1) { + throw error; + } + // Wait before retrying + await new Promise(resolve => setTimeout(resolve, retryDelay)); + } + } + + throw new Error("Backend server is not responding after multiple attempts"); }; fetch.setApiKey = (newApiKey: string) => { diff --git a/cognee/api/health.py b/cognee/api/health.py index f3f36c2ed..1b1e45efa 100644 --- a/cognee/api/health.py +++ b/cognee/api/health.py @@ -194,7 +194,7 @@ class HealthChecker: config = get_llm_config() # Test actual API connection with minimal request - LLMGateway.show_prompt("test", "test") + LLMGateway.show_prompt("test", "test.txt") response_time = int((time.time() - start_time) * 1000) return ComponentHealth( diff --git a/cognee/api/v1/cloud/routers/get_checks_router.py b/cognee/api/v1/cloud/routers/get_checks_router.py index 163aff9bc..336b0a732 100644 --- a/cognee/api/v1/cloud/routers/get_checks_router.py +++ b/cognee/api/v1/cloud/routers/get_checks_router.py @@ -16,7 +16,13 @@ def get_checks_router(): api_token = request.headers.get("X-Api-Key") if api_token is None: - raise CloudApiKeyMissingError() + # Return a graceful response for local/self-hosted installations + return { + "status": "local_mode", + "message": "Running in local mode. Cloud features are not available without API key.", + "cloud_available": False, + "local_mode": True, + } return await check_api_key(api_token) diff --git a/cognee/api/v1/ui/ui.py b/cognee/api/v1/ui/ui.py index 6faca19e8..2571b2b7e 100644 --- a/cognee/api/v1/ui/ui.py +++ b/cognee/api/v1/ui/ui.py @@ -1,4 +1,5 @@ import os +import platform import signal import subprocess import threading @@ -214,6 +215,7 @@ def check_node_npm() -> tuple[bool, str]: Check if Node.js and npm are available. Returns (is_available, error_message) """ + try: # Check Node.js result = subprocess.run(["node", "--version"], capture_output=True, text=True, timeout=10) @@ -223,8 +225,17 @@ def check_node_npm() -> tuple[bool, str]: node_version = result.stdout.strip() logger.debug(f"Found Node.js version: {node_version}") - # Check npm - result = subprocess.run(["npm", "--version"], capture_output=True, text=True, timeout=10) + # Check npm - handle Windows PowerShell scripts + if platform.system() == "Windows": + # On Windows, npm might be a PowerShell script, so we need to use shell=True + result = subprocess.run( + ["npm", "--version"], capture_output=True, text=True, timeout=10, shell=True + ) + else: + result = subprocess.run( + ["npm", "--version"], capture_output=True, text=True, timeout=10 + ) + if result.returncode != 0: return False, "npm is not installed or not in PATH" @@ -246,6 +257,7 @@ def install_frontend_dependencies(frontend_path: Path) -> bool: Install frontend dependencies if node_modules doesn't exist. This is needed for both development and downloaded frontends since both use npm run dev. """ + node_modules = frontend_path / "node_modules" if node_modules.exists(): logger.debug("Frontend dependencies already installed") @@ -254,13 +266,24 @@ def install_frontend_dependencies(frontend_path: Path) -> bool: logger.info("Installing frontend dependencies (this may take a few minutes)...") try: - result = subprocess.run( - ["npm", "install"], - cwd=frontend_path, - capture_output=True, - text=True, - timeout=300, # 5 minutes timeout - ) + # Use shell=True on Windows for npm commands + if platform.system() == "Windows": + result = subprocess.run( + ["npm", "install"], + cwd=frontend_path, + capture_output=True, + text=True, + timeout=300, # 5 minutes timeout + shell=True, + ) + else: + result = subprocess.run( + ["npm", "install"], + cwd=frontend_path, + capture_output=True, + text=True, + timeout=300, # 5 minutes timeout + ) if result.returncode == 0: logger.info("Frontend dependencies installed successfully") @@ -462,15 +485,27 @@ def start_ui( try: # Create frontend in its own process group for clean termination - process = subprocess.Popen( - ["npm", "run", "dev"], - cwd=frontend_path, - env=env, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - preexec_fn=os.setsid if hasattr(os, "setsid") else None, - ) + # Use shell=True on Windows for npm commands + if platform.system() == "Windows": + process = subprocess.Popen( + ["npm", "run", "dev"], + cwd=frontend_path, + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + shell=True, + ) + else: + process = subprocess.Popen( + ["npm", "run", "dev"], + cwd=frontend_path, + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + preexec_fn=os.setsid if hasattr(os, "setsid") else None, + ) pid_callback(process.pid) diff --git a/cognee/cli/_cognee.py b/cognee/cli/_cognee.py index 52915594b..7b8eb67fc 100644 --- a/cognee/cli/_cognee.py +++ b/cognee/cli/_cognee.py @@ -183,10 +183,20 @@ def main() -> int: for pid in spawned_pids: try: - pgid = os.getpgid(pid) - os.killpg(pgid, signal.SIGTERM) - fmt.success(f"✓ Process group {pgid} (PID {pid}) terminated.") - except (OSError, ProcessLookupError) as e: + if hasattr(os, "killpg"): + # Unix-like systems: Use process groups + pgid = os.getpgid(pid) + os.killpg(pgid, signal.SIGTERM) + fmt.success(f"✓ Process group {pgid} (PID {pid}) terminated.") + else: + # Windows: Use taskkill to terminate process and its children + subprocess.run( + ["taskkill", "/F", "/T", "/PID", str(pid)], + capture_output=True, + check=False, + ) + fmt.success(f"✓ Process {pid} and its children terminated.") + except (OSError, ProcessLookupError, subprocess.SubprocessError) as e: fmt.warning(f"Could not terminate process {pid}: {e}") sys.exit(0) diff --git a/cognee/infrastructure/llm/prompts/read_query_prompt.py b/cognee/infrastructure/llm/prompts/read_query_prompt.py index 592114d4e..6244b479c 100644 --- a/cognee/infrastructure/llm/prompts/read_query_prompt.py +++ b/cognee/infrastructure/llm/prompts/read_query_prompt.py @@ -26,6 +26,7 @@ def read_query_prompt(prompt_file_name: str, base_directory: str = None): read due to an error. """ logger = get_logger(level=ERROR) + try: if base_directory is None: base_directory = get_absolute_path("./infrastructure/llm/prompts") @@ -35,8 +36,8 @@ def read_query_prompt(prompt_file_name: str, base_directory: str = None): with open(file_path, "r", encoding="utf-8") as file: return file.read() except FileNotFoundError: - logger.error(f"Error: Prompt file not found. Attempted to read: %s {file_path}") + logger.error(f"Error: Prompt file not found. Attempted to read: {file_path}") return None except Exception as e: - logger.error(f"An error occurred: %s {e}") + logger.error(f"An error occurred: {e}") return None diff --git a/cognee/infrastructure/llm/prompts/test.txt b/cognee/infrastructure/llm/prompts/test.txt new file mode 100644 index 000000000..529a11d86 --- /dev/null +++ b/cognee/infrastructure/llm/prompts/test.txt @@ -0,0 +1 @@ +Respond with: test \ No newline at end of file diff --git a/cognee/tests/test_cognee_server_start.py b/cognee/tests/test_cognee_server_start.py index 40ae96548..ab68a8ef1 100644 --- a/cognee/tests/test_cognee_server_start.py +++ b/cognee/tests/test_cognee_server_start.py @@ -41,7 +41,12 @@ class TestCogneeServerStart(unittest.TestCase): def tearDownClass(cls): # Terminate the server process if hasattr(cls, "server_process") and cls.server_process: - os.killpg(os.getpgid(cls.server_process.pid), signal.SIGTERM) + if hasattr(os, "killpg"): + # Unix-like systems: Use process groups + os.killpg(os.getpgid(cls.server_process.pid), signal.SIGTERM) + else: + # Windows: Just terminate the main process + cls.server_process.terminate() cls.server_process.wait() def test_server_is_running(self): diff --git a/pyproject.toml b/pyproject.toml index bbe3732e9..e440d38bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -147,6 +147,7 @@ Homepage = "https://www.cognee.ai" Repository = "https://github.com/topoteretes/cognee" [project.scripts] +cognee = "cognee.cli._cognee:main" cognee-cli = "cognee.cli._cognee:main" [build-system]