import React from 'react'; import { Header, Segment, Table, Button, Icon, Label, Message, Modal, Pagination, Dropdown, Input } from 'semantic-ui-react'; import superagent from 'superagent'; import ExperimentalWrapper from './ExperimentalWrapper.jsx'; export default class KBDocuments extends React.Component { constructor(props) { super(props); this.state = { documents: [], pagination: { limit: 20, offset: 0, total: 0 }, loading: true, error: null, selectedDocument: null, documentDetails: null, detailsModalOpen: false, deleteModalOpen: false, documentToDelete: null, filterSourceType: '', searchQuery: '' }; } componentDidMount() { this.loadDocuments(); } async loadDocuments() { const { pagination, filterSourceType } = this.state; this.setState({ loading: true, error: null }); try { let url = `/api/admin/kb/documents?limit=${pagination.limit}&offset=${pagination.offset}`; if (filterSourceType) { url += `&sourceType=${filterSourceType}`; } const res = await superagent.get(url); this.setState({ documents: res.body.documents || [], pagination: res.body.pagination || pagination, loading: false }); } catch (error) { console.error('Failed to load documents:', error); this.setState({ error: error.response?.body?.error || error.message, loading: false }); } } async loadDocumentDetails(documentId) { this.setState({ detailsModalOpen: true, documentDetails: null }); try { const res = await superagent.get(`/api/admin/kb/document/${documentId}`); this.setState({ documentDetails: res.body }); } catch (error) { console.error('Failed to load document details:', error); this.setState({ error: error.response?.body?.error || error.message, detailsModalOpen: false }); } } async deleteDocument() { const { documentToDelete } = this.state; if (!documentToDelete) return; try { await superagent.delete(`/api/admin/kb/document/${documentToDelete.source_type}/${documentToDelete.source_id}`); this.setState({ deleteModalOpen: false, documentToDelete: null }); this.loadDocuments(); } catch (error) { console.error('Failed to delete document:', error); this.setState({ error: error.response?.body?.error || error.message, deleteModalOpen: false }); } } handlePageChange(e, { activePage }) { const { pagination } = this.state; this.setState({ pagination: { ...pagination, offset: (activePage - 1) * pagination.limit } }, () => this.loadDocuments()); } handleFilterChange(e, { value }) { this.setState({ filterSourceType: value, pagination: { ...this.state.pagination, offset: 0 } }, () => this.loadDocuments()); } handlePageSizeChange(e, { value }) { this.setState({ pagination: { ...this.state.pagination, limit: value, offset: 0 } }, () => this.loadDocuments()); } getSourceColor(sourceType) { const colors = { file: 'blue', github: 'black', slack: 'purple', google_drive: 'green', notion: 'orange', discord: 'violet' }; return colors[sourceType] || 'grey'; } formatDate(dateString) { if (!dateString) return '-'; const date = new Date(dateString); return date.toLocaleDateString() + ' ' + date.toLocaleTimeString(); } renderDocumentDetailsModal() { const { detailsModalOpen, documentDetails } = this.state; return ( this.setState({ detailsModalOpen: false, documentDetails: null })} size="large" > Document Details {!documentDetails ? (
Loading...
) : ( <>
Metadata
ID {documentDetails.document.id} Title {documentDetails.document.title || '-'} Source Type Source ID {documentDetails.document.source_id} Source URL {documentDetails.document.source_url ? ( {documentDetails.document.source_url} ) : '-'} Visibility {documentDetails.document.visibility || 'internal'} Indexed At {this.formatDate(documentDetails.document.indexed_at)}
Chunks ({documentDetails.chunks?.length || 0})
{documentDetails.chunks?.map((chunk, index) => (

{chunk.content}

))}
)}
); } renderDeleteModal() { const { deleteModalOpen, documentToDelete } = this.state; return ( this.setState({ deleteModalOpen: false, documentToDelete: null })} size="small" > Delete Document

Are you sure you want to delete this document?

{documentToDelete && ( {documentToDelete.title || 'Untitled'}
{' '} {documentToDelete.source_id}
)} This will permanently delete the document and all its indexed chunks.
); } render() { const { documents, pagination, loading, error, filterSourceType } = this.state; const sourceTypeOptions = [ { key: 'all', text: 'All Sources', value: '' }, { key: 'file', text: 'File', value: 'file' }, { key: 'github', text: 'GitHub', value: 'github' }, { key: 'slack', text: 'Slack', value: 'slack' }, { key: 'google_drive', text: 'Google Drive', value: 'google_drive' }, { key: 'notion', text: 'Notion', value: 'notion' }, { key: 'discord', text: 'Discord', value: 'discord' } ]; const pageSizeOptions = [ { key: '20', text: '20 per page', value: 20 }, { key: '50', text: '50 per page', value: 50 }, { key: '100', text: '100 per page', value: 100 } ]; const totalPages = Math.ceil((pagination.total || documents.length) / pagination.limit); const currentPage = Math.floor(pagination.offset / pagination.limit) + 1; return (
Knowledge Base Documents View and manage indexed documents
{pagination.total > 0 && ( {pagination.total.toLocaleString()} documents )}
{error && ( Error

{error}

)} Title Source Source ID Indexed At Actions {documents.length === 0 ? (

No documents found

) : ( documents.map(doc => ( { e.preventDefault(); this.loadDocumentDetails(doc.id); }} > {doc.title || 'Untitled'} {doc.source_id.length > 30 ? doc.source_id.substring(0, 30) + '...' : doc.source_id} {this.formatDate(doc.indexed_at)}
{totalPages > 0 && (
Showing {pagination.offset + 1} - {Math.min(pagination.offset + pagination.limit, pagination.total)} of {pagination.total} {totalPages > 1 && ( )}
)}
{this.renderDocumentDetailsModal()} {this.renderDeleteModal()}
); } }