use super::storage::ChatMessage;
use crate::error;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatArchive {
pub name: String,
pub created_at: String,
pub messages: Vec<ChatMessage>,
}
pub fn get_archives_dir() -> PathBuf {
super::storage::agent_data_dir().join("archives")
}
pub fn ensure_archives_dir() -> std::io::Result<()> {
let dir = get_archives_dir();
if !dir.exists() {
fs::create_dir_all(&dir)?;
}
Ok(())
}
pub fn list_archives() -> Vec<ChatArchive> {
let dir = get_archives_dir();
if !dir.exists() {
return Vec::new();
}
let mut archives = Vec::new();
if let Ok(entries) = fs::read_dir(&dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().is_some_and(|ext| ext == "json")
&& let Ok(content) = fs::read_to_string(&path)
{
match serde_json::from_str::<ChatArchive>(&content) {
Ok(archive) => archives.push(archive),
Err(e) => {
error!("[list_archives] 解析归档文件失败: {:?}, 错误: {}", path, e);
}
}
}
}
}
archives.sort_by(|a, b| b.created_at.cmp(&a.created_at));
archives
}
pub fn create_archive(name: &str, messages: Vec<ChatMessage>) -> Result<ChatArchive, String> {
validate_archive_name(name)?;
if let Err(e) = ensure_archives_dir() {
return Err(format!("创建归档目录失败: {}", e));
}
let now: DateTime<Utc> = Utc::now();
let archive = ChatArchive {
name: name.to_string(),
created_at: now.to_rfc3339(),
messages,
};
let path = get_archive_path(name);
let json =
serde_json::to_string_pretty(&archive).map_err(|e| format!("序列化归档失败: {}", e))?;
fs::write(&path, json).map_err(|e| format!("写入归档文件失败: {}", e))?;
Ok(archive)
}
pub fn restore_archive(name: &str) -> Result<Vec<ChatMessage>, String> {
let path = get_archive_path(name);
if !path.exists() {
return Err(format!("归档文件不存在: {}", name));
}
let content = fs::read_to_string(&path).map_err(|e| format!("读取归档文件失败: {}", e))?;
let archive: ChatArchive =
serde_json::from_str(&content).map_err(|e| format!("解析归档文件失败: {}", e))?;
Ok(archive.messages)
}
pub fn delete_archive(name: &str) -> Result<(), String> {
let path = get_archive_path(name);
if !path.exists() {
return Err(format!("归档文件不存在: {}", name));
}
fs::remove_file(&path).map_err(|e| format!("删除归档文件失败: {}", e))?;
Ok(())
}
pub fn validate_archive_name(name: &str) -> Result<(), String> {
if name.is_empty() {
return Err("归档名称不能为空".to_string());
}
if name.len() > 50 {
return Err("归档名称过长,最多 50 字符".to_string());
}
let invalid_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|'];
for c in invalid_chars {
if name.contains(c) {
return Err(format!("归档名称包含非法字符: {}", c));
}
}
Ok(())
}
pub fn generate_default_archive_name() -> String {
let today = chrono::Local::now().format("%Y-%m-%d").to_string();
let base_name = format!("archive-{}", today);
if !archive_exists(&base_name) {
return base_name;
}
let mut suffix = 1;
loop {
let name = format!("{}({})", base_name, suffix);
if !archive_exists(&name) {
return name;
}
suffix += 1;
}
}
pub fn archive_exists(name: &str) -> bool {
let path = get_archives_dir().join(format!("{}.json", name));
path.exists()
}
fn get_archive_path(name: &str) -> PathBuf {
get_archives_dir().join(format!("{}.json", name))
}