Skip to main content

steer_tui/tui/widgets/formatters/
fetch.rs

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