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