checklist & datasource icon

This commit is contained in:
zxhlyh 2025-05-23 14:25:38 +08:00
parent 693107a6c8
commit 720ce79901
14 changed files with 108 additions and 58 deletions

View File

@ -15,6 +15,7 @@ type TagInputProps = {
customizedConfirmKey?: 'Enter' | 'Tab' customizedConfirmKey?: 'Enter' | 'Tab'
isInWorkflow?: boolean isInWorkflow?: boolean
placeholder?: string placeholder?: string
inputClassName?: string
} }
const TagInput: FC<TagInputProps> = ({ const TagInput: FC<TagInputProps> = ({
@ -25,6 +26,7 @@ const TagInput: FC<TagInputProps> = ({
customizedConfirmKey = 'Enter', customizedConfirmKey = 'Enter',
isInWorkflow, isInWorkflow,
placeholder, placeholder,
inputClassName,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { notify } = useToastContext() const { notify } = useToastContext()
@ -93,15 +95,18 @@ const TagInput: FC<TagInputProps> = ({
<div className={cn('group/tag-add mt-1 flex items-center gap-x-0.5', !isSpecialMode ? 'rounded-md border border-dashed border-divider-deep px-1.5' : '')}> <div className={cn('group/tag-add mt-1 flex items-center gap-x-0.5', !isSpecialMode ? 'rounded-md border border-dashed border-divider-deep px-1.5' : '')}>
{!isSpecialMode && !focused && <RiAddLine className='h-3.5 w-3.5 text-text-placeholder group-hover/tag-add:text-text-secondary' />} {!isSpecialMode && !focused && <RiAddLine className='h-3.5 w-3.5 text-text-placeholder group-hover/tag-add:text-text-secondary' />}
<AutosizeInput <AutosizeInput
inputClassName={cn('appearance-none text-text-primary caret-[#295EFF] outline-none placeholder:text-text-placeholder group-hover/tag-add:placeholder:text-text-secondary', isSpecialMode ? 'bg-transparent' : '')} inputClassName={cn(
'appearance-none text-text-primary caret-[#295EFF] outline-none placeholder:text-text-placeholder group-hover/tag-add:placeholder:text-text-secondary',
isSpecialMode ? 'bg-transparent' : '',
inputClassName,
)}
className={cn( className={cn(
!isInWorkflow && 'max-w-[300px]', !isInWorkflow && 'max-w-[300px]',
isInWorkflow && 'max-w-[146px]', isInWorkflow && 'max-w-[146px]',
` 'system-xs-regular overflow-hidden rounded-md py-1',
system-xs-regular overflow-hidden rounded-md py-1 isSpecialMode && 'border border-transparent px-1.5',
${isSpecialMode && 'border border-transparent px-1.5'} focused && isSpecialMode && 'border-dashed border-divider-deep',
${focused && isSpecialMode && 'border-dashed border-divider-deep'} )}
`)}
onFocus={() => setFocused(true)} onFocus={() => setFocused(true)}
onBlur={handleBlur} onBlur={handleBlur}
value={value} value={value}

View File

@ -5,6 +5,9 @@ import {
} from '@/app/components/workflow/store' } from '@/app/components/workflow/store'
import { useWorkflowConfig } from '@/service/use-workflow' import { useWorkflowConfig } from '@/service/use-workflow'
import type { FetchWorkflowDraftResponse } from '@/types/workflow' import type { FetchWorkflowDraftResponse } from '@/types/workflow'
import { useDataSourceList } from '@/service/use-pipeline'
import type { ToolWithProvider } from '@/app/components/workflow/types'
import { basePath } from '@/utils/var'
export const usePipelineConfig = () => { export const usePipelineConfig = () => {
const pipelineId = useStore(s => s.pipelineId) const pipelineId = useStore(s => s.pipelineId)
@ -39,4 +42,15 @@ export const usePipelineConfig = () => {
pipelineId ? `/rag/pipelines/${pipelineId}/workflows/publish` : '', pipelineId ? `/rag/pipelines/${pipelineId}/workflows/publish` : '',
handleUpdatePublishedAt, handleUpdatePublishedAt,
) )
const handleUpdateDataSourceList = useCallback((dataSourceList: ToolWithProvider[]) => {
dataSourceList.forEach((item) => {
if (typeof item.icon == 'string' && !item.icon.includes(basePath))
item.icon = `${basePath}${item.icon}`
})
const { setDataSourceList } = workflowStore.getState()
setDataSourceList!(dataSourceList)
}, [workflowStore])
useDataSourceList(!!pipelineId, handleUpdateDataSourceList)
} }

View File

@ -1,5 +1,8 @@
import type { RAGPipelineVariables } from '@/models/pipeline' import type { RAGPipelineVariables } from '@/models/pipeline'
import type { StateCreator } from 'zustand' import type { StateCreator } from 'zustand'
import type {
ToolWithProvider,
} from '@/app/components/workflow/types'
export type RagPipelineSliceShape = { export type RagPipelineSliceShape = {
pipelineId: string pipelineId: string
@ -9,6 +12,8 @@ export type RagPipelineSliceShape = {
setNodesDefaultConfigs: (nodesDefaultConfigs: Record<string, any>) => void setNodesDefaultConfigs: (nodesDefaultConfigs: Record<string, any>) => void
ragPipelineVariables: RAGPipelineVariables ragPipelineVariables: RAGPipelineVariables
setRagPipelineVariables: (ragPipelineVariables: RAGPipelineVariables) => void setRagPipelineVariables: (ragPipelineVariables: RAGPipelineVariables) => void
dataSourceList: ToolWithProvider[]
setDataSourceList: (dataSourceList: ToolWithProvider[]) => void
} }
export type CreateRagPipelineSliceSlice = StateCreator<RagPipelineSliceShape> export type CreateRagPipelineSliceSlice = StateCreator<RagPipelineSliceShape>
@ -20,4 +25,6 @@ export const createRagPipelineSliceSlice: StateCreator<RagPipelineSliceShape> =
setNodesDefaultConfigs: nodesDefaultConfigs => set(() => ({ nodesDefaultConfigs })), setNodesDefaultConfigs: nodesDefaultConfigs => set(() => ({ nodesDefaultConfigs })),
ragPipelineVariables: [], ragPipelineVariables: [],
setRagPipelineVariables: (ragPipelineVariables: RAGPipelineVariables) => set(() => ({ ragPipelineVariables })), setRagPipelineVariables: (ragPipelineVariables: RAGPipelineVariables) => set(() => ({ ragPipelineVariables })),
dataSourceList: [],
setDataSourceList: (dataSourceList: ToolWithProvider[]) => set(() => ({ dataSourceList })),
}) })

View File

@ -3,8 +3,6 @@ import {
useState, useState,
} from 'react' } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useDataSourceList } from '@/service/use-pipeline'
import { useStore } from '../store'
import { import {
TabsEnum, TabsEnum,
ToolTypeEnum, ToolTypeEnum,
@ -77,10 +75,3 @@ export const useToolTabs = () => {
}, },
] ]
} }
export const useDataSources = () => {
const pipelineId = useStore(s => s.pipelineId)
const { data: dataSourceList } = useDataSourceList(!!pipelineId)
return dataSourceList || []
}

View File

@ -5,10 +5,11 @@ import type { NodeSelectorProps } from './main'
import NodeSelector from './main' import NodeSelector from './main'
import { useHooksStore } from '@/app/components/workflow/hooks-store/store' import { useHooksStore } from '@/app/components/workflow/hooks-store/store'
import { BlockEnum } from '@/app/components/workflow/types' import { BlockEnum } from '@/app/components/workflow/types'
import { useDataSources } from './hooks' import { useStore } from '../store'
const NodeSelectorWrapper = (props: NodeSelectorProps) => { const NodeSelectorWrapper = (props: NodeSelectorProps) => {
const availableNodesMetaData = useHooksStore(s => s.availableNodesMetaData) const availableNodesMetaData = useHooksStore(s => s.availableNodesMetaData)
const dataSourceList = useStore(s => s.dataSourceList)
const blocks = useMemo(() => { const blocks = useMemo(() => {
const result = availableNodesMetaData?.nodes || [] const result = availableNodesMetaData?.nodes || []
@ -33,8 +34,6 @@ const NodeSelectorWrapper = (props: NodeSelectorProps) => {
}) })
}, [availableNodesMetaData?.nodes]) }, [availableNodesMetaData?.nodes])
const dataSourceList = useDataSources()
return ( return (
<NodeSelector <NodeSelector
{...props} {...props}

View File

@ -20,6 +20,7 @@ import {
CUSTOM_NODE, CUSTOM_NODE,
MAX_TREE_DEPTH, MAX_TREE_DEPTH,
} from '../constants' } from '../constants'
import { useWorkflow } from '../hooks'
import type { ToolNodeType } from '../nodes/tool/types' import type { ToolNodeType } from '../nodes/tool/types'
import { useNodesMetaData } from './use-nodes-meta-data' import { useNodesMetaData } from './use-nodes-meta-data'
import { useToastContext } from '@/app/components/base/toast' import { useToastContext } from '@/app/components/base/toast'
@ -42,6 +43,7 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
const workflowTools = useStore(s => s.workflowTools) const workflowTools = useStore(s => s.workflowTools)
const { data: strategyProviders } = useStrategyProviders() const { data: strategyProviders } = useStrategyProviders()
const datasetsDetail = useDatasetsDetailStore(s => s.datasetsDetail) const datasetsDetail = useDatasetsDetailStore(s => s.datasetsDetail)
const { getStartNodes } = useWorkflow()
const getCheckData = useCallback((data: CommonNodeType<{}>) => { const getCheckData = useCallback((data: CommonNodeType<{}>) => {
let checkData = data let checkData = data
@ -62,7 +64,14 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
const needWarningNodes = useMemo(() => { const needWarningNodes = useMemo(() => {
const list = [] const list = []
const { validNodes } = getValidTreeNodes(nodes.filter(node => node.type === CUSTOM_NODE), edges) const filteredNodes = nodes.filter(node => node.type === CUSTOM_NODE)
const startNodes = getStartNodes(filteredNodes)
const validNodesFlattened = startNodes.map(startNode => getValidTreeNodes(startNode, filteredNodes, edges))
const validNodes = validNodesFlattened.reduce((acc, curr) => {
if (curr.validNodes)
acc.push(...curr.validNodes)
return acc
}, [] as Node[])
for (let i = 0; i < nodes.length; i++) { for (let i = 0; i < nodes.length; i++) {
const node = nodes[i] const node = nodes[i]
@ -126,7 +135,7 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
}) })
return list return list
}, [nodes, edges, buildInTools, customTools, workflowTools, language, nodesExtraData, t, strategyProviders, getCheckData]) }, [nodes, edges, buildInTools, customTools, workflowTools, language, nodesExtraData, t, strategyProviders, getCheckData, getStartNodes])
return needWarningNodes return needWarningNodes
} }
@ -143,6 +152,7 @@ export const useChecklistBeforePublish = () => {
const { data: strategyProviders } = useStrategyProviders() const { data: strategyProviders } = useStrategyProviders()
const updateDatasetsDetail = useDatasetsDetailStore(s => s.updateDatasetsDetail) const updateDatasetsDetail = useDatasetsDetailStore(s => s.updateDatasetsDetail)
const updateTime = useRef(0) const updateTime = useRef(0)
const { getStartNodes } = useWorkflow()
const getCheckData = useCallback((data: CommonNodeType<{}>, datasets: DataSet[]) => { const getCheckData = useCallback((data: CommonNodeType<{}>, datasets: DataSet[]) => {
let checkData = data let checkData = data
@ -170,16 +180,23 @@ export const useChecklistBeforePublish = () => {
getNodes, getNodes,
edges, edges,
} = store.getState() } = store.getState()
const nodes = getNodes().filter(node => node.type === CUSTOM_NODE) const nodes = getNodes()
const { const filteredNodes = nodes.filter(node => node.type === CUSTOM_NODE)
validNodes, const startNodes = getStartNodes(filteredNodes)
maxDepth, const validNodesFlattened = startNodes.map(startNode => getValidTreeNodes(startNode, filteredNodes, edges))
} = getValidTreeNodes(nodes.filter(node => node.type === CUSTOM_NODE), edges) const validNodes = validNodesFlattened.reduce((acc, curr) => {
if (curr.validNodes)
acc.push(...curr.validNodes)
return acc
}, [] as Node[])
const maxDepthArr = validNodesFlattened.map(item => item.maxDepth)
if (maxDepth > MAX_TREE_DEPTH) { for (let i = 0; i < maxDepthArr.length; i++) {
if (maxDepthArr[i] > MAX_TREE_DEPTH) {
notify({ type: 'error', message: t('workflow.common.maxTreeDepth', { depth: MAX_TREE_DEPTH }) }) notify({ type: 'error', message: t('workflow.common.maxTreeDepth', { depth: MAX_TREE_DEPTH }) })
return false return false
} }
}
// Before publish, we need to fetch datasets detail, in case of the settings of datasets have been changed // Before publish, we need to fetch datasets detail, in case of the settings of datasets have been changed
const knowledgeRetrievalNodes = nodes.filter(node => node.data.type === BlockEnum.KnowledgeRetrieval) const knowledgeRetrievalNodes = nodes.filter(node => node.data.type === BlockEnum.KnowledgeRetrieval)
const allDatasetIds = knowledgeRetrievalNodes.reduce<string[]>((acc, node) => { const allDatasetIds = knowledgeRetrievalNodes.reduce<string[]>((acc, node) => {
@ -243,7 +260,7 @@ export const useChecklistBeforePublish = () => {
} }
return true return true
}, [store, notify, t, buildInTools, customTools, workflowTools, language, nodesExtraData, strategyProviders, updateDatasetsDetail, getCheckData]) }, [store, notify, t, buildInTools, customTools, workflowTools, language, nodesExtraData, strategyProviders, updateDatasetsDetail, getCheckData, getStartNodes])
return { return {
handleCheckBeforePublish, handleCheckBeforePublish,

View File

@ -10,7 +10,6 @@ import {
import { import {
useStore, useStore,
} from '../store' } from '../store'
import { useDataSources } from '../block-selector/hooks'
import { CollectionType } from '@/app/components/tools/types' import { CollectionType } from '@/app/components/tools/types'
import { canFindTool } from '@/utils' import { canFindTool } from '@/utils'
@ -18,7 +17,7 @@ export const useToolIcon = (data: Node['data']) => {
const buildInTools = useStore(s => s.buildInTools) const buildInTools = useStore(s => s.buildInTools)
const customTools = useStore(s => s.customTools) const customTools = useStore(s => s.customTools)
const workflowTools = useStore(s => s.workflowTools) const workflowTools = useStore(s => s.workflowTools)
const dataSourceList = useDataSources() const dataSourceList = useStore(s => s.dataSourceList)
// const a = useStore(s => s.data) // const a = useStore(s => s.data)
const toolIcon = useMemo(() => { const toolIcon = useMemo(() => {
if (data.type === BlockEnum.Tool) { if (data.type === BlockEnum.Tool) {
@ -32,7 +31,7 @@ export const useToolIcon = (data: Node['data']) => {
return targetTools.find(toolWithProvider => canFindTool(toolWithProvider.id, data.provider_id))?.icon return targetTools.find(toolWithProvider => canFindTool(toolWithProvider.id, data.provider_id))?.icon
} }
if (data.type === BlockEnum.DataSource) if (data.type === BlockEnum.DataSource)
return dataSourceList.find(toolWithProvider => canFindTool(toolWithProvider.id, data.provider_id))?.icon return dataSourceList?.find(toolWithProvider => canFindTool(toolWithProvider.id, data.provider_id))?.icon
}, [data, buildInTools, customTools, workflowTools, dataSourceList]) }, [data, buildInTools, customTools, workflowTools, dataSourceList])
return toolIcon return toolIcon

View File

@ -347,8 +347,8 @@ export const useWorkflow = () => {
return [] return []
}, [store]) }, [store])
const checkNestedParallelLimit = useCallback((nodes: Node[], edges: Edge[], targetNode?: Node) => { const getStartNodes = useCallback((nodes: Node[], currentNode?: Node) => {
const { id, parentId } = targetNode || {} const { id, parentId } = currentNode || {}
let startNodes: Node[] = [] let startNodes: Node[] = []
if (parentId) { if (parentId) {
@ -367,6 +367,12 @@ export const useWorkflow = () => {
if (!startNodes.length) if (!startNodes.length)
startNodes = getRootNodesById(id || '') startNodes = getRootNodesById(id || '')
return startNodes
}, [nodesMap, getRootNodesById])
const checkNestedParallelLimit = useCallback((nodes: Node[], edges: Edge[], targetNode?: Node) => {
const startNodes = getStartNodes(nodes, targetNode)
for (let i = 0; i < startNodes.length; i++) { for (let i = 0; i < startNodes.length; i++) {
const { const {
parallelList, parallelList,
@ -389,7 +395,7 @@ export const useWorkflow = () => {
} }
return true return true
}, [t, workflowStore, nodesMap, getRootNodesById]) }, [t, workflowStore, getStartNodes])
const isValidConnection = useCallback(({ source, sourceHandle, target }: Connection) => { const isValidConnection = useCallback(({ source, sourceHandle, target }: Connection) => {
const { const {
@ -454,6 +460,7 @@ export const useWorkflow = () => {
getIterationNodeChildren, getIterationNodeChildren,
getLoopNodeChildren, getLoopNodeChildren,
getRootNodesById, getRootNodesById,
getStartNodes,
} }
} }

View File

@ -146,7 +146,7 @@ const BaseNode: FC<BaseNodeProps> = ({
data.type === BlockEnum.DataSource && ( data.type === BlockEnum.DataSource && (
<div className='absolute inset-[-2px] top-[-22px] z-[-1] rounded-[18px] bg-node-data-source-bg p-0.5 backdrop-blur-[6px]'> <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'> <div className='system-2xs-semibold-uppercase flex h-5 items-center px-2.5 text-text-tertiary'>
data source {t('workflow.blocks.data-source')}
</div> </div>
</div> </div>
) )

View File

@ -65,14 +65,17 @@ const Panel: FC<NodePanelProps<DataSourceNodeType>> = ({ id, data }) => {
<GroupWithBox boxProps={{ withBorderBottom: true }}> <GroupWithBox boxProps={{ withBorderBottom: true }}>
<Field <Field
fieldTitleProps={{ fieldTitleProps={{
title: 'supported file formats', title: t('workflow.nodes.dataSource.supportedFileFormats'),
}} }}
> >
<div className='rounded-lg bg-components-input-bg-normal p-1 pt-0'>
<TagInput <TagInput
items={fileExtensions} items={fileExtensions}
onChange={handleFileExtensionsChange} onChange={handleFileExtensionsChange}
placeholder='File extension, e.g. doc' placeholder={t('workflow.nodes.dataSource.supportedFileFormatsPlaceholder')}
inputClassName='bg-transparent'
/> />
</div>
</Field> </Field>
</GroupWithBox> </GroupWithBox>
) )

View File

@ -84,9 +84,7 @@ export const getNodesConnectedSourceOrTargetHandleIdsMap = (changes: ConnectedSo
return nodesConnectedSourceOrTargetHandleIdsMap return nodesConnectedSourceOrTargetHandleIdsMap
} }
export const getValidTreeNodes = (nodes: Node[], edges: Edge[]) => { export const getValidTreeNodes = (startNode: Node, nodes: Node[], edges: Edge[]) => {
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
if (!startNode) { if (!startNode) {
return { return {
validNodes: [], validNodes: [],

View File

@ -770,11 +770,6 @@ const translation = {
currentLoopCount: 'Current loop count: {{count}}', currentLoopCount: 'Current loop count: {{count}}',
totalLoopCount: 'Total loop count: {{count}}', totalLoopCount: 'Total loop count: {{count}}',
}, },
knowledgeBase: {
chunkStructure: 'Chunk Structure',
changeChunkStructure: 'Change Chunk Structure',
aboutRetrieval: 'about retrieval method.',
},
note: { note: {
addNote: 'Add Note', addNote: 'Add Note',
editor: { editor: {
@ -889,6 +884,15 @@ const translation = {
cancel: 'Cancel', cancel: 'Cancel',
}, },
}, },
dataSource: {
supportedFileFormats: 'Supported file formats',
supportedFileFormatsPlaceholder: 'File extension, e.g. doc',
},
knowledgeBase: {
chunkStructure: 'Chunk Structure',
changeChunkStructure: 'Change Chunk Structure',
aboutRetrieval: 'about retrieval method.',
},
}, },
tracing: { tracing: {
stopBy: 'Stop by {{user}}', stopBy: 'Stop by {{user}}',

View File

@ -771,11 +771,6 @@ const translation = {
currentLoopCount: '当前循环次数:{{count}}', currentLoopCount: '当前循环次数:{{count}}',
totalLoopCount: '总循环次数:{{count}}', totalLoopCount: '总循环次数:{{count}}',
}, },
knowledgeBase: {
chunkStructure: '分段结构',
changeChunkStructure: '更改分段结构',
aboutRetrieval: '关于知识检索。',
},
note: { note: {
addNote: '添加注释', addNote: '添加注释',
editor: { editor: {
@ -890,6 +885,15 @@ const translation = {
cancel: '取消', cancel: '取消',
}, },
}, },
dataSource: {
supportedFileFormats: '支持的文件格式',
supportedFileFormatsPlaceholder: '文件格式例如doc',
},
knowledgeBase: {
chunkStructure: '分段结构',
changeChunkStructure: '更改分段结构',
aboutRetrieval: '关于知识检索。',
},
}, },
tracing: { tracing: {
stopBy: '由{{user}}终止', stopBy: '由{{user}}终止',

View File

@ -160,12 +160,14 @@ export const usePublishedPipelineProcessingParams = (params: PipelineProcessingP
}) })
} }
export const useDataSourceList = (enabled?: boolean) => { export const useDataSourceList = (enabled: boolean, onSuccess: (v: ToolWithProvider[]) => void) => {
return useQuery<ToolWithProvider[]>({ return useQuery<ToolWithProvider[]>({
enabled, enabled,
queryKey: [NAME_SPACE, 'data-source'], queryKey: [NAME_SPACE, 'data-source'],
queryFn: () => { queryFn: async () => {
return get('/rag/pipelines/datasource-plugins') const data = await get<ToolWithProvider[]>('/rag/pipelines/datasource-plugins')
onSuccess(data)
return data
}, },
retry: false, retry: false,
}) })