memoryos / src /components /MemoryCard.tsx
Deeraj
initial deploy
ebab9b7
Raw
History Blame Contribute Delete
4.41 kB
/**
* MemoryCard — Expandable card showing memory content + metadata
*/
import { useState } from 'react';
import type { ScoredMemory } from '../api/client';
import TypeBadge from './TypeBadge';
import ScoreBar from './ScoreBar';
interface MemoryCardProps {
scored: ScoredMemory;
onForget?: (memoryId: string) => void;
}
export default function MemoryCard({ scored, onForget }: MemoryCardProps) {
const [expanded, setExpanded] = useState(false);
const { memory, score, cosine_similarity } = scored;
const timeAgo = formatTimeAgo(memory.created_at);
return (
<div
className="glass-card animate-fade-in-up"
style={{
padding: '16px',
cursor: 'pointer',
transition: 'all 0.3s ease',
}}
onClick={() => setExpanded(!expanded)}
>
{/* Header */}
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: '8px',
}}
>
<TypeBadge type={memory.memory_type} size="sm" />
<span style={{ fontSize: '12px', color: 'var(--color-text-muted)' }}>
{timeAgo}
</span>
</div>
{/* Content */}
<p
style={{
fontSize: '14px',
color: 'var(--color-text-primary)',
lineHeight: 1.6,
marginBottom: '12px',
overflow: 'hidden',
textOverflow: 'ellipsis',
display: '-webkit-box',
WebkitLineClamp: expanded ? 999 : 3,
WebkitBoxOrient: 'vertical',
}}
>
{memory.content}
</p>
{/* Score bars */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
<ScoreBar score={score} label="Score" color="#6366f1" />
<ScoreBar score={cosine_similarity} label="Cosine" color="#22c55e" />
<ScoreBar score={memory.importance} label="Import." color="#f59e0b" />
</div>
{/* Expanded metadata */}
{expanded && (
<div
style={{
marginTop: '12px',
padding: '12px',
borderRadius: '8px',
background: 'var(--color-surface)',
border: '1px solid var(--color-border)',
fontSize: '12px',
fontFamily: 'monospace',
}}
>
<pre
style={{
color: 'var(--color-text-secondary)',
whiteSpace: 'pre-wrap',
wordBreak: 'break-all',
}}
>
{JSON.stringify(memory.metadata, null, 2)}
</pre>
<div
style={{
marginTop: '8px',
display: 'flex',
gap: '12px',
color: 'var(--color-text-muted)',
}}
>
<span>Accesses: {memory.access_count}</span>
<span>Recency: {memory.recency_score.toFixed(3)}</span>
<span>ID: {memory.id.slice(0, 8)}…</span>
</div>
{onForget && (
<button
onClick={(e) => {
e.stopPropagation();
onForget(memory.id);
}}
style={{
marginTop: '8px',
padding: '6px 16px',
borderRadius: '8px',
border: '1px solid var(--color-danger)',
background: 'transparent',
color: 'var(--color-danger)',
fontSize: '12px',
fontWeight: 600,
cursor: 'pointer',
transition: 'all 0.2s',
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'rgba(239, 68, 68, 0.1)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'transparent';
}}
>
🗑 Forget Memory
</button>
)}
</div>
)}
</div>
);
}
function formatTimeAgo(dateStr: string): string {
const date = new Date(dateStr);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMin = Math.floor(diffMs / 60000);
if (diffMin < 1) return 'just now';
if (diffMin < 60) return `${diffMin}m ago`;
const diffHr = Math.floor(diffMin / 60);
if (diffHr < 24) return `${diffHr}h ago`;
const diffDays = Math.floor(diffHr / 24);
return `${diffDays}d ago`;
}