mirror of
https://github.com/langgenius/dify.git
synced 2025-12-03 22:36:52 +00:00
feat: Refactor dataset info components and add export pipeline functionality
This commit is contained in:
parent
a0942399cd
commit
8fc15c83d0
@ -5,7 +5,6 @@ import { usePathname } from 'next/navigation'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { RemixiconComponentType } from '@remixicon/react'
|
||||
import {
|
||||
RiAttachmentLine,
|
||||
RiEqualizer2Fill,
|
||||
RiEqualizer2Line,
|
||||
RiFileTextFill,
|
||||
@ -14,7 +13,6 @@ import {
|
||||
RiFocus2Line,
|
||||
} from '@remixicon/react'
|
||||
import { RiInformation2Line } from '@remixicon/react'
|
||||
import classNames from '@/utils/classnames'
|
||||
import type { RelatedAppResponse } from '@/models/datasets'
|
||||
import AppSideBar from '@/app/components/app-sidebar'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
@ -22,7 +20,6 @@ import DatasetDetailContext from '@/context/dataset-detail'
|
||||
import { DataSourceType } from '@/models/datasets'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import { useStore } from '@/app/components/app/store'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import LinkedAppsPanel from '@/app/components/base/linked-apps-panel'
|
||||
@ -49,7 +46,6 @@ const ExtraInfo = React.memo(({
|
||||
expand,
|
||||
}: IExtraInfoProps) => {
|
||||
const { t } = useTranslation()
|
||||
const docLink = useDocLink()
|
||||
|
||||
const hasRelatedApps = relatedApps?.data && relatedApps?.data?.length > 0
|
||||
const relatedAppsTotal = relatedApps?.data?.length || 0
|
||||
@ -57,7 +53,7 @@ const ExtraInfo = React.memo(({
|
||||
return (
|
||||
<>
|
||||
{!expand && (
|
||||
<div className='flex items-center gap-x-0.5'>
|
||||
<div className='flex items-center gap-x-0.5 p-2 pb-3'>
|
||||
<div className='flex grow flex-col px-2 pb-1.5 pt-1'>
|
||||
<div className='system-md-semibold-uppercase text-text-secondary'>
|
||||
{documentCount ?? '--'}
|
||||
@ -74,7 +70,7 @@ const ExtraInfo = React.memo(({
|
||||
{relatedAppsTotal ?? '--'}
|
||||
</div>
|
||||
<Tooltip
|
||||
position='bottom-start'
|
||||
position='top-start'
|
||||
noDecoration
|
||||
needsDelay
|
||||
popupContent={
|
||||
@ -94,13 +90,6 @@ const ExtraInfo = React.memo(({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{expand && (
|
||||
<div className={classNames('uppercase text-xs text-text-tertiary font-medium pb-2 pt-4', 'flex items-center justify-center !px-0 gap-1')}>
|
||||
{relatedAppsTotal ?? '--'}
|
||||
<RiAttachmentLine className='size-4 text-text-secondary' />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
@ -1,94 +0,0 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AppIcon from '../base/app-icon'
|
||||
import Effect from '../base/effect'
|
||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import { DOC_FORM_ICON_WITH_BG, DOC_FORM_TEXT } from '@/models/datasets'
|
||||
import { useKnowledge } from '@/hooks/use-knowledge'
|
||||
import Badge from '../base/badge'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
expand: boolean
|
||||
extraInfo?: React.ReactNode
|
||||
}
|
||||
|
||||
const DatasetInfo: FC<Props> = ({
|
||||
expand,
|
||||
extraInfo,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const dataset = useDatasetDetailContextWithSelector(state => state.dataset) as DataSet
|
||||
const iconInfo = dataset.icon_info || {
|
||||
icon: '📙',
|
||||
icon_type: 'emoji',
|
||||
icon_background: '#FFF4ED',
|
||||
icon_url: '',
|
||||
}
|
||||
const isExternalProvider = dataset.provider === 'external'
|
||||
const { formatIndexingTechniqueAndMethod } = useKnowledge()
|
||||
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
|
||||
|
||||
return (
|
||||
<div className={cn('relative flex flex-col', expand ? '' : 'p-1')}>
|
||||
{expand && (
|
||||
<>
|
||||
<Effect className='-left-5 top-[-22px] opacity-15' />
|
||||
<div className='flex flex-col gap-y-2 p-2'>
|
||||
<div className='relative w-fit'>
|
||||
<AppIcon
|
||||
size='medium'
|
||||
iconType={iconInfo.icon_type}
|
||||
icon={iconInfo.icon}
|
||||
background={iconInfo.icon_background}
|
||||
imageUrl={iconInfo.icon_url}
|
||||
/>
|
||||
{(dataset.doc_form || isExternalProvider) && (
|
||||
<div className='absolute -bottom-1 -right-1 z-10'>
|
||||
<Icon className='size-4' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<>
|
||||
<div className='flex flex-col gap-y-1'>
|
||||
<div
|
||||
className='system-md-semibold truncate text-text-secondary'
|
||||
title={dataset.name}
|
||||
>
|
||||
{dataset.name}
|
||||
</div>
|
||||
<div className='system-2xs-medium-uppercase text-text-tertiary'>
|
||||
{isExternalProvider && t('dataset.externalTag')}
|
||||
{!isExternalProvider && dataset.doc_form && dataset.indexing_technique && (
|
||||
<div className='flex items-center gap-x-1'>
|
||||
<Badge>{t(`dataset.chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}`)}</Badge>
|
||||
<Badge>{formatIndexingTechniqueAndMethod(dataset.indexing_technique, dataset.retrieval_model_dict?.search_method)}</Badge>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className='system-xs-regular line-clamp-3 text-text-tertiary first-letter:capitalize'>
|
||||
{dataset.description}
|
||||
</p>
|
||||
</>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!expand && (
|
||||
<AppIcon
|
||||
size='medium'
|
||||
iconType={iconInfo.icon_type}
|
||||
icon={iconInfo.icon}
|
||||
background={iconInfo.icon_background}
|
||||
imageUrl={iconInfo.icon_url}
|
||||
/>
|
||||
)}
|
||||
{extraInfo}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(DatasetInfo)
|
||||
45
web/app/components/app-sidebar/dataset-info/dropdown.tsx
Normal file
45
web/app/components/app-sidebar/dataset-info/dropdown.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../../base/portal-to-follow-elem'
|
||||
import ActionButton from '../../base/action-button'
|
||||
import { RiMoreFill } from '@remixicon/react'
|
||||
import cn from '@/utils/classnames'
|
||||
import Menu from './menu'
|
||||
|
||||
type DropDownProps = {
|
||||
expand: boolean
|
||||
}
|
||||
|
||||
const DropDown = ({
|
||||
expand,
|
||||
}: DropDownProps) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const handleTrigger = useCallback(() => {
|
||||
setOpen(prev => !prev)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement={expand ? 'bottom-end' : 'right'}
|
||||
offset={expand ? {
|
||||
mainAxis: 4,
|
||||
crossAxis: 10,
|
||||
} : {
|
||||
mainAxis: 4,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={handleTrigger}>
|
||||
<ActionButton className={cn(expand ? 'size-8 rounded-lg' : 'size-6 rounded-md')}>
|
||||
<RiMoreFill className='size-4' />
|
||||
</ActionButton>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent>
|
||||
<Menu />
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(DropDown)
|
||||
88
web/app/components/app-sidebar/dataset-info/index.tsx
Normal file
88
web/app/components/app-sidebar/dataset-info/index.tsx
Normal file
@ -0,0 +1,88 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AppIcon from '../../base/app-icon'
|
||||
import Effect from '../../base/effect'
|
||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import { DOC_FORM_TEXT } from '@/models/datasets'
|
||||
import { useKnowledge } from '@/hooks/use-knowledge'
|
||||
import cn from '@/utils/classnames'
|
||||
import Dropdown from './dropdown'
|
||||
|
||||
type DatasetInfoProps = {
|
||||
expand: boolean
|
||||
}
|
||||
|
||||
const DatasetInfo: FC<DatasetInfoProps> = ({
|
||||
expand,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const dataset = useDatasetDetailContextWithSelector(state => state.dataset) as DataSet
|
||||
const iconInfo = dataset.icon_info || {
|
||||
icon: '📙',
|
||||
icon_type: 'emoji',
|
||||
icon_background: '#FFF4ED',
|
||||
icon_url: '',
|
||||
}
|
||||
const isExternalProvider = dataset.provider === 'external'
|
||||
const { formatIndexingTechniqueAndMethod } = useKnowledge()
|
||||
|
||||
return (
|
||||
<div className={cn('relative flex flex-col', expand ? '' : 'p-1')}>
|
||||
{expand && (
|
||||
<>
|
||||
<Effect className='-left-5 top-[-22px] opacity-15' />
|
||||
<div className='flex flex-col gap-y-2 p-2'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<AppIcon
|
||||
size='medium'
|
||||
iconType={iconInfo.icon_type}
|
||||
icon={iconInfo.icon}
|
||||
background={iconInfo.icon_background}
|
||||
imageUrl={iconInfo.icon_url}
|
||||
/>
|
||||
<Dropdown expand />
|
||||
</div>
|
||||
<div className='flex flex-col gap-y-1 pb-0.5'>
|
||||
<div
|
||||
className='system-md-semibold truncate text-text-secondary'
|
||||
title={dataset.name}
|
||||
>
|
||||
{dataset.name}
|
||||
</div>
|
||||
<div className='system-2xs-medium-uppercase text-text-tertiary'>
|
||||
{isExternalProvider && t('dataset.externalTag')}
|
||||
{!isExternalProvider && dataset.doc_form && dataset.indexing_technique && (
|
||||
<div className='flex items-center gap-x-2'>
|
||||
<span>{t(`dataset.chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}`)}</span>
|
||||
<span>{formatIndexingTechniqueAndMethod(dataset.indexing_technique, dataset.retrieval_model_dict?.search_method)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!!dataset.description && (
|
||||
<p className='system-xs-regular line-clamp-3 text-text-tertiary first-letter:capitalize'>
|
||||
{dataset.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!expand && (
|
||||
<div className='flex flex-col items-center gap-y-1'>
|
||||
<AppIcon
|
||||
size='medium'
|
||||
iconType={iconInfo.icon_type}
|
||||
icon={iconInfo.icon}
|
||||
background={iconInfo.icon_background}
|
||||
imageUrl={iconInfo.icon_url}
|
||||
/>
|
||||
<Dropdown expand={false} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(DatasetInfo)
|
||||
28
web/app/components/app-sidebar/dataset-info/menu-item.tsx
Normal file
28
web/app/components/app-sidebar/dataset-info/menu-item.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import React from 'react'
|
||||
import type { RemixiconComponentType } from '@remixicon/react'
|
||||
|
||||
type MenuItemProps = {
|
||||
name: string
|
||||
Icon: RemixiconComponentType
|
||||
handleClick?: () => void
|
||||
}
|
||||
|
||||
const MenuItem = ({
|
||||
Icon,
|
||||
name,
|
||||
handleClick,
|
||||
}: MenuItemProps) => {
|
||||
return (
|
||||
<div
|
||||
className='flex items-center gap-x-1 rounded-lg px-2 py-1.5 hover:bg-state-base-hover'
|
||||
onClick={() => {
|
||||
handleClick?.()
|
||||
}}
|
||||
>
|
||||
<Icon className='size-4 text-text-tertiary' />
|
||||
<span className='system-md-regular px-1 text-text-secondary'>{name}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(MenuItem)
|
||||
21
web/app/components/app-sidebar/dataset-info/menu.tsx
Normal file
21
web/app/components/app-sidebar/dataset-info/menu.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import MenuItem from './menu-item'
|
||||
import { RiEditLine } from '@remixicon/react'
|
||||
import { noop } from 'lodash-es'
|
||||
|
||||
const Menu = () => {
|
||||
const { t } = useTranslation()
|
||||
const dataset = useDatasetDetailContextWithSelector(state => state.dataset)
|
||||
|
||||
return (
|
||||
<div className='flex w-[200px] flex-col rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px]'>
|
||||
<div className='flex flex-col p-1'>
|
||||
<MenuItem Icon={RiEditLine} name={t('common.operation.edit')} handleClick={noop} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Menu)
|
||||
@ -82,25 +82,22 @@ const AppDetailNav = ({
|
||||
return (
|
||||
<div
|
||||
ref={sidebarRef}
|
||||
className={`
|
||||
flex shrink-0 flex-col border-r border-divider-burn bg-background-default-subtle transition-all
|
||||
${expand ? 'w-[216px]' : 'w-14'}
|
||||
`}
|
||||
className={cn(
|
||||
'flex shrink-0 flex-col border-r border-divider-burn bg-background-default-subtle transition-all',
|
||||
expand ? 'w-[216px]' : 'w-14',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
shrink-0
|
||||
${expand ? 'p-2' : 'p-1'}
|
||||
`}
|
||||
className={cn(
|
||||
'shrink-0',
|
||||
expand ? 'p-2' : 'p-1',
|
||||
)}
|
||||
>
|
||||
{iconType === 'app' && (
|
||||
<AppInfo expand={expand} />
|
||||
)}
|
||||
{iconType !== 'app' && (
|
||||
<DatasetInfo
|
||||
expand={expand}
|
||||
extraInfo={extraInfo && extraInfo(appSidebarExpand)}
|
||||
/>
|
||||
<DatasetInfo expand={expand} />
|
||||
)}
|
||||
</div>
|
||||
<div className='relative px-4 py-2'>
|
||||
@ -141,6 +138,7 @@ const AppDetailNav = ({
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
{iconType !== 'app' && extraInfo && extraInfo(appSidebarExpand)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ import Operations from './operations'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import CornerLabel from '@/app/components/base/corner-label'
|
||||
import { DOC_FORM_ICON_WITH_BG, DOC_FORM_TEXT } from '@/models/datasets'
|
||||
import { useExportPipelineDSL } from '@/service/use-pipeline'
|
||||
|
||||
const EXTERNAL_PROVIDER = 'external'
|
||||
|
||||
@ -45,6 +46,7 @@ const DatasetCard = ({
|
||||
const [showRenameModal, setShowRenameModal] = useState(false)
|
||||
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
|
||||
const [confirmMessage, setConfirmMessage] = useState<string>('')
|
||||
const [exporting, setExporting] = useState(false)
|
||||
|
||||
const isExternalProvider = useMemo(() => {
|
||||
return dataset.provider === EXTERNAL_PROVIDER
|
||||
@ -81,6 +83,36 @@ const DatasetCard = ({
|
||||
return dayjs(time * 1_000).locale(language === 'zh_Hans' ? 'zh-cn' : language.replace('_', '-')).fromNow()
|
||||
}, [language])
|
||||
|
||||
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' })
|
||||
a.href = URL.createObjectURL(file)
|
||||
a.download = `${name}.yml`
|
||||
a.click()
|
||||
}
|
||||
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)
|
||||
@ -234,6 +266,7 @@ const DatasetCard = ({
|
||||
setShowRenameModal(true)
|
||||
}}
|
||||
detectIsUsedByApp={detectIsUsedByApp}
|
||||
handleExportPipeline={handleExportPipeline}
|
||||
/>
|
||||
}
|
||||
className={'z-20 min-w-[186px]'}
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
import React from 'react'
|
||||
import type { RemixiconComponentType } from '@remixicon/react'
|
||||
|
||||
type OperationItemProps = {
|
||||
Icon: RemixiconComponentType
|
||||
name: string
|
||||
handleClick?: (e: React.MouseEvent<HTMLDivElement>) => void
|
||||
}
|
||||
|
||||
const OperationItem = ({
|
||||
Icon,
|
||||
name,
|
||||
handleClick,
|
||||
}: OperationItemProps) => {
|
||||
return (
|
||||
<div
|
||||
className='flex cursor-pointer items-center gap-x-1 rounded-lg px-2 py-1.5 hover:bg-state-base-hover'
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Icon className='size-4 text-text-tertiary' />
|
||||
<span className='system-md-regular px-1 text-text-secondary'>
|
||||
{name}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(OperationItem)
|
||||
@ -1,17 +1,20 @@
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiDeleteBinLine, RiEditLine } from '@remixicon/react'
|
||||
import { RiDeleteBinLine, RiEditLine, RiFileDownloadLine } from '@remixicon/react'
|
||||
import OperationItem from './operation-item'
|
||||
|
||||
type OperationsProps = {
|
||||
showDelete: boolean
|
||||
openRenameModal: () => void
|
||||
handleExportPipeline: () => void
|
||||
detectIsUsedByApp: () => void
|
||||
}
|
||||
|
||||
const Operations = ({
|
||||
showDelete,
|
||||
openRenameModal,
|
||||
handleExportPipeline,
|
||||
detectIsUsedByApp,
|
||||
}: OperationsProps) => {
|
||||
const { t } = useTranslation()
|
||||
@ -22,6 +25,12 @@ const Operations = ({
|
||||
openRenameModal()
|
||||
}
|
||||
|
||||
const onClickExport = async (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
handleExportPipeline()
|
||||
}
|
||||
|
||||
const onClickDelete = async (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
@ -31,59 +40,26 @@ const Operations = ({
|
||||
return (
|
||||
<div className='relative flex w-full flex-col rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg shadow-shadow-shadow-5'>
|
||||
<div className='flex flex-col p-1'>
|
||||
<div
|
||||
className='flex cursor-pointer items-center gap-x-1 rounded-lg px-2 py-1.5 hover:bg-state-base-hover'
|
||||
onClick={onClickRename}
|
||||
>
|
||||
<RiEditLine className='size-4 text-text-tertiary' />
|
||||
<span className='system-md-regular px-1 text-text-secondary'>
|
||||
{t('common.operation.edit')}
|
||||
</span>
|
||||
</div>
|
||||
{/* <div
|
||||
className='flex cursor-pointer items-center gap-x-1 rounded-lg px-2 py-1.5 hover:bg-state-base-hover'
|
||||
onClick={() => { console.log('duplicate') }}
|
||||
>
|
||||
<RiFileCopyLine className='size-4 text-text-tertiary' />
|
||||
<span className='system-md-regular px-1 text-text-secondary'>
|
||||
{t('common.operation.duplicate')}
|
||||
</span>
|
||||
</div> */}
|
||||
<OperationItem
|
||||
Icon={RiEditLine}
|
||||
name={t('common.operation.edit')}
|
||||
handleClick={onClickRename}
|
||||
/>
|
||||
<OperationItem
|
||||
Icon={RiFileDownloadLine}
|
||||
name={t('datasetPipeline.operations.exportPipeline')}
|
||||
handleClick={onClickExport}
|
||||
/>
|
||||
</div>
|
||||
{/* <Divider type='horizontal' className='my-0 bg-divider-subtle' />
|
||||
<div className='flex flex-col p-1'>
|
||||
<div
|
||||
className='flex cursor-pointer items-center gap-x-1 rounded-lg px-2 py-1.5 hover:bg-state-base-hover'
|
||||
onClick={() => { console.log('Export') }}
|
||||
>
|
||||
<RiEditLine className='size-4 text-text-tertiary' />
|
||||
<span className='system-md-regular px-1 text-text-secondary'>
|
||||
Export Solution
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className='flex cursor-pointer items-center gap-x-1 rounded-lg px-2 py-1.5 hover:bg-state-base-hover'
|
||||
onClick={() => { console.log('Import') }}
|
||||
>
|
||||
<RiFileCopyLine className='size-4 text-text-tertiary' />
|
||||
<span className='system-md-regular px-1 text-text-secondary'>
|
||||
Import Solution
|
||||
</span>
|
||||
</div>
|
||||
</div> */}
|
||||
{showDelete && (
|
||||
<>
|
||||
<Divider type='horizontal' className='my-0 bg-divider-subtle' />
|
||||
<div className='flex flex-col p-1'>
|
||||
<div
|
||||
className='group flex cursor-pointer items-center gap-x-1 rounded-lg px-2 py-1.5 hover:bg-state-destructive-hover'
|
||||
onClick={onClickDelete}
|
||||
>
|
||||
<RiDeleteBinLine className='size-4 text-text-tertiary group-hover:text-text-destructive' />
|
||||
<span className='system-md-regular px-1 text-text-secondary group-hover:text-text-destructive'>
|
||||
{t('common.operation.delete')}
|
||||
</span>
|
||||
</div>
|
||||
<OperationItem
|
||||
Icon={RiDeleteBinLine}
|
||||
name={t('common.operation.delete')}
|
||||
handleClick={onClickDelete}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -29,6 +29,7 @@ const translation = {
|
||||
dataSource: 'Data Source',
|
||||
saveAndProcess: 'Save & Process',
|
||||
preview: 'Preview',
|
||||
exportPipeline: 'Export Pipeline',
|
||||
},
|
||||
knowledgeNameAndIcon: 'Knowledge name & icon',
|
||||
knowledgeNameAndIconPlaceholder: 'Please enter the name of the Knowledge Base',
|
||||
|
||||
@ -29,6 +29,7 @@ const translation = {
|
||||
dataSource: '数据源',
|
||||
saveAndProcess: '保存并处理',
|
||||
preview: '预览',
|
||||
exportPipeline: '导出 pipeline',
|
||||
},
|
||||
knowledgeNameAndIcon: '知识库名称和图标',
|
||||
knowledgeNameAndIconPlaceholder: '请输入知识库名称',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user