use anyhow::{anyhow, Context, Result};
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use chrono::Utc;
#[derive(Debug, Clone)]
pub struct MemoryManager {
memory_dir: PathBuf,
}
impl MemoryManager {
pub fn new(memory_dir: PathBuf) -> Result<Self> {
fs::create_dir_all(&memory_dir)
.with_context(|| format!("Failed to create memory directory at {}", memory_dir.display()))?;
for mem_file in ["long_term.md", "short_term.md"] {
let file_path = memory_dir.join(mem_file);
if !file_path.exists() {
let header = format!("# Prime {} Memory\n\n(This file is for notes. The AI will read this.)",
if mem_file == "long_term.md" { "Long-term" } else { "Short-term" });
fs::write(&file_path, header)
.with_context(|| format!("Failed to create initial memory file at {}", file_path.display()))?;
}
}
Ok(Self { memory_dir })
}
pub fn read_memory(&self, memory_type: Option<&str>) -> Result<String> {
let mut memory_content = String::new();
match memory_type {
Some("long_term") => {
let content = self.read_file("long_term.md")?;
memory_content.push_str("## Long-term Memory\n");
memory_content.push_str(&content);
}
Some("short_term") => {
let content = self.read_file("short_term.md")?;
memory_content.push_str("## Short-term Memory\n");
memory_content.push_str(&content);
}
None => {
let long_term = self.read_file("long_term.md").unwrap_or_default();
let short_term = self.read_file("short_term.md").unwrap_or_default();
memory_content.push_str("\n<LONG_TERM_MEMORY>\n");
memory_content.push_str(long_term.trim());
memory_content.push_str("\n</LONG_TERM_MEMORY>\n");
memory_content.push_str("\n<SHORT_TERM_MEMORY>\n");
memory_content.push_str(short_term.trim());
memory_content.push_str("\n</SHORT_TERM_MEMORY>\n");
}
Some(other) => return Err(anyhow!("Invalid memory type '{}' specified", other)),
}
Ok(memory_content)
}
pub fn write_memory(&self, memory_type: &str, content: &str) -> Result<()> {
let file_name = match memory_type {
"long_term" => "long_term.md",
"short_term" => "short_term.md",
_ => return Err(anyhow!("Invalid memory type '{}' specified", memory_type)),
};
let file_path = self.memory_dir.join(file_name);
let timestamp = Utc::now();
let entry = format!("\n## Entry ({})\n{}\n", timestamp, content);
fs::OpenOptions::new()
.append(true)
.open(&file_path)
.with_context(|| format!("Failed to open memory file for writing: {}", file_path.display()))
.and_then(|mut file| {
file.write_all(entry.as_bytes())
.map_err(|e| anyhow::anyhow!("Failed to write to memory file: {}", e))
})
.with_context(|| format!("Failed to write to memory file: {}", file_path.display()))
}
pub fn clear_memory(&self, memory_type: &str) -> Result<()> {
let file_name = match memory_type {
"long_term" => "long_term.md",
"short_term" => "short_term.md",
_ => return Err(anyhow!("Invalid memory type '{}' specified", memory_type)),
};
let file_path = self.memory_dir.join(file_name);
let header = format!("# Prime {} Memory\n\n(This file is for notes. The AI will read this.)",
if file_name == "long_term.md" { "Long-term" } else { "Short-term" });
fs::write(&file_path, header)
.with_context(|| format!("Failed to clear memory file: {}", file_path.display()))
}
fn read_file(&self, file_name: &str) -> Result<String> {
let file_path = self.memory_dir.join(file_name);
fs::read_to_string(&file_path)
.with_context(|| format!("Failed to read memory file: {}", file_path.display()))
}
}