feat: add InputField component and integrate into RagPipeline panel

pull/21398/head
twwu 1 year ago
parent 51165408ed
commit 47af1a9c42

@ -0,0 +1,9 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Icon L">
<g id="Subtract">
<path 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="#354052"/>
<path 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="#354052"/>
<path 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="#354052"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@ -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"
}

@ -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<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'InputField'
export default Icon

@ -0,0 +1 @@
export { default as InputField } from './InputField'

@ -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 && (
<InputField />
)
}
</>
)
}
const RagPipelinePanel = () => {
const panelProps: PanelProps = useMemo(() => {
return {
components: {
left: null,
right: <RagPipelinePanelOnRight />,
},
}
}, [])
return (
<Panel {...panelProps} />
)
}
export default RagPipelinePanel

@ -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 (
<div className='relative flex h-fit w-[400px] flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-2xl shadow-shadow-shadow-9'>
<div className='system-xl-semibold flex items-center pb-1 pl-4 pr-11 pt-3.5 text-text-primary'>
Add Input Field
</div>
<button
type='button'
className='absolute right-2.5 top-2.5 flex size-8 items-center justify-center'
onClick={closeEditor}
>
<RiCloseLine className='size-4 text-text-tertiary' />
</button>
<InputFieldForm
initialData={undefined}
supportFile
onCancel={closeEditor}
onSubmit={(value) => {
console.log('submit', value)
closeEditor()
}}
/>
</div>
)
}
export default InputFieldEditor

@ -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 (
<div
ref={ref}
className={cn(
'flex h-8 cursor-pointer items-center justify-between gap-x-1 rounded-lg border border-components-panel-border-subtle bg-components-panel-on-panel-item-bg py-1 pl-2 shadow-xs hover:shadow-sm',
(!isHovering || readonly) ? 'pr-2.5' : !readonly && 'pr-1',
)}
>
<div className='flex grow basis-0 items-center gap-x-1'>
<InputField className='size-4 text-text-accent' />
<div
title={payload.variable}
className='system-sm-medium max-w-[130px] shrink-0 truncate text-text-secondary'
>
{payload.variable}
</div>
{payload.label && (
<>
<div className='system-xs-regular shrink-0 text-text-quaternary'>·</div>
<div
title={payload.label as string}
className='system-xs-medium max-w-[130px] truncate text-text-tertiary'
>
{payload.label as string}
</div>
</>
)}
</div>
{(!isHovering || readonly)
? (
<div className='flex shrink-0 items-center gap-x-2'>
{payload.required && (
<Badge>{t('workflow.nodes.start.required')}</Badge>
)}
<InputVarTypeIcon type={payload.type} className='h-3 w-3 text-text-tertiary' />
</div>
)
: (!readonly && (
<div className='flex shrink-0 items-center gap-x-1'>
<button
type='button'
className='cursor-pointer rounded-md p-1 hover:bg-state-base-hover'
onClick={showInputFieldEditor}
>
<RiEditLine className='size-4 text-text-tertiary' />
</button>
<button
onClick={onRemove}
className='group cursor-pointer rounded-md p-1 hover:bg-state-destructive-hover'
>
<RiDeleteBinLine className='size-4 text-text-tertiary group-hover:text-text-destructive' />
</button>
</div>
))
}
</div>
)
}
export default React.memo(FieldItem)

@ -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 (
<div className='flex flex-col'>
<div className={cn('flex items-center gap-x-2 px-4', labelClassName)}>
<div className='grow'>
{LabelRightContent}
</div>
<button
type='button'
className='h-6 px-2 py-1 disabled:cursor-not-allowed'
onClick={handleAddField}
disabled={isReadonly}
aria-disabled={isReadonly}
>
<RiAddLine className='h-4 w-4 text-text-tertiary' />
</button>
</div>
<div className='flex flex-col gap-y-1 px-4 pb-2'>
{inputFields?.map((item, index) => (
<FieldItem
key={index}
readonly={isReadonly}
payload={item}
onRemove={() => {
// Handle remove action
}}
/>
))}
</div>
</div>
)
}
export default FieldList

@ -0,0 +1,13 @@
import React from 'react'
import { RiDragDropLine } from '@remixicon/react'
const FooterTip = () => {
return (
<div className='flex items-center justify-center gap-x-2 py-4 text-text-quaternary'>
<RiDragDropLine className='size-4' />
<span className='system-xs-regular'>Drag to adjust grouping</span>
</div>
)
}
export default React.memo(FooterTip)

@ -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 (
<div className='flex h-full flex-row-reverse gap-x-1'>
<div className='flex h-full w-[420px] flex-col rounded-l-2xl border-y border-l border-components-panel-border bg-components-panel-bg-alt shadow-xl shadow-shadow-shadow-5'>
<div className='flex items-center p-4 pb-0'>
<div className='system-xl-semibold grow'>
User input fields
</div>
<button
type='button'
className='flex size-6 shrink-0 items-center justify-center p-0.5'
onClick={closePanel}
>
<RiCloseLine className='size-4 text-text-tertiary' />
</button>
</div>
<div className='system-sm-regular px-4 py-1 text-text-tertiary'>
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.
</div>
<div className='flex grow flex-col overflow-y-auto'>
{/* Jina Reader Field List */}
<FieldList
LabelRightContent={(
<div className='flex items-center gap-x-1.5'>
<div className='flex size-5 items-center justify-center rounded-md border-[0.5px] border-components-panel-border-subtle bg-background-default'>
<Jina className='size-3.5' />
</div>
<span className='system-sm-medium text-text-secondary'>Jina Reader</span>
</div>
)}
inputFields={[{
variable: 'name',
label: 'name',
type: InputVarType.textInput,
required: true,
max_length: 12,
}]}
readonly={readonly}
labelClassName='pt-2 pb-1'
/>
{/* Firecrawl Field List */}
<FieldList
LabelRightContent={(
<div className='flex items-center gap-x-1.5'>
<div className='flex size-5 items-center justify-center rounded-md border-[0.5px] border-components-panel-border-subtle bg-background-default'>
<span className='text-[14px] leading-[14px]'>🔥</span>
</div>
<span className='system-sm-medium text-text-secondary'>Firecrawl</span>
</div>
)}
inputFields={[{
variable: 'name',
label: 'name',
type: InputVarType.textInput,
required: true,
max_length: 12,
}]}
readonly={readonly}
labelClassName='pt-2 pb-1'
/>
{/* Shared Inputs */}
<FieldList
LabelRightContent={(
<div className='flex items-center gap-x-1'>
<span className='system-sm-medium text-text-secondary'>SHARED INPUTS</span>
<Tooltip
popupContent='Shared Inputs are available to all downstream nodes across data sources. For example, variables like delimiter and maximum chunk length can be uniformly applied when processing documents from multiple sources.'
popupClassName='!w-[300px]'
/>
</div>
)}
inputFields={[{
variable: 'name',
label: 'name',
type: InputVarType.textInput,
required: true,
max_length: 12,
}]}
readonly={readonly}
labelClassName='pt-1 pb-2'
/>
</div>
<FooterTip />
</div>
{showInputFieldEditor && <InputFieldEditor />}
</div>
)
}
export default memo(InputFieldPanel)

@ -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 (
<Button>Input Field</Button>
<Button
variant='secondary'
className='flex gap-x-0.5'
onClick={handleClick}
>
<InputField className='h-4 w-4' />
<span className='px-0.5'>Input Field</span>
</Button>
)
}

@ -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={[]}
>
<RagPipelineHeader />
<Panel />
<RagPipelinePanel />
</WorkflowWithInnerContext>
</WorkflowWithDefaultContext>
</WorkflowContextProvider>

@ -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<RagPipelineSliceShape>
export const createRagPipelineSliceSlice: StateCreator<RagPipelineSliceShape> = set => ({
showInputFieldDialog: false,
setShowInputFieldDialog: showInputFieldDialog => set(() => ({ showInputFieldDialog })),
showInputFieldEditor: false,
setShowInputFieldEditor: showInputFieldEditor => set(() => ({ showInputFieldEditor })),
showInputFieldPanel: false,
setShowInputFieldPanel: showInputFieldPanel => set(() => ({ showInputFieldPanel })),
})

Loading…
Cancel
Save