Files
archon/original_archon/streamlit_pages/agent_service.py

230 lines
9.9 KiB
Python

import streamlit as st
import subprocess
import threading
import platform
import queue
import time
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from utils.utils import reload_archon_graph
def agent_service_tab():
"""Display the agent service interface for managing the graph service"""
st.header("MCP Agent Service")
st.write("Start, restart, and monitor the Archon agent service for MCP.")
# Initialize session state variables if they don't exist
if "service_process" not in st.session_state:
st.session_state.service_process = None
if "service_running" not in st.session_state:
st.session_state.service_running = False
if "service_output" not in st.session_state:
st.session_state.service_output = []
if "output_queue" not in st.session_state:
st.session_state.output_queue = queue.Queue()
# Function to check if the service is running
def is_service_running():
if st.session_state.service_process is None:
return False
# Check if process is still running
return st.session_state.service_process.poll() is None
# Function to kill any process using port 8100
def kill_process_on_port(port):
try:
if platform.system() == "Windows":
# Windows: use netstat to find the process using the port
result = subprocess.run(
f'netstat -ano | findstr :{port}',
shell=True,
capture_output=True,
text=True
)
if result.stdout:
# Extract the PID from the output
for line in result.stdout.splitlines():
if f":{port}" in line and "LISTENING" in line:
parts = line.strip().split()
pid = parts[-1]
# Kill the process
subprocess.run(f'taskkill /F /PID {pid}', shell=True)
st.session_state.output_queue.put(f"[{time.strftime('%H:%M:%S')}] Killed any existing process using port {port} (PID: {pid})\n")
return True
else:
# Unix-like systems: use lsof to find the process using the port
result = subprocess.run(
f'lsof -i :{port} -t',
shell=True,
capture_output=True,
text=True
)
if result.stdout:
# Extract the PID from the output
pid = result.stdout.strip()
# Kill the process
subprocess.run(f'kill -9 {pid}', shell=True)
st.session_state.output_queue.put(f"[{time.strftime('%H:%M:%S')}] Killed process using port {port} (PID: {pid})\n")
return True
return False
except Exception as e:
st.session_state.output_queue.put(f"[{time.strftime('%H:%M:%S')}] Error killing process on port {port}: {str(e)}\n")
return False
# Update service status
st.session_state.service_running = is_service_running()
# Process any new output in the queue
try:
while not st.session_state.output_queue.empty():
line = st.session_state.output_queue.get_nowait()
if line:
st.session_state.service_output.append(line)
except Exception:
pass
# Create button text based on service status
button_text = "Restart Agent Service" if st.session_state.service_running else "Start Agent Service"
# Create columns for buttons
col1, col2 = st.columns([1, 1])
# Start/Restart button
with col1:
if st.button(button_text, use_container_width=True):
# Stop existing process if running
if st.session_state.service_running:
try:
st.session_state.service_process.terminate()
time.sleep(1) # Give it time to terminate
if st.session_state.service_process.poll() is None:
# Force kill if still running
st.session_state.service_process.kill()
except Exception as e:
st.error(f"Error stopping service: {str(e)}")
# Clear previous output
st.session_state.service_output = []
st.session_state.output_queue = queue.Queue()
# Kill any process using port 8100
kill_process_on_port(8100)
# Start new process
try:
# Get the absolute path to the graph service script
base_path = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
graph_service_path = os.path.join(base_path, 'graph_service.py')
# Start the process with output redirection
process = subprocess.Popen(
[sys.executable, graph_service_path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True
)
st.session_state.service_process = process
st.session_state.service_running = True
# Start threads to read output
def read_output(stream, queue_obj):
for line in iter(stream.readline, ''):
queue_obj.put(line)
stream.close()
# Start threads for stdout and stderr
threading.Thread(target=read_output, args=(process.stdout, st.session_state.output_queue), daemon=True).start()
threading.Thread(target=read_output, args=(process.stderr, st.session_state.output_queue), daemon=True).start()
# Add startup message
st.session_state.output_queue.put(f"[{time.strftime('%H:%M:%S')}] Agent service started\n")
st.success("Agent service started successfully!")
st.rerun()
except Exception as e:
st.error(f"Error starting service: {str(e)}")
st.session_state.output_queue.put(f"[{time.strftime('%H:%M:%S')}] Error: {str(e)}\n")
# Stop button
with col2:
stop_button = st.button("Stop Agent Service", disabled=not st.session_state.service_running, use_container_width=True)
if stop_button and st.session_state.service_running:
try:
st.session_state.service_process.terminate()
time.sleep(1) # Give it time to terminate
if st.session_state.service_process.poll() is None:
# Force kill if still running
st.session_state.service_process.kill()
st.session_state.service_running = False
st.session_state.output_queue.put(f"[{time.strftime('%H:%M:%S')}] Agent service stopped\n")
st.success("Agent service stopped successfully!")
st.rerun()
except Exception as e:
st.error(f"Error stopping service: {str(e)}")
st.session_state.output_queue.put(f"[{time.strftime('%H:%M:%S')}] Error stopping: {str(e)}\n")
# Service status indicator
status_color = "🟢" if st.session_state.service_running else "🔴"
status_text = "Running" if st.session_state.service_running else "Stopped"
st.write(f"**Service Status:** {status_color} {status_text}")
# Add auto-refresh option
auto_refresh = st.checkbox("Auto-refresh output (uncheck this before copying any error message)", value=True)
# Display output in a scrollable container
st.subheader("Service Output")
# Calculate height based on number of lines, but cap it
output_height = min(400, max(200, len(st.session_state.service_output) * 20))
# Create a scrollable container for the output
with st.container():
# Join all output lines and display in the container
output_text = "".join(st.session_state.service_output)
# For auto-scrolling, we'll use a different approach
if auto_refresh and st.session_state.service_running and output_text:
# We'll reverse the output text so the newest lines appear at the top
# This way they're always visible without needing to scroll
lines = output_text.splitlines()
reversed_lines = lines[::-1] # Reverse the lines
output_text = "\n".join(reversed_lines)
# Add a note at the top (which will appear at the bottom of the reversed text)
note = "--- SHOWING NEWEST LOGS FIRST (AUTO-SCROLL MODE) ---\n\n"
output_text = note + output_text
# Use a text area for scrollable output
st.text_area(
label="Realtime Logs from Archon Service",
value=output_text,
height=output_height,
disabled=True,
key="output_text_area" # Use a fixed key to maintain state between refreshes
)
# Add a toggle for reversed mode
if auto_refresh and st.session_state.service_running:
st.caption("Logs are shown newest-first for auto-scrolling. Disable auto-refresh to see logs in chronological order.")
# Add a clear output button
if st.button("Clear Output"):
st.session_state.service_output = []
st.rerun()
# Auto-refresh if enabled and service is running
if auto_refresh and st.session_state.service_running:
time.sleep(0.1) # Small delay to prevent excessive CPU usage
st.rerun()