1use anyhow::Result;
9use chrono::{DateTime, Datelike, Duration, Timelike, Utc};
10use serde::{Deserialize, Serialize};
11use std::collections::{HashMap, VecDeque};
12
13use super::{ContextContent, GatheredContext};
14use crate::mem8::wave::{FrequencyBand, MemoryWave};
15
16#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
18pub enum SessionType {
19 SoloAI,
21 SoloHuman,
23 Tandem,
25 Transitional,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub enum FlowState {
32 Flow { depth: f32, sustained_minutes: u32 },
34 Whirlpool {
36 confusion_score: f32,
37 repeated_concepts: Vec<String>,
38 },
39 Fork {
41 branch_count: usize,
42 topics: Vec<String>,
43 },
44 Steady,
46 Interrupted { reason: String },
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct MemoryAnchor {
53 pub id: String,
54 pub origin: CollaborativeOrigin,
55 pub anchor_type: AnchorType,
56 pub context: String,
57 pub keywords: Vec<String>,
58 pub timestamp: DateTime<Utc>,
59 pub relevance_wave: MemoryWave,
60 pub co_created: bool,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub enum CollaborativeOrigin {
65 Single(String),
67 Tandem { human: String, ai: String },
69 Emergent,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
74pub enum AnchorType {
75 PatternInsight,
76 Solution,
77 Breakthrough,
78 LearningMoment,
79 SharedJoke, TechnicalPattern,
81 ProcessImprovement,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct TrustFlowScore {
87 pub clarity: f32, pub responsiveness: f32, pub autonomy_ratio: f32, pub frustration_markers: f32, pub flow_depth: f32, }
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct RapportIndex {
97 pub overall_score: f32,
98 pub trust_level: f32,
99 pub communication_efficiency: f32,
100 pub shared_vocabulary_size: usize,
101 pub inside_jokes_count: usize, pub preferred_working_hours: Vec<u32>,
103 pub avg_session_productivity: f32,
104 pub evolution_trend: f32, }
106
107pub struct CollaborativeSessionTracker {
109 pub active_session: Option<CollaborativeSession>,
110 pub session_history: VecDeque<CollaborativeSession>,
111 pub memory_anchors: HashMap<String, MemoryAnchor>,
112 pub rapport_indices: HashMap<String, RapportIndex>, pub cross_session_links: HashMap<String, Vec<String>>,
114}
115
116impl Default for CollaborativeSessionTracker {
117 fn default() -> Self {
118 Self::new()
119 }
120}
121
122impl CollaborativeSessionTracker {
123 pub fn new() -> Self {
124 Self {
125 active_session: None,
126 session_history: VecDeque::with_capacity(1000),
127 memory_anchors: HashMap::new(),
128 rapport_indices: HashMap::new(),
129 cross_session_links: HashMap::new(),
130 }
131 }
132
133 pub fn process_context(&mut self, context: &GatheredContext) -> Result<()> {
135 let session_type = self.detect_session_type(context);
137 let timestamp = context.timestamp;
138
139 let should_start_new = if let Some(session) = &self.active_session {
141 Self::should_start_new_session_static(session, ×tamp, &session_type)
142 } else {
143 true
144 };
145
146 if should_start_new {
147 if self.active_session.is_some() {
149 self.end_active_session();
150 }
151 self.start_new_session(context, session_type);
152 } else {
153 if let Some(session) = &mut self.active_session {
155 session.update(context, session_type);
156 }
157 }
158
159 if let Some(session) = &self.active_session {
161 let flow_state = self.analyze_flow_state(session);
162 if let Some(session) = &mut self.active_session {
163 session.flow_state = flow_state;
164 }
165 }
166
167 Ok(())
168 }
169
170 fn detect_session_type(&self, context: &GatheredContext) -> SessionType {
172 match &context.content {
174 ContextContent::Json(json) => {
175 if let Some(messages) = json.get("messages").and_then(|m| m.as_array()) {
176 let recent_messages = messages.iter().rev().take(5).collect::<Vec<_>>();
178 let has_user = recent_messages
179 .iter()
180 .any(|m| m.get("role").and_then(|r| r.as_str()) == Some("user"));
181 let has_assistant = recent_messages
182 .iter()
183 .any(|m| m.get("role").and_then(|r| r.as_str()) == Some("assistant"));
184
185 if has_user && has_assistant {
186 SessionType::Tandem
187 } else if has_assistant {
188 SessionType::SoloAI
189 } else {
190 SessionType::SoloHuman
191 }
192 } else {
193 SessionType::SoloAI
194 }
195 }
196 _ => SessionType::SoloHuman,
197 }
198 }
199
200 fn should_start_new_session_static(
202 current: &CollaborativeSession,
203 timestamp: &DateTime<Utc>,
204 new_type: &SessionType,
205 ) -> bool {
206 let time_gap = *timestamp - current.last_activity;
207
208 time_gap > Duration::minutes(30)
210 || current.session_type != *new_type
211 || (*new_type == SessionType::Tandem && time_gap > Duration::minutes(5))
212 }
213
214 fn start_new_session(&mut self, context: &GatheredContext, session_type: SessionType) {
216 let session = CollaborativeSession {
217 id: format!("session_{}", chrono::Utc::now().timestamp()),
218 start_time: context.timestamp,
219 last_activity: context.timestamp,
220 session_type,
221 ai_tool: context.ai_tool.clone(),
222 interactions: vec![context.clone()],
223 flow_state: FlowState::Steady,
224 trust_flow_score: TrustFlowScore::default(),
225 anchored_memories: Vec::new(),
226 };
227
228 self.active_session = Some(session);
229 }
230
231 pub fn end_active_session(&mut self) {
233 if let Some(mut session) = self.active_session.take() {
234 session.trust_flow_score = self.calculate_trust_flow(&session);
236
237 self.update_rapport_index(&session);
239
240 if self.session_history.len() >= 1000 {
242 self.session_history.pop_front();
243 }
244 self.session_history.push_back(session);
245 }
246 }
247
248 fn analyze_flow_state(&self, session: &CollaborativeSession) -> FlowState {
250 let recent_interactions = session
251 .interactions
252 .iter()
253 .rev()
254 .take(10)
255 .collect::<Vec<_>>();
256
257 if recent_interactions.is_empty() {
258 return FlowState::Steady;
259 }
260
261 let mut back_and_forth_count = 0;
263 let mut repeated_concepts = HashMap::new();
264 let topics = Vec::new();
265
266 for (i, interaction) in recent_interactions.iter().enumerate() {
267 if i > 0 {
268 let prev = &recent_interactions[i - 1];
269 let time_diff = interaction.timestamp - prev.timestamp;
271 if time_diff < Duration::minutes(2) {
272 back_and_forth_count += 1;
273 }
274 }
275
276 if let ContextContent::Text(text) = &interaction.content {
278 for word in text.split_whitespace() {
279 if word.len() > 5 {
280 *repeated_concepts.entry(word.to_lowercase()).or_insert(0) += 1;
281 }
282 }
283 }
284 }
285
286 if back_and_forth_count > 5 {
288 FlowState::Flow {
289 depth: back_and_forth_count as f32 / 10.0,
290 sustained_minutes: (recent_interactions.first().unwrap().timestamp
291 - recent_interactions.last().unwrap().timestamp)
292 .num_minutes() as u32,
293 }
294 } else if repeated_concepts.values().any(|&count| count > 3) {
295 let repeated: Vec<String> = repeated_concepts
296 .into_iter()
297 .filter(|(_, count)| *count > 3)
298 .map(|(concept, _)| concept)
299 .collect();
300 FlowState::Whirlpool {
301 confusion_score: repeated.len() as f32 / 10.0,
302 repeated_concepts: repeated,
303 }
304 } else if topics.len() > 3 {
305 FlowState::Fork {
306 branch_count: topics.len(),
307 topics,
308 }
309 } else {
310 FlowState::Steady
311 }
312 }
313
314 fn calculate_trust_flow(&self, session: &CollaborativeSession) -> TrustFlowScore {
316 TrustFlowScore {
317 clarity: self.calculate_clarity(session),
318 responsiveness: self.calculate_responsiveness(session),
319 autonomy_ratio: self.calculate_autonomy_ratio(session),
320 frustration_markers: self.detect_frustration(session),
321 flow_depth: match &session.flow_state {
322 FlowState::Flow { depth, .. } => *depth,
323 FlowState::Whirlpool {
324 confusion_score, ..
325 } => 1.0 - confusion_score,
326 _ => 0.5,
327 },
328 }
329 }
330
331 fn update_rapport_index(&mut self, session: &CollaborativeSession) {
333 let pair_id = format!("{}_{}", "human", session.ai_tool);
334
335 let rapport = self
336 .rapport_indices
337 .entry(pair_id)
338 .or_insert_with(|| RapportIndex {
339 overall_score: 0.5,
340 trust_level: 0.5,
341 communication_efficiency: 0.5,
342 shared_vocabulary_size: 0,
343 inside_jokes_count: 0,
344 preferred_working_hours: Vec::new(),
345 avg_session_productivity: 0.5,
346 evolution_trend: 0.0,
347 });
348
349 let session_score = (session.trust_flow_score.clarity
351 + session.trust_flow_score.responsiveness
352 + session.trust_flow_score.flow_depth)
353 / 3.0;
354
355 rapport.overall_score = rapport.overall_score * 0.8 + session_score * 0.2;
357
358 if session.trust_flow_score.autonomy_ratio > 0.3 {
360 rapport.trust_level = (rapport.trust_level * 0.9 + 0.1).min(1.0);
361 }
362
363 let hour = session.start_time.hour();
365 if !rapport.preferred_working_hours.contains(&hour) {
366 rapport.preferred_working_hours.push(hour);
367 }
368
369 rapport.evolution_trend = session_score - rapport.avg_session_productivity;
371 rapport.avg_session_productivity =
372 rapport.avg_session_productivity * 0.9 + session_score * 0.1;
373 }
374
375 pub fn anchor_memory(
377 &mut self,
378 origin: CollaborativeOrigin,
379 anchor_type: AnchorType,
380 context: String,
381 keywords: Vec<String>,
382 ) -> Result<String> {
383 let id = format!(
384 "anchor_{}",
385 chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0)
386 );
387
388 let co_created = matches!(origin, CollaborativeOrigin::Tandem { .. });
389
390 let anchor = MemoryAnchor {
391 id: id.clone(),
392 origin,
393 anchor_type,
394 context,
395 keywords,
396 timestamp: chrono::Utc::now(),
397 relevance_wave: MemoryWave::new_with_band(
398 FrequencyBand::Beta,
399 1.0, 0.0,
401 0.05, ),
403 co_created,
404 };
405
406 self.memory_anchors.insert(id.clone(), anchor);
407
408 if let Some(ref mut session) = self.active_session {
410 session.anchored_memories.push(id.clone());
411 }
412
413 Ok(id)
414 }
415
416 pub fn find_relevant_anchors(&self, keywords: &[String]) -> Vec<&MemoryAnchor> {
418 let mut relevant = Vec::new();
419
420 for anchor in self.memory_anchors.values() {
421 let relevance = keywords
422 .iter()
423 .filter(|k| anchor.keywords.contains(k))
424 .count() as f32
425 / keywords.len().max(1) as f32;
426
427 if relevance > 0.3 {
428 relevant.push(anchor);
429 }
430 }
431
432 relevant.sort_by(|a, b| {
434 let a_score = a.relevance_wave.amplitude;
435 let b_score = b.relevance_wave.amplitude;
436 b_score.partial_cmp(&a_score).unwrap()
437 });
438
439 relevant
440 }
441
442 pub fn link_cross_session(&mut self, session_id: String, related_ids: Vec<String>) {
444 self.cross_session_links
445 .entry(session_id)
446 .or_default()
447 .extend(related_ids);
448 }
449
450 pub fn get_rapport(&self, ai_tool: &str) -> Option<&RapportIndex> {
452 let pair_id = format!("human_{}", ai_tool);
453 self.rapport_indices.get(&pair_id)
454 }
455
456 fn calculate_clarity(&self, session: &CollaborativeSession) -> f32 {
459 let tandem_ratio = session
461 .interactions
462 .iter()
463 .filter(|i| matches!(self.detect_session_type(i), SessionType::Tandem))
464 .count() as f32
465 / session.interactions.len().max(1) as f32;
466
467 tandem_ratio
468 }
469
470 fn calculate_responsiveness(&self, session: &CollaborativeSession) -> f32 {
471 let mut response_times = Vec::new();
473
474 for i in 1..session.interactions.len() {
475 let time_diff =
476 session.interactions[i].timestamp - session.interactions[i - 1].timestamp;
477 if time_diff < Duration::minutes(5) {
478 response_times.push(time_diff.num_seconds() as f32);
479 }
480 }
481
482 if response_times.is_empty() {
483 return 0.5;
484 }
485
486 let avg_response = response_times.iter().sum::<f32>() / response_times.len() as f32;
487 1.0 - (avg_response / 300.0).min(1.0)
489 }
490
491 fn calculate_autonomy_ratio(&self, session: &CollaborativeSession) -> f32 {
492 let ai_initiated = session
494 .interactions
495 .iter()
496 .filter(|i| matches!(self.detect_session_type(i), SessionType::SoloAI))
497 .count() as f32;
498 let human_initiated = session
499 .interactions
500 .iter()
501 .filter(|i| matches!(self.detect_session_type(i), SessionType::SoloHuman))
502 .count() as f32;
503
504 let total = ai_initiated + human_initiated;
505 if total == 0.0 {
506 return 0.5;
507 }
508
509 ai_initiated / total
511 }
512
513 fn detect_frustration(&self, session: &CollaborativeSession) -> f32 {
514 let mut frustration_score = 0.0;
516
517 if let FlowState::Whirlpool {
519 confusion_score, ..
520 } = &session.flow_state
521 {
522 frustration_score += confusion_score;
523 }
524
525 for i in 1..session.interactions.len() {
527 let gap = session.interactions[i].timestamp - session.interactions[i - 1].timestamp;
528 if gap > Duration::minutes(10) && gap < Duration::hours(1) {
529 frustration_score += 0.1;
530 }
531 }
532
533 frustration_score.min(1.0)
534 }
535}
536
537#[derive(Debug, Clone, Serialize, Deserialize)]
539pub struct CollaborativeSession {
540 pub id: String,
541 pub start_time: DateTime<Utc>,
542 pub last_activity: DateTime<Utc>,
543 pub session_type: SessionType,
544 pub ai_tool: String,
545 pub interactions: Vec<GatheredContext>,
546 pub flow_state: FlowState,
547 pub trust_flow_score: TrustFlowScore,
548 pub anchored_memories: Vec<String>,
549}
550
551impl CollaborativeSession {
552 fn update(&mut self, context: &GatheredContext, session_type: SessionType) {
553 self.last_activity = context.timestamp;
554 self.session_type = session_type;
555 self.interactions.push(context.clone());
556 }
557}
558
559impl Default for TrustFlowScore {
560 fn default() -> Self {
561 Self {
562 clarity: 0.5,
563 responsiveness: 0.5,
564 autonomy_ratio: 0.5,
565 frustration_markers: 0.0,
566 flow_depth: 0.0,
567 }
568 }
569}
570
571#[derive(Debug, Clone, Serialize, Deserialize)]
573pub struct CoEngagementHeatmap {
574 pub time_slots: Vec<Vec<f32>>, pub peak_collaboration_zones: Vec<(usize, usize)>, pub collaboration_density: f32,
577}
578
579impl CoEngagementHeatmap {
580 pub fn from_sessions(sessions: &[CollaborativeSession]) -> Self {
581 let mut grid = vec![vec![0.0; 7]; 24]; for session in sessions {
585 if session.session_type == SessionType::Tandem {
586 let hour = session.start_time.hour() as usize;
587 let day = session.start_time.weekday().num_days_from_monday() as usize;
588 grid[hour][day] += 1.0;
589 }
590 }
591
592 let max_val = grid
594 .iter()
595 .flat_map(|row| row.iter())
596 .fold(0.0f32, |a, &b| a.max(b));
597
598 if max_val > 0.0 {
599 for row in &mut grid {
600 for val in row {
601 *val /= max_val;
602 }
603 }
604 }
605
606 let mut peaks = Vec::new();
608 for (hour, row) in grid.iter().enumerate() {
609 for (day, &val) in row.iter().enumerate() {
610 if val > 0.7 {
611 peaks.push((hour, day));
612 }
613 }
614 }
615
616 let density = grid
617 .iter()
618 .flat_map(|row| row.iter())
619 .filter(|&&v| v > 0.0)
620 .count() as f32
621 / (24.0 * 7.0);
622
623 Self {
624 time_slots: grid,
625 peak_collaboration_zones: peaks,
626 collaboration_density: density,
627 }
628 }
629}