1
- use chrono:: { Duration , Utc } ;
1
+ use chrono:: { DateTime , Duration , Utc } ;
2
2
use hypr_db_user:: Event ;
3
- use std:: collections:: HashMap ;
4
3
use std:: sync:: { Arc , Mutex } ;
5
4
use tauri_plugin_listener:: ListenerPluginExt ;
6
5
use tokio:: sync:: mpsc;
@@ -31,10 +30,98 @@ pub enum MeetingType {
31
30
Unknown ,
32
31
}
33
32
33
+ /// Timestamped signal entry for the circular buffer
34
+ #[ derive( Debug , Clone ) ]
35
+ struct TimestampedSignal {
36
+ signal : MeetingSignal ,
37
+ timestamp : DateTime < Utc > ,
38
+ }
39
+
40
+ /// Circular buffer for storing recent meeting signals
41
+ #[ derive( Debug , Clone ) ]
42
+ struct SignalBuffer {
43
+ buffer : Vec < Option < TimestampedSignal > > ,
44
+ head : usize ,
45
+ size : usize ,
46
+ capacity : usize ,
47
+ }
48
+
49
+ impl SignalBuffer {
50
+ fn new ( capacity : usize ) -> Self {
51
+ Self {
52
+ buffer : vec ! [ None ; capacity] ,
53
+ head : 0 ,
54
+ size : 0 ,
55
+ capacity,
56
+ }
57
+ }
58
+
59
+ fn push ( & mut self , signal : MeetingSignal ) {
60
+ let entry = TimestampedSignal {
61
+ signal,
62
+ timestamp : Utc :: now ( ) ,
63
+ } ;
64
+
65
+ self . buffer [ self . head ] = Some ( entry) ;
66
+ self . head = ( self . head + 1 ) % self . capacity ;
67
+ if self . size < self . capacity {
68
+ self . size += 1 ;
69
+ }
70
+ }
71
+
72
+ fn get_recent_signals ( & self , duration : Duration ) -> Vec < MeetingSignal > {
73
+ let cutoff = Utc :: now ( ) - duration;
74
+ let mut signals = Vec :: new ( ) ;
75
+
76
+ // Iterate through the buffer starting from the oldest entry
77
+ let start_idx = if self . size == self . capacity {
78
+ self . head
79
+ } else {
80
+ 0
81
+ } ;
82
+
83
+ for i in 0 ..self . size {
84
+ let idx = ( start_idx + i) % self . capacity ;
85
+ if let Some ( ref entry) = self . buffer [ idx] {
86
+ if entry. timestamp >= cutoff {
87
+ signals. push ( entry. signal . clone ( ) ) ;
88
+ }
89
+ }
90
+ }
91
+
92
+ signals
93
+ }
94
+
95
+ fn cleanup_old_signals ( & mut self , max_age : Duration ) {
96
+ let cutoff = Utc :: now ( ) - max_age;
97
+ let mut new_size = 0 ;
98
+
99
+ // Count valid signals (more recent than cutoff)
100
+ for i in 0 ..self . size {
101
+ let idx = if self . size == self . capacity {
102
+ ( self . head + i) % self . capacity
103
+ } else {
104
+ i
105
+ } ;
106
+
107
+ if let Some ( ref entry) = self . buffer [ idx] {
108
+ if entry. timestamp >= cutoff {
109
+ new_size += 1 ;
110
+ } else {
111
+ // Clear old entries
112
+ self . buffer [ idx] = None ;
113
+ }
114
+ }
115
+ }
116
+
117
+ self . size = new_size;
118
+ }
119
+ }
120
+
34
121
/// Intelligent meeting detector that combines multiple signals
35
122
#[ derive( Clone ) ]
36
123
pub struct MeetingDetector {
37
- signals : Arc < Mutex < HashMap < String , Vec < MeetingSignal > > > > ,
124
+ signal_buffer : Arc < Mutex < SignalBuffer > > ,
38
125
// Note: hypr_detect::Detector doesn't implement Clone, so we'll manage it differently
39
126
signal_tx : mpsc:: UnboundedSender < MeetingSignal > ,
40
127
signal_rx : Arc < Mutex < mpsc:: UnboundedReceiver < MeetingSignal > > > ,
@@ -43,18 +130,21 @@ pub struct MeetingDetector {
43
130
auto_record_threshold : Arc < Mutex < f64 > > ,
44
131
// App handle for triggering recording
45
132
app_handle : Arc < Mutex < Option < tauri:: AppHandle < tauri:: Wry > > > > ,
133
+ // Event data for temporal calculations
134
+ current_events : Arc < Mutex < Vec < Event > > > ,
46
135
}
47
136
48
137
impl Default for MeetingDetector {
49
138
fn default ( ) -> Self {
50
139
let ( signal_tx, signal_rx) = mpsc:: unbounded_channel ( ) ;
51
140
Self {
52
- signals : Arc :: new ( Mutex :: new ( HashMap :: new ( ) ) ) ,
141
+ signal_buffer : Arc :: new ( Mutex :: new ( SignalBuffer :: new ( 1000 ) ) ) , // Buffer for 1000 signals
53
142
signal_tx,
54
143
signal_rx : Arc :: new ( Mutex :: new ( signal_rx) ) ,
55
144
auto_record_enabled : Arc :: new ( Mutex :: new ( false ) ) ,
56
145
auto_record_threshold : Arc :: new ( Mutex :: new ( 0.7 ) ) ,
57
146
app_handle : Arc :: new ( Mutex :: new ( None ) ) ,
147
+ current_events : Arc :: new ( Mutex :: new ( Vec :: new ( ) ) ) ,
58
148
}
59
149
}
60
150
}
@@ -86,6 +176,13 @@ impl MeetingDetector {
86
176
}
87
177
}
88
178
179
+ /// Update the current events for temporal calculations
180
+ pub fn update_events ( & self , events : Vec < Event > ) {
181
+ if let Ok ( mut current_events) = self . current_events . lock ( ) {
182
+ * current_events = events;
183
+ }
184
+ }
185
+
89
186
/// Set auto-recording configuration
90
187
///
91
188
/// # Arguments
@@ -150,21 +247,16 @@ impl MeetingDetector {
150
247
151
248
/// Store signal for correlation analysis
152
249
fn store_signal ( & self , signal : MeetingSignal ) {
153
- let mut signals = self . signals . lock ( ) . expect ( "Failed to acquire signals lock" ) ;
154
- let now = chrono:: Utc :: now ( ) ;
155
- let time_key = now. timestamp ( ) . to_string ( ) ;
156
-
157
- // Store recent signals (last 10 minutes) for correlation
158
- let cutoff = now - Duration :: minutes ( 10 ) ;
159
- signals. retain ( |k, _| {
160
- k. parse :: < i64 > ( )
161
- . map_or ( false , |ts| ts >= cutoff. timestamp ( ) )
162
- } ) ;
250
+ let mut buffer = self
251
+ . signal_buffer
252
+ . lock ( )
253
+ . expect ( "Failed to acquire signal buffer lock" ) ;
163
254
164
- signals
165
- . entry ( time_key)
166
- . or_insert_with ( Vec :: new)
167
- . push ( signal) ;
255
+ // Add the signal to the circular buffer
256
+ buffer. push ( signal) ;
257
+
258
+ // Cleanup old signals (older than 10 minutes)
259
+ buffer. cleanup_old_signals ( Duration :: minutes ( 10 ) ) ;
168
260
}
169
261
170
262
/// Calculate enhanced confidence score with signal correlation
@@ -179,48 +271,63 @@ impl MeetingDetector {
179
271
let mut total_signals = vec ! [ current_signal. clone( ) ] ;
180
272
181
273
// Check for signal correlation patterns
182
- for signals_group in recent_signals. values ( ) {
183
- for signal in signals_group {
184
- total_signals. push ( signal. clone ( ) ) ;
185
-
186
- // Correlation bonuses for complementary signals
187
- match ( current_signal, signal) {
188
- // Mic + Calendar = Strong meeting indication
189
- ( MeetingSignal :: MicrophoneActive , MeetingSignal :: CalendarEvent ( _) )
190
- | ( MeetingSignal :: CalendarEvent ( _) , MeetingSignal :: MicrophoneActive ) => {
191
- correlation_bonus += 0.2 ;
192
- }
193
- // Browser + Mic = Active meeting
194
- ( MeetingSignal :: MicrophoneActive , MeetingSignal :: BrowserMeeting ( _) )
195
- | ( MeetingSignal :: BrowserMeeting ( _) , MeetingSignal :: MicrophoneActive ) => {
196
- correlation_bonus += 0.25 ;
197
- }
198
- // App + Mic = Active meeting
199
- ( MeetingSignal :: MicrophoneActive , MeetingSignal :: AppLaunched ( _) )
200
- | ( MeetingSignal :: AppLaunched ( _) , MeetingSignal :: MicrophoneActive ) => {
201
- correlation_bonus += 0.2 ;
202
- }
203
- // Calendar + Browser/App = Scheduled meeting starting
204
- ( MeetingSignal :: CalendarEvent ( _) , MeetingSignal :: BrowserMeeting ( _) )
205
- | ( MeetingSignal :: BrowserMeeting ( _) , MeetingSignal :: CalendarEvent ( _) )
206
- | ( MeetingSignal :: CalendarEvent ( _) , MeetingSignal :: AppLaunched ( _) )
207
- | ( MeetingSignal :: AppLaunched ( _) , MeetingSignal :: CalendarEvent ( _) ) => {
208
- correlation_bonus += 0.15 ;
209
- }
210
- // Multiple mic signals = sustained activity
211
- ( MeetingSignal :: MicrophoneActive , MeetingSignal :: MicrophoneActive ) => {
212
- correlation_bonus += 0.1 ;
213
- }
214
- _ => { }
274
+ for signal in & recent_signals {
275
+ total_signals. push ( signal. clone ( ) ) ;
276
+
277
+ // Correlation bonuses for complementary signals
278
+ match ( current_signal, signal) {
279
+ // Mic + Calendar = Strong meeting indication
280
+ ( MeetingSignal :: MicrophoneActive , MeetingSignal :: CalendarEvent ( _) )
281
+ | ( MeetingSignal :: CalendarEvent ( _) , MeetingSignal :: MicrophoneActive ) => {
282
+ correlation_bonus += 0.2 ;
283
+ }
284
+ // Browser + Mic = Active meeting
285
+ ( MeetingSignal :: MicrophoneActive , MeetingSignal :: BrowserMeeting ( _) )
286
+ | ( MeetingSignal :: BrowserMeeting ( _) , MeetingSignal :: MicrophoneActive ) => {
287
+ correlation_bonus += 0.25 ;
288
+ }
289
+ // App + Mic = Active meeting
290
+ ( MeetingSignal :: MicrophoneActive , MeetingSignal :: AppLaunched ( _) )
291
+ | ( MeetingSignal :: AppLaunched ( _) , MeetingSignal :: MicrophoneActive ) => {
292
+ correlation_bonus += 0.2 ;
293
+ }
294
+ // Calendar + Browser/App = Scheduled meeting starting
295
+ ( MeetingSignal :: CalendarEvent ( _) , MeetingSignal :: BrowserMeeting ( _) )
296
+ | ( MeetingSignal :: BrowserMeeting ( _) , MeetingSignal :: CalendarEvent ( _) )
297
+ | ( MeetingSignal :: CalendarEvent ( _) , MeetingSignal :: AppLaunched ( _) )
298
+ | ( MeetingSignal :: AppLaunched ( _) , MeetingSignal :: CalendarEvent ( _) ) => {
299
+ correlation_bonus += 0.15 ;
215
300
}
301
+ // Multiple mic signals = sustained activity
302
+ ( MeetingSignal :: MicrophoneActive , MeetingSignal :: MicrophoneActive ) => {
303
+ correlation_bonus += 0.1 ;
304
+ }
305
+ _ => { }
216
306
}
217
307
}
218
308
219
309
// Apply temporal proximity bonus for calendar events
220
310
let temporal_bonus = match current_signal {
221
- MeetingSignal :: CalendarEvent ( _) => {
222
- // This would need calendar integration to calculate actual time proximity
223
- 0.1 // Placeholder bonus
311
+ MeetingSignal :: CalendarEvent ( event_id) => {
312
+ // Calculate actual time proximity to event start
313
+ if let Ok ( events) = self . current_events . lock ( ) {
314
+ if let Some ( event) = events. iter ( ) . find ( |e| & e. id == event_id) {
315
+ let now = Utc :: now ( ) ;
316
+ let time_diff = ( now - event. start_date ) . num_minutes ( ) . abs ( ) ;
317
+
318
+ match time_diff {
319
+ 0 ..=2 => 0.25 , // Very close to start time - highest bonus
320
+ 3 ..=5 => 0.2 , // Close to start time
321
+ 6 ..=10 => 0.15 , // Nearby start time
322
+ 11 ..=15 => 0.1 , // Within reasonable window
323
+ _ => 0.05 , // Default small bonus for calendar events
324
+ }
325
+ } else {
326
+ 0.05 // Small bonus if event not found but still calendar signal
327
+ }
328
+ } else {
329
+ 0.05 // Small bonus if can't access events
330
+ }
224
331
}
225
332
_ => 0.0 ,
226
333
} ;
@@ -264,17 +371,12 @@ impl MeetingDetector {
264
371
}
265
372
266
373
/// Get recent signals within the specified duration
267
- fn get_recent_signals ( & self , duration : Duration ) -> HashMap < String , Vec < MeetingSignal > > {
268
- let signals = self . signals . lock ( ) . expect ( "Failed to acquire signals lock" ) ;
269
- let cutoff = chrono:: Utc :: now ( ) - duration;
270
- signals
271
- . iter ( )
272
- . filter ( |( k, _) | {
273
- k. parse :: < i64 > ( )
274
- . map_or ( false , |ts| ts >= cutoff. timestamp ( ) )
275
- } )
276
- . map ( |( k, v) | ( k. clone ( ) , v. clone ( ) ) )
277
- . collect ( )
374
+ fn get_recent_signals ( & self , duration : Duration ) -> Vec < MeetingSignal > {
375
+ let buffer = self
376
+ . signal_buffer
377
+ . lock ( )
378
+ . expect ( "Failed to acquire signal buffer lock" ) ;
379
+ buffer. get_recent_signals ( duration)
278
380
}
279
381
280
382
/// Determine meeting type based on signal composition
0 commit comments