### What problem does this PR solve? Feat: Render DynamicCategorize with shadcn-ui. #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.17.1
| import { | |||||
| FormControl, | |||||
| FormField, | |||||
| FormItem, | |||||
| FormLabel, | |||||
| FormMessage, | |||||
| } from '@/components/ui/form'; | |||||
| import { useFormContext } from 'react-hook-form'; | |||||
| import { useTranslation } from 'react-i18next'; | |||||
| import { NextLLMSelect } from './llm-select'; | |||||
| export function LargeModelFormField() { | |||||
| const form = useFormContext(); | |||||
| const { t } = useTranslation(); | |||||
| return ( | |||||
| <FormField | |||||
| control={form.control} | |||||
| name="llm_id" | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel tooltip={t('chat.modelTip')}>{t('chat.model')}</FormLabel> | |||||
| <FormControl> | |||||
| <NextLLMSelect {...field} /> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| ); | |||||
| } |
| import { useTranslate } from '@/hooks/common-hooks'; | |||||
| import { IModalProps } from '@/interfaces/common'; | |||||
| import { Flex, Form, Input } from 'antd'; | |||||
| import { get, isPlainObject, lowerFirst } from 'lodash'; | |||||
| import { Play } from 'lucide-react'; | |||||
| import { useEffect, useRef } from 'react'; | |||||
| import { BeginId, Operator, operatorMap } from '../constant'; | |||||
| import AkShareForm from '../form/akshare-form'; | |||||
| import AnswerForm from '../form/answer-form'; | |||||
| import ArXivForm from '../form/arxiv-form'; | |||||
| import BaiduFanyiForm from '../form/baidu-fanyi-form'; | |||||
| import BaiduForm from '../form/baidu-form'; | |||||
| import BeginForm from '../form/begin-form'; | |||||
| import BingForm from '../form/bing-form'; | |||||
| import CategorizeForm from '../form/categorize-form'; | |||||
| import CrawlerForm from '../form/crawler-form'; | |||||
| import DeepLForm from '../form/deepl-form'; | |||||
| import DuckDuckGoForm from '../form/duckduckgo-form'; | |||||
| import EmailForm from '../form/email-form'; | |||||
| import ExeSQLForm from '../form/exesql-form'; | |||||
| import GenerateForm from '../form/generate-form'; | |||||
| import GithubForm from '../form/github-form'; | |||||
| import GoogleForm from '../form/google-form'; | |||||
| import GoogleScholarForm from '../form/google-scholar-form'; | |||||
| import InvokeForm from '../form/invoke-form'; | |||||
| import Jin10Form from '../form/jin10-form'; | |||||
| import KeywordExtractForm from '../form/keyword-extract-form'; | |||||
| import MessageForm from '../form/message-form'; | |||||
| import PubMedForm from '../form/pubmed-form'; | |||||
| import QWeatherForm from '../form/qweather-form'; | |||||
| import RelevantForm from '../form/relevant-form'; | |||||
| import RetrievalForm from '../form/retrieval-form/next'; | |||||
| import RewriteQuestionForm from '../form/rewrite-question-form'; | |||||
| import SwitchForm from '../form/switch-form'; | |||||
| import TemplateForm from '../form/template-form'; | |||||
| import TuShareForm from '../form/tushare-form'; | |||||
| import WenCaiForm from '../form/wencai-form'; | |||||
| import WikipediaForm from '../form/wikipedia-form'; | |||||
| import YahooFinanceForm from '../form/yahoo-finance-form'; | |||||
| import { useHandleFormValuesChange, useHandleNodeNameChange } from '../hooks'; | |||||
| import OperatorIcon from '../operator-icon'; | |||||
| import { | |||||
| buildCategorizeListFromObject, | |||||
| needsSingleStepDebugging, | |||||
| } from '../utils'; | |||||
| import { Sheet, SheetContent, SheetHeader } from '@/components/ui/sheet'; | |||||
| import { RAGFlowNodeType } from '@/interfaces/database/flow'; | |||||
| import { FlowFormContext } from '../context'; | |||||
| import { RunTooltip } from '../flow-tooltip'; | |||||
| import IterationForm from '../form/iteration-from'; | |||||
| import styles from './index.less'; | |||||
| interface IProps { | |||||
| node?: RAGFlowNodeType; | |||||
| singleDebugDrawerVisible: IModalProps<any>['visible']; | |||||
| hideSingleDebugDrawer: IModalProps<any>['hideModal']; | |||||
| showSingleDebugDrawer: IModalProps<any>['showModal']; | |||||
| } | |||||
| const FormMap = { | |||||
| [Operator.Begin]: BeginForm, | |||||
| [Operator.Retrieval]: RetrievalForm, | |||||
| [Operator.Generate]: GenerateForm, | |||||
| [Operator.Answer]: AnswerForm, | |||||
| [Operator.Categorize]: CategorizeForm, | |||||
| [Operator.Message]: MessageForm, | |||||
| [Operator.Relevant]: RelevantForm, | |||||
| [Operator.RewriteQuestion]: RewriteQuestionForm, | |||||
| [Operator.Baidu]: BaiduForm, | |||||
| [Operator.DuckDuckGo]: DuckDuckGoForm, | |||||
| [Operator.KeywordExtract]: KeywordExtractForm, | |||||
| [Operator.Wikipedia]: WikipediaForm, | |||||
| [Operator.PubMed]: PubMedForm, | |||||
| [Operator.ArXiv]: ArXivForm, | |||||
| [Operator.Google]: GoogleForm, | |||||
| [Operator.Bing]: BingForm, | |||||
| [Operator.GoogleScholar]: GoogleScholarForm, | |||||
| [Operator.DeepL]: DeepLForm, | |||||
| [Operator.GitHub]: GithubForm, | |||||
| [Operator.BaiduFanyi]: BaiduFanyiForm, | |||||
| [Operator.QWeather]: QWeatherForm, | |||||
| [Operator.ExeSQL]: ExeSQLForm, | |||||
| [Operator.Switch]: SwitchForm, | |||||
| [Operator.WenCai]: WenCaiForm, | |||||
| [Operator.AkShare]: AkShareForm, | |||||
| [Operator.YahooFinance]: YahooFinanceForm, | |||||
| [Operator.Jin10]: Jin10Form, | |||||
| [Operator.TuShare]: TuShareForm, | |||||
| [Operator.Crawler]: CrawlerForm, | |||||
| [Operator.Invoke]: InvokeForm, | |||||
| [Operator.Concentrator]: () => <></>, | |||||
| [Operator.Note]: () => <></>, | |||||
| [Operator.Template]: TemplateForm, | |||||
| [Operator.Email]: EmailForm, | |||||
| [Operator.Iteration]: IterationForm, | |||||
| [Operator.IterationStart]: () => <></>, | |||||
| }; | |||||
| const EmptyContent = () => <div></div>; | |||||
| const FormDrawer = ({ | |||||
| visible, | |||||
| hideModal, | |||||
| node, | |||||
| singleDebugDrawerVisible, | |||||
| hideSingleDebugDrawer, | |||||
| showSingleDebugDrawer, | |||||
| }: IModalProps<any> & IProps) => { | |||||
| const operatorName: Operator = node?.data.label as Operator; | |||||
| const OperatorForm = FormMap[operatorName] ?? EmptyContent; | |||||
| const [form] = Form.useForm(); | |||||
| const { name, handleNameBlur, handleNameChange } = useHandleNodeNameChange({ | |||||
| id: node?.id, | |||||
| data: node?.data, | |||||
| }); | |||||
| const previousId = useRef<string | undefined>(node?.id); | |||||
| const { t } = useTranslate('flow'); | |||||
| const { handleValuesChange } = useHandleFormValuesChange(node?.id); | |||||
| useEffect(() => { | |||||
| if (visible) { | |||||
| if (node?.id !== previousId.current) { | |||||
| form.resetFields(); | |||||
| } | |||||
| if (operatorName === Operator.Categorize) { | |||||
| const items = buildCategorizeListFromObject( | |||||
| get(node, 'data.form.category_description', {}), | |||||
| ); | |||||
| const formData = node?.data?.form; | |||||
| if (isPlainObject(formData)) { | |||||
| form.setFieldsValue({ ...formData, items }); | |||||
| } | |||||
| } else { | |||||
| form.setFieldsValue(node?.data?.form); | |||||
| } | |||||
| previousId.current = node?.id; | |||||
| } | |||||
| }, [visible, form, node?.data?.form, node?.id, node, operatorName]); | |||||
| return ( | |||||
| <Sheet onOpenChange={hideModal} open={visible}> | |||||
| <SheetContent> | |||||
| <SheetHeader> | |||||
| <Flex vertical> | |||||
| <Flex gap={'middle'} align="center"> | |||||
| <OperatorIcon | |||||
| name={operatorName} | |||||
| color={operatorMap[operatorName]?.color} | |||||
| ></OperatorIcon> | |||||
| <Flex align="center" gap={'small'} flex={1}> | |||||
| <label htmlFor="" className={styles.title}> | |||||
| {t('title')} | |||||
| </label> | |||||
| {node?.id === BeginId ? ( | |||||
| <span>{t(BeginId)}</span> | |||||
| ) : ( | |||||
| <Input | |||||
| value={name} | |||||
| onBlur={handleNameBlur} | |||||
| onChange={handleNameChange} | |||||
| ></Input> | |||||
| )} | |||||
| </Flex> | |||||
| {needsSingleStepDebugging(operatorName) && ( | |||||
| <RunTooltip> | |||||
| <Play | |||||
| className="size-5 cursor-pointer" | |||||
| onClick={showSingleDebugDrawer} | |||||
| /> | |||||
| </RunTooltip> | |||||
| )} | |||||
| {/* <CloseOutlined onClick={hideModal} /> */} | |||||
| </Flex> | |||||
| <span className={styles.operatorDescription}> | |||||
| {t(`${lowerFirst(operatorName)}Description`)} | |||||
| </span> | |||||
| </Flex> | |||||
| </SheetHeader> | |||||
| <section className={styles.formWrapper}> | |||||
| {visible && ( | |||||
| <FlowFormContext.Provider value={node}> | |||||
| <OperatorForm | |||||
| onValuesChange={handleValuesChange} | |||||
| form={form} | |||||
| node={node} | |||||
| ></OperatorForm> | |||||
| </FlowFormContext.Provider> | |||||
| )} | |||||
| </section> | |||||
| </SheetContent> | |||||
| {/* {singleDebugDrawerVisible && ( | |||||
| <SingleDebugDrawer | |||||
| visible={singleDebugDrawerVisible} | |||||
| hideModal={hideSingleDebugDrawer} | |||||
| componentId={node?.id} | |||||
| ></SingleDebugDrawer> | |||||
| )} */} | |||||
| </Sheet> | |||||
| ); | |||||
| }; | |||||
| export default FormDrawer; |
| import { useTranslate } from '@/hooks/common-hooks'; | import { useTranslate } from '@/hooks/common-hooks'; | ||||
| import { IModalProps } from '@/interfaces/common'; | import { IModalProps } from '@/interfaces/common'; | ||||
| import { RAGFlowNodeType } from '@/interfaces/database/flow'; | import { RAGFlowNodeType } from '@/interfaces/database/flow'; | ||||
| import { cn } from '@/lib/utils'; | |||||
| import { zodResolver } from '@hookform/resolvers/zod'; | import { zodResolver } from '@hookform/resolvers/zod'; | ||||
| import { get, isPlainObject, lowerFirst } from 'lodash'; | import { get, isPlainObject, lowerFirst } from 'lodash'; | ||||
| import { Play, X } from 'lucide-react'; | import { Play, X } from 'lucide-react'; | ||||
| return ( | return ( | ||||
| <Sheet open={visible} modal={false}> | <Sheet open={visible} modal={false}> | ||||
| <SheetTitle className="hidden"></SheetTitle> | <SheetTitle className="hidden"></SheetTitle> | ||||
| <SheetContent className="bg-white top-20" closeIcon={false}> | |||||
| <SheetContent className={cn('bg-white top-20 p-0')} closeIcon={false}> | |||||
| <SheetHeader> | <SheetHeader> | ||||
| <section className="flex-col border-b pb-2"> | |||||
| <section className="flex-col border-b py-2 px-5"> | |||||
| <div className="flex items-center gap-2 pb-3"> | <div className="flex items-center gap-2 pb-3"> | ||||
| <OperatorIcon | <OperatorIcon | ||||
| name={operatorName} | name={operatorName} |
| }, | }, | ||||
| [Operator.Categorize]: { | [Operator.Categorize]: { | ||||
| component: CategorizeForm, | component: CategorizeForm, | ||||
| defaultValues: {}, | |||||
| schema: z.object({}), | |||||
| defaultValues: { message_history_window_size: 1 }, | |||||
| schema: z.object({ | |||||
| message_history_window_size: z.number(), | |||||
| items: z.array( | |||||
| z.object({ | |||||
| name: z.string().min(1, t('flow.nameMessage')).trim(), | |||||
| }), | |||||
| ), | |||||
| }), | |||||
| }, | }, | ||||
| [Operator.Message]: { | [Operator.Message]: { | ||||
| component: MessageForm, | component: MessageForm, |
| import { Button } from '@/components/ui/button'; | |||||
| import { | |||||
| Collapsible, | |||||
| CollapsibleContent, | |||||
| CollapsibleTrigger, | |||||
| } from '@/components/ui/collapsible'; | |||||
| import { | |||||
| FormControl, | |||||
| FormField, | |||||
| FormItem, | |||||
| FormLabel, | |||||
| FormMessage, | |||||
| } from '@/components/ui/form'; | |||||
| import { Input } from '@/components/ui/input'; | |||||
| import { RAGFlowSelect } from '@/components/ui/select'; | |||||
| import { Textarea } from '@/components/ui/textarea'; | |||||
| import { useTranslate } from '@/hooks/common-hooks'; | import { useTranslate } from '@/hooks/common-hooks'; | ||||
| import { CloseOutlined, PlusOutlined } from '@ant-design/icons'; | |||||
| import { PlusOutlined } from '@ant-design/icons'; | |||||
| import { useUpdateNodeInternals } from '@xyflow/react'; | import { useUpdateNodeInternals } from '@xyflow/react'; | ||||
| import { | |||||
| Button, | |||||
| Collapse, | |||||
| Flex, | |||||
| Form, | |||||
| FormListFieldData, | |||||
| Input, | |||||
| Select, | |||||
| } from 'antd'; | |||||
| import { FormInstance } from 'antd/lib'; | |||||
| import { humanId } from 'human-id'; | |||||
| import humanId from 'human-id'; | |||||
| import trim from 'lodash/trim'; | import trim from 'lodash/trim'; | ||||
| import { ChevronsUpDown, X } from 'lucide-react'; | |||||
| import { | import { | ||||
| ChangeEventHandler, | ChangeEventHandler, | ||||
| FocusEventHandler, | FocusEventHandler, | ||||
| useEffect, | useEffect, | ||||
| useState, | useState, | ||||
| } from 'react'; | } from 'react'; | ||||
| import { UseFormReturn, useFieldArray, useFormContext } from 'react-hook-form'; | |||||
| import { Operator } from '../../constant'; | import { Operator } from '../../constant'; | ||||
| import { useBuildFormSelectOptions } from '../../form-hooks'; | import { useBuildFormSelectOptions } from '../../form-hooks'; | ||||
| import styles from './index.less'; | |||||
| interface IProps { | interface IProps { | ||||
| nodeId?: string; | nodeId?: string; | ||||
| } | } | ||||
| value?: string; | value?: string; | ||||
| onChange?: (value: string) => void; | onChange?: (value: string) => void; | ||||
| otherNames?: string[]; | otherNames?: string[]; | ||||
| validate(errors: string[]): void; | |||||
| validate(error?: string): void; | |||||
| } | } | ||||
| const getOtherFieldValues = ( | const getOtherFieldValues = ( | ||||
| form: FormInstance, | |||||
| form: UseFormReturn, | |||||
| formListName: string = 'items', | formListName: string = 'items', | ||||
| field: FormListFieldData, | |||||
| index: number, | |||||
| latestField: string, | latestField: string, | ||||
| ) => | ) => | ||||
| (form.getFieldValue([formListName]) ?? []) | |||||
| (form.getValues(formListName) ?? []) | |||||
| .map((x: any) => x[latestField]) | .map((x: any) => x[latestField]) | ||||
| .filter( | .filter( | ||||
| (x: string) => | (x: string) => | ||||
| x !== form.getFieldValue([formListName, field.name, latestField]), | |||||
| x !== form.getValues(`${formListName}.${index}.${latestField}`), | |||||
| ); | ); | ||||
| const NameInput = ({ | const NameInput = ({ | ||||
| const handleNameChange: ChangeEventHandler<HTMLInputElement> = useCallback( | const handleNameChange: ChangeEventHandler<HTMLInputElement> = useCallback( | ||||
| (e) => { | (e) => { | ||||
| const val = e.target.value; | const val = e.target.value; | ||||
| setName(val); | |||||
| const trimmedVal = trim(val); | |||||
| // trigger validation | // trigger validation | ||||
| if (otherNames?.some((x) => x === val)) { | |||||
| validate([t('nameRepeatedMsg')]); | |||||
| } else if (trim(val) === '') { | |||||
| validate([t('nameRequiredMsg')]); | |||||
| if (otherNames?.some((x) => x === trimmedVal)) { | |||||
| validate(t('nameRepeatedMsg')); | |||||
| } else if (trimmedVal === '') { | |||||
| validate(t('nameRequiredMsg')); | |||||
| } else { | } else { | ||||
| validate([]); | |||||
| validate(''); | |||||
| } | } | ||||
| setName(val); | |||||
| }, | }, | ||||
| [otherNames, validate, t], | [otherNames, validate, t], | ||||
| ); | ); | ||||
| ); | ); | ||||
| }; | }; | ||||
| const FormSet = ({ nodeId, field }: IProps & { field: FormListFieldData }) => { | |||||
| const form = Form.useFormInstance(); | |||||
| const FormSet = ({ nodeId, index }: IProps & { index: number }) => { | |||||
| const form = useFormContext(); | |||||
| const { t } = useTranslate('flow'); | const { t } = useTranslate('flow'); | ||||
| const buildCategorizeToOptions = useBuildFormSelectOptions( | const buildCategorizeToOptions = useBuildFormSelectOptions( | ||||
| Operator.Categorize, | Operator.Categorize, | ||||
| nodeId, | nodeId, | ||||
| ); | ); | ||||
| const buildFieldName = useCallback( | |||||
| (name: string) => { | |||||
| return `items.${index}.${name}`; | |||||
| }, | |||||
| [index], | |||||
| ); | |||||
| return ( | return ( | ||||
| <section> | |||||
| <Form.Item | |||||
| label={t('categoryName')} | |||||
| name={[field.name, 'name']} | |||||
| validateTrigger={['onChange', 'onBlur']} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| whitespace: true, | |||||
| message: t('nameMessage'), | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <NameInput | |||||
| otherNames={getOtherFieldValues(form, 'items', field, 'name')} | |||||
| validate={(errors: string[]) => | |||||
| form.setFields([ | |||||
| { | |||||
| name: ['items', field.name, 'name'], | |||||
| errors, | |||||
| }, | |||||
| ]) | |||||
| } | |||||
| ></NameInput> | |||||
| </Form.Item> | |||||
| <Form.Item label={t('description')} name={[field.name, 'description']}> | |||||
| <Input.TextArea rows={3} /> | |||||
| </Form.Item> | |||||
| <Form.Item label={t('examples')} name={[field.name, 'examples']}> | |||||
| <Input.TextArea rows={3} /> | |||||
| </Form.Item> | |||||
| <Form.Item label={t('nextStep')} name={[field.name, 'to']}> | |||||
| <Select | |||||
| allowClear | |||||
| options={buildCategorizeToOptions( | |||||
| getOtherFieldValues(form, 'items', field, 'to'), | |||||
| )} | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item hidden name={[field.name, 'index']}> | |||||
| <Input /> | |||||
| </Form.Item> | |||||
| <section className="space-y-4"> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name={buildFieldName('name')} | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel>{t('categoryName')}</FormLabel> | |||||
| <FormControl> | |||||
| <NameInput | |||||
| {...field} | |||||
| otherNames={getOtherFieldValues(form, 'items', index, 'name')} | |||||
| validate={(error?: string) => { | |||||
| const fieldName = buildFieldName('name'); | |||||
| if (error) { | |||||
| form.setError(fieldName, { message: error }); | |||||
| } else { | |||||
| form.clearErrors(fieldName); | |||||
| } | |||||
| }} | |||||
| ></NameInput> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name={buildFieldName('description')} | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel>{t('description')}</FormLabel> | |||||
| <FormControl> | |||||
| <Textarea {...field} rows={3} /> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name={buildFieldName('examples')} | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel>{t('examples')}</FormLabel> | |||||
| <FormControl> | |||||
| <Textarea {...field} rows={3} /> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name={buildFieldName('to')} | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel>{t('nextStep')}</FormLabel> | |||||
| <FormControl> | |||||
| <RAGFlowSelect | |||||
| {...field} | |||||
| allowClear | |||||
| options={buildCategorizeToOptions( | |||||
| getOtherFieldValues(form, 'items', index, 'to'), | |||||
| )} | |||||
| /> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name="index" | |||||
| render={({ field }) => ( | |||||
| <FormItem className="hidden"> | |||||
| <FormLabel>{t('examples')}</FormLabel> | |||||
| <FormControl> | |||||
| <Input {...field} /> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| </section> | </section> | ||||
| ); | ); | ||||
| }; | }; | ||||
| const DynamicCategorize = ({ nodeId }: IProps) => { | const DynamicCategorize = ({ nodeId }: IProps) => { | ||||
| const updateNodeInternals = useUpdateNodeInternals(); | const updateNodeInternals = useUpdateNodeInternals(); | ||||
| const form = Form.useFormInstance(); | |||||
| const form = useFormContext(); | |||||
| const { t } = useTranslate('flow'); | const { t } = useTranslate('flow'); | ||||
| const { fields, remove, append } = useFieldArray({ | |||||
| name: 'items', | |||||
| control: form.control, | |||||
| }); | |||||
| const handleAdd = () => { | |||||
| append({ | |||||
| name: humanId(), | |||||
| }); | |||||
| if (nodeId) updateNodeInternals(nodeId); | |||||
| }; | |||||
| return ( | return ( | ||||
| <> | |||||
| <Form.List name="items"> | |||||
| {(fields, { add, remove }) => { | |||||
| const handleAdd = () => { | |||||
| const idx = form.getFieldValue([ | |||||
| 'items', | |||||
| fields.at(-1)?.name, | |||||
| 'index', | |||||
| ]); | |||||
| add({ | |||||
| name: humanId(), | |||||
| index: fields.length === 0 ? 0 : idx + 1, | |||||
| }); | |||||
| if (nodeId) updateNodeInternals(nodeId); | |||||
| }; | |||||
| return ( | |||||
| <Flex gap={18} vertical> | |||||
| {fields.map((field) => ( | |||||
| <Collapse | |||||
| size="small" | |||||
| key={field.key} | |||||
| className={styles.caseCard} | |||||
| items={[ | |||||
| { | |||||
| key: field.key, | |||||
| label: ( | |||||
| <div className="flex justify-between"> | |||||
| <span> | |||||
| {form.getFieldValue(['items', field.name, 'name'])} | |||||
| </span> | |||||
| <CloseOutlined | |||||
| onClick={() => { | |||||
| remove(field.name); | |||||
| }} | |||||
| /> | |||||
| </div> | |||||
| ), | |||||
| children: ( | |||||
| <FormSet nodeId={nodeId} field={field}></FormSet> | |||||
| ), | |||||
| }, | |||||
| ]} | |||||
| ></Collapse> | |||||
| ))} | |||||
| <Button | |||||
| type="dashed" | |||||
| onClick={handleAdd} | |||||
| block | |||||
| className={styles.addButton} | |||||
| icon={<PlusOutlined />} | |||||
| > | |||||
| {t('addCategory')} | |||||
| </Button> | |||||
| </Flex> | |||||
| ); | |||||
| }} | |||||
| </Form.List> | |||||
| </> | |||||
| <div className="flex flex-col gap-4 "> | |||||
| {fields.map((field, index) => ( | |||||
| <Collapsible key={field.id}> | |||||
| <div className="flex items-center justify-between space-x-4"> | |||||
| <h4 className="font-bold"> | |||||
| {form.getValues(`items.${index}.name`)} | |||||
| </h4> | |||||
| <CollapsibleTrigger asChild> | |||||
| <div className="flex gap-4"> | |||||
| <Button | |||||
| variant="ghost" | |||||
| size="sm" | |||||
| className="w-9 p-0" | |||||
| onClick={() => remove(index)} | |||||
| > | |||||
| <X className="h-4 w-4" /> | |||||
| </Button> | |||||
| <Button variant="ghost" size="sm" className="w-9 p-0"> | |||||
| <ChevronsUpDown className="h-4 w-4" /> | |||||
| <span className="sr-only">Toggle</span> | |||||
| </Button> | |||||
| </div> | |||||
| </CollapsibleTrigger> | |||||
| </div> | |||||
| <CollapsibleContent> | |||||
| <FormSet nodeId={nodeId} index={index}></FormSet> | |||||
| </CollapsibleContent> | |||||
| </Collapsible> | |||||
| ))} | |||||
| <Button type={'button'} onClick={handleAdd}> | |||||
| <PlusOutlined /> | |||||
| {t('addCategory')} | |||||
| </Button> | |||||
| </div> | |||||
| ); | ); | ||||
| }; | }; | ||||
| @lightBackgroundColor: rgba(150, 150, 150, 0.07); | |||||
| @darkBackgroundColor: rgba(150, 150, 150, 0.12); | |||||
| .caseCard { | |||||
| :global(.ant-collapse-content) { | |||||
| background-color: @darkBackgroundColor; | |||||
| } | |||||
| } | |||||
| .addButton { | |||||
| color: rgb(22, 119, 255); | |||||
| font-weight: 600; | |||||
| } |
| import LLMSelect from '@/components/llm-select'; | |||||
| import MessageHistoryWindowSizeItem from '@/components/message-history-window-size-item'; | |||||
| import { useTranslate } from '@/hooks/common-hooks'; | |||||
| import { Form } from 'antd'; | |||||
| import { IOperatorForm } from '../../interface'; | |||||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||||
| import { LargeModelFormField } from '@/components/large-model-form-field'; | |||||
| import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item'; | |||||
| import { Form } from '@/components/ui/form'; | |||||
| import { INextOperatorForm } from '../../interface'; | |||||
| import { DynamicInputVariable } from '../components/next-dynamic-input-variable'; | |||||
| import DynamicCategorize from './dynamic-categorize'; | import DynamicCategorize from './dynamic-categorize'; | ||||
| import { useHandleFormValuesChange } from './hooks'; | |||||
| const CategorizeForm = ({ form, onValuesChange, node }: IOperatorForm) => { | |||||
| const { t } = useTranslate('flow'); | |||||
| const { handleValuesChange } = useHandleFormValuesChange({ | |||||
| form, | |||||
| nodeId: node?.id, | |||||
| onValuesChange, | |||||
| }); | |||||
| const CategorizeForm = ({ form, node }: INextOperatorForm) => { | |||||
| // const { handleValuesChange } = useHandleFormValuesChange({ | |||||
| // form, | |||||
| // nodeId: node?.id, | |||||
| // onValuesChange, | |||||
| // }); | |||||
| return ( | return ( | ||||
| <Form | <Form | ||||
| name="basic" | |||||
| autoComplete="off" | |||||
| form={form} | |||||
| onValuesChange={handleValuesChange} | |||||
| initialValues={{ items: [{}] }} | |||||
| layout={'vertical'} | |||||
| // onValuesChange={handleValuesChange} | |||||
| {...form} | |||||
| > | > | ||||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||||
| <Form.Item | |||||
| name={'llm_id'} | |||||
| label={t('model', { keyPrefix: 'chat' })} | |||||
| tooltip={t('modelTip', { keyPrefix: 'chat' })} | |||||
| <form | |||||
| className="space-y-6 p-5 overflow-auto max-h-[76vh]" | |||||
| onSubmit={(e) => { | |||||
| e.preventDefault(); | |||||
| }} | |||||
| > | > | ||||
| <LLMSelect></LLMSelect> | |||||
| </Form.Item> | |||||
| <MessageHistoryWindowSizeItem | |||||
| initialValue={1} | |||||
| ></MessageHistoryWindowSizeItem> | |||||
| <DynamicCategorize nodeId={node?.id}></DynamicCategorize> | |||||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||||
| <LargeModelFormField></LargeModelFormField> | |||||
| <MessageHistoryWindowSizeFormField></MessageHistoryWindowSizeFormField> | |||||
| <DynamicCategorize nodeId={node?.id}></DynamicCategorize> | |||||
| </form> | |||||
| </Form> | </Form> | ||||
| ); | ); | ||||
| }; | }; |
| .dynamicDeleteButton { | |||||
| position: relative; | |||||
| top: 4px; | |||||
| margin: 0 8px; | |||||
| color: #999; | |||||
| font-size: 24px; | |||||
| cursor: pointer; | |||||
| transition: all 0.3s; | |||||
| &:hover { | |||||
| color: #777; | |||||
| } | |||||
| &[disabled] { | |||||
| cursor: not-allowed; | |||||
| opacity: 0.5; | |||||
| } | |||||
| } |