mirror of
https://github.com/langgenius/dify.git
synced 2025-12-05 15:26:11 +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 { useTranslation } from 'react-i18next'
|
||||||
import type { RemixiconComponentType } from '@remixicon/react'
|
import type { RemixiconComponentType } from '@remixicon/react'
|
||||||
import {
|
import {
|
||||||
RiAttachmentLine,
|
|
||||||
RiEqualizer2Fill,
|
RiEqualizer2Fill,
|
||||||
RiEqualizer2Line,
|
RiEqualizer2Line,
|
||||||
RiFileTextFill,
|
RiFileTextFill,
|
||||||
@ -14,7 +13,6 @@ import {
|
|||||||
RiFocus2Line,
|
RiFocus2Line,
|
||||||
} from '@remixicon/react'
|
} from '@remixicon/react'
|
||||||
import { RiInformation2Line } from '@remixicon/react'
|
import { RiInformation2Line } from '@remixicon/react'
|
||||||
import classNames from '@/utils/classnames'
|
|
||||||
import type { RelatedAppResponse } from '@/models/datasets'
|
import type { RelatedAppResponse } from '@/models/datasets'
|
||||||
import AppSideBar from '@/app/components/app-sidebar'
|
import AppSideBar from '@/app/components/app-sidebar'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
@ -22,7 +20,6 @@ import DatasetDetailContext from '@/context/dataset-detail'
|
|||||||
import { DataSourceType } from '@/models/datasets'
|
import { DataSourceType } from '@/models/datasets'
|
||||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||||
import { useStore } from '@/app/components/app/store'
|
import { useStore } from '@/app/components/app/store'
|
||||||
import { useDocLink } from '@/context/i18n'
|
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import LinkedAppsPanel from '@/app/components/base/linked-apps-panel'
|
import LinkedAppsPanel from '@/app/components/base/linked-apps-panel'
|
||||||
@ -49,7 +46,6 @@ const ExtraInfo = React.memo(({
|
|||||||
expand,
|
expand,
|
||||||
}: IExtraInfoProps) => {
|
}: IExtraInfoProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const docLink = useDocLink()
|
|
||||||
|
|
||||||
const hasRelatedApps = relatedApps?.data && relatedApps?.data?.length > 0
|
const hasRelatedApps = relatedApps?.data && relatedApps?.data?.length > 0
|
||||||
const relatedAppsTotal = relatedApps?.data?.length || 0
|
const relatedAppsTotal = relatedApps?.data?.length || 0
|
||||||
@ -57,7 +53,7 @@ const ExtraInfo = React.memo(({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!expand && (
|
{!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='flex grow flex-col px-2 pb-1.5 pt-1'>
|
||||||
<div className='system-md-semibold-uppercase text-text-secondary'>
|
<div className='system-md-semibold-uppercase text-text-secondary'>
|
||||||
{documentCount ?? '--'}
|
{documentCount ?? '--'}
|
||||||
@ -74,7 +70,7 @@ const ExtraInfo = React.memo(({
|
|||||||
{relatedAppsTotal ?? '--'}
|
{relatedAppsTotal ?? '--'}
|
||||||
</div>
|
</div>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
position='bottom-start'
|
position='top-start'
|
||||||
noDecoration
|
noDecoration
|
||||||
needsDelay
|
needsDelay
|
||||||
popupContent={
|
popupContent={
|
||||||
@ -94,13 +90,6 @@ const ExtraInfo = React.memo(({
|
|||||||
</div>
|
</div>
|
||||||
</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 (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={sidebarRef}
|
ref={sidebarRef}
|
||||||
className={`
|
className={cn(
|
||||||
flex shrink-0 flex-col border-r border-divider-burn bg-background-default-subtle transition-all
|
'flex shrink-0 flex-col border-r border-divider-burn bg-background-default-subtle transition-all',
|
||||||
${expand ? 'w-[216px]' : 'w-14'}
|
expand ? 'w-[216px]' : 'w-14',
|
||||||
`}
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`
|
className={cn(
|
||||||
shrink-0
|
'shrink-0',
|
||||||
${expand ? 'p-2' : 'p-1'}
|
expand ? 'p-2' : 'p-1',
|
||||||
`}
|
)}
|
||||||
>
|
>
|
||||||
{iconType === 'app' && (
|
{iconType === 'app' && (
|
||||||
<AppInfo expand={expand} />
|
<AppInfo expand={expand} />
|
||||||
)}
|
)}
|
||||||
{iconType !== 'app' && (
|
{iconType !== 'app' && (
|
||||||
<DatasetInfo
|
<DatasetInfo expand={expand} />
|
||||||
expand={expand}
|
|
||||||
extraInfo={extraInfo && extraInfo(appSidebarExpand)}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className='relative px-4 py-2'>
|
<div className='relative px-4 py-2'>
|
||||||
@ -141,6 +138,7 @@ const AppDetailNav = ({
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</nav>
|
</nav>
|
||||||
|
{iconType !== 'app' && extraInfo && extraInfo(appSidebarExpand)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import Operations from './operations'
|
|||||||
import AppIcon from '@/app/components/base/app-icon'
|
import AppIcon from '@/app/components/base/app-icon'
|
||||||
import CornerLabel from '@/app/components/base/corner-label'
|
import CornerLabel from '@/app/components/base/corner-label'
|
||||||
import { DOC_FORM_ICON_WITH_BG, DOC_FORM_TEXT } from '@/models/datasets'
|
import { DOC_FORM_ICON_WITH_BG, DOC_FORM_TEXT } from '@/models/datasets'
|
||||||
|
import { useExportPipelineDSL } from '@/service/use-pipeline'
|
||||||
|
|
||||||
const EXTERNAL_PROVIDER = 'external'
|
const EXTERNAL_PROVIDER = 'external'
|
||||||
|
|
||||||
@ -45,6 +46,7 @@ const DatasetCard = ({
|
|||||||
const [showRenameModal, setShowRenameModal] = useState(false)
|
const [showRenameModal, setShowRenameModal] = useState(false)
|
||||||
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
|
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
|
||||||
const [confirmMessage, setConfirmMessage] = useState<string>('')
|
const [confirmMessage, setConfirmMessage] = useState<string>('')
|
||||||
|
const [exporting, setExporting] = useState(false)
|
||||||
|
|
||||||
const isExternalProvider = useMemo(() => {
|
const isExternalProvider = useMemo(() => {
|
||||||
return dataset.provider === EXTERNAL_PROVIDER
|
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()
|
return dayjs(time * 1_000).locale(language === 'zh_Hans' ? 'zh-cn' : language.replace('_', '-')).fromNow()
|
||||||
}, [language])
|
}, [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 () => {
|
const detectIsUsedByApp = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const { is_using: isUsedByApp } = await checkIsUsedInApp(dataset.id)
|
const { is_using: isUsedByApp } = await checkIsUsedInApp(dataset.id)
|
||||||
@ -234,6 +266,7 @@ const DatasetCard = ({
|
|||||||
setShowRenameModal(true)
|
setShowRenameModal(true)
|
||||||
}}
|
}}
|
||||||
detectIsUsedByApp={detectIsUsedByApp}
|
detectIsUsedByApp={detectIsUsedByApp}
|
||||||
|
handleExportPipeline={handleExportPipeline}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
className={'z-20 min-w-[186px]'}
|
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 Divider from '@/app/components/base/divider'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
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 = {
|
type OperationsProps = {
|
||||||
showDelete: boolean
|
showDelete: boolean
|
||||||
openRenameModal: () => void
|
openRenameModal: () => void
|
||||||
|
handleExportPipeline: () => void
|
||||||
detectIsUsedByApp: () => void
|
detectIsUsedByApp: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const Operations = ({
|
const Operations = ({
|
||||||
showDelete,
|
showDelete,
|
||||||
openRenameModal,
|
openRenameModal,
|
||||||
|
handleExportPipeline,
|
||||||
detectIsUsedByApp,
|
detectIsUsedByApp,
|
||||||
}: OperationsProps) => {
|
}: OperationsProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -22,6 +25,12 @@ const Operations = ({
|
|||||||
openRenameModal()
|
openRenameModal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onClickExport = async (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
handleExportPipeline()
|
||||||
|
}
|
||||||
|
|
||||||
const onClickDelete = async (e: React.MouseEvent<HTMLDivElement>) => {
|
const onClickDelete = async (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@ -31,59 +40,26 @@ const Operations = ({
|
|||||||
return (
|
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='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 flex-col p-1'>
|
||||||
<div
|
<OperationItem
|
||||||
className='flex cursor-pointer items-center gap-x-1 rounded-lg px-2 py-1.5 hover:bg-state-base-hover'
|
Icon={RiEditLine}
|
||||||
onClick={onClickRename}
|
name={t('common.operation.edit')}
|
||||||
>
|
handleClick={onClickRename}
|
||||||
<RiEditLine className='size-4 text-text-tertiary' />
|
/>
|
||||||
<span className='system-md-regular px-1 text-text-secondary'>
|
<OperationItem
|
||||||
{t('common.operation.edit')}
|
Icon={RiFileDownloadLine}
|
||||||
</span>
|
name={t('datasetPipeline.operations.exportPipeline')}
|
||||||
</div>
|
handleClick={onClickExport}
|
||||||
{/* <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> */}
|
|
||||||
</div>
|
</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 && (
|
{showDelete && (
|
||||||
<>
|
<>
|
||||||
<Divider type='horizontal' className='my-0 bg-divider-subtle' />
|
<Divider type='horizontal' className='my-0 bg-divider-subtle' />
|
||||||
<div className='flex flex-col p-1'>
|
<div className='flex flex-col p-1'>
|
||||||
<div
|
<OperationItem
|
||||||
className='group flex cursor-pointer items-center gap-x-1 rounded-lg px-2 py-1.5 hover:bg-state-destructive-hover'
|
Icon={RiDeleteBinLine}
|
||||||
onClick={onClickDelete}
|
name={t('common.operation.delete')}
|
||||||
>
|
handleClick={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>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -29,6 +29,7 @@ const translation = {
|
|||||||
dataSource: 'Data Source',
|
dataSource: 'Data Source',
|
||||||
saveAndProcess: 'Save & Process',
|
saveAndProcess: 'Save & Process',
|
||||||
preview: 'Preview',
|
preview: 'Preview',
|
||||||
|
exportPipeline: 'Export Pipeline',
|
||||||
},
|
},
|
||||||
knowledgeNameAndIcon: 'Knowledge name & icon',
|
knowledgeNameAndIcon: 'Knowledge name & icon',
|
||||||
knowledgeNameAndIconPlaceholder: 'Please enter the name of the Knowledge Base',
|
knowledgeNameAndIconPlaceholder: 'Please enter the name of the Knowledge Base',
|
||||||
|
|||||||
@ -29,6 +29,7 @@ const translation = {
|
|||||||
dataSource: '数据源',
|
dataSource: '数据源',
|
||||||
saveAndProcess: '保存并处理',
|
saveAndProcess: '保存并处理',
|
||||||
preview: '预览',
|
preview: '预览',
|
||||||
|
exportPipeline: '导出 pipeline',
|
||||||
},
|
},
|
||||||
knowledgeNameAndIcon: '知识库名称和图标',
|
knowledgeNameAndIcon: '知识库名称和图标',
|
||||||
knowledgeNameAndIconPlaceholder: '请输入知识库名称',
|
knowledgeNameAndIconPlaceholder: '请输入知识库名称',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user