Skip to main content

car_browser/perception/
formatting.rs

1//! UiMap formatting for LLM prompt consumption.
2//!
3//! Provides configurable formatters that serialize UiMap data into compact
4//! text that fits within token budgets.
5
6use super::ui_map::UiMap;
7
8/// Configurable UiMap formatter.
9pub struct UiMapFormatter {
10    /// Maximum elements to include.
11    pub max_elements: usize,
12    /// Whether to include bounds coordinates.
13    pub include_bounds: bool,
14    /// Whether to include state flags.
15    pub include_states: bool,
16    /// Whether to only show interactive elements.
17    pub interactive_only: bool,
18    /// Maximum name length before truncation.
19    pub max_name_length: usize,
20}
21
22impl UiMapFormatter {
23    /// Default formatter.
24    pub fn new() -> Self {
25        Self {
26            max_elements: 100,
27            include_bounds: true,
28            include_states: true,
29            interactive_only: false,
30            max_name_length: 50,
31        }
32    }
33
34    /// Compact formatter for token-constrained contexts.
35    pub fn compact() -> Self {
36        Self {
37            max_elements: 40,
38            include_bounds: true,
39            include_states: true,
40            interactive_only: true,
41            max_name_length: 30,
42        }
43    }
44
45    /// Format a UiMap to text.
46    pub fn format(&self, ui_map: &UiMap) -> String {
47        // Delegate to UiMap's format_compact for now — more modes can be added
48        // based on formatter configuration
49        if self.interactive_only && ui_map.elements.len() > self.max_elements {
50            let mut output = String::new();
51            let elements = ui_map.interactive_elements();
52            for element in elements.iter().take(self.max_elements) {
53                use std::fmt::Write;
54                let role_str = element.role.to_hash_string();
55                let name_str = element
56                    .name
57                    .as_deref()
58                    .map(|n| {
59                        if n.len() > self.max_name_length {
60                            let truncated: String = n.chars().take(self.max_name_length - 3).collect();
61                            format!(" \"{}...\"", truncated)
62                        } else {
63                            format!(" \"{}\"", n)
64                        }
65                    })
66                    .unwrap_or_default();
67                let _ = writeln!(output, "[{}] {}{}", element.id, role_str, name_str);
68            }
69            output
70        } else {
71            ui_map.format_compact()
72        }
73    }
74}
75
76impl Default for UiMapFormatter {
77    fn default() -> Self {
78        Self::new()
79    }
80}