1use anyhow::{Context, Result};
7use serde::{Deserialize, Serialize};
8use serde_json::{Map, Value};
9use std::fs;
10use std::path::PathBuf;
11use std::time::SystemTime;
12
13use super::wave::{MemoryWave, WaveGrid};
14
15pub struct ConversationMemory {
17 base_path: PathBuf,
19 wave_grid: Option<WaveGrid>,
21 analyzer: ConversationAnalyzer,
23}
24
25impl ConversationMemory {
26 pub fn new() -> Result<Self> {
28 let home_dir = dirs::home_dir().context("Failed to get home directory")?;
29
30 let base_path = home_dir.join(".mem8").join("conversations");
31
32 fs::create_dir_all(&base_path)?;
34
35 Ok(Self {
36 base_path,
37 wave_grid: None, analyzer: ConversationAnalyzer::new(),
39 })
40 }
41
42 fn ensure_wave_grid(&mut self) {
44 if self.wave_grid.is_none() {
45 self.wave_grid = Some(WaveGrid::new());
46 }
47 }
48
49 pub fn save_conversation(
51 &mut self,
52 json_data: &Value,
53 source: Option<&str>,
54 ) -> Result<PathBuf> {
55 self.ensure_wave_grid();
57
58 let analysis = self.analyzer.analyze(json_data)?;
60
61 let timestamp = SystemTime::now()
63 .duration_since(SystemTime::UNIX_EPOCH)?
64 .as_secs();
65
66 let filename = format!(
67 "conv_{}_{}_{}.m8",
68 analysis.conversation_type.as_str(),
69 source.unwrap_or("unknown"),
70 timestamp
71 );
72
73 let file_path = self.base_path.join(&filename);
74
75 let waves = self.conversation_to_waves(&analysis)?;
77
78 if let Some(ref mut grid) = self.wave_grid {
80 for (idx, wave) in waves.iter().enumerate() {
81 let x = (idx % 256) as u8;
82 let y = ((idx / 256) % 256) as u8;
83 let z = (idx / (256 * 256)) as u16;
84 grid.store(x, y, z, wave.clone());
85 }
86 }
87
88 let json_path = file_path.with_extension("json");
93 fs::write(&json_path, serde_json::to_string_pretty(json_data)?)?;
94
95 println!("🧠Conversation saved to MEM|8: {}", filename);
96 println!(" Type: {:?}", analysis.conversation_type);
97 println!(" Messages: {}", analysis.message_count);
98 println!(" Participants: {}", analysis.participants.join(", "));
99
100 Ok(file_path)
101 }
102
103 fn conversation_to_waves(&self, analysis: &ConversationAnalysis) -> Result<Vec<MemoryWave>> {
105 let mut waves = Vec::new();
106
107 for message in &analysis.messages {
108 let frequency = match message.emotion.as_str() {
110 "happy" | "excited" => 100.0, "sad" | "worried" => 20.0, "angry" | "frustrated" => 150.0, "neutral" | "thinking" => 50.0, _ => 44.1, };
116
117 let mut wave = MemoryWave::new(frequency, message.importance as f32);
119 wave.phase = message.timestamp as f32;
120 wave.valence = match message.emotion.as_str() {
121 "happy" | "excited" => 0.8,
122 "sad" | "worried" => -0.5,
123 "angry" | "frustrated" => -0.8,
124 _ => 0.0,
125 };
126 wave.arousal = message.importance as f32 / 10.0;
127
128 waves.push(wave);
129 }
130
131 Ok(waves)
132 }
133
134 pub fn list_conversations(&self) -> Result<Vec<ConversationSummary>> {
136 let mut summaries = Vec::new();
137
138 if !self.base_path.exists() {
139 return Ok(summaries);
140 }
141
142 for entry in fs::read_dir(&self.base_path)? {
143 let entry = entry?;
144 let path = entry.path();
145
146 if path.extension() == Some(std::ffi::OsStr::new("m8")) {
147 let json_path = path.with_extension("json");
149 if json_path.exists() {
150 let json_str = fs::read_to_string(&json_path)?;
151 let json_data: Value = serde_json::from_str(&json_str)?;
152
153 let analysis = self.analyzer.analyze(&json_data)?;
154 summaries.push(ConversationSummary {
155 file_name: path.file_name().unwrap().to_string_lossy().to_string(),
156 conversation_type: analysis.conversation_type,
157 message_count: analysis.message_count,
158 participants: analysis.participants,
159 timestamp: entry.metadata()?.modified()?,
160 });
161 }
162 }
163 }
164
165 Ok(summaries)
166 }
167}
168
169pub struct ConversationAnalyzer {
171 patterns: Vec<ConversationPattern>,
173}
174
175impl Default for ConversationAnalyzer {
176 fn default() -> Self {
177 Self::new()
178 }
179}
180
181impl ConversationAnalyzer {
182 pub fn new() -> Self {
183 Self {
184 patterns: Self::default_patterns(),
185 }
186 }
187
188 pub fn analyze(&self, json_data: &Value) -> Result<ConversationAnalysis> {
190 let conversation_type = self.detect_type(json_data);
192
193 let messages = self.extract_messages(json_data, &conversation_type)?;
195
196 let participants = self.extract_participants(&messages);
198
199 let message_count = messages.len();
201
202 let mut metadata = Map::new();
204 metadata.insert(
205 "type".to_string(),
206 Value::String(conversation_type.to_string()),
207 );
208 metadata.insert("version".to_string(), Value::String("1.0".to_string()));
209
210 Ok(ConversationAnalysis {
211 conversation_type,
212 messages,
213 participants,
214 message_count,
215 metadata,
216 })
217 }
218
219 fn detect_type(&self, json_data: &Value) -> ConversationType {
221 if json_data.get("messages").is_some() {
223 ConversationType::ChatGPT
224 } else if json_data.get("conversation").is_some() {
225 ConversationType::Claude
226 } else if json_data.get("history").is_some() {
227 ConversationType::Generic
228 } else if json_data.is_array() {
229 ConversationType::MessageArray
230 } else {
231 ConversationType::Unknown
232 }
233 }
234
235 fn extract_messages(
237 &self,
238 json_data: &Value,
239 conv_type: &ConversationType,
240 ) -> Result<Vec<Message>> {
241 let mut messages = Vec::new();
242
243 match conv_type {
244 ConversationType::ChatGPT => {
245 if let Some(msgs) = json_data.get("messages").and_then(|m| m.as_array()) {
246 for (idx, msg) in msgs.iter().enumerate() {
247 messages.push(Message {
248 content: msg
249 .get("content")
250 .and_then(|c| c.as_str())
251 .unwrap_or("")
252 .to_string(),
253 role: msg
254 .get("role")
255 .and_then(|r| r.as_str())
256 .unwrap_or("unknown")
257 .to_string(),
258 timestamp: idx as u64,
259 emotion: self.detect_emotion(msg),
260 importance: self.calculate_importance(msg),
261 });
262 }
263 }
264 }
265 ConversationType::MessageArray => {
266 if let Some(msgs) = json_data.as_array() {
267 for (idx, msg) in msgs.iter().enumerate() {
268 messages.push(Message {
269 content: msg
270 .get("text")
271 .or_else(|| msg.get("content"))
272 .and_then(|c| c.as_str())
273 .unwrap_or("")
274 .to_string(),
275 role: msg
276 .get("sender")
277 .or_else(|| msg.get("role"))
278 .and_then(|r| r.as_str())
279 .unwrap_or("unknown")
280 .to_string(),
281 timestamp: idx as u64,
282 emotion: self.detect_emotion(msg),
283 importance: self.calculate_importance(msg),
284 });
285 }
286 }
287 }
288 _ => {
289 self.extract_generic_messages(json_data, &mut messages, 0);
291 }
292 }
293
294 Ok(messages)
295 }
296
297 fn extract_generic_messages(&self, value: &Value, messages: &mut Vec<Message>, depth: usize) {
299 if depth > 10 {
300 return; }
302
303 match value {
304 Value::String(s) if s.len() > 20 => {
305 messages.push(Message {
306 content: s.clone(),
307 role: "extracted".to_string(),
308 timestamp: messages.len() as u64,
309 emotion: "neutral".to_string(),
310 importance: 5,
311 });
312 }
313 Value::Object(map) => {
314 for (_key, val) in map {
315 self.extract_generic_messages(val, messages, depth + 1);
316 }
317 }
318 Value::Array(arr) => {
319 for val in arr {
320 self.extract_generic_messages(val, messages, depth + 1);
321 }
322 }
323 _ => {}
324 }
325 }
326
327 fn extract_participants(&self, messages: &[Message]) -> Vec<String> {
329 let mut participants = Vec::new();
330 for msg in messages {
331 if !participants.contains(&msg.role) {
332 participants.push(msg.role.clone());
333 }
334 }
335 participants
336 }
337
338 fn detect_emotion(&self, _msg: &Value) -> String {
340 "neutral".to_string()
342 }
343
344 fn calculate_importance(&self, msg: &Value) -> u8 {
346 if let Some(content) = msg.get("content").and_then(|c| c.as_str()) {
348 let len = content.len();
349 if len > 500 {
350 8
351 } else if len > 200 {
352 6
353 } else if len > 50 {
354 5
355 } else {
356 3
357 }
358 } else {
359 5
360 }
361 }
362
363 fn default_patterns() -> Vec<ConversationPattern> {
365 vec![
366 ConversationPattern {
367 name: "OpenAI".to_string(),
368 message_path: vec!["messages".to_string()],
369 content_field: "content".to_string(),
370 role_field: "role".to_string(),
371 },
372 ConversationPattern {
373 name: "Claude".to_string(),
374 message_path: vec!["conversation".to_string()],
375 content_field: "text".to_string(),
376 role_field: "sender".to_string(),
377 },
378 ]
379 }
380}
381
382#[derive(Debug, Clone, Serialize, Deserialize)]
384pub enum ConversationType {
385 ChatGPT,
386 Claude,
387 Generic,
388 MessageArray,
389 Unknown,
390}
391
392impl ConversationType {
393 fn as_str(&self) -> &str {
394 match self {
395 Self::ChatGPT => "chatgpt",
396 Self::Claude => "claude",
397 Self::Generic => "generic",
398 Self::MessageArray => "array",
399 Self::Unknown => "unknown",
400 }
401 }
402}
403
404impl std::fmt::Display for ConversationType {
405 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
406 write!(f, "{}", self.as_str())
407 }
408}
409
410#[derive(Debug)]
412pub struct ConversationAnalysis {
413 pub conversation_type: ConversationType,
414 pub messages: Vec<Message>,
415 pub participants: Vec<String>,
416 pub message_count: usize,
417 pub metadata: Map<String, Value>,
418}
419
420#[derive(Debug, Clone)]
422pub struct Message {
423 pub content: String,
424 pub role: String,
425 pub timestamp: u64,
426 pub emotion: String,
427 pub importance: u8,
428}
429
430#[derive(Debug, Clone)]
432pub struct ConversationPattern {
433 pub name: String,
434 pub message_path: Vec<String>,
435 pub content_field: String,
436 pub role_field: String,
437}
438
439#[derive(Debug, Serialize)]
441pub struct ConversationSummary {
442 pub file_name: String,
443 pub conversation_type: ConversationType,
444 pub message_count: usize,
445 pub participants: Vec<String>,
446 pub timestamp: std::time::SystemTime,
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452
453 #[test]
454 fn test_conversation_detection() {
455 let analyzer = ConversationAnalyzer::new();
456
457 let chatgpt_json = serde_json::json!({
459 "messages": [
460 {"role": "user", "content": "Hello"},
461 {"role": "assistant", "content": "Hi there!"}
462 ]
463 });
464
465 let analysis = analyzer.analyze(&chatgpt_json).unwrap();
466 assert!(matches!(
467 analysis.conversation_type,
468 ConversationType::ChatGPT
469 ));
470 assert_eq!(analysis.message_count, 2);
471
472 let array_json = serde_json::json!([
474 {"text": "Hello", "sender": "user"},
475 {"text": "Hi!", "sender": "bot"}
476 ]);
477
478 let analysis = analyzer.analyze(&array_json).unwrap();
479 assert!(matches!(
480 analysis.conversation_type,
481 ConversationType::MessageArray
482 ));
483 }
484
485 #[test]
486 fn test_lazy_wave_grid_initialization() {
487 let memory_result = ConversationMemory::new();
490
491 assert!(memory_result.is_ok());
493
494 let memory = memory_result.unwrap();
495
496 assert!(memory.wave_grid.is_none());
498
499 let _list_result = memory.list_conversations();
501 }
503}