ricecoder_modes/
thinking_display.rs1use crate::error::Result;
2use crate::models::{ModeAction, ModeResponse, ThinkingDepth};
3
4#[derive(Debug, Clone)]
6pub struct ThinkingDisplay;
7
8impl ThinkingDisplay {
9 pub fn format_thinking(content: &str, depth: ThinkingDepth) -> String {
11 let header = match depth {
12 ThinkingDepth::Light => "š Quick Thinking",
13 ThinkingDepth::Medium => "š§ Thinking",
14 ThinkingDepth::Deep => "š¬ Deep Analysis",
15 };
16
17 format!(
18 "{}\n{}\n{}\n{}",
19 "ā".repeat(50),
20 header,
21 "ā".repeat(50),
22 content
23 )
24 }
25
26 pub fn add_thinking_to_response(
28 response: &mut ModeResponse,
29 thinking_content: &str,
30 depth: ThinkingDepth,
31 ) -> Result<()> {
32 response.metadata.think_more_used = true;
34 response.metadata.thinking_content = Some(thinking_content.to_string());
35
36 let formatted = Self::format_thinking(thinking_content, depth);
38 response.add_action(ModeAction::DisplayThinking { content: formatted });
39
40 Ok(())
41 }
42
43 pub fn format_thinking_with_line_numbers(content: &str, depth: ThinkingDepth) -> String {
45 let header = match depth {
46 ThinkingDepth::Light => "š Quick Thinking",
47 ThinkingDepth::Medium => "š§ Thinking",
48 ThinkingDepth::Deep => "š¬ Deep Analysis",
49 };
50
51 let lines: Vec<&str> = content.lines().collect();
52 let max_line_num = lines.len().to_string().len();
53
54 let formatted_lines: Vec<String> = lines
55 .iter()
56 .enumerate()
57 .map(|(i, line)| format!("{:width$} | {}", i + 1, line, width = max_line_num))
58 .collect();
59
60 format!(
61 "{}\n{}\n{}\n{}",
62 "ā".repeat(50),
63 header,
64 "ā".repeat(50),
65 formatted_lines.join("\n")
66 )
67 }
68
69 pub fn format_thinking_collapsible(content: &str, depth: ThinkingDepth) -> String {
71 let header = match depth {
72 ThinkingDepth::Light => "š Quick Thinking",
73 ThinkingDepth::Medium => "š§ Thinking",
74 ThinkingDepth::Deep => "š¬ Deep Analysis",
75 };
76
77 format!("ā¼ {}\n{}\n{}", header, "ā".repeat(50), content)
78 }
79
80 pub fn extract_insights(content: &str) -> Vec<String> {
82 content
83 .lines()
84 .filter(|line| {
85 let trimmed = line.trim();
86 trimmed.starts_with("ā")
88 || trimmed.starts_with("ā¢")
89 || trimmed.starts_with("ā")
90 || trimmed.starts_with("Key:")
91 || trimmed.starts_with("Conclusion:")
92 })
93 .map(|line| line.to_string())
94 .collect()
95 }
96
97 pub fn summarize_thinking(content: &str, max_lines: usize) -> String {
99 let lines: Vec<&str> = content.lines().collect();
100 if lines.len() <= max_lines {
101 return content.to_string();
102 }
103
104 let mut summary = lines[..max_lines].join("\n");
105 summary.push_str(&format!("\n... ({} more lines)", lines.len() - max_lines));
106 summary
107 }
108
109 pub fn format_thinking_with_emphasis(content: &str, depth: ThinkingDepth) -> String {
111 let header = match depth {
112 ThinkingDepth::Light => "š Quick Thinking",
113 ThinkingDepth::Medium => "š§ Thinking",
114 ThinkingDepth::Deep => "š¬ Deep Analysis",
115 };
116
117 let emphasized_lines: Vec<String> = content
118 .lines()
119 .map(|line| {
120 let trimmed = line.trim();
121 if trimmed.starts_with("ā") || trimmed.starts_with("ā") {
122 format!(" ā {}", trimmed)
123 } else if trimmed.starts_with("Key:") || trimmed.starts_with("Conclusion:") {
124 format!(" š {}", trimmed)
125 } else {
126 format!(" {}", trimmed)
127 }
128 })
129 .collect();
130
131 format!(
132 "{}\n{}\n{}\n{}",
133 "ā".repeat(50),
134 header,
135 "ā".repeat(50),
136 emphasized_lines.join("\n")
137 )
138 }
139
140 pub fn is_empty_or_minimal(content: &str) -> bool {
142 content.trim().is_empty() || content.lines().count() < 2
143 }
144
145 pub fn get_statistics(content: &str) -> ThinkingStatistics {
147 let lines = content.lines().count();
148 let words = content.split_whitespace().count();
149 let chars = content.len();
150
151 ThinkingStatistics {
152 lines,
153 words,
154 chars,
155 }
156 }
157}
158
159#[derive(Debug, Clone)]
161pub struct ThinkingStatistics {
162 pub lines: usize,
164 pub words: usize,
166 pub chars: usize,
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn test_format_thinking_light() {
176 let content = "This is a quick thought";
177 let formatted = ThinkingDisplay::format_thinking(content, ThinkingDepth::Light);
178 assert!(formatted.contains("š Quick Thinking"));
179 assert!(formatted.contains(content));
180 }
181
182 #[test]
183 fn test_format_thinking_medium() {
184 let content = "This is a medium thought";
185 let formatted = ThinkingDisplay::format_thinking(content, ThinkingDepth::Medium);
186 assert!(formatted.contains("š§ Thinking"));
187 assert!(formatted.contains(content));
188 }
189
190 #[test]
191 fn test_format_thinking_deep() {
192 let content = "This is a deep thought";
193 let formatted = ThinkingDisplay::format_thinking(content, ThinkingDepth::Deep);
194 assert!(formatted.contains("š¬ Deep Analysis"));
195 assert!(formatted.contains(content));
196 }
197
198 #[test]
199 fn test_format_thinking_with_line_numbers() {
200 let content = "Line 1\nLine 2\nLine 3";
201 let formatted =
202 ThinkingDisplay::format_thinking_with_line_numbers(content, ThinkingDepth::Medium);
203 assert!(formatted.contains("1 |"));
204 assert!(formatted.contains("2 |"));
205 assert!(formatted.contains("3 |"));
206 }
207
208 #[test]
209 fn test_format_thinking_collapsible() {
210 let content = "This is collapsible";
211 let formatted =
212 ThinkingDisplay::format_thinking_collapsible(content, ThinkingDepth::Medium);
213 assert!(formatted.contains("ā¼"));
214 assert!(formatted.contains("š§ Thinking"));
215 }
216
217 #[test]
218 fn test_extract_insights() {
219 let content = "Some text\nā Key insight 1\nMore text\n⢠Key insight 2\nā Conclusion";
220 let insights = ThinkingDisplay::extract_insights(content);
221 assert_eq!(insights.len(), 3);
222 assert!(insights[0].contains("ā"));
223 }
224
225 #[test]
226 fn test_summarize_thinking() {
227 let content = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5";
228 let summary = ThinkingDisplay::summarize_thinking(content, 3);
229 assert!(summary.contains("Line 1"));
230 assert!(summary.contains("Line 3"));
231 assert!(summary.contains("2 more lines"));
232 }
233
234 #[test]
235 fn test_format_thinking_with_emphasis() {
236 let content = "Normal line\nā Important point\nKey: This is important";
237 let formatted =
238 ThinkingDisplay::format_thinking_with_emphasis(content, ThinkingDepth::Medium);
239 assert!(formatted.contains("ā"));
240 assert!(formatted.contains("š"));
241 }
242
243 #[test]
244 fn test_is_empty_or_minimal() {
245 assert!(ThinkingDisplay::is_empty_or_minimal(""));
246 assert!(ThinkingDisplay::is_empty_or_minimal(" "));
247 assert!(!ThinkingDisplay::is_empty_or_minimal("Line 1\nLine 2"));
248 }
249
250 #[test]
251 fn test_get_statistics() {
252 let content = "Line 1\nLine 2\nLine 3";
253 let stats = ThinkingDisplay::get_statistics(content);
254 assert_eq!(stats.lines, 3);
255 assert!(stats.words > 0);
256 assert!(stats.chars > 0);
257 }
258
259 #[test]
260 fn test_add_thinking_to_response() {
261 let mut response = ModeResponse::new("Test response".to_string(), "test-mode".to_string());
262 ThinkingDisplay::add_thinking_to_response(
263 &mut response,
264 "Test thinking",
265 ThinkingDepth::Medium,
266 )
267 .unwrap();
268
269 assert!(response.metadata.think_more_used);
270 assert!(response.metadata.thinking_content.is_some());
271 assert_eq!(response.actions.len(), 1);
272 }
273}