'use client' import type { FC } from 'react' import React, { useMemo, useState } from 'react' import useSWR from 'swr' import { useTranslation } from 'react-i18next' import { useRouter } from 'next/navigation' import { useDebounce, useDebounceFn } from 'ahooks' import { groupBy, omit } from 'lodash-es' import { PlusIcon } from '@heroicons/react/24/solid' import List from './list' import s from './style.module.css' import Loading from '@/app/components/base/loading' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' import Pagination from '@/app/components/base/pagination' import { get } from '@/service/base' import { createDocument, fetchDocuments } from '@/service/datasets' import { useDatasetDetailContext } from '@/context/dataset-detail' import { NotionPageSelectorModal } from '@/app/components/base/notion-page-selector' import type { NotionPage } from '@/models/common' import type { CreateDocumentReq } from '@/models/datasets' import { DataSourceType } from '@/models/datasets' import RetryButton from '@/app/components/base/retry-button' // Custom page count is not currently supported. const limit = 15 const FolderPlusIcon = ({ className }: React.SVGProps) => { return } const ThreeDotsIcon = ({ className }: React.SVGProps) => { return } const NotionIcon = ({ className }: React.SVGProps) => { return } const EmptyElement: FC<{ canAdd: boolean; onClick: () => void; type?: 'upload' | 'sync' }> = ({ canAdd = true, onClick, type = 'upload' }) => { const { t } = useTranslation() return
{type === 'upload' ? : }
{t('datasetDocuments.list.empty.title')}
{t(`datasetDocuments.list.empty.${type}.tip`)}
{type === 'upload' && canAdd && }
} type IDocumentsProps = { datasetId: string } export const fetcher = (url: string) => get(url, {}, {}) const Documents: FC = ({ datasetId }) => { const { t } = useTranslation() const [inputValue, setInputValue] = useState('') // the input value const [searchValue, setSearchValue] = useState('') const [currPage, setCurrPage] = React.useState(0) const router = useRouter() const { dataset } = useDatasetDetailContext() const [notionPageSelectorModalVisible, setNotionPageSelectorModalVisible] = useState(false) const [timerCanRun, setTimerCanRun] = useState(true) const isDataSourceNotion = dataset?.data_source_type === DataSourceType.NOTION const isDataSourceWeb = dataset?.data_source_type === DataSourceType.WEB const isDataSourceFile = dataset?.data_source_type === DataSourceType.FILE const embeddingAvailable = !!dataset?.embedding_available const debouncedSearchValue = useDebounce(searchValue, { wait: 500 }) const query = useMemo(() => { return { page: currPage + 1, limit, keyword: debouncedSearchValue, fetch: isDataSourceNotion ? true : '' } }, [currPage, debouncedSearchValue, isDataSourceNotion]) const { data: documentsRes, error, mutate } = useSWR( { action: 'fetchDocuments', datasetId, params: query, }, apiParams => fetchDocuments(omit(apiParams, 'action')), { refreshInterval: (isDataSourceNotion && timerCanRun) ? 2500 : 0 }, ) const documentsWithProgress = useMemo(() => { let completedNum = 0 let percent = 0 const documentsData = documentsRes?.data?.map((documentItem) => { const { indexing_status, completed_segments, total_segments } = documentItem const isEmbedded = indexing_status === 'completed' || indexing_status === 'paused' || indexing_status === 'error' if (isEmbedded) completedNum++ const completedCount = completed_segments || 0 const totalCount = total_segments || 0 if (totalCount === 0 && completedCount === 0) { percent = isEmbedded ? 100 : 0 } else { const per = Math.round(completedCount * 100 / totalCount) percent = per > 100 ? 100 : per } return { ...documentItem, percent, } }) if (completedNum === documentsRes?.data?.length) setTimerCanRun(false) return { ...documentsRes, data: documentsData, } }, [documentsRes]) const total = documentsRes?.total || 0 const routeToDocCreate = () => { if (isDataSourceNotion) { setNotionPageSelectorModalVisible(true) return } router.push(`/datasets/${datasetId}/documents/create`) } const isLoading = !documentsRes && !error const handleSaveNotionPageSelected = async (selectedPages: NotionPage[]) => { const workspacesMap = groupBy(selectedPages, 'workspace_id') const workspaces = Object.keys(workspacesMap).map((workspaceId) => { return { workspaceId, pages: workspacesMap[workspaceId], } }) const params = { data_source: { type: dataset?.data_source_type, info_list: { data_source_type: dataset?.data_source_type, notion_info_list: workspaces.map((workspace) => { return { workspace_id: workspace.workspaceId, pages: workspace.pages.map((page) => { const { page_id, page_name, page_icon, type } = page return { page_id, page_name, page_icon, type, } }), } }), }, }, indexing_technique: dataset?.indexing_technique, process_rule: { rules: {}, mode: 'automatic', }, } as CreateDocumentReq await createDocument({ datasetId, body: params, }) mutate() setTimerCanRun(true) // mutateDatasetIndexingStatus(undefined, { revalidate: true }) setNotionPageSelectorModalVisible(false) } const documentsList = isDataSourceNotion ? documentsWithProgress?.data : documentsRes?.data const { run: handleSearch } = useDebounceFn(() => { setSearchValue(inputValue) }, { wait: 500 }) const handleInputChange = (value: string) => { setInputValue(value) handleSearch() } return (

{t('datasetDocuments.list.title')}

{t('datasetDocuments.list.desc')}

handleInputChange(e.target.value)} onClear={() => handleInputChange('')} />
{embeddingAvailable && ( )}
{isLoading ? : total > 0 ? : } {/* Show Pagination only if the total is more than the limit */} {(total && total > limit) ? : null} setNotionPageSelectorModalVisible(false)} onSave={handleSaveNotionPageSelected} datasetId={dataset?.id || ''} />
) } export default Documents