Implement chat and markdown views, enhance status bar, and add workflow panel
- Added `chat.rs` for rendering chat messages with timestamps and roles. - Introduced `markdown.rs` for rendering markdown content with styling. - Created `status.rs` to display the application status bar with session and message counts. - Developed `workflow.rs` to show the current workflow status, including tool calls and active jobs. - Established a `theme.rs` for centralized color management across the UI. - Updated `mod.rs` to include new modules and manage rendering logic.
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
use ratatui::text::Span;
|
||||
|
||||
pub fn render_markdown<'a>(text: &'a str, width: u16) -> Vec<Span<'a>> {
|
||||
let mut spans = Vec::new();
|
||||
let parser = pulldown_cmark::Parser::new(text);
|
||||
let mut in_code_block = false;
|
||||
|
||||
for event in parser {
|
||||
match event {
|
||||
pulldown_cmark::Event::Start(tag) => {
|
||||
match tag {
|
||||
pulldown_cmark::Tag::CodeBlock(_) => {
|
||||
in_code_block = true;
|
||||
spans.push(Span::styled(
|
||||
"```\n",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
));
|
||||
}
|
||||
pulldown_cmark::Tag::Heading { level, .. } => {
|
||||
let color = match level {
|
||||
pulldown_cmark::HeadingLevel::H1 => Color::LightCyan,
|
||||
pulldown_cmark::HeadingLevel::H2 => Color::Cyan,
|
||||
_ => Color::White,
|
||||
};
|
||||
let prefix = match level {
|
||||
pulldown_cmark::HeadingLevel::H1 => "# ",
|
||||
pulldown_cmark::HeadingLevel::H2 => "## ",
|
||||
pulldown_cmark::HeadingLevel::H3 => "### ",
|
||||
_ => "# ",
|
||||
};
|
||||
spans.push(Span::styled(
|
||||
prefix,
|
||||
Style::default().fg(color).add_modifier(Modifier::BOLD),
|
||||
));
|
||||
}
|
||||
pulldown_cmark::Tag::Paragraph => {}
|
||||
pulldown_cmark::Tag::Emphasis => {}
|
||||
pulldown_cmark::Tag::Strong => {}
|
||||
pulldown_cmark::Tag::List(_) => {}
|
||||
pulldown_cmark::Tag::Item => {
|
||||
spans.push(Span::styled(
|
||||
" * ",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
));
|
||||
}
|
||||
pulldown_cmark::Tag::Link { dest_url, .. } => {
|
||||
spans.push(Span::styled(
|
||||
"[",
|
||||
Style::default().fg(Color::Cyan),
|
||||
));
|
||||
spans.push(Span::styled(
|
||||
format!("]({})", dest_url),
|
||||
Style::default().fg(Color::Blue),
|
||||
));
|
||||
}
|
||||
pulldown_cmark::Tag::BlockQuote(_) => {
|
||||
spans.push(Span::styled(
|
||||
"> ",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
pulldown_cmark::Event::End(tag) => {
|
||||
match tag {
|
||||
pulldown_cmark::TagEnd::CodeBlock => {
|
||||
in_code_block = false;
|
||||
spans.push(Span::styled(
|
||||
"\n```\n",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
));
|
||||
}
|
||||
pulldown_cmark::TagEnd::Heading(_) => {
|
||||
spans.push(Span::raw("\n"));
|
||||
}
|
||||
pulldown_cmark::TagEnd::Paragraph => {
|
||||
spans.push(Span::raw("\n\n"));
|
||||
}
|
||||
pulldown_cmark::TagEnd::Emphasis => {}
|
||||
pulldown_cmark::TagEnd::Strong => {}
|
||||
pulldown_cmark::TagEnd::List(_) => {}
|
||||
pulldown_cmark::TagEnd::Item => {
|
||||
spans.push(Span::raw("\n"));
|
||||
}
|
||||
pulldown_cmark::TagEnd::Link => {}
|
||||
pulldown_cmark::TagEnd::BlockQuote(_) => {
|
||||
spans.push(Span::raw("\n"));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
pulldown_cmark::Event::Text(text) => {
|
||||
let s = text.to_string();
|
||||
if in_code_block {
|
||||
spans.push(Span::styled(
|
||||
s,
|
||||
Style::default().fg(Color::Green),
|
||||
));
|
||||
} else {
|
||||
spans.push(Span::raw(s));
|
||||
}
|
||||
}
|
||||
pulldown_cmark::Event::Code(text) => {
|
||||
spans.push(Span::styled(
|
||||
format!("`{}`", text),
|
||||
Style::default().fg(Color::Green),
|
||||
));
|
||||
}
|
||||
pulldown_cmark::Event::SoftBreak => {
|
||||
spans.push(Span::raw("\n"));
|
||||
}
|
||||
pulldown_cmark::Event::HardBreak => {
|
||||
spans.push(Span::raw("\n"));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if width > 0 {
|
||||
let mut spans_out = Vec::new();
|
||||
let mut line_len = 0;
|
||||
for span in &spans {
|
||||
let s = span.content.clone();
|
||||
let text = s.as_ref();
|
||||
let remaining = text.len();
|
||||
if line_len + remaining > width as usize && line_len > 0 {
|
||||
spans_out.push(Span::raw("\n"));
|
||||
line_len = 0;
|
||||
}
|
||||
spans_out.push(Span::raw(text.to_string()));
|
||||
if !text.contains('\n') {
|
||||
line_len += remaining;
|
||||
} else {
|
||||
line_len = text.split('\n').next_back().unwrap_or("").len();
|
||||
}
|
||||
}
|
||||
spans = spans_out;
|
||||
}
|
||||
|
||||
spans
|
||||
}
|
||||
Reference in New Issue
Block a user