theater_cli/tui/
app.rs

1use chrono::{DateTime, Utc};
2use std::collections::VecDeque;
3use theater::ChainEvent;
4use theater_server::ManagementResponse;
5
6#[derive(Debug, Clone)]
7pub struct TuiApp {
8    // Core state
9    pub actor_id: String,
10    pub manifest_path: String,
11    pub start_time: DateTime<Utc>,
12
13    // Event tracking
14    pub events: VecDeque<DisplayEvent>,
15    pub max_events: usize,
16    pub auto_scroll: bool,
17
18    // Lifecycle tracking
19    pub lifecycle_events: Vec<LifecycleEvent>,
20    pub current_status: ActorStatus,
21    pub error_count: usize,
22    pub event_count: usize,
23
24    // UI state
25    pub should_quit: bool,
26    pub paused: bool,
27}
28
29#[derive(Debug, Clone)]
30pub struct DisplayEvent {
31    pub timestamp: DateTime<Utc>,
32    pub event_type: String,
33    pub message: String,
34    pub details: Option<String>,
35    pub level: EventLevel,
36}
37
38#[derive(Debug, Clone)]
39pub struct LifecycleEvent {
40    pub timestamp: DateTime<Utc>,
41    pub event_type: LifecycleEventType,
42    pub message: String,
43}
44
45#[derive(Debug, Clone)]
46pub enum LifecycleEventType {
47    ActorStarted,
48    ActorStopped,
49    ActorError,
50    ActorResult,
51    StatusUpdate,
52}
53
54#[derive(Debug, Clone)]
55pub enum EventLevel {
56    Info,
57    Warning,
58    Error,
59}
60
61#[derive(Debug, Clone)]
62pub enum ActorStatus {
63    Starting,
64    Running,
65    Paused,
66    Stopped,
67    Error,
68}
69
70impl TuiApp {
71    pub fn new(actor_id: String, manifest_path: String) -> Self {
72        Self {
73            actor_id,
74            manifest_path,
75            start_time: Utc::now(),
76            events: VecDeque::new(),
77            max_events: 1000,
78            auto_scroll: true,
79            lifecycle_events: Vec::new(),
80            current_status: ActorStatus::Starting,
81            error_count: 0,
82            event_count: 0,
83            should_quit: false,
84            paused: false,
85        }
86    }
87
88    pub fn add_event(&mut self, event: DisplayEvent) {
89        if !self.paused {
90            self.event_count += 1;
91
92            if event.level == EventLevel::Error {
93                self.error_count += 1;
94            }
95
96            self.events.push_back(event);
97
98            // Keep only the last max_events
99            while self.events.len() > self.max_events {
100                self.events.pop_front();
101            }
102        }
103    }
104
105    pub fn add_lifecycle_event(&mut self, event: LifecycleEvent) {
106        match &event.event_type {
107            LifecycleEventType::ActorStarted => {
108                self.current_status = ActorStatus::Running;
109            }
110            LifecycleEventType::ActorStopped => {
111                self.current_status = ActorStatus::Stopped;
112            }
113            LifecycleEventType::ActorError => {
114                self.current_status = ActorStatus::Error;
115                self.error_count += 1;
116            }
117            _ => {}
118        }
119
120        self.lifecycle_events.push(event);
121
122        // Keep only the last 50 lifecycle events
123        if self.lifecycle_events.len() > 50 {
124            self.lifecycle_events.remove(0);
125        }
126    }
127
128    pub fn handle_management_response(&mut self, response: ManagementResponse) {
129        let timestamp = Utc::now();
130
131        match response {
132            ManagementResponse::ActorStarted { id } => {
133                let lifecycle_event = LifecycleEvent {
134                    timestamp,
135                    event_type: LifecycleEventType::ActorStarted,
136                    message: format!("Actor started with ID: {}", id),
137                };
138                self.add_lifecycle_event(lifecycle_event);
139            }
140            ManagementResponse::ActorEvent { event } => {
141                let display_event = self.chain_event_to_display_event(event, timestamp);
142                self.add_event(display_event);
143            }
144            ManagementResponse::ActorError { error } => {
145                let lifecycle_event = LifecycleEvent {
146                    timestamp,
147                    event_type: LifecycleEventType::ActorError,
148                    message: format!("Actor error: {}", error),
149                };
150                self.add_lifecycle_event(lifecycle_event);
151            }
152            ManagementResponse::ActorStopped { id } => {
153                let lifecycle_event = LifecycleEvent {
154                    timestamp,
155                    event_type: LifecycleEventType::ActorStopped,
156                    message: format!("Actor stopped: {}", id),
157                };
158                self.add_lifecycle_event(lifecycle_event);
159            }
160            ManagementResponse::ActorResult(result) => {
161                let lifecycle_event = LifecycleEvent {
162                    timestamp,
163                    event_type: LifecycleEventType::ActorResult,
164                    message: format!("Actor result: {}", result),
165                };
166                self.add_lifecycle_event(lifecycle_event);
167            }
168            _ => {
169                // Handle other response types if needed
170            }
171        }
172    }
173
174    fn chain_event_to_display_event(
175        &self,
176        event: ChainEvent,
177        timestamp: DateTime<Utc>,
178    ) -> DisplayEvent {
179        let description = event.description.as_deref().unwrap_or("Unknown event");
180        let level = if description.contains("error") {
181            EventLevel::Error
182        } else if description.contains("warn") {
183            EventLevel::Warning
184        } else {
185            EventLevel::Info
186        };
187
188        DisplayEvent {
189            timestamp,
190            event_type: event.event_type.clone(),
191            message: description.to_string(),
192            details: Some(format!("{:?}", event.data)),
193            level,
194        }
195    }
196
197    pub fn toggle_pause(&mut self) {
198        self.paused = !self.paused;
199    }
200
201    pub fn toggle_auto_scroll(&mut self) {
202        self.auto_scroll = !self.auto_scroll;
203    }
204
205    pub fn quit(&mut self) {
206        self.should_quit = true;
207    }
208
209    pub fn reset_events(&mut self) {
210        self.events.clear();
211        self.event_count = 0;
212    }
213}
214
215impl PartialEq for EventLevel {
216    fn eq(&self, other: &Self) -> bool {
217        matches!(
218            (self, other),
219            (EventLevel::Info, EventLevel::Info)
220                | (EventLevel::Warning, EventLevel::Warning)
221                | (EventLevel::Error, EventLevel::Error)
222        )
223    }
224}