/* eslint-disable no-mixed-operators */ 'use client' import type { FC } from 'react' import React, { useEffect, useState } from 'react' import { ArrowDownIcon, TrashIcon } from '@heroicons/react/24/outline' import { ExclamationCircleIcon } from '@heroicons/react/24/solid' import dayjs from 'dayjs' import { pick } from 'lodash-es' import { useContext } from 'use-context-selector' import { useRouter } from 'next/navigation' import { useTranslation } from 'react-i18next' import cn from 'classnames' import s from './style.module.css' import Switch from '@/app/components/base/switch' import Divider from '@/app/components/base/divider' import Popover from '@/app/components/base/popover' import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' import Tooltip from '@/app/components/base/tooltip' import { ToastContext } from '@/app/components/base/toast' import type { IndicatorProps } from '@/app/components/header/indicator' import Indicator from '@/app/components/header/indicator' import { asyncRunSafe } from '@/utils' import { formatNumber } from '@/utils/format' import { archiveDocument, deleteDocument, disableDocument, enableDocument } from '@/service/datasets' import type { DocumentDisplayStatus, DocumentListResponse } from '@/models/datasets' import type { CommonResponse } from '@/models/common' export const SettingsIcon: FC<{ className?: string }> = ({ className }) => { return } export const FilePlusIcon: FC<{ className?: string }> = ({ className }) => { return } export const ArchiveIcon: FC<{ className?: string }> = ({ className }) => { return } export const useIndexStatus = () => { const { t } = useTranslation() return { queuing: { color: 'orange', text: t('datasetDocuments.list.status.queuing') }, // waiting indexing: { color: 'blue', text: t('datasetDocuments.list.status.indexing') }, // indexing splitting parsing cleaning paused: { color: 'orange', text: t('datasetDocuments.list.status.parsed') }, // paused error: { color: 'red', text: t('datasetDocuments.list.status.error') }, // error available: { color: 'green', text: t('datasetDocuments.list.status.available') }, // completed,archived = false,enabled = true enabled: { color: 'green', text: t('datasetDocuments.list.status.enabled') }, // completed,archived = false,enabled = true disabled: { color: 'gray', text: t('datasetDocuments.list.status.disabled') }, // completed,archived = false,enabled = false archived: { color: 'gray', text: t('datasetDocuments.list.status.archived') }, // completed,archived = true } } // status item for list export const StatusItem: FC<{ status: DocumentDisplayStatus reverse?: boolean scene?: 'list' | 'detail' textCls?: string }> = ({ status, reverse = false, scene = 'list', textCls = '' }) => { const DOC_INDEX_STATUS_MAP = useIndexStatus() const localStatus = status.toLowerCase() as keyof typeof DOC_INDEX_STATUS_MAP return
{DOC_INDEX_STATUS_MAP[localStatus]?.text}
} type OperationName = 'delete' | 'archive' | 'enable' | 'disable' // operation action for list and detail export const OperationAction: FC<{ detail: { enabled: boolean archived: boolean id: string } datasetId: string onUpdate: () => void scene?: 'list' | 'detail' className?: string }> = ({ datasetId, detail, onUpdate, scene = 'list', className = '' }) => { const { id, enabled = false, archived = false } = detail || {} const [showModal, setShowModal] = useState(false) const { notify } = useContext(ToastContext) const { t } = useTranslation() const router = useRouter() const isListScene = scene === 'list' const onOperate = async (operationName: OperationName) => { let opApi = deleteDocument switch (operationName) { case 'archive': opApi = archiveDocument break case 'enable': opApi = enableDocument break case 'disable': opApi = disableDocument break default: opApi = deleteDocument break } const [e] = await asyncRunSafe(opApi({ datasetId, documentId: id }) as Promise) if (!e) notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) else notify({ type: 'error', message: t('common.actionMsg.modificationFailed') }) onUpdate() } return
e.stopPropagation()} > {isListScene && <> {archived ?
{ }} disabled={true} size='md' />
: onOperate(v ? 'enable' : 'disable')} size='md' /> } } {!isListScene && <>
{!archived && enabled ? t('datasetDocuments.list.index.enable') : t('datasetDocuments.list.index.disable')}
!archived && onOperate(v ? 'enable' : 'disable')} disabled={archived} size='md' />
{!archived && enabled ? t('datasetDocuments.list.index.enableTip') : t('datasetDocuments.list.index.disableTip')}
} {!archived && ( <>
router.push(`/datasets/${datasetId}/documents/${detail.id}/settings`)}> {t('datasetDocuments.list.action.settings')}
{/*
router.push(`/datasets/${datasetId}/documents/create`)}> {t('datasetDocuments.list.action.uploadFile')}
*/} )} {!archived &&
onOperate('archive')}> {t('datasetDocuments.list.action.archive')}
}
setShowModal(true)}> {t('datasetDocuments.list.action.delete')}
} trigger='click' position='br' btnElement={
} btnClassName={open => cn(isListScene ? s.actionIconWrapperList : s.actionIconWrapperDetail, open ? '!bg-gray-100 !shadow-none' : '!bg-transparent')} className={`!w-[200px] h-fit !z-20 ${className}`} /> {showModal && setShowModal(false)} className={s.delModal} closable>
{t('datasetDocuments.list.delete.title')}
{t('datasetDocuments.list.delete.content')}
}
} export const renderTdValue = (value: string | number | null, isEmptyStyle = false) => { return (
{value ?? '-'}
) } const renderCount = (count: number | undefined) => { if (!count) return renderTdValue(0, true) if (count < 1000) return count return `${formatNumber((count / 1000).toFixed(1))}k` } type IDocumentListProps = { documents: DocumentListResponse['data'] datasetId: string onUpdate: () => void } /** * Document list component including basic information */ const DocumentList: FC = ({ documents = [], datasetId, onUpdate }) => { const { t } = useTranslation() const router = useRouter() const [localDocs, setLocalDocs] = useState(documents) const [enableSort, setEnableSort] = useState(false) useEffect(() => { setLocalDocs(documents) }, [documents]) const onClickSort = () => { setEnableSort(!enableSort) if (!enableSort) { const sortedDocs = [...localDocs].sort((a, b) => dayjs(a.created_at).isBefore(dayjs(b.created_at)) ? -1 : 1) setLocalDocs(sortedDocs) } else { setLocalDocs(documents) } } return ( <> {localDocs.map((doc) => { const suffix = doc.name.split('.').pop() || 'txt' return { router.push(`datasets/${datasetId}/documents/${doc.id}`) }}> })}
# {t('datasetDocuments.list.table.header.fileName')} {t('datasetDocuments.list.table.header.words')} {t('datasetDocuments.list.table.header.hitCount')}
{t('datasetDocuments.list.table.header.uploadTime')}
{t('datasetDocuments.list.table.header.status')} {t('datasetDocuments.list.table.header.action')}
{doc.position}
{doc?.name?.replace(/\.[^/.]+$/, '')}.{suffix}
{renderCount(doc.word_count)} {renderCount(doc.hit_count)} {dayjs.unix(doc.created_at).format(t('datasetHitTesting.dateTimeFormat') as string)}
) } export default DocumentList