1use serde::{Deserialize, Serialize};
2use std::collections::{HashMap, HashSet};
3use chrono::{DateTime, Utc};
4use std::path::PathBuf;
5use super::hardcode_config::HardcodeConfig;
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
9pub struct FocusPoint {
10 pub id: String,
12
13 pub topic: String,
16
17 pub keywords: Vec<String>,
20
21 pub entities: Vec<String>,
24
25 pub core_question: Option<String>,
28
29 pub status: FocusStatus,
31
32 pub created_at: DateTime<Utc>,
34
35 pub last_active: DateTime<Utc>,
37
38 pub importance: f32,
40
41 pub message_range: MessageRange,
43
44 pub sub_foci: Vec<String>,
46
47 pub semantic_summary: Option<String>,
51
52 pub related_files: Vec<PathBuf>,
54
55 pub confidence: f32,
57
58 pub dynamic_switch_threshold: f32,
60
61 pub focus_type: FocusType,
63
64 pub feedback_history: Vec<FocusFeedback>,
66}
67
68#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
70pub enum FocusType {
71 ProblemSolving,
73
74 TaskExecution,
76
77 KnowledgeExploration,
79
80 DecisionMaking,
82
83 CodeOptimization,
85
86 General,
88}
89
90impl Default for FocusType {
91 fn default() -> Self {
92 FocusType::General
93 }
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
98pub struct FocusFeedback {
99 pub timestamp: DateTime<Utc>,
101
102 pub feedback_type: FocusFeedbackType,
104
105 pub rating: Option<u8>,
107
108 pub message_index: usize,
110}
111
112#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
114pub enum FocusFeedbackType {
115 Confirmed,
117
118 AutoSwitched,
120
121 Completed,
123
124 Rejected,
126
127 TooBroad,
129
130 TooNarrow,
132}
133
134#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
136pub enum FocusStatus {
137 Active, Suspended, Completed, Abandoned, }
142
143impl std::fmt::Display for FocusStatus {
144 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145 match self {
146 FocusStatus::Active => write!(f, "Active"),
147 FocusStatus::Suspended => write!(f, "Suspended"),
148 FocusStatus::Completed => write!(f, "Completed"),
149 FocusStatus::Abandoned => write!(f, "Abandoned"),
150 }
151 }
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
156pub struct MessageRange {
157 pub start: usize,
158 pub end: usize,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct FocusManager {
164 pub current_focus_id: Option<String>,
166
167 pub foci: HashMap<String, FocusPoint>,
169
170 pub focus_history: Vec<String>,
172
173 pub config: FocusConfig,
175
176 pub focus_stack: Vec<String>,
181
182 pub active_foci: HashSet<String>,
184
185 pub focus_graph: HashMap<String, Vec<(String, f32)>>,
188
189 pub focus_tree: HashMap<String, Vec<String>>,
192
193 pub relevance_cache: HashMap<String, HashMap<String, f32>>,
196
197 pub cache_capacity: usize,
199
200 pub hardcode_config: HardcodeConfig,
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize)]
206pub struct FocusConfig {
207 pub max_active_foci: usize,
209
210 pub switch_threshold: f32,
212
213 pub max_history_foci: usize,
215
216 pub auto_suspend_after: usize,
218}
219
220impl Default for FocusConfig {
221 fn default() -> Self {
222 Self {
223 max_active_foci: 3,
224 switch_threshold: 0.3,
225 max_history_foci: 10,
226 auto_suspend_after: 5,
227 }
228 }
229}
230
231impl FocusPoint {
232 pub fn new(
234 id: String,
235 topic: String,
236 keywords: Vec<String>,
237 entities: Vec<String>,
238 core_question: Option<String>,
239 message_index: usize,
240 ) -> Self {
241 Self {
242 id,
243 topic,
244 keywords,
245 entities,
246 core_question,
247 status: FocusStatus::Active,
248 created_at: Utc::now(),
249 last_active: Utc::now(),
250 importance: 0.5,
251 message_range: MessageRange {
252 start: message_index,
253 end: message_index,
254 },
255 sub_foci: Vec::new(),
256 semantic_summary: None,
258 related_files: Vec::new(),
259 confidence: 0.5,
260 dynamic_switch_threshold: 0.3, focus_type: FocusType::default(),
262 feedback_history: Vec::new(),
263 }
264 }
265
266 pub fn new_with_ai(
268 id: String,
269 topic: String,
270 keywords: Vec<String>,
271 entities: Vec<String>,
272 core_question: Option<String>,
273 semantic_summary: Option<String>,
274 related_files: Vec<PathBuf>,
275 confidence: f32,
276 focus_type: FocusType,
277 message_index: usize,
278 ) -> Self {
279 Self {
280 id,
281 topic,
282 keywords,
283 entities,
284 core_question,
285 status: FocusStatus::Active,
286 created_at: Utc::now(),
287 last_active: Utc::now(),
288 importance: confidence, message_range: MessageRange {
290 start: message_index,
291 end: message_index,
292 },
293 sub_foci: Vec::new(),
294 semantic_summary,
295 related_files,
296 confidence,
297 dynamic_switch_threshold: Self::calculate_initial_threshold(confidence, focus_type),
298 focus_type,
299 feedback_history: Vec::new(),
300 }
301 }
302
303 fn calculate_initial_threshold(confidence: f32, focus_type: FocusType) -> f32 {
305 let base_threshold = 0.3;
306
307 let confidence_modifier = confidence * 0.2;
309
310 let type_modifier = match focus_type {
312 FocusType::ProblemSolving => 0.15, FocusType::TaskExecution => 0.1, FocusType::DecisionMaking => 0.2, FocusType::CodeOptimization => 0.15,
316 FocusType::KnowledgeExploration => 0.0, FocusType::General => 0.0,
318 };
319
320 base_threshold + confidence_modifier + type_modifier
321 }
322
323 pub fn adjust_threshold_from_feedback(&mut self) {
325 if self.feedback_history.is_empty() {
326 return;
327 }
328
329 let recent_feedbacks = self.feedback_history.iter().rev().take(10);
331 let mut confirmed_count = 0;
332 let mut rejected_count = 0;
333
334 for feedback in recent_feedbacks {
335 match feedback.feedback_type {
336 FocusFeedbackType::Confirmed => confirmed_count += 1,
337 FocusFeedbackType::Rejected => rejected_count += 1,
338 FocusFeedbackType::TooBroad => self.dynamic_switch_threshold *= 0.9, FocusFeedbackType::TooNarrow => self.dynamic_switch_threshold *= 1.1, _ => {}
341 }
342 }
343
344 if confirmed_count > rejected_count + 3 {
346 self.dynamic_switch_threshold = (self.dynamic_switch_threshold * 1.15).min(0.6);
347 }
348
349 if rejected_count > confirmed_count + 2 {
351 self.dynamic_switch_threshold = (self.dynamic_switch_threshold * 0.85).max(0.1);
352 }
353
354 log::debug!(
355 "Focus '{}' threshold adjusted to {:.2} based on {} feedbacks",
356 self.topic,
357 self.dynamic_switch_threshold,
358 self.feedback_history.len()
359 );
360 }
361
362 pub fn add_feedback(&mut self, feedback_type: FocusFeedbackType, rating: Option<u8>, message_index: usize) {
364 self.feedback_history.push(FocusFeedback {
365 timestamp: Utc::now(),
366 feedback_type,
367 rating,
368 message_index,
369 });
370
371 self.adjust_threshold_from_feedback();
373 }
374
375 pub fn should_switch(&self, relevance_score: f32) -> bool {
377 relevance_score < self.dynamic_switch_threshold
379 }
380
381 pub fn with_importance(mut self, importance: f32) -> Self {
383 self.importance = importance;
384 self
385 }
386
387 pub fn with_confidence(mut self, confidence: f32) -> Self {
389 self.confidence = confidence;
390 self.dynamic_switch_threshold = Self::calculate_initial_threshold(confidence, self.focus_type);
391 self
392 }
393
394 pub fn with_type(mut self, focus_type: FocusType) -> Self {
396 self.focus_type = focus_type;
397 self.dynamic_switch_threshold = Self::calculate_initial_threshold(self.confidence, focus_type);
398 self
399 }
400
401 pub fn effective_importance(&self) -> f32 {
408 let elapsed_minutes = (Utc::now() - self.last_active).num_seconds() as f32 / 60.0;
409
410 if elapsed_minutes < 0.0 {
412 return self.importance;
413 }
414
415 let half_life = 30.0;
417 let decay_factor = (-elapsed_minutes / half_life).exp();
418
419 self.importance * decay_factor
420 }
421
422 pub fn effective_confidence(&self) -> f32 {
424 if self.feedback_history.is_empty() {
425 return self.confidence;
426 }
427
428 let recent_feedbacks = self.feedback_history.iter().rev().take(10);
430 let mut positive_count = 0;
431 let mut negative_count = 0;
432
433 for feedback in recent_feedbacks {
434 match feedback.feedback_type {
435 FocusFeedbackType::Confirmed | FocusFeedbackType::Completed => positive_count += 1,
436 FocusFeedbackType::Rejected => negative_count += 1,
437 _ => {}
438 }
439 }
440
441 let adjustment = (positive_count as f32 - negative_count as f32) * 0.05;
443 (self.confidence + adjustment).clamp(0.1, 1.0)
444 }
445
446 pub fn wake_up(&mut self) {
448 self.last_active = Utc::now();
449 self.importance = (self.importance + 0.1).min(1.0); }
451
452 pub fn update_message_range(&mut self, message_index: usize) {
454 self.message_range.end = message_index;
455 }
456
457 pub fn add_keywords(&mut self, new_keywords: &[String]) {
459 for kw in new_keywords {
460 if !self.keywords.contains(kw) {
461 self.keywords.push(kw.clone());
462 }
463 }
464 }
465
466 pub fn add_entities(&mut self, new_entities: &[String]) {
468 for entity in new_entities {
469 if !self.entities.contains(entity) {
470 self.entities.push(entity.clone());
471 }
472 }
473 }
474}
475
476impl FocusManager {
477 pub fn new() -> Self {
478 let hardcode_config = HardcodeConfig::default();
479 Self {
480 current_focus_id: None,
481 foci: HashMap::new(),
482 focus_history: Vec::new(),
483 config: FocusConfig::default(),
484 focus_stack: Vec::new(),
486 active_foci: HashSet::new(),
487 focus_graph: HashMap::new(),
488 focus_tree: HashMap::new(),
489 relevance_cache: HashMap::new(),
490 cache_capacity: hardcode_config.focus_cache_capacity,
491 hardcode_config,
492 }
493 }
494
495 pub fn current_focus(&self) -> Option<&FocusPoint> {
497 self.current_focus_id
498 .as_ref()
499 .and_then(|id| self.foci.get(id))
500 }
501
502 pub fn current_focus_mut(&mut self) -> Option<&mut FocusPoint> {
504 self.current_focus_id
505 .as_ref()
506 .and_then(|id| self.foci.get_mut(id))
507 }
508
509 pub fn add_focus(&mut self, focus: FocusPoint) {
511 if self.foci.len() >= self.config.max_active_foci {
513 self.suspend_oldest_focus();
514 }
515
516 let focus_id = focus.id.clone();
517 self.foci.insert(focus_id.clone(), focus);
518 self.current_focus_id = Some(focus_id.clone());
519 self.focus_history.push(focus_id.clone());
520
521 self.focus_stack.push(focus_id.clone());
523 self.active_foci.insert(focus_id.clone());
524
525 if self.focus_history.len() > self.config.max_history_foci {
527 let removed = self.focus_history.remove(0);
528 if let Some(f) = self.foci.get_mut(&removed) {
529 f.status = FocusStatus::Abandoned;
530 }
531 self.focus_stack.retain(|id| id != &removed);
533 self.active_foci.remove(&removed);
534 }
535 }
536
537 pub fn switch_focus(&mut self, focus_id: &str) -> Option<()> {
539 if !self.foci.contains_key(focus_id) {
540 return None;
541 }
542
543 if let Some(current_id) = self.current_focus_id.clone() {
545 self.add_focus_transition(¤t_id, focus_id, 1.0);
546 }
547
548 if let Some(current_id) = &self.current_focus_id {
550 if let Some(current) = self.foci.get_mut(current_id) {
551 current.status = FocusStatus::Suspended;
552 }
553 if let Some(pos) = self.focus_stack.iter().position(|id| id == current_id) {
555 if pos == self.focus_stack.len() - 1 {
556 self.focus_stack.pop();
557 }
558 }
559 self.active_foci.remove(current_id);
560 }
561
562 if let Some(new_focus) = self.foci.get_mut(focus_id) {
564 new_focus.status = FocusStatus::Active;
565 new_focus.last_active = Utc::now();
566 self.current_focus_id = Some(focus_id.to_string());
567
568 self.focus_stack.retain(|id| id != focus_id);
570 self.focus_stack.push(focus_id.to_string());
571 self.active_foci.insert(focus_id.to_string());
572 }
573
574 Some(())
575 }
576
577 fn suspend_oldest_focus(&mut self) {
579 let oldest_active = self.foci.iter()
580 .filter(|(_, f)| f.status == FocusStatus::Active)
581 .min_by_key(|(_, f)| f.last_active)
582 .map(|(id, _)| id.clone());
583
584 if let Some(id) = oldest_active {
585 if let Some(focus) = self.foci.get_mut(&id) {
586 focus.status = FocusStatus::Suspended;
587 }
588 }
589 }
590
591 pub fn calculate_relevance(&self, user_input: &str) -> HashMap<String, f32> {
593 let mut relevance = HashMap::new();
594
595 for (id, focus) in &self.foci {
596 if focus.status != FocusStatus::Abandoned {
597 let score = self.calculate_focus_relevance(user_input, focus);
598 relevance.insert(id.clone(), score);
599 }
600 }
601
602 relevance
603 }
604
605 fn calculate_focus_relevance(&self, input: &str, focus: &FocusPoint) -> f32 {
607 let input_lower = input.to_lowercase();
608 let mut score = 0.0;
609
610 let keyword_matches = focus.keywords.iter()
612 .filter(|kw| input_lower.contains(&kw.to_lowercase()))
613 .count();
614 score += keyword_matches as f32 * 0.2;
615
616 let entity_matches = focus.entities.iter()
618 .filter(|ent| input_lower.contains(&ent.to_lowercase()))
619 .count();
620 score += entity_matches as f32 * 0.3;
621
622 if let Some(question) = &focus.core_question {
624 if self.question_similar(input, question) {
625 score += 0.4;
626 }
627 }
628
629 score *= focus.importance;
631
632 let hours_since_active = (Utc::now() - focus.last_active).num_hours() as f32;
634 let time_decay = 1.0 / (1.0 + hours_since_active * 0.1);
635 score *= time_decay;
636
637 score.min(1.0)
638 }
639
640 fn question_similar(&self, input: &str, question: &str) -> bool {
642 let input_words = self.extract_words(input);
643 let question_words = self.extract_words(question);
644
645 let common = input_words.intersection(&question_words).count();
646 let total = question_words.len();
647
648 common as f32 / total as f32 > 0.5
649 }
650
651 fn extract_words(&self, text: &str) -> std::collections::HashSet<String> {
653 text.to_lowercase()
654 .split_whitespace()
655 .map(|s| s.to_string())
656 .collect()
657 }
658
659 pub fn should_create_new_focus(&self, user_input: &str) -> bool {
661 let relevance = self.calculate_relevance(user_input);
662
663 if relevance.is_empty() {
665 return true;
666 }
667
668 if let Some(current_focus) = self.current_focus() {
670 let current_relevance = relevance.get(¤t_focus.id).copied().unwrap_or(0.0);
671
672 if current_focus.should_switch(current_relevance) {
674 log::debug!(
675 "Focus '{}' relevance {:.2} below dynamic threshold {:.2}, suggesting new focus",
676 current_focus.topic,
677 current_relevance,
678 current_focus.dynamic_switch_threshold
679 );
680 return true;
681 }
682 }
683
684 false
685 }
686
687 pub fn get_most_relevant_focus(&self, user_input: &str) -> Option<String> {
689 let relevance = self.calculate_relevance(user_input);
690
691 relevance.iter()
692 .max_by(|(_, score_a), (_, score_b)| score_a.partial_cmp(score_b).unwrap_or(std::cmp::Ordering::Equal))
693 .map(|(id, _)| id.clone())
694 }
695
696 pub fn update_focus_range(&mut self, focus_id: &str, message_index: usize) {
698 if let Some(focus) = self.foci.get_mut(focus_id) {
699 focus.message_range.end = message_index;
700 focus.last_active = Utc::now();
701 }
702 }
703
704 pub fn complete_focus(&mut self, focus_id: &str) {
706 if let Some(focus) = self.foci.get_mut(focus_id) {
707 focus.status = FocusStatus::Completed;
708 }
709
710 if self.current_focus_id.as_ref() == Some(&focus_id.to_string()) {
711 self.current_focus_id = None;
712 }
713 }
714
715 pub fn create_focus_message(&self) -> Option<String> {
717 let current = self.current_focus()?;
718
719 let mut message = format!(
720 "🎯 **Current Focus: {}**\n\n",
721 current.topic
722 );
723
724 if let Some(question) = ¤t.core_question {
725 message.push_str(&format!("**Core Question:** {}\n\n", question));
726 }
727
728 if !current.keywords.is_empty() {
729 message.push_str(&format!(
730 "**Related Keywords:** {}\n\n",
731 current.keywords.join(", ")
732 ));
733 }
734
735 if !current.entities.is_empty() {
736 message.push_str(&format!(
737 "**Related Entities:** {}\n\n",
738 current.entities.join(", ")
739 ));
740 }
741
742 if self.focus_history.len() > 1 {
744 message.push_str("**Previous Focuses:**\n");
745 for (idx, focus_id) in self.focus_history.iter().rev().take(3).enumerate() {
746 if let Some(f) = self.foci.get(focus_id) {
747 if f.id != current.id {
748 message.push_str(&format!(
749 "{}. {} ({})\n",
750 idx + 1,
751 f.topic,
752 f.status
753 ));
754 }
755 }
756 }
757 }
758
759 if self.focus_stack.len() > 1 {
761 message.push_str("\n**Active Focus Stack:**\n");
762 for (idx, focus_id) in self.focus_stack.iter().rev().enumerate() {
763 if let Some(f) = self.foci.get(focus_id) {
764 message.push_str(&format!(
765 "{}. {} (importance: {:.2})\n",
766 idx + 1,
767 f.topic,
768 f.effective_importance()
769 ));
770 }
771 }
772 }
773
774 Some(message)
775 }
776
777 pub fn primary_focus(&self) -> Option<&FocusPoint> {
781 self.focus_stack.last()
782 .and_then(|id| self.foci.get(id))
783 }
784
785 pub fn get_active_foci(&self) -> Vec<&FocusPoint> {
787 self.active_foci.iter()
788 .filter_map(|id| self.foci.get(id))
789 .collect()
790 }
791
792 pub fn switch_to_previous_focus(&mut self) -> Option<()> {
794 if self.focus_stack.len() < 2 {
795 return None;
796 }
797
798 let current_id = self.focus_stack.pop()?;
800
801 let previous_id = self.focus_stack.last()?.clone();
803
804 self.switch_focus(&previous_id)?;
806
807 self.focus_stack.insert(self.focus_stack.len() - 1, current_id);
809
810 Some(())
811 }
812
813 pub fn activate_multiple_foci(&mut self, focus_ids: &[String]) {
815 for id in focus_ids {
816 if self.foci.contains_key(id) {
817 self.active_foci.insert(id.clone());
818 if let Some(focus) = self.foci.get_mut(id) {
819 focus.status = FocusStatus::Active;
820 }
821 }
822 }
823 }
824
825 pub fn add_focus_transition(&mut self, from_id: &str, to_id: &str, strength: f32) {
829 self.focus_graph
830 .entry(from_id.to_string())
831 .or_default()
832 .push((to_id.to_string(), strength));
833
834 if let Some(transitions) = self.focus_graph.get_mut(from_id) {
836 for (id, s) in transitions.iter_mut() {
837 if id == to_id {
838 *s = (*s + strength).min(1.0);
839 return;
840 }
841 }
842 }
843 }
844
845 pub fn wake_related_foci(&mut self, focus_id: &str, min_strength: f32) {
847 if let Some(related) = self.focus_graph.get(focus_id) {
848 for (related_id, strength) in related {
849 if *strength >= min_strength {
850 if let Some(focus) = self.foci.get_mut(related_id) {
851 focus.wake_up();
852 focus.status = FocusStatus::Active;
853 self.active_foci.insert(related_id.clone());
854 }
855 }
856 }
857 }
858 }
859
860 pub fn get_related_foci(&self, focus_id: &str) -> Vec<(String, f32)> {
862 self.focus_graph.get(focus_id)
863 .map(|relations| relations.clone())
864 .unwrap_or_default()
865 }
866
867 pub fn split_focus(&mut self, parent_id: &str, child: FocusPoint) {
871 let child_id = child.id.clone();
872
873 self.foci.insert(child_id.clone(), child);
875
876 self.focus_tree
878 .entry(parent_id.to_string())
879 .or_default()
880 .push(child_id.clone());
881
882 if let Some(parent) = self.foci.get_mut(parent_id) {
884 parent.sub_foci.push(child_id.clone());
885 }
886
887 self.active_foci.insert(child_id);
889 }
890
891 pub fn merge_focus_to_parent(&mut self, parent_id: &str, child_id: &str) {
893 if let (Some(parent), Some(child)) =
895 (self.foci.get(parent_id), self.foci.get(child_id).cloned())
896 {
897 let mut parent = parent.clone();
898 parent.add_keywords(&child.keywords);
899 parent.add_entities(&child.entities);
900
901 parent.importance = (parent.importance + 0.1).min(1.0);
903
904 if let Some(child_focus) = self.foci.get_mut(child_id) {
906 child_focus.status = FocusStatus::Completed;
907 }
908
909 if let Some(children) = self.focus_tree.get_mut(parent_id) {
911 children.retain(|id| id != child_id);
912 }
913
914 self.foci.insert(parent_id.to_string(), parent);
916 }
917 }
918
919 pub fn get_focus_tree_depth(&self, focus_id: &str) -> usize {
921 let empty_children = Vec::new();
922 let children = self.focus_tree.get(focus_id).unwrap_or(&empty_children);
923 if children.is_empty() {
924 return 1;
925 }
926
927 1 + children.iter()
928 .map(|child_id| self.get_focus_tree_depth(child_id))
929 .max()
930 .unwrap_or(0)
931 }
932
933 pub fn record_negative_feedback(&mut self, focus_id: &str, message_index: usize) {
937 if let Some(focus) = self.foci.get_mut(focus_id) {
938 focus.add_feedback(FocusFeedbackType::Rejected, None, message_index);
939
940 focus.confidence *= 0.8;
942
943 focus.dynamic_switch_threshold = FocusPoint::calculate_initial_threshold(
945 focus.confidence,
946 focus.focus_type
947 );
948
949 log::info!(
950 "Negative feedback recorded for focus '{}', confidence reduced to {:.2}",
951 focus.topic,
952 focus.confidence
953 );
954 }
955 }
956
957 pub fn record_positive_feedback(&mut self, focus_id: &str, message_index: usize) {
959 if let Some(focus) = self.foci.get_mut(focus_id) {
960 focus.add_feedback(FocusFeedbackType::Confirmed, None, message_index);
961
962 focus.confidence = (focus.confidence + 0.1).min(1.0);
964
965 focus.dynamic_switch_threshold = FocusPoint::calculate_initial_threshold(
967 focus.confidence,
968 focus.focus_type
969 );
970 }
971 }
972
973 pub fn predict_next_focus(&self) -> Option<String> {
977 if let Some(current_id) = &self.current_focus_id {
978 if let Some(transitions) = self.focus_graph.get(current_id) {
979 return transitions.iter()
981 .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
982 .map(|(id, _)| id.clone());
983 }
984 }
985 None
986 }
987
988 pub fn predict_by_keywords(&self, keywords: &[String]) -> Option<String> {
990 let mut best_match: Option<(String, f32)> = None;
991
992 for (focus_id, focus) in &self.foci {
993 if focus.status == FocusStatus::Active {
994 let overlap = keywords.iter()
996 .filter(|kw| focus.keywords.contains(kw))
997 .count() as f32;
998 let score = overlap / focus.keywords.len() as f32;
999
1000 if score > 0.3 {
1001 if best_match.is_none() || score > best_match.as_ref().unwrap().1 {
1002 best_match = Some((focus_id.clone(), score));
1003 }
1004 }
1005 }
1006 }
1007
1008 best_match.map(|(id, _)| id)
1009 }
1010
1011 fn compute_input_hash(&self, input: &str) -> String {
1015 input.chars().take(50).collect()
1017 }
1018
1019 pub fn get_cached_relevance(&mut self, user_input: &str) -> Option<HashMap<String, f32>> {
1021 let hash = self.compute_input_hash(user_input);
1022
1023 if let Some(cached) = self.relevance_cache.get(&hash) {
1024 return Some(cached.clone());
1025 }
1026
1027 None
1028 }
1029
1030 pub fn cache_relevance(&mut self, user_input: &str, relevance: HashMap<String, f32>) {
1032 let hash = self.compute_input_hash(user_input);
1033
1034 if self.relevance_cache.len() >= self.cache_capacity {
1036 let keys_to_remove = self.relevance_cache.keys()
1038 .take(self.cache_capacity / 2)
1039 .cloned()
1040 .collect::<Vec<_>>();
1041
1042 for key in keys_to_remove {
1043 self.relevance_cache.remove(&key);
1044 }
1045 }
1046
1047 self.relevance_cache.insert(hash, relevance);
1048 }
1049
1050 pub fn update_active_relevance(&mut self, user_input: &str) -> HashMap<String, f32> {
1054 let mut relevance = HashMap::new();
1055
1056 if let Some(cached) = self.get_cached_relevance(user_input) {
1058 return cached;
1059 }
1060
1061 for focus_id in &self.active_foci {
1063 if let Some(focus) = self.foci.get(focus_id) {
1064 let score = self.calculate_focus_relevance(user_input, focus);
1065 relevance.insert(focus_id.clone(), score);
1066 }
1067 }
1068
1069 self.cache_relevance(user_input, relevance.clone());
1071
1072 relevance
1073 }
1074
1075 pub fn calculate_relevance_with_decay(&self, user_input: &str) -> HashMap<String, f32> {
1077 let mut relevance = HashMap::new();
1078
1079 for (id, focus) in &self.foci {
1080 if focus.status != FocusStatus::Abandoned {
1081 let effective_importance = focus.effective_importance();
1083 let score = self.calculate_focus_relevance(user_input, focus) * effective_importance;
1084 relevance.insert(id.clone(), score);
1085 }
1086 }
1087
1088 relevance
1089 }
1090}
1091
1092#[cfg(test)]
1093mod tests {
1094 use super::*;
1095
1096 #[test]
1097 fn test_focus_manager() {
1098 let mut manager = FocusManager::new();
1099
1100 let focus = FocusPoint::new(
1101 "focus-1".to_string(),
1102 "Optimizing Rust performance".to_string(),
1103 vec!["performance".to_string(), "rust".to_string()],
1104 vec!["main.rs".to_string()],
1105 Some("How to reduce memory usage?".to_string()),
1106 0,
1107 ).with_importance(0.8);
1108
1109 manager.add_focus(focus);
1110
1111 assert!(manager.current_focus().is_some());
1112 assert_eq!(manager.current_focus().unwrap().topic, "Optimizing Rust performance");
1113 }
1114
1115 #[test]
1116 fn test_relevance_calculation() {
1117 let mut manager = FocusManager::new();
1118
1119 let focus = FocusPoint::new(
1120 "focus-1".to_string(),
1121 "Database optimization".to_string(),
1122 vec!["database".to_string(), "sql".to_string()],
1123 vec!["db.rs".to_string()],
1124 Some("Why is query slow?".to_string()),
1125 0,
1126 ).with_importance(0.8);
1127
1128 manager.add_focus(focus);
1129
1130 let relevance = manager.calculate_relevance("The database query is still slow with SQL");
1132 assert!(relevance["focus-1"] >= 0.15);
1134
1135 let relevance = manager.calculate_relevance("Let's talk about UI design");
1137 assert!(relevance["focus-1"] < 0.1);
1138 }
1139
1140 #[test]
1141 fn test_should_create_new_focus() {
1142 let mut manager = FocusManager::new();
1143
1144 assert!(manager.should_create_new_focus("any input"));
1146
1147 let focus = FocusPoint::new(
1149 "focus-1".to_string(),
1150 "API design".to_string(),
1151 vec!["api".to_string()],
1152 vec![],
1153 None,
1154 0,
1155 ).with_importance(0.5);
1156
1157 manager.add_focus(focus);
1158
1159 let relevance = manager.calculate_relevance("How to improve API response time?");
1164 println!("API relevance: {}", relevance["focus-1"]);
1165 assert!(manager.should_create_new_focus("I want to change the color scheme"));
1170 }
1171}