generated from nhcarrigan/template
feat: add memory system activity display
Implements issue #118 to display auto-memory system operations and provide a browser for viewing memory files. Backend changes: - Detect memory file operations in format_tool_description() - Add emoji icons (📝/💾) for memory Read/Write/Edit operations - Add list_memory_files() Tauri command to find all memory files - Add 4 new tests for memory detection logic - Update Tauri capabilities to allow reading .claude directory Frontend changes: - Create MemoryBrowserPanel component with file list and viewer - Add memory panel to main app layout - Support Markdown rendering of memory file contents - Add loading states and error handling Testing: - All 354 Rust tests passing - TypeScript type-checks pass Co-Authored-By: Hikari <hikari@nhcarrigan.com>
This commit is contained in:
@@ -1167,6 +1167,76 @@ pub async fn close_application(app_handle: AppHandle) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct MemoryFilesResponse {
|
||||
pub files: Vec<String>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_memory_files() -> Result<MemoryFilesResponse, String> {
|
||||
use std::fs;
|
||||
|
||||
// Get the .claude directory in the user's home
|
||||
let home_dir = match dirs::home_dir() {
|
||||
Some(dir) => dir,
|
||||
None => return Err("Could not find home directory".to_string()),
|
||||
};
|
||||
|
||||
let claude_dir = home_dir.join(".claude");
|
||||
let projects_dir = claude_dir.join("projects");
|
||||
|
||||
if !projects_dir.exists() {
|
||||
return Ok(MemoryFilesResponse { files: Vec::new() });
|
||||
}
|
||||
|
||||
let mut memory_files = Vec::new();
|
||||
|
||||
// Recursively find all memory directories
|
||||
fn find_memory_files(dir: &std::path::Path, files: &mut Vec<String>) -> std::io::Result<()> {
|
||||
if !dir.is_dir() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for entry in fs::read_dir(dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
if path.is_dir() {
|
||||
// Check if this is a "memory" directory
|
||||
if path.file_name().and_then(|n| n.to_str()) == Some("memory") {
|
||||
// List all files in the memory directory
|
||||
for mem_entry in fs::read_dir(&path)? {
|
||||
let mem_entry = mem_entry?;
|
||||
let mem_path = mem_entry.path();
|
||||
|
||||
if mem_path.is_file() {
|
||||
if let Some(path_str) = mem_path.to_str() {
|
||||
files.push(path_str.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Recurse into subdirectories
|
||||
find_memory_files(&path, files)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
if let Err(e) = find_memory_files(&projects_dir, &mut memory_files) {
|
||||
return Err(format!("Failed to list memory files: {}", e));
|
||||
}
|
||||
|
||||
// Sort files alphabetically
|
||||
memory_files.sort();
|
||||
|
||||
Ok(MemoryFilesResponse {
|
||||
files: memory_files,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -197,6 +197,7 @@ pub fn run() {
|
||||
stop_discord_rpc,
|
||||
log_discord_rpc,
|
||||
close_application,
|
||||
list_memory_files,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
@@ -1505,10 +1505,21 @@ fn get_tool_state(tool_name: &str) -> CharacterState {
|
||||
}
|
||||
|
||||
fn format_tool_description(name: &str, input: &serde_json::Value) -> String {
|
||||
// Helper function to check if a path is a memory file
|
||||
fn is_memory_path(path: &str) -> bool {
|
||||
path.contains("/.claude/") && (path.contains("/memory/") || path.ends_with("/MEMORY.md"))
|
||||
}
|
||||
|
||||
match name {
|
||||
"Read" => {
|
||||
if let Some(path) = input.get("file_path").and_then(|v| v.as_str()) {
|
||||
format!("Reading file: {}", path)
|
||||
if is_memory_path(path) {
|
||||
// Extract just the filename for cleaner display
|
||||
let filename = path.split('/').last().unwrap_or(path);
|
||||
format!("📝 Reading memory: {}", filename)
|
||||
} else {
|
||||
format!("Reading file: {}", path)
|
||||
}
|
||||
} else {
|
||||
"Reading file...".to_string()
|
||||
}
|
||||
@@ -1527,9 +1538,26 @@ fn format_tool_description(name: &str, input: &serde_json::Value) -> String {
|
||||
"Searching in files...".to_string()
|
||||
}
|
||||
}
|
||||
"Edit" | "Write" => {
|
||||
"Edit" => {
|
||||
if let Some(path) = input.get("file_path").and_then(|v| v.as_str()) {
|
||||
format!("Editing: {}", path)
|
||||
if is_memory_path(path) {
|
||||
let filename = path.split('/').last().unwrap_or(path);
|
||||
format!("💾 Updating memory: {}", filename)
|
||||
} else {
|
||||
format!("Editing: {}", path)
|
||||
}
|
||||
} else {
|
||||
"Editing file...".to_string()
|
||||
}
|
||||
}
|
||||
"Write" => {
|
||||
if let Some(path) = input.get("file_path").and_then(|v| v.as_str()) {
|
||||
if is_memory_path(path) {
|
||||
let filename = path.split('/').last().unwrap_or(path);
|
||||
format!("💾 Writing memory: {}", filename)
|
||||
} else {
|
||||
format!("Editing: {}", path)
|
||||
}
|
||||
} else {
|
||||
"Editing file...".to_string()
|
||||
}
|
||||
@@ -1714,6 +1742,39 @@ mod tests {
|
||||
assert_eq!(desc, "Using tool: CustomTool");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_tool_description_memory_read() {
|
||||
let input =
|
||||
serde_json::json!({"file_path": "/home/user/.claude/projects/test/memory/MEMORY.md"});
|
||||
let desc = format_tool_description("Read", &input);
|
||||
assert_eq!(desc, "📝 Reading memory: MEMORY.md");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_tool_description_memory_write() {
|
||||
let input = serde_json::json!(
|
||||
{"file_path": "/home/user/.claude/projects/test/memory/notes.md"}
|
||||
);
|
||||
let desc = format_tool_description("Write", &input);
|
||||
assert_eq!(desc, "💾 Writing memory: notes.md");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_tool_description_memory_edit() {
|
||||
let input = serde_json::json!(
|
||||
{"file_path": "/home/user/.claude/projects/test/memory/patterns.md"}
|
||||
);
|
||||
let desc = format_tool_description("Edit", &input);
|
||||
assert_eq!(desc, "💾 Updating memory: patterns.md");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_tool_description_non_memory_read() {
|
||||
let input = serde_json::json!({"file_path": "/home/user/code/test.txt"});
|
||||
let desc = format_tool_description("Read", &input);
|
||||
assert_eq!(desc, "Reading file: /home/user/code/test.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wsl_bridge_new() {
|
||||
let bridge = WslBridge::new();
|
||||
|
||||
Reference in New Issue
Block a user