From b3fb93649460e206457460aa67c8a21d2cd13a68 Mon Sep 17 00:00:00 2001 From: Storme-bit Date: Fri, 17 Apr 2026 19:50:13 -0700 Subject: [PATCH] memory view in chat client --- packages/chat-client/src/App.jsx | 3 + packages/chat-client/src/api/orchestration.js | 17 ++ .../chat-client/src/components/MemoryView.jsx | 167 ++++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 packages/chat-client/src/components/MemoryView.jsx diff --git a/packages/chat-client/src/App.jsx b/packages/chat-client/src/App.jsx index c47ebf7..d284a98 100644 --- a/packages/chat-client/src/App.jsx +++ b/packages/chat-client/src/App.jsx @@ -9,6 +9,7 @@ import AllChatsView from './components/AllChatsView'; import AllProjectsView from './components/AllProjectsView'; import SettingsView from './components/SettingsView'; import ProjectView from './components/ProjectView'; +import MemoryView from './components/MemoryView'; /**** useHooks **** */ import { useSession } from './hooks/useSession'; @@ -124,6 +125,8 @@ export default function App() { onNewProjectChat={handleNewProjectChat} /> )} + + {view === 'memory' && } { + setLoading(true); + setError(null); + try { + const data = await getEpisodes({ limit: PAGE_SIZE, offset, q: query || undefined }); + setEpisodes(data.episodes); + setTotal(data.total); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + }, [offset, query]); + + useEffect(() => { load(); }, [load]); + + function handleSearch(e) { + e.preventDefault(); + setOffset(0); // reset to page 1 on new search + setQuery(search); + } + + async function handleDelete(id) { + if (!confirm('Delete this memory? This cannot be undone.')) return; + await deleteEpisode(id); + load(); + } + + const totalPages = Math.ceil(total / PAGE_SIZE); + const currentPage = Math.floor(offset / PAGE_SIZE) + 1; + + return ( +
+ + {/* Header */} +
+ + Memory Viewer + + {total} episode{total !== 1 ? 's' : ''} + +
+ + {/* Search bar */} +
+
+ setSearch(e.target.value)} + placeholder="Search memories…" + style={{ + flex: 1, padding: '8px 12px', + background: 'var(--bg-surface)', border: '1px solid var(--border)', + borderRadius: 'var(--radius)', color: 'var(--text-primary)', + }} + /> + + {query && ( + + )} +
+
+ + {/* Episode list */} +
+ {loading &&

Loading…

} + {error &&

{error}

} + {!loading && episodes.length === 0 && ( +

No memories found.

+ )} + + {episodes.map(ep => ( + setExpanded(expanded === ep.id ? null : ep.id)} + onDelete={() => handleDelete(ep.id)} + /> + ))} +
+ + {/* Pagination */} + {totalPages > 1 && ( +
+ + {currentPage} / {totalPages} + +
+ )} +
+ ); +} + +function EpisodeCard({ episode, expanded, onToggle, onDelete }) { + const date = new Date(episode.created_at * 1000).toLocaleString(); + const preview = episode.user_message.slice(0, 80) + (episode.user_message.length > 80 ? '…' : ''); + + return ( +
+ {/* Card header — always visible */} +
+ {preview} + {date} + #{episode.id} + + {expanded ? '▲' : '▼'} +
+ + {/* Expanded content */} + {expanded && ( +
+ + + {episode.token_count > 0 && ( +

+ Tokens: {episode.token_count} +

+ )} +
+ )} +
+ ); +} + +function MessageBlock({ label, content, color }) { + return ( +
+

+ {label} +

+

{content}

+
+ ); +} \ No newline at end of file