use crate::components::{Box, Text};
use crate::core::{Color, Element, FlexDirection};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MessageRole {
User,
Assistant,
System,
Tool,
ToolResult,
Error,
}
pub struct Message {
role: MessageRole,
content: String,
prefix: Option<String>,
}
impl Message {
pub fn new(role: MessageRole, content: impl Into<String>) -> Self {
Self {
role,
content: content.into(),
prefix: None,
}
}
pub fn user(content: impl Into<String>) -> Self {
Self::new(MessageRole::User, content)
}
pub fn assistant(content: impl Into<String>) -> Self {
Self::new(MessageRole::Assistant, content)
}
pub fn system(content: impl Into<String>) -> Self {
Self::new(MessageRole::System, content)
}
pub fn tool(content: impl Into<String>) -> Self {
Self::new(MessageRole::Tool, content)
}
pub fn tool_result(content: impl Into<String>) -> Self {
Self::new(MessageRole::ToolResult, content)
}
pub fn error(content: impl Into<String>) -> Self {
Self::new(MessageRole::Error, content)
}
pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
self.prefix = Some(prefix.into());
self
}
fn default_prefix(&self) -> &str {
match self.role {
MessageRole::User => "> ",
MessageRole::Assistant => "● ",
MessageRole::System => "● ",
MessageRole::Tool => "● ",
MessageRole::ToolResult => " ⎿ ",
MessageRole::Error => "● ",
}
}
fn color(&self) -> Color {
match self.role {
MessageRole::User => Color::Yellow,
MessageRole::Assistant => Color::BrightWhite,
MessageRole::System => Color::Cyan,
MessageRole::Tool => Color::Magenta,
MessageRole::ToolResult => Color::Ansi256(245),
MessageRole::Error => Color::Red,
}
}
fn prefix_color(&self) -> Color {
match self.role {
MessageRole::User => Color::Yellow,
MessageRole::Assistant => Color::BrightWhite,
MessageRole::System => Color::Cyan,
MessageRole::Tool => Color::Magenta,
MessageRole::ToolResult => Color::Ansi256(245),
MessageRole::Error => Color::Red,
}
}
pub fn into_element(self) -> Element {
let prefix = self
.prefix
.as_deref()
.unwrap_or_else(|| self.default_prefix());
let prefix_color = self.prefix_color();
let content_color = self.color();
let mut container = Box::new().flex_direction(FlexDirection::Row);
if !prefix.is_empty() {
container =
container.child(Text::new(prefix).color(prefix_color).bold().into_element());
}
container = container.child(Text::new(&self.content).color(content_color).into_element());
container.into_element()
}
}
pub struct ToolCall {
name: String,
args: String,
}
impl ToolCall {
pub fn new(name: impl Into<String>, args: impl Into<String>) -> Self {
Self {
name: name.into(),
args: args.into(),
}
}
pub fn into_element(self) -> Element {
Box::new()
.flex_direction(FlexDirection::Row)
.child(Text::new("● ").color(Color::Magenta).into_element())
.child(
Text::new(&self.name)
.color(Color::Magenta)
.bold()
.into_element(),
)
.child(
Text::new(format!("({})", self.args))
.color(Color::Magenta)
.into_element(),
)
.into_element()
}
}
pub struct ThinkingBlock {
content: String,
max_lines: usize,
}
impl ThinkingBlock {
pub fn new(content: impl Into<String>) -> Self {
Self {
content: content.into(),
max_lines: 5,
}
}
pub fn max_lines(mut self, max_lines: usize) -> Self {
self.max_lines = max_lines;
self
}
pub fn into_element(self) -> Element {
let lines: Vec<&str> = self.content.lines().take(self.max_lines).collect();
let has_more = self.content.lines().count() > self.max_lines;
let mut container = Box::new().flex_direction(FlexDirection::Column).child(
Text::new("● Thinking...")
.color(Color::Magenta)
.into_element(),
);
for line in lines {
container = container.child(
Box::new()
.flex_direction(FlexDirection::Row)
.child(Text::new(" ").into_element())
.child(Text::new(line).color(Color::Magenta).dim().into_element())
.into_element(),
);
}
if has_more {
container = container.child(
Text::new(" ...")
.color(Color::Ansi256(245))
.dim()
.into_element(),
);
}
container.into_element()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_message_creation() {
let msg = Message::user("Hello");
let element = msg.into_element();
assert!(!element.children.is_empty());
}
#[test]
fn test_message_roles() {
let _user = Message::user("test");
let _assistant = Message::assistant("test");
let _system = Message::system("test");
let _tool = Message::tool("test");
let _error = Message::error("test");
}
#[test]
fn test_tool_call() {
let tool = ToolCall::new("read_file", "path=/tmp/test.txt");
let element = tool.into_element();
assert!(!element.children.is_empty());
}
#[test]
fn test_thinking_block() {
let thinking = ThinkingBlock::new("Line 1\nLine 2\nLine 3");
let element = thinking.into_element();
assert!(!element.children.is_empty());
}
}