Refactor TUI message rendering: simplify role label logic, improve line wrapping, and optimize loading indicator handling.

This commit is contained in:
2025-09-29 22:51:01 +02:00
parent b8d1866b7d
commit e193b839f2

View File

@@ -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