Skip to main content

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