ricecoder_tui/
image_integration.rs1use std::path::PathBuf;
13
14pub struct ImageIntegration {
27 pub enabled: bool,
29 pub max_images_per_prompt: usize,
31 pub current_images: Vec<PathBuf>,
33}
34
35impl ImageIntegration {
36 pub fn new() -> Self {
38 Self {
39 enabled: true,
40 max_images_per_prompt: 10,
41 current_images: Vec::new(),
42 }
43 }
44
45 pub fn handle_drag_drop_event(&mut self, paths: Vec<PathBuf>) -> (Vec<PathBuf>, Vec<String>) {
60 let mut added = Vec::new();
61 let mut errors = Vec::new();
62
63 for path in paths {
64 if self.current_images.len() >= self.max_images_per_prompt {
66 errors.push(format!(
67 "Maximum number of images ({}) reached",
68 self.max_images_per_prompt
69 ));
70 break;
71 }
72
73 if self.current_images.contains(&path) {
75 errors.push(format!("Image already in context: {}", path.display()));
76 continue;
77 }
78
79 if !path.exists() {
81 errors.push(format!("File does not exist: {}", path.display()));
82 continue;
83 }
84
85 if !path.is_file() {
86 errors.push(format!("Path is not a file: {}", path.display()));
87 continue;
88 }
89
90 self.current_images.push(path.clone());
92 added.push(path);
93 }
94
95 (added, errors)
96 }
97
98 pub fn remove_image(&mut self, path: &PathBuf) -> bool {
108 if let Some(pos) = self.current_images.iter().position(|p| p == path) {
109 self.current_images.remove(pos);
110 true
111 } else {
112 false
113 }
114 }
115
116 pub fn clear_images(&mut self) {
118 self.current_images.clear();
119 }
120
121 pub fn get_images(&self) -> &[PathBuf] {
123 &self.current_images
124 }
125
126 pub fn has_images(&self) -> bool {
128 !self.current_images.is_empty()
129 }
130
131 pub fn image_count(&self) -> usize {
133 self.current_images.len()
134 }
135
136 pub fn enable(&mut self) {
138 self.enabled = true;
139 }
140
141 pub fn disable(&mut self) {
143 self.enabled = false;
144 }
145
146 pub fn set_max_images(&mut self, max: usize) {
148 self.max_images_per_prompt = max;
149 }
150}
151
152impl Default for ImageIntegration {
153 fn default() -> Self {
154 Self::new()
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use tempfile::NamedTempFile;
162
163 #[test]
164 fn test_image_integration_creation() {
165 let integration = ImageIntegration::new();
166 assert!(integration.enabled);
167 assert_eq!(integration.max_images_per_prompt, 10);
168 assert_eq!(integration.current_images.len(), 0);
169 }
170
171 #[test]
172 fn test_handle_drag_drop_event_single_file() {
173 let mut integration = ImageIntegration::new();
174 let temp_file = NamedTempFile::new().unwrap();
175 let path = temp_file.path().to_path_buf();
176
177 let (added, errors) = integration.handle_drag_drop_event(vec![path.clone()]);
178
179 assert_eq!(added.len(), 1);
180 assert_eq!(errors.len(), 0);
181 assert_eq!(integration.current_images.len(), 1);
182 assert_eq!(integration.current_images[0], path);
183 }
184
185 #[test]
186 fn test_handle_drag_drop_event_multiple_files() {
187 let mut integration = ImageIntegration::new();
188 let temp_file1 = NamedTempFile::new().unwrap();
189 let temp_file2 = NamedTempFile::new().unwrap();
190 let path1 = temp_file1.path().to_path_buf();
191 let path2 = temp_file2.path().to_path_buf();
192
193 let (added, errors) = integration.handle_drag_drop_event(vec![path1.clone(), path2.clone()]);
194
195 assert_eq!(added.len(), 2);
196 assert_eq!(errors.len(), 0);
197 assert_eq!(integration.current_images.len(), 2);
198 }
199
200 #[test]
201 fn test_handle_drag_drop_event_nonexistent_file() {
202 let mut integration = ImageIntegration::new();
203 let path = PathBuf::from("/nonexistent/image.png");
204
205 let (added, errors) = integration.handle_drag_drop_event(vec![path]);
206
207 assert_eq!(added.len(), 0);
208 assert_eq!(errors.len(), 1);
209 assert!(errors[0].contains("does not exist"));
210 }
211
212 #[test]
213 fn test_handle_drag_drop_event_directory() {
214 let mut integration = ImageIntegration::new();
215 let temp_dir = tempfile::tempdir().unwrap();
216 let path = temp_dir.path().to_path_buf();
217
218 let (added, errors) = integration.handle_drag_drop_event(vec![path]);
219
220 assert_eq!(added.len(), 0);
221 assert_eq!(errors.len(), 1);
222 assert!(errors[0].contains("not a file"));
223 }
224
225 #[test]
226 fn test_handle_drag_drop_event_duplicate() {
227 let mut integration = ImageIntegration::new();
228 let temp_file = NamedTempFile::new().unwrap();
229 let path = temp_file.path().to_path_buf();
230
231 let (added1, errors1) = integration.handle_drag_drop_event(vec![path.clone()]);
233 assert_eq!(added1.len(), 1);
234 assert_eq!(errors1.len(), 0);
235
236 let (added2, errors2) = integration.handle_drag_drop_event(vec![path]);
238 assert_eq!(added2.len(), 0);
239 assert_eq!(errors2.len(), 1);
240 assert!(errors2[0].contains("already in context"));
241 }
242
243 #[test]
244 fn test_handle_drag_drop_event_max_images() {
245 let mut integration = ImageIntegration::new();
246 integration.set_max_images(2);
247
248 let temp_file1 = NamedTempFile::new().unwrap();
249 let temp_file2 = NamedTempFile::new().unwrap();
250 let temp_file3 = NamedTempFile::new().unwrap();
251
252 let path1 = temp_file1.path().to_path_buf();
253 let path2 = temp_file2.path().to_path_buf();
254 let path3 = temp_file3.path().to_path_buf();
255
256 let (added1, errors1) = integration.handle_drag_drop_event(vec![path1, path2]);
258 assert_eq!(added1.len(), 2);
259 assert_eq!(errors1.len(), 0);
260
261 let (added2, errors2) = integration.handle_drag_drop_event(vec![path3]);
263 assert_eq!(added2.len(), 0);
264 assert_eq!(errors2.len(), 1);
265 assert!(errors2[0].contains("Maximum number of images"));
266 }
267
268 #[test]
269 fn test_remove_image() {
270 let mut integration = ImageIntegration::new();
271 let temp_file = NamedTempFile::new().unwrap();
272 let path = temp_file.path().to_path_buf();
273
274 integration.handle_drag_drop_event(vec![path.clone()]);
275 assert_eq!(integration.current_images.len(), 1);
276
277 let removed = integration.remove_image(&path);
278 assert!(removed);
279 assert_eq!(integration.current_images.len(), 0);
280 }
281
282 #[test]
283 fn test_remove_image_not_found() {
284 let mut integration = ImageIntegration::new();
285 let path = PathBuf::from("/nonexistent/image.png");
286
287 let removed = integration.remove_image(&path);
288 assert!(!removed);
289 }
290
291 #[test]
292 fn test_clear_images() {
293 let mut integration = ImageIntegration::new();
294 let temp_file1 = NamedTempFile::new().unwrap();
295 let temp_file2 = NamedTempFile::new().unwrap();
296
297 integration.handle_drag_drop_event(vec![
298 temp_file1.path().to_path_buf(),
299 temp_file2.path().to_path_buf(),
300 ]);
301 assert_eq!(integration.current_images.len(), 2);
302
303 integration.clear_images();
304 assert_eq!(integration.current_images.len(), 0);
305 }
306
307 #[test]
308 fn test_get_images() {
309 let mut integration = ImageIntegration::new();
310 let temp_file = NamedTempFile::new().unwrap();
311 let path = temp_file.path().to_path_buf();
312
313 integration.handle_drag_drop_event(vec![path.clone()]);
314
315 let images = integration.get_images();
316 assert_eq!(images.len(), 1);
317 assert_eq!(images[0], path);
318 }
319
320 #[test]
321 fn test_has_images() {
322 let mut integration = ImageIntegration::new();
323 assert!(!integration.has_images());
324
325 let temp_file = NamedTempFile::new().unwrap();
326 integration.handle_drag_drop_event(vec![temp_file.path().to_path_buf()]);
327 assert!(integration.has_images());
328 }
329
330 #[test]
331 fn test_image_count() {
332 let mut integration = ImageIntegration::new();
333 assert_eq!(integration.image_count(), 0);
334
335 let temp_file1 = NamedTempFile::new().unwrap();
336 let temp_file2 = NamedTempFile::new().unwrap();
337
338 integration.handle_drag_drop_event(vec![
339 temp_file1.path().to_path_buf(),
340 temp_file2.path().to_path_buf(),
341 ]);
342 assert_eq!(integration.image_count(), 2);
343 }
344
345 #[test]
346 fn test_enable_disable() {
347 let mut integration = ImageIntegration::new();
348 assert!(integration.enabled);
349
350 integration.disable();
351 assert!(!integration.enabled);
352
353 integration.enable();
354 assert!(integration.enabled);
355 }
356
357 #[test]
358 fn test_set_max_images() {
359 let mut integration = ImageIntegration::new();
360 assert_eq!(integration.max_images_per_prompt, 10);
361
362 integration.set_max_images(5);
363 assert_eq!(integration.max_images_per_prompt, 5);
364 }
365}