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]