# /home/jon/nexus-core/management/controlpanel.py import time import tkinter as tk import subprocess import threading import os import signal import sys import webbrowser import requests from pathlib import Path # --- CONFIGURATION --- PROJECT_ROOT = Path(__file__).resolve().parent.parent # Force 0.0.0.0 to ensure the Windows Host bridge works for Project Nexus FRONTEND_DIR = PROJECT_ROOT / "interface" / "web" FRONTEND_CMD = ["/home/jon/.nvm/versions/node/v20.20.2/bin/npm", "run", "dev", "--", "--host", "0.0.0.0"] FRONTEND_URL = "http://127.0.0.1:5173" BACKEND_DIR = PROJECT_ROOT BACKEND_CMD = [ str(PROJECT_ROOT / "Promethean" / "bin" / "python"), "-m", "uvicorn", "synapse.main:sio_app", "--host", "0.0.0.0", "--port", "8000", "--reload", "--reload-dir", str(PROJECT_ROOT / "synapse"), "--log-level", "info" ] MEMORY_DIR = PROJECT_ROOT MEMORY_CMD = [ str(PROJECT_ROOT / "Promethean" / "bin" / "python"), "-m", "uvicorn", "synapse.memory.service:app", "--host", "0.0.0.0", "--port", "8001", "--reload", "--reload-dir", str(PROJECT_ROOT / "synapse"), "--log-level", "info" ] FRONTEND_PID = PROJECT_ROOT / "runtime" / "pids" / "frontend.pid" BACKEND_PID = PROJECT_ROOT / "runtime" / "pids" / "backend.pid" MEMORY_PID = PROJECT_ROOT / "runtime" / "pids" / "memory.pid" CHAT_LOG = PROJECT_ROOT / "runtime" / "logs" / "chat.log" class NexusControlPanel: def __init__(self, root): self.root = root if os.name != 'nt' and not os.environ.get('DISPLAY'): print("ERROR: No DISPLAY variable. Check WSLg.") sys.exit(1) self.root.title("Nexus Control Panel") # Example icon path; replace with your actual icon file path try: self.root.iconphoto(True, tk.PhotoImage(file=r"/home/jon/nexus-core/assets/n-small.png")) except Exception as e: print(f"Icon load failed: {e}") if not sys.platform.startswith("linux"): # Keep the custom borderless titlebar on Windows/WSL, # but allow normal stacking on Linux Mint. self.root.overrideredirect(True) self.root.configure(background="#1e1e1e") window_width = 1800 window_height = 720 try: self.root.update_idletasks() sw = self.root.winfo_screenwidth() sh = self.root.winfo_screenheight() self.root.geometry(f'{window_width}x{window_height}+{int(sw/2 - window_width/2)}+{int(sh/2 - window_height/2)}') except: self.root.geometry(f'{window_width}x{window_height}+100+100') self.frontend_process = None self.backend_process = None self.memory_process = None self._frontend_starting = False self._backend_starting = False self._memory_starting = False self._setup_custom_titlebar() self._setup_ui() self._init_log_tags() self._load_existing_logs() # Set up window close handler for graceful shutdown self.root.protocol("WM_DELETE_WINDOW", self.on_close) backend_pid = PROJECT_ROOT / "runtime/pids/backend.pid" frontend_pid = PROJECT_ROOT / "runtime/pids/frontend.pid" if self._is_running(backend_pid): threading.Thread(target=self._monitor_backend, daemon=True).start() if self._is_running(frontend_pid): threading.Thread(target=self._monitor_frontend, daemon=True).start() if self._is_running(MEMORY_PID): threading.Thread(target=self._monitor_memory, daemon=True).start() threading.Thread(target=self._monitor_synapses, daemon=True).start() self.update_master_ui() self._poll_status() threading.Thread(target=self._detect_gpu, daemon=True).start() def _load_existing_logs(self): backend_pid = PROJECT_ROOT / "runtime/pids/backend.pid" frontend_pid = PROJECT_ROOT / "runtime/pids/frontend.pid" # BACKEND if self._is_running(backend_pid): try: with open(PROJECT_ROOT / "runtime/backend.log", "r") as f: for line in f.readlines()[-200:]: self.log("BACKEND", line.rstrip()) except: pass # FRONTEND if self._is_running(frontend_pid): try: with open(PROJECT_ROOT / "runtime/frontend.log", "r") as f: for line in f.readlines()[-200:]: self.log("FRONTEND", line.rstrip()) except: pass def _init_log_tags(self): for console in [self.sys_console, self.front_console, self.back_console, self.mind_console, self.mem_console]: console.tag_config("SYSTEM", foreground="#ff00ea") console.tag_config("FRONTEND", foreground="#61dbfb") console.tag_config("BACKEND", foreground="#ffd43b") console.tag_config("MINDTRACE", foreground="#cc88ff") console.tag_config("MEMORY", foreground="#78e08f") console.tag_config("ERROR", foreground="#ff0000") console.tag_config("GPU_NVIDIA", foreground="#76b900") console.tag_config("GPU_AMD", foreground="#ed1c24") console.tag_config("GPU_VULKAN", foreground="#7b68ee") def _setup_custom_titlebar(self): self.title_bar = tk.Frame(self.root, bg="#2d2d2d", bd=0) self.title_bar.pack(fill=tk.X, side=tk.TOP) tk.Label(self.title_bar, text="NexusOS // AI CONTROL PANEL", bg="#2d2d2d", fg="#00ff00", font=("Consolas", 10, "bold"), padx=8, pady=3).pack(side=tk.LEFT) self.title_bar.bind("", self.start_move) self.title_bar.bind("", self.do_move) def start_move(self, event): self.x, self.y = event.x, event.y def do_move(self, event): self.root.geometry(f"+{self.root.winfo_x() + (event.x - self.x)}+{self.root.winfo_y() + (event.y - self.y)}") def _clear_all_logs(self): for console in [self.sys_console, self.front_console, self.back_console, self.mem_console, self.mind_console]: console.delete("1.0", tk.END) def _copy_selection(self, widget): """Copy selection or full console contents to the system clipboard.""" try: content = widget.get(tk.SEL_FIRST, tk.SEL_LAST) if widget.tag_ranges(tk.SEL) else widget.get("1.0", tk.END) content = content.strip() if content: self.root.clipboard_clear() self.root.clipboard_append(content) self.root.update() self.log("SYSTEM", "Copied log contents to clipboard.") except Exception as e: self.log("ERROR", f"Clipboard Error: {e}") return "break" def _create_terminal(self, parent, label_text, height=10, text_color="#cccccc"): frame = tk.Frame(parent, bg="#1e1e1e", bd=0) header = tk.Frame(frame, bg="#1e1e1e", bd=0) header.pack(fill=tk.X, pady=(0, 1)) tk.Label(header, text=label_text, bg="#1e1e1e", fg=text_color, font=("Consolas", 8, "bold")).pack(side=tk.LEFT) console = tk.Text(frame, bg="#000000", fg=text_color, font=("Consolas", 8), height=height, borderwidth=3, relief="flat", exportselection=True) tk.Button(header, text="CLEAR LOG", bg="#2d2d2d", fg="#ff0000", font=("Consolas", 7, "bold"), relief="flat", pady=2, command=lambda c=console: c.delete("1.0", tk.END)).pack(side=tk.RIGHT, padx=0) tk.Button(header, text="COPY LOG", bg="#2d2d2d", fg=text_color, font=("Consolas", 7, "bold"), relief="flat", pady=2, command=lambda c=console: self._copy_selection(c)).pack(side=tk.RIGHT, padx=4) console.pack(fill=tk.BOTH, expand=True) return frame, console def _setup_ui(self): controls = tk.Frame(self.root, bg="#1e1e1e") controls.pack(fill=tk.X, padx=10, pady=8) # Style shared across all STOP buttons STOP_STYLE = {"bg": "#331111", "fg": "#ff4444", "disabledforeground": "#552222", "relief": "flat", "font": ("Consolas", 9, "bold"), "pady": 3} START_STYLE = {"bg": "#2d2d2d", "relief": "flat", "font": ("Consolas", 9, "bold"), "pady": 3} # Clear All Logs tk.Button(controls, text="CLEAR ALL LOGS", bg="#2d2d2d", fg="#ff0000", relief="flat", font=("Consolas", 9, "bold"), pady=3, command=self._clear_all_logs).pack(side=tk.RIGHT, padx=(5, 0)) # Master Controls self.btn_master_start = tk.Button(controls, text="SYSTEM START", fg="#00ff00", **START_STYLE, command=self.start_all) self.btn_master_start.pack(side=tk.LEFT, padx=4) self.btn_master_stop = tk.Button(controls, text="SYSTEM STOP", **STOP_STYLE, command=self.stop_all) self.btn_master_stop.pack(side=tk.LEFT, padx=4) self.btn_master_kill = tk.Button(controls, text="SYSTEM KILL", bg="#4a0000", fg="#ff2222", disabledforeground="#552222", relief="flat", font=("Consolas", 9, "bold"), pady=3, command=self.kill_all) self.btn_master_kill.pack(side=tk.LEFT, padx=4) tk.Label(controls, text=" | ", bg="#1e1e1e", fg="#444444").pack(side=tk.LEFT, padx=5) # Interface Controls self.btn_front_start = tk.Button(controls, text="INTERFACE", fg="#61dbfb", **START_STYLE, command=self.start_frontend) self.btn_front_start.pack(side=tk.LEFT, padx=2) self.btn_front_stop = tk.Button(controls, text="OFF", **STOP_STYLE, command=self.stop_frontend) self.btn_front_stop.pack(side=tk.LEFT, padx=2) self.btn_front_view = tk.Button(controls, text="VIEW", fg="#61dbfb", **START_STYLE, command=self.open_frontend_view) self.btn_front_view.pack(side=tk.LEFT, padx=2) # Synapse Controls self.btn_back_start = tk.Button(controls, text="SYNAPSE", fg="#ffd43b", **START_STYLE, command=self.start_backend) self.btn_back_start.pack(side=tk.LEFT, padx=(8, 2)) self.btn_back_stop = tk.Button(controls, text="OFF", **STOP_STYLE, command=self.stop_backend) self.btn_back_stop.pack(side=tk.LEFT, padx=2) # Memory Service Controls self.btn_mem_start = tk.Button(controls, text="MEMORY", fg="#78e08f", **START_STYLE, command=self.start_memory) self.btn_mem_start.pack(side=tk.LEFT, padx=(8, 2)) self.btn_mem_stop = tk.Button(controls, text="OFF", **STOP_STYLE, command=self.stop_memory) self.btn_mem_stop.pack(side=tk.LEFT, padx=2) # Terminal Displays sys_f, self.sys_console = self._create_terminal(self.root, " >> SYSTEM STATUS", height=5, text_color="#ff00ea") sys_f.pack(fill=tk.X, padx=10, pady=(0, 8)) bottom_f = tk.Frame(self.root, bg="#1e1e1e") bottom_f.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 8)) for col in range(4): bottom_f.columnconfigure(col, weight=1, uniform="term") bottom_f.rowconfigure(0, weight=1) f_f, self.front_console = self._create_terminal(bottom_f, " >> INTERFACE (NPM)", text_color="#61dbfb") f_f.grid(row=0, column=0, sticky="nsew", padx=(0, 4)) b_f, self.back_console = self._create_terminal(bottom_f, " >> SYNAPSE (UVICORN)", text_color="#ffd43b") b_f.grid(row=0, column=1, sticky="nsew", padx=4) mm_f, self.mem_console = self._create_terminal(bottom_f, " >> MEMORY SERVICE", text_color="#78e08f") mm_f.grid(row=0, column=2, sticky="nsew", padx=4) m_f, self.mind_console = self._create_terminal(bottom_f, " >> MINDTRACE", text_color="#cc88ff") m_f.grid(row=0, column=3, sticky="nsew", padx=(4, 0)) def log(self, tag, message): def _log(): con = self.sys_console if tag == "FRONTEND": con = self.front_console elif tag == "BACKEND": con = self.back_console elif tag == "MEMORY": con = self.mem_console elif tag == "MINDTRACE": con = self.mind_console con.insert(tk.END, f"[{tag}] {message}\n", tag) con.see(tk.END) self.root.after(0, _log) def _detect_gpu(self): # Try NVIDIA (CUDA) try: r = subprocess.run( ["nvidia-smi", "--query-gpu=name", "--format=csv,noheader"], capture_output=True, text=True, timeout=5 ) if r.returncode == 0: name = r.stdout.strip().splitlines()[0] self.log("GPU_NVIDIA", f"GPU: NVIDIA — {name} (CUDA)") return except (FileNotFoundError, subprocess.TimeoutExpired): pass # Try AMD ROCm try: r = subprocess.run( ["rocm-smi", "--showproductname"], capture_output=True, text=True, timeout=5 ) if r.returncode == 0: for line in r.stdout.splitlines(): if line.strip() and not line.startswith("="): self.log("GPU_AMD", f"GPU: AMD — {line.strip()} (ROCm)") return self.log("GPU_AMD", "GPU: AMD (ROCm)") return except (FileNotFoundError, subprocess.TimeoutExpired): pass # Try Vulkan (works on AMD, Intel, and NVIDIA without CUDA/ROCm stack) vulkan_ok = False try: r = subprocess.run( ["vulkaninfo", "--summary"], capture_output=True, text=True, timeout=5 ) vulkan_ok = r.returncode == 0 except (FileNotFoundError, subprocess.TimeoutExpired): pass if not vulkan_ok: icd_dirs = [Path("/usr/share/vulkan/icd.d"), Path("/etc/vulkan/icd.d")] try: vulkan_ok = any(p.is_dir() and any(p.iterdir()) for p in icd_dirs) except PermissionError: pass if vulkan_ok: # Use vulkaninfo to find the best device (discrete > integrated, AMD/NVIDIA > Intel) gpu_name = "GPU" try: r = subprocess.run( ["vulkaninfo", "--summary"], capture_output=True, text=True, timeout=5 ) if r.returncode == 0: devices = [] current = {} for line in r.stdout.splitlines(): line = line.strip() if line.startswith("GPU") and line.endswith(":"): if current: devices.append(current) current = {"name": "GPU", "type": "", "vendor": ""} elif "=" in line: key, _, val = line.partition("=") key, val = key.strip(), val.strip() if key == "deviceName": current["name"] = val elif key == "deviceType": current["type"] = val.upper() elif key == "vendorID": current["vendor"] = val.lower() if current: devices.append(current) def _score(d): type_score = 2 if "DISCRETE" in d["type"] else (0 if "INTEGRATED" in d["type"] else 1) vendor_score = 1 if any(v in d["vendor"] for v in ("0x1002", "0x10de")) else 0 return (type_score, vendor_score) if devices: best = max(devices, key=_score) gpu_name = best["name"] except Exception: pass self.log("GPU_VULKAN", f"GPU: {gpu_name} (Vulkan)") return # Fallback: lspci — hardware present but no usable drivers try: r = subprocess.run(["lspci"], capture_output=True, text=True, timeout=5) if r.returncode == 0: for line in r.stdout.splitlines(): ll = line.lower() if any(x in ll for x in ["vga", "3d controller", "display"]): if "nvidia" in ll: self.log("GPU_NVIDIA", "GPU: NVIDIA detected (no drivers)") return if "amd" in ll or "radeon" in ll or "advanced micro" in ll: self.log("GPU_AMD", "GPU: AMD detected (no drivers)") return except (FileNotFoundError, subprocess.TimeoutExpired): pass self.log("SYSTEM", "GPU: not detected") def _poll_status(self): self.update_master_ui() self.root.after(2000, self._poll_status) def update_master_ui(self): f, b, m = self._frontend_is_active(), self._backend_is_active(), self._memory_is_active() self.btn_master_start.config(state=tk.DISABLED if (f and b and m) else tk.NORMAL) self.btn_master_stop.config(state=tk.NORMAL if (f or b or m) else tk.DISABLED) self.btn_front_start.config(state=tk.DISABLED if f else tk.NORMAL, bg="#0b3d0b" if f else "#2d2d2d") self.btn_back_start.config(state=tk.DISABLED if b else tk.NORMAL, bg="#3d3d0b" if b else "#2d2d2d") self.btn_mem_start.config(state=tk.DISABLED if m else tk.NORMAL, bg="#0b2d14" if m else "#2d2d2d") self.btn_front_stop.config(state=tk.NORMAL if f else tk.DISABLED) self.btn_front_view.config(state=tk.NORMAL if f else tk.DISABLED) self.btn_back_stop.config(state=tk.NORMAL if b else tk.DISABLED) self.btn_mem_stop.config(state=tk.NORMAL if m else tk.DISABLED) def open_frontend_view(self): webbrowser.open_new_tab(FRONTEND_URL) def _spawn_process(self, cmd, cwd): env = os.environ.copy() env["PYTHONUNBUFFERED"] = "1" # If the command uses an absolute path (e.g. nvm npm), prepend its # bin directory to PATH so child processes (node, vite shebang) resolve # from the same installation instead of falling back to /usr/bin/node. if cmd and os.path.isabs(cmd[0]): bin_dir = str(Path(cmd[0]).parent) env["PATH"] = bin_dir + os.pathsep + env.get("PATH", "") kwargs = { "cwd": str(cwd), "stdout": subprocess.PIPE, "stderr": subprocess.STDOUT, "text": True, "bufsize": 1, "env": env, } if os.name == "nt": kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP else: kwargs["preexec_fn"] = os.setsid return subprocess.Popen(cmd, **kwargs) def _terminate_process(self, process): try: if os.name == "nt": try: process.send_signal(signal.CTRL_BREAK_EVENT) except Exception: pass process.terminate() else: os.killpg(os.getpgid(process.pid), signal.SIGKILL) except Exception: try: process.kill() except Exception: pass def _write_pid(self, pid_file, pid): try: pid_file.parent.mkdir(parents=True, exist_ok=True) with open(pid_file, "w") as f: f.write(str(pid)) except Exception as e: self.log("ERROR", f"PID write failed ({pid_file.name}): {e}") def _worker(self, name, cmd, cwd): try: p = self._spawn_process(cmd, cwd) if name == "FRONTEND": self._frontend_starting = False self.frontend_process = p self._write_pid(FRONTEND_PID, p.pid) elif name == "MEMORY": self._memory_starting = False self.memory_process = p self._write_pid(MEMORY_PID, p.pid) else: self._backend_starting = False self.backend_process = p self._write_pid(BACKEND_PID, p.pid) self.root.after(0, self.update_master_ui) if not p.stdout: raise RuntimeError("Process has no stdout stream") while True: try: line = p.stdout.readline() except ValueError: break if not line: break line = line.strip() if not line: continue # Silence routine HTTP access logs if any(x in line for x in ["GET /", "POST /", "OPTIONS /"]): continue # Normalize prefixes to consistent casing line = (line .replace("[Ollama]", "[OLLAMA]") .replace("[Synapse]", "[SYNAPSE]") ) self.log(name, line) p.wait() except Exception as e: self.log("ERROR", f"{name} fault: {e}") finally: self.log("SYSTEM", f"{name} connection closed.") if name == "FRONTEND": self._frontend_starting = False self.frontend_process = None FRONTEND_PID.unlink(missing_ok=True) elif name == "MEMORY": self._memory_starting = False self.memory_process = None MEMORY_PID.unlink(missing_ok=True) else: self._backend_starting = False self.backend_process = None BACKEND_PID.unlink(missing_ok=True) self.root.after(0, self.update_master_ui) def start_frontend(self): if self._frontend_is_active(): return self._frontend_starting = True self.update_master_ui() self.log("SYSTEM", "Starting INTERFACE...") threading.Thread(target=self._worker, args=("FRONTEND", FRONTEND_CMD, FRONTEND_DIR), daemon=True).start() def stop_frontend(self): if self.frontend_process: self._terminate_process(self.frontend_process) self.frontend_process = None elif self._is_running(FRONTEND_PID): self._terminate_pid_file(FRONTEND_PID) self.update_master_ui() def start_backend(self): if self._backend_is_active(): return self._backend_starting = True self.update_master_ui() self.log("SYSTEM", "Starting SYNAPSE...") threading.Thread(target=self._worker, args=("BACKEND", BACKEND_CMD, BACKEND_DIR), daemon=True).start() # Wait for backend to start, then start Ollama threading.Thread(target=self._start_ollama_after_backend, daemon=True).start() def _start_ollama_after_backend(self): """Wait for backend to be ready, then start Ollama. If the backend process dies before becoming reachable, stop Ollama.""" max_retries = 30 for attempt in range(max_retries): # Once the _worker has had a chance to set backend_process, check # whether it has already exited (import error, port conflict, etc.) if attempt >= 2 and self.backend_process is not None: if self.backend_process.poll() is not None: self.log("SYSTEM", "Backend exited before becoming reachable — stopping OLLAMA.") self._stop_ollama_direct() return try: response = requests.get("http://localhost:8000/", timeout=2) if response.status_code == 200: time.sleep(1) self.log("SYSTEM", "Starting OLLAMA...") try: requests.post("http://localhost:8000/ollama/start", timeout=2) self.log("SYSTEM", "OLLAMA startup command sent.") except Exception as e: self.log("SYSTEM", f"OLLAMA startup error: {e}") return except (requests.RequestException, ConnectionError): time.sleep(1) self.log("SYSTEM", "Backend failed to become reachable — stopping OLLAMA.") self._stop_ollama_direct() def _stop_ollama_direct(self): """Kill Ollama without the backend API (used when backend is not running).""" try: import httpx r = httpx.get("http://localhost:11434/api/tags", timeout=2.0) ollama_running = r.status_code == 200 except Exception: ollama_running = False if not ollama_running: self.log("SYSTEM", "OLLAMA is not running — nothing to stop.") return try: result = subprocess.run( ["pkill", "-f", "ollama serve"], capture_output=True, timeout=5, ) if result.returncode == 0: self.log("SYSTEM", "OLLAMA stopped.") else: self.log("SYSTEM", "OLLAMA process not found via pkill.") except Exception as e: self.log("SYSTEM", f"Could not stop OLLAMA: {e}") def stop_backend(self): self.log("SYSTEM", "Stopping OLLAMA...") try: requests.post("http://localhost:8000/ollama/stop", timeout=5) except Exception as e: self.log("SYSTEM", f"OLLAMA stop error: {e}") if self.backend_process: self._terminate_process(self.backend_process) self.backend_process = None elif self._is_running(BACKEND_PID): self._terminate_pid_file(BACKEND_PID) self.update_master_ui() def start_memory(self): if self._memory_is_active(): return self._memory_starting = True self.update_master_ui() self.log("SYSTEM", "Starting MEMORY SERVICE...") threading.Thread(target=self._worker, args=("MEMORY", MEMORY_CMD, MEMORY_DIR), daemon=True).start() def stop_memory(self): if self.memory_process: self._terminate_process(self.memory_process) self.memory_process = None elif self._is_running(MEMORY_PID): self._terminate_pid_file(MEMORY_PID) self.update_master_ui() def _memory_is_active(self): return self._memory_starting or self.memory_process is not None or self._is_running(MEMORY_PID) def _monitor_memory(self): log_path = PROJECT_ROOT / "runtime/memory.log" try: with open(log_path, "r") as f: f.seek(0, os.SEEK_END) while self._is_running(MEMORY_PID): line = f.readline() if line: self.log("MEMORY", line.rstrip()) else: time.sleep(0.1) except Exception as e: self.log("ERROR", f"MEMORY monitor fault: {e}") def start_all(self): self.start_memory() self.start_frontend() self.start_backend() def kill_all(self): self.log("SYSTEM", "FORCE KILL — terminating all Nexus processes by port...") ports = {8000: "SYNAPSE", 8001: "MEMORY", 5173: "INTERFACE"} for port, name in ports.items(): try: r = subprocess.run(["fuser", "-k", f"{port}/tcp"], capture_output=True, timeout=5) self.log("SYSTEM", f"{name} (:{port}) {'killed' if r.returncode == 0 else 'not running'}") except Exception as e: self.log("ERROR", f" Kill :{port} failed: {e}") for pat in ["uvicorn synapse", "npm run dev"]: try: subprocess.run(["pkill", "-9", "-f", pat], capture_output=True, timeout=5) except Exception: pass for pid_file in [FRONTEND_PID, BACKEND_PID, MEMORY_PID]: pid_file.unlink(missing_ok=True) self.frontend_process = None self.backend_process = None self.memory_process = None self._frontend_starting = False self._backend_starting = False self._memory_starting = False self.root.after(0, self.update_master_ui) self.log("SYSTEM", "Force kill complete.") def stop_all(self): self.stop_frontend() self.stop_backend() self.stop_memory() def on_close(self): self.stop_all() self.root.destroy() os._exit(0) def _frontend_is_active(self): return self._frontend_starting or self.frontend_process is not None or self._is_running(FRONTEND_PID) def _backend_is_active(self): return self._backend_starting or self.backend_process is not None or self._is_running(BACKEND_PID) def _terminate_pid_file(self, pid_file): try: with open(pid_file, "r") as f: pid = int(f.read().strip()) if os.name == "nt": try: os.kill(pid, signal.CTRL_BREAK_EVENT) except Exception: pass os.kill(pid, signal.SIGKILL) else: try: os.killpg(os.getpgid(pid), signal.SIGKILL) except (ProcessLookupError, OSError): os.kill(pid, signal.SIGKILL) except Exception: pass try: pid_file.unlink(missing_ok=True) except Exception: pass def _is_running(self, pid_file): try: with open(pid_file, "r") as f: pid = int(f.read().strip()) os.kill(pid, 0) # Check if process exists return True except: return False def _monitor_backend(self): log_path = PROJECT_ROOT / "runtime/backend.log" try: with open(log_path, "r") as f: f.seek(0, os.SEEK_END) # Start at end of file while self._is_running(PROJECT_ROOT / "runtime/pids/backend.pid"): line = f.readline() if line: self.log("BACKEND", line.rstrip()) else: time.sleep(0.1) except Exception as e: self.log("ERROR", f"BACKEND fault: {e}") def _monitor_frontend(self): log_path = PROJECT_ROOT / "runtime/frontend.log" try: with open(log_path, "r") as f: f.seek(0, os.SEEK_END) while self._is_running(PROJECT_ROOT / "runtime/pids/frontend.pid"): line = f.readline() if line: self.log("FRONTEND", line.rstrip()) else: time.sleep(0.1) except Exception as e: self.log("ERROR", f"FRONTEND fault: {e}") def _monitor_synapses(self): while True: try: if not CHAT_LOG.exists(): time.sleep(2) continue with open(CHAT_LOG, "r", encoding="utf-8") as f: f.seek(0, os.SEEK_END) while True: line = f.readline() if line: stripped = line.rstrip() if stripped: self.log("MINDTRACE", stripped) else: time.sleep(0.05) except Exception as e: self.log("ERROR", f"MINDTRACE monitor fault: {e}") time.sleep(2) if __name__ == "__main__": root = tk.Tk() app = NexusControlPanel(root) root.mainloop()