Files
clawpanel/src-tauri/src/commands/memory.rs
晴天 1b9a195d32 feat: 精简页面结构并增强核心功能
- 删除 MCP 配置、Agent 配置、部署 3 个页面,保留 6 个核心页面
- 重写模型配置页:Provider/模型 CRUD + 一键应用默认模型(自动生成 fallback)
- 增强服务管理页:版本检测 + 配置备份管理(创建/恢复/删除)
- 增强记忆文件页:单个文件下载 + 分类打包 zip 下载
- Rust 后端新增 5 个命令(4 个备份 + export_memory_zip)
- 更新路由和侧边栏,同步清理
2026-02-27 00:16:45 +08:00

177 lines
5.0 KiB
Rust

/// 记忆文件管理命令
use std::fs;
use std::io::Write;
use std::path::PathBuf;
fn openclaw_dir() -> PathBuf {
dirs::home_dir()
.unwrap_or_default()
.join(".openclaw")
}
fn memory_dir(category: &str) -> PathBuf {
match category {
"memory" => openclaw_dir().join("workspace").join("memory"),
"archive" => openclaw_dir().join("workspace-memory"),
"core" => openclaw_dir().join("workspace"),
_ => openclaw_dir().join("workspace").join("memory"),
}
}
#[tauri::command]
pub fn list_memory_files(category: String) -> Result<Vec<String>, String> {
let dir = memory_dir(&category);
if !dir.exists() {
return Ok(vec![]);
}
let mut files = Vec::new();
collect_files(&dir, &dir, &mut files, &category)?;
files.sort();
Ok(files)
}
fn collect_files(
base: &PathBuf,
dir: &PathBuf,
files: &mut Vec<String>,
category: &str,
) -> Result<(), String> {
let entries = fs::read_dir(dir)
.map_err(|e| format!("读取目录失败: {e}"))?;
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
// core 类别只读根目录的 .md 文件
if category != "core" {
collect_files(base, &path, files, category)?;
}
} else {
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
if matches!(ext, "md" | "txt" | "json" | "jsonl") {
let rel = path.strip_prefix(base)
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|_| path.to_string_lossy().to_string());
files.push(rel);
}
}
}
Ok(())
}
#[tauri::command]
pub fn read_memory_file(path: String) -> Result<String, String> {
// 安全检查:路径不能包含 ..
if path.contains("..") {
return Err("非法路径".to_string());
}
// 尝试在各个记忆目录下查找
let candidates = [
memory_dir("memory").join(&path),
memory_dir("archive").join(&path),
memory_dir("core").join(&path),
];
for candidate in &candidates {
if candidate.exists() {
return fs::read_to_string(candidate)
.map_err(|e| format!("读取失败: {e}"));
}
}
Err(format!("文件不存在: {path}"))
}
#[tauri::command]
pub fn write_memory_file(path: String, content: String) -> Result<(), String> {
if path.contains("..") {
return Err("非法路径".to_string());
}
let candidates = [
memory_dir("memory").join(&path),
memory_dir("archive").join(&path),
memory_dir("core").join(&path),
];
for candidate in &candidates {
if candidate.exists() {
return fs::write(candidate, &content)
.map_err(|e| format!("写入失败: {e}"));
}
}
// 默认写入 memory 目录
let target = memory_dir("memory").join(&path);
if let Some(parent) = target.parent() {
let _ = fs::create_dir_all(parent);
}
fs::write(&target, &content)
.map_err(|e| format!("写入失败: {e}"))
}
#[tauri::command]
pub fn delete_memory_file(path: String) -> Result<(), String> {
if path.contains("..") {
return Err("非法路径".to_string());
}
let candidates = [
memory_dir("memory").join(&path),
memory_dir("archive").join(&path),
memory_dir("core").join(&path),
];
for candidate in &candidates {
if candidate.exists() {
return fs::remove_file(candidate)
.map_err(|e| format!("删除失败: {e}"));
}
}
Err(format!("文件不存在: {path}"))
}
#[tauri::command]
pub fn export_memory_zip(category: String) -> Result<String, String> {
let dir = memory_dir(&category);
if !dir.exists() {
return Err("目录不存在".to_string());
}
let mut files = Vec::new();
collect_files(&dir, &dir, &mut files, &category)?;
if files.is_empty() {
return Err("没有可导出的文件".to_string());
}
let tmp_dir = std::env::temp_dir();
let zip_name = format!(
"openclaw-{}-{}.zip",
category,
chrono::Local::now().format("%Y%m%d-%H%M%S")
);
let zip_path = tmp_dir.join(&zip_name);
let file = fs::File::create(&zip_path)
.map_err(|e| format!("创建 zip 失败: {e}"))?;
let mut zip = zip::ZipWriter::new(file);
let options = zip::write::SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Deflated);
for rel_path in &files {
let full_path = dir.join(rel_path);
let content = fs::read_to_string(&full_path)
.map_err(|e| format!("读取 {rel_path} 失败: {e}"))?;
zip.start_file(rel_path, options)
.map_err(|e| format!("写入 zip 失败: {e}"))?;
zip.write_all(content.as_bytes())
.map_err(|e| format!("写入内容失败: {e}"))?;
}
zip.finish().map_err(|e| format!("完成 zip 失败: {e}"))?;
Ok(zip_path.to_string_lossy().to_string())
}