'use client' import type { Tag } from '@/app/components/base/tag-management/constant' import type { DataSet } from '@/models/datasets' import { RiFileTextFill, RiMoreFill, RiRobot2Fill } from '@remixicon/react' import { useHover } from 'ahooks' import { useRouter } from 'next/navigation' import * as React from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import Confirm from '@/app/components/base/confirm' import CornerLabel from '@/app/components/base/corner-label' import CustomPopover from '@/app/components/base/popover' import TagSelector from '@/app/components/base/tag-management/selector' import Toast from '@/app/components/base/toast' import Tooltip from '@/app/components/base/tooltip' import { useSelector as useAppContextWithSelector } from '@/context/app-context' import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' import { useKnowledge } from '@/hooks/use-knowledge' import { DOC_FORM_ICON_WITH_BG, DOC_FORM_TEXT } from '@/models/datasets' import { checkIsUsedInApp, deleteDataset } from '@/service/datasets' import { useExportPipelineDSL } from '@/service/use-pipeline' import { cn } from '@/utils/classnames' import RenameDatasetModal from '../../rename-modal' import Operations from './operations' const EXTERNAL_PROVIDER = 'external' type DatasetCardProps = { dataset: DataSet onSuccess?: () => void } const DatasetCard = ({ dataset, onSuccess, }: DatasetCardProps) => { const { t } = useTranslation() const { push } = useRouter() const isCurrentWorkspaceDatasetOperator = useAppContextWithSelector(state => state.isCurrentWorkspaceDatasetOperator) const [tags, setTags] = useState(dataset.tags) const tagSelectorRef = useRef(null) const isHoveringTagSelector = useHover(tagSelectorRef) const [showRenameModal, setShowRenameModal] = useState(false) const [showConfirmDelete, setShowConfirmDelete] = useState(false) const [confirmMessage, setConfirmMessage] = useState('') const [exporting, setExporting] = useState(false) const isExternalProvider = useMemo(() => { return dataset.provider === EXTERNAL_PROVIDER }, [dataset.provider]) const isPipelineUnpublished = useMemo(() => { return dataset.runtime_mode === 'rag_pipeline' && !dataset.is_published }, [dataset.runtime_mode, dataset.is_published]) const isShowChunkingModeIcon = useMemo(() => { return dataset.doc_form && (dataset.runtime_mode !== 'rag_pipeline' || dataset.is_published) }, [dataset.doc_form, dataset.runtime_mode, dataset.is_published]) const isShowDocModeInfo = useMemo(() => { return dataset.doc_form && dataset.indexing_technique && dataset.retrieval_model_dict?.search_method && (dataset.runtime_mode !== 'rag_pipeline' || dataset.is_published) }, [dataset.doc_form, dataset.indexing_technique, dataset.retrieval_model_dict?.search_method, dataset.runtime_mode, dataset.is_published]) const chunkingModeIcon = dataset.doc_form ? DOC_FORM_ICON_WITH_BG[dataset.doc_form] : React.Fragment const Icon = isExternalProvider ? DOC_FORM_ICON_WITH_BG.external : chunkingModeIcon const iconInfo = dataset.icon_info || { icon: '๐Ÿ“™', icon_type: 'emoji', icon_background: '#FFF4ED', icon_url: '', } const { formatIndexingTechniqueAndMethod } = useKnowledge() const documentCount = useMemo(() => { const availableDocCount = dataset.total_available_documents ?? 0 if (availableDocCount === dataset.document_count) return `${dataset.document_count}` if (availableDocCount < dataset.document_count) return `${availableDocCount} / ${dataset.document_count}` }, [dataset.document_count, dataset.total_available_documents]) const documentCountTooltip = useMemo(() => { const availableDocCount = dataset.total_available_documents ?? 0 if (availableDocCount === dataset.document_count) return t('dataset.docAllEnabled', { count: availableDocCount }) if (availableDocCount < dataset.document_count) return t('dataset.partialEnabled', { count: dataset.document_count, num: availableDocCount }) }, [t, dataset.document_count, dataset.total_available_documents]) const { formatTimeFromNow } = useFormatTimeFromNow() const editTimeText = useMemo(() => { return `${t('datasetDocuments.segment.editedAt')} ${formatTimeFromNow(dataset.updated_at * 1000)}` }, [t, dataset.updated_at, formatTimeFromNow]) const openRenameModal = useCallback(() => { setShowRenameModal(true) }, []) const { mutateAsync: exportPipelineConfig } = useExportPipelineDSL() const handleExportPipeline = useCallback(async (include = false) => { const { pipeline_id, name } = dataset if (!pipeline_id) return if (exporting) return try { setExporting(true) const { data } = await exportPipelineConfig({ pipelineId: pipeline_id, include, }) const a = document.createElement('a') const file = new Blob([data], { type: 'application/yaml' }) const url = URL.createObjectURL(file) a.href = url a.download = `${name}.pipeline` a.click() URL.revokeObjectURL(url) } catch { Toast.notify({ type: 'error', message: t('app.exportFailed') }) } finally { setExporting(false) } }, [dataset, exportPipelineConfig, exporting, t]) const detectIsUsedByApp = useCallback(async () => { try { const { is_using: isUsedByApp } = await checkIsUsedInApp(dataset.id) setConfirmMessage(isUsedByApp ? t('dataset.datasetUsedByApp')! : t('dataset.deleteDatasetConfirmContent')!) setShowConfirmDelete(true) } catch (e: any) { const res = await e.json() Toast.notify({ type: 'error', message: res?.message || 'Unknown error' }) } }, [dataset.id, t]) const onConfirmDelete = useCallback(async () => { try { await deleteDataset(dataset.id) Toast.notify({ type: 'success', message: t('dataset.datasetDeleted') }) if (onSuccess) onSuccess() } finally { setShowConfirmDelete(false) } }, [dataset.id, onSuccess, t]) useEffect(() => { setTags(dataset.tags) }, [dataset]) return ( <>
{ e.preventDefault() if (isExternalProvider) push(`/datasets/${dataset.id}/hitTesting`) else if (isPipelineUnpublished) push(`/datasets/${dataset.id}/pipeline`) else push(`/datasets/${dataset.id}/documents`) }} > {!dataset.embedding_available && ( )} {dataset.embedding_available && dataset.runtime_mode === 'rag_pipeline' && ( )}
{(isShowChunkingModeIcon || isExternalProvider) && (
)}
{dataset.name}
{dataset.author_name}
ยท
{editTimeText}
{isExternalProvider && {t('dataset.externalKnowledgeBase')}} {!isExternalProvider && isShowDocModeInfo && ( <> {dataset.doc_form && ( {t(`dataset.chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}` as any) as string} )} {dataset.indexing_technique && ( {formatIndexingTechniqueAndMethod(dataset.indexing_technique, dataset.retrieval_model_dict?.search_method) as any} )} {dataset.is_multimodal && ( {t('dataset.multimodal')} )} )}
{dataset.description}
{ e.stopPropagation() e.preventDefault() }} >
0 && 'visible', )} > tag.id)} selectedTags={tags} onCacheUpdate={setTags} onChange={onSuccess} />
{/* Tag Mask */}
{documentCount}
{!isExternalProvider && (
{dataset.app_count}
)} / {`${t('dataset.updated')} ${formatTimeFromNow(dataset.updated_at * 1000)}`}
)} className="z-20 min-w-[186px]" popupClassName="rounded-xl bg-none shadow-none ring-0 min-w-[186px]" position="br" trigger="click" btnElement={(
)} btnClassName={open => cn( 'size-9 cursor-pointer justify-center rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0 shadow-lg shadow-shadow-shadow-5 ring-[2px] ring-inset ring-components-actionbar-bg hover:border-components-actionbar-border', open ? 'border-components-actionbar-border bg-state-base-hover' : '', )} />
{showRenameModal && ( setShowRenameModal(false)} onSuccess={onSuccess} /> )} {showConfirmDelete && ( setShowConfirmDelete(false)} /> )} ) } export default DatasetCard