Parcourir la source

feat: Refactor dataset pipeline creation components and add internationalization support

tags/2.0.0-beta.1
twwu il y a 6 mois
Parent
révision
4025cd0b46
36 fichiers modifiés avec 473 ajouts et 126 suppressions
  1. 3
    8
      web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx
  2. 0
    1
      web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/index.tsx
  3. 59
    20
      web/app/components/datasets/create-from-pipeline/create-options/create-from-scratch.tsx
  4. 7
    4
      web/app/components/datasets/create-from-pipeline/create-options/index.tsx
  5. 4
    1
      web/app/components/datasets/create-from-pipeline/header.tsx
  6. 35
    27
      web/app/components/datasets/create-from-pipeline/list/built-in-pipeline-list.tsx
  7. 34
    15
      web/app/components/datasets/create-from-pipeline/list/customized-list.tsx
  8. 11
    7
      web/app/components/datasets/create-from-pipeline/list/index.tsx
  9. 9
    8
      web/app/components/datasets/create-from-pipeline/list/template-card/edit-pipeline-info.tsx
  10. 8
    7
      web/app/components/datasets/create-from-pipeline/list/template-card/index.tsx
  11. 5
    20
      web/app/components/datasets/list/dataset-card/index.tsx
  12. 3
    0
      web/i18n/de-DE/dataset-pipeline.ts
  13. 25
    0
      web/i18n/en-US/dataset-pipeline.ts
  14. 3
    0
      web/i18n/es-ES/dataset-pipeline.ts
  15. 3
    0
      web/i18n/fa-IR/dataset-pipeline.ts
  16. 3
    0
      web/i18n/fr-FR/dataset-pipeline.ts
  17. 3
    0
      web/i18n/hi-IN/dataset-pipeline.ts
  18. 1
    0
      web/i18n/i18next-config.ts
  19. 3
    0
      web/i18n/it-IT/dataset-pipeline.ts
  20. 3
    0
      web/i18n/ja-JP/dataset-pipeline.ts
  21. 3
    0
      web/i18n/ko-KR/dataset-pipeline.ts
  22. 3
    0
      web/i18n/pl-PL/dataset-pipeline.ts
  23. 3
    0
      web/i18n/pt-BR/dataset-pipeline.ts
  24. 3
    0
      web/i18n/ro-RO/dataset-pipeline.ts
  25. 3
    0
      web/i18n/ru-RU/dataset-pipeline.ts
  26. 3
    0
      web/i18n/sl-SI/dataset-pipeline.ts
  27. 3
    0
      web/i18n/th-TH/dataset-pipeline.ts
  28. 3
    0
      web/i18n/tr-TR/dataset-pipeline.ts
  29. 3
    0
      web/i18n/uk-UA/dataset-pipeline.ts
  30. 3
    0
      web/i18n/vi-VN/dataset-pipeline.ts
  31. 25
    0
      web/i18n/zh-Hans/dataset-pipeline.ts
  32. 3
    0
      web/i18n/zh-Hant/dataset-pipeline.ts
  33. 54
    7
      web/models/datasets.ts
  34. 36
    0
      web/models/pipeline.ts
  35. 28
    1
      web/service/knowledge/use-create-dataset.ts
  36. 75
    0
      web/service/use-pipeline.ts

+ 3
- 8
web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx Voir le fichier

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 { getLocaleOnClient } from '@/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'
import { PipelineFill, PipelineLine } from '@/app/components/base/icons/src/public/pipeline' import { PipelineFill, PipelineLine } from '@/app/components/base/icons/src/public/pipeline'
import { Divider } from '@/app/components/base/icons/src/public/knowledge' import { Divider } from '@/app/components/base/icons/src/public/knowledge'
import { LanguagesSupported } from '@/i18n/language'
import { useGetDocLanguage } from '@/context/i18n'


export type IAppDetailLayoutProps = { export type IAppDetailLayoutProps = {
children: React.ReactNode children: React.ReactNode
documentCount, documentCount,
expand, expand,
}: IExtraInfoProps) => { }: IExtraInfoProps) => {
const locale = getLocaleOnClient()
const { t } = useTranslation() const { t } = useTranslation()
const docLanguage = useGetDocLanguage()


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
<div className='my-2 text-xs text-text-tertiary'>{t('common.datasetMenus.emptyTip')}</div> <div className='my-2 text-xs text-text-tertiary'>{t('common.datasetMenus.emptyTip')}</div>
<a <a
className='mt-2 inline-flex cursor-pointer items-center text-xs text-text-accent' className='mt-2 inline-flex cursor-pointer items-center text-xs text-text-accent'
href={
locale === LanguagesSupported[1]
? 'https://docs.dify.ai/v/zh-hans/guides/knowledge-base/integrate-knowledge-within-application'
: 'https://docs.dify.ai/guides/knowledge-base/integrate-knowledge-within-application'
}
href={`https://docs.dify.ai/${docLanguage}/guides/knowledge-base/integrate-knowledge-within-application`}
target='_blank' rel='noopener noreferrer' target='_blank' rel='noopener noreferrer'
> >
<RiBookOpenLine className='mr-1 text-text-accent' /> <RiBookOpenLine className='mr-1 text-text-accent' />

+ 0
- 1
web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/index.tsx Voir le fichier

message: t(status === DSLImportStatus.COMPLETED ? 'app.newApp.appCreated' : 'app.newApp.caution'), message: t(status === DSLImportStatus.COMPLETED ? 'app.newApp.appCreated' : 'app.newApp.caution'),
children: status === DSLImportStatus.COMPLETED_WITH_WARNINGS && t('app.newApp.appCreateDSLWarning'), children: status === DSLImportStatus.COMPLETED_WITH_WARNINGS && t('app.newApp.appCreateDSLWarning'),
}) })
localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1')
if (app_id) if (app_id)
await handleCheckPluginDependencies(app_id) await handleCheckPluginDependencies(app_id)
getRedirection(isCurrentWorkspaceEditor, { id: app_id!, mode: app_mode }, push) getRedirection(isCurrentWorkspaceEditor, { id: app_id!, mode: app_mode }, push)

+ 59
- 20
web/app/components/datasets/create-from-pipeline/create-options/create-from-scratch.tsx Voir le fichier

import React, { useCallback, useEffect, useRef, useState } from 'react'
import AppIcon from '@/app/components/base/app-icon' import AppIcon from '@/app/components/base/app-icon'
import type { AppIconSelection } from '@/app/components/base/app-icon-picker' import type { AppIconSelection } from '@/app/components/base/app-icon-picker'
import AppIconPicker from '@/app/components/base/app-icon-picker' import AppIconPicker from '@/app/components/base/app-icon-picker'
import Textarea from '@/app/components/base/textarea' import Textarea from '@/app/components/base/textarea'
import type { AppIconType } from '@/types/app' import type { AppIconType } from '@/types/app'
import { RiCloseLine } from '@remixicon/react' import { RiCloseLine } from '@remixicon/react'
import React, { useCallback, useRef, useState } from 'react'
import PermissionSelector from '../../settings/permission-selector' import PermissionSelector from '../../settings/permission-selector'
import type { CreateDatasetReq } from '@/models/datasets'
import { DatasetPermission } from '@/models/datasets' import { DatasetPermission } from '@/models/datasets'
import { useMembers } from '@/service/use-common' import { useMembers } from '@/service/use-common'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import { useCreateDataset } from '@/service/knowledge/use-create-dataset'
import type { Member } from '@/models/common'


type CreateFromScratchProps = { type CreateFromScratchProps = {
onClose: () => void
onCreate: () => void
onClose?: () => void
onCreate?: () => void
} }


const DEFAULT_APP_ICON: AppIconSelection = { const DEFAULT_APP_ICON: AppIconSelection = {
const [showAppIconPicker, setShowAppIconPicker] = useState(false) const [showAppIconPicker, setShowAppIconPicker] = useState(false)
const [selectedMemberIDs, setSelectedMemberIDs] = useState<string[]>([]) const [selectedMemberIDs, setSelectedMemberIDs] = useState<string[]>([])
const previousAppIcon = useRef<AppIconSelection>(DEFAULT_APP_ICON) const previousAppIcon = useRef<AppIconSelection>(DEFAULT_APP_ICON)
const [memberList, setMemberList] = useState<Member[]>([])


const { data: memberList } = useMembers()
const { data: members } = useMembers()

useEffect(() => {
if (members?.accounts)
setMemberList(members.accounts)
}, [members])


const handleAppNameChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => { const handleAppNameChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value const value = event.target.value
setPermission(value!) setPermission(value!)
}, []) }, [])


const { mutateAsync: createEmptyDataset } = useCreateDataset()

const handleCreate = useCallback(() => { const handleCreate = useCallback(() => {
if (!name) { if (!name) {
Toast.notify({ Toast.notify({
}) })
return return
} }
onCreate()
onClose()
}, [name, onCreate, onClose])
const request: CreateDatasetReq = {
name,
description,
icon_info: {
icon_type: appIcon.type,
icon: appIcon.type === 'image' ? appIcon.fileId : appIcon.icon,
icon_background: appIcon.type === 'image' ? undefined : appIcon.background,
icon_url: appIcon.type === 'image' ? appIcon.url : undefined,
},
permission,
}
// Handle permission
if (request.permission === DatasetPermission.partialMembers) {
const selectedMemberList = selectedMemberIDs.map((id) => {
return {
user_id: id,
role: memberList.find(member => member.id === id)?.role,
}
})
request.partial_member_list = selectedMemberList
}
createEmptyDataset(request)
onCreate?.()
onClose?.()
}, [name, permission, appIcon, description, createEmptyDataset, memberList, selectedMemberIDs, onCreate, onClose])


return ( return (
<div className='relative flex flex-col'> <div className='relative flex flex-col'>
{/* Header */} {/* Header */}
<div className='pb-3 pl-6 pr-14 pt-6'> <div className='pb-3 pl-6 pr-14 pt-6'>
<span className='title-2xl-semi-bold text-text-primary'> <span className='title-2xl-semi-bold text-text-primary'>
Create Knowledge
{t('datasetPipeline.creation.createKnowledge')}
</span> </span>
</div> </div>
<button <button
<div className='flex flex-col gap-y-5 px-6 py-3'> <div className='flex flex-col gap-y-5 px-6 py-3'>
<div className='flex items-end gap-x-3 self-stretch'> <div className='flex items-end gap-x-3 self-stretch'>
<div className='flex grow flex-col gap-y-1 pb-1'> <div className='flex grow flex-col gap-y-1 pb-1'>
<label className='system-sm-medium flex h-6 items-center text-text-secondary'>Knowledge name & icon</label>
<label className='system-sm-medium flex h-6 items-center text-text-secondary'>
{t('datasetPipeline.creation.knowledgeNameAndIcon')}
</label>
<Input <Input
onChange={handleAppNameChange} onChange={handleAppNameChange}
value={name} value={name}
placeholder='Please enter the name of the Knowledge Base'
placeholder={t('datasetPipeline.creation.knowledgeNameAndIconPlaceholder')}
/> />
</div> </div>
<AppIcon <AppIcon
/> />
</div> </div>
<div className='flex flex-col gap-y-1'> <div className='flex flex-col gap-y-1'>
<label className='system-sm-medium flex h-6 items-center text-text-secondary'>Knowledge description</label>
<label className='system-sm-medium flex h-6 items-center text-text-secondary'>
{t('datasetPipeline.creation.knowledgeDescription')}
</label>
<Textarea <Textarea
onChange={handleDescriptionChange} onChange={handleDescriptionChange}
value={description} value={description}
placeholder='Describe what is in this Knowledge Base. A detailed description allows AI to access the content of the dataset more accurately. If empty, Dify will use the default hit strategy. (Optional)'
placeholder={t('datasetPipeline.creation.knowledgeDescriptionPlaceholder')}
/> />
</div> </div>
<div className='flex flex-col gap-y-1'> <div className='flex flex-col gap-y-1'>
<label className='system-sm-medium flex h-6 items-center text-text-secondary'>Permissions</label>
<PermissionSelector
permission={permission}
value={selectedMemberIDs}
onChange={handlePermissionChange}
onMemberSelect={setSelectedMemberIDs}
memberList={memberList?.accounts || []}
/>
<label className='system-sm-medium flex h-6 items-center text-text-secondary'>
{t('datasetPipeline.creation.knowledgePermissions')}
</label>
<PermissionSelector
permission={permission}
value={selectedMemberIDs}
onChange={handlePermissionChange}
onMemberSelect={setSelectedMemberIDs}
memberList={memberList}
/>
</div> </div>
</div> </div>
{/* Actions */} {/* Actions */}

+ 7
- 4
web/app/components/datasets/create-from-pipeline/create-options/index.tsx Voir le fichier

import { useRouter, useSearchParams } from 'next/navigation' import { useRouter, useSearchParams } from 'next/navigation'
import CreateFromDSLModal, { CreateFromDSLModalTab } from './create-from-dsl-modal' import CreateFromDSLModal, { CreateFromDSLModalTab } from './create-from-dsl-modal'
import { useProviderContextSelector } from '@/context/provider-context' import { useProviderContextSelector } from '@/context/provider-context'
import { useTranslation } from 'react-i18next'


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

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


<div className='flex items-center gap-x-3 px-16 py-2'> <div className='flex items-center gap-x-3 px-16 py-2'>
<Item <Item
Icon={RiAddCircleFill} Icon={RiAddCircleFill}
title='Create from scratch'
description='Blank knowledge pipeline'
title={t('datasetPipeline.creation.createFromScratch.title')}
description={t('datasetPipeline.creation.createFromScratch.description')}
onClick={openCreateFromScratch} onClick={openCreateFromScratch}
/> />
<Item <Item
Icon={RiFileUploadLine} Icon={RiFileUploadLine}
title='Import'
description='Import from a DSL file'
title={t('datasetPipeline.creation.ImportDSL.title')}
description={t('datasetPipeline.creation.ImportDSL.description')}
onClick={openImportFromDSL} onClick={openImportFromDSL}
/> />
<Modal <Modal

+ 4
- 1
web/app/components/datasets/create-from-pipeline/header.tsx Voir le fichier

import React from 'react' import React from 'react'
import { RiArrowLeftLine } from '@remixicon/react' import { RiArrowLeftLine } from '@remixicon/react'
import Button from '../../base/button' import Button from '../../base/button'
import { useTranslation } from 'react-i18next'


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

return ( return (
<div className='system-md-semibold relative flex px-16 pb-2 pt-5 text-text-primary'> <div className='system-md-semibold relative flex px-16 pb-2 pt-5 text-text-primary'>
<span>Create knowledge pipeline</span>
<span>{t('datasetPipeline.creation.title')}</span>
<a <a
className='absolute bottom-0 left-5' className='absolute bottom-0 left-5'
href='/datasets' href='/datasets'

+ 35
- 27
web/app/components/datasets/create-from-pipeline/list/built-in-pipeline-list.tsx Voir le fichier

import { ChunkingMode } from '@/models/datasets'
import { usePipelineTemplateList } from '@/service/use-pipeline'
import TemplateCard from './template-card' import TemplateCard from './template-card'

export type Pipeline = {
id: string
name: string
icon_type: 'emoji' | 'image'
icon?: string
icon_background?: string
file_id?: string
url?: string
description: string
doc_form: ChunkingMode
}
import { ChunkingMode } from '@/models/datasets'
import type { PipelineTemple } from '@/models/pipeline'


const BuiltInPipelineList = () => { const BuiltInPipelineList = () => {
const mockData: Pipeline[] = [{
// TODO: remove mock data
const mockData: PipelineTemple[] = [{
id: '1', id: '1',
name: 'Pipeline 1', name: 'Pipeline 1',
description: 'This is a description of Pipeline 1. When use the general chunking mode, the chunks retrieved and recalled are the same. When use the general chunking mode, the chunks retrieved and recalled are the same.', description: 'This is a description of Pipeline 1. When use the general chunking mode, the chunks retrieved and recalled are the same. When use the general chunking mode, the chunks retrieved and recalled are the same.',
icon_type: 'emoji',
icon: '🤖',
icon_background: '#F0FDF9',
icon_info: {
icon: '🤖',
icon_background: '#F0FDF9',
icon_type: 'emoji',
},
doc_form: ChunkingMode.text, doc_form: ChunkingMode.text,
position: 0,
}, { }, {
id: '2', id: '2',
name: 'Pipeline 2', name: 'Pipeline 2',
description: 'This is a description of Pipeline 2. When use the general chunking mode, the chunks retrieved and recalled are the same.', description: 'This is a description of Pipeline 2. When use the general chunking mode, the chunks retrieved and recalled are the same.',
icon_type: 'emoji',
icon: '🏖️',
icon_background: '#FFF4ED',
icon_info: {
icon: '🏖️',
icon_background: '#FFF4ED',
icon_type: 'emoji',
},
doc_form: ChunkingMode.parentChild, doc_form: ChunkingMode.parentChild,
position: 1,
}, { }, {
id: '3', id: '3',
name: 'Pipeline 3', name: 'Pipeline 3',
description: 'This is a description of Pipeline 3', description: 'This is a description of Pipeline 3',
icon_type: 'emoji',
icon: '🚀',
icon_background: '#FEFBE8',
icon_info: {
icon: '🚀',
icon_background: '#FEFBE8',
icon_type: 'emoji',
},
doc_form: ChunkingMode.qa, doc_form: ChunkingMode.qa,
position: 2,
}, { }, {
id: '4', id: '4',
name: 'Pipeline 4', name: 'Pipeline 4',
description: 'This is a description of Pipeline 4', description: 'This is a description of Pipeline 4',
icon_type: 'emoji',
icon: '🍯',
icon_background: '#F5F3FF',
icon_info: {
icon: '🍯',
icon_background: '#F5F3FF',
icon_type: 'emoji',
},
doc_form: ChunkingMode.graph, doc_form: ChunkingMode.graph,
position: 3,
}] }]
const { data: pipelineList, isLoading } = usePipelineTemplateList({ type: 'built-in' })
const list = pipelineList?.pipelines || mockData

if (isLoading || !list)
return null


return ( return (
<div className='grid grow grid-cols-1 gap-3 overflow-y-auto px-16 pt-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'> <div className='grid grow grid-cols-1 gap-3 overflow-y-auto px-16 pt-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'>
{mockData.map((pipeline, index) => (
{list.map((pipeline, index) => (
<TemplateCard <TemplateCard
key={index} key={index}
pipeline={pipeline} pipeline={pipeline}

+ 34
- 15
web/app/components/datasets/create-from-pipeline/list/customized-list.tsx Voir le fichier

import { ChunkingMode } from '@/models/datasets' import { ChunkingMode } from '@/models/datasets'
import type { Pipeline } from './built-in-pipeline-list'
import TemplateCard from './template-card' import TemplateCard from './template-card'
import { usePipelineTemplateList } from '@/service/use-pipeline'
import type { PipelineTemple } from '@/models/pipeline'


const CustomizedList = () => { const CustomizedList = () => {
const mockData: Pipeline[] = [{
const mockData: PipelineTemple[] = [{
id: '1', id: '1',
name: 'Pipeline 1', name: 'Pipeline 1',
description: 'This is a description of Pipeline 1. When use the general chunking mode, the chunks retrieved and recalled are the same. When use the general chunking mode, the chunks retrieved and recalled are the same.', description: 'This is a description of Pipeline 1. When use the general chunking mode, the chunks retrieved and recalled are the same. When use the general chunking mode, the chunks retrieved and recalled are the same.',
icon_type: 'emoji',
icon: '🤖',
icon_background: '#F0FDF9',
icon_info: {
icon: '🤖',
icon_background: '#F0FDF9',
icon_type: 'emoji',
},
doc_form: ChunkingMode.text, doc_form: ChunkingMode.text,
position: 0,
}, { }, {
id: '2', id: '2',
name: 'Pipeline 2', name: 'Pipeline 2',
description: 'This is a description of Pipeline 2. When use the general chunking mode, the chunks retrieved and recalled are the same.', description: 'This is a description of Pipeline 2. When use the general chunking mode, the chunks retrieved and recalled are the same.',
icon_type: 'emoji',
icon: '🏖️',
icon_background: '#FFF4ED',
icon_info: {
icon: '🏖️',
icon_background: '#FFF4ED',
icon_type: 'emoji',
},
doc_form: ChunkingMode.parentChild, doc_form: ChunkingMode.parentChild,
position: 1,
}, { }, {
id: '3', id: '3',
name: 'Pipeline 3', name: 'Pipeline 3',
description: 'This is a description of Pipeline 3', description: 'This is a description of Pipeline 3',
icon_type: 'emoji',
icon: '🚀',
icon_background: '#FEFBE8',
icon_info: {
icon: '🚀',
icon_background: '#FEFBE8',
icon_type: 'emoji',
},
doc_form: ChunkingMode.qa, doc_form: ChunkingMode.qa,
position: 2,
}, { }, {
id: '4', id: '4',
name: 'Pipeline 4', name: 'Pipeline 4',
description: 'This is a description of Pipeline 4', description: 'This is a description of Pipeline 4',
icon_type: 'emoji',
icon: '🍯',
icon_background: '#F5F3FF',
icon_info: {
icon: '🍯',
icon_background: '#F5F3FF',
icon_type: 'emoji',
},
doc_form: ChunkingMode.graph, doc_form: ChunkingMode.graph,
position: 3,
}] }]


const { data: pipelineList, isLoading } = usePipelineTemplateList({ type: 'customized' })
const list = pipelineList?.pipelines || mockData

if (isLoading || !list)
return null

return ( return (
<div className='grid grow grid-cols-1 gap-3 overflow-y-auto px-16 pt-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'> <div className='grid grow grid-cols-1 gap-3 overflow-y-auto px-16 pt-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'>
{mockData.map((pipeline, index) => (
{list.map((pipeline, index) => (
<TemplateCard <TemplateCard
key={index} key={index}
pipeline={pipeline} pipeline={pipeline}

+ 11
- 7
web/app/components/datasets/create-from-pipeline/list/index.tsx Voir le fichier

import { useCallback, useState } from 'react'
import { useCallback, useMemo, useState } from 'react'
import Tab from './tab' import Tab from './tab'
import BuiltInPipelineList from './built-in-pipeline-list' import BuiltInPipelineList from './built-in-pipeline-list'
import CustomizedList from './customized-list' import CustomizedList from './customized-list'

const OPTIONS = [
{ value: 'built-in', label: 'Built-in Pipeline' },
{ value: 'customized', label: 'Customized' },
]
import { useTranslation } from 'react-i18next'


const List = () => { const List = () => {
const { t } = useTranslation()
const [activeTab, setActiveTab] = useState('built-in') 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) => { const handleTabChange = useCallback((tab: string) => {
setActiveTab(tab) setActiveTab(tab)
}, []) }, [])
<Tab <Tab
activeTab={activeTab} activeTab={activeTab}
handleTabChange={handleTabChange} handleTabChange={handleTabChange}
options={OPTIONS}
options={options}
/> />
{ {
activeTab === 'built-in' && <BuiltInPipelineList /> activeTab === 'built-in' && <BuiltInPipelineList />

+ 9
- 8
web/app/components/datasets/create-from-pipeline/list/template-card/edit-pipeline-info.tsx Voir le fichier

import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import type { Pipeline } from '../built-in-pipeline-list'
import type { PipelineTemple } from '@/models/pipeline'


type EditPipelineInfoProps = { type EditPipelineInfoProps = {
onClose: () => void onClose: () => void
onSave: () => void onSave: () => void
pipeline: Pipeline
pipeline: PipelineTemple
} }


const EditPipelineInfo = ({ const EditPipelineInfo = ({
}: EditPipelineInfoProps) => { }: EditPipelineInfoProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const [name, setName] = useState(pipeline.name) const [name, setName] = useState(pipeline.name)
const iconInfo = pipeline.icon_info
const [appIcon, setAppIcon] = useState<AppIconSelection>( const [appIcon, setAppIcon] = useState<AppIconSelection>(
pipeline.icon_type === 'image'
? { type: 'image' as const, url: pipeline.url || '', fileId: pipeline.file_id || '' }
: { type: 'emoji' as const, icon: pipeline.icon || '', background: pipeline.icon_background || '' },
iconInfo.icon_type === 'image'
? { type: 'image' as const, url: iconInfo.icon_url || '', fileId: iconInfo.icon || '' }
: { type: 'emoji' as const, icon: iconInfo.icon || '', background: iconInfo.icon_background || '' },
) )
const [description, setDescription] = useState(pipeline.description) const [description, setDescription] = useState(pipeline.description)
const [showAppIconPicker, setShowAppIconPicker] = useState(false) const [showAppIconPicker, setShowAppIconPicker] = useState(false)
const previousAppIcon = useRef<AppIconSelection>( const previousAppIcon = useRef<AppIconSelection>(
pipeline.icon_type === 'image'
? { type: 'image' as const, url: pipeline.url || '', fileId: pipeline.file_id || '' }
: { type: 'emoji' as const, icon: pipeline.icon || '', background: pipeline.icon_background || '' },
iconInfo.icon_type === 'image'
? { type: 'image' as const, url: iconInfo.icon_url || '', fileId: iconInfo.icon || '' }
: { type: 'emoji' as const, icon: iconInfo.icon || '', background: iconInfo.icon_background || '' },
) )


const handleAppNameChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => { const handleAppNameChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {

+ 8
- 7
web/app/components/datasets/create-from-pipeline/list/template-card/index.tsx Voir le fichier

import React, { useCallback, useState } from 'react' import React, { useCallback, useState } from 'react'
import type { Pipeline } from '../built-in-pipeline-list'
import AppIcon from '@/app/components/base/app-icon' import AppIcon from '@/app/components/base/app-icon'
import { DOC_FORM_ICON, DOC_FORM_TEXT } from '../../../list/dataset-card'
import { General } from '@/app/components/base/icons/src/public/knowledge' import { General } from '@/app/components/base/icons/src/public/knowledge'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import Operations from './operations' import Operations from './operations'
import Modal from '@/app/components/base/modal' import Modal from '@/app/components/base/modal'
import EditPipelineInfo from './edit-pipeline-info' import EditPipelineInfo from './edit-pipeline-info'
import type { PipelineTemple } from '@/models/pipeline'
import { DOC_FORM_ICON, DOC_FORM_TEXT } from '@/models/datasets'


type TemplateCardProps = { type TemplateCardProps = {
pipeline: Pipeline
pipeline: PipelineTemple
showMoreOperations?: boolean showMoreOperations?: boolean
} }


}, []) }, [])


const Icon = DOC_FORM_ICON[pipeline.doc_form] || General const Icon = DOC_FORM_ICON[pipeline.doc_form] || General
const iconInfo = pipeline.icon_info


return ( 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'> <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'>
<div className='relative shrink-0'> <div className='relative shrink-0'>
<AppIcon <AppIcon
size='large' size='large'
iconType={pipeline.icon_type}
icon={pipeline.icon_type === 'image' ? pipeline.file_id : pipeline.icon}
background={pipeline.icon_type === 'image' ? undefined : pipeline.icon_background}
imageUrl={pipeline.icon_type === 'image' ? pipeline.url : undefined}
iconType={iconInfo.icon_type}
icon={iconInfo.icon}
background={iconInfo.icon_type === 'image' ? undefined : iconInfo.icon_background}
imageUrl={iconInfo.icon_type === 'image' ? iconInfo.icon_url : undefined}
/> />
<div className='absolute -bottom-1 -right-1 z-10'> <div className='absolute -bottom-1 -right-1 z-10'>
<Icon className='size-4' /> <Icon className='size-4' />

+ 5
- 20
web/app/components/datasets/list/dataset-card/index.tsx Voir le fichier

import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import type { DataSet } from '@/models/datasets' import type { DataSet } from '@/models/datasets'
import { ChunkingMode } from '@/models/datasets'
import { useAppContext } from '@/context/app-context'
import { ExternalKnowledgeBase, General, Graph, ParentChild, Qa } from '@/app/components/base/icons/src/public/knowledge'
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
import { General } from '@/app/components/base/icons/src/public/knowledge'
import { useKnowledge } from '@/hooks/use-knowledge' import { useKnowledge } from '@/hooks/use-knowledge'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import type { Tag } from '@/app/components/base/tag-management/constant' import type { Tag } from '@/app/components/base/tag-management/constant'
import Operations from './operations' 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, DOC_FORM_TEXT } from '@/models/datasets'


const EXTERNAL_PROVIDER = 'external' const EXTERNAL_PROVIDER = 'external'


export const DOC_FORM_ICON: Record<ChunkingMode | 'external', React.ComponentType<{ className: string }>> = {
[ChunkingMode.text]: General,
[ChunkingMode.qa]: Qa,
[ChunkingMode.parentChild]: ParentChild,
[ChunkingMode.graph]: Graph,
external: ExternalKnowledgeBase,
}

export const DOC_FORM_TEXT: Record<ChunkingMode, string> = {
[ChunkingMode.text]: 'general',
[ChunkingMode.qa]: 'qa',
[ChunkingMode.parentChild]: 'parentChild',
[ChunkingMode.graph]: 'graph',
}

export type DatasetCardProps = {
type DatasetCardProps = {
dataset: DataSet dataset: DataSet
onSuccess?: () => void onSuccess?: () => void
} }
const { t } = useTranslation() const { t } = useTranslation()
const { push } = useRouter() const { push } = useRouter()


const { isCurrentWorkspaceDatasetOperator } = useAppContext()
const isCurrentWorkspaceDatasetOperator = useAppContextWithSelector(state => state.isCurrentWorkspaceDatasetOperator)
const [tags, setTags] = useState<Tag[]>(dataset.tags) const [tags, setTags] = useState<Tag[]>(dataset.tags)
const tagSelectorRef = useRef<HTMLDivElement>(null) const tagSelectorRef = useRef<HTMLDivElement>(null)
const isHoveringTagSelector = useHover(tagSelectorRef) const isHoveringTagSelector = useHover(tagSelectorRef)

+ 3
- 0
web/i18n/de-DE/dataset-pipeline.ts Voir le fichier

const translation = {}

export default translation

+ 25
- 0
web/i18n/en-US/dataset-pipeline.ts Voir le fichier

const translation = {
creation: {
title: 'Create knowledge pipeline',
createFromScratch: {
title: 'Create from scratch',
description: 'Blank knowledge pipeline',
},
ImportDSL: {
title: 'Import',
description: 'Import from a DSL file',
},
createKnowledge: 'Create Knowledge',
knowledgeNameAndIcon: 'Knowledge name & icon',
knowledgeNameAndIconPlaceholder: 'Please enter the name of the Knowledge Base',
knowledgeDescription: 'Knowledge description',
knowledgeDescriptionPlaceholder: 'Describe what is in this Knowledge Base. A detailed description allows AI to access the content of the dataset more accurately. If empty, Dify will use the default hit strategy. (Optional)',
knowledgePermissions: 'Permissions',
},
tabs: {
builtInPipeline: 'Built-in pipeline',
customized: 'Customized',
},
}

export default translation

+ 3
- 0
web/i18n/es-ES/dataset-pipeline.ts Voir le fichier

const translation = {}

export default translation

+ 3
- 0
web/i18n/fa-IR/dataset-pipeline.ts Voir le fichier

const translation = {}

export default translation

+ 3
- 0
web/i18n/fr-FR/dataset-pipeline.ts Voir le fichier

const translation = {}

export default translation

+ 3
- 0
web/i18n/hi-IN/dataset-pipeline.ts Voir le fichier

const translation = {}

export default translation

+ 1
- 0
web/i18n/i18next-config.ts Voir le fichier

datasetHitTesting: require(`./${lang}/dataset-hit-testing`).default, datasetHitTesting: require(`./${lang}/dataset-hit-testing`).default,
datasetSettings: require(`./${lang}/dataset-settings`).default, datasetSettings: require(`./${lang}/dataset-settings`).default,
datasetCreation: require(`./${lang}/dataset-creation`).default, datasetCreation: require(`./${lang}/dataset-creation`).default,
datasetPipeline: require(`./${lang}/dataset-pipeline`).default,
explore: require(`./${lang}/explore`).default, explore: require(`./${lang}/explore`).default,
billing: require(`./${lang}/billing`).default, billing: require(`./${lang}/billing`).default,
custom: require(`./${lang}/custom`).default, custom: require(`./${lang}/custom`).default,

+ 3
- 0
web/i18n/it-IT/dataset-pipeline.ts Voir le fichier

const translation = {}

export default translation

+ 3
- 0
web/i18n/ja-JP/dataset-pipeline.ts Voir le fichier

const translation = {}

export default translation

+ 3
- 0
web/i18n/ko-KR/dataset-pipeline.ts Voir le fichier

const translation = {}

export default translation

+ 3
- 0
web/i18n/pl-PL/dataset-pipeline.ts Voir le fichier

const translation = {}

export default translation

+ 3
- 0
web/i18n/pt-BR/dataset-pipeline.ts Voir le fichier

const translation = {}

export default translation

+ 3
- 0
web/i18n/ro-RO/dataset-pipeline.ts Voir le fichier

const translation = {}

export default translation

+ 3
- 0
web/i18n/ru-RU/dataset-pipeline.ts Voir le fichier

const translation = {}

export default translation

+ 3
- 0
web/i18n/sl-SI/dataset-pipeline.ts Voir le fichier

const translation = {}

export default translation

+ 3
- 0
web/i18n/th-TH/dataset-pipeline.ts Voir le fichier

const translation = {}

export default translation

+ 3
- 0
web/i18n/tr-TR/dataset-pipeline.ts Voir le fichier

const translation = {}

export default translation

+ 3
- 0
web/i18n/uk-UA/dataset-pipeline.ts Voir le fichier

const translation = {}

export default translation

+ 3
- 0
web/i18n/vi-VN/dataset-pipeline.ts Voir le fichier

const translation = {}

export default translation

+ 25
- 0
web/i18n/zh-Hans/dataset-pipeline.ts Voir le fichier

const translation = {
creation: {
title: '创建知识库流水线',
createFromScratch: {
title: '从零开始创建',
description: '空白知识库流水线',
},
ImportDSL: {
title: '导入',
description: '从 DSL 文件导入',
},
createKnowledge: '创建知识库',
knowledgeNameAndIcon: '知识库名称和图标',
knowledgeNameAndIconPlaceholder: '请输入知识库名称',
knowledgeDescription: '知识库描述',
knowledgeDescriptionPlaceholder: '描述知识库中的内容。详细的描述可以让 AI 更准确地访问数据集的内容。如果为空,Dify 将使用默认的命中策略。(可选)',
knowledgePermissions: '权限',
},
tabs: {
builtInPipeline: '内置流水线',
customized: '自定义',
},
}

export default translation

+ 3
- 0
web/i18n/zh-Hant/dataset-pipeline.ts Voir le fichier

const translation = {}

export default translation

+ 54
- 7
web/models/datasets.ts Voir le fichier

import type { IndexingType } from '@/app/components/datasets/create/step-two' import type { IndexingType } from '@/app/components/datasets/create/step-two'
import type { MetadataFilteringVariableType } from '@/app/components/workflow/nodes/knowledge-retrieval/types' import type { MetadataFilteringVariableType } from '@/app/components/workflow/nodes/knowledge-retrieval/types'
import type { MetadataItemWithValue } from '@/app/components/datasets/metadata/types' import type { MetadataItemWithValue } from '@/app/components/datasets/metadata/types'
import { ExternalKnowledgeBase, General, Graph, ParentChild, Qa } from '@/app/components/base/icons/src/public/knowledge'


export enum DataSourceType { export enum DataSourceType {
FILE = 'upload_file', FILE = 'upload_file',
name: string name: string
} }


export type IconInfo = {
icon: string
icon_background?: string
icon_type: AppIconType
icon_url?: string
}

export type DataSet = { export type DataSet = {
id: string id: string
name: string name: string
indexing_status: DocumentIndexingStatus indexing_status: DocumentIndexingStatus
icon_info: {
icon: string
icon_background?: string
icon_type: AppIconType
icon_url?: string
}
icon_info: IconInfo
description: string description: string
permission: DatasetPermission permission: DatasetPermission
data_source_type: DataSourceType data_source_type: DataSourceType


export type DocumentReq = { export type DocumentReq = {
original_document_id?: string original_document_id?: string
indexing_technique?: string
indexing_technique?: IndexingType
doc_form: ChunkingMode doc_form: ChunkingMode
doc_language: string doc_language: string
process_rule: ProcessRule process_rule: ProcessRule
job_id: string job_id: string
job_status: string job_status: string
} }

export const DOC_FORM_ICON: Record<ChunkingMode | 'external', React.ComponentType<{ className: string }>> = {
[ChunkingMode.text]: General,
[ChunkingMode.qa]: Qa,
[ChunkingMode.parentChild]: ParentChild,
[ChunkingMode.graph]: Graph,
external: ExternalKnowledgeBase,
}

export const DOC_FORM_TEXT: Record<ChunkingMode, string> = {
[ChunkingMode.text]: 'general',
[ChunkingMode.qa]: 'qa',
[ChunkingMode.parentChild]: 'parentChild',
[ChunkingMode.graph]: 'graph',
}

export type CreateDatasetReq = {
name: string
description: string
icon_info: IconInfo
doc_form?: ChunkingMode
permission: DatasetPermission
partial_member_list?: {
user_id: string
role?: 'owner' | 'admin' | 'editor' | 'normal' | 'dataset_operator'
}[]
indexing_technique?: IndexingType
retrieval_mode?: RetrievalConfig
embedding_model?: string
embedding_model_provider?: string
}

export type CreateDatasetResponse = {
id: string
name: string
description: string
permission: DatasetPermission
data_source_type: DataSourceType
indexing_technique: IndexingType
created_by: string
created_at: number
updated_by: string
updated_at: number
}

+ 36
- 0
web/models/pipeline.ts Voir le fichier

import type { ChunkingMode, IconInfo } from './datasets'

export type PipelineTemplateListParams = {
type: 'built-in' | 'customized'
}

export type PipelineTemple = {
id: string
name: string
icon_info: IconInfo
description: string
position: number
doc_form: ChunkingMode
}

export type PipelineTemplateListResponse = {
pipelines: PipelineTemple[]
}

export type PipelineTemplateByIdResponse = {
name: string
icon_info: IconInfo
description: string
export_data: string
}

export type UpdatePipelineInfoPayload = {
pipelineId: string
name: string
icon_info: IconInfo
description: string
}

export type ExportPipelineDSLResponse = {
data: string
}

+ 28
- 1
web/service/knowledge/use-create-dataset.ts Voir le fichier

import { useMutation } from '@tanstack/react-query' import { useMutation } from '@tanstack/react-query'
import { createDocument, createFirstDocument, fetchDefaultProcessRule, fetchFileIndexingEstimate } from '../datasets' import { createDocument, createFirstDocument, fetchDefaultProcessRule, fetchFileIndexingEstimate } from '../datasets'
import type { IndexingType } from '@/app/components/datasets/create/step-two' import type { IndexingType } from '@/app/components/datasets/create/step-two'
import type { ChunkingMode, CrawlOptions, CrawlResultItem, CreateDocumentReq, CustomFile, DataSourceType, FileIndexingEstimateResponse, IndexingEstimateParams, NotionInfo, ProcessRule, ProcessRuleResponse, createDocumentResponse } from '@/models/datasets'
import type {
ChunkingMode,
CrawlOptions,
CrawlResultItem,
CreateDatasetReq,
CreateDatasetResponse,
CreateDocumentReq,
CustomFile,
DataSourceType,
FileIndexingEstimateResponse,
IndexingEstimateParams,
NotionInfo,
ProcessRule,
ProcessRuleResponse,
createDocumentResponse,
} from '@/models/datasets'
import type { DataSourceProvider, NotionPage } from '@/models/common' import type { DataSourceProvider, NotionPage } from '@/models/common'
import { post } from '../base'


export const getNotionInfo = ( export const getNotionInfo = (
notionPages: NotionPage[], notionPages: NotionPage[],
...mutationOptions, ...mutationOptions,
}) })
} }

export const useCreateDataset = (
mutationOptions: MutationOptions<CreateDatasetResponse, Error, CreateDatasetReq> = {},
) => {
return useMutation({
mutationFn: (req: CreateDatasetReq) => {
return post<CreateDatasetResponse>('/datasets', { body: req })
},
...mutationOptions,
})
}

+ 75
- 0
web/service/use-pipeline.ts Voir le fichier

import { useMutation, useQuery } from '@tanstack/react-query'
import { del, get, patch } from './base'
import type {
ExportPipelineDSLResponse,
PipelineTemplateByIdResponse,
PipelineTemplateListParams,
PipelineTemplateListResponse,
UpdatePipelineInfoPayload,
} from '@/models/pipeline'

const NAME_SPACE = 'pipeline'

export const usePipelineTemplateList = (params: PipelineTemplateListParams) => {
return useQuery<PipelineTemplateListResponse>({
queryKey: [NAME_SPACE, 'template', 'list'],
queryFn: () => {
return get<PipelineTemplateListResponse>('/rag/pipeline/template', { params })
},
})
}

export const usePipelineTemplateById = (templateId: string) => {
return useQuery<PipelineTemplateByIdResponse>({
queryKey: [NAME_SPACE, 'template', templateId],
queryFn: () => {
return get<PipelineTemplateByIdResponse>(`/rag/pipeline/template/${templateId}`)
},
})
}

export const useUpdatePipelineInfo = ({
onSuccess,
onError,
}: {
onSuccess?: () => void
onError?: (error: any) => void
}) => {
return useMutation({
mutationKey: [NAME_SPACE, 'template', 'update'],
mutationFn: (payload: UpdatePipelineInfoPayload) => {
const { pipelineId, ...rest } = payload
return patch(`/rag/pipeline/${pipelineId}`, {
body: rest,
})
},
onSuccess,
onError,
})
}

export const useDeletePipeline = ({
onSuccess,
onError,
}: {
onSuccess?: () => void
onError?: (error: any) => void
}) => {
return useMutation({
mutationKey: [NAME_SPACE, 'template', 'delete'],
mutationFn: (pipelineId: string) => {
return del(`/rag/pipeline/${pipelineId}`)
},
onSuccess,
onError,
})
}

export const useExportPipelineDSL = (pipelineId: string) => {
return useQuery<ExportPipelineDSLResponse>({
queryKey: [NAME_SPACE, 'template', 'export', pipelineId],
queryFn: () => {
return get<ExportPipelineDSLResponse>(`/rag/pipeline/${pipelineId}`)
},
})
}

Chargement…
Annuler
Enregistrer