Skip to main content

rustyclaw_tui/components/
message_bubble.rs

1// ── Message bubble ──────────────────────────────────────────────────────────
2
3use crate::markdown;
4use crate::theme;
5use iocraft::prelude::*;
6use rustyclaw_core::types::MessageRole;
7
8#[derive(Default, Props)]
9pub struct MessageBubbleProps {
10    pub role: Option<MessageRole>,
11    pub content: String,
12    /// Custom name to display for assistant messages (falls back to "Assistant").
13    pub assistant_name: Option<String>,
14}
15
16#[component]
17pub fn MessageBubble(props: &MessageBubbleProps) -> impl Into<AnyElement<'static>> {
18    let role = props.role.unwrap_or(MessageRole::System);
19    let fg = theme::role_color(&role);
20    let bg = theme::role_bg(&role);
21    let border = theme::role_border(&role);
22
23    let icon = role.icon();
24    let label = match role {
25        MessageRole::User => "You".to_string(),
26        MessageRole::Assistant => props
27            .assistant_name
28            .clone()
29            .unwrap_or_else(|| "Assistant".to_string()),
30        MessageRole::Info => "Info".to_string(),
31        MessageRole::Success => "Success".to_string(),
32        MessageRole::Warning => "Warning".to_string(),
33        MessageRole::Error => "Error".to_string(),
34        MessageRole::System => "System".to_string(),
35        MessageRole::ToolCall => "Tool Call".to_string(),
36        MessageRole::ToolResult => "Tool Result".to_string(),
37        MessageRole::Thinking => "Thinking".to_string(),
38    };
39
40    // Render markdown for assistant messages, plain text for others
41    let display = if role == MessageRole::Thinking && props.content.len() > 120 {
42        format!("{}…", &props.content[..120])
43    } else if role == MessageRole::Assistant {
44        markdown::render_ansi(&props.content)
45    } else {
46        props.content.clone()
47    };
48
49    element! {
50        View(
51            width: 100pct,
52            margin_bottom: 1,
53            flex_direction: FlexDirection::Column,
54            background_color: bg,
55            border_style: BorderStyle::Round,
56            border_color: border,
57            border_edges: Edges::Left,
58            padding_left: 1,
59            padding_right: 1,
60        ) {
61            Text(content: format!("{} {}", icon, label), color: border, weight: Weight::Bold)
62            Text(content: display, color: fg, wrap: TextWrap::Wrap)
63        }
64    }
65}