diff --git a/crates/owlen-tui/src/ui.rs b/crates/owlen-tui/src/ui.rs index 93888df..67abffa 100644 --- a/crates/owlen-tui/src/ui.rs +++ b/crates/owlen-tui/src/ui.rs @@ -377,70 +377,44 @@ fn render_messages(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) { let show_role_labels = formatter.show_role_labels(); if show_role_labels { - // Calculate the prefix width for proper wrapping - let prefix = format!("{emoji}{name}"); - let prefix_width = UnicodeWidthStr::width(prefix.as_str()); + // Role name line + let mut role_line_spans = vec![ + Span::raw(emoji), + Span::styled(name, role_color(role).add_modifier(Modifier::BOLD)), + ]; + + // Add loading indicator if applicable + if matches!(role, Role::Assistant) && + app.get_loading_indicator() != "" && + message_index == conversation.messages.len() - 1 && + is_streaming { + role_line_spans.push(Span::styled( + format!(" {}", app.get_loading_indicator()), + Style::default().fg(Color::Yellow), + )); + } + + lines.push(Line::from(role_line_spans)); // Join all formatted lines into single content string let content = formatted.join("\n"); - // Add loading indicator if applicable - let loading_indicator = if matches!(role, Role::Assistant) && - app.get_loading_indicator() != "" && - message_index == conversation.messages.len() - 1 && - is_streaming { - format!("{} ", app.get_loading_indicator()) - } else { - String::new() - }; - - // Wrap content considering available width minus prefix - let available_width = (content_width as usize).saturating_sub(prefix_width); + // Wrap content with available width minus indent (2 spaces) + let indent = " "; + let available_width = (content_width as usize).saturating_sub(2); let chunks = if available_width > 0 { wrap(&content, available_width) } else { vec![] }; - if chunks.is_empty() { - let mut first_line_spans = vec![ - Span::raw(emoji), - Span::styled(name, role_color(role).add_modifier(Modifier::BOLD)), - ]; - if !loading_indicator.is_empty() { - first_line_spans.push(Span::styled( - loading_indicator, - Style::default().fg(Color::Yellow), - )); - } - lines.push(Line::from(first_line_spans)); - } else { - let chunks_len = chunks.len(); - for (i, seg) in chunks.into_iter().enumerate() { - if i == 0 { - let mut first_line_spans = vec![ - Span::raw(emoji), - Span::styled(name, role_color(role).add_modifier(Modifier::BOLD)), - ]; - if !loading_indicator.is_empty() { - first_line_spans.push(Span::styled( - loading_indicator.clone(), - Style::default().fg(Color::Yellow), - )); - } - first_line_spans.push(Span::raw(seg.into_owned())); - if chunks_len == 1 && is_streaming { - first_line_spans.push(Span::styled(" ▌", Style::default().fg(Color::Magenta))); - } - lines.push(Line::from(first_line_spans)); - } else { - let mut spans = vec![Span::raw(seg.into_owned())]; - if i == chunks_len - 1 && is_streaming { - spans.push(Span::styled(" ▌", Style::default().fg(Color::Magenta))); - } - lines.push(Line::from(spans)); - } + let chunks_len = chunks.len(); + for (i, seg) in chunks.into_iter().enumerate() { + let mut spans = vec![Span::raw(format!("{indent}{}", seg))]; + if i == chunks_len - 1 && is_streaming { + spans.push(Span::styled(" ▌", Style::default().fg(Color::Magenta))); } + lines.push(Line::from(spans)); } } else { // No role labels - just show content