use agcodex_core::config_types::TuiNotifications;
use std::collections::HashSet;
use std::io::Write;
use std::io::{self};
use std::thread;
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NotificationLevel {
TaskComplete,
Error,
Warning,
Info,
}
pub struct NotificationSystem {
config: TuiNotifications,
enabled_levels: HashSet<NotificationLevel>,
sound_enabled: bool,
visual_enabled: bool,
}
pub type NotificationManager = NotificationSystem;
impl NotificationSystem {
pub fn new(config: TuiNotifications) -> Self {
let mut enabled_levels = HashSet::new();
if config.agent_complete {
enabled_levels.insert(NotificationLevel::TaskComplete);
}
if config.agent_failed || config.error_occurred {
enabled_levels.insert(NotificationLevel::Error);
}
if config.user_input_needed || config.warnings {
enabled_levels.insert(NotificationLevel::Warning);
}
if config.info_messages {
enabled_levels.insert(NotificationLevel::Info);
}
let sound_enabled = config.terminal_bell;
let visual_enabled = config.visual_flash;
Self {
config,
enabled_levels,
sound_enabled,
visual_enabled,
}
}
pub fn update_config(&mut self, config: TuiNotifications) {
self.enabled_levels.clear();
if config.agent_complete {
self.enabled_levels.insert(NotificationLevel::TaskComplete);
}
if config.agent_failed || config.error_occurred {
self.enabled_levels.insert(NotificationLevel::Error);
}
if config.user_input_needed || config.warnings {
self.enabled_levels.insert(NotificationLevel::Warning);
}
if config.info_messages {
self.enabled_levels.insert(NotificationLevel::Info);
}
self.sound_enabled = config.terminal_bell;
self.visual_enabled = config.visual_flash;
self.config = config;
}
pub fn set_level_enabled(&mut self, level: NotificationLevel, enabled: bool) {
if enabled {
self.enabled_levels.insert(level);
} else {
self.enabled_levels.remove(&level);
}
}
pub fn is_level_enabled(&self, level: NotificationLevel) -> bool {
self.enabled_levels.contains(&level)
}
pub const fn set_sound_enabled(&mut self, enabled: bool) {
self.sound_enabled = enabled;
}
pub const fn set_visual_enabled(&mut self, enabled: bool) {
self.visual_enabled = enabled;
}
fn ring_bell(&self, level: NotificationLevel) -> io::Result<()> {
if !self.sound_enabled || !self.enabled_levels.contains(&level) {
return Ok(());
}
match level {
NotificationLevel::TaskComplete => {
print!("\x07");
io::stdout().flush()?;
}
NotificationLevel::Error => {
print!("\x07");
io::stdout().flush()?;
thread::sleep(Duration::from_millis(150));
print!("\x07");
io::stdout().flush()?;
}
NotificationLevel::Warning => {
print!("\x07");
io::stdout().flush()?;
}
NotificationLevel::Info => {
}
}
Ok(())
}
fn visual_flash(&self, _level: NotificationLevel) -> io::Result<()> {
if !self.visual_enabled {
return Ok(());
}
print!("\x1B[?5h"); io::stdout().flush()?;
thread::sleep(Duration::from_millis(50));
print!("\x1B[?5l"); io::stdout().flush()?;
Ok(())
}
pub fn notify(&self, level: NotificationLevel) -> io::Result<()> {
if !self.enabled_levels.contains(&level) {
return Ok(());
}
if self.sound_enabled {
self.ring_bell(level)?;
}
if self.visual_enabled && (!self.sound_enabled || level == NotificationLevel::Error) {
self.visual_flash(level)?;
}
Ok(())
}
pub fn notify_with_message(&self, level: NotificationLevel, message: &str) -> io::Result<()> {
#[cfg(debug_assertions)]
eprintln!("[NOTIFICATION {:?}] {}", level, message);
self.notify(level)
}
pub fn agent_completed(&self) -> io::Result<()> {
if self.config.agent_complete {
self.notify(NotificationLevel::TaskComplete)?;
}
Ok(())
}
pub fn agent_completed_with_message(&self, agent_name: &str) -> io::Result<()> {
if self.config.agent_complete {
self.notify_with_message(
NotificationLevel::TaskComplete,
&format!("Agent '{}' completed successfully", agent_name),
)?;
}
Ok(())
}
pub fn agent_failed(&self) -> io::Result<()> {
if self.config.agent_failed {
self.notify(NotificationLevel::Error)?;
}
Ok(())
}
pub fn agent_failed_with_message(&self, agent_name: &str, error: &str) -> io::Result<()> {
if self.config.agent_failed {
self.notify_with_message(
NotificationLevel::Error,
&format!("Agent '{}' failed: {}", agent_name, error),
)?;
}
Ok(())
}
pub fn error_occurred(&self) -> io::Result<()> {
if self.config.error_occurred {
self.notify(NotificationLevel::Error)?;
}
Ok(())
}
pub fn error_occurred_with_message(&self, error: &str) -> io::Result<()> {
if self.config.error_occurred {
self.notify_with_message(NotificationLevel::Error, error)?;
}
Ok(())
}
pub fn user_input_needed(&self) -> io::Result<()> {
if self.config.user_input_needed {
self.notify(NotificationLevel::Warning)?;
}
Ok(())
}
pub fn user_input_needed_with_message(&self, message: &str) -> io::Result<()> {
if self.config.user_input_needed {
self.notify_with_message(NotificationLevel::Warning, message)?;
}
Ok(())
}
pub fn info(&self, message: &str) -> io::Result<()> {
self.notify_with_message(NotificationLevel::Info, message)
}
pub fn warning(&self, message: &str) -> io::Result<()> {
self.notify_with_message(NotificationLevel::Warning, message)
}
pub const fn config(&self) -> &TuiNotifications {
&self.config
}
pub fn has_enabled_notifications(&self) -> bool {
!self.enabled_levels.is_empty() && (self.sound_enabled || self.visual_enabled)
}
}
#[cfg(test)]
mod tests {
use super::*;
use agcodex_core::config_types::TuiNotifications;
#[test]
fn test_notification_system_creation() {
let config = TuiNotifications::default();
let system = NotificationSystem::new(config);
assert!(system.config.terminal_bell);
assert!(system.config.agent_complete);
assert!(system.config.agent_failed);
assert!(system.config.error_occurred);
assert!(system.config.user_input_needed);
assert!(system.sound_enabled);
assert!(system.visual_enabled);
}
#[test]
fn test_disabled_notifications() {
let config = TuiNotifications {
terminal_bell: false,
visual_flash: false,
agent_complete: false,
agent_failed: false,
error_occurred: false,
user_input_needed: false,
warnings: false,
info_messages: false,
};
let system = NotificationSystem::new(config);
assert!(!system.sound_enabled);
assert!(!system.is_level_enabled(NotificationLevel::TaskComplete));
assert!(!system.is_level_enabled(NotificationLevel::Error));
assert!(!system.is_level_enabled(NotificationLevel::Warning));
assert!(system.agent_completed().is_ok());
assert!(system.agent_failed().is_ok());
assert!(system.error_occurred().is_ok());
assert!(system.user_input_needed().is_ok());
}
#[test]
fn test_config_update() {
let initial_config = TuiNotifications::default();
let mut system = NotificationSystem::new(initial_config);
let new_config = TuiNotifications {
terminal_bell: false,
visual_flash: false,
agent_complete: false,
agent_failed: true,
error_occurred: true,
user_input_needed: false,
warnings: false,
info_messages: false,
};
system.update_config(new_config.clone());
assert_eq!(system.config, new_config);
assert!(!system.sound_enabled);
assert!(system.is_level_enabled(NotificationLevel::Error));
assert!(!system.is_level_enabled(NotificationLevel::TaskComplete));
assert!(!system.is_level_enabled(NotificationLevel::Warning));
}
#[test]
fn test_level_management() {
let config = TuiNotifications::default();
let mut system = NotificationSystem::new(config);
system.set_level_enabled(NotificationLevel::TaskComplete, false);
assert!(!system.is_level_enabled(NotificationLevel::TaskComplete));
system.set_level_enabled(NotificationLevel::TaskComplete, true);
assert!(system.is_level_enabled(NotificationLevel::TaskComplete));
}
#[test]
fn test_sound_and_visual_controls() {
let config = TuiNotifications::default();
let mut system = NotificationSystem::new(config);
system.set_sound_enabled(false);
assert!(!system.sound_enabled);
system.set_visual_enabled(false);
assert!(!system.visual_enabled);
assert!(!system.has_enabled_notifications());
}
#[test]
fn test_notification_levels() {
let levels = vec![
NotificationLevel::TaskComplete,
NotificationLevel::Error,
NotificationLevel::Warning,
NotificationLevel::Info,
];
for level in levels {
let mut set = HashSet::new();
set.insert(level);
assert!(set.contains(&level));
}
}
#[test]
fn test_notification_methods_dont_panic() {
let config = TuiNotifications::default();
let system = NotificationSystem::new(config);
assert!(system.notify(NotificationLevel::TaskComplete).is_ok());
assert!(system.notify(NotificationLevel::Error).is_ok());
assert!(system.notify(NotificationLevel::Warning).is_ok());
assert!(system.notify(NotificationLevel::Info).is_ok());
assert!(system.agent_completed_with_message("test-agent").is_ok());
assert!(
system
.agent_failed_with_message("test-agent", "test error")
.is_ok()
);
assert!(system.info("test info").is_ok());
assert!(system.warning("test warning").is_ok());
}
}