supercode_frontend_model/
lib.rs1mod capabilities;
10mod composer;
11mod paste_burst;
12mod transcript;
13
14pub use capabilities::ComposerCapabilities;
15pub use composer::{
16 ComposerAction, ComposerInputEvent, ComposerKey, ComposerKeyCode, ComposerModel,
17 ComposerOverlay, ComposerOverlayKind, ComposerTurnState,
18};
19pub use paste_burst::{CharDecision, FlushResult, PasteBurst, RetroGrab};
20pub use transcript::{CellState, TranscriptCell, TranscriptKind, TranscriptModel};
21
22use serde_json::Value;
23
24pub fn semantic_value_summary(value: &Value, label: &str, limit: usize) -> String {
27 for field in [
28 "summary", "prompt", "message", "question", "tool", "command", "reason", "detail", "error",
29 "name",
30 ] {
31 if let Some(text) = value.get(field).and_then(Value::as_str) {
32 return bounded_text(text, limit);
33 }
34 }
35
36 let summary = match value {
37 Value::Object(fields) if fields.is_empty() => format!("{label} (no details)"),
38 Value::Object(fields) => {
39 let mut names = fields
40 .keys()
41 .take(6)
42 .map(|name| bounded_metadata(name, 32))
43 .collect::<Vec<_>>();
44 if fields.len() > names.len() {
45 names.push("…".into());
46 }
47 format!("{label} (fields: {})", names.join(", "))
48 }
49 Value::Array(items) => format!("{label} ({} items)", items.len()),
50 Value::String(text) => bounded_text(text, limit),
51 Value::Number(number) => format!("{label}: {number}"),
52 Value::Bool(value) => format!("{label}: {value}"),
53 Value::Null => format!("{label} (no details)"),
54 };
55 bounded_text(&summary, limit)
56}
57
58fn bounded_metadata(input: &str, limit: usize) -> String {
59 let flattened = strip_controls(input)
60 .chars()
61 .map(|character| match character {
62 '\n' | '\t' => ' ',
63 character => character,
64 })
65 .collect::<String>();
66 bounded_text(&flattened, limit)
67}
68
69fn bounded_text(input: &str, limit: usize) -> String {
70 let sanitized = strip_controls(input);
71 if sanitized.chars().count() <= limit {
72 return sanitized;
73 }
74 let mut preview = sanitized
75 .chars()
76 .take(limit.saturating_sub(1))
77 .collect::<String>();
78 preview.push('…');
79 preview
80}
81
82fn strip_controls(input: &str) -> String {
83 #[derive(Clone, Copy)]
84 enum State {
85 Text,
86 Escape,
87 Csi,
88 Osc,
89 OscEscape,
90 }
91
92 let mut state = State::Text;
93 let mut output = String::with_capacity(input.len());
94 for character in input.chars() {
95 state = match state {
96 State::Text if character == '\u{1b}' => State::Escape,
97 State::Text => {
98 if character == '\n' || character == '\t' || !character.is_control() {
99 output.push(character);
100 }
101 State::Text
102 }
103 State::Escape if character == '[' => State::Csi,
104 State::Escape if character == ']' => State::Osc,
105 State::Escape => State::Text,
106 State::Csi if ('@'..='~').contains(&character) => State::Text,
107 State::Csi => State::Csi,
108 State::Osc if character == '\u{7}' => State::Text,
109 State::Osc if character == '\u{1b}' => State::OscEscape,
110 State::Osc => State::Osc,
111 State::OscEscape if character == '\\' => State::Text,
112 State::OscEscape if character == '\u{1b}' => State::OscEscape,
113 State::OscEscape => State::Osc,
114 };
115 }
116 output
117}