diff --git a/web/app/components/base/form/components/field/mixed-variable-text-input/index.tsx b/web/app/components/base/form/components/field/mixed-variable-text-input/index.tsx new file mode 100644 index 0000000000..4bb562ba3a --- /dev/null +++ b/web/app/components/base/form/components/field/mixed-variable-text-input/index.tsx @@ -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 ( + } + onChange={onChange} + /> + ) +} + +export default memo(MixedVariableTextInput) diff --git a/web/app/components/base/form/components/field/mixed-variable-text-input/placeholder.tsx b/web/app/components/base/form/components/field/mixed-variable-text-input/placeholder.tsx new file mode 100644 index 0000000000..e84ffbeb28 --- /dev/null +++ b/web/app/components/base/form/components/field/mixed-variable-text-input/placeholder.tsx @@ -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 ( +
{ + e.stopPropagation() + handleInsert('') + }} + > +
+ Type or press +
/
+
{ + e.stopPropagation() + handleInsert('/') + })} + > + insert variable +
+
+ +
+ ) +} + +export default Placeholder diff --git a/web/app/components/base/prompt-editor/index.tsx b/web/app/components/base/prompt-editor/index.tsx index 94a65e4b62..a87a51cd50 100644 --- a/web/app/components/base/prompt-editor/index.tsx +++ b/web/app/components/base/prompt-editor/index.tsx @@ -64,8 +64,9 @@ import cn from '@/utils/classnames' export type PromptEditorProps = { instanceId?: string compact?: boolean + wrapperClassName?: string className?: string - placeholder?: string + placeholder?: string | JSX.Element placeholderClassName?: string style?: React.CSSProperties value?: string @@ -85,6 +86,7 @@ export type PromptEditorProps = { const PromptEditor: FC = ({ instanceId, compact, + wrapperClassName, className, placeholder, placeholderClassName, @@ -147,10 +149,25 @@ const PromptEditor: FC = ({ return ( -
+
} - placeholder={} + contentEditable={ + + } + placeholder={ + + } ErrorBoundary={LexicalErrorBoundary} /> { const { t } = useTranslation() return (
{value || t('common.promptEditor.placeholder')}
diff --git a/web/app/components/rag-pipeline/hooks/use-pipeline-config.ts b/web/app/components/rag-pipeline/hooks/use-pipeline-config.ts new file mode 100644 index 0000000000..f16cd03938 --- /dev/null +++ b/web/app/components/rag-pipeline/hooks/use-pipeline-config.ts @@ -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) => { + 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( + '/rag/pipelines/datasource-plugins', + handleUpdateDataSourceList, + ) + + const handleUpdateNodesDefaultConfigs = useCallback((nodesDefaultConfigs: Record) => { + 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, + ) +} diff --git a/web/app/components/rag-pipeline/hooks/use-pipeline-init.ts b/web/app/components/rag-pipeline/hooks/use-pipeline-init.ts index 7d7bdcb531..ab1ee1ea59 100644 --- a/web/app/components/rag-pipeline/hooks/use-pipeline-init.ts +++ b/web/app/components/rag-pipeline/hooks/use-pipeline-init.ts @@ -5,18 +5,15 @@ import { } from 'react' import { useParams } from 'next/navigation' import { - useStore, useWorkflowStore, } from '@/app/components/workflow/store' import { usePipelineTemplate } from './use-pipeline-template' import { - fetchNodesDefaultConfigs, - fetchPublishedWorkflow, fetchWorkflowDraft, syncWorkflowDraft, } from '@/service/workflow' import type { FetchWorkflowDraftResponse } from '@/types/workflow' -// import { useWorkflowConfig } from '@/service/use-workflow' +import { usePipelineConfig } from './use-pipeline-config' export const usePipelineInit = () => { const { datasetId } = useParams() @@ -25,31 +22,33 @@ export const usePipelineInit = () => { nodes: nodesTemplate, edges: edgesTemplate, } = usePipelineTemplate() - const setSyncWorkflowDraftHash = useStore(s => s.setSyncWorkflowDraftHash) const [data, setData] = useState() const [isLoading, setIsLoading] = useState(true) + useEffect(() => { - // workflowStore.setState({ pipelineId: datasetId as string }) + workflowStore.setState({ pipelineId: datasetId as string }) }, [datasetId, workflowStore]) - const handleUpdateWorkflowConfig = useCallback((config: Record) => { - const { setWorkflowConfig } = workflowStore.getState() - - setWorkflowConfig(config) - }, [workflowStore]) - // useWorkflowConfig(`/rag/pipeline/${datasetId}/workflows/draft/config`, handleUpdateWorkflowConfig) + usePipelineConfig() const handleGetInitialWorkflowData = useCallback(async () => { + const { + setEnvSecrets, + setEnvironmentVariables, + setSyncWorkflowDraftHash, + setDraftUpdatedAt, + setToolPublished, + } = workflowStore.getState() try { const res = await fetchWorkflowDraft(`/rag/pipeline/${datasetId}/workflows/draft`) setData(res) - workflowStore.setState({ - envSecrets: (res.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => { + setDraftUpdatedAt(res.updated_at) + setToolPublished(res.tool_published) + setEnvSecrets((res.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => { acc[env.id] = env.value return acc - }, {} as Record), - environmentVariables: res.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [], - }) + }, {} as Record)) + setEnvironmentVariables(res.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || []) setSyncWorkflowDraftHash(res.hash) setIsLoading(false) } @@ -68,49 +67,21 @@ export const usePipelineInit = () => { environment_variables: [], }, }).then((res) => { - workflowStore.getState().setDraftUpdatedAt(res.updated_at) + const { setDraftUpdatedAt } = workflowStore.getState() + setDraftUpdatedAt(res.updated_at) handleGetInitialWorkflowData() }) } }) } } - }, [nodesTemplate, edgesTemplate, workflowStore, setSyncWorkflowDraftHash, datasetId]) + }, [nodesTemplate, edgesTemplate, workflowStore, datasetId]) 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), - }) - 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 { data, isLoading, diff --git a/web/app/components/rag-pipeline/store/index.ts b/web/app/components/rag-pipeline/store/index.ts index 769d7f69f2..5d06543e22 100644 --- a/web/app/components/rag-pipeline/store/index.ts +++ b/web/app/components/rag-pipeline/store/index.ts @@ -1,5 +1,6 @@ import type { RAGPipelineVariables } from '@/models/pipeline' import type { StateCreator } from 'zustand' +import type { ToolWithProvider } from '../../workflow/types' import { InputVarType } from '../../workflow/types' export type RagPipelineSliceShape = { @@ -10,6 +11,8 @@ export type RagPipelineSliceShape = { setNodesDefaultConfigs: (nodesDefaultConfigs: Record) => void ragPipelineVariables: RAGPipelineVariables setRagPipelineVariables: (ragPipelineVariables: RAGPipelineVariables) => void + dataSourceList: ToolWithProvider[] + setDataSourceList: (dataSourceList: ToolWithProvider[]) => void } export type CreateRagPipelineSliceSlice = StateCreator @@ -50,4 +53,6 @@ export const createRagPipelineSliceSlice: StateCreator = }], }], setRagPipelineVariables: (ragPipelineVariables: RAGPipelineVariables) => set(() => ({ ragPipelineVariables })), + dataSourceList: [], + setDataSourceList: (dataSourceList: ToolWithProvider[]) => set(() => ({ dataSourceList })), }) diff --git a/web/app/components/tools/types.ts b/web/app/components/tools/types.ts index 32c468cde8..4a66ac3063 100644 --- a/web/app/components/tools/types.ts +++ b/web/app/components/tools/types.ts @@ -29,6 +29,7 @@ export enum CollectionType { custom = 'api', model = 'model', workflow = 'workflow', + datasource = 'datasource', } export type Emoji = { diff --git a/web/app/components/workflow/block-selector/data-sources.tsx b/web/app/components/workflow/block-selector/data-sources.tsx new file mode 100644 index 0000000000..f54c113b83 --- /dev/null +++ b/web/app/components/workflow/block-selector/data-sources.tsx @@ -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(null) + const wrapElemRef = useRef(null) + + return ( +
+
+ +
+
+ ) +} + +export default DataSources diff --git a/web/app/components/workflow/block-selector/hooks.ts b/web/app/components/workflow/block-selector/hooks.ts index 5f435a76c2..208a44dff6 100644 --- a/web/app/components/workflow/block-selector/hooks.ts +++ b/web/app/components/workflow/block-selector/hooks.ts @@ -8,7 +8,7 @@ import { ToolTypeEnum, } from './types' -export const useTabs = (noBlocks?: boolean) => { +export const useTabs = (noBlocks?: boolean, noSources?: boolean) => { const { t } = useTranslation() const tabs = useMemo(() => { return [ @@ -22,16 +22,22 @@ export const useTabs = (noBlocks?: boolean) => { }, ] ), - { - key: TabsEnum.Sources, - name: t('workflow.tabs.sources'), - }, + ...( + noSources + ? [] + : [ + { + key: TabsEnum.Sources, + name: t('workflow.tabs.sources'), + }, + ] + ), { key: TabsEnum.Tools, name: t('workflow.tabs.tools'), }, ] - }, [t, noBlocks]) + }, [t, noBlocks, noSources]) const initialTab = useMemo(() => { if (noBlocks) return TabsEnum.Sources diff --git a/web/app/components/workflow/block-selector/index.tsx b/web/app/components/workflow/block-selector/index.tsx index b2d11288b6..949567886d 100644 --- a/web/app/components/workflow/block-selector/index.tsx +++ b/web/app/components/workflow/block-selector/index.tsx @@ -3,6 +3,7 @@ import type { NodeSelectorProps } from './main' import NodeSelector from './main' import { useHooksStore } from '@/app/components/workflow/hooks-store/store' import { BlockEnum } from '@/app/components/workflow/types' +import { useStore } from '@/app/components/workflow/store' const NodeSelectorWrapper = (props: NodeSelectorProps) => { const availableNodesMetaData = useHooksStore(s => s.availableNodesMetaData) @@ -27,10 +28,13 @@ const NodeSelectorWrapper = (props: NodeSelectorProps) => { }) }, [availableNodesMetaData?.nodes]) + const dataSourceList = useStore(s => s.dataSourceList) + return ( ) } diff --git a/web/app/components/workflow/block-selector/main.tsx b/web/app/components/workflow/block-selector/main.tsx index 254045e231..e1bcd84c24 100644 --- a/web/app/components/workflow/block-selector/main.tsx +++ b/web/app/components/workflow/block-selector/main.tsx @@ -17,6 +17,7 @@ import type { BlockEnum, NodeDefault, OnSelectBlock, + ToolWithProvider, } from '../types' import Tabs from './tabs' import { TabsEnum } from './types' @@ -49,6 +50,7 @@ export type NodeSelectorProps = { availableBlocksTypes?: BlockEnum[] disabled?: boolean blocks?: NodeDefault[] + dataSources?: ToolWithProvider[] } const NodeSelector: FC = ({ open: openFromProps, @@ -65,6 +67,7 @@ const NodeSelector: FC = ({ availableBlocksTypes, disabled, blocks = [], + dataSources = [], }) => { const { t } = useTranslation() const [searchText, setSearchText] = useState('') @@ -95,7 +98,7 @@ const NodeSelector: FC = ({ activeTab, setActiveTab, tabs, - } = useTabs(!blocks.length) + } = useTabs(!blocks.length, !dataSources.length) const searchPlaceholder = useMemo(() => { if (activeTab === TabsEnum.Blocks) @@ -193,6 +196,7 @@ const NodeSelector: FC = ({ tags={tags} availableBlocksTypes={availableBlocksTypes} blocks={blocks} + dataSources={dataSources} />
diff --git a/web/app/components/workflow/block-selector/tabs.tsx b/web/app/components/workflow/block-selector/tabs.tsx index bad9481c21..c4dc4532cf 100644 --- a/web/app/components/workflow/block-selector/tabs.tsx +++ b/web/app/components/workflow/block-selector/tabs.tsx @@ -4,11 +4,13 @@ import { useAllBuiltInTools, useAllCustomTools, useAllWorkflowTools } from '@/se import type { BlockEnum, NodeDefault, + ToolWithProvider, } from '../types' import type { ToolDefaultValue } from './types' import { TabsEnum } from './types' import Blocks from './blocks' import AllTools from './all-tools' +import DataSources from './data-sources' export type TabsProps = { activeTab: TabsEnum @@ -17,6 +19,7 @@ export type TabsProps = { onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void availableBlocksTypes?: BlockEnum[] blocks: NodeDefault[] + dataSources?: ToolWithProvider[] } const Tabs: FC = ({ activeTab, @@ -25,6 +28,7 @@ const Tabs: FC = ({ onSelect, availableBlocksTypes, blocks, + dataSources = [], }) => { const { data: buildInTools } = useAllBuiltInTools() const { data: customTools } = useAllCustomTools() @@ -42,6 +46,15 @@ const Tabs: FC = ({ /> ) } + { + activeTab === TabsEnum.Sources && !!dataSources.length && ( + + ) + } { activeTab === TabsEnum.Tools && ( { }) } -export const useWorkflowConfig = (url: string, onSuccess: (v: WorkflowConfigResponse) => void) => { +export const useWorkflowConfig = (url: string, onSuccess: (v: T) => void) => { return useQuery({ + enabled: !!url, queryKey: [NAME_SPACE, 'config', url], queryFn: async () => { - const data = await get(url) + const data = await get(url) onSuccess(data) return data },