fix: pipeline init

This commit is contained in:
zxhlyh 2025-05-13 16:01:58 +08:00
parent 4d68aadc1c
commit f6978ce6b1
15 changed files with 279 additions and 66 deletions

View File

@ -0,0 +1,39 @@
import {
memo,
} from 'react'
import PromptEditor from '@/app/components/base/prompt-editor'
import cn from '@/utils/classnames'
import Placeholder from './placeholder'
type MixedVariableTextInputProps = {
editable?: boolean
value?: string
onChange?: (text: string) => void
}
const MixedVariableTextInput = ({
editable = true,
value = '',
onChange,
}: MixedVariableTextInputProps) => {
return (
<PromptEditor
wrapperClassName={cn(
'rounded-lg border border-transparent bg-components-input-bg-normal px-2 py-1',
'hover:border-components-input-border-hover hover:bg-components-input-bg-hover',
'focus-within:border-components-input-border-active focus-within:bg-components-input-bg-active focus-within:shadow-xs',
)}
className='caret:text-text-accent'
editable={editable}
value={value}
workflowVariableBlock={{
show: true,
variables: [],
workflowNodesMap: {},
}}
placeholder={<Placeholder />}
onChange={onChange}
/>
)
}
export default memo(MixedVariableTextInput)

View File

@ -0,0 +1,49 @@
import { useCallback } from 'react'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { FOCUS_COMMAND } from 'lexical'
import { $insertNodes } from 'lexical'
import { CustomTextNode } from '@/app/components/base/prompt-editor/plugins/custom-text/node'
import Badge from '@/app/components/base/badge'
const Placeholder = () => {
const [editor] = useLexicalComposerContext()
const handleInsert = useCallback((text: string) => {
editor.update(() => {
const textNode = new CustomTextNode(text)
$insertNodes([textNode])
})
editor.dispatchCommand(FOCUS_COMMAND, undefined as any)
}, [editor])
return (
<div
className='pointer-events-auto flex h-full w-full cursor-text items-center px-2'
onClick={(e) => {
e.stopPropagation()
handleInsert('')
}}
>
<div className='flex grow items-center'>
Type or press
<div className='system-kbd mx-0.5 flex h-4 w-4 items-center justify-center rounded bg-components-kbd-bg-gray text-text-placeholder'>/</div>
<div
className='system-sm-regular cursor-pointer text-components-input-text-placeholder underline decoration-dotted decoration-auto underline-offset-auto hover:text-text-tertiary'
onClick={((e) => {
e.stopPropagation()
handleInsert('/')
})}
>
insert variable
</div>
</div>
<Badge
className='shrink-0'
text='String'
uppercase={false}
/>
</div>
)
}
export default Placeholder

View File

@ -64,8 +64,9 @@ import cn from '@/utils/classnames'
export type PromptEditorProps = { export type PromptEditorProps = {
instanceId?: string instanceId?: string
compact?: boolean compact?: boolean
wrapperClassName?: string
className?: string className?: string
placeholder?: string placeholder?: string | JSX.Element
placeholderClassName?: string placeholderClassName?: string
style?: React.CSSProperties style?: React.CSSProperties
value?: string value?: string
@ -85,6 +86,7 @@ export type PromptEditorProps = {
const PromptEditor: FC<PromptEditorProps> = ({ const PromptEditor: FC<PromptEditorProps> = ({
instanceId, instanceId,
compact, compact,
wrapperClassName,
className, className,
placeholder, placeholder,
placeholderClassName, placeholderClassName,
@ -147,10 +149,25 @@ const PromptEditor: FC<PromptEditorProps> = ({
return ( return (
<LexicalComposer initialConfig={{ ...initialConfig, editable }}> <LexicalComposer initialConfig={{ ...initialConfig, editable }}>
<div className='relative min-h-5'> <div className={cn('relative', wrapperClassName)}>
<RichTextPlugin <RichTextPlugin
contentEditable={<ContentEditable className={`${className} outline-none ${compact ? 'text-[13px] leading-5' : 'text-sm leading-6'} text-text-secondary`} style={style || {}} />} contentEditable={
placeholder={<Placeholder value={placeholder} className={cn('truncate', placeholderClassName)} compact={compact} />} <ContentEditable
className={cn(
'text-text-secondary outline-none',
compact ? 'text-[13px] leading-5' : 'text-sm leading-6',
className,
)}
style={style || {}}
/>
}
placeholder={
<Placeholder
value={placeholder}
className={cn('truncate', placeholderClassName)}
compact={compact}
/>
}
ErrorBoundary={LexicalErrorBoundary} ErrorBoundary={LexicalErrorBoundary}
/> />
<ComponentPickerBlock <ComponentPickerBlock

View File

@ -16,7 +16,6 @@ export class CustomTextNode extends TextNode {
createDOM(config: EditorConfig) { createDOM(config: EditorConfig) {
const dom = super.createDOM(config) const dom = super.createDOM(config)
dom.classList.add('align-middle')
return dom return dom
} }

View File

@ -8,16 +8,16 @@ const Placeholder = ({
className, className,
}: { }: {
compact?: boolean compact?: boolean
value?: string value?: string | JSX.Element
className?: string className?: string
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
<div className={cn( <div className={cn(
className,
'pointer-events-none absolute left-0 top-0 h-full w-full select-none text-sm text-components-input-text-placeholder', 'pointer-events-none absolute left-0 top-0 h-full w-full select-none text-sm text-components-input-text-placeholder',
compact ? 'text-[13px] leading-5' : 'text-sm leading-6', compact ? 'text-[13px] leading-5' : 'text-sm leading-6',
className,
)}> )}>
{value || t('common.promptEditor.placeholder')} {value || t('common.promptEditor.placeholder')}
</div> </div>

View File

@ -0,0 +1,53 @@
import { useCallback } from 'react'
import {
useStore,
useWorkflowStore,
} from '@/app/components/workflow/store'
import { useWorkflowConfig } from '@/service/use-workflow'
import type { ToolWithProvider } from '@/app/components/workflow/types'
import type { FetchWorkflowDraftResponse } from '@/types/workflow'
export const usePipelineConfig = () => {
const pipelineId = useStore(s => s.pipelineId)
const workflowStore = useWorkflowStore()
const handleUpdateWorkflowConfig = useCallback((config: Record<string, any>) => {
const { setWorkflowConfig } = workflowStore.getState()
setWorkflowConfig(config)
}, [workflowStore])
useWorkflowConfig(
pipelineId ? `/rag/pipeline/${pipelineId}/workflows/draft/config` : '',
handleUpdateWorkflowConfig,
)
const handleUpdateDataSourceList = useCallback((dataSourceList: ToolWithProvider[]) => {
const { setDataSourceList } = workflowStore.getState()
setDataSourceList!(dataSourceList)
}, [workflowStore])
useWorkflowConfig<ToolWithProvider[]>(
'/rag/pipelines/datasource-plugins',
handleUpdateDataSourceList,
)
const handleUpdateNodesDefaultConfigs = useCallback((nodesDefaultConfigs: Record<string, any>) => {
const { setNodesDefaultConfigs } = workflowStore.getState()
setNodesDefaultConfigs!(nodesDefaultConfigs)
}, [workflowStore])
useWorkflowConfig(
pipelineId ? `/rag/pipeline/${pipelineId}/workflows/default-workflow-block-configs` : '',
handleUpdateNodesDefaultConfigs,
)
const handleUpdatePublishedAt = useCallback((publishedWorkflow: FetchWorkflowDraftResponse) => {
const { setPublishedAt } = workflowStore.getState()
setPublishedAt(publishedWorkflow?.created_at)
}, [workflowStore])
useWorkflowConfig(
pipelineId ? `/rag/pipeline/${pipelineId}/workflows/publish` : '',
handleUpdatePublishedAt,
)
}

View File

@ -5,18 +5,15 @@ import {
} from 'react' } from 'react'
import { useParams } from 'next/navigation' import { useParams } from 'next/navigation'
import { import {
useStore,
useWorkflowStore, useWorkflowStore,
} from '@/app/components/workflow/store' } from '@/app/components/workflow/store'
import { usePipelineTemplate } from './use-pipeline-template' import { usePipelineTemplate } from './use-pipeline-template'
import { import {
fetchNodesDefaultConfigs,
fetchPublishedWorkflow,
fetchWorkflowDraft, fetchWorkflowDraft,
syncWorkflowDraft, syncWorkflowDraft,
} from '@/service/workflow' } from '@/service/workflow'
import type { FetchWorkflowDraftResponse } from '@/types/workflow' import type { FetchWorkflowDraftResponse } from '@/types/workflow'
// import { useWorkflowConfig } from '@/service/use-workflow' import { usePipelineConfig } from './use-pipeline-config'
export const usePipelineInit = () => { export const usePipelineInit = () => {
const { datasetId } = useParams() const { datasetId } = useParams()
@ -25,31 +22,33 @@ export const usePipelineInit = () => {
nodes: nodesTemplate, nodes: nodesTemplate,
edges: edgesTemplate, edges: edgesTemplate,
} = usePipelineTemplate() } = usePipelineTemplate()
const setSyncWorkflowDraftHash = useStore(s => s.setSyncWorkflowDraftHash)
const [data, setData] = useState<FetchWorkflowDraftResponse>() const [data, setData] = useState<FetchWorkflowDraftResponse>()
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
useEffect(() => { useEffect(() => {
// workflowStore.setState({ pipelineId: datasetId as string }) workflowStore.setState({ pipelineId: datasetId as string })
}, [datasetId, workflowStore]) }, [datasetId, workflowStore])
const handleUpdateWorkflowConfig = useCallback((config: Record<string, any>) => { usePipelineConfig()
const { setWorkflowConfig } = workflowStore.getState()
setWorkflowConfig(config)
}, [workflowStore])
// useWorkflowConfig(`/rag/pipeline/${datasetId}/workflows/draft/config`, handleUpdateWorkflowConfig)
const handleGetInitialWorkflowData = useCallback(async () => { const handleGetInitialWorkflowData = useCallback(async () => {
const {
setEnvSecrets,
setEnvironmentVariables,
setSyncWorkflowDraftHash,
setDraftUpdatedAt,
setToolPublished,
} = workflowStore.getState()
try { try {
const res = await fetchWorkflowDraft(`/rag/pipeline/${datasetId}/workflows/draft`) const res = await fetchWorkflowDraft(`/rag/pipeline/${datasetId}/workflows/draft`)
setData(res) setData(res)
workflowStore.setState({ setDraftUpdatedAt(res.updated_at)
envSecrets: (res.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => { setToolPublished(res.tool_published)
setEnvSecrets((res.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => {
acc[env.id] = env.value acc[env.id] = env.value
return acc return acc
}, {} as Record<string, string>), }, {} as Record<string, string>))
environmentVariables: res.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [], setEnvironmentVariables(res.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [])
})
setSyncWorkflowDraftHash(res.hash) setSyncWorkflowDraftHash(res.hash)
setIsLoading(false) setIsLoading(false)
} }
@ -68,49 +67,21 @@ export const usePipelineInit = () => {
environment_variables: [], environment_variables: [],
}, },
}).then((res) => { }).then((res) => {
workflowStore.getState().setDraftUpdatedAt(res.updated_at) const { setDraftUpdatedAt } = workflowStore.getState()
setDraftUpdatedAt(res.updated_at)
handleGetInitialWorkflowData() handleGetInitialWorkflowData()
}) })
} }
}) })
} }
} }
}, [nodesTemplate, edgesTemplate, workflowStore, setSyncWorkflowDraftHash, datasetId]) }, [nodesTemplate, edgesTemplate, workflowStore, datasetId])
useEffect(() => { useEffect(() => {
// handleGetInitialWorkflowData() handleGetInitialWorkflowData()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [])
const handleFetchPreloadData = useCallback(async () => {
try {
const nodesDefaultConfigsData = await fetchNodesDefaultConfigs(`/rag/pipeline/${datasetId}/workflows/default-workflow-block-configs`)
const publishedWorkflow = await fetchPublishedWorkflow(`/rag/pipeline/${datasetId}/workflows/publish`)
workflowStore.setState({
nodesDefaultConfigs: nodesDefaultConfigsData.reduce((acc, block) => {
if (!acc[block.type])
acc[block.type] = { ...block.config }
return acc
}, {} as Record<string, any>),
})
workflowStore.getState().setPublishedAt(publishedWorkflow?.created_at)
}
catch (e) {
console.error(e)
}
}, [workflowStore, datasetId])
useEffect(() => {
// handleFetchPreloadData()
}, [handleFetchPreloadData])
useEffect(() => {
if (data) {
workflowStore.getState().setDraftUpdatedAt(data.updated_at)
workflowStore.getState().setToolPublished(data.tool_published)
}
}, [data, workflowStore])
return { return {
data, data,
isLoading, isLoading,

View File

@ -1,5 +1,6 @@
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 '../../workflow/types'
import { InputVarType } from '../../workflow/types' import { InputVarType } from '../../workflow/types'
export type RagPipelineSliceShape = { export type RagPipelineSliceShape = {
@ -10,6 +11,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>
@ -50,4 +53,6 @@ export const createRagPipelineSliceSlice: StateCreator<RagPipelineSliceShape> =
}], }],
}], }],
setRagPipelineVariables: (ragPipelineVariables: RAGPipelineVariables) => set(() => ({ ragPipelineVariables })), setRagPipelineVariables: (ragPipelineVariables: RAGPipelineVariables) => set(() => ({ ragPipelineVariables })),
dataSourceList: [],
setDataSourceList: (dataSourceList: ToolWithProvider[]) => set(() => ({ dataSourceList })),
}) })

View File

@ -29,6 +29,7 @@ export enum CollectionType {
custom = 'api', custom = 'api',
model = 'model', model = 'model',
workflow = 'workflow', workflow = 'workflow',
datasource = 'datasource',
} }
export type Emoji = { export type Emoji = {

View File

@ -0,0 +1,51 @@
import {
useRef,
} from 'react'
import type {
OnSelectBlock,
ToolWithProvider,
} from '../types'
import Tools from './tools'
import { ViewType } from './view-type-select'
import cn from '@/utils/classnames'
import type { ListRef } from '@/app/components/workflow/block-selector/market-place-plugin/list'
type AllToolsProps = {
className?: string
toolContentClassName?: string
searchText: string
onSelect: OnSelectBlock
dataSources: ToolWithProvider[]
}
const DataSources = ({
className,
toolContentClassName,
searchText,
onSelect,
dataSources,
}: AllToolsProps) => {
const pluginRef = useRef<ListRef>(null)
const wrapElemRef = useRef<HTMLDivElement>(null)
return (
<div className={cn(className)}>
<div
ref={wrapElemRef}
className='max-h-[464px] overflow-y-auto'
onScroll={pluginRef.current?.handleScroll}
>
<Tools
className={toolContentClassName}
showWorkflowEmpty={false}
tools={dataSources}
onSelect={onSelect}
viewType={ViewType.flat}
hasSearchText={!!searchText}
/>
</div>
</div>
)
}
export default DataSources

View File

@ -8,7 +8,7 @@ import {
ToolTypeEnum, ToolTypeEnum,
} from './types' } from './types'
export const useTabs = (noBlocks?: boolean) => { export const useTabs = (noBlocks?: boolean, noSources?: boolean) => {
const { t } = useTranslation() const { t } = useTranslation()
const tabs = useMemo(() => { const tabs = useMemo(() => {
return [ return [
@ -22,16 +22,22 @@ export const useTabs = (noBlocks?: boolean) => {
}, },
] ]
), ),
{ ...(
key: TabsEnum.Sources, noSources
name: t('workflow.tabs.sources'), ? []
}, : [
{
key: TabsEnum.Sources,
name: t('workflow.tabs.sources'),
},
]
),
{ {
key: TabsEnum.Tools, key: TabsEnum.Tools,
name: t('workflow.tabs.tools'), name: t('workflow.tabs.tools'),
}, },
] ]
}, [t, noBlocks]) }, [t, noBlocks, noSources])
const initialTab = useMemo(() => { const initialTab = useMemo(() => {
if (noBlocks) if (noBlocks)
return TabsEnum.Sources return TabsEnum.Sources

View File

@ -3,6 +3,7 @@ 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 { useStore } from '@/app/components/workflow/store'
const NodeSelectorWrapper = (props: NodeSelectorProps) => { const NodeSelectorWrapper = (props: NodeSelectorProps) => {
const availableNodesMetaData = useHooksStore(s => s.availableNodesMetaData) const availableNodesMetaData = useHooksStore(s => s.availableNodesMetaData)
@ -27,10 +28,13 @@ const NodeSelectorWrapper = (props: NodeSelectorProps) => {
}) })
}, [availableNodesMetaData?.nodes]) }, [availableNodesMetaData?.nodes])
const dataSourceList = useStore(s => s.dataSourceList)
return ( return (
<NodeSelector <NodeSelector
{...props} {...props}
blocks={blocks} blocks={blocks}
dataSources={dataSourceList || []}
/> />
) )
} }

View File

@ -17,6 +17,7 @@ import type {
BlockEnum, BlockEnum,
NodeDefault, NodeDefault,
OnSelectBlock, OnSelectBlock,
ToolWithProvider,
} from '../types' } from '../types'
import Tabs from './tabs' import Tabs from './tabs'
import { TabsEnum } from './types' import { TabsEnum } from './types'
@ -49,6 +50,7 @@ export type NodeSelectorProps = {
availableBlocksTypes?: BlockEnum[] availableBlocksTypes?: BlockEnum[]
disabled?: boolean disabled?: boolean
blocks?: NodeDefault[] blocks?: NodeDefault[]
dataSources?: ToolWithProvider[]
} }
const NodeSelector: FC<NodeSelectorProps> = ({ const NodeSelector: FC<NodeSelectorProps> = ({
open: openFromProps, open: openFromProps,
@ -65,6 +67,7 @@ const NodeSelector: FC<NodeSelectorProps> = ({
availableBlocksTypes, availableBlocksTypes,
disabled, disabled,
blocks = [], blocks = [],
dataSources = [],
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const [searchText, setSearchText] = useState('') const [searchText, setSearchText] = useState('')
@ -95,7 +98,7 @@ const NodeSelector: FC<NodeSelectorProps> = ({
activeTab, activeTab,
setActiveTab, setActiveTab,
tabs, tabs,
} = useTabs(!blocks.length) } = useTabs(!blocks.length, !dataSources.length)
const searchPlaceholder = useMemo(() => { const searchPlaceholder = useMemo(() => {
if (activeTab === TabsEnum.Blocks) if (activeTab === TabsEnum.Blocks)
@ -193,6 +196,7 @@ const NodeSelector: FC<NodeSelectorProps> = ({
tags={tags} tags={tags}
availableBlocksTypes={availableBlocksTypes} availableBlocksTypes={availableBlocksTypes}
blocks={blocks} blocks={blocks}
dataSources={dataSources}
/> />
</div> </div>
</PortalToFollowElemContent> </PortalToFollowElemContent>

View File

@ -4,11 +4,13 @@ import { useAllBuiltInTools, useAllCustomTools, useAllWorkflowTools } from '@/se
import type { import type {
BlockEnum, BlockEnum,
NodeDefault, NodeDefault,
ToolWithProvider,
} from '../types' } from '../types'
import type { ToolDefaultValue } from './types' import type { ToolDefaultValue } from './types'
import { TabsEnum } from './types' import { TabsEnum } from './types'
import Blocks from './blocks' import Blocks from './blocks'
import AllTools from './all-tools' import AllTools from './all-tools'
import DataSources from './data-sources'
export type TabsProps = { export type TabsProps = {
activeTab: TabsEnum activeTab: TabsEnum
@ -17,6 +19,7 @@ export type TabsProps = {
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
availableBlocksTypes?: BlockEnum[] availableBlocksTypes?: BlockEnum[]
blocks: NodeDefault[] blocks: NodeDefault[]
dataSources?: ToolWithProvider[]
} }
const Tabs: FC<TabsProps> = ({ const Tabs: FC<TabsProps> = ({
activeTab, activeTab,
@ -25,6 +28,7 @@ const Tabs: FC<TabsProps> = ({
onSelect, onSelect,
availableBlocksTypes, availableBlocksTypes,
blocks, blocks,
dataSources = [],
}) => { }) => {
const { data: buildInTools } = useAllBuiltInTools() const { data: buildInTools } = useAllBuiltInTools()
const { data: customTools } = useAllCustomTools() const { data: customTools } = useAllCustomTools()
@ -42,6 +46,15 @@ const Tabs: FC<TabsProps> = ({
/> />
) )
} }
{
activeTab === TabsEnum.Sources && !!dataSources.length && (
<DataSources
searchText={searchText}
onSelect={onSelect}
dataSources={dataSources}
/>
)
}
{ {
activeTab === TabsEnum.Tools && ( activeTab === TabsEnum.Tools && (
<AllTools <AllTools

View File

@ -21,11 +21,12 @@ export const useAppWorkflow = (appID: string) => {
}) })
} }
export const useWorkflowConfig = (url: string, onSuccess: (v: WorkflowConfigResponse) => void) => { export const useWorkflowConfig = <T = WorkflowConfigResponse>(url: string, onSuccess: (v: T) => void) => {
return useQuery({ return useQuery({
enabled: !!url,
queryKey: [NAME_SPACE, 'config', url], queryKey: [NAME_SPACE, 'config', url],
queryFn: async () => { queryFn: async () => {
const data = await get<WorkflowConfigResponse>(url) const data = await get<T>(url)
onSuccess(data) onSuccess(data)
return data return data
}, },