124 lines
4.8 KiB
JavaScript
124 lines
4.8 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import { fetchSessionSummaries } from '../api/orchestration';
|
|
import ReactMarkdown from 'react-markdown';
|
|
|
|
export default function SummaryView({ activeSession, onBack }) {
|
|
const [summaries, setSummaries] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState(null);
|
|
const [expanded, setExpanded] = useState(null);
|
|
|
|
useEffect(() => {
|
|
if (!activeSession || activeSession.isNew) {
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
fetchSessionSummaries(activeSession.external_id)
|
|
.then(data => setSummaries(Array.isArray(data) ? data : []))
|
|
.catch(err => setError(err.message))
|
|
.finally(() => setLoading(false));
|
|
}, [activeSession]);
|
|
|
|
function formatTimestamp(ts) {
|
|
if (!ts) return '—';
|
|
return new Date(ts * 1000).toLocaleString([], {
|
|
month: 'short', day: 'numeric',
|
|
hour: '2-digit', minute: '2-digit',
|
|
});
|
|
}
|
|
|
|
return (
|
|
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, overflow: 'hidden', background: 'var(--bg-base)' }}>
|
|
|
|
{/* Header */}
|
|
<div className="panel-header" style={{ padding: '0 24px', gap: 12 }}>
|
|
<button className="btn-icon" onClick={onBack}>←</button>
|
|
<span className="text-base" style={{ fontWeight: 500 }}>Session Memory</span>
|
|
<span className="text-sm text-muted" style={{ marginLeft: 'auto' }}>
|
|
{summaries.length} summar{summaries.length !== 1 ? 'ies' : 'y'}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Session name pill */}
|
|
{activeSession && (
|
|
<div style={{ padding: '8px 24px 0' }}>
|
|
<span className="text-xs text-muted" style={{
|
|
background: 'var(--bg-elevated)',
|
|
border: '1px solid var(--border)',
|
|
borderRadius: '999px',
|
|
padding: '3px 10px',
|
|
}}>
|
|
{activeSession.name || activeSession.external_id}
|
|
</span>
|
|
</div>
|
|
)}
|
|
|
|
{/* Content */}
|
|
<div className="scroll-y flex-1" style={{ padding: '16px 24px' }}>
|
|
{loading && <p className="text-sm text-muted">Loading…</p>}
|
|
{error && <p className="text-sm" style={{ color: 'var(--error, #e05)' }}>{error}</p>}
|
|
|
|
{!loading && !activeSession && (
|
|
<p className="text-sm text-muted">No active session.</p>
|
|
)}
|
|
|
|
{!loading && activeSession && summaries.length === 0 && (
|
|
<div style={{
|
|
display: 'flex', flexDirection: 'column', alignItems: 'center',
|
|
gap: '12px', padding: '48px 0', color: 'var(--text-muted)',
|
|
}}>
|
|
<span style={{ fontSize: '28px', opacity: 0.3 }}>◈</span>
|
|
<p className="text-sm">No summaries yet for this session.</p>
|
|
<p className="text-xs text-muted" style={{ maxWidth: '280px', textAlign: 'center', lineHeight: 1.6 }}>
|
|
Summaries generate automatically once a session accumulates enough conversation.
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{summaries.map(summary => (
|
|
<div key={summary.id} style={{
|
|
background: 'var(--bg-surface)',
|
|
border: '1px solid var(--border)',
|
|
borderRadius: 'var(--radius-lg)',
|
|
marginBottom: '10px', overflow: 'hidden',
|
|
}}>
|
|
{/* Card header */}
|
|
<div
|
|
onClick={() => setExpanded(expanded === summary.id ? null : summary.id)}
|
|
style={{ display: 'flex', alignItems: 'center', gap: '10px', padding: '10px 14px', cursor: 'pointer' }}
|
|
>
|
|
<span style={{ flex: 1, fontSize: 13, color: 'var(--text-primary)' }}>
|
|
Episodes {summary.episode_range}
|
|
</span>
|
|
<span className="text-xs text-muted">{formatTimestamp(summary.created_at)}</span>
|
|
<span className="text-muted" style={{ fontSize: 11 }}>
|
|
{expanded === summary.id ? '▲' : '▼'}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Expanded content */}
|
|
{expanded === summary.id && (
|
|
<div style={{ padding: '0 14px 14px', borderTop: '1px solid var(--border)' }}>
|
|
<ReactMarkdown components={{
|
|
p: ({ children }) => (
|
|
<p style={{ margin: '8px 0', lineHeight: 1.7, fontSize: 13, color: 'var(--text-secondary)' }}>
|
|
{children}
|
|
</p>
|
|
),
|
|
}}>
|
|
{summary.content}
|
|
</ReactMarkdown>
|
|
{summary.token_count > 0 && (
|
|
<p className="text-xs text-muted" style={{ marginTop: 8 }}>
|
|
{summary.token_count.toLocaleString()} tokens covered
|
|
</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
} |