use ratatui::{
buffer::Buffer,
layout::Rect,
style::{Color, Modifier, Style},
text::Span,
widgets::Widget,
};
use crate::widgets::ai_chat::components::theme::ChatColors;
use super::block_tool::BlockTool;
use super::inline_tool::ToolStatus;
pub struct ToolQuestion<'a> {
question: &'a str,
answer: Option<String>,
status: ToolStatus,
}
impl<'a> ToolQuestion<'a> {
pub fn new(question: &'a str) -> Self {
Self {
question,
answer: None,
status: ToolStatus::Pending,
}
}
pub fn answer(mut self, answer: Option<String>) -> Self {
self.answer = answer;
if answer.is_some() {
self.status = ToolStatus::Complete;
}
self
}
pub fn status(mut self, status: ToolStatus) -> Self {
self.status = status;
self
}
fn border_color(&self, colors: &ChatColors) -> Color {
match self.status {
ToolStatus::Pending => colors.warning,
ToolStatus::Complete => colors.success,
ToolStatus::Error => colors.error,
ToolStatus::PermissionPending => Color::Rgb(255, 165, 0),
}
}
pub fn render(&self, area: Rect, buf: &mut Buffer, colors: &ChatColors) {
if area.height < 1 {
return;
}
let max_y = area.y + area.height;
let mut y = area.y;
let border_color = self.border_color(colors);
for y_pos in area.y..max_y {
buf.get_mut(area.x, y_pos)
.set_char('│')
.set_style(Style::default().fg(border_color));
}
let icon = '→';
let status_text = match self.status {
ToolStatus::Pending => "Awaiting answer...",
ToolStatus::Complete => "Answered",
ToolStatus::Error => "Failed",
ToolStatus::PermissionPending => "Waiting...",
};
let header = format!("{} Question: {}", icon, status_text);
let header_span = Span::styled(
header,
Style::default()
.fg(colors.warning)
.add_modifier(Modifier::BOLD),
);
buf.set_span(area.x + 2, y, &header_span, area.width.saturating_sub(3));
y += 1;
if y < max_y {
buf.set_span(
area.x + 2,
y,
&Span::styled(
"─".repeat(25),
Style::default()
.fg(border_color)
.add_modifier(Modifier::DIM),
),
area.width.saturating_sub(3),
);
y += 1;
}
if y < max_y {
let q_span = Span::styled(
" Q:",
Style::default()
.fg(colors.primary)
.add_modifier(Modifier::BOLD),
);
buf.set_span(area.x + 2, y, &q_span, 4);
let question_text = if self.question.len() > area.width as usize - 8 {
format!("{}...", &self.question[..area.width as usize - 11])
} else {
self.question.to_string()
};
buf.set_span(
area.x + 6,
y,
&Span::styled(question_text, Style::default().fg(colors.text)),
area.width.saturating_sub(8),
);
y += 1;
}
if let Some(answer) = &self.answer {
if y < max_y {
let a_span = Span::styled(
" A:",
Style::default()
.fg(colors.success)
.add_modifier(Modifier::BOLD),
);
buf.set_span(area.x + 2, y, &a_span, 4);
let answer_text = if answer.len() > area.width as usize - 8 {
format!("{}...", &answer[..area.width as usize - 11])
} else {
answer.clone()
};
buf.set_span(
area.x + 6,
y,
&Span::styled(answer_text, Style::default().fg(colors.text)),
area.width.saturating_sub(8),
);
y += 1;
}
}
if self.answer.is_none() && y < max_y {
let waiting_span = Span::styled(
" ⏳ Please provide an answer...",
Style::default()
.fg(colors.text_muted)
.add_modifier(Modifier::ITALIC),
);
buf.set_span(area.x + 2, y, &waiting_span, area.width.saturating_sub(3));
y += 1;
}
}
}
impl Widget for ToolQuestion<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
let colors = ChatColors::default();
self.render(area, buf, &colors);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tool_question_basic() {
let question = ToolQuestion::new("What is your name?");
assert_eq!(question.question, "What is your name?");
}
#[test]
fn test_tool_question_with_answer() {
let question = ToolQuestion::new("What is 2+2?").answer(Some("4".to_string()));
assert!(question.answer.is_some());
assert_eq!(question.status, ToolStatus::Complete);
}
}