shodh_memory/memory/
context.rs1use super::types::*;
4use anyhow::Result;
5use chrono::{Datelike, Timelike, Utc};
6use std::collections::HashMap;
7use uuid::Uuid;
8
9#[allow(unused)] pub struct ContextBuilder {
15 context: RichContext,
16}
17
18#[allow(unused)] impl Default for ContextBuilder {
20 fn default() -> Self {
21 Self::new()
22 }
23}
24
25#[allow(unused)] impl ContextBuilder {
27 pub fn new() -> Self {
29 Self {
30 context: RichContext {
31 id: ContextId(Uuid::new_v4()),
32 conversation: ConversationContext::default(),
33 user: UserContext::default(),
34 project: ProjectContext::default(),
35 temporal: TemporalContext::default(),
36 semantic: SemanticContext::default(),
37 code: CodeContext::default(),
38 document: DocumentContext::default(),
39 environment: EnvironmentContext::default(),
40 emotional: EmotionalContext::default(),
42 source: SourceContext::default(),
43 episode: EpisodeContext::default(),
44 parent: None,
45 embeddings: None,
46 decay_rate: 0.1, created_at: Utc::now(),
48 updated_at: Utc::now(),
49 },
50 }
51 }
52
53 pub fn with_conversation(mut self, conv: ConversationContext) -> Self {
55 self.context.conversation = conv;
56 self
57 }
58
59 pub fn with_user(mut self, user: UserContext) -> Self {
61 self.context.user = user;
62 self
63 }
64
65 pub fn with_project(mut self, project: ProjectContext) -> Self {
67 self.context.project = project;
68 self
69 }
70
71 pub fn with_code(mut self, code: CodeContext) -> Self {
73 self.context.code = code;
74 self
75 }
76
77 pub fn with_semantic(mut self, semantic: SemanticContext) -> Self {
79 self.context.semantic = semantic;
80 self
81 }
82
83 pub fn with_document(mut self, doc: DocumentContext) -> Self {
85 self.context.document = doc;
86 self
87 }
88
89 pub fn with_parent(mut self, parent: RichContext) -> Self {
91 self.context.parent = Some(Box::new(parent));
92 self
93 }
94
95 pub fn with_decay_rate(mut self, rate: f32) -> Self {
97 self.context.decay_rate = rate;
98 self
99 }
100
101 pub fn with_temporal(mut self, temporal: TemporalContext) -> Self {
103 self.context.temporal = temporal;
104 self
105 }
106
107 pub fn with_emotional(mut self, emotional: EmotionalContext) -> Self {
111 self.context.emotional = emotional;
112 self
113 }
114
115 pub fn with_source(mut self, source: SourceContext) -> Self {
117 self.context.source = source;
118 self
119 }
120
121 pub fn with_episode(mut self, episode: EpisodeContext) -> Self {
123 self.context.episode = episode;
124 self
125 }
126
127 pub fn build(self) -> RichContext {
129 self.context
130 }
131}
132
133#[allow(unused)] pub struct ContextManager {
139 current_context: Option<RichContext>,
141
142 context_history: Vec<RichContext>,
144 max_history: usize,
145
146 user_profile: UserContext,
148
149 project_state: HashMap<String, ProjectContext>,
151}
152
153#[allow(unused)] impl Default for ContextManager {
155 fn default() -> Self {
156 Self::new()
157 }
158}
159
160#[allow(unused)] impl ContextManager {
162 pub fn new() -> Self {
164 Self {
165 current_context: None,
166 context_history: Vec::new(),
167 max_history: 50,
168 user_profile: UserContext::default(),
169 project_state: HashMap::new(),
170 }
171 }
172
173 pub fn capture_context(&mut self) -> Result<RichContext> {
175 let mut builder = ContextBuilder::new();
176
177 let now = Utc::now();
179 let temporal = TemporalContext {
180 time_of_day: Some(format!("{:02}:{:02}", now.hour(), now.minute())),
181 day_of_week: Some(now.weekday().to_string()),
182 session_duration_minutes: self.calculate_session_duration(),
183 time_since_last_interaction: self.calculate_time_since_last(),
184 patterns: self.detect_temporal_patterns(),
185 trends: Vec::new(),
186 };
187
188 builder = builder
189 .with_temporal(temporal)
190 .with_user(self.user_profile.clone());
191
192 if let Some(parent) = &self.current_context {
194 builder = builder.with_parent(parent.clone());
195 }
196
197 let context = builder.build();
199
200 self.current_context = Some(context.clone());
202
203 self.context_history.push(context.clone());
205 if self.context_history.len() > self.max_history {
206 self.context_history.remove(0);
207 }
208
209 Ok(context)
210 }
211
212 pub fn update_user_profile(&mut self, experience: &Experience) {
214 for entity in &experience.entities {
216 if !self.user_profile.expertise.contains(entity) {
217 let count = self
219 .context_history
220 .iter()
221 .filter(|ctx| ctx.conversation.mentioned_entities.contains(entity))
222 .count();
223
224 if count > 5 {
225 self.user_profile.expertise.push(entity.clone());
226 }
227 }
228 }
229
230 if let Some(ctx) = &experience.context {
232 for (key, value) in &ctx.user.preferences {
233 self.user_profile
234 .preferences
235 .insert(key.clone(), value.clone());
236 }
237 }
238 }
239
240 pub fn update_conversation(
242 &mut self,
243 conv_id: String,
244 message: String,
245 topic: Option<String>,
246 ) -> Result<()> {
247 if let Some(ref mut ctx) = self.current_context {
248 ctx.conversation.conversation_id = Some(conv_id);
249 ctx.conversation.recent_messages.push(message.clone());
250
251 if ctx.conversation.recent_messages.len() > 10 {
253 ctx.conversation.recent_messages.remove(0);
254 }
255
256 if let Some(t) = topic {
257 ctx.conversation.topic = Some(t);
258 }
259
260 ctx.updated_at = Utc::now();
261 }
262 Ok(())
263 }
264
265 pub fn update_code_context(&mut self, file: String, scope: Option<String>) -> Result<()> {
267 if let Some(ref mut ctx) = self.current_context {
268 ctx.code.current_file = Some(file.clone());
269 ctx.code.current_scope = scope;
270
271 if !ctx.code.recent_edits.contains(&file) {
273 ctx.code.recent_edits.push(file);
274 }
275
276 if ctx.code.recent_edits.len() > 20 {
278 ctx.code.recent_edits.remove(0);
279 }
280
281 ctx.updated_at = Utc::now();
282 }
283 Ok(())
284 }
285
286 pub fn update_project_context(
288 &mut self,
289 project_id: String,
290 project: ProjectContext,
291 ) -> Result<()> {
292 self.project_state
293 .insert(project_id.clone(), project.clone());
294
295 if let Some(ref mut ctx) = self.current_context {
296 ctx.project = project;
297 ctx.updated_at = Utc::now();
298 }
299 Ok(())
300 }
301
302 pub fn get_current_context(&self) -> Option<&RichContext> {
304 self.current_context.as_ref()
305 }
306
307 pub fn merge_contexts(&self, contexts: Vec<RichContext>) -> RichContext {
309 if contexts.is_empty() {
310 return ContextBuilder::new().build();
311 }
312
313 let mut merged = contexts[0].clone();
314
315 for ctx in contexts.iter().skip(1) {
316 merged
318 .conversation
319 .recent_messages
320 .extend(ctx.conversation.recent_messages.clone());
321 merged
322 .conversation
323 .mentioned_entities
324 .extend(ctx.conversation.mentioned_entities.clone());
325 merged
326 .conversation
327 .active_intents
328 .extend(ctx.conversation.active_intents.clone());
329
330 merged
332 .code
333 .related_files
334 .extend(ctx.code.related_files.clone());
335 merged
336 .code
337 .recent_edits
338 .extend(ctx.code.recent_edits.clone());
339 merged.code.patterns.extend(ctx.code.patterns.clone());
340
341 merged
343 .semantic
344 .concepts
345 .extend(ctx.semantic.concepts.clone());
346 merged
347 .semantic
348 .related_concepts
349 .extend(ctx.semantic.related_concepts.clone());
350 merged.semantic.tags.extend(ctx.semantic.tags.clone());
351
352 merged.conversation.mentioned_entities.sort();
354 merged.conversation.mentioned_entities.dedup();
355 merged.code.related_files.sort();
356 merged.code.related_files.dedup();
357 merged.semantic.concepts.sort();
358 merged.semantic.concepts.dedup();
359 }
360
361 merged.updated_at = Utc::now();
362 merged
363 }
364
365 pub fn find_similar_contexts(
367 &self,
368 query_context: &RichContext,
369 top_k: usize,
370 ) -> Vec<RichContext> {
371 let mut scored_contexts: Vec<(f32, RichContext)> = self
372 .context_history
373 .iter()
374 .map(|ctx| {
375 let score = self.calculate_context_similarity(query_context, ctx);
376 (score, ctx.clone())
377 })
378 .collect();
379
380 scored_contexts.sort_by(|a, b| b.0.total_cmp(&a.0));
381 scored_contexts
382 .into_iter()
383 .take(top_k)
384 .map(|(_, ctx)| ctx)
385 .collect()
386 }
387
388 fn calculate_context_similarity(&self, ctx1: &RichContext, ctx2: &RichContext) -> f32 {
390 let mut score = 0.0;
391
392 let conv_overlap = ctx1
394 .conversation
395 .mentioned_entities
396 .iter()
397 .filter(|e| ctx2.conversation.mentioned_entities.contains(e))
398 .count();
399 score += conv_overlap as f32 * 0.2;
400
401 if ctx1.project.project_id == ctx2.project.project_id {
403 score += 0.3;
404 }
405
406 let code_overlap = ctx1
408 .code
409 .related_files
410 .iter()
411 .filter(|f| ctx2.code.related_files.contains(f))
412 .count();
413 score += code_overlap as f32 * 0.15;
414
415 let concept_overlap = ctx1
417 .semantic
418 .concepts
419 .iter()
420 .filter(|c| ctx2.semantic.concepts.contains(c))
421 .count();
422 score += concept_overlap as f32 * 0.25;
423
424 let time_diff = (Utc::now() - ctx2.created_at).num_hours() as f32;
426 let decay_factor = (-ctx2.decay_rate * time_diff / 24.0).exp();
427 score *= decay_factor;
428
429 score
430 }
431
432 fn calculate_session_duration(&self) -> Option<u32> {
434 self.context_history.first().map(|first| {
435 let duration = Utc::now() - first.created_at;
436 duration.num_minutes() as u32
437 })
438 }
439
440 fn calculate_time_since_last(&self) -> Option<i64> {
442 self.context_history
443 .last()
444 .map(|last| (Utc::now() - last.updated_at).num_seconds())
445 }
446
447 fn detect_temporal_patterns(&self) -> Vec<TimePattern> {
449 Vec::new()
451 }
452
453 pub fn get_user_expertise(&self) -> &Vec<String> {
455 &self.user_profile.expertise
456 }
457
458 pub fn add_user_expertise(&mut self, entity: String) {
460 if !self.user_profile.expertise.contains(&entity) {
461 self.user_profile.expertise.push(entity);
462 }
463 }
464}
465
466#[cfg(test)]
467mod tests {
468 use super::*;
469
470 #[test]
471 fn test_context_builder() {
472 let ctx = ContextBuilder::new()
473 .with_conversation(ConversationContext {
474 topic: Some("Testing".to_string()),
475 ..Default::default()
476 })
477 .build();
478
479 assert_eq!(ctx.conversation.topic, Some("Testing".to_string()));
480 }
481
482 #[test]
483 fn test_context_manager() {
484 let mut manager = ContextManager::new();
485 let ctx = manager.capture_context().unwrap();
486
487 assert!(ctx.temporal.time_of_day.is_some());
488 }
489}