### 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
| export interface ICategorizeItem { | |||||
| name: string; | |||||
| description?: string; | |||||
| examples?: { value: string }[]; | |||||
| index: number; | |||||
| } | |||||
| export type ICategorizeItemResult = Record< | |||||
| string, | |||||
| Omit<ICategorizeItem, 'name' | 'examples'> & { examples: string[] } | |||||
| >; |
| FormLabel, | FormLabel, | ||||
| FormMessage, | FormMessage, | ||||
| } from '@/components/ui/form'; | } from '@/components/ui/form'; | ||||
| import { BlurInput, Input } from '@/components/ui/input'; | |||||
| import { Input } from '@/components/ui/input'; | |||||
| import { BlurTextarea } from '@/components/ui/textarea'; | import { BlurTextarea } from '@/components/ui/textarea'; | ||||
| import { useTranslate } from '@/hooks/common-hooks'; | import { useTranslate } from '@/hooks/common-hooks'; | ||||
| import { PlusOutlined } from '@ant-design/icons'; | import { PlusOutlined } from '@ant-design/icons'; | ||||
| import { UseFormReturn, useFieldArray, useFormContext } from 'react-hook-form'; | 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 DynamicExample from './dynamic-example'; | |||||
| interface IProps { | interface IProps { | ||||
| nodeId?: string; | nodeId?: string; | ||||
| <FormItem> | <FormItem> | ||||
| <FormLabel>{t('categoryName')}</FormLabel> | <FormLabel>{t('categoryName')}</FormLabel> | ||||
| <FormControl> | <FormControl> | ||||
| <BlurInput {...field}></BlurInput> | |||||
| {/* <NameInput | |||||
| <NameInput | |||||
| {...field} | {...field} | ||||
| otherNames={getOtherFieldValues(form, 'items', index, 'name')} | otherNames={getOtherFieldValues(form, 'items', index, 'name')} | ||||
| validate={(error?: string) => { | validate={(error?: string) => { | ||||
| form.clearErrors(fieldName); | form.clearErrors(fieldName); | ||||
| } | } | ||||
| }} | }} | ||||
| ></NameInput> */} | |||||
| ></NameInput> | |||||
| </FormControl> | </FormControl> | ||||
| <FormMessage /> | <FormMessage /> | ||||
| </FormItem> | </FormItem> | ||||
| </FormItem> | </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> | </section> | ||||
| ); | ); | ||||
| }; | }; | ||||
| append({ | append({ | ||||
| name: humanId(), | name: humanId(), | ||||
| description: '', | description: '', | ||||
| // examples: [], | |||||
| examples: [{ value: '' }], | |||||
| }); | }); | ||||
| if (nodeId) updateNodeInternals(nodeId); | if (nodeId) updateNodeInternals(nodeId); | ||||
| }; | }; |
| 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); |
| import { FormContainer } from '@/components/form-container'; | |||||
| import { LargeModelFormField } from '@/components/large-model-form-field'; | import { LargeModelFormField } from '@/components/large-model-form-field'; | ||||
| import { LlmSettingSchema } from '@/components/llm-setting-items/next'; | import { LlmSettingSchema } from '@/components/llm-setting-items/next'; | ||||
| import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item'; | import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item'; | ||||
| const values = useValues(node); | const values = useValues(node); | ||||
| const FormSchema = z.object({ | const FormSchema = z.object({ | ||||
| input: z.string().optional(), | |||||
| parameter: z.string().optional(), | parameter: z.string().optional(), | ||||
| ...LlmSettingSchema, | ...LlmSettingSchema, | ||||
| message_history_window_size: z.coerce.number(), | message_history_window_size: z.coerce.number(), | ||||
| .object({ | .object({ | ||||
| name: z.string().min(1, t('flow.nameMessage')).trim(), | name: z.string().min(1, t('flow.nameMessage')).trim(), | ||||
| description: z.string().optional(), | description: z.string().optional(), | ||||
| // examples: z | |||||
| // .array( | |||||
| // z.object({ | |||||
| // value: z.string(), | |||||
| // }), | |||||
| // ) | |||||
| // .optional(), | |||||
| examples: z | |||||
| .array( | |||||
| z.object({ | |||||
| value: z.string(), | |||||
| }), | |||||
| ) | |||||
| .optional(), | |||||
| }) | }) | ||||
| .optional(), | .optional(), | ||||
| ), | ), | ||||
| e.preventDefault(); | 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 | <MessageHistoryWindowSizeFormField | ||||
| useBlurInput | useBlurInput | ||||
| ></MessageHistoryWindowSizeFormField> | ></MessageHistoryWindowSizeFormField> |
| import { | import { | ||||
| DSLComponents, | |||||
| ICategorizeItem, | ICategorizeItem, | ||||
| ICategorizeItemResult, | ICategorizeItemResult, | ||||
| RAGFlowNodeType, | |||||
| } from '@/interfaces/database/flow'; | |||||
| } from '@/interfaces/database/agent'; | |||||
| import { DSLComponents, RAGFlowNodeType } from '@/interfaces/database/flow'; | |||||
| import { removeUselessFieldsFromValues } from '@/utils/form'; | import { removeUselessFieldsFromValues } from '@/utils/form'; | ||||
| import { Edge, Node, Position, XYPosition } from '@xyflow/react'; | import { Edge, Node, Position, XYPosition } from '@xyflow/react'; | ||||
| import { FormInstance, FormListFieldData } from 'antd'; | import { FormInstance, FormListFieldData } from 'antd'; | ||||
| }; | }; | ||||
| }; | }; | ||||
| 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 | * convert the following object into a list | ||||
| * | * | ||||
| .reduce<Array<ICategorizeItem>>((pre, cur) => { | .reduce<Array<ICategorizeItem>>((pre, cur) => { | ||||
| // synchronize edge data to the to field | // 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; | return pre; | ||||
| }, []) | }, []) | ||||
| .sort((a, b) => a.index - b.index); | .sort((a, b) => a.index - b.index); | ||||
| { | { | ||||
| "name": "Categorize 1", | "name": "Categorize 1", | ||||
| "description": "111", | "description": "111", | ||||
| "examples": "ddd", | |||||
| "examples": ["ddd"], | |||||
| "to": "Retrieval:LazyEelsStick" | "to": "Retrieval:LazyEelsStick" | ||||
| } | } | ||||
| ] | ] | ||||
| export const buildCategorizeObjectFromList = (list: Array<ICategorizeItem>) => { | export const buildCategorizeObjectFromList = (list: Array<ICategorizeItem>) => { | ||||
| return list.reduce<ICategorizeItemResult>((pre, cur) => { | return list.reduce<ICategorizeItemResult>((pre, cur) => { | ||||
| if (cur?.name) { | if (cur?.name) { | ||||
| pre[cur.name] = omit(cur, 'name'); | |||||
| pre[cur.name] = { | |||||
| ...omit(cur, 'name', 'examples'), | |||||
| examples: convertToStringArray(cur.examples), | |||||
| }; | |||||
| } | } | ||||
| return pre; | 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 })); | |||||
| } |