### What problem does this PR solve? Feat: Add the example component of the classification operator #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.19.1
| @@ -0,0 +1,11 @@ | |||
| export interface ICategorizeItem { | |||
| name: string; | |||
| description?: string; | |||
| examples?: { value: string }[]; | |||
| index: number; | |||
| } | |||
| export type ICategorizeItemResult = Record< | |||
| string, | |||
| Omit<ICategorizeItem, 'name' | 'examples'> & { examples: string[] } | |||
| >; | |||
| @@ -11,7 +11,7 @@ import { | |||
| FormLabel, | |||
| FormMessage, | |||
| } from '@/components/ui/form'; | |||
| import { BlurInput, Input } from '@/components/ui/input'; | |||
| import { Input } from '@/components/ui/input'; | |||
| import { BlurTextarea } from '@/components/ui/textarea'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { PlusOutlined } from '@ant-design/icons'; | |||
| @@ -30,6 +30,7 @@ import { | |||
| import { UseFormReturn, useFieldArray, useFormContext } from 'react-hook-form'; | |||
| import { Operator } from '../../constant'; | |||
| import { useBuildFormSelectOptions } from '../../form-hooks'; | |||
| import DynamicExample from './dynamic-example'; | |||
| interface IProps { | |||
| nodeId?: string; | |||
| @@ -130,8 +131,7 @@ const InnerFormSet = ({ nodeId, index }: IProps & { index: number }) => { | |||
| <FormItem> | |||
| <FormLabel>{t('categoryName')}</FormLabel> | |||
| <FormControl> | |||
| <BlurInput {...field}></BlurInput> | |||
| {/* <NameInput | |||
| <NameInput | |||
| {...field} | |||
| otherNames={getOtherFieldValues(form, 'items', index, 'name')} | |||
| validate={(error?: string) => { | |||
| @@ -142,7 +142,7 @@ const InnerFormSet = ({ nodeId, index }: IProps & { index: number }) => { | |||
| form.clearErrors(fieldName); | |||
| } | |||
| }} | |||
| ></NameInput> */} | |||
| ></NameInput> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| @@ -161,51 +161,7 @@ const InnerFormSet = ({ nodeId, index }: IProps & { index: number }) => { | |||
| </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> | |||
| )} | |||
| /> */} | |||
| <DynamicExample name={buildFieldName('examples')}></DynamicExample> | |||
| </section> | |||
| ); | |||
| }; | |||
| @@ -225,7 +181,7 @@ const DynamicCategorize = ({ nodeId }: IProps) => { | |||
| append({ | |||
| name: humanId(), | |||
| description: '', | |||
| // examples: [], | |||
| examples: [{ value: '' }], | |||
| }); | |||
| if (nodeId) updateNodeInternals(nodeId); | |||
| }; | |||
| @@ -0,0 +1,75 @@ | |||
| import { BlockButton, Button } from '@/components/ui/button'; | |||
| import { | |||
| FormControl, | |||
| FormField, | |||
| FormItem, | |||
| FormLabel, | |||
| FormMessage, | |||
| } from '@/components/ui/form'; | |||
| import { Textarea } from '@/components/ui/textarea'; | |||
| import { Plus, X } from 'lucide-react'; | |||
| import { memo } from 'react'; | |||
| import { useFieldArray, useFormContext } from 'react-hook-form'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| type DynamicExampleProps = { name: string }; | |||
| const DynamicExample = ({ name }: DynamicExampleProps) => { | |||
| const { t } = useTranslation(); | |||
| const form = useFormContext(); | |||
| const { fields, append, remove } = useFieldArray({ | |||
| name: name, | |||
| control: form.control, | |||
| }); | |||
| return ( | |||
| <FormItem> | |||
| <FormLabel tooltip={t('flow.msgTip')}>{t('flow.msg')}</FormLabel> | |||
| <div className="space-y-4"> | |||
| {fields.map((field, index) => ( | |||
| <div key={field.id} className="flex items-start gap-2"> | |||
| <FormField | |||
| control={form.control} | |||
| name={`${name}.${index}.value`} | |||
| render={({ field }) => ( | |||
| <FormItem className="flex-1"> | |||
| <FormControl> | |||
| <Textarea {...field}> </Textarea> | |||
| </FormControl> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| {index === 0 ? ( | |||
| <Button | |||
| type="button" | |||
| variant={'ghost'} | |||
| onClick={() => append({ value: '' })} | |||
| > | |||
| <Plus /> | |||
| </Button> | |||
| ) : ( | |||
| <Button | |||
| type="button" | |||
| variant={'ghost'} | |||
| onClick={() => remove(index)} | |||
| > | |||
| <X /> | |||
| </Button> | |||
| )} | |||
| </div> | |||
| ))} | |||
| <BlockButton | |||
| type="button" | |||
| onClick={() => append({ value: '' })} // "" will cause the inability to add, refer to: https://github.com/orgs/react-hook-form/discussions/8485#discussioncomment-2961861 | |||
| > | |||
| {t('flow.addMessage')} | |||
| </BlockButton> | |||
| </div> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| ); | |||
| }; | |||
| export default memo(DynamicExample); | |||
| @@ -1,3 +1,4 @@ | |||
| import { FormContainer } from '@/components/form-container'; | |||
| import { LargeModelFormField } from '@/components/large-model-form-field'; | |||
| import { LlmSettingSchema } from '@/components/llm-setting-items/next'; | |||
| import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item'; | |||
| @@ -25,6 +26,7 @@ const CategorizeForm = ({ node }: INextOperatorForm) => { | |||
| const values = useValues(node); | |||
| const FormSchema = z.object({ | |||
| input: z.string().optional(), | |||
| parameter: z.string().optional(), | |||
| ...LlmSettingSchema, | |||
| message_history_window_size: z.coerce.number(), | |||
| @@ -33,13 +35,13 @@ const CategorizeForm = ({ node }: INextOperatorForm) => { | |||
| .object({ | |||
| name: z.string().min(1, t('flow.nameMessage')).trim(), | |||
| description: z.string().optional(), | |||
| // examples: z | |||
| // .array( | |||
| // z.object({ | |||
| // value: z.string(), | |||
| // }), | |||
| // ) | |||
| // .optional(), | |||
| examples: z | |||
| .array( | |||
| z.object({ | |||
| value: z.string(), | |||
| }), | |||
| ) | |||
| .optional(), | |||
| }) | |||
| .optional(), | |||
| ), | |||
| @@ -60,23 +62,25 @@ const CategorizeForm = ({ node }: INextOperatorForm) => { | |||
| e.preventDefault(); | |||
| }} | |||
| > | |||
| <FormField | |||
| control={form.control} | |||
| name="input" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel tooltip={t('chat.modelTip')}> | |||
| {t('chat.input')} | |||
| </FormLabel> | |||
| <FormControl> | |||
| <SelectWithSearch {...field}></SelectWithSearch> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <FormContainer> | |||
| <FormField | |||
| control={form.control} | |||
| name="input" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel tooltip={t('chat.modelTip')}> | |||
| {t('chat.input')} | |||
| </FormLabel> | |||
| <FormControl> | |||
| <SelectWithSearch {...field}></SelectWithSearch> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <LargeModelFormField></LargeModelFormField> | |||
| <LargeModelFormField></LargeModelFormField> | |||
| </FormContainer> | |||
| <MessageHistoryWindowSizeFormField | |||
| useBlurInput | |||
| ></MessageHistoryWindowSizeFormField> | |||
| @@ -1,9 +1,8 @@ | |||
| import { | |||
| DSLComponents, | |||
| ICategorizeItem, | |||
| ICategorizeItemResult, | |||
| RAGFlowNodeType, | |||
| } from '@/interfaces/database/flow'; | |||
| } from '@/interfaces/database/agent'; | |||
| import { DSLComponents, RAGFlowNodeType } from '@/interfaces/database/flow'; | |||
| import { removeUselessFieldsFromValues } from '@/utils/form'; | |||
| import { Edge, Node, Position, XYPosition } from '@xyflow/react'; | |||
| import { FormInstance, FormListFieldData } from 'antd'; | |||
| @@ -391,6 +390,22 @@ export const generateDuplicateNode = ( | |||
| }; | |||
| }; | |||
| export function convertToStringArray( | |||
| list?: Array<{ value: string | number | boolean }>, | |||
| ) { | |||
| if (!Array.isArray(list)) { | |||
| return []; | |||
| } | |||
| return list.map((x) => x.value); | |||
| } | |||
| export function convertToObjectArray(list: Array<string | number | boolean>) { | |||
| if (!Array.isArray(list)) { | |||
| return []; | |||
| } | |||
| return list.map((x) => ({ value: x })); | |||
| } | |||
| /** | |||
| * convert the following object into a list | |||
| * | |||
| @@ -411,7 +426,11 @@ export const buildCategorizeListFromObject = ( | |||
| .reduce<Array<ICategorizeItem>>((pre, cur) => { | |||
| // synchronize edge data to the to field | |||
| pre.push({ name: cur, ...categorizeItem[cur] }); | |||
| pre.push({ | |||
| name: cur, | |||
| ...categorizeItem[cur], | |||
| examples: convertToObjectArray(categorizeItem[cur].examples), | |||
| }); | |||
| return pre; | |||
| }, []) | |||
| .sort((a, b) => a.index - b.index); | |||
| @@ -424,7 +443,7 @@ export const buildCategorizeListFromObject = ( | |||
| { | |||
| "name": "Categorize 1", | |||
| "description": "111", | |||
| "examples": "ddd", | |||
| "examples": ["ddd"], | |||
| "to": "Retrieval:LazyEelsStick" | |||
| } | |||
| ] | |||
| @@ -433,24 +452,11 @@ export const buildCategorizeListFromObject = ( | |||
| export const buildCategorizeObjectFromList = (list: Array<ICategorizeItem>) => { | |||
| return list.reduce<ICategorizeItemResult>((pre, cur) => { | |||
| if (cur?.name) { | |||
| pre[cur.name] = omit(cur, 'name'); | |||
| pre[cur.name] = { | |||
| ...omit(cur, 'name', 'examples'), | |||
| examples: convertToStringArray(cur.examples), | |||
| }; | |||
| } | |||
| return pre; | |||
| }, {}); | |||
| }; | |||
| export function convertToStringArray( | |||
| list: Array<{ value: string | number | boolean }>, | |||
| ) { | |||
| if (!Array.isArray(list)) { | |||
| return []; | |||
| } | |||
| return list.map((x) => x.value); | |||
| } | |||
| export function convertToObjectArray(list: Array<string | number | boolean>) { | |||
| if (!Array.isArray(list)) { | |||
| return []; | |||
| } | |||
| return list.map((x) => ({ value: x })); | |||
| } | |||