1use serde::{Deserialize, Serialize};
4
5fn default_quality_score() -> f32 {
6 0.0
7}
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct VisualObservation {
12 pub id: u64,
13 pub timestamp: u64,
14 pub session_id: u32,
15 pub source: CaptureSource,
16 pub embedding: Vec<f32>,
17 pub thumbnail: Vec<u8>,
18 pub metadata: ObservationMeta,
19 pub memory_link: Option<u64>,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24#[serde(tag = "type", rename_all = "snake_case")]
25pub enum CaptureSource {
26 File { path: String },
27 Base64 { mime: String },
28 Screenshot { region: Option<Rect> },
29 Clipboard,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct ObservationMeta {
35 pub width: u32,
36 pub height: u32,
37 pub original_width: u32,
38 pub original_height: u32,
39 pub labels: Vec<String>,
40 pub description: Option<String>,
41 #[serde(default = "default_quality_score")]
43 pub quality_score: f32,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct VisualDiff {
49 pub before_id: u64,
50 pub after_id: u64,
51 pub similarity: f32,
52 pub changed_regions: Vec<Rect>,
53 pub pixel_diff_ratio: f32,
54}
55
56#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
58pub struct Rect {
59 pub x: u32,
60 pub y: u32,
61 pub w: u32,
62 pub h: u32,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct SimilarityMatch {
68 pub id: u64,
69 pub similarity: f32,
70}
71
72#[derive(Debug, Clone)]
74pub struct VisualMemoryStore {
75 pub observations: Vec<VisualObservation>,
76 pub embedding_dim: u32,
77 pub next_id: u64,
78 pub session_count: u32,
79 pub created_at: u64,
80 pub updated_at: u64,
81}
82
83impl VisualMemoryStore {
84 pub fn new(embedding_dim: u32) -> Self {
86 let now = std::time::SystemTime::now()
87 .duration_since(std::time::UNIX_EPOCH)
88 .unwrap_or_default()
89 .as_secs();
90
91 Self {
92 observations: Vec::new(),
93 embedding_dim,
94 next_id: 1,
95 session_count: 0,
96 created_at: now,
97 updated_at: now,
98 }
99 }
100
101 pub fn get(&self, id: u64) -> Option<&VisualObservation> {
103 self.observations.iter().find(|o| o.id == id)
104 }
105
106 pub fn get_mut(&mut self, id: u64) -> Option<&mut VisualObservation> {
108 self.observations.iter_mut().find(|o| o.id == id)
109 }
110
111 pub fn add(&mut self, mut obs: VisualObservation) -> u64 {
113 let id = self.next_id;
114 obs.id = id;
115 self.next_id += 1;
116 self.updated_at = std::time::SystemTime::now()
117 .duration_since(std::time::UNIX_EPOCH)
118 .unwrap_or_default()
119 .as_secs();
120 self.observations.push(obs);
121 id
122 }
123
124 pub fn count(&self) -> usize {
126 self.observations.len()
127 }
128
129 pub fn by_session(&self, session_id: u32) -> Vec<&VisualObservation> {
131 self.observations
132 .iter()
133 .filter(|o| o.session_id == session_id)
134 .collect()
135 }
136
137 pub fn in_time_range(&self, start: u64, end: u64) -> Vec<&VisualObservation> {
139 self.observations
140 .iter()
141 .filter(|o| o.timestamp >= start && o.timestamp <= end)
142 .collect()
143 }
144
145 pub fn recent(&self, limit: usize) -> Vec<&VisualObservation> {
147 let mut sorted: Vec<_> = self.observations.iter().collect();
148 sorted.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
149 sorted.truncate(limit);
150 sorted
151 }
152}
153
154#[derive(thiserror::Error, Debug)]
156pub enum VisionError {
157 #[error("Image error: {0}")]
158 Image(#[from] image::ImageError),
159
160 #[error("IO error: {0}")]
161 Io(#[from] std::io::Error),
162
163 #[error("Embedding error: {0}")]
164 Embedding(String),
165
166 #[error("Storage error: {0}")]
167 Storage(String),
168
169 #[error("Capture not found: {0}")]
170 CaptureNotFound(u64),
171
172 #[error("Invalid input: {0}")]
173 InvalidInput(String),
174
175 #[error("Capture error: {0}")]
176 Capture(String),
177
178 #[error("Model not available: {0}")]
179 ModelNotAvailable(String),
180}
181
182pub type VisionResult<T> = Result<T, VisionError>;