@@ -145,6 +145,44 @@ class Agent:
|
||||
def name(self) -> str:
|
||||
return self.config.name
|
||||
|
||||
def is_alive(self) -> bool:
|
||||
"""Check if the agent process is still running."""
|
||||
# First check if we have a subprocess object
|
||||
if self.process is not None:
|
||||
return self.process.poll() is None
|
||||
|
||||
# If no process object, search for cagent process with our config file
|
||||
try:
|
||||
import psutil
|
||||
work_dir = Path(self.config.working_directory)
|
||||
agent_config_path = work_dir / f"agent_{self.id}.yaml"
|
||||
|
||||
# Search for cagent processes
|
||||
for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
|
||||
try:
|
||||
if proc.info['name'] == 'cagent' and proc.info['cmdline']:
|
||||
cmdline = ' '.join(proc.info['cmdline'])
|
||||
if str(agent_config_path) in cmdline:
|
||||
return True
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
continue
|
||||
except ImportError:
|
||||
# psutil not available, fall back to checking self.process only
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
def update_status(self) -> None:
|
||||
"""Update the agent status based on process state."""
|
||||
if self.is_alive():
|
||||
# Process is running, update status to RUNNING
|
||||
if self.status in (AgentStatus.STOPPED, AgentStatus.ERROR, AgentStatus.STARTING):
|
||||
self.status = AgentStatus.RUNNING
|
||||
else:
|
||||
# Process is not running, update status to STOPPED
|
||||
if self.status == AgentStatus.RUNNING:
|
||||
self.status = AgentStatus.STOPPED
|
||||
|
||||
def on(self, event: str, callback: Callable) -> None:
|
||||
"""Register an event callback."""
|
||||
if event in self._callbacks:
|
||||
@@ -177,14 +215,33 @@ class Agent:
|
||||
agent_config_path = work_dir / f"agent_{self.id}.yaml"
|
||||
self._write_cagent_config(agent_config_path)
|
||||
|
||||
# Start cagent process
|
||||
# Check if model is available, download if needed
|
||||
# Get the model ID from the config we just wrote
|
||||
with open(agent_config_path) as f:
|
||||
config_data = yaml.safe_load(f)
|
||||
model_id = config_data["agents"]["root"]["model"]
|
||||
|
||||
if not self._check_model_available(model_id):
|
||||
logger.info(f"Model {model_id} not found locally, downloading...")
|
||||
if not self._pull_model(model_id):
|
||||
raise Exception(f"Failed to download model {model_id}")
|
||||
|
||||
# Start cagent API server
|
||||
# Use 'cagent api' to run agent as HTTP API service
|
||||
# Each agent gets a unique port based on hash of agent ID
|
||||
port = 8000 + (hash(self.id) % 1000)
|
||||
|
||||
cmd = [
|
||||
"cagent",
|
||||
"--config", str(agent_config_path),
|
||||
"--model", self.config.model_id,
|
||||
"--interactive" if self.config.interactive else "--daemon",
|
||||
"api",
|
||||
str(agent_config_path),
|
||||
"--listen", f":{port}",
|
||||
]
|
||||
|
||||
# Log the command for debugging
|
||||
logger.debug(f"Starting agent with command: {' '.join(cmd)}")
|
||||
logger.info(f"Agent API will listen on port {port}")
|
||||
|
||||
self.process = subprocess.Popen(
|
||||
cmd,
|
||||
stdin=subprocess.PIPE,
|
||||
@@ -194,6 +251,15 @@ class Agent:
|
||||
env={**os.environ, **self.config.environment},
|
||||
)
|
||||
|
||||
# Give it a moment to start
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
# Check if process is still running
|
||||
if self.process.poll() is not None:
|
||||
# Process died immediately
|
||||
stderr = self.process.stderr.read().decode() if self.process.stderr else ""
|
||||
raise Exception(f"Agent process exited immediately: {stderr}")
|
||||
|
||||
self.status = AgentStatus.RUNNING
|
||||
self._emit("on_start", self)
|
||||
logger.info(f"Agent {self.name} started successfully")
|
||||
@@ -208,10 +274,14 @@ class Agent:
|
||||
async def stop(self) -> bool:
|
||||
"""Stop the agent."""
|
||||
async with self._lock:
|
||||
if self.status == AgentStatus.STOPPED:
|
||||
# Update status first to check actual process state
|
||||
self.update_status()
|
||||
|
||||
if self.status == AgentStatus.STOPPED and not self.is_alive():
|
||||
return True
|
||||
|
||||
try:
|
||||
# Try to stop via subprocess object first
|
||||
if self.process:
|
||||
self.process.terminate()
|
||||
try:
|
||||
@@ -219,6 +289,32 @@ class Agent:
|
||||
except subprocess.TimeoutExpired:
|
||||
self.process.kill()
|
||||
self.process.wait()
|
||||
else:
|
||||
# No subprocess object, find and kill the process using psutil
|
||||
try:
|
||||
import psutil
|
||||
work_dir = Path(self.config.working_directory)
|
||||
agent_config_path = work_dir / f"agent_{self.id}.yaml"
|
||||
|
||||
# Search for cagent processes
|
||||
for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
|
||||
try:
|
||||
if proc.info['name'] == 'cagent' and proc.info['cmdline']:
|
||||
cmdline = ' '.join(proc.info['cmdline'])
|
||||
if str(agent_config_path) in cmdline:
|
||||
logger.info(f"Terminating cagent process PID {proc.info['pid']}")
|
||||
proc.terminate()
|
||||
try:
|
||||
proc.wait(timeout=10)
|
||||
except psutil.TimeoutExpired:
|
||||
logger.warning(f"Process {proc.info['pid']} didn't terminate, killing")
|
||||
proc.kill()
|
||||
proc.wait()
|
||||
break
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
continue
|
||||
except ImportError:
|
||||
logger.warning("psutil not available, cannot stop agent without process object")
|
||||
|
||||
self.status = AgentStatus.STOPPED
|
||||
self._emit("on_stop", self)
|
||||
@@ -237,25 +333,60 @@ class Agent:
|
||||
return None
|
||||
|
||||
try:
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
# Record user message
|
||||
user_msg = AgentMessage(role="user", content=content)
|
||||
self.message_history.append(user_msg)
|
||||
|
||||
# Send to agent process
|
||||
if self.process and self.process.stdin:
|
||||
self.process.stdin.write(f"{content}\n".encode())
|
||||
self.process.stdin.flush()
|
||||
# Get the temporary agent config path
|
||||
agent_config_path = Path(tempfile.gettempdir()) / "debai" / f"agent_{self.id}.yaml"
|
||||
|
||||
# Read response
|
||||
if self.process and self.process.stdout:
|
||||
response = self.process.stdout.readline().decode().strip()
|
||||
assistant_msg = AgentMessage(role="assistant", content=response)
|
||||
self.message_history.append(assistant_msg)
|
||||
self._emit("on_message", self, assistant_msg)
|
||||
return assistant_msg
|
||||
if not agent_config_path.exists():
|
||||
logger.error(f"Agent config file not found: {agent_config_path}")
|
||||
return None
|
||||
|
||||
# Run cagent with the message
|
||||
cmd = [
|
||||
"cagent", "run", str(agent_config_path), content,
|
||||
"--tui=false"
|
||||
]
|
||||
|
||||
# Execute cagent run command
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
*cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=str(agent_config_path.parent)
|
||||
)
|
||||
|
||||
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=120.0)
|
||||
|
||||
if process.returncode == 0:
|
||||
response_content = stdout.decode('utf-8').strip()
|
||||
# Remove the "--- Agent: root ---" header if present
|
||||
if response_content.startswith("--- Agent:"):
|
||||
lines = response_content.split('\n')
|
||||
if len(lines) > 1:
|
||||
response_content = '\n'.join(lines[1:]).strip()
|
||||
|
||||
if response_content:
|
||||
assistant_msg = AgentMessage(role="assistant", content=response_content)
|
||||
self.message_history.append(assistant_msg)
|
||||
self._emit("on_message", self, assistant_msg)
|
||||
return assistant_msg
|
||||
else:
|
||||
logger.error(f"Empty response from agent {self.name}")
|
||||
return None
|
||||
else:
|
||||
error_text = stderr.decode('utf-8')
|
||||
logger.error(f"Agent {self.name} error: {error_text}")
|
||||
return None
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"Timeout waiting for response from agent {self.name}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending message to agent {self.name}: {e}")
|
||||
return None
|
||||
@@ -288,29 +419,71 @@ class Agent:
|
||||
result["duration_seconds"] = (datetime.now() - start_time).total_seconds()
|
||||
return result
|
||||
|
||||
def _check_model_available(self, model_name: str) -> bool:
|
||||
"""Check if a model is available locally in docker-model."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["docker-model", "list"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
# Parse output and check if model exists
|
||||
# Model name might be in format "dmr/model" or just "model"
|
||||
check_name = model_name.replace("dmr/", "")
|
||||
return check_name in result.stdout
|
||||
return False
|
||||
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||
logger.warning("Could not check model availability (docker-model not found)")
|
||||
return True # Assume available if we can't check
|
||||
|
||||
def _pull_model(self, model_name: str) -> bool:
|
||||
"""Pull a model using docker-model."""
|
||||
try:
|
||||
# Remove dmr/ prefix if present
|
||||
pull_name = model_name.replace("dmr/", "")
|
||||
logger.info(f"Downloading model {pull_name}...")
|
||||
|
||||
result = subprocess.run(
|
||||
["docker-model", "pull", pull_name],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=300, # 5 minutes timeout for download
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
logger.info(f"Successfully downloaded model {pull_name}")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"Failed to download model {pull_name}: {result.stderr}")
|
||||
return False
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.error(f"Timeout while downloading model {model_name}")
|
||||
return False
|
||||
except FileNotFoundError:
|
||||
logger.error("docker-model command not found")
|
||||
return False
|
||||
|
||||
def _write_cagent_config(self, path: Path) -> None:
|
||||
"""Write cagent configuration file."""
|
||||
# cagent v2 format
|
||||
# Use the exact model requested by the user
|
||||
model_id = self.config.model_id
|
||||
|
||||
# Add dmr/ prefix if not already present and no other provider is specified
|
||||
if "/" not in model_id:
|
||||
model_id = f"dmr/{model_id}"
|
||||
|
||||
config = {
|
||||
"agent": {
|
||||
"name": self.config.name,
|
||||
"description": self.config.description,
|
||||
"type": self.config.agent_type,
|
||||
},
|
||||
"model": {
|
||||
"id": self.config.model_id,
|
||||
"provider": "docker-model",
|
||||
},
|
||||
"capabilities": list(self.config.capabilities),
|
||||
"system_prompt": self.config.system_prompt,
|
||||
"limits": {
|
||||
"max_memory_mb": self.config.max_memory_mb,
|
||||
"max_cpu_percent": self.config.max_cpu_percent,
|
||||
"timeout_seconds": self.config.timeout_seconds,
|
||||
},
|
||||
"security": {
|
||||
"allowed_commands": self.config.allowed_commands,
|
||||
"denied_commands": self.config.denied_commands,
|
||||
},
|
||||
"version": "2",
|
||||
"agents": {
|
||||
"root": {
|
||||
"description": self.config.description or self.config.name,
|
||||
"instruction": self.config.system_prompt,
|
||||
"model": model_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
with open(path, "w") as f:
|
||||
@@ -368,6 +541,10 @@ class AgentManager:
|
||||
agent_type: Optional[AgentType] = None,
|
||||
) -> list[Agent]:
|
||||
"""List agents with optional filtering."""
|
||||
# Update all agent statuses first
|
||||
for agent in self.agents.values():
|
||||
agent.update_status()
|
||||
|
||||
agents = list(self.agents.values())
|
||||
|
||||
if status:
|
||||
|
||||
@@ -51,7 +51,13 @@ class DebaiApplication(Adw.Application):
|
||||
def do_activate(self):
|
||||
"""Called when the application is activated."""
|
||||
if not self.window:
|
||||
self.window = DebaiWindow(application=self)
|
||||
self.window = DebaiWindow(
|
||||
application=self,
|
||||
agent_manager=self.agent_manager,
|
||||
model_manager=self.model_manager,
|
||||
task_manager=self.task_manager,
|
||||
resource_monitor=self.resource_monitor
|
||||
)
|
||||
self.window.present()
|
||||
|
||||
def do_startup(self):
|
||||
@@ -142,6 +148,14 @@ class DebaiApplication(Adw.Application):
|
||||
padding: 8px 12px;
|
||||
margin: 4px 12px 4px 48px;
|
||||
}
|
||||
|
||||
.user-message {
|
||||
background: alpha(@accent_bg_color, 0.15);
|
||||
}
|
||||
|
||||
.assistant-message {
|
||||
background: alpha(@card_bg_color, 0.9);
|
||||
}
|
||||
"""
|
||||
|
||||
provider = Gtk.CssProvider()
|
||||
@@ -200,12 +214,221 @@ class DebaiApplication(Adw.Application):
|
||||
self.window.show_new_task_dialog()
|
||||
|
||||
|
||||
class ChatWindow(Adw.Window):
|
||||
"""Chat window for interacting with an agent."""
|
||||
|
||||
def __init__(self, agent, agent_manager, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.agent = agent
|
||||
self.agent_manager = agent_manager
|
||||
self.messages = []
|
||||
|
||||
# Window setup
|
||||
self.set_title(f"Chat: {agent.name}")
|
||||
self.set_default_size(700, 600)
|
||||
|
||||
# Main box
|
||||
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
|
||||
self.set_content(main_box)
|
||||
|
||||
# Header bar
|
||||
header = Adw.HeaderBar()
|
||||
main_box.append(header)
|
||||
|
||||
# Agent info
|
||||
info_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
|
||||
info_box.set_margin_start(12)
|
||||
info_box.set_margin_end(12)
|
||||
info_box.set_margin_top(8)
|
||||
info_box.set_margin_bottom(8)
|
||||
|
||||
status_icon = Gtk.Image.new_from_icon_name("emblem-ok-symbolic")
|
||||
status_icon.add_css_class("success")
|
||||
info_box.append(status_icon)
|
||||
|
||||
agent_label = Gtk.Label(label=f"{agent.name} ({agent.config.model_id})")
|
||||
agent_label.add_css_class("caption")
|
||||
info_box.append(agent_label)
|
||||
|
||||
main_box.append(info_box)
|
||||
|
||||
# Separator
|
||||
main_box.append(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL))
|
||||
|
||||
# Chat area
|
||||
scrolled = Gtk.ScrolledWindow()
|
||||
scrolled.set_vexpand(True)
|
||||
scrolled.set_hexpand(True)
|
||||
main_box.append(scrolled)
|
||||
|
||||
self.chat_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
|
||||
self.chat_box.set_margin_start(12)
|
||||
self.chat_box.set_margin_end(12)
|
||||
self.chat_box.set_margin_top(12)
|
||||
self.chat_box.set_margin_bottom(12)
|
||||
scrolled.set_child(self.chat_box)
|
||||
|
||||
# Store scrolled window for auto-scroll
|
||||
self.scrolled = scrolled
|
||||
|
||||
# Welcome message
|
||||
welcome_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
|
||||
welcome_box.set_halign(Gtk.Align.CENTER)
|
||||
welcome_box.set_valign(Gtk.Align.CENTER)
|
||||
|
||||
welcome_icon = Gtk.Image.new_from_icon_name("user-available-symbolic")
|
||||
welcome_icon.set_pixel_size(48)
|
||||
welcome_icon.add_css_class("dim-label")
|
||||
welcome_box.append(welcome_icon)
|
||||
|
||||
welcome_label = Gtk.Label(label=f"Chat with {agent.name}")
|
||||
welcome_label.add_css_class("title-2")
|
||||
welcome_box.append(welcome_label)
|
||||
|
||||
welcome_subtitle = Gtk.Label(label="Ask questions or give commands to the agent")
|
||||
welcome_subtitle.add_css_class("dim-label")
|
||||
welcome_box.append(welcome_subtitle)
|
||||
|
||||
self.chat_box.append(welcome_box)
|
||||
|
||||
# Input area
|
||||
input_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
|
||||
input_box.set_margin_start(12)
|
||||
input_box.set_margin_end(12)
|
||||
input_box.set_margin_top(8)
|
||||
input_box.set_margin_bottom(12)
|
||||
main_box.append(input_box)
|
||||
|
||||
# Message entry
|
||||
self.message_entry = Gtk.Entry()
|
||||
self.message_entry.set_placeholder_text("Type your message...")
|
||||
self.message_entry.set_hexpand(True)
|
||||
self.message_entry.connect("activate", self._on_send_message)
|
||||
input_box.append(self.message_entry)
|
||||
|
||||
# Send button
|
||||
send_btn = Gtk.Button(label="Send")
|
||||
send_btn.add_css_class("suggested-action")
|
||||
send_btn.connect("clicked", self._on_send_message)
|
||||
input_box.append(send_btn)
|
||||
|
||||
# Store send button to enable/disable
|
||||
self.send_btn = send_btn
|
||||
|
||||
def _add_message(self, role, content):
|
||||
"""Add a message to the chat."""
|
||||
message_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
|
||||
message_box.set_halign(Gtk.Align.START if role == "assistant" else Gtk.Align.END)
|
||||
|
||||
# Message bubble
|
||||
bubble = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4)
|
||||
bubble.add_css_class("card")
|
||||
bubble.set_margin_start(8)
|
||||
bubble.set_margin_end(8)
|
||||
bubble.set_margin_top(4)
|
||||
bubble.set_margin_bottom(4)
|
||||
|
||||
if role == "user":
|
||||
bubble.add_css_class("user-message")
|
||||
else:
|
||||
bubble.add_css_class("assistant-message")
|
||||
|
||||
# Role label
|
||||
role_label = Gtk.Label(label="You" if role == "user" else self.agent.name)
|
||||
role_label.add_css_class("caption")
|
||||
role_label.add_css_class("dim-label")
|
||||
role_label.set_halign(Gtk.Align.START)
|
||||
role_label.set_margin_start(12)
|
||||
role_label.set_margin_end(12)
|
||||
role_label.set_margin_top(8)
|
||||
bubble.append(role_label)
|
||||
|
||||
# Message content
|
||||
content_label = Gtk.Label(label=content)
|
||||
content_label.set_wrap(True)
|
||||
content_label.set_wrap_mode(Gtk.WrapMode.WORD_CHAR)
|
||||
content_label.set_max_width_chars(60)
|
||||
content_label.set_halign(Gtk.Align.START)
|
||||
content_label.set_margin_start(12)
|
||||
content_label.set_margin_end(12)
|
||||
content_label.set_margin_bottom(8)
|
||||
content_label.set_selectable(True)
|
||||
bubble.append(content_label)
|
||||
|
||||
message_box.append(bubble)
|
||||
self.chat_box.append(message_box)
|
||||
|
||||
# Auto-scroll to bottom
|
||||
GLib.idle_add(self._scroll_to_bottom)
|
||||
|
||||
def _scroll_to_bottom(self):
|
||||
"""Scroll chat to bottom."""
|
||||
adj = self.scrolled.get_vadjustment()
|
||||
adj.set_value(adj.get_upper() - adj.get_page_size())
|
||||
|
||||
def _on_send_message(self, widget):
|
||||
"""Handle send message."""
|
||||
message = self.message_entry.get_text().strip()
|
||||
if not message:
|
||||
return
|
||||
|
||||
# Clear input
|
||||
self.message_entry.set_text("")
|
||||
|
||||
# Disable input while processing
|
||||
self.message_entry.set_sensitive(False)
|
||||
self.send_btn.set_sensitive(False)
|
||||
|
||||
# Add user message
|
||||
self._add_message("user", message)
|
||||
|
||||
# Send to agent in background thread
|
||||
def send_async():
|
||||
try:
|
||||
response = asyncio.run(self.agent.send_message(message))
|
||||
|
||||
if response:
|
||||
GLib.idle_add(self._handle_response, response.content)
|
||||
else:
|
||||
GLib.idle_add(self._handle_error, "No response from agent")
|
||||
except Exception as e:
|
||||
GLib.idle_add(self._handle_error, str(e))
|
||||
|
||||
thread = threading.Thread(target=send_async, daemon=True)
|
||||
thread.start()
|
||||
|
||||
def _handle_response(self, content):
|
||||
"""Handle agent response."""
|
||||
self._add_message("assistant", content)
|
||||
|
||||
# Re-enable input
|
||||
self.message_entry.set_sensitive(True)
|
||||
self.send_btn.set_sensitive(True)
|
||||
self.message_entry.grab_focus()
|
||||
|
||||
def _handle_error(self, error_msg):
|
||||
"""Handle error."""
|
||||
self._add_message("assistant", f"Error: {error_msg}")
|
||||
|
||||
# Re-enable input
|
||||
self.message_entry.set_sensitive(True)
|
||||
self.send_btn.set_sensitive(True)
|
||||
self.message_entry.grab_focus()
|
||||
|
||||
|
||||
class DebaiWindow(Adw.ApplicationWindow):
|
||||
"""Main application window."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
def __init__(self, agent_manager=None, model_manager=None, task_manager=None, resource_monitor=None, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# Store managers
|
||||
self.agent_manager = agent_manager or AgentManager()
|
||||
self.model_manager = model_manager or ModelManager()
|
||||
self.task_manager = task_manager or TaskManager()
|
||||
self.resource_monitor = resource_monitor or ResourceMonitor()
|
||||
|
||||
self.set_title("Debai")
|
||||
self.set_default_size(1400, 900)
|
||||
self.set_icon_name("debai")
|
||||
@@ -385,13 +608,12 @@ class DebaiWindow(Adw.ApplicationWindow):
|
||||
metrics_box.append(self.memory_metric)
|
||||
|
||||
# Agents metric
|
||||
app = self.get_application()
|
||||
agent_count = len(app.agent_manager.list_agents()) if app else 0
|
||||
agent_count = len(self.agent_manager.list_agents())
|
||||
self.agents_metric = self._create_metric_card("Agents", str(agent_count), "system-users-symbolic")
|
||||
metrics_box.append(self.agents_metric)
|
||||
|
||||
# Tasks metric
|
||||
task_count = len(app.task_manager.list_tasks()) if app else 0
|
||||
task_count = len(self.task_manager.list_tasks()) if self.task_manager else 0
|
||||
self.tasks_metric = self._create_metric_card("Tasks", str(task_count), "view-list-symbolic")
|
||||
metrics_box.append(self.tasks_metric)
|
||||
|
||||
@@ -487,6 +709,26 @@ class DebaiWindow(Adw.ApplicationWindow):
|
||||
|
||||
return card
|
||||
|
||||
def _update_dashboard_metrics(self):
|
||||
"""Update dashboard metrics with current values."""
|
||||
app = self.get_application()
|
||||
if not app:
|
||||
return
|
||||
|
||||
# Update agents count
|
||||
agent_count = len(self.agent_manager.list_agents())
|
||||
for child in self.agents_metric:
|
||||
if isinstance(child, Gtk.Label) and "metric-value" in child.get_css_classes():
|
||||
child.set_label(str(agent_count))
|
||||
break
|
||||
|
||||
# Update tasks count
|
||||
task_count = len(self.task_manager.list_tasks())
|
||||
for child in self.tasks_metric:
|
||||
if isinstance(child, Gtk.Label) and "metric-value" in child.get_css_classes():
|
||||
child.set_label(str(task_count))
|
||||
break
|
||||
|
||||
def _create_agents_page(self) -> Gtk.Widget:
|
||||
"""Create the agents page."""
|
||||
page = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
@@ -549,11 +791,9 @@ class DebaiWindow(Adw.ApplicationWindow):
|
||||
break
|
||||
self.agents_list.remove(row)
|
||||
|
||||
app = self.get_application()
|
||||
if not app:
|
||||
return
|
||||
|
||||
agents = app.agent_manager.list_agents()
|
||||
# Load agents from manager
|
||||
self.agent_manager.load_agents()
|
||||
agents = self.agent_manager.list_agents()
|
||||
|
||||
if not agents:
|
||||
# Empty state
|
||||
@@ -958,8 +1198,7 @@ class DebaiWindow(Adw.ApplicationWindow):
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
app = self.get_application()
|
||||
success = loop.run_until_complete(app.agent_manager.start_agent(agent_id))
|
||||
success = loop.run_until_complete(self.agent_manager.start_agent(agent_id))
|
||||
if success:
|
||||
GLib.idle_add(self._show_agent_started, agent_id)
|
||||
else:
|
||||
@@ -981,8 +1220,7 @@ class DebaiWindow(Adw.ApplicationWindow):
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
app = self.get_application()
|
||||
success = loop.run_until_complete(app.agent_manager.stop_agent(agent_id))
|
||||
success = loop.run_until_complete(self.agent_manager.stop_agent(agent_id))
|
||||
if success:
|
||||
GLib.idle_add(self._show_agent_stopped, agent_id)
|
||||
else:
|
||||
@@ -999,8 +1237,7 @@ class DebaiWindow(Adw.ApplicationWindow):
|
||||
def _on_delete_agent(self, button):
|
||||
"""Delete an agent."""
|
||||
agent_id = button.agent_id
|
||||
app = self.get_application()
|
||||
agent = app.agent_manager.get_agent(agent_id)
|
||||
agent = self.agent_manager.get_agent(agent_id)
|
||||
|
||||
if not agent:
|
||||
return
|
||||
@@ -1022,8 +1259,7 @@ class DebaiWindow(Adw.ApplicationWindow):
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
app = self.get_application()
|
||||
success = loop.run_until_complete(app.agent_manager.delete_agent(agent_id))
|
||||
success = loop.run_until_complete(self.agent_manager.delete_agent(agent_id))
|
||||
if success:
|
||||
GLib.idle_add(self._show_agent_deleted, agent_id)
|
||||
else:
|
||||
@@ -1040,14 +1276,34 @@ class DebaiWindow(Adw.ApplicationWindow):
|
||||
def _on_chat_agent(self, button):
|
||||
"""Open chat with agent."""
|
||||
agent_id = button.agent_id
|
||||
# TODO: Implement chat interface
|
||||
dialog = Adw.MessageDialog(
|
||||
transient_for=self,
|
||||
heading="Chat",
|
||||
body="Chat interface coming soon!",
|
||||
)
|
||||
dialog.add_response("ok", "OK")
|
||||
dialog.present()
|
||||
agent = self.agent_manager.get_agent(agent_id)
|
||||
|
||||
if not agent:
|
||||
dialog = Adw.MessageDialog(
|
||||
transient_for=self,
|
||||
heading="Error",
|
||||
body="Agent not found",
|
||||
)
|
||||
dialog.add_response("ok", "OK")
|
||||
dialog.present()
|
||||
return
|
||||
|
||||
# Check if agent is running
|
||||
agent.update_status()
|
||||
if not agent.is_alive():
|
||||
dialog = Adw.MessageDialog(
|
||||
transient_for=self,
|
||||
heading="Agent Not Running",
|
||||
body=f"Agent '{agent.name}' is not running. Please start it first.",
|
||||
)
|
||||
dialog.add_response("ok", "OK")
|
||||
dialog.present()
|
||||
return
|
||||
|
||||
# Open chat window
|
||||
chat_window = ChatWindow(agent=agent, agent_manager=self.agent_manager)
|
||||
chat_window.set_transient_for(self)
|
||||
chat_window.present()
|
||||
|
||||
def _show_agent_started(self, agent_id):
|
||||
"""Show agent started notification and refresh."""
|
||||
@@ -1077,8 +1333,7 @@ class DebaiWindow(Adw.ApplicationWindow):
|
||||
|
||||
def show_new_agent_dialog(self):
|
||||
"""Show dialog to create a new agent."""
|
||||
app = self.get_application()
|
||||
dialog = NewAgentDialog(transient_for=self, agent_manager=app.agent_manager)
|
||||
dialog = NewAgentDialog(transient_for=self, agent_manager=self.agent_manager, main_window=self)
|
||||
dialog.present()
|
||||
|
||||
def show_new_task_dialog(self):
|
||||
@@ -1095,10 +1350,11 @@ class DebaiWindow(Adw.ApplicationWindow):
|
||||
class NewAgentDialog(Adw.Window):
|
||||
"""Dialog for creating a new agent."""
|
||||
|
||||
def __init__(self, agent_manager: AgentManager, **kwargs):
|
||||
def __init__(self, agent_manager: AgentManager, main_window, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.agent_manager = agent_manager
|
||||
self.main_window = main_window
|
||||
self.set_title("Create Agent")
|
||||
self.set_default_size(500, 600)
|
||||
self.set_modal(True)
|
||||
@@ -1278,6 +1534,11 @@ class NewAgentDialog(Adw.Window):
|
||||
|
||||
def _show_success(self, name):
|
||||
"""Show success dialog."""
|
||||
# Refresh the agents list in the main window
|
||||
if self.main_window:
|
||||
self.main_window._refresh_agents()
|
||||
self.main_window._update_dashboard_metrics()
|
||||
|
||||
success_dialog = Adw.MessageDialog(
|
||||
transient_for=self,
|
||||
heading="Success",
|
||||
|
||||
Referencia en una nueva incidencia
Block a user