From 47af1a9c42d36c56d9d3a91413d116b03e20a1e0 Mon Sep 17 00:00:00 2001 From: twwu Date: Mon, 21 Apr 2025 16:58:22 +0800 Subject: [PATCH] feat: add InputField component and integrate into RagPipeline panel --- .../assets/public/pipeline/input-field.svg | 9 ++ .../icons/src/public/pipeline/InputField.json | 64 ++++++++++ .../icons/src/public/pipeline/InputField.tsx | 20 +++ .../base/icons/src/public/pipeline/index.ts | 1 + .../rag-pipeline/components/panel/index.tsx | 36 ++++++ .../components/panel/input-field/editor.tsx | 38 ++++++ .../input-field/field-list/field-item.tsx | 96 +++++++++++++++ .../panel/input-field/field-list/index.tsx | 61 +++++++++ .../panel/input-field/footer-tip.tsx | 13 ++ .../components/panel/input-field/index.tsx | 116 ++++++++++++++++++ .../input-field-button.tsx | 18 ++- web/app/components/rag-pipeline/index.tsx | 4 +- .../components/rag-pipeline/store/index.ts | 12 +- 13 files changed, 481 insertions(+), 7 deletions(-) create mode 100644 web/app/components/base/icons/assets/public/pipeline/input-field.svg create mode 100644 web/app/components/base/icons/src/public/pipeline/InputField.json create mode 100644 web/app/components/base/icons/src/public/pipeline/InputField.tsx create mode 100644 web/app/components/base/icons/src/public/pipeline/index.ts create mode 100644 web/app/components/rag-pipeline/components/panel/index.tsx create mode 100644 web/app/components/rag-pipeline/components/panel/input-field/editor.tsx create mode 100644 web/app/components/rag-pipeline/components/panel/input-field/field-list/field-item.tsx create mode 100644 web/app/components/rag-pipeline/components/panel/input-field/field-list/index.tsx create mode 100644 web/app/components/rag-pipeline/components/panel/input-field/footer-tip.tsx create mode 100644 web/app/components/rag-pipeline/components/panel/input-field/index.tsx diff --git a/web/app/components/base/icons/assets/public/pipeline/input-field.svg b/web/app/components/base/icons/assets/public/pipeline/input-field.svg new file mode 100644 index 0000000000..5a991368de --- /dev/null +++ b/web/app/components/base/icons/assets/public/pipeline/input-field.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/web/app/components/base/icons/src/public/pipeline/InputField.json b/web/app/components/base/icons/src/public/pipeline/InputField.json new file mode 100644 index 0000000000..356d379768 --- /dev/null +++ b/web/app/components/base/icons/src/public/pipeline/InputField.json @@ -0,0 +1,64 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "16", + "height": "16", + "viewBox": "0 0 16 16", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "Icon L" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "Subtract" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M11.3333 1.66667C11.3333 1.29848 11.0348 1 10.6666 1C10.2984 1 9.99992 1.29848 9.99992 1.66667V14.3333C9.99992 14.7015 10.2984 15 10.6666 15C11.0348 15 11.3333 14.7015 11.3333 14.3333V13.3333H12.6666C13.7712 13.3333 14.6666 12.4379 14.6666 11.3333V4.66667C14.6666 3.5621 13.7712 2.66667 12.6666 2.66667H11.3333V1.66667ZM12.6666 12H11.3333V4H12.6666C13.0348 4 13.3333 4.29847 13.3333 4.66667V11.3333C13.3333 11.7015 13.0348 12 12.6666 12Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M8.66658 13.3333V12H3.33325C2.96506 12 2.66659 11.7015 2.66659 11.3333V4.66667C2.66659 4.29848 2.96506 4 3.33325 4H8.66658V2.66667H3.33325C2.22868 2.66667 1.33325 3.5621 1.33325 4.66667V11.3333C1.33325 12.4379 2.22869 13.3333 3.33325 13.3333H8.66658Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M8.66658 5.24892C8.63219 5.24484 8.59703 5.24339 8.56132 5.24478L8.51461 5.24659C7.98481 5.26717 7.48663 5.51351 7.15247 5.92673L6.57985 6.63483L6.44192 6.22222C6.26445 5.69137 5.75558 5.35376 5.20721 5.37506L4.4811 5.40327C4.11319 5.41756 3.82653 5.7274 3.84082 6.09531C3.85511 6.46322 4.16494 6.74989 4.53286 6.7356L5.19902 6.70972L5.58518 7.86484L4.47727 9.23487C4.38815 9.34509 4.25098 9.41522 4.10014 9.42108L4.05343 9.4229C3.68552 9.43719 3.39885 9.74702 3.41314 10.1149C3.42743 10.4828 3.73727 10.7695 4.10518 10.7552L4.15189 10.7534C4.68169 10.7328 5.17987 10.4865 5.51403 10.0733L6.08671 9.3651L6.22466 9.77778C6.40213 10.3086 6.911 10.6462 7.45937 10.6249C7.46692 10.6246 7.47448 10.6242 7.48202 10.6237L8.66658 10.5372V9.20033L7.46676 9.2879L7.08138 8.13509L8.18923 6.76513C8.27836 6.65491 8.41553 6.58478 8.56637 6.57892L8.61307 6.5771C8.63111 6.5764 8.64896 6.57499 8.66658 6.5729V5.24892Z", + "fill": "currentColor" + }, + "children": [] + } + ] + } + ] + } + ] + }, + "name": "InputField" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/public/pipeline/InputField.tsx b/web/app/components/base/icons/src/public/pipeline/InputField.tsx new file mode 100644 index 0000000000..4c224844d0 --- /dev/null +++ b/web/app/components/base/icons/src/public/pipeline/InputField.tsx @@ -0,0 +1,20 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './InputField.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconData } from '@/app/components/base/icons/IconBase' + +const Icon = ( + { + ref, + ...props + }: React.SVGProps & { + ref?: React.RefObject>; + }, +) => + +Icon.displayName = 'InputField' + +export default Icon diff --git a/web/app/components/base/icons/src/public/pipeline/index.ts b/web/app/components/base/icons/src/public/pipeline/index.ts new file mode 100644 index 0000000000..2f249bc45e --- /dev/null +++ b/web/app/components/base/icons/src/public/pipeline/index.ts @@ -0,0 +1 @@ +export { default as InputField } from './InputField' diff --git a/web/app/components/rag-pipeline/components/panel/index.tsx b/web/app/components/rag-pipeline/components/panel/index.tsx new file mode 100644 index 0000000000..aa9f69fc52 --- /dev/null +++ b/web/app/components/rag-pipeline/components/panel/index.tsx @@ -0,0 +1,36 @@ +import { useStore } from '@/app/components/workflow/store' +import InputField from './input-field' +import { useMemo } from 'react' +import type { PanelProps } from '@/app/components/workflow/panel' +import Panel from '@/app/components/workflow/panel' + +const RagPipelinePanelOnRight = () => { + const showInputField = useStore(s => s.showInputFieldPanel) + + return ( + <> + { + showInputField && ( + + ) + } + + ) +} + +const RagPipelinePanel = () => { + const panelProps: PanelProps = useMemo(() => { + return { + components: { + left: null, + right: , + }, + } + }, []) + + return ( + + ) +} + +export default RagPipelinePanel diff --git a/web/app/components/rag-pipeline/components/panel/input-field/editor.tsx b/web/app/components/rag-pipeline/components/panel/input-field/editor.tsx new file mode 100644 index 0000000000..a5e052d325 --- /dev/null +++ b/web/app/components/rag-pipeline/components/panel/input-field/editor.tsx @@ -0,0 +1,38 @@ +import { useStore } from '@/app/components/workflow/store' +import InputFieldForm from '@/app/components/base/form/form-scenarios/input-field' +import { useCallback } from 'react' +import { RiCloseLine } from '@remixicon/react' + +const InputFieldEditor = () => { + const setShowInputFieldEditor = useStore(state => state.setShowInputFieldEditor) + + const closeEditor = useCallback(() => { + setShowInputFieldEditor?.(false) + }, [setShowInputFieldEditor]) + + return ( +
+
+ Add Input Field +
+ + { + console.log('submit', value) + closeEditor() + }} + /> +
+ ) +} + +export default InputFieldEditor diff --git a/web/app/components/rag-pipeline/components/panel/input-field/field-list/field-item.tsx b/web/app/components/rag-pipeline/components/panel/input-field/field-list/field-item.tsx new file mode 100644 index 0000000000..a3e6f3f9f1 --- /dev/null +++ b/web/app/components/rag-pipeline/components/panel/input-field/field-list/field-item.tsx @@ -0,0 +1,96 @@ +'use client' +import React, { useCallback, useRef } from 'react' +import { useHover } from 'ahooks' +import { useTranslation } from 'react-i18next' +import { + RiDeleteBinLine, + RiEditLine, +} from '@remixicon/react' +import type { InputVar } from '@/app/components/workflow/types' +import { noop } from 'lodash-es' +import { useStore } from '@/app/components/workflow/store' +import { InputField } from '@/app/components/base/icons/src/public/pipeline' +import InputVarTypeIcon from '@/app/components/workflow/nodes/_base/components/input-var-type-icon' +import cn from '@/utils/classnames' +import Badge from '@/app/components/base/badge' + +type FieldItemProps = { + readonly?: boolean + payload: InputVar + onRemove?: () => void +} + +const FieldItem = ({ + readonly, + payload, + onRemove = noop, +}: FieldItemProps) => { + const { t } = useTranslation() + + const ref = useRef(null) + const isHovering = useHover(ref) + const setShowInputFieldEditor = useStore(state => state.setShowInputFieldEditor) + + const showInputFieldEditor = useCallback(() => { + setShowInputFieldEditor?.(true) + }, [setShowInputFieldEditor]) + + return ( +
+
+ +
+ {payload.variable} +
+ {payload.label && ( + <> +
·
+
+ {payload.label as string} +
+ + )} +
+ {(!isHovering || readonly) + ? ( +
+ {payload.required && ( + {t('workflow.nodes.start.required')} + )} + +
+ ) + : (!readonly && ( +
+ + +
+ )) + } +
+ ) +} +export default React.memo(FieldItem) diff --git a/web/app/components/rag-pipeline/components/panel/input-field/field-list/index.tsx b/web/app/components/rag-pipeline/components/panel/input-field/field-list/index.tsx new file mode 100644 index 0000000000..afac5b91bd --- /dev/null +++ b/web/app/components/rag-pipeline/components/panel/input-field/field-list/index.tsx @@ -0,0 +1,61 @@ +import { useStore } from '@/app/components/workflow/store' +import type { InputVar } from '@/app/components/workflow/types' +import { RiAddLine } from '@remixicon/react' +import FieldItem from './field-item' +import cn from '@/utils/classnames' + +type FieldListProps = { + LabelRightContent: React.ReactNode + inputFields?: InputVar[] + readonly?: boolean + labelClassName?: string +} + +const FieldList = ({ + LabelRightContent, + inputFields, + readonly, + labelClassName, +}: FieldListProps) => { + const showInputFieldEditor = useStore(state => state.showInputFieldEditor) + const setShowInputFieldEditor = useStore(state => state.setShowInputFieldEditor) + + const isReadonly = readonly || showInputFieldEditor + + const handleAddField = () => { + setShowInputFieldEditor?.(true) + } + + return ( +
+
+
+ {LabelRightContent} +
+ +
+
+ {inputFields?.map((item, index) => ( + { + // Handle remove action + }} + /> + ))} +
+
+ ) +} + +export default FieldList diff --git a/web/app/components/rag-pipeline/components/panel/input-field/footer-tip.tsx b/web/app/components/rag-pipeline/components/panel/input-field/footer-tip.tsx new file mode 100644 index 0000000000..5b3d98be22 --- /dev/null +++ b/web/app/components/rag-pipeline/components/panel/input-field/footer-tip.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import { RiDragDropLine } from '@remixicon/react' + +const FooterTip = () => { + return ( +
+ + Drag to adjust grouping +
+ ) +} + +export default React.memo(FooterTip) diff --git a/web/app/components/rag-pipeline/components/panel/input-field/index.tsx b/web/app/components/rag-pipeline/components/panel/input-field/index.tsx new file mode 100644 index 0000000000..d456c87500 --- /dev/null +++ b/web/app/components/rag-pipeline/components/panel/input-field/index.tsx @@ -0,0 +1,116 @@ +import { + memo, + useCallback, +} from 'react' +import { useStore } from '@/app/components/workflow/store' +import { RiCloseLine } from '@remixicon/react' +import FieldList from './field-list' +import { Jina } from '@/app/components/base/icons/src/public/llm' +import { InputVarType } from '@/app/components/workflow/types' +import Tooltip from '@/app/components/base/tooltip' +import FooterTip from './footer-tip' +import InputFieldEditor from './editor' + +type InputFieldPanelProps = { + readonly?: boolean +} + +const InputFieldPanel = ({ + readonly = false, +}: InputFieldPanelProps) => { + const showInputFieldEditor = useStore(state => state.showInputFieldEditor) + const setShowInputFieldPanel = useStore(state => state.setShowInputFieldPanel) + + const closePanel = useCallback(() => { + setShowInputFieldPanel?.(false) + }, [setShowInputFieldPanel]) + + return ( +
+
+
+
+ User input fields +
+ +
+
+ User input fields are used to define and collect variables required during the pipeline execution process. Users can customize the field type and flexibly configure the input value to meet the needs of different data sources or document processing steps. +
+
+ {/* Jina Reader Field List */} + +
+ +
+ Jina Reader +
+ )} + inputFields={[{ + variable: 'name', + label: 'name', + type: InputVarType.textInput, + required: true, + max_length: 12, + }]} + readonly={readonly} + labelClassName='pt-2 pb-1' + /> + {/* Firecrawl Field List */} + +
+ 🔥 +
+ Firecrawl +
+ )} + inputFields={[{ + variable: 'name', + label: 'name', + type: InputVarType.textInput, + required: true, + max_length: 12, + }]} + readonly={readonly} + labelClassName='pt-2 pb-1' + /> + {/* Shared Inputs */} + + SHARED INPUTS + +
+ )} + inputFields={[{ + variable: 'name', + label: 'name', + type: InputVarType.textInput, + required: true, + max_length: 12, + }]} + readonly={readonly} + labelClassName='pt-1 pb-2' + /> + + + + {showInputFieldEditor && } + + ) +} + +export default memo(InputFieldPanel) diff --git a/web/app/components/rag-pipeline/components/rag-pipeline-header/input-field-button.tsx b/web/app/components/rag-pipeline/components/rag-pipeline-header/input-field-button.tsx index 149b5d0186..5fb50fe72a 100644 --- a/web/app/components/rag-pipeline/components/rag-pipeline-header/input-field-button.tsx +++ b/web/app/components/rag-pipeline/components/rag-pipeline-header/input-field-button.tsx @@ -1,8 +1,24 @@ import Button from '@/app/components/base/button' +import { InputField } from '@/app/components/base/icons/src/public/pipeline' +import { useStore } from '@/app/components/workflow/store' +import { useCallback } from 'react' +// TODO: i18n const InputFieldButton = () => { + const setShowInputFieldPanel = useStore(state => state.setShowInputFieldPanel) + const handleClick = useCallback(() => { + setShowInputFieldPanel?.(true) + }, [setShowInputFieldPanel]) + return ( - + ) } diff --git a/web/app/components/rag-pipeline/index.tsx b/web/app/components/rag-pipeline/index.tsx index 7a1991e743..4aa0cd85fb 100644 --- a/web/app/components/rag-pipeline/index.tsx +++ b/web/app/components/rag-pipeline/index.tsx @@ -1,7 +1,7 @@ import WorkflowWithDefaultContext, { WorkflowWithInnerContext, } from '@/app/components/workflow' -import Panel from '@/app/components/workflow/panel' +import RagPipelinePanel from './components/panel' import { WorkflowContextProvider, } from '@/app/components/workflow/context' @@ -23,7 +23,7 @@ const RagPipeline = () => { edges={[]} > - + diff --git a/web/app/components/rag-pipeline/store/index.ts b/web/app/components/rag-pipeline/store/index.ts index 7dcc089cdc..2a186ce700 100644 --- a/web/app/components/rag-pipeline/store/index.ts +++ b/web/app/components/rag-pipeline/store/index.ts @@ -1,12 +1,16 @@ import type { StateCreator } from 'zustand' export type RagPipelineSliceShape = { - showInputFieldDialog: boolean - setShowInputFieldDialog: (showInputFieldDialog: boolean) => void + showInputFieldEditor: boolean + setShowInputFieldEditor: (showInputFieldDialog: boolean) => void + showInputFieldPanel: boolean + setShowInputFieldPanel: (showInputFieldPanel: boolean) => void } export type CreateRagPipelineSliceSlice = StateCreator export const createRagPipelineSliceSlice: StateCreator = set => ({ - showInputFieldDialog: false, - setShowInputFieldDialog: showInputFieldDialog => set(() => ({ showInputFieldDialog })), + showInputFieldEditor: false, + setShowInputFieldEditor: showInputFieldEditor => set(() => ({ showInputFieldEditor })), + showInputFieldPanel: false, + setShowInputFieldPanel: showInputFieldPanel => set(() => ({ showInputFieldPanel })), })