// Primus IQ — Library Screen
const LIB_FILES = [
{ name: 'Public Policy Whitepaper 2026.pdf', ext: 'pdf', size: '6.2 MB', modified: '12 May 2026', conf: 'public', practice: 'Public Policy Realization' },
{ name: 'GCC Fintech Sector Map.png', ext: 'png', size: '2.1 MB', modified: '10 May 2026', conf: 'internal', practice: 'Sector Potential Realization' },
{ name: 'Sector Outlook · KSA.pptx', ext: 'pptx', size: '14.8 MB', modified: '08 May 2026', conf: 'internal', practice: 'Sector Potential Realization' },
{ name: 'TCFD Compliance Matrix.xlsx', ext: 'xlsx', size: '3.4 MB', modified: '04 May 2026', conf: 'public', practice: 'Economic Potential Realization' },
{ name: 'Renewables Capex 2026.pdf', ext: 'pdf', size: '4.8 MB', modified: '30 Apr 2026', conf: 'public', practice: 'Economic Potential Realization' },
{ name: 'CFO interview transcript.docx', ext: 'docx', size: '180 KB', modified: '22 Apr 2026', conf: 'client', practice: 'Sector Potential Realization' },
{ name: 'Cost build · Phase 1.xlsx', ext: 'xlsx', size: '2.4 MB', modified: '18 Apr 2026', conf: 'client', practice: 'Transaction Realization' },
];
const adaptLibraryFile = (f) => {
const ext = (f.file_name || '').split('.').pop().toLowerCase() || 'doc';
const bytes = f.file_size || 0;
const size = bytes < 1024 ? `${bytes} B` : bytes < 1048576 ? `${(bytes / 1024).toFixed(1)} KB` : `${(bytes / 1048576).toFixed(1)} MB`;
return {
id: f.id,
name: f.file_name || f.name || '',
ext,
size: size || '',
modified: f.uploaded_at ? new Date(f.uploaded_at).toLocaleDateString('en-IN', { day: 'numeric', month: 'short', year: 'numeric' }) : '—',
conf: f.confidentiality || 'internal',
practice: (f.tags && f.tags.length > 0) ? f.tags[0] : '',
status: f.status || 'uploading',
};
};
const LIBRARY_PAGE_SIZE = 50; // rows rendered per "page" — keeps 1000+ files from all mounting at once
const ScreenLibrary = ({ openAddSources, libraryFiles, onDeleteFile, onDownloadFile, onEditFile, onViewFile }) => {
const [filter, setFilter] = React.useState('all');
const [q, setQ] = React.useState('');
const [visible, setVisible] = React.useState(LIBRARY_PAGE_SIZE);
const sourceFiles = (libraryFiles && libraryFiles.length > 0) ? libraryFiles.map(adaptLibraryFile) : [];
const filtered = sourceFiles.filter(f =>
(filter === 'all'
|| (filter === 'images' && (f.ext === 'png' || f.ext === 'jpg'))
|| (filter === 'docs' && (f.ext === 'pdf' || f.ext === 'docx'))
|| (filter === 'sheets' && f.ext === 'xlsx')
|| (filter === 'decks' && f.ext === 'pptx')
) && (!q || f.name.toLowerCase().includes(q.toLowerCase()))
);
// Render only a window of rows; reset to the first page whenever the filter/search changes.
React.useEffect(() => { setVisible(LIBRARY_PAGE_SIZE); }, [filter, q]);
const shown = filtered.slice(0, visible);
const hasMore = filtered.length > visible;
return (
Library
Firm-wide knowledge base — searchable across every chat.
{[
{ id: 'all', l: 'All', n: sourceFiles.length },
{ id: 'docs', l: 'Docs', n: sourceFiles.filter(f => f.ext === 'pdf' || f.ext === 'docx').length },
{ id: 'decks', l: 'Decks', n: sourceFiles.filter(f => f.ext === 'pptx').length },
{ id: 'sheets', l: 'Sheets', n: sourceFiles.filter(f => f.ext === 'xlsx').length },
{ id: 'images', l: 'Images', n: sourceFiles.filter(f => f.ext === 'png' || f.ext === 'jpg').length },
].map(t => (
))}
{sourceFiles.length === 0 && !q && (
No files in the library yet.{' '}
)}
{sourceFiles.length > 0 && (
{/* Column headers - sticky so they stay aligned with rows under the scrollbar */}
Name
Confidentiality
Status
Modified
{filtered.length === 0 && (
No files match your filter.
)}
{shown.map((f, i) => (
onDownloadFile(f.id) : null}
onDelete={onDeleteFile ? () => onDeleteFile(f.id) : null}
onEdit={onEditFile ? () => onEditFile(f) : null}
onView={onViewFile ? () => onViewFile(f) : null}
/>
))}
{hasMore && (
)}
)}
);
};
const LibraryFileRow = ({ f, isLast, onDownload, onDelete, onEdit, onView }) => {
const [menuOpen, setMenuOpen] = React.useState(false);
const menuRef = React.useRef(null);
React.useEffect(() => {
if (!menuOpen) return;
const handler = (e) => { if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false); };
document.addEventListener('mousedown', handler);
return () => document.removeEventListener('mousedown', handler);
}, [menuOpen]);
return (
onView && onView()} style={{
display: 'grid', gridTemplateColumns: '1fr 180px 120px 160px 40px',
padding: '12px 18px', alignItems: 'center',
borderBottom: isLast ? 'none' : '1px solid var(--line)',
cursor: onView ? 'pointer' : 'default', transition: 'background 0.12s',
}}
onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-soft)'}
onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
>
{CONF_LABELS[f.conf] || f.conf}
{
f.status === 'graph_complete' ? 'Published to AI'
: f.status === 'dirty_ready' ? 'AI Search Ready'
: f.status === 'complete' ? 'File Saved'
: f.status === 'failed' ? 'Save Failed'
: f.status
}
{f.modified}
{menuOpen && (
{onDownload && (
)}
{onEdit && (
)}
{onDelete && (
)}
)}
);
};
Object.assign(window, {
LIB_FILES,
adaptLibraryFile,
ScreenLibrary,
LibraryFileRow,
});