only show tool detail when running

This commit is contained in:
Dhanji Prasanna
2025-10-06 14:33:15 +11:00
parent 340ba78eb3
commit c9487db5e7

View File

@@ -71,6 +71,10 @@ struct TerminalState {
scroll_offset: usize, scroll_offset: usize,
/// Cursor blink state /// Cursor blink state
cursor_blink: bool, cursor_blink: bool,
/// Animation state for activity area (0.0 = hidden, 1.0 = fully shown)
activity_animation: f32,
/// Target animation state
activity_animation_target: f32,
/// Tool activity history (left side of activity box) /// Tool activity history (left side of activity box)
tool_activity: Vec<String>, tool_activity: Vec<String>,
/// Track if tool activity should auto-scroll /// Track if tool activity should auto-scroll
@@ -121,6 +125,8 @@ impl TerminalState {
], ],
scroll_offset: 0, scroll_offset: 0,
cursor_blink: true, cursor_blink: true,
activity_animation: 0.0,
activity_animation_target: 0.0,
tool_activity: Vec::new(), tool_activity: Vec::new(),
tool_activity_auto_scroll: true, tool_activity_auto_scroll: true,
tool_activity_scroll: 0, tool_activity_scroll: 0,
@@ -371,6 +377,8 @@ impl RetroTui {
let was_processing = state.status_line == "PROCESSING"; let was_processing = state.status_line == "PROCESSING";
state.status_line = status; state.status_line = status;
state.is_processing = state.status_line == "PROCESSING"; state.is_processing = state.status_line == "PROCESSING";
// Set animation target based on processing state
state.activity_animation_target = if state.is_processing { 1.0 } else { 0.0 };
// Remove cursor when exiting PROCESSING mode // Remove cursor when exiting PROCESSING mode
if was_processing && !state.is_processing { if was_processing && !state.is_processing {
@@ -439,6 +447,18 @@ impl RetroTui {
state.last_status_blink = Instant::now(); state.last_status_blink = Instant::now();
} }
} }
// Update activity area animation
let animation_speed = 0.15; // Adjust for faster/slower animation
if (state.activity_animation - state.activity_animation_target).abs() > 0.01 {
// Smoothly interpolate towards target
state.activity_animation += (state.activity_animation_target - state.activity_animation) * animation_speed;
// Clamp to valid range
state.activity_animation = state.activity_animation.clamp(0.0, 1.0);
} else {
// Snap to target when close enough
state.activity_animation = state.activity_animation_target;
}
} }
// Redraw at ~60fps // Redraw at ~60fps
@@ -476,16 +496,31 @@ impl RetroTui {
terminal.draw(|f| { terminal.draw(|f| {
let size = f.area(); let size = f.area();
// Create main layout - header, input, output // Calculate activity area height based on animation (0 to 8)
let chunks = Layout::default() let activity_height = (8.0 * state.activity_animation).round() as u16;
// Create main layout - dynamically adjust based on whether activity area is shown
let chunks = if activity_height > 0 {
Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints([ .constraints([
Constraint::Length(5), // Header/input area Constraint::Length(5), // Header/input area
Constraint::Min(10), // Main output area (will be further split) Constraint::Min(10), // Main output area (will be further split)
Constraint::Length(8), // Activity area Constraint::Length(activity_height), // Activity area (animated)
Constraint::Length(1), // Status bar Constraint::Length(1), // Status bar
]) ])
.split(size); .split(size)
} else {
// When activity area is hidden, give more space to output
Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(5), // Header/input area
Constraint::Min(10), // Main output area gets all remaining space
Constraint::Length(1), // Status bar
])
.split(size)
};
// IMPORTANT: Update the last known visible height BEFORE drawing // IMPORTANT: Update the last known visible height BEFORE drawing
// This ensures auto-scroll calculations use the correct height // This ensures auto-scroll calculations use the correct height
@@ -513,13 +548,22 @@ impl RetroTui {
// Draw main output area // Draw main output area
Self::draw_output_area(f, chunks[1], &state.output_history, state.scroll_offset); Self::draw_output_area(f, chunks[1], &state.output_history, state.scroll_offset);
// Draw activity area (tool output) // Draw activity area only if it's visible (during animation or when shown)
Self::draw_activity_area(f, chunks[2], state); if activity_height > 0 {
// Apply fade effect by adjusting opacity through color intensity
let opacity = state.activity_animation;
Self::draw_activity_area(f, chunks[2], state, opacity);
}
// Draw status bar // Draw status bar - use the last chunk which is either index 2 or 3
let status_bar_chunk = if activity_height > 0 {
chunks[3]
} else {
chunks[2]
};
Self::draw_status_bar( Self::draw_status_bar(
f, f,
chunks[3], status_bar_chunk,
&state.status_line, &state.status_line,
state.context_info, state.context_info,
&state.provider_info, &state.provider_info,
@@ -704,9 +748,23 @@ impl RetroTui {
f: &mut Frame, f: &mut Frame,
area: Rect, area: Rect,
state: &TerminalState, state: &TerminalState,
opacity: f32,
) { ) {
// Note: scroll_offset is managed by the state and auto-scrolls to show latest content when new data arrives // Note: scroll_offset is managed by the state and auto-scrolls to show latest content when new data arrives
// Apply fade effect by adjusting colors based on opacity
let fade_color = |color: Color| -> Color {
match color {
Color::Rgb(r, g, b) => {
let faded_r = ((r as f32 * opacity) as u8).max(0);
let faded_g = ((g as f32 * opacity) as u8).max(0);
let faded_b = ((b as f32 * opacity) as u8).max(0);
Color::Rgb(faded_r, faded_g, faded_b)
}
_ => color,
}
};
// Split the activity area into left and right halves // Split the activity area into left and right halves
let chunks = Layout::default() let chunks = Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
@@ -732,7 +790,7 @@ impl RetroTui {
let visible_lines: Vec<Line> = if state.tool_activity.is_empty() { let visible_lines: Vec<Line> = if state.tool_activity.is_empty() {
vec![Line::from(Span::styled( vec![Line::from(Span::styled(
" No tool activity yet", " No tool activity yet",
Style::default().fg(TERMINAL_DIM_GREEN).add_modifier(Modifier::ITALIC), Style::default().fg(fade_color(TERMINAL_DIM_GREEN)).add_modifier(Modifier::ITALIC),
))] ))]
} else { } else {
state.tool_activity state.tool_activity
@@ -742,11 +800,11 @@ impl RetroTui {
.map(|line| { .map(|line| {
// Style the header lines differently // Style the header lines differently
let style = if line.starts_with('[') && line.contains(']') { let style = if line.starts_with('[') && line.contains(']') {
Style::default().fg(TERMINAL_CYAN).add_modifier(Modifier::BOLD) Style::default().fg(fade_color(TERMINAL_CYAN)).add_modifier(Modifier::BOLD)
} else if line.is_empty() { } else if line.is_empty() {
Style::default() Style::default()
} else { } else {
Style::default().fg(TERMINAL_GREEN) Style::default().fg(fade_color(TERMINAL_GREEN))
}; };
Line::from(Span::styled(format!(" {}", line), style)) Line::from(Span::styled(format!(" {}", line), style))
}) })
@@ -759,7 +817,7 @@ impl RetroTui {
.title(" TOOL DETAIL ") .title(" TOOL DETAIL ")
.title_alignment(Alignment::Center) .title_alignment(Alignment::Center)
.borders(Borders::ALL) .borders(Borders::ALL)
.border_style(Style::default().fg(TERMINAL_DIM_GREEN)) .border_style(Style::default().fg(fade_color(TERMINAL_DIM_GREEN)))
.style(Style::default().bg(TERMINAL_BG)), .style(Style::default().bg(TERMINAL_BG)),
) )
.wrap(Wrap { trim: false }); .wrap(Wrap { trim: false });
@@ -767,7 +825,7 @@ impl RetroTui {
f.render_widget(tool_output, chunks[0]); f.render_widget(tool_output, chunks[0]);
// Draw right half - Token Chart // Draw right half - Token Chart
Self::draw_token_chart(f, chunks[1], &state.token_rate_history, state.is_processing); Self::draw_token_chart(f, chunks[1], &state.token_rate_history, state.is_processing, opacity);
} }
/// Draw a line chart showing tokens received over time /// Draw a line chart showing tokens received over time
@@ -776,13 +834,27 @@ impl RetroTui {
area: Rect, area: Rect,
token_history: &VecDeque<(f64, f64)>, token_history: &VecDeque<(f64, f64)>,
is_processing: bool, is_processing: bool,
opacity: f32,
) { ) {
// Apply fade effect by adjusting colors based on opacity
let fade_color = |color: Color| -> Color {
match color {
Color::Rgb(r, g, b) => {
let faded_r = ((r as f32 * opacity) as u8).max(0);
let faded_g = ((g as f32 * opacity) as u8).max(0);
let faded_b = ((b as f32 * opacity) as u8).max(0);
Color::Rgb(faded_r, faded_g, faded_b)
}
_ => color,
}
};
// Create the chart block // Create the chart block
let block = Block::default() let block = Block::default()
.title(" TOKENS RECEIVED ") .title(" TOKENS RECEIVED ")
.title_alignment(Alignment::Center) .title_alignment(Alignment::Center)
.borders(Borders::ALL) .borders(Borders::ALL)
.border_style(Style::default().fg(TERMINAL_DIM_GREEN)) .border_style(Style::default().fg(fade_color(TERMINAL_DIM_GREEN)))
.style(Style::default().bg(TERMINAL_BG)); .style(Style::default().bg(TERMINAL_BG));
// Calculate inner area for chart // Calculate inner area for chart
@@ -795,7 +867,7 @@ impl RetroTui {
if token_history.is_empty() || inner.width < 10 || inner.height < 3 { if token_history.is_empty() || inner.width < 10 || inner.height < 3 {
let placeholder = Paragraph::new(vec![Line::from(Span::styled( let placeholder = Paragraph::new(vec![Line::from(Span::styled(
" Waiting for token data...", " Waiting for token data...",
Style::default().fg(TERMINAL_DIM_GREEN).add_modifier(Modifier::ITALIC), Style::default().fg(fade_color(TERMINAL_DIM_GREEN)).add_modifier(Modifier::ITALIC),
))]) ))])
.alignment(Alignment::Center); .alignment(Alignment::Center);
f.render_widget(placeholder, inner); f.render_widget(placeholder, inner);
@@ -826,9 +898,9 @@ impl RetroTui {
lines.push(Line::from(vec![ lines.push(Line::from(vec![
Span::styled( Span::styled(
format!("{:>5.0}", max_tokens), format!("{:>5.0}", max_tokens),
Style::default().fg(TERMINAL_AMBER), Style::default().fg(fade_color(TERMINAL_AMBER)),
), ),
Span::styled("", Style::default().fg(TERMINAL_DIM_GREEN)), Span::styled("", Style::default().fg(fade_color(TERMINAL_DIM_GREEN))),
])); ]));
// Draw the sparkline chart // Draw the sparkline chart
@@ -851,16 +923,16 @@ impl RetroTui {
} }
} }
let color = if is_processing { TERMINAL_CYAN } else { TERMINAL_GREEN }; let color = if is_processing { fade_color(TERMINAL_CYAN) } else { fade_color(TERMINAL_GREEN) };
lines.push(Line::from(Span::styled(chart_line, Style::default().fg(color)))); lines.push(Line::from(Span::styled(chart_line, Style::default().fg(color))));
// Add bottom axis // Add bottom axis
lines.push(Line::from(vec![ lines.push(Line::from(vec![
Span::styled(" 0", Style::default().fg(TERMINAL_AMBER)), Span::styled(" 0", Style::default().fg(fade_color(TERMINAL_AMBER))),
Span::styled("", Style::default().fg(TERMINAL_DIM_GREEN)), Span::styled("", Style::default().fg(fade_color(TERMINAL_DIM_GREEN))),
Span::styled( Span::styled(
format!("{}T (seconds)", "".repeat(chart_width.saturating_sub(15))), format!("{}T (seconds)", "".repeat(chart_width.saturating_sub(15))),
Style::default().fg(TERMINAL_DIM_GREEN), Style::default().fg(fade_color(TERMINAL_DIM_GREEN)),
), ),
])); ]));
} }