1use std::collections::HashMap;
4
5use chrono::{DateTime, Local};
6use ratatui::{
7 Frame,
8 layout::{Alignment, Rect},
9 style::{Color, Style},
10 text::{Line, Span},
11 widgets::{Block, Borders, Padding, Paragraph},
12};
13
14use crate::markdown::{render_markdown_with_prefix, wrap_with_prefix};
15use crate::themes::theme as app_theme;
16
17pub mod defaults {
19 pub const USER_PREFIX: &str = "> ";
21 pub const SYSTEM_PREFIX: &str = "* ";
23 pub const TIMESTAMP_PREFIX: &str = " - ";
25 pub const CONTINUATION: &str = " ";
27 pub const SPINNER_CHARS: &[char] = &[
29 '\u{280B}', '\u{2819}', '\u{2839}', '\u{2838}', '\u{283C}', '\u{2834}', '\u{2826}',
30 '\u{2827}', '\u{2807}', '\u{280F}',
31 ];
32 pub const DEFAULT_TITLE: &str = "Chat";
34 pub const DEFAULT_EMPTY_MESSAGE: &str = " Type a message to start chatting...";
36 pub const TOOL_ICON: &str = "\u{2692}";
38 pub const TOOL_EXECUTING_ARROW: &str = "\u{2192}";
40 pub const TOOL_COMPLETED_CHECKMARK: &str = "\u{2713}";
42 pub const TOOL_FAILED_ICON: &str = "\u{26A0}";
44}
45
46#[derive(Clone)]
58pub struct ChatViewConfig {
59 pub user_prefix: String,
61 pub system_prefix: String,
63 pub timestamp_prefix: String,
65 pub continuation: String,
67 pub spinner_chars: Vec<char>,
69 pub default_title: String,
71 pub empty_message: String,
73 pub tool_icon: String,
75 pub tool_executing_arrow: String,
77 pub tool_completed_checkmark: String,
79 pub tool_failed_icon: String,
81}
82
83impl Default for ChatViewConfig {
84 fn default() -> Self {
85 Self::new()
86 }
87}
88
89impl ChatViewConfig {
90 pub fn new() -> Self {
92 Self {
93 user_prefix: defaults::USER_PREFIX.to_string(),
94 system_prefix: defaults::SYSTEM_PREFIX.to_string(),
95 timestamp_prefix: defaults::TIMESTAMP_PREFIX.to_string(),
96 continuation: defaults::CONTINUATION.to_string(),
97 spinner_chars: defaults::SPINNER_CHARS.to_vec(),
98 default_title: defaults::DEFAULT_TITLE.to_string(),
99 empty_message: defaults::DEFAULT_EMPTY_MESSAGE.to_string(),
100 tool_icon: defaults::TOOL_ICON.to_string(),
101 tool_executing_arrow: defaults::TOOL_EXECUTING_ARROW.to_string(),
102 tool_completed_checkmark: defaults::TOOL_COMPLETED_CHECKMARK.to_string(),
103 tool_failed_icon: defaults::TOOL_FAILED_ICON.to_string(),
104 }
105 }
106
107 pub fn with_user_prefix(mut self, prefix: impl Into<String>) -> Self {
109 self.user_prefix = prefix.into();
110 self
111 }
112
113 pub fn with_system_prefix(mut self, prefix: impl Into<String>) -> Self {
115 self.system_prefix = prefix.into();
116 self
117 }
118
119 pub fn with_timestamp_prefix(mut self, prefix: impl Into<String>) -> Self {
121 self.timestamp_prefix = prefix.into();
122 self
123 }
124
125 pub fn with_continuation(mut self, continuation: impl Into<String>) -> Self {
127 self.continuation = continuation.into();
128 self
129 }
130
131 pub fn with_spinner_chars(mut self, chars: &[char]) -> Self {
133 self.spinner_chars = chars.to_vec();
134 self
135 }
136
137 pub fn with_default_title(mut self, title: impl Into<String>) -> Self {
139 self.default_title = title.into();
140 self
141 }
142
143 pub fn with_empty_message(mut self, message: impl Into<String>) -> Self {
145 self.empty_message = message.into();
146 self
147 }
148
149 pub fn with_tool_icon(mut self, icon: impl Into<String>) -> Self {
151 self.tool_icon = icon.into();
152 self
153 }
154
155 pub fn with_tool_status_icons(
157 mut self,
158 executing_arrow: impl Into<String>,
159 completed_checkmark: impl Into<String>,
160 failed_icon: impl Into<String>,
161 ) -> Self {
162 self.tool_executing_arrow = executing_arrow.into();
163 self.tool_completed_checkmark = completed_checkmark.into();
164 self.tool_failed_icon = failed_icon.into();
165 self
166 }
167}
168
169#[derive(Debug, Clone, Copy, PartialEq)]
171pub enum MessageRole {
172 User,
173 Assistant,
174 System,
175 Tool,
176}
177
178#[derive(Debug, Clone, PartialEq)]
180pub enum ToolStatus {
181 Executing,
182 WaitingForUser,
183 Completed,
184 Failed(String),
185}
186
187#[derive(Debug, Clone)]
190pub struct ToolMessageData {
191 #[allow(dead_code)]
194 pub tool_use_id: String,
195 pub display_name: String,
197 pub display_title: String,
199 pub status: ToolStatus,
201}
202
203struct Message {
204 role: MessageRole,
205 content: String,
206 timestamp: DateTime<Local>,
207 cached_lines: Option<Vec<Line<'static>>>,
209 cached_width: usize,
211 tool_data: Option<ToolMessageData>,
213}
214
215impl Message {
216 fn new(role: MessageRole, content: String) -> Self {
217 Self {
218 role,
219 content,
220 timestamp: Local::now(),
221 cached_lines: None,
222 cached_width: 0,
223 tool_data: None,
224 }
225 }
226
227 fn new_tool(tool_data: ToolMessageData) -> Self {
228 Self {
229 role: MessageRole::Tool,
230 content: String::new(),
231 timestamp: Local::now(),
232 cached_lines: None,
233 cached_width: 0,
234 tool_data: Some(tool_data),
235 }
236 }
237
238 fn get_rendered_lines(
240 &mut self,
241 available_width: usize,
242 config: &ChatViewConfig,
243 ) -> &[Line<'static>] {
244 if self.cached_width != available_width {
246 self.cached_lines = None;
247 }
248
249 if self.cached_lines.is_none() {
251 let lines = self.render_lines(available_width, config);
252 self.cached_lines = Some(lines);
253 self.cached_width = available_width;
254 }
255
256 self.cached_lines.as_ref().unwrap()
257 }
258
259 fn render_lines(&self, available_width: usize, config: &ChatViewConfig) -> Vec<Line<'static>> {
261 let mut lines = Vec::new();
262 let t = app_theme();
263
264 match self.role {
265 MessageRole::User => {
266 let rendered = wrap_with_prefix(
267 &self.content,
268 &config.user_prefix,
269 t.user_prefix,
270 &config.continuation,
271 available_width,
272 &t,
273 );
274 lines.extend(rendered);
275 }
276 MessageRole::System => {
277 let rendered = wrap_with_prefix(
278 &self.content,
279 &config.system_prefix,
280 t.system_prefix,
281 &config.continuation,
282 available_width,
283 &t,
284 );
285 lines.extend(rendered);
286 }
287 MessageRole::Assistant => {
288 let rendered = render_markdown_with_prefix(&self.content, available_width, &t);
289 lines.extend(rendered);
290 }
291 MessageRole::Tool => {
292 if let Some(ref data) = self.tool_data {
293 lines.extend(render_tool_message(data, config, available_width));
294 }
295 }
296 }
297
298 if self.role != MessageRole::Assistant && self.role != MessageRole::Tool {
301 let time_str = self.timestamp.format("%I:%M:%S %p").to_string();
302 let timestamp_text = format!("{}{}", config.timestamp_prefix, time_str);
303 lines.push(Line::from(vec![Span::styled(
304 timestamp_text,
305 app_theme().timestamp,
306 )]));
307 }
308
309 lines.push(Line::from(""));
311
312 lines
313 }
314}
315
316pub use super::chat_helpers::RenderFn;
318
319use crate::themes::Theme;
320
321pub type TitleRenderFn = Box<dyn Fn(&str, &Theme) -> (Line<'static>, Line<'static>) + Send + Sync>;
324
325pub struct ChatView {
327 messages: Vec<Message>,
328 scroll_offset: u16,
329 streaming_buffer: Option<String>,
331 streaming_cache: Option<Vec<Line<'static>>>,
333 streaming_cache_len: usize,
335 streaming_cache_width: usize,
337 last_max_scroll: u16,
339 auto_scroll_enabled: bool,
341 tool_index: HashMap<String, usize>,
343 spinner_index: usize,
345 title: String,
347 render_initial_content: Option<RenderFn>,
349 render_title: Option<TitleRenderFn>,
351 config: ChatViewConfig,
353}
354
355impl ChatView {
356 pub fn new() -> Self {
358 Self::with_config(ChatViewConfig::new())
359 }
360
361 pub fn with_config(config: ChatViewConfig) -> Self {
363 let title = config.default_title.clone();
364 Self {
365 messages: Vec::new(),
366 scroll_offset: 0,
367 streaming_buffer: None,
368 streaming_cache: None,
369 streaming_cache_len: 0,
370 streaming_cache_width: 0,
371 last_max_scroll: 0,
372 auto_scroll_enabled: true,
373 tool_index: HashMap::new(),
374 spinner_index: 0,
375 title,
376 render_initial_content: None,
377 render_title: None,
378 config,
379 }
380 }
381
382 pub fn config(&self) -> &ChatViewConfig {
384 &self.config
385 }
386
387 pub fn set_config(&mut self, config: ChatViewConfig) {
389 self.config = config;
390 for msg in &mut self.messages {
392 msg.cached_lines = None;
393 }
394 }
395
396 pub fn with_title(mut self, title: impl Into<String>) -> Self {
398 self.title = title.into();
399 self
400 }
401
402 pub fn with_initial_content(mut self, render: RenderFn) -> Self {
407 self.render_initial_content = Some(render);
408 self
409 }
410
411 pub fn with_title_renderer<F>(mut self, render: F) -> Self
416 where
417 F: Fn(&str, &Theme) -> (Line<'static>, Line<'static>) + Send + Sync + 'static,
418 {
419 self.render_title = Some(Box::new(render));
420 self
421 }
422
423 pub fn set_title(&mut self, title: impl Into<String>) {
425 self.title = title.into();
426 }
427
428 pub fn title(&self) -> &str {
430 &self.title
431 }
432
433 pub fn step_spinner(&mut self) {
435 let len = self.config.spinner_chars.len().max(1);
436 self.spinner_index = (self.spinner_index + 1) % len;
437 }
438
439 pub fn add_user_message(&mut self, content: String) {
441 if !content.trim().is_empty() {
442 self.messages.push(Message::new(MessageRole::User, content));
443 if self.auto_scroll_enabled {
445 self.scroll_offset = u16::MAX;
446 }
447 }
448 }
449
450 pub fn add_assistant_message(&mut self, content: String) {
452 if !content.trim().is_empty() {
453 self.messages
454 .push(Message::new(MessageRole::Assistant, content));
455 if self.auto_scroll_enabled {
457 self.scroll_offset = u16::MAX;
458 }
459 }
460 }
461
462 pub fn add_system_message(&mut self, content: String) {
464 if content.trim().is_empty() {
465 return;
466 }
467 self.messages
468 .push(Message::new(MessageRole::System, content));
469 if self.auto_scroll_enabled {
471 self.scroll_offset = u16::MAX;
472 }
473 }
474
475 pub fn add_tool_message(&mut self, tool_use_id: &str, display_name: &str, display_title: &str) {
477 let index = self.messages.len();
478
479 let tool_data = ToolMessageData {
480 tool_use_id: tool_use_id.to_string(),
481 display_name: display_name.to_string(),
482 display_title: display_title.to_string(),
483 status: ToolStatus::Executing,
484 };
485
486 self.messages.push(Message::new_tool(tool_data));
487 self.tool_index.insert(tool_use_id.to_string(), index);
488
489 if self.auto_scroll_enabled {
491 self.scroll_offset = u16::MAX;
492 }
493 }
494
495 pub fn update_tool_status(&mut self, tool_use_id: &str, status: ToolStatus) {
497 if let Some(&index) = self.tool_index.get(tool_use_id)
498 && let Some(msg) = self.messages.get_mut(index)
499 && let Some(ref mut data) = msg.tool_data
500 {
501 data.status = status;
502 msg.cached_lines = None; }
504 }
505
506 pub fn enable_auto_scroll(&mut self) {
508 self.auto_scroll_enabled = true;
509 self.scroll_offset = u16::MAX;
510 }
511
512 pub fn append_streaming(&mut self, text: &str) {
514 match &mut self.streaming_buffer {
515 Some(buffer) => buffer.push_str(text),
516 None => self.streaming_buffer = Some(text.to_string()),
517 }
518 self.streaming_cache = None;
520 self.streaming_cache_len = 0;
521 self.streaming_cache_width = 0;
522 if self.auto_scroll_enabled {
524 self.scroll_offset = u16::MAX;
525 }
526 }
527
528 pub fn complete_streaming(&mut self) {
530 if let Some(content) = self.streaming_buffer.take()
531 && !content.trim().is_empty()
532 {
533 self.messages
534 .push(Message::new(MessageRole::Assistant, content));
535 }
536 self.streaming_cache = None;
538 self.streaming_cache_len = 0;
539 self.streaming_cache_width = 0;
540 }
541
542 pub fn discard_streaming(&mut self) {
544 self.streaming_buffer = None;
545 self.streaming_cache = None;
547 self.streaming_cache_len = 0;
548 self.streaming_cache_width = 0;
549 }
550
551 pub fn is_streaming(&self) -> bool {
553 self.streaming_buffer.is_some()
554 }
555
556 pub fn scroll_up(&mut self) {
557 if self.scroll_offset == u16::MAX {
559 self.scroll_offset = self.last_max_scroll;
560 }
561 self.scroll_offset = self.scroll_offset.saturating_sub(3);
562 self.auto_scroll_enabled = false;
564 }
565
566 pub fn scroll_down(&mut self) {
567 if self.scroll_offset == u16::MAX {
569 return;
570 }
571 self.scroll_offset = self.scroll_offset.saturating_add(3);
572 if self.scroll_offset >= self.last_max_scroll {
574 self.scroll_offset = u16::MAX;
575 self.auto_scroll_enabled = true; }
577 }
578
579 pub fn render_chat(&mut self, frame: &mut Frame, area: Rect, pending_status: Option<&str>) {
580 let theme = app_theme();
581
582 let content_block = if let Some(ref render_fn) = self.render_title {
584 let (left_title, right_title) = render_fn(&self.title, &theme);
585 Block::default()
586 .title(left_title)
587 .title_alignment(Alignment::Left)
588 .title(right_title.alignment(Alignment::Right))
589 .borders(Borders::TOP)
590 .border_style(theme.border)
591 .padding(Padding::new(1, 0, 1, 0))
592 } else {
593 Block::default()
594 .borders(Borders::TOP)
595 .border_style(theme.border)
596 .padding(Padding::new(1, 0, 1, 0))
597 };
598
599 let is_initial_state =
601 self.messages.is_empty() && self.streaming_buffer.is_none() && pending_status.is_none();
602
603 if is_initial_state && let Some(ref render_fn) = self.render_initial_content {
605 let inner = content_block.inner(area);
606 frame.render_widget(content_block, area);
607 render_fn(frame, inner, &theme);
608 return;
609 }
610
611 let available_width = area.width.saturating_sub(2) as usize; let mut message_lines: Vec<Line> = Vec::new();
616
617 if is_initial_state {
619 message_lines.push(Line::from(""));
620 message_lines.push(Line::from(Span::styled(
621 self.config.empty_message.clone(),
622 Style::default().fg(Color::DarkGray),
623 )));
624 }
625
626 for msg in &mut self.messages {
627 let cached = msg.get_rendered_lines(available_width, &self.config);
629 message_lines.extend(cached.iter().cloned());
630 }
631
632 if let Some(ref buffer) = self.streaming_buffer {
634 let buffer_len = buffer.len();
635
636 let cache_valid = self.streaming_cache.is_some()
638 && self.streaming_cache_len == buffer_len
639 && self.streaming_cache_width == available_width;
640
641 if !cache_valid {
642 let rendered = render_markdown_with_prefix(buffer, available_width, &theme);
644 self.streaming_cache = Some(rendered);
645 self.streaming_cache_len = buffer_len;
646 self.streaming_cache_width = available_width;
647 }
648
649 if let Some(ref cached) = self.streaming_cache {
651 message_lines.extend(cached.iter().cloned());
652 }
653
654 if let Some(last) = message_lines.last_mut() {
656 last.spans.push(Span::styled("\u{2588}", theme.cursor));
657 }
658 } else if let Some(status) = pending_status {
659 let spinner_char = self
661 .config
662 .spinner_chars
663 .get(self.spinner_index)
664 .copied()
665 .unwrap_or(' ');
666 message_lines.push(Line::from(vec![
667 Span::styled(format!("{} ", spinner_char), theme.throbber_spinner),
668 Span::styled(status, theme.throbber_label),
669 ]));
670 }
671
672 let available_height = area.height.saturating_sub(2) as usize; let total_lines = message_lines.len();
675 let max_scroll = total_lines.saturating_sub(available_height) as u16;
676 self.last_max_scroll = max_scroll;
677
678 let scroll_offset = if self.scroll_offset == u16::MAX {
686 max_scroll
687 } else {
688 let clamped = self.scroll_offset.min(max_scroll);
689 if clamped != self.scroll_offset {
690 self.scroll_offset = clamped;
691 }
692 clamped
693 };
694
695 let messages_widget = Paragraph::new(message_lines)
696 .block(content_block)
697 .style(theme.background.patch(theme.text))
698 .scroll((scroll_offset, 0));
699 frame.render_widget(messages_widget, area);
700 }
701}
702
703fn render_tool_message(
705 data: &ToolMessageData,
706 config: &ChatViewConfig,
707 available_width: usize,
708) -> Vec<Line<'static>> {
709 let mut lines = Vec::new();
710 let theme = app_theme();
711
712 let header = if data.display_title.is_empty() {
714 format!("{} {}", config.tool_icon, data.display_name)
715 } else {
716 format!(
717 "{} {}({})",
718 config.tool_icon, data.display_name, data.display_title
719 )
720 };
721 lines.push(Line::from(Span::styled(header, theme.tool_header)));
722
723 match &data.status {
725 ToolStatus::Executing => {
726 lines.push(Line::from(Span::styled(
727 format!(" {} executing...", config.tool_executing_arrow),
728 theme.tool_executing,
729 )));
730 }
731 ToolStatus::WaitingForUser => {
732 lines.push(Line::from(Span::styled(
733 format!(" {} waiting for user...", config.tool_executing_arrow),
734 theme.tool_executing,
735 )));
736 }
737 ToolStatus::Completed => {
738 lines.push(Line::from(Span::styled(
739 format!(" {} Completed", config.tool_completed_checkmark),
740 theme.tool_completed,
741 )));
742 }
743 ToolStatus::Failed(err) => {
744 let prefix = format!(" {} ", config.tool_failed_icon);
746 let cont_prefix = " "; let wrapped = wrap_with_prefix(
748 err,
749 &prefix,
750 theme.tool_failed,
751 cont_prefix,
752 available_width,
753 &theme,
754 );
755 lines.extend(wrapped);
756 }
757 }
758
759 lines
760}
761
762impl Default for ChatView {
763 fn default() -> Self {
764 Self::new()
765 }
766}
767
768use super::ConversationView;
771
772#[derive(Clone)]
774struct ChatViewState {
775 messages: Vec<MessageSnapshot>,
776 scroll_offset: u16,
777 streaming_buffer: Option<String>,
778 last_max_scroll: u16,
779 auto_scroll_enabled: bool,
780 tool_index: HashMap<String, usize>,
781 spinner_index: usize,
782}
783
784#[derive(Clone)]
786struct MessageSnapshot {
787 role: MessageRole,
788 content: String,
789 timestamp: DateTime<Local>,
790 tool_data: Option<ToolMessageData>,
791}
792
793impl From<&Message> for MessageSnapshot {
794 fn from(msg: &Message) -> Self {
795 Self {
796 role: msg.role,
797 content: msg.content.clone(),
798 timestamp: msg.timestamp,
799 tool_data: msg.tool_data.clone(),
800 }
801 }
802}
803
804impl From<MessageSnapshot> for Message {
805 fn from(snapshot: MessageSnapshot) -> Self {
806 Self {
807 role: snapshot.role,
808 content: snapshot.content,
809 timestamp: snapshot.timestamp,
810 cached_lines: None,
811 cached_width: 0,
812 tool_data: snapshot.tool_data,
813 }
814 }
815}
816
817impl ConversationView for ChatView {
818 fn add_user_message(&mut self, content: String) {
819 ChatView::add_user_message(self, content);
820 }
821
822 fn add_assistant_message(&mut self, content: String) {
823 ChatView::add_assistant_message(self, content);
824 }
825
826 fn add_system_message(&mut self, content: String) {
827 ChatView::add_system_message(self, content);
828 }
829
830 fn append_streaming(&mut self, text: &str) {
831 ChatView::append_streaming(self, text);
832 }
833
834 fn complete_streaming(&mut self) {
835 ChatView::complete_streaming(self);
836 }
837
838 fn discard_streaming(&mut self) {
839 ChatView::discard_streaming(self);
840 }
841
842 fn is_streaming(&self) -> bool {
843 ChatView::is_streaming(self)
844 }
845
846 fn add_tool_message(&mut self, tool_use_id: &str, display_name: &str, display_title: &str) {
847 ChatView::add_tool_message(self, tool_use_id, display_name, display_title);
848 }
849
850 fn update_tool_status(&mut self, tool_use_id: &str, status: ToolStatus) {
851 ChatView::update_tool_status(self, tool_use_id, status);
852 }
853
854 fn scroll_up(&mut self) {
855 ChatView::scroll_up(self);
856 }
857
858 fn scroll_down(&mut self) {
859 ChatView::scroll_down(self);
860 }
861
862 fn enable_auto_scroll(&mut self) {
863 ChatView::enable_auto_scroll(self);
864 }
865
866 fn render(
867 &mut self,
868 frame: &mut Frame,
869 area: Rect,
870 _theme: &Theme,
871 pending_status: Option<&str>,
872 ) {
873 self.render_chat(frame, area, pending_status);
874 }
875
876 fn step_spinner(&mut self) {
877 ChatView::step_spinner(self);
878 }
879
880 fn save_state(&self) -> Box<dyn Any + Send> {
881 let state = ChatViewState {
882 messages: self.messages.iter().map(MessageSnapshot::from).collect(),
883 scroll_offset: self.scroll_offset,
884 streaming_buffer: self.streaming_buffer.clone(),
885 last_max_scroll: self.last_max_scroll,
886 auto_scroll_enabled: self.auto_scroll_enabled,
887 tool_index: self.tool_index.clone(),
888 spinner_index: self.spinner_index,
889 };
890 Box::new(state)
891 }
892
893 fn restore_state(&mut self, state: Box<dyn Any + Send>) {
894 if let Ok(chat_state) = state.downcast::<ChatViewState>() {
895 self.messages = chat_state.messages.into_iter().map(Message::from).collect();
896 self.scroll_offset = chat_state.scroll_offset;
897 self.streaming_buffer = chat_state.streaming_buffer;
898 self.streaming_cache = None;
900 self.streaming_cache_len = 0;
901 self.streaming_cache_width = 0;
902 self.last_max_scroll = chat_state.last_max_scroll;
903 self.auto_scroll_enabled = chat_state.auto_scroll_enabled;
904 self.tool_index = chat_state.tool_index;
905 self.spinner_index = chat_state.spinner_index;
906 }
907 }
908
909 fn clear(&mut self) {
910 self.messages.clear();
911 self.streaming_buffer = None;
912 self.streaming_cache = None;
913 self.streaming_cache_len = 0;
914 self.streaming_cache_width = 0;
915 self.tool_index.clear();
916 self.scroll_offset = 0;
917 self.last_max_scroll = 0;
918 self.auto_scroll_enabled = true;
919 self.spinner_index = 0;
920 }
922}
923
924use super::{Widget, WidgetKeyContext, WidgetKeyResult, widget_ids};
927use crossterm::event::KeyEvent;
928use std::any::Any;
929
930impl Widget for ChatView {
931 fn id(&self) -> &'static str {
932 widget_ids::CHAT_VIEW
933 }
934
935 fn priority(&self) -> u8 {
936 50 }
938
939 fn is_active(&self) -> bool {
940 true }
942
943 fn handle_key(&mut self, _key: KeyEvent, _ctx: &WidgetKeyContext) -> WidgetKeyResult {
944 WidgetKeyResult::NotHandled
947 }
948
949 fn render(&mut self, frame: &mut Frame, area: Rect, _theme: &Theme) {
950 self.render_chat(frame, area, None);
953 }
954
955 fn required_height(&self, _available: u16) -> u16 {
956 0 }
958
959 fn blocks_input(&self) -> bool {
960 false
961 }
962
963 fn is_overlay(&self) -> bool {
964 false
965 }
966
967 fn as_any(&self) -> &dyn Any {
968 self
969 }
970
971 fn as_any_mut(&mut self) -> &mut dyn Any {
972 self
973 }
974
975 fn into_any(self: Box<Self>) -> Box<dyn Any> {
976 self
977 }
978}