use super::{AgentPanel, NotificationManager, NotificationType, Notification};
use agcodex_core::subagents::{SubagentExecution, SubagentStatus};
use crossterm::event::{self, Event, KeyCode, KeyEvent};
use ratatui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
terminal::Frame,
widgets::{Block, Borders, Paragraph},
};
use std::sync::Arc;
use std::time::Duration;
use uuid::Uuid;
pub struct AppWithAgents {
pub agent_panel: AgentPanel,
pub notification_manager: NotificationManager,
pub content: String,
pub show_help: bool,
}
impl AppWithAgents {
pub fn new() -> Self {
Self {
agent_panel: AgentPanel::new(),
notification_manager: NotificationManager::new()
.with_position(super::NotificationPosition::BottomRight)
.with_max_visible(3),
content: String::from("AGCodex TUI with Agent Management"),
show_help: false,
}
}
pub fn handle_key(&mut self, key: KeyCode) -> bool {
if self.agent_panel.handle_key(key) {
return true;
}
match key {
KeyCode::Char('a') if event::KeyModifiers::CONTROL == event::KeyModifiers::CONTROL => {
self.agent_panel.toggle();
true
}
KeyCode::Char('n') if event::KeyModifiers::CONTROL == event::KeyModifiers::CONTROL => {
self.spawn_test_agent();
true
}
KeyCode::Char('c') if event::KeyModifiers::CONTROL == event::KeyModifiers::CONTROL => {
self.notification_manager.clear_all();
true
}
KeyCode::Char('?') | KeyCode::F(1) => {
self.show_help = !self.show_help;
true
}
_ => false,
}
}
pub fn spawn_test_agent(&mut self) {
let agent_name = format!("agent-{}", uuid::Uuid::new_v4().to_string().split('-').next().unwrap());
let mut execution = SubagentExecution::new(agent_name.clone());
execution.start();
let agent_id = execution.id;
self.agent_panel.add_agent(execution);
self.notification_manager.agent_started(agent_name.clone());
let panel = Arc::new(self.agent_panel.clone());
let notifier = Arc::new(self.notification_manager.clone());
std::thread::spawn(move || {
for i in 1..=10 {
std::thread::sleep(Duration::from_millis(500));
let progress = i as f32 / 10.0;
let message = format!("Processing step {}/10", i);
panel.update_progress(agent_id, progress, message.clone());
if i % 3 == 0 {
notifier.agent_progress(
agent_name.clone(),
progress,
format!("{}% complete", (progress * 100.0) as u32),
);
}
panel.add_output(agent_id, format!("[{}] {}", i, message));
}
panel.complete_agent(agent_id, "Task completed successfully!".to_string());
notifier.agent_completed(
agent_name.clone(),
"All steps processed".to_string(),
);
});
}
pub fn simulate_agent_failure(&mut self) {
let agent_name = "failing-agent";
let mut execution = SubagentExecution::new(agent_name.to_string());
execution.start();
let agent_id = execution.id;
self.agent_panel.add_agent(execution);
self.agent_panel.fail_agent(agent_id, "Connection timeout".to_string());
self.notification_manager.agent_failed(
agent_name.to_string(),
"Failed to connect to remote service".to_string(),
);
}
pub fn tick(&mut self) {
self.agent_panel.tick();
self.notification_manager.tick();
}
pub fn render<B: Backend>(&self, frame: &mut Frame<B>) {
let size = frame.size();
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Min(10), Constraint::Length(3), ])
.split(size);
let content_block = Block::default()
.title(" AGCodex TUI ")
.borders(Borders::ALL);
let content = Paragraph::new(self.content.as_str())
.block(content_block);
frame.render_widget(content, chunks[0]);
let status = if self.show_help {
"Ctrl+A: Agent Panel | Ctrl+N: New Agent | Ctrl+C: Clear Notifications | ?: Help | Esc: Exit"
} else {
"Press ? for help"
};
let status_bar = Paragraph::new(status)
.block(Block::default().borders(Borders::TOP));
frame.render_widget(status_bar, chunks[1]);
if self.agent_panel.is_visible() {
let panel_area = centered_rect(60, 70, size);
frame.render_widget(&self.agent_panel, panel_area);
}
frame.render_widget(&self.notification_manager, size);
}
}
fn centered_rect(percent_x: u16, percent_y: u16, area: Rect) -> Rect {
let popup_layout = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Percentage((100 - percent_y) / 2),
Constraint::Percentage(percent_y),
Constraint::Percentage((100 - percent_y) / 2),
])
.split(area);
Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage((100 - percent_x) / 2),
Constraint::Percentage(percent_x),
Constraint::Percentage((100 - percent_x) / 2),
])
.split(popup_layout[1])[1]
}
pub async fn run_example() -> std::io::Result<()> {
let mut app = AppWithAgents::new();
app.spawn_test_agent();
std::thread::sleep(Duration::from_millis(100));
app.spawn_test_agent();
app.simulate_agent_failure();
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_app_creation() {
let app = AppWithAgents::new();
assert!(!app.agent_panel.is_visible());
assert!(!app.show_help);
}
#[test]
fn test_agent_panel_toggle() {
let mut app = AppWithAgents::new();
assert!(!app.agent_panel.is_visible());
app.agent_panel.toggle();
assert!(app.agent_panel.is_visible());
app.agent_panel.toggle();
assert!(!app.agent_panel.is_visible());
}
#[test]
fn test_spawn_agent() {
let mut app = AppWithAgents::new();
app.spawn_test_agent();
std::thread::sleep(Duration::from_millis(50));
}
#[test]
fn test_notification_manager() {
let app = AppWithAgents::new();
app.notification_manager.agent_started("test-agent".to_string());
app.notification_manager.agent_progress(
"test-agent".to_string(),
0.5,
"50% complete".to_string(),
);
app.notification_manager.agent_completed(
"test-agent".to_string(),
"Success!".to_string(),
);
app.notification_manager.agent_failed(
"test-agent".to_string(),
"Error occurred".to_string(),
);
}
#[test]
fn test_centered_rect() {
let area = Rect {
x: 0,
y: 0,
width: 100,
height: 50,
};
let centered = centered_rect(50, 50, area);
assert_eq!(centered.width, 50);
assert_eq!(centered.height, 25);
assert_eq!(centered.x, 25);
assert_eq!(centered.y, 12);
}
}