agent_core/tui/widgets/mod.rs
1//! TUI Widgets
2//!
3//! Reusable widget components for LLM-powered terminal applications.
4//!
5//! # Core Widgets (always present)
6//! - [`ChatView`] - Chat message display with streaming support
7//! - [`TextInput`] - Text input buffer with cursor management
8//!
9//! # Registerable Widgets
10//! - [`PermissionPanel`] - Permission request panel for tool interactions
11//! - [`QuestionPanel`] - Question panel for user input collection
12//! - [`SessionPickerState`] - Session picker for viewing/switching sessions
13//! - [`SlashPopupState`] - Slash command popup
14//!
15//! # Widget System
16//!
17//! Widgets can be registered with the App via the Widget trait. This allows
18//! agents to customize which widgets are available.
19
20use crossterm::event::KeyEvent;
21use ratatui::{layout::Rect, Frame};
22use std::any::Any;
23
24use crate::controller::{AskUserQuestionsResponse, PermissionResponse};
25use crate::tui::keys::NavigationHelper;
26use crate::tui::themes::Theme;
27
28pub mod chat;
29pub mod chat_helpers;
30pub mod conversation;
31pub mod input;
32pub mod permission_panel;
33pub mod question_panel;
34pub mod session_picker;
35pub mod slash_popup;
36pub mod status_bar;
37
38pub use chat::{ChatView, ChatViewConfig, MessageRole, ToolMessageData, ToolStatus};
39pub use chat_helpers::RenderFn;
40pub use conversation::{ConversationView, ConversationViewFactory};
41pub use input::TextInput;
42pub use permission_panel::{
43 KeyAction as PermissionKeyAction, PermissionOption, PermissionPanel, PermissionPanelConfig,
44};
45pub use question_panel::{
46 AnswerState, EnterAction, FocusItem, KeyAction as QuestionKeyAction, QuestionPanel,
47 QuestionPanelConfig,
48};
49pub use session_picker::{render_session_picker, SessionInfo, SessionPickerConfig, SessionPickerState};
50pub use slash_popup::{
51 render_slash_popup, SimpleCommand, SlashCommandDisplay, SlashPopupConfig,
52 SlashPopupState,
53};
54pub use status_bar::{StatusBar, StatusBarConfig, StatusBarData};
55
56/// Standard widget IDs for built-in widgets
57pub mod widget_ids {
58 // Core widgets (always present)
59 pub const CHAT_VIEW: &str = "chat_view";
60 pub const TEXT_INPUT: &str = "text_input";
61
62 // Registerable widgets
63 pub const PERMISSION_PANEL: &str = "permission_panel";
64 pub const QUESTION_PANEL: &str = "question_panel";
65 pub const SESSION_PICKER: &str = "session_picker";
66 pub const SLASH_POPUP: &str = "slash_popup";
67 pub const THEME_PICKER: &str = "theme_picker";
68 pub const STATUS_BAR: &str = "status_bar";
69}
70
71/// Context provided to widgets when handling key events.
72///
73/// This contains references to the theme and a navigation helper that allows
74/// widgets to respect configured key bindings instead of hardcoding key codes.
75pub struct WidgetKeyContext<'a> {
76 /// Theme for styling.
77 pub theme: &'a Theme,
78 /// Navigation helper for checking key bindings.
79 pub nav: NavigationHelper<'a>,
80}
81
82/// Result of a widget handling a key event
83#[derive(Debug, Clone)]
84pub enum WidgetKeyResult {
85 /// Key was not handled by this widget
86 NotHandled,
87 /// Key was handled, no further action needed
88 Handled,
89 /// Key was handled, and the widget requests an action from App
90 Action(WidgetAction),
91}
92
93/// Actions that widgets can request from the App
94#[derive(Debug, Clone)]
95pub enum WidgetAction {
96 /// Submit a question panel response
97 SubmitQuestion {
98 tool_use_id: String,
99 response: AskUserQuestionsResponse,
100 },
101 /// Cancel a question panel
102 CancelQuestion { tool_use_id: String },
103 /// Submit a permission panel response
104 SubmitPermission {
105 tool_use_id: String,
106 response: PermissionResponse,
107 },
108 /// Cancel a permission panel
109 CancelPermission { tool_use_id: String },
110 /// Switch to a different session
111 SwitchSession { session_id: i64 },
112 /// Execute a slash command
113 ExecuteCommand { command: String },
114 /// Close the widget (theme picker confirm/cancel)
115 Close,
116}
117
118/// Trait for registerable TUI widgets
119///
120/// Widgets that implement this trait can be registered with the App and will
121/// receive key events and rendering opportunities based on their state.
122pub trait Widget: Send + 'static {
123 /// Unique identifier for this widget type
124 fn id(&self) -> &'static str;
125
126 /// Priority for key event handling (higher = checked first)
127 ///
128 /// Modal widgets should have high priority to intercept input.
129 /// Default is 100.
130 fn priority(&self) -> u8 {
131 100
132 }
133
134 /// Whether the widget is currently active/visible
135 fn is_active(&self) -> bool;
136
137 /// Handle key event, return result indicating what action to take.
138 ///
139 /// The `ctx` parameter provides access to the theme and a navigation helper
140 /// that respects configured key bindings.
141 fn handle_key(&mut self, key: KeyEvent, ctx: &WidgetKeyContext) -> WidgetKeyResult;
142
143 /// Render the widget
144 fn render(&mut self, frame: &mut Frame, area: Rect, theme: &Theme);
145
146 /// Calculate required height for this widget
147 ///
148 /// Returns 0 if the widget doesn't need dedicated space.
149 fn required_height(&self, available: u16) -> u16 {
150 let _ = available;
151 0
152 }
153
154 /// Whether this widget blocks input to the text input when active
155 fn blocks_input(&self) -> bool {
156 false
157 }
158
159 /// Whether this widget is a full-screen overlay
160 ///
161 /// Overlay widgets are rendered on top of everything else.
162 fn is_overlay(&self) -> bool {
163 false
164 }
165
166 /// Cast to Any for downcasting
167 fn as_any(&self) -> &dyn Any;
168
169 /// Cast to Any for mutable downcasting
170 fn as_any_mut(&mut self) -> &mut dyn Any;
171
172 /// Convert to `Box<dyn Any>` for owned downcasting
173 fn into_any(self: Box<Self>) -> Box<dyn Any>;
174}