1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use super::config::*;
7use crate::truncate::{find_boundary, truncate_with_suffix};
8
9pub(crate) fn truncate_str(s: &str, max_len: usize) -> String {
15 truncate_with_suffix(s, max_len)
16}
17
18pub(crate) fn truncate(s: &str, max_len: usize) -> String {
20 if s.len() <= max_len {
21 s.to_string()
22 } else {
23 let end = find_boundary(s, max_len);
24 s[..end].to_string()
25 }
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
34#[serde(rename_all = "snake_case")]
35pub enum MemoryCategory {
36 Preference,
38 Decision,
40 Finding,
42 Solution,
44 Technical,
46 Structure,
48 KeyDecision,
50 FailedApproach,
52 UserIntentPattern,
54 TaskPattern,
56}
57
58impl MemoryCategory {
59 pub fn display_name(&self) -> &'static str {
61 match self {
62 MemoryCategory::Preference => "偏好",
63 MemoryCategory::Decision => "决策",
64 MemoryCategory::Finding => "发现",
65 MemoryCategory::Solution => "解决方案",
66 MemoryCategory::Technical => "技术",
67 MemoryCategory::Structure => "结构",
68 MemoryCategory::KeyDecision => "关键决策",
69 MemoryCategory::FailedApproach => "失败方案",
70 MemoryCategory::UserIntentPattern => "意图模式",
71 MemoryCategory::TaskPattern => "任务模式",
72 }
73 }
74
75 pub fn icon(&self) -> &'static str {
77 match self {
78 MemoryCategory::Preference => "👤",
79 MemoryCategory::Decision => "🎯",
80 MemoryCategory::Finding => "💡",
81 MemoryCategory::Solution => "🔧",
82 MemoryCategory::Technical => "📚",
83 MemoryCategory::Structure => "🏗️",
84 MemoryCategory::KeyDecision => "⚡",
85 MemoryCategory::FailedApproach => "❌",
86 MemoryCategory::UserIntentPattern => "🧠",
87 MemoryCategory::TaskPattern => "📋",
88 }
89 }
90
91 pub fn default_importance(&self) -> f64 {
93 match self {
94 MemoryCategory::Decision => DEFAULT_IMPORTANCE_DECISION,
95 MemoryCategory::Solution => DEFAULT_IMPORTANCE_SOLUTION,
96 MemoryCategory::Preference => DEFAULT_IMPORTANCE_PREF,
97 MemoryCategory::Finding => DEFAULT_IMPORTANCE_FINDING,
98 MemoryCategory::Technical => DEFAULT_IMPORTANCE_TECH,
99 MemoryCategory::Structure => DEFAULT_IMPORTANCE_STRUCTURE,
100 MemoryCategory::KeyDecision => 85.0,
101 MemoryCategory::FailedApproach => 70.0,
102 MemoryCategory::UserIntentPattern => 80.0,
103 MemoryCategory::TaskPattern => 75.0,
104 }
105 }
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct MemoryEntry {
115 pub id: String,
117 pub created_at: DateTime<Utc>,
119 pub last_referenced: DateTime<Utc>,
121 pub category: MemoryCategory,
123 pub content: String,
125 pub source_session: Option<String>,
127 #[serde(default, skip_serializing_if = "Option::is_none")]
129 pub project_path: Option<String>,
130 pub reference_count: u32,
132 pub importance: f64,
134 pub tags: Vec<String>,
136 pub is_manual: bool,
138}
139
140impl MemoryEntry {
141 pub fn new(
143 category: MemoryCategory,
144 content: String,
145 source_session: Option<String>,
146 project_path: Option<String>,
147 ) -> Self {
148 let id = uuid::Uuid::new_v4().to_string();
149 Self {
150 id,
151 created_at: Utc::now(),
152 last_referenced: Utc::now(),
153 category,
154 content,
155 source_session,
156 project_path,
157 reference_count: 0,
158 importance: category.default_importance(),
159 tags: Vec::new(),
160 is_manual: false,
161 }
162 }
163
164 pub fn manual(category: MemoryCategory, content: String, project_path: Option<String>) -> Self {
166 let mut entry = Self::new(category, content, None, project_path);
167 entry.is_manual = true;
168 entry.importance = 95.0;
169 entry
170 }
171
172 pub fn manual_global(category: MemoryCategory, content: String) -> Self {
174 Self::manual(category, content, None)
175 }
176
177 pub fn mark_referenced(&mut self) {
179 self.mark_referenced_with_increment(2.0);
180 }
181
182 pub fn mark_referenced_with_increment(&mut self, increment: f64) {
184 self.reference_count += 1;
185 self.last_referenced = Utc::now();
186 self.importance = (self.importance + increment).min(MAX_IMPORTANCE_CEILING);
187 }
188
189 pub fn format_line(&self) -> String {
191 let time = self.created_at.format("%Y-%m-%d %H:%M");
192 let importance_marker = if self.importance >= IMPORTANCE_STAR_THRESHOLD {
193 "⭐"
194 } else {
195 ""
196 };
197 let manual_marker = if self.is_manual { "📝" } else { "" };
198 format!(
199 "{} {} {}{}{} {}",
200 self.category.icon(),
201 time,
202 importance_marker,
203 manual_marker,
204 self.category.display_name(),
205 truncate_str(&self.content, MAX_DISPLAY_LENGTH)
206 )
207 }
208
209 pub fn format_for_prompt(&self) -> String {
212 if self.content.len() > MAX_MEMORY_CONTENT_LENGTH {
213 format!(
214 "{}...",
215 truncate(&self.content, MAX_MEMORY_CONTENT_LENGTH - 3)
216 )
217 } else {
218 self.content.clone()
219 }
220 }
221
222 pub fn format_for_prompt_with_category(&self) -> String {
225 let category_name = self.category.display_name();
226 if self.content.len() > MAX_MEMORY_CONTENT_LENGTH {
227 format!(
228 "{}: {}...",
229 category_name,
230 truncate(&self.content, MAX_MEMORY_CONTENT_LENGTH - 3)
231 )
232 } else {
233 format!("{}: {}", category_name, self.content)
234 }
235 }
236}