use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use super::config::*;
use crate::truncate::{find_boundary, truncate_with_suffix};
pub(crate) fn truncate_str(s: &str, max_len: usize) -> String {
truncate_with_suffix(s, max_len)
}
pub(crate) fn truncate(s: &str, max_len: usize) -> String {
if s.len() <= max_len {
s.to_string()
} else {
let end = find_boundary(s, max_len);
s[..end].to_string()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[serde(rename_all = "snake_case")]
pub enum MemoryCategory {
Preference,
Decision,
Finding,
Solution,
Technical,
Structure,
KeyDecision,
FailedApproach,
UserIntentPattern,
TaskPattern,
}
impl MemoryCategory {
pub fn display_name(&self) -> &'static str {
match self {
MemoryCategory::Preference => "偏好",
MemoryCategory::Decision => "决策",
MemoryCategory::Finding => "发现",
MemoryCategory::Solution => "解决方案",
MemoryCategory::Technical => "技术",
MemoryCategory::Structure => "结构",
MemoryCategory::KeyDecision => "关键决策",
MemoryCategory::FailedApproach => "失败方案",
MemoryCategory::UserIntentPattern => "意图模式",
MemoryCategory::TaskPattern => "任务模式",
}
}
pub fn icon(&self) -> &'static str {
match self {
MemoryCategory::Preference => "👤",
MemoryCategory::Decision => "🎯",
MemoryCategory::Finding => "💡",
MemoryCategory::Solution => "🔧",
MemoryCategory::Technical => "📚",
MemoryCategory::Structure => "🏗️",
MemoryCategory::KeyDecision => "⚡",
MemoryCategory::FailedApproach => "❌",
MemoryCategory::UserIntentPattern => "🧠",
MemoryCategory::TaskPattern => "📋",
}
}
pub fn default_importance(&self) -> f64 {
match self {
MemoryCategory::Decision => DEFAULT_IMPORTANCE_DECISION,
MemoryCategory::Solution => DEFAULT_IMPORTANCE_SOLUTION,
MemoryCategory::Preference => DEFAULT_IMPORTANCE_PREF,
MemoryCategory::Finding => DEFAULT_IMPORTANCE_FINDING,
MemoryCategory::Technical => DEFAULT_IMPORTANCE_TECH,
MemoryCategory::Structure => DEFAULT_IMPORTANCE_STRUCTURE,
MemoryCategory::KeyDecision => 85.0,
MemoryCategory::FailedApproach => 70.0,
MemoryCategory::UserIntentPattern => 80.0,
MemoryCategory::TaskPattern => 75.0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryEntry {
pub id: String,
pub created_at: DateTime<Utc>,
pub last_referenced: DateTime<Utc>,
pub category: MemoryCategory,
pub content: String,
pub source_session: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub project_path: Option<String>,
pub reference_count: u32,
pub importance: f64,
pub tags: Vec<String>,
pub is_manual: bool,
}
impl MemoryEntry {
pub fn new(
category: MemoryCategory,
content: String,
source_session: Option<String>,
project_path: Option<String>,
) -> Self {
let id = uuid::Uuid::new_v4().to_string();
Self {
id,
created_at: Utc::now(),
last_referenced: Utc::now(),
category,
content,
source_session,
project_path,
reference_count: 0,
importance: category.default_importance(),
tags: Vec::new(),
is_manual: false,
}
}
pub fn manual(category: MemoryCategory, content: String, project_path: Option<String>) -> Self {
let mut entry = Self::new(category, content, None, project_path);
entry.is_manual = true;
entry.importance = 95.0;
entry
}
pub fn manual_global(category: MemoryCategory, content: String) -> Self {
Self::manual(category, content, None)
}
pub fn mark_referenced(&mut self) {
self.mark_referenced_with_increment(2.0);
}
pub fn mark_referenced_with_increment(&mut self, increment: f64) {
self.reference_count += 1;
self.last_referenced = Utc::now();
self.importance = (self.importance + increment).min(MAX_IMPORTANCE_CEILING);
}
pub fn format_line(&self) -> String {
let time = self.created_at.format("%Y-%m-%d %H:%M");
let importance_marker = if self.importance >= IMPORTANCE_STAR_THRESHOLD {
"⭐"
} else {
""
};
let manual_marker = if self.is_manual { "📝" } else { "" };
format!(
"{} {} {}{}{} {}",
self.category.icon(),
time,
importance_marker,
manual_marker,
self.category.display_name(),
truncate_str(&self.content, MAX_DISPLAY_LENGTH)
)
}
pub fn format_for_prompt(&self) -> String {
let category_name = self.category.display_name();
if self.content.len() > MAX_MEMORY_CONTENT_LENGTH {
format!(
"{}: {}...",
category_name,
truncate(&self.content, MAX_MEMORY_CONTENT_LENGTH - 3)
)
} else {
format!("{}: {}", category_name, self.content)
}
}
}