浏览代码

feat(pipeline): implement footer component for dataset creation and enhance UI with new styles

tags/2.0.0-beta.1
twwu 2 个月前
父节点
当前提交
4a5c883988

+ 1
- 1
web/app/components/datasets/create-from-pipeline/create-form/index.tsx 查看文件

@@ -9,7 +9,7 @@ import { useMembers } from '@/service/use-common'
import type { AppIconType } from '@/types/app'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import PermissionSelector from '../../settings/permission-selector'
import PermissionSelector from '@/app/components/datasets/settings/permission-selector'
import Button from '@/app/components/base/button'
import { RiCloseLine } from '@remixicon/react'
import Toast from '@/app/components/base/toast'

+ 0
- 5
web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/index.tsx 查看文件

@@ -68,11 +68,6 @@ const CreateFromDSLModal = ({
setFileContent('')
}

// todo: TBD billing plan
// const plan = useProviderContextSelector(state => state.plan)
// const enableBilling = useProviderContextSelector(state => state.enableBilling)
// const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps)

const isCreatingRef = useRef(false)

const { mutateAsync: importDSL } = useImportPipelineDSL()

+ 0
- 37
web/app/components/datasets/create-from-pipeline/create-options/item.tsx 查看文件

@@ -1,37 +0,0 @@
import type { RemixiconComponentType } from '@remixicon/react'
import React from 'react'

type ItemProps = {
Icon: RemixiconComponentType
title: string
description: string
onClick: () => void
}

const Item = ({
Icon,
title,
description,
onClick,
}: ItemProps) => {
return (
<div
className='group flex w-[337px] cursor-pointer items-center gap-x-3 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-4 shadow-xs shadow-shadow-shadow-3 hover:shadow-md hover:shadow-shadow-shadow-5'
onClick={onClick}
>
<div className='flex size-10 shrink-0 items-center justify-center rounded-[10px] border border-dashed border-divider-regular bg-background-section group-hover:border-state-accent-hover-alt group-hover:bg-state-accent-hover'>
<Icon className='size-5 text-text-quaternary group-hover:text-text-accent' />
</div>
<div className='flex grow flex-col gap-y-0.5 py-px'>
<div className='system-md-semibold truncate text-text-secondary'>
{title}
</div>
<div className='system-xs-regular text-text-tertiary'>
{description}
</div>
</div>
</div>
)
}

export default React.memo(Item)

web/app/components/datasets/create-from-pipeline/create-options/index.tsx → web/app/components/datasets/create-from-pipeline/footer.tsx 查看文件

@@ -1,20 +1,16 @@
import React, { useCallback, useMemo, useState } from 'react'
import Item from './item'
import { RiAddCircleFill, RiFileUploadLine } from '@remixicon/react'
import CreateFromScratchModal from './create-from-scratch-modal'
import { useRouter, useSearchParams } from 'next/navigation'
import CreateFromDSLModal, { CreateFromDSLModalTab } from './create-from-dsl-modal'
import { useProviderContextSelector } from '@/context/provider-context'
import { RiFileUploadLine } from '@remixicon/react'
import Divider from '../../base/divider'
import { useTranslation } from 'react-i18next'
import CreateFromDSLModal, { CreateFromDSLModalTab } from './create-options/create-from-dsl-modal'
import { useRouter, useSearchParams } from 'next/navigation'
import { useResetDatasetList } from '@/service/knowledge/use-dataset'

const CreateOptions = () => {
const Footer = () => {
const { t } = useTranslation()

const [showCreateModal, setShowCreateModal] = useState(false)
const [showImportModal, setShowImportModal] = useState(false)

const onPlanInfoChanged = useProviderContextSelector(state => state.onPlanInfoChanged)
const searchParams = useSearchParams()
const { replace } = useRouter()
const dslUrl = searchParams.get('remoteInstallUrl') || undefined
@@ -27,14 +23,6 @@ const CreateOptions = () => {
return undefined
}, [dslUrl])

const openCreateFromScratch = useCallback(() => {
setShowCreateModal(true)
}, [])

const closeCreateFromScratch = useCallback(() => {
setShowCreateModal(false)
}, [])

const openImportFromDSL = useCallback(() => {
setShowImportModal(true)
}, [])
@@ -46,28 +34,20 @@ const CreateOptions = () => {
}, [dslUrl, replace])

const onImportFromDSLSuccess = useCallback(() => {
onPlanInfoChanged()
resetDatasetList()
}, [onPlanInfoChanged, resetDatasetList])
}, [resetDatasetList])

return (
<div className='flex items-center gap-x-3 px-16 py-2'>
<Item
Icon={RiAddCircleFill}
title={t('datasetPipeline.creation.createFromScratch.title')}
description={t('datasetPipeline.creation.createFromScratch.description')}
onClick={openCreateFromScratch}
/>
<Item
Icon={RiFileUploadLine}
title={t('datasetPipeline.creation.ImportDSL.title')}
description={t('datasetPipeline.creation.ImportDSL.description')}
<div className='absolute bottom-0 left-0 right-0 z-10 flex flex-col gap-y-4 bg-knowledge-pipeline-creation-footer-bg px-16 pb-6 backdrop-blur-[6px]'>
<Divider type='horizontal' className='my-0 w-8' />
<button
type='button'
className='system-md-medium flex items-center gap-x-3 text-text-accent'
onClick={openImportFromDSL}
/>
<CreateFromScratchModal
show={showCreateModal}
onClose={closeCreateFromScratch}
/>
>
<RiFileUploadLine className='size-5' />
<span>{t('datasetPipeline.creation.importDSL')}</span>
</button>
<CreateFromDSLModal
show={showImportModal}
onClose={onCloseImportModal}
@@ -79,4 +59,4 @@ const CreateOptions = () => {
)
}

export default CreateOptions
export default React.memo(Footer)

+ 2
- 2
web/app/components/datasets/create-from-pipeline/index.tsx 查看文件

@@ -1,8 +1,8 @@
'use client'
import Header from './header'
import CreateOptions from './create-options'
import List from './list'
import Effect from '../../base/effect'
import Footer from './footer'

const CreateFromPipeline = () => {
return (
@@ -11,8 +11,8 @@ const CreateFromPipeline = () => {
>
<Effect className='left-8 top-[-34px] opacity-20' />
<Header />
<CreateOptions />
<List />
<Footer />
</div>
)
}

+ 5
- 6
web/app/components/datasets/create-from-pipeline/list/built-in-pipeline-list.tsx 查看文件

@@ -1,16 +1,15 @@
import { usePipelineTemplateList } from '@/service/use-pipeline'
import TemplateCard from './template-card'
import CreateCard from './create-card'

const BuiltInPipelineList = () => {
const { data: pipelineList, isLoading } = usePipelineTemplateList({ type: 'built-in' })
const list = pipelineList?.pipeline_templates

if (isLoading || !list)
return null
const list = pipelineList?.pipeline_templates || []

return (
<div className='grid grid-cols-1 gap-3 py-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'>
{list.map((pipeline, index) => (
<div className='grid grid-cols-1 gap-3 py-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'>
<CreateCard />
{!isLoading && list.map((pipeline, index) => (
<TemplateCard
key={index}
type='built-in'

+ 43
- 0
web/app/components/datasets/create-from-pipeline/list/create-card.tsx 查看文件

@@ -0,0 +1,43 @@
import React, { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { RiAddCircleLine } from '@remixicon/react'
import CreateFromScratchModal from '../create-options/create-from-scratch-modal'

const CreateCard = () => {
const { t } = useTranslation()

const [showCreateModal, setShowCreateModal] = useState(false)

const openCreateFromScratch = useCallback(() => {
setShowCreateModal(true)
}, [])

const closeCreateFromScratch = useCallback(() => {
setShowCreateModal(false)
}, [])

return (
<div
className='group relative flex h-[132px] cursor-pointer flex-col rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg pb-3 shadow-xs shadow-shadow-shadow-3'
onClick={openCreateFromScratch}
>
<div className='flex items-center gap-x-3 p-4 pb-2'>
<div className='flex size-10 shrink-0 items-center justify-center rounded-[10px] border border-dashed border-divider-regular bg-background-section group-hover:border-state-accent-hover-alt group-hover:bg-state-accent-hover'>
<RiAddCircleLine className='size-5 text-text-quaternary group-hover:text-text-accent' />
</div>
<div className='system-md-semibold truncate text-text-primary'>
{t('datasetPipeline.creation.createFromScratch.title')}
</div>
</div>
<p className='system-xs-regular line-clamp-3 px-4 py-1 text-text-tertiary'>
{t('datasetPipeline.creation.createFromScratch.description')}
</p>
<CreateFromScratchModal
show={showCreateModal}
onClose={closeCreateFromScratch}
/>
</div>
)
}

export default React.memo(CreateCard)

+ 16
- 11
web/app/components/datasets/create-from-pipeline/list/customized-list.tsx 查看文件

@@ -1,23 +1,28 @@
import TemplateCard from './template-card'
import { usePipelineTemplateList } from '@/service/use-pipeline'
import { useTranslation } from 'react-i18next'

const CustomizedList = () => {
const { t } = useTranslation()
const { data: pipelineList, isLoading } = usePipelineTemplateList({ type: 'customized' })
const list = pipelineList?.pipeline_templates
const list = pipelineList?.pipeline_templates || []

if (isLoading || !list)
if (isLoading || list.length === 0)
return null

return (
<div className='grid grid-cols-1 gap-3 py-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'>
{list.map((pipeline, index) => (
<TemplateCard
key={index}
type='customized'
pipeline={pipeline}
/>
))}
</div>
<>
<div className='system-sm-semibold-uppercase pt-2 text-text-tertiary'>{t('datasetPipeline.templates.customized')}</div>
<div className='grid grid-cols-1 gap-3 py-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'>
{list.map((pipeline, index) => (
<TemplateCard
key={index}
type='customized'
pipeline={pipeline}
/>
))}
</div>
</>
)
}


+ 3
- 31
web/app/components/datasets/create-from-pipeline/list/index.tsx 查看文件

@@ -1,39 +1,11 @@
import { useCallback, useMemo, useState } from 'react'
import Tab from './tab'
import BuiltInPipelineList from './built-in-pipeline-list'
import CustomizedList from './customized-list'
import { useTranslation } from 'react-i18next'

const List = () => {
const { t } = useTranslation()
const [activeTab, setActiveTab] = useState('built-in')

const options = useMemo(() => {
return [
{ value: 'built-in', label: t('datasetPipeline.tabs.builtInPipeline') },
{ value: 'customized', label: t('datasetPipeline.tabs.customized') },
]
}, [t])

const handleTabChange = useCallback((tab: string) => {
setActiveTab(tab)
}, [])

return (
<div className='flex grow flex-col overflow-hidden'>
<Tab
activeTab={activeTab}
handleTabChange={handleTabChange}
options={options}
/>
<div className='grow overflow-y-auto px-16'>
{
activeTab === 'built-in' && <BuiltInPipelineList />
}
{
activeTab === 'customized' && <CustomizedList />
}
</div>
<div className='grow gap-y-1 overflow-y-auto px-16 pb-[60px] pt-1'>
<BuiltInPipelineList />
<CustomizedList />
</div>
)
}

+ 0
- 32
web/app/components/datasets/create-from-pipeline/list/tab/index.tsx 查看文件

@@ -1,32 +0,0 @@
import React from 'react'
import Item from './item'

type TabProps = {
activeTab: string
handleTabChange: (tab: string) => void
options: { value: string; label: string; }[]
}

const Tab = ({
activeTab,
handleTabChange,
options,
}: TabProps) => {
return (
<div className='px-16 pt-2'>
<div className='relative flex h-10 items-center gap-x-6'>
{options.map((option, index) => (
<Item
key={index}
option={option}
isSelected={activeTab === option.value}
onClick={handleTabChange}
/>
))}
<div className='absolute bottom-0 left-0 h-px w-full bg-divider-subtle' />
</div>
</div>
)
}

export default React.memo(Tab)

+ 0
- 29
web/app/components/datasets/create-from-pipeline/list/tab/item.tsx 查看文件

@@ -1,29 +0,0 @@
import cn from '@/utils/classnames'
import React from 'react'

type ItemProps = {
isSelected: boolean
option: { value: string; label: string }
onClick: (value: string) => void
}

const Item = ({
isSelected,
option,
onClick,
}: ItemProps) => {
return (
<div
className={cn(
'system-sm-semibold-uppercase relative flex h-full cursor-pointer items-center',
isSelected ? 'text-text-primary' : 'text-text-tertiary',
)}
onClick={onClick.bind(null, option.value)}
>
<span>{option.label}</span>
{isSelected && <div className='absolute bottom-0 left-0 h-0.5 w-full bg-util-colors-blue-brand-blue-brand-600' />}
</div>
)
}

export default React.memo(Item)

+ 5
- 9
web/i18n/en-US/dataset-pipeline.ts 查看文件

@@ -1,21 +1,17 @@
const translation = {
creation: {
title: 'Create knowledge pipeline',
title: 'Blank Knowledge Pipeline',
createFromScratch: {
title: 'Create from scratch',
description: 'Blank knowledge pipeline',
},
ImportDSL: {
title: 'Import',
description: 'Import from a DSL file',
title: 'Blank knowledge pipeline',
description: 'Create a custom pipeline from scratch with full control over data processing and structure.',
},
importDSL: 'Import from a DSL file',
createKnowledge: 'Create Knowledge',
errorTip: 'Failed to create a Knowledge Base',
successTip: 'Successfully created a Knowledge Base',
caution: 'Caution',
},
tabs: {
builtInPipeline: 'Built-in pipeline',
templates: {
customized: 'Customized',
},
operations: {

+ 21
- 25
web/i18n/zh-Hans/dataset-pipeline.ts 查看文件

@@ -1,21 +1,17 @@
const translation = {
creation: {
title: '创建知识库 pipeline',
title: '创建知识流水线',
createFromScratch: {
title: '从零开始创建',
description: '空白知识库 pipeline',
title: '空白知识流水线',
description: '从零开始创建一个自定义知识流水线,对数据处理和结构拥有完全控制权。',
},
ImportDSL: {
title: '导入',
description: '从 DSL 文件导入',
},
createKnowledge: '创建知识库',
errorTip: '创建知识库',
successTip: '成功创建知识库',
importDSL: '从 DSL 文件导入',
createKnowledge: '创建知识流水线',
errorTip: '创建知识流水线失败',
successTip: '成功创建知识流水线',
caution: '注意',
},
tabs: {
builtInPipeline: '内置 pipeline',
templates: {
customized: '自定义',
},
operations: {
@@ -23,13 +19,13 @@ const translation = {
details: '详情',
editInfo: '编辑信息',
exportDSL: '导出 DSL',
useTemplate: '使用此知识库 pipeline',
useTemplate: '使用此知识流水线',
backToDataSource: '返回数据源',
process: '处理',
dataSource: '数据源',
saveAndProcess: '保存并处理',
preview: '预览',
exportPipeline: '导出 pipeline',
exportPipeline: '导出知识流水线',
convert: '转换',
},
knowledgeNameAndIcon: '知识库名称和图标',
@@ -37,15 +33,15 @@ const translation = {
knowledgeDescription: '知识库描述',
knowledgeDescriptionPlaceholder: '描述知识库中的内容。详细的描述可以让 AI 更准确地访问数据集的内容。如果为空,Dify 将使用默认的命中策略。(可选)',
knowledgePermissions: '权限',
editPipelineInfo: '编辑 pipeline 信息',
pipelineNameAndIcon: 'pipeline 名称和图标',
editPipelineInfo: '编辑知识流水线信息',
pipelineNameAndIcon: '知识流水线名称和图标',
deletePipeline: {
title: '要删除此 pipeline 模板吗?',
content: '删除 pipeline 模板是不可逆的。',
title: '要删除此知识流水线模板吗?',
content: '删除知识流水线模板是不可逆的。',
},
exportDSL: {
successTip: '成功导出 pipeline DSL',
errorTip: '导出 pipeline DSL 失败',
successTip: '成功导出知识流水线 DSL',
errorTip: '导出知识流水线 DSL 失败',
},
details: {
createdBy: '由 {{author}} 创建',
@@ -70,7 +66,7 @@ const translation = {
inputField: '输入字段',
inputFieldPanel: {
title: '用户输入字段',
description: '用户输入字段用于定义和收集 pipeline 执行过程中所需的变量,用户可以自定义字段类型,并灵活配置输入,以满足不同数据源或文档处理的需求。',
description: '用户输入字段用于定义和收集知识流水线执行过程中所需的变量,用户可以自定义字段类型,并灵活配置输入,以满足不同数据源或文档处理的需求。',
uniqueInputs: {
title: '非共享输入',
tooltip: '非共享输入只能被选定的数据源及其下游节点访问。用户在选择其他数据源时不需要填写它。只有数据源变量引用的输入字段才会出现在第一步(数据源)中。所有其他字段将在第二步(Process Documents)中显示。',
@@ -136,16 +132,16 @@ const translation = {
},
configurationTip: '配置 {{pluginName}}',
conversion: {
title: '转换为知识库 pipeline',
descriptionChunk1: '您现在可以将现有知识库转换为使用知识库 pipeline 来处理文档',
title: '转换为知识流水线',
descriptionChunk1: '您现在可以将现有知识库转换为使用知识流水线来处理文档',
descriptionChunk2: ' —— 这是一种更开放、更灵活的方式,可以访问我们市场中的插件。新的处理方式将应用到后续添加的所有文档。',
warning: '此操作无法撤销。',
confirm: {
title: '确认',
content: '此操作是永久性的。您将无法恢复到之前的方式。请确认转换。',
},
errorMessage: '转换数据集为 pipeline 失败',
successMessage: '成功将数据集转换为 pipeline',
errorMessage: '转换数据集为知识流水线失败',
successMessage: '成功将数据集转换为知识流水线',
},
}


+ 1
- 0
web/tailwind-common-config.ts 查看文件

@@ -135,6 +135,7 @@ const config = {
'billing-plan-title-bg': 'var(--color-billing-plan-title-bg)',
'billing-plan-card-premium-bg': 'var(--color-billing-plan-card-premium-bg)',
'billing-plan-card-enterprise-bg': 'var(--color-billing-plan-card-enterprise-bg)',
'knowledge-pipeline-creation-footer-bg': 'var(--color-knowledge-pipeline-creation-footer-bg)',
},
animation: {
'spin-slow': 'spin 2s linear infinite',

+ 1
- 0
web/themes/manual-dark.css 查看文件

@@ -72,4 +72,5 @@ html[data-theme="dark"] {
--color-billing-plan-title-bg: linear-gradient(95deg, #0A68FF 29.47%, #03F 105.31%);
--color-billing-plan-card-premium-bg: linear-gradient(180deg, #F90 0%, rgba(255, 153, 0, 0.00) 100%);
--color-billing-plan-card-enterprise-bg: linear-gradient(180deg, #03F 0%, rgba(0, 51, 255, 0.00) 100%);
--color-knowledge-pipeline-creation-footer-bg: linear-gradient(90deg, rgba(34, 34, 37, 1) 4.89%, rgba(0, 0, 0, 0) 100%);
}

+ 1
- 0
web/themes/manual-light.css 查看文件

@@ -72,4 +72,5 @@ html[data-theme="light"] {
--color-billing-plan-title-bg: linear-gradient(95deg, #03F 29.47%, #03F 105.31%);
--color-billing-plan-card-premium-bg: linear-gradient(180deg, #F90 0%, rgba(255, 153, 0, 0.00) 100%);
--color-billing-plan-card-enterprise-bg: linear-gradient(180deg, #03F 0%, rgba(0, 51, 255, 0.00) 100%);
--color-knowledge-pipeline-creation-footer-bg: linear-gradient(90deg, #FCFCFD 4.89%, rgba(255, 255, 255, 0.00) 100%);
}

正在加载...
取消
保存