steer_tui/tui/widgets/formatters/
fetch.rs1use 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!("{}...", ¶ms.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 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 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}