matrixcode_core/compress/
focus_config.rs1use serde::{Deserialize, Serialize};
7
8use crate::memory::ExtractedKeywords;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct FocusTrackerConfig {
13 #[serde(skip)]
16 current_keywords: Option<ExtractedKeywords>,
17
18 pub fallback_topic_word_count: usize,
20
21 pub focus_window_size: usize,
23
24 pub max_recent_context_count: usize,
26
27 pub max_question_extract_length: usize,
29
30 pub min_substantial_text_length: usize,
32
33 pub focus_score_boost: f32,
35
36 pub max_focus_score: f32,
38}
39
40impl Default for FocusTrackerConfig {
41 fn default() -> Self {
42 Self {
43 current_keywords: None,
45
46 fallback_topic_word_count: 3,
48
49 focus_window_size: 10, max_recent_context_count: 5, max_question_extract_length: 100, min_substantial_text_length: 10, focus_score_boost: 0.3, max_focus_score: 1.0, }
59 }
60}
61
62impl FocusTrackerConfig {
63 pub fn simple_conversation() -> Self {
65 Self {
66 focus_window_size: 5,
67 max_recent_context_count: 3,
68 min_substantial_text_length: 5,
69 ..Self::default()
70 }
71 }
72
73 pub fn complex_technical() -> Self {
75 Self {
76 focus_window_size: 15,
77 max_recent_context_count: 7,
78 max_question_extract_length: 150,
79 min_substantial_text_length: 20,
80 focus_score_boost: 0.4,
81 ..Self::default()
82 }
83 }
84
85 pub fn from_complexity(level: crate::compress::complexity::ComplexityLevel) -> Self {
87 match level {
88 crate::compress::complexity::ComplexityLevel::High => Self::complex_technical(),
89 crate::compress::complexity::ComplexityLevel::Medium => Self::default(),
90 crate::compress::complexity::ComplexityLevel::Low => Self::simple_conversation(),
91 }
92 }
93
94 pub fn set_keywords(&mut self, keywords: &ExtractedKeywords) {
99 self.current_keywords = Some(keywords.clone());
100 }
101
102 pub fn get_keywords(&self) -> Option<&ExtractedKeywords> {
104 self.current_keywords.as_ref()
105 }
106
107 pub fn transition_keywords(&self) -> Vec<String> {
109 if let Some(kw) = &self.current_keywords {
110 kw.transition.clone()
111 } else {
112 vec![
114 "however".to_string(), "but".to_string(), "switching".to_string(),
115 "转换".to_string(), "切换".to_string(), "换个话题".to_string(),
116 ]
117 }
118 }
119
120 pub fn question_keywords(&self) -> Vec<String> {
122 if let Some(kw) = &self.current_keywords {
123 kw.question.clone()
124 } else {
125 vec![
127 "how".to_string(), "what".to_string(), "why".to_string(),
128 "如何".to_string(), "什么".to_string(), "为什么".to_string(),
129 ]
130 }
131 }
132
133 pub fn task_keywords(&self) -> Vec<String> {
135 if let Some(kw) = &self.current_keywords {
136 kw.task.clone()
137 } else {
138 vec![
140 "implement".to_string(), "create".to_string(), "fix".to_string(),
141 "实现".to_string(), "创建".to_string(), "修复".to_string(),
142 ]
143 }
144 }
145
146 pub fn tech_keywords(&self) -> Vec<String> {
148 if let Some(kw) = &self.current_keywords {
149 kw.tech.clone()
150 } else {
151 vec![
153 "rust".to_string(), "python".to_string(), "javascript".to_string(),
154 "api".to_string(), "database".to_string(), "performance".to_string(),
155 ]
156 }
157 }
158
159 pub fn matches_transition(&self, text: &str) -> bool {
161 let lower = text.to_lowercase();
162 self.transition_keywords().iter().any(|kw| lower.contains(&kw.to_lowercase()))
163 }
164
165 pub fn matches_question(&self, text: &str) -> bool {
167 let lower = text.to_lowercase();
168 self.question_keywords().iter().any(|kw| lower.contains(&kw.to_lowercase()))
169 }
170
171 pub fn matches_task(&self, text: &str) -> bool {
173 let lower = text.to_lowercase();
174 self.task_keywords().iter().any(|kw| lower.contains(&kw.to_lowercase()))
175 }
176
177 pub fn find_tech_keywords(&self, text: &str) -> Vec<String> {
179 let lower = text.to_lowercase();
180 self.tech_keywords()
181 .iter()
182 .filter(|kw| lower.contains(&kw.to_lowercase()))
183 .cloned()
184 .collect()
185 }
186
187 pub fn merge_keywords(&mut self, additional: &ExtractedKeywords) {
189 match self.current_keywords.take() {
190 Some(mut current) => {
191 current.merge(additional);
192 self.current_keywords = Some(current);
193 }
194 None => {
195 self.current_keywords = Some(additional.clone());
196 }
197 }
198 }
199
200 pub fn clear_keywords(&mut self) {
202 self.current_keywords = None;
203 }
204
205 pub fn validate(&self) -> bool {
207 self.focus_window_size > 0 &&
208 self.max_recent_context_count > 0 &&
209 self.max_question_extract_length > 0 &&
210 self.min_substantial_text_length > 0 &&
211 self.focus_score_boost > 0.0 &&
212 self.max_focus_score > 0.0 &&
213 self.fallback_topic_word_count > 0
214 }
215}
216
217#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219pub enum KeywordType {
220 Transition,
221 Question,
222 Task,
223 Tech,
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn test_default_config() {
232 let config = FocusTrackerConfig::default();
233 assert!(config.validate());
234 assert_eq!(config.focus_window_size, 10);
235 assert_eq!(config.max_recent_context_count, 5);
236 }
237
238 #[test]
239 fn test_simple_conversation_config() {
240 let config = FocusTrackerConfig::simple_conversation();
241 assert_eq!(config.focus_window_size, 5);
242 assert_eq!(config.max_recent_context_count, 3);
243 }
244
245 #[test]
246 fn test_complex_technical_config() {
247 let config = FocusTrackerConfig::complex_technical();
248 assert_eq!(config.focus_window_size, 15);
249 assert_eq!(config.max_question_extract_length, 150);
250 }
251
252 #[test]
253 fn test_set_keywords() {
254 let mut config = FocusTrackerConfig::default();
255
256 assert!(config.get_keywords().is_none());
258
259 let keywords = ExtractedKeywords {
261 transition: vec!["new_transition".to_string()],
262 question: vec!["new_question".to_string()],
263 task: vec!["new_task".to_string()],
264 tech: vec!["new_tech".to_string()],
265 };
266 config.set_keywords(&keywords);
267
268 assert!(config.get_keywords().is_some());
270 assert_eq!(config.transition_keywords(), vec!["new_transition".to_string()]);
271 assert_eq!(config.question_keywords(), vec!["new_question".to_string()]);
272 }
273
274 #[test]
275 fn test_fallback_keywords() {
276 let config = FocusTrackerConfig::default();
277
278 assert!(!config.transition_keywords().is_empty());
280 assert!(!config.question_keywords().is_empty());
281 assert!(!config.task_keywords().is_empty());
282 assert!(!config.tech_keywords().is_empty());
283
284 assert!(config.transition_keywords().contains(&"however".to_string()));
286 assert!(config.question_keywords().contains(&"how".to_string()));
287 assert!(config.task_keywords().contains(&"implement".to_string()));
288 assert!(config.tech_keywords().contains(&"rust".to_string()));
289 }
290
291 #[test]
292 fn test_matches_keywords() {
293 let config = FocusTrackerConfig::default();
294
295 assert!(config.matches_question("How do I do this?"));
297 assert!(config.matches_task("Please implement this"));
298 assert!(config.matches_transition("However, let's move on"));
299 }
300
301 #[test]
302 fn test_find_tech_keywords() {
303 let config = FocusTrackerConfig::default();
304
305 let found = config.find_tech_keywords("Using Rust and Python for development");
306 assert!(found.contains(&"rust".to_string()));
307 assert!(found.contains(&"python".to_string()));
308 }
309
310 #[test]
311 fn test_merge_keywords() {
312 let mut config = FocusTrackerConfig::default();
313
314 let initial = ExtractedKeywords {
316 transition: vec!["switch".to_string()],
317 question: vec!["how".to_string()],
318 task: vec!["create".to_string()],
319 tech: vec!["rust".to_string()],
320 };
321 config.set_keywords(&initial);
322
323 let additional = ExtractedKeywords {
325 transition: vec!["new".to_string()],
326 question: vec!["why".to_string()],
327 task: vec!["delete".to_string()],
328 tech: vec!["python".to_string()],
329 };
330 config.merge_keywords(&additional);
331
332 let merged = config.get_keywords().unwrap();
334 assert!(merged.transition.contains(&"switch".to_string()));
335 assert!(merged.transition.contains(&"new".to_string()));
336 assert!(merged.tech.contains(&"rust".to_string()));
337 assert!(merged.tech.contains(&"python".to_string()));
338 }
339
340 #[test]
341 fn test_clear_keywords() {
342 let mut config = FocusTrackerConfig::default();
343
344 let keywords = ExtractedKeywords {
346 transition: vec!["test".to_string()],
347 question: vec![],
348 task: vec![],
349 tech: vec![],
350 };
351 config.set_keywords(&keywords);
352 assert!(config.get_keywords().is_some());
353
354 config.clear_keywords();
356 assert!(config.get_keywords().is_none());
357
358 assert!(config.transition_keywords().contains(&"however".to_string()));
360 }
361}