'use client' import type { FC } from 'react' import React, { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useRouter } from 'next/navigation' import { useDebounce, useDebounceFn } from 'ahooks' import { groupBy } from 'lodash-es' import { PlusIcon } from '@heroicons/react/24/solid' import { RiExternalLinkLine } from '@remixicon/react' import AutoDisabledDocument from '../common/document-status-with-action/auto-disabled-document' 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 { get } from '@/service/base' import { createDocument } 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, ProcessMode } from '@/models/datasets' import IndexFailed from '@/app/components/datasets/common/document-status-with-action/index-failed' import { useProviderContext } from '@/context/provider-context' import cn from '@/utils/classnames' import { useDocumentList, useInvalidDocumentDetailKey, useInvalidDocumentList } from '@/service/knowledge/use-document' import { useInvalid } from '@/service/use-base' import { useChildSegmentListKey, useSegmentListKey } from '@/service/knowledge/use-segment' 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 DEFAULT_LIMIT = 10 const Documents: FC = ({ datasetId }) => { const { t } = useTranslation() const { plan } = useProviderContext() const isFreePlan = plan.type === 'sandbox' const [inputValue, setInputValue] = useState('') // the input value const [searchValue, setSearchValue] = useState('') const [currPage, setCurrPage] = React.useState(0) const [limit, setLimit] = useState(DEFAULT_LIMIT) 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 { data: documentsRes, isFetching: isListLoading } = useDocumentList({ datasetId, query: { page: currPage + 1, limit, keyword: debouncedSearchValue, }, refetchInterval: (isDataSourceNotion && timerCanRun) ? 2500 : 0, }) const invalidDocumentList = useInvalidDocumentList(datasetId) useEffect(() => { if (documentsRes) { const totalPages = Math.ceil(documentsRes.total / limit) if (totalPages < currPage + 1) setCurrPage(totalPages === 0 ? 0 : totalPages - 1) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [documentsRes]) const invalidDocumentDetail = useInvalidDocumentDetailKey() const invalidChunkList = useInvalid(useSegmentListKey) const invalidChildChunkList = useInvalid(useChildSegmentListKey) const handleUpdate = useCallback(() => { invalidDocumentList() invalidDocumentDetail() setTimeout(() => { invalidChunkList() invalidChildChunkList() }, 5000) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) 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 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: ProcessMode.general, }, } as CreateDocumentReq await createDocument({ datasetId, body: params, }) invalidDocumentList() setTimerCanRun(true) // mutateDatasetIndexingStatus(undefined, { revalidate: true }) setNotionPageSelectorModalVisible(false) } const documentsList = isDataSourceNotion ? documentsWithProgress?.data : documentsRes?.data const [selectedIds, setSelectedIds] = useState([]) const { run: handleSearch } = useDebounceFn(() => { setSearchValue(inputValue) }, { wait: 500 }) const handleInputChange = (value: string) => { setInputValue(value) handleSearch() } return (

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

{t('datasetDocuments.list.desc')} {t('datasetDocuments.list.learnMore')}
handleInputChange(e.target.value)} onClear={() => handleInputChange('')} />
{!isFreePlan && } {embeddingAvailable && ( )}
{isListLoading ? : total > 0 ? : } setNotionPageSelectorModalVisible(false)} onSave={handleSaveNotionPageSelected} datasetId={dataset?.id || ''} />
) } export default Documents