memoryos / src /pages /MemoryExplorer.tsx
Deeraj
initial deploy
ebab9b7
Raw
History Blame Contribute Delete
5.84 kB
/**
* MemoryExplorer — Searchable, filterable, sortable memory table
*/
import { useState } from 'react';
import { searchMemories, deleteMemory } from '../api/client';
import type { ScoredMemory } from '../api/client';
import MemoryCard from '../components/MemoryCard';
export default function MemoryExplorer() {
const [userId, setUserId] = useState('');
const [query, setQuery] = useState('');
const [typeFilter, setTypeFilter] = useState<string>('');
const [sortBy, setSortBy] = useState<'score' | 'importance' | 'recency'>('score');
const [results, setResults] = useState<ScoredMemory[]>([]);
const [loading, setLoading] = useState(false);
const [searched, setSearched] = useState(false);
const handleSearch = async () => {
if (!userId || !query) return;
setLoading(true);
try {
const data = await searchMemories(userId, query, typeFilter || undefined, 20);
let sorted = [...data];
if (sortBy === 'importance') {
sorted.sort((a, b) => b.memory.importance - a.memory.importance);
} else if (sortBy === 'recency') {
sorted.sort((a, b) => b.memory.recency_score - a.memory.recency_score);
}
setResults(sorted);
setSearched(true);
} catch {
setResults([]);
} finally {
setLoading(false);
}
};
const handleForget = async (memoryId: string) => {
try {
await deleteMemory(memoryId);
setResults((prev) => prev.filter((r) => r.memory.id !== memoryId));
} catch (e) {
console.error('Failed to forget memory:', e);
}
};
return (
<div style={{ padding: '32px', maxWidth: '900px', margin: '0 auto' }}>
<h1 style={{ fontSize: '28px', fontWeight: 700, marginBottom: '8px', color: 'var(--color-text-primary)' }}>
Memory Explorer
</h1>
<p style={{ color: 'var(--color-text-muted)', marginBottom: '32px', fontSize: '14px' }}>
Search, filter, and manage memories
</p>
{/* Search Controls */}
<div className="glass-card" style={{ padding: '20px', marginBottom: '24px' }}>
<div style={{ display: 'flex', gap: '12px', marginBottom: '12px' }}>
<input
type="text"
placeholder="User ID (UUID)"
value={userId}
onChange={(e) => setUserId(e.target.value)}
style={{
flex: 1,
padding: '10px 16px',
borderRadius: '10px',
border: '1px solid var(--color-border)',
background: 'var(--color-surface)',
color: 'var(--color-text-primary)',
fontSize: '14px',
outline: 'none',
}}
/>
</div>
<div style={{ display: 'flex', gap: '12px' }}>
<input
type="text"
placeholder="Search query..."
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
style={{
flex: 1,
padding: '10px 16px',
borderRadius: '10px',
border: '1px solid var(--color-border)',
background: 'var(--color-surface)',
color: 'var(--color-text-primary)',
fontSize: '14px',
outline: 'none',
}}
/>
<select
value={typeFilter}
onChange={(e) => setTypeFilter(e.target.value)}
style={{
padding: '10px 16px',
borderRadius: '10px',
border: '1px solid var(--color-border)',
background: 'var(--color-surface)',
color: 'var(--color-text-primary)',
fontSize: '14px',
outline: 'none',
cursor: 'pointer',
}}
>
<option value="">All Types</option>
<option value="episodic">Episodic</option>
<option value="semantic">Semantic</option>
<option value="procedural">Procedural</option>
</select>
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value as typeof sortBy)}
style={{
padding: '10px 16px',
borderRadius: '10px',
border: '1px solid var(--color-border)',
background: 'var(--color-surface)',
color: 'var(--color-text-primary)',
fontSize: '14px',
outline: 'none',
cursor: 'pointer',
}}
>
<option value="score">Sort: Score</option>
<option value="importance">Sort: Importance</option>
<option value="recency">Sort: Recency</option>
</select>
<button
onClick={handleSearch}
disabled={loading}
style={{
padding: '10px 24px',
borderRadius: '10px',
border: 'none',
background: 'var(--color-accent)',
color: '#fff',
fontSize: '14px',
fontWeight: 600,
cursor: loading ? 'not-allowed' : 'pointer',
opacity: loading ? 0.7 : 1,
transition: 'all 0.2s',
}}
>
{loading ? 'Searching...' : 'Search'}
</button>
</div>
</div>
{/* Results */}
{searched && results.length === 0 && (
<div style={{ textAlign: 'center', padding: '40px', color: 'var(--color-text-muted)' }}>
No memories found. Try a different query.
</div>
)}
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
{results.map((scored) => (
<MemoryCard key={scored.memory.id} scored={scored} onForget={handleForget} />
))}
</div>
</div>
);
}