generated from nhcarrigan/template
feat: surface memory file last-modified timestamps in MemoryBrowserPanel (#230)
Extends MemoryFileInfo with last_modified (Unix timestamp) populated via fs::metadata on native platforms, and displays a formatted date in the file list as secondary text.
This commit is contained in:
@@ -1464,6 +1464,7 @@ pub async fn close_application(app_handle: AppHandle) -> Result<(), String> {
|
|||||||
pub struct MemoryFileInfo {
|
pub struct MemoryFileInfo {
|
||||||
pub path: String,
|
pub path: String,
|
||||||
pub heading: Option<String>,
|
pub heading: Option<String>,
|
||||||
|
pub last_modified: Option<String>, // Unix timestamp in seconds as a string
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
@@ -1535,7 +1536,11 @@ async fn list_memory_files_via_wsl() -> Result<MemoryFilesResponse, String> {
|
|||||||
let mut files = Vec::new();
|
let mut files = Vec::new();
|
||||||
for path in paths {
|
for path in paths {
|
||||||
let heading = read_wsl_file_first_heading(&path);
|
let heading = read_wsl_file_first_heading(&path);
|
||||||
files.push(MemoryFileInfo { path, heading });
|
files.push(MemoryFileInfo {
|
||||||
|
path,
|
||||||
|
heading,
|
||||||
|
last_modified: None,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(MemoryFilesResponse { files })
|
Ok(MemoryFilesResponse { files })
|
||||||
@@ -1605,14 +1610,23 @@ async fn list_memory_files_native() -> Result<MemoryFilesResponse, String> {
|
|||||||
// Sort files alphabetically
|
// Sort files alphabetically
|
||||||
memory_paths.sort();
|
memory_paths.sort();
|
||||||
|
|
||||||
// Read first heading from each file
|
// Read first heading and modification time from each file
|
||||||
let files = memory_paths
|
let files = memory_paths
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|path| {
|
.map(|path| {
|
||||||
let heading = fs::read_to_string(&path)
|
let heading = fs::read_to_string(&path)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|content| extract_first_heading(&content));
|
.and_then(|content| extract_first_heading(&content));
|
||||||
MemoryFileInfo { path, heading }
|
let last_modified = fs::metadata(&path)
|
||||||
|
.ok()
|
||||||
|
.and_then(|m| m.modified().ok())
|
||||||
|
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
|
||||||
|
.map(|d| d.as_secs().to_string());
|
||||||
|
MemoryFileInfo {
|
||||||
|
path,
|
||||||
|
heading,
|
||||||
|
last_modified,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
interface MemoryFileInfo {
|
interface MemoryFileInfo {
|
||||||
path: string;
|
path: string;
|
||||||
heading: string | null;
|
heading: string | null;
|
||||||
|
last_modified?: string; // Unix timestamp in seconds as a string, optional for backwards compat
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MemoryFilesResponse {
|
interface MemoryFilesResponse {
|
||||||
@@ -65,6 +66,16 @@
|
|||||||
return file.heading ?? getFileName(file.path);
|
return file.heading ?? getFileName(file.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatLastModified(ts: string | undefined): string {
|
||||||
|
if (!ts) return "";
|
||||||
|
const date = new Date(Number(ts) * 1000);
|
||||||
|
return date.toLocaleDateString(undefined, {
|
||||||
|
year: "numeric",
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function sendMemoryCommand() {
|
async function sendMemoryCommand() {
|
||||||
const conversationId = get(claudeStore.activeConversationId);
|
const conversationId = get(claudeStore.activeConversationId);
|
||||||
if (!conversationId) return;
|
if (!conversationId) return;
|
||||||
@@ -205,7 +216,12 @@
|
|||||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="file-name">{getDisplayName(file)}</span>
|
<div class="file-info">
|
||||||
|
<span class="file-name">{getDisplayName(file)}</span>
|
||||||
|
{#if file.last_modified}
|
||||||
|
<span class="file-date">{formatLastModified(file.last_modified)}</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -416,7 +432,7 @@
|
|||||||
|
|
||||||
.file-item {
|
.file-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
@@ -445,6 +461,13 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.125rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.file-name {
|
.file-name {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -453,6 +476,15 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-date {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item.active .file-date {
|
||||||
|
color: rgba(255, 255, 255, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
.file-viewer {
|
.file-viewer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
Reference in New Issue
Block a user