feat: Enhance token usage tracking and improve chat UI with emojis
This commit is contained in:
+24
-25
@@ -18,7 +18,7 @@
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::{Color, Style, Modifier};
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::{Block, Borders, Paragraph, Wrap};
|
||||
use ratatui::widgets::{Block, BorderType, Borders, Paragraph, Wrap};
|
||||
use ratatui::Frame;
|
||||
use super::theme::Theme;
|
||||
use crate::dto::chat::message::Role;
|
||||
@@ -26,7 +26,7 @@ use crate::dto::chat::message::Role;
|
||||
/// Column width reserved for the `{role} {time} ` header prefix; wrapped
|
||||
/// continuation lines and Tool sub-lines indent to this width so content
|
||||
/// stays aligned under the first line's content column.
|
||||
const PREFIX_WIDTH: usize = 12;
|
||||
const PREFIX_WIDTH: usize = 15;
|
||||
|
||||
/// Break a flat run of styled spans into `Line`s at embedded `\n` boundaries.
|
||||
fn split_spans_into_lines(spans: Vec<Span<'_>>) -> Vec<Line<'_>> {
|
||||
@@ -68,10 +68,10 @@ fn role_accent_color(role: &Role) -> Color {
|
||||
/// raw label).
|
||||
fn format_role_label(role: &Role) -> &'static str {
|
||||
match role {
|
||||
Role::User => "you",
|
||||
Role::Assistant => "ai",
|
||||
Role::System => "sys",
|
||||
Role::Tool => "tool",
|
||||
Role::User => "👤 you ",
|
||||
Role::Assistant => "🤖 ai ",
|
||||
Role::System => "💻 sys ",
|
||||
Role::Tool => "🔧 tool",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,8 +90,8 @@ fn format_timestamp(ts: i64) -> String {
|
||||
/// `draw_chat`) and must never be passed as `prev_role` — a Tool message
|
||||
/// never triggers a separator, and it never causes one to be inserted
|
||||
/// before the next real turn either.
|
||||
fn needs_speaker_separator(prev_role: Option<&Role>, role: &Role) -> bool {
|
||||
matches!(prev_role, Some(p) if p != role)
|
||||
fn needs_speaker_separator(_prev_role: Option<&Role>, _role: &Role) -> bool {
|
||||
false // User requested zsh-style compactness (no empty lines between speakers)
|
||||
}
|
||||
|
||||
/// Render the scrollable chat transcript panel in tight inline-log style.
|
||||
@@ -108,9 +108,9 @@ pub fn draw_chat(frame: &mut Frame, area: Rect, state: &crate::app::state::rest:
|
||||
let mut prev_role: Option<Role> = None;
|
||||
|
||||
let title = if messages.is_empty() {
|
||||
String::from(" Chat ")
|
||||
String::from(" 💬 Chat ")
|
||||
} else {
|
||||
format!(" Chat [{} msgs]", messages.len())
|
||||
format!(" 💬 Chat [{} msgs] ", messages.len())
|
||||
};
|
||||
|
||||
for msg in messages {
|
||||
@@ -163,7 +163,7 @@ pub fn draw_chat(frame: &mut Frame, area: Rect, state: &crate::app::state::rest:
|
||||
let label = format_role_label(&msg.role);
|
||||
let ts_str = format_timestamp(msg.timestamp);
|
||||
let header_prefix = vec![
|
||||
Span::styled(format!("{label:<4} "), Style::default().fg(accent).add_modifier(Modifier::BOLD)),
|
||||
Span::styled(format!("{label} "), Style::default().fg(accent).add_modifier(Modifier::BOLD)),
|
||||
Span::styled(format!("{ts_str:<5} "), Style::default().fg(Theme::TEXT_DIM)),
|
||||
];
|
||||
|
||||
@@ -207,7 +207,7 @@ pub fn draw_chat(frame: &mut Frame, area: Rect, state: &crate::app::state::rest:
|
||||
}
|
||||
display_lines.push(Line::from(vec![
|
||||
Span::styled(
|
||||
format!("{:<4} ", format_role_label(&Role::Assistant)),
|
||||
format!("{} ", format_role_label(&Role::Assistant)),
|
||||
Style::default().fg(Theme::ROLE_ASSISTANT).add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::styled(format!("{spinner} "), Style::default().fg(Theme::TEXT_DIM)),
|
||||
@@ -218,8 +218,9 @@ pub fn draw_chat(frame: &mut Frame, area: Rect, state: &crate::app::state::rest:
|
||||
// ── Scrolling ────────────────────────────────────────────────────────
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(Theme::BORDER))
|
||||
.title(Span::styled(title, Style::default().fg(Theme::TEXT_MUTED)));
|
||||
.title(Span::styled(title, Style::default().fg(Theme::TEXT_MUTED).add_modifier(Modifier::BOLD)));
|
||||
|
||||
let total = display_lines.len();
|
||||
let max_offset = total.saturating_sub(max_visible);
|
||||
@@ -240,11 +241,12 @@ pub fn draw_chat(frame: &mut Frame, area: Rect, state: &crate::app::state::rest:
|
||||
};
|
||||
|
||||
let block = if scroll_pct > 0 {
|
||||
let scroll_title = format!(" Chat [{} msgs] ── {}% ↑ ", messages.len(), scroll_pct);
|
||||
let scroll_title = format!(" 💬 Chat [{} msgs] ── {}% ↑ ", messages.len(), scroll_pct);
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(Theme::BORDER))
|
||||
.title(Span::styled(scroll_title, Style::default().fg(Theme::TEXT_MUTED)))
|
||||
.title(Span::styled(scroll_title, Style::default().fg(Theme::TEXT_MUTED).add_modifier(Modifier::BOLD)))
|
||||
} else {
|
||||
block
|
||||
};
|
||||
@@ -272,18 +274,15 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn separator_when_speaker_changes() {
|
||||
assert!(needs_speaker_separator(Some(&Role::User), &Role::Assistant));
|
||||
fn no_separator_when_speaker_changes_because_zsh_style() {
|
||||
assert!(!needs_speaker_separator(Some(&Role::User), &Role::Assistant));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn role_labels_are_lowercase_and_fit_prefix_width() {
|
||||
assert_eq!(format_role_label(&Role::User), "you");
|
||||
assert_eq!(format_role_label(&Role::Assistant), "ai");
|
||||
assert_eq!(format_role_label(&Role::System), "sys");
|
||||
assert_eq!(format_role_label(&Role::Tool), "tool");
|
||||
for role in [Role::User, Role::Assistant, Role::System, Role::Tool] {
|
||||
assert!(format_role_label(&role).len() <= 4);
|
||||
}
|
||||
fn role_labels_include_emojis_and_padding() {
|
||||
assert_eq!(format_role_label(&Role::User), "👤 you ");
|
||||
assert_eq!(format_role_label(&Role::Assistant), "🤖 ai ");
|
||||
assert_eq!(format_role_label(&Role::System), "💻 sys ");
|
||||
assert_eq!(format_role_label(&Role::Tool), "🔧 tool");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user