steer_tui/tui/widgets/formatters/
fetch.rs

1use super::{ToolFormatter, helpers::*};
2use crate::tui::theme::Theme;
3use ratatui::{
4    style::Style,
5    text::{Line, Span},
6};
7use serde_json::Value;
8use steer_core::app::conversation::ToolResult;
9use steer_core::tools::fetch::FetchParams;
10
11pub struct FetchFormatter;
12
13impl ToolFormatter for FetchFormatter {
14    fn compact(
15        &self,
16        params: &Value,
17        result: &Option<ToolResult>,
18        _wrap_width: usize,
19        theme: &Theme,
20    ) -> Vec<Line<'static>> {
21        let mut lines = Vec::new();
22
23        let Ok(params) = serde_json::from_value::<FetchParams>(params.clone()) else {
24            return vec![Line::from(Span::styled(
25                "Invalid fetch params",
26                theme.error_text(),
27            ))];
28        };
29
30        let info = match result {
31            Some(ToolResult::Fetch(fetch_result)) => format_size(fetch_result.content.len()),
32            Some(ToolResult::Error(_)) => "failed".to_string(),
33            Some(_) => "unexpected result type".to_string(),
34            None => "fetching...".to_string(),
35        };
36
37        let url_display = if params.url.len() > 50 {
38            format!("{}...", &params.url[..47])
39        } else {
40            params.url.clone()
41        };
42
43        lines.push(Line::from(vec![
44            Span::styled(format!("url={url_display} "), Style::default()),
45            Span::styled(format!("({info})"), theme.subtle_text()),
46        ]));
47
48        lines
49    }
50
51    fn detailed(
52        &self,
53        params: &Value,
54        result: &Option<ToolResult>,
55        wrap_width: usize,
56        theme: &Theme,
57    ) -> Vec<Line<'static>> {
58        let mut lines = Vec::new();
59
60        let Ok(params) = serde_json::from_value::<FetchParams>(params.clone()) else {
61            return vec![Line::from(Span::styled(
62                "Invalid fetch params",
63                theme.error_text(),
64            ))];
65        };
66
67        lines.push(Line::from(Span::styled("Fetch Parameters:", theme.text())));
68        lines.push(Line::from(Span::styled(
69            format!("  URL: {}", params.url),
70            Style::default(),
71        )));
72
73        // Show prompt in wrapped lines
74        lines.push(Line::from(Span::styled("  Prompt:", Style::default())));
75        for line in params.prompt.lines() {
76            for wrapped in textwrap::wrap(line, wrap_width.saturating_sub(4)) {
77                lines.push(Line::from(Span::styled(
78                    format!("    {wrapped}"),
79                    theme.dim_text(),
80                )));
81            }
82        }
83
84        // Show result if available
85        if let Some(result) = result {
86            match result {
87                ToolResult::Fetch(fetch_result) => {
88                    if !fetch_result.content.trim().is_empty() {
89                        lines.push(separator_line(wrap_width, theme.dim_text()));
90
91                        const MAX_OUTPUT_LINES: usize = 25;
92                        let (output_lines, truncated) =
93                            truncate_lines(&fetch_result.content, MAX_OUTPUT_LINES);
94
95                        for line in output_lines {
96                            for wrapped in textwrap::wrap(line, wrap_width) {
97                                lines.push(Line::from(Span::raw(wrapped.to_string())));
98                            }
99                        }
100
101                        if truncated {
102                            lines.push(Line::from(Span::styled(
103                                format!(
104                                    "... ({} more lines)",
105                                    fetch_result.content.lines().count() - MAX_OUTPUT_LINES
106                                ),
107                                theme.subtle_text(),
108                            )));
109                        }
110                    }
111                }
112                ToolResult::Error(error) => {
113                    lines.push(separator_line(wrap_width, theme.dim_text()));
114                    lines.push(Line::from(Span::styled(
115                        error.to_string(),
116                        theme.error_text(),
117                    )));
118                }
119                _ => {
120                    lines.push(Line::from(Span::styled(
121                        "Unexpected result type",
122                        theme.error_text(),
123                    )));
124                }
125            }
126        }
127
128        lines
129    }
130}