import React, { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; import userService from '../api/services/userService'; import ArrowLeft from '../assets/arrow-left.svg'; import caretSort from '../assets/caret-sort.svg'; import NoFilesDarkIcon from '../assets/no-files-dark.svg'; import NoFilesIcon from '../assets/no-files.svg'; import SyncIcon from '../assets/sync.svg'; import Trash from '../assets/trash.svg'; import Pagination from '../components/DocumentPagination'; import DropdownMenu from '../components/DropdownMenu'; import Input from '../components/Input'; import SkeletonLoader from '../components/SkeletonLoader'; import Spinner from '../components/Spinner'; import { useDarkTheme } from '../hooks'; import AddChunkModal from '../modals/AddChunkModal'; import ConfirmationModal from '../modals/ConfirmationModal'; import { ActiveState, Doc, DocumentsProps } from '../models/misc'; import { getDocs, getDocsWithPagination } from '../preferences/preferenceApi'; import { setPaginatedDocuments, setSourceDocs, } from '../preferences/preferenceSlice'; import Upload from '../upload/Upload'; import { formatDate } from '../utils/dateTimeUtils'; import { ChunkType } from './types'; const formatTokens = (tokens: number): string => { const roundToTwoDecimals = (num: number): string => { return (Math.round((num + Number.EPSILON) * 100) / 100).toString(); }; if (tokens >= 1_000_000_000) { return roundToTwoDecimals(tokens / 1_000_000_000) + 'b'; } else if (tokens >= 1_000_000) { return roundToTwoDecimals(tokens / 1_000_000) + 'm'; } else if (tokens >= 1_000) { return roundToTwoDecimals(tokens / 1_000) + 'k'; } else { return tokens.toString(); } }; export default function Documents({ paginatedDocuments, handleDeleteDocument, }: DocumentsProps) { const { t } = useTranslation(); const dispatch = useDispatch(); const [searchTerm, setSearchTerm] = useState(''); const [modalState, setModalState] = useState('INACTIVE'); const [isOnboarding, setIsOnboarding] = useState(false); const [loading, setLoading] = useState(false); const [sortField, setSortField] = useState<'date' | 'tokens'>('date'); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); // Pagination const [currentPage, setCurrentPage] = useState(1); const [rowsPerPage, setRowsPerPage] = useState(10); const [totalPages, setTotalPages] = useState(1); const currentDocuments = paginatedDocuments ?? []; const syncOptions = [ { label: t('settings.documents.syncFrequency.never'), value: 'never' }, { label: t('settings.documents.syncFrequency.daily'), value: 'daily' }, { label: t('settings.documents.syncFrequency.weekly'), value: 'weekly' }, { label: t('settings.documents.syncFrequency.monthly'), value: 'monthly' }, ]; const [showDocumentChunks, setShowDocumentChunks] = useState(); const refreshDocs = useCallback( ( field: 'date' | 'tokens' | undefined, pageNumber?: number, rows?: number, ) => { const page = pageNumber ?? currentPage; const rowsPerPg = rows ?? rowsPerPage; // If field is undefined, (Pagination or Search) use the current sortField const newSortField = field ?? sortField; // If field is undefined, (Pagination or Search) use the current sortOrder const newSortOrder = field === sortField ? sortOrder === 'asc' ? 'desc' : 'asc' : sortOrder; // If field is defined, update the sortField and sortOrder if (field) { setSortField(newSortField); setSortOrder(newSortOrder); } setLoading(true); getDocsWithPagination( newSortField, newSortOrder, page, rowsPerPg, searchTerm, ) .then((data) => { dispatch(setPaginatedDocuments(data ? data.docs : [])); setTotalPages(data ? data.totalPages : 0); }) .catch((error) => console.error(error)) .finally(() => { setLoading(false); }); }, [currentPage, rowsPerPage, sortField, sortOrder, searchTerm], ); const handleManageSync = (doc: Doc, sync_frequency: string) => { setLoading(true); userService .manageSync({ source_id: doc.id, sync_frequency }) .then(() => { return getDocs(); }) .then((data) => { dispatch(setSourceDocs(data)); return getDocsWithPagination( sortField, sortOrder, currentPage, rowsPerPage, ); }) .then((paginatedData) => { dispatch( setPaginatedDocuments(paginatedData ? paginatedData.docs : []), ); setTotalPages(paginatedData ? paginatedData.totalPages : 0); }) .catch((error) => console.error('Error in handleManageSync:', error)) .finally(() => { setLoading(false); }); }; const [documentToDelete, setDocumentToDelete] = useState<{ index: number; document: Doc; } | null>(null); const [deleteModalState, setDeleteModalState] = useState('INACTIVE'); const handleDeleteConfirmation = (index: number, document: Doc) => { setDocumentToDelete({ index, document }); setDeleteModalState('ACTIVE'); }; const handleConfirmedDelete = () => { if (documentToDelete) { handleDeleteDocument(documentToDelete.index, documentToDelete.document); setDeleteModalState('INACTIVE'); setDocumentToDelete(null); } }; useEffect(() => { refreshDocs(undefined, 1, rowsPerPage); }, [searchTerm]); return showDocumentChunks ? ( { setShowDocumentChunks(undefined); }} /> ) : (

{t('settings.documents.title')}

{ setSearchTerm(e.target.value); setCurrentPage(1); }} borderVariant="thin" />
{loading ? ( ) : (
{' '} {/* Removed overflow-auto */}
{!currentDocuments?.length ? ( ) : ( currentDocuments.map((document, index) => ( setShowDocumentChunks(document)} > )) )}
{t('settings.documents.name')}
{t('settings.documents.date')} refreshDocs('date')} src={caretSort} alt="sort" />
{t('settings.documents.tokenUsage')} {t('settings.documents.tokenUsage')} refreshDocs('tokens')} src={caretSort} alt="sort" />
{t('settings.documents.actions')}
{t('settings.documents.noData')}
{document.name} {document.date ? formatDate(document.date) : ''} {document.tokens ? formatTokens(+document.tokens) : ''}
{!document.syncFrequency && (
)} {document.syncFrequency && ( { handleManageSync(document, value); }} defaultValue={document.syncFrequency} icon={SyncIcon} /> )}
)}
{ setCurrentPage(page); refreshDocs(undefined, page, rowsPerPage); }} onRowsPerPageChange={(rows) => { setRowsPerPage(rows); setCurrentPage(1); refreshDocs(undefined, 1, rows); }} />
{modalState === 'ACTIVE' && ( setModalState('INACTIVE')} onSuccessfulUpload={() => refreshDocs(undefined, currentPage, rowsPerPage) } /> )} {deleteModalState === 'ACTIVE' && documentToDelete && ( { setDeleteModalState('INACTIVE'); setDocumentToDelete(null); }} submitLabel={t('convTile.delete')} /> )}
); } function DocumentChunks({ document, handleGoBack, }: { document: Doc; handleGoBack: () => void; }) { const { t } = useTranslation(); const [isDarkTheme] = useDarkTheme(); const [paginatedChunks, setPaginatedChunks] = useState([]); const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(5); const [totalChunks, setTotalChunks] = useState(0); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(''); const [deleteModalState, setDeleteModalState] = useState<{ state: ActiveState; chunkId: string | null; }>({ state: 'INACTIVE', chunkId: null }); const [addModalState, setAddModalState] = useState('INACTIVE'); const fetchChunks = () => { setLoading(true); try { userService .getDocumentChunks(document.id ?? '', page, perPage) .then((response) => { if (!response.ok) { setLoading(false); setPaginatedChunks([]); throw new Error('Failed to fetch chunks data'); } return response.json(); }) .then((data) => { setPage(data.page); setPerPage(data.per_page); setTotalChunks(data.total); setPaginatedChunks(data.chunks); setLoading(false); }); } catch (e) { console.log(e); } }; const handleAddChunk = (title: string, text: string) => { try { userService .addChunk({ id: document.id ?? '', text: text, metadata: { title: title, }, }) .then((response) => { if (!response.ok) { throw new Error('Failed to add chunk'); } fetchChunks(); }); } catch (e) { console.log(e); } }; const handleDeleteChunk = (chunkId: string) => { try { userService.deleteChunk(document.id ?? '', chunkId).then((response) => { if (!response.ok) { throw new Error('Failed to delete chunk'); } setDeleteModalState({ state: 'INACTIVE', chunkId: null }); fetchChunks(); }); } catch (e) { console.log(e); } }; React.useEffect(() => { fetchChunks(); }, [page, perPage]); return (

Back to all documents

{`${totalChunks} Chunks`}

{ setSearchTerm(e.target.value); }} borderVariant="thin" />
{loading ? (
) : (
{paginatedChunks.filter((chunk) => chunk.metadata?.title .toLowerCase() .includes(searchTerm.toLowerCase()), ).length === 0 ? (
No tools found No chunks found
) : ( paginatedChunks .filter((chunk) => chunk.metadata?.title .toLowerCase() .includes(searchTerm.toLowerCase()), ) .map((chunk, index) => (

{chunk.metadata?.title}

{chunk.text}

)) )}
)} {!loading && paginatedChunks.filter((chunk) => chunk.metadata?.title .toLowerCase() .includes(searchTerm.toLowerCase()), ).length !== 0 && (
{ setPage(page); }} onRowsPerPageChange={(rows) => { setPerPage(rows); setPage(1); }} />
)} setDeleteModalState((prev) => ({ ...prev, state })) } handleSubmit={() => handleDeleteChunk(deleteModalState.chunkId ?? '')} submitLabel="Delete" />
); }