1use serde::{Deserialize, Serialize};
2use chrono::{DateTime, Utc};
3use std::collections::HashMap;
4use uuid::Uuid;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "lowercase")]
8#[derive(Default)]
9pub enum MemoryType {
10 #[default]
11 Conversation,
12 Knowledge,
13 Decision,
14 Preference,
15 Context,
16}
17
18
19impl std::fmt::Display for MemoryType {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 match self {
22 MemoryType::Conversation => write!(f, "conversation"),
23 MemoryType::Knowledge => write!(f, "knowledge"),
24 MemoryType::Decision => write!(f, "decision"),
25 MemoryType::Preference => write!(f, "preference"),
26 MemoryType::Context => write!(f, "context"),
27 }
28 }
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct Memory {
33 pub id: Uuid,
34 pub memory_type: MemoryType,
35 pub content: String,
36 pub summary: Option<String>,
37 pub embedding: Option<Vec<f32>>,
38 pub importance: f32,
39 pub created_at: DateTime<Utc>,
40 pub last_accessed: DateTime<Utc>,
41 pub access_count: u32,
42 pub tags: Vec<String>,
43 pub metadata: HashMap<String, String>,
44 pub project_id: Option<Uuid>,
45 pub is_deleted: bool,
46 pub deleted_at: Option<DateTime<Utc>>,
47
48 pub chunk_group_id: Option<Uuid>,
49 pub chunk_index: Option<u32>,
50 pub chunk_total: Option<u32>,
51}
52
53impl Memory {
54 pub fn new(content: String, memory_type: MemoryType) -> Self {
55 let now = Utc::now();
56 Self {
57 id: Uuid::new_v4(),
58 memory_type,
59 content,
60 summary: None,
61 embedding: None,
62 importance: 0.8,
63 created_at: now,
64 last_accessed: now,
65 access_count: 0,
66 tags: Vec::new(),
67 metadata: HashMap::new(),
68 project_id: None,
69 is_deleted: false,
70 deleted_at: None,
71 chunk_group_id: None,
72 chunk_index: None,
73 chunk_total: None,
74 }
75 }
76
77 pub fn with_tags(mut self, tags: Vec<String>) -> Self {
78 self.tags = tags;
79 self
80 }
81
82 pub fn with_project(mut self, project_id: Uuid) -> Self {
83 self.project_id = Some(project_id);
84 self
85 }
86
87 pub fn with_chunk_info(mut self, group_id: Uuid, index: u32, total: u32) -> Self {
88 self.chunk_group_id = Some(group_id);
89 self.chunk_index = Some(index);
90 self.chunk_total = Some(total);
91 self
92 }
93
94 pub fn access(&mut self) {
95 self.last_accessed = Utc::now();
96 self.access_count += 1;
97 }
98
99 pub fn is_chunked(&self) -> bool {
100 self.chunk_group_id.is_some()
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn test_memory_type_serde() {
110 let types = [
111 MemoryType::Conversation,
112 MemoryType::Knowledge,
113 MemoryType::Decision,
114 MemoryType::Preference,
115 MemoryType::Context,
116 ];
117
118 for t in types {
119 let json = serde_json::to_string(&t).unwrap();
120 let parsed: MemoryType = serde_json::from_str(&json).unwrap();
121 assert_eq!(t, parsed);
122 }
123 }
124
125 #[test]
126 fn test_memory_type_json_values() {
127 assert_eq!(serde_json::to_string(&MemoryType::Conversation).unwrap(), "\"conversation\"");
128 assert_eq!(serde_json::to_string(&MemoryType::Knowledge).unwrap(), "\"knowledge\"");
129 }
130
131 #[test]
132 fn test_memory_creation() {
133 let memory = Memory::new("test content".to_string(), MemoryType::Knowledge);
134
135 assert!(!memory.id.to_string().is_empty());
136 assert_eq!(memory.memory_type, MemoryType::Knowledge);
137 assert_eq!(memory.content, "test content");
138 assert!(memory.embedding.is_none());
139 assert_eq!(memory.importance, 0.8);
140 assert_eq!(memory.access_count, 0);
141 assert!(memory.tags.is_empty());
142 assert!(!memory.is_deleted);
143 }
144
145 #[test]
146 fn test_memory_with_tags() {
147 let memory = Memory::new("test".to_string(), MemoryType::Decision)
148 .with_tags(vec!["important".to_string(), "project-x".to_string()]);
149
150 assert_eq!(memory.tags.len(), 2);
151 assert!(memory.tags.contains(&"important".to_string()));
152 }
153
154 #[test]
155 fn test_memory_access() {
156 let mut memory = Memory::new("test".to_string(), MemoryType::Conversation);
157 let initial_accessed = memory.last_accessed;
158
159 memory.access();
160
161 assert!(memory.last_accessed > initial_accessed);
162 assert_eq!(memory.access_count, 1);
163 }
164
165 #[test]
166 fn test_memory_serde() {
167 let memory = Memory::new("test content".to_string(), MemoryType::Knowledge)
168 .with_tags(vec!["tag1".to_string()]);
169
170 let json = serde_json::to_string(&memory).unwrap();
171 let parsed: Memory = serde_json::from_str(&json).unwrap();
172
173 assert_eq!(memory.id, parsed.id);
174 assert_eq!(memory.content, parsed.content);
175 assert_eq!(memory.tags, parsed.tags);
176 }
177
178 #[test]
179 fn test_memory_chunk_fields() {
180 let group_id = Uuid::new_v4();
181 let memory = Memory::new("test".to_string(), MemoryType::Knowledge)
182 .with_chunk_info(group_id, 0, 3);
183
184 assert_eq!(memory.chunk_group_id, Some(group_id));
185 assert_eq!(memory.chunk_index, Some(0));
186 assert_eq!(memory.chunk_total, Some(3));
187 assert!(memory.is_chunked());
188 }
189
190 #[test]
191 fn test_memory_chunk_serde() {
192 let group_id = Uuid::new_v4();
193 let memory = Memory::new("test".to_string(), MemoryType::Knowledge)
194 .with_chunk_info(group_id, 1, 5);
195
196 let json = serde_json::to_string(&memory).unwrap();
197 let parsed: Memory = serde_json::from_str(&json).unwrap();
198
199 assert_eq!(memory.chunk_group_id, parsed.chunk_group_id);
200 assert_eq!(memory.chunk_index, parsed.chunk_index);
201 assert_eq!(memory.chunk_total, parsed.chunk_total);
202 }
203}