add datasource empty node
parent
3b8d96f45c
commit
d76e37b018
@ -1,13 +1,13 @@
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '../base/button'
|
||||
import Button from '@/app/components/base/button'
|
||||
import PipelineScreenShot from './screenshot'
|
||||
import Confirm from '../base/confirm'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import { useConvertDatasetToPipeline } from '@/service/use-pipeline'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { useInvalid } from '@/service/use-base'
|
||||
import { datasetDetailQueryKeyPrefix } from '@/service/knowledge/use-dataset'
|
||||
import Toast from '../base/toast'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
|
||||
const Conversion = () => {
|
||||
const { t } = useTranslation()
|
||||
@ -0,0 +1 @@
|
||||
export * from './nodes'
|
||||
@ -0,0 +1,77 @@
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { generateNewNode } from '@/app/components/workflow/utils'
|
||||
import { CUSTOM_DATA_SOURCE_EMPTY_NODE } from '@/app/components/workflow/nodes/data-source-empty/constants'
|
||||
import { CUSTOM_NOTE_NODE } from '@/app/components/workflow/note-node/constants'
|
||||
import { NoteTheme } from '@/app/components/workflow/note-node/types'
|
||||
import type { NoteNodeType } from '@/app/components/workflow/note-node/types'
|
||||
import { CUSTOM_NODE } from '@/app/components/workflow/constants'
|
||||
|
||||
export const processNodesWithoutDataSource = (nodes: Node[]) => {
|
||||
if (!nodes || nodes.length === 0) return []
|
||||
|
||||
let leftNode
|
||||
let hasNoteBySystem
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i]
|
||||
if (node.type === CUSTOM_NOTE_NODE && node.data.noteBySystem)
|
||||
hasNoteBySystem = true
|
||||
|
||||
if (node.type !== CUSTOM_NODE)
|
||||
continue
|
||||
|
||||
if (node.data.type === BlockEnum.DataSource)
|
||||
return nodes
|
||||
|
||||
if (!leftNode)
|
||||
leftNode = node
|
||||
|
||||
if (node.position.x < leftNode.position.x)
|
||||
leftNode = node
|
||||
}
|
||||
|
||||
if (leftNode) {
|
||||
const { newNode } = generateNewNode({
|
||||
type: CUSTOM_DATA_SOURCE_EMPTY_NODE,
|
||||
data: {
|
||||
title: '',
|
||||
desc: '',
|
||||
type: BlockEnum.DataSourceEmpty,
|
||||
width: 240,
|
||||
},
|
||||
position: {
|
||||
x: leftNode.position.x - 500,
|
||||
y: leftNode.position.y,
|
||||
},
|
||||
})
|
||||
let newNoteNode
|
||||
if (!hasNoteBySystem) {
|
||||
newNoteNode = generateNewNode({
|
||||
type: CUSTOM_NOTE_NODE,
|
||||
data: {
|
||||
title: '',
|
||||
desc: '',
|
||||
type: '' as any,
|
||||
text: '{"root":{"children":[{"children":[{"detail":0,"format":1,"mode":"normal","style":"font-size: 14px;","text":"Get started with a blank pipeline","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":1,"textStyle":"font-size: 14px;"},{"children":[],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":1,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"A Knowledge Pipeline starts with Data Source as the starting node and ends with the knowledge base node. The general steps are: import documents from the data source → use extractor to extract document content → split and clean content into structured chunks → store in the knowledge base.","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""},{"children":[],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""},{"children":[{"children":[{"detail":0,"format":2,"mode":"normal","style":"","text":"Link to documentation","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"link","version":1,"textFormat":2,"rel":"noreferrer","target":null,"title":null,"url":"https://dify.ai"}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":2,"textStyle":""},{"children":[],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","format":"","indent":0,"type":"root","version":1,"textFormat":1,"textStyle":"font-size: 14px;"}}',
|
||||
theme: NoteTheme.blue,
|
||||
author: '',
|
||||
showAuthor: true,
|
||||
width: 240,
|
||||
height: 300,
|
||||
noteBySystem: true,
|
||||
} as NoteNodeType,
|
||||
position: {
|
||||
x: leftNode.position.x - 500,
|
||||
y: leftNode.position.y + 100,
|
||||
},
|
||||
}).newNode
|
||||
}
|
||||
return [
|
||||
newNode,
|
||||
...(newNoteNode ? [newNoteNode] : []),
|
||||
...nodes,
|
||||
]
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export const CUSTOM_DATA_SOURCE_EMPTY_NODE = 'custom-data-source-empty'
|
||||
@ -0,0 +1,20 @@
|
||||
import type { NodeDefault } from '../../types'
|
||||
import type { DataSourceEmptyNodeType } from './types'
|
||||
import { genNodeMetaData } from '@/app/components/workflow/utils'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
|
||||
const metaData = genNodeMetaData({
|
||||
sort: -1,
|
||||
type: BlockEnum.DataSourceEmpty,
|
||||
})
|
||||
const nodeDefault: NodeDefault<DataSourceEmptyNodeType> = {
|
||||
metaData,
|
||||
defaultValue: {},
|
||||
checkValid() {
|
||||
return {
|
||||
isValid: true,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default nodeDefault
|
||||
@ -0,0 +1,47 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import { produce } from 'immer'
|
||||
import type { OnSelectBlock } from '@/app/components/workflow/types'
|
||||
import { generateNewNode } from '@/app/components/workflow/utils'
|
||||
import { useNodesMetaData } from '@/app/components/workflow/hooks'
|
||||
|
||||
export const useReplaceDataSourceNode = (id: string) => {
|
||||
const store = useStoreApi()
|
||||
const { nodesMap: nodesMetaDataMap } = useNodesMetaData()
|
||||
|
||||
const handleReplaceNode = useCallback<OnSelectBlock>((
|
||||
type,
|
||||
toolDefaultValue,
|
||||
) => {
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
const emptyNodeIndex = nodes.findIndex(node => node.id === id)
|
||||
|
||||
if (emptyNodeIndex < 0) return
|
||||
const {
|
||||
defaultValue,
|
||||
} = nodesMetaDataMap![type]
|
||||
const emptyNode = nodes[emptyNodeIndex]
|
||||
const { newNode } = generateNewNode({
|
||||
data: {
|
||||
...(defaultValue as any),
|
||||
...(toolDefaultValue || {}),
|
||||
},
|
||||
position: {
|
||||
x: emptyNode.position.x,
|
||||
y: emptyNode.position.y,
|
||||
},
|
||||
})
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
draft[emptyNodeIndex] = newNode
|
||||
})
|
||||
setNodes(newNodes)
|
||||
}, [])
|
||||
|
||||
return {
|
||||
handleReplaceNode,
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { NodeProps } from 'reactflow'
|
||||
import { RiAddLine } from '@remixicon/react'
|
||||
import cn from '@/utils/classnames'
|
||||
import Button from '@/app/components/base/button'
|
||||
import BlockSelector from '@/app/components/workflow/block-selector'
|
||||
import { useReplaceDataSourceNode } from './hooks'
|
||||
|
||||
const DataSourceEmptyNode = ({ id }: NodeProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleReplaceNode } = useReplaceDataSourceNode(id)
|
||||
|
||||
const renderTrigger = useCallback(() => {
|
||||
return (
|
||||
<Button
|
||||
variant='primary'
|
||||
className='w-full'
|
||||
>
|
||||
<RiAddLine className='mr-1 h-4 w-4' />
|
||||
{t('workflow.nodes.dataSource.add')}
|
||||
</Button>
|
||||
)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'relative flex rounded-2xl border',
|
||||
'border-transparent',
|
||||
)}
|
||||
>
|
||||
<div className='absolute inset-[-2px] top-[-22px] z-[-1] rounded-[18px] bg-node-data-source-bg p-0.5 backdrop-blur-[6px]'>
|
||||
<div className='system-2xs-semibold-uppercase flex h-5 items-center px-2.5 text-text-tertiary'>
|
||||
{t('workflow.blocks.datasource')}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
'group relative shadow-xs',
|
||||
'rounded-[15px] border border-transparent',
|
||||
'w-[240px] bg-workflow-block-bg',
|
||||
)}
|
||||
>
|
||||
<div className={cn(
|
||||
'flex items-center rounded-t-2xl p-3',
|
||||
)}>
|
||||
<BlockSelector
|
||||
asChild
|
||||
onSelect={handleReplaceNode}
|
||||
trigger={renderTrigger}
|
||||
noBlocks
|
||||
noTools
|
||||
popupClassName='w-[320px]'
|
||||
placement='bottom-start'
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
crossAxis: 0,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(DataSourceEmptyNode)
|
||||
@ -0,0 +1,3 @@
|
||||
import type { CommonNodeType } from '@/app/components/workflow/types'
|
||||
|
||||
export type DataSourceEmptyNodeType = CommonNodeType
|
||||
Loading…
Reference in New Issue