### What problem does this PR solve? Feat: Use shadcn-ui to build GenerateForm. #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.17.0
| @@ -1,5 +1,14 @@ | |||
| import { Form, InputNumber } from 'antd'; | |||
| import { useFormContext } from 'react-hook-form'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { | |||
| FormControl, | |||
| FormField, | |||
| FormItem, | |||
| FormLabel, | |||
| FormMessage, | |||
| } from './ui/form'; | |||
| import { Input } from './ui/input'; | |||
| const MessageHistoryWindowSizeItem = ({ | |||
| initialValue, | |||
| @@ -21,3 +30,24 @@ const MessageHistoryWindowSizeItem = ({ | |||
| }; | |||
| export default MessageHistoryWindowSizeItem; | |||
| export function MessageHistoryWindowSizeFormField() { | |||
| const form = useFormContext(); | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <FormField | |||
| control={form.control} | |||
| name={'message_history_window_size'} | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('flow.messageHistoryWindowSize')}</FormLabel> | |||
| <FormControl> | |||
| <Input {...field} type={'number'}></Input> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| ); | |||
| } | |||
| @@ -52,10 +52,12 @@ export default SimilaritySlider; | |||
| interface SimilaritySliderFormFieldProps { | |||
| vectorSimilarityWeightName?: string; | |||
| isTooltipShown?: boolean; | |||
| } | |||
| export function SimilaritySliderFormField({ | |||
| vectorSimilarityWeightName = 'vector_similarity_weight', | |||
| isTooltipShown, | |||
| }: SimilaritySliderFormFieldProps) { | |||
| const form = useFormContext(); | |||
| const { t } = useTranslate('knowledgeDetails'); | |||
| @@ -67,7 +69,9 @@ export function SimilaritySliderFormField({ | |||
| name={'similarity_threshold'} | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('similarityThreshold')}</FormLabel> | |||
| <FormLabel tooltip={isTooltipShown && t('similarityThresholdTip')}> | |||
| {t('similarityThreshold')} | |||
| </FormLabel> | |||
| <FormControl> | |||
| <SingleFormSlider | |||
| {...field} | |||
| @@ -84,7 +88,11 @@ export function SimilaritySliderFormField({ | |||
| name={vectorSimilarityWeightName} | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('vectorSimilarityWeight')}</FormLabel> | |||
| <FormLabel | |||
| tooltip={isTooltipShown && t('vectorSimilarityWeightTip')} | |||
| > | |||
| {t('vectorSimilarityWeight')} | |||
| </FormLabel> | |||
| <FormControl> | |||
| <SingleFormSlider | |||
| {...field} | |||
| @@ -14,6 +14,8 @@ import { | |||
| import { Label } from '@/components/ui/label'; | |||
| import { cn } from '@/lib/utils'; | |||
| import { Info } from 'lucide-react'; | |||
| import { Tooltip, TooltipContent, TooltipTrigger } from './tooltip'; | |||
| const Form = FormProvider; | |||
| @@ -88,17 +90,31 @@ FormItem.displayName = 'FormItem'; | |||
| const FormLabel = React.forwardRef< | |||
| React.ElementRef<typeof LabelPrimitive.Root>, | |||
| React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> | |||
| >(({ className, ...props }, ref) => { | |||
| React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & { | |||
| tooltip?: React.ReactNode; | |||
| } | |||
| >(({ className, tooltip, ...props }, ref) => { | |||
| const { error, formItemId } = useFormField(); | |||
| return ( | |||
| <Label | |||
| ref={ref} | |||
| className={cn(error && 'text-destructive', className)} | |||
| className={cn(error && 'text-destructive', className, 'flex')} | |||
| htmlFor={formItemId} | |||
| {...props} | |||
| /> | |||
| > | |||
| {props.children} | |||
| {tooltip && ( | |||
| <Tooltip> | |||
| <TooltipTrigger> | |||
| <Info className="size-3 ml-2" /> | |||
| </TooltipTrigger> | |||
| <TooltipContent> | |||
| <p>{tooltip}</p> | |||
| </TooltipContent> | |||
| </Tooltip> | |||
| )} | |||
| </Label> | |||
| ); | |||
| }); | |||
| FormLabel.displayName = 'FormLabel'; | |||
| @@ -122,7 +122,7 @@ const FormSheet = ({ | |||
| <span>{t(`${lowerFirst(operatorName)}Description`)}</span> | |||
| </section> | |||
| </SheetHeader> | |||
| <section> | |||
| <section className="pt-4"> | |||
| {visible && ( | |||
| <FlowFormContext.Provider value={node}> | |||
| <OperatorForm | |||
| @@ -82,8 +82,15 @@ export function useFormConfigMap() { | |||
| }, | |||
| [Operator.Generate]: { | |||
| component: GenerateForm, | |||
| defaultValues: {}, | |||
| schema: z.object({}), | |||
| defaultValues: { | |||
| cite: true, | |||
| prompt: t('flow.promptText'), | |||
| }, | |||
| schema: z.object({ | |||
| prompt: z.string().min(1, { | |||
| message: t('flow.promptMessage'), | |||
| }), | |||
| }), | |||
| }, | |||
| [Operator.Answer]: { | |||
| component: AnswerForm, | |||
| @@ -121,7 +121,7 @@ export function DynamicInputVariable({ node }: IProps) { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <Collapsible defaultOpen className="group/collapsible pt-4"> | |||
| <Collapsible defaultOpen className="group/collapsible"> | |||
| <CollapsibleTrigger className="flex justify-between w-full pb-2"> | |||
| <span className="font-bold text-2xl text-colors-text-neutral-strong"> | |||
| {t('flow.input')} | |||
| @@ -1,101 +0,0 @@ | |||
| import { EditableCell, EditableRow } from '@/components/editable-cell'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { RAGFlowNodeType } from '@/interfaces/database/flow'; | |||
| import { DeleteOutlined } from '@ant-design/icons'; | |||
| import { Button, Flex, Select, Table, TableProps } from 'antd'; | |||
| import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query'; | |||
| import { IGenerateParameter } from '../../interface'; | |||
| import { useHandleOperateParameters } from './hooks'; | |||
| import styles from './index.less'; | |||
| interface IProps { | |||
| node?: RAGFlowNodeType; | |||
| } | |||
| const components = { | |||
| body: { | |||
| row: EditableRow, | |||
| cell: EditableCell, | |||
| }, | |||
| }; | |||
| const DynamicParameters = ({ node }: IProps) => { | |||
| const nodeId = node?.id; | |||
| const { t } = useTranslate('flow'); | |||
| const options = useBuildComponentIdSelectOptions(nodeId, node?.parentId); | |||
| const { | |||
| dataSource, | |||
| handleAdd, | |||
| handleRemove, | |||
| handleSave, | |||
| handleComponentIdChange, | |||
| } = useHandleOperateParameters(nodeId!); | |||
| const columns: TableProps<IGenerateParameter>['columns'] = [ | |||
| { | |||
| title: t('key'), | |||
| dataIndex: 'key', | |||
| key: 'key', | |||
| width: '40%', | |||
| onCell: (record: IGenerateParameter) => ({ | |||
| record, | |||
| editable: true, | |||
| dataIndex: 'key', | |||
| title: 'key', | |||
| handleSave, | |||
| }), | |||
| }, | |||
| { | |||
| title: t('value'), | |||
| dataIndex: 'component_id', | |||
| key: 'component_id', | |||
| align: 'center', | |||
| width: '40%', | |||
| render(text, record) { | |||
| return ( | |||
| <Select | |||
| style={{ width: '100%' }} | |||
| allowClear | |||
| options={options} | |||
| value={text} | |||
| onChange={handleComponentIdChange(record)} | |||
| /> | |||
| ); | |||
| }, | |||
| }, | |||
| { | |||
| title: t('operation'), | |||
| dataIndex: 'operation', | |||
| width: 20, | |||
| key: 'operation', | |||
| align: 'center', | |||
| fixed: 'right', | |||
| render(_, record) { | |||
| return <DeleteOutlined onClick={handleRemove(record.id)} />; | |||
| }, | |||
| }, | |||
| ]; | |||
| return ( | |||
| <section> | |||
| <Flex justify="end"> | |||
| <Button size="small" onClick={handleAdd}> | |||
| {t('add')} | |||
| </Button> | |||
| </Flex> | |||
| <Table | |||
| dataSource={dataSource} | |||
| columns={columns} | |||
| rowKey={'id'} | |||
| className={styles.variableTable} | |||
| components={components} | |||
| rowClassName={() => styles.editableRow} | |||
| scroll={{ x: true }} | |||
| bordered | |||
| /> | |||
| </section> | |||
| ); | |||
| }; | |||
| export default DynamicParameters; | |||
| @@ -1,70 +0,0 @@ | |||
| import get from 'lodash/get'; | |||
| import { useCallback, useMemo } from 'react'; | |||
| import { v4 as uuid } from 'uuid'; | |||
| import { IGenerateParameter } from '../../interface'; | |||
| import useGraphStore from '../../store'; | |||
| export const useHandleOperateParameters = (nodeId: string) => { | |||
| const { getNode, updateNodeForm } = useGraphStore((state) => state); | |||
| const node = getNode(nodeId); | |||
| const dataSource: IGenerateParameter[] = useMemo( | |||
| () => get(node, 'data.form.parameters', []) as IGenerateParameter[], | |||
| [node], | |||
| ); | |||
| const handleComponentIdChange = useCallback( | |||
| (row: IGenerateParameter) => (value: string) => { | |||
| const newData = [...dataSource]; | |||
| const index = newData.findIndex((item) => row.id === item.id); | |||
| const item = newData[index]; | |||
| newData.splice(index, 1, { | |||
| ...item, | |||
| component_id: value, | |||
| }); | |||
| updateNodeForm(nodeId, { parameters: newData }); | |||
| }, | |||
| [updateNodeForm, nodeId, dataSource], | |||
| ); | |||
| const handleRemove = useCallback( | |||
| (id?: string) => () => { | |||
| const newData = dataSource.filter((item) => item.id !== id); | |||
| updateNodeForm(nodeId, { parameters: newData }); | |||
| }, | |||
| [updateNodeForm, nodeId, dataSource], | |||
| ); | |||
| const handleAdd = useCallback(() => { | |||
| updateNodeForm(nodeId, { | |||
| parameters: [ | |||
| ...dataSource, | |||
| { | |||
| id: uuid(), | |||
| key: '', | |||
| component_id: undefined, | |||
| }, | |||
| ], | |||
| }); | |||
| }, [dataSource, nodeId, updateNodeForm]); | |||
| const handleSave = (row: IGenerateParameter) => { | |||
| const newData = [...dataSource]; | |||
| const index = newData.findIndex((item) => row.id === item.id); | |||
| const item = newData[index]; | |||
| newData.splice(index, 1, { | |||
| ...item, | |||
| ...row, | |||
| }); | |||
| updateNodeForm(nodeId, { parameters: newData }); | |||
| }; | |||
| return { | |||
| handleAdd, | |||
| handleRemove, | |||
| handleComponentIdChange, | |||
| handleSave, | |||
| dataSource, | |||
| }; | |||
| }; | |||
| @@ -1,21 +0,0 @@ | |||
| .variableTable { | |||
| margin-top: 14px; | |||
| } | |||
| .editableRow { | |||
| :global(.editable-cell) { | |||
| position: relative; | |||
| } | |||
| :global(.editable-cell-value-wrap) { | |||
| padding: 5px 12px; | |||
| cursor: pointer; | |||
| height: 30px !important; | |||
| } | |||
| &:hover { | |||
| :global(.editable-cell-value-wrap) { | |||
| padding: 4px 11px; | |||
| border: 1px solid #d9d9d9; | |||
| border-radius: 2px; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,55 +1,76 @@ | |||
| import LLMSelect from '@/components/llm-select'; | |||
| import MessageHistoryWindowSizeItem from '@/components/message-history-window-size-item'; | |||
| import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item'; | |||
| import { PromptEditor } from '@/components/prompt-editor'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Form, Switch } from 'antd'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import { | |||
| Form, | |||
| FormControl, | |||
| FormField, | |||
| FormItem, | |||
| FormLabel, | |||
| FormMessage, | |||
| } from '@/components/ui/form'; | |||
| import { Switch } from '@/components/ui/switch'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { INextOperatorForm } from '../../interface'; | |||
| const GenerateForm = ({ onValuesChange, form }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| const GenerateForm = ({ form }: INextOperatorForm) => { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <Form.Item | |||
| name={'llm_id'} | |||
| label={t('model', { keyPrefix: 'chat' })} | |||
| tooltip={t('modelTip', { keyPrefix: 'chat' })} | |||
| <Form {...form}> | |||
| <form | |||
| className="space-y-6" | |||
| onSubmit={(e) => { | |||
| e.preventDefault(); | |||
| }} | |||
| > | |||
| <LLMSelect></LLMSelect> | |||
| </Form.Item> | |||
| <Form.Item | |||
| name={['prompt']} | |||
| label={t('systemPrompt')} | |||
| initialValue={t('promptText')} | |||
| tooltip={t('promptTip', { keyPrefix: 'knowledgeConfiguration' })} | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: t('promptMessage'), | |||
| }, | |||
| ]} | |||
| > | |||
| {/* <Input.TextArea rows={8}></Input.TextArea> */} | |||
| <PromptEditor></PromptEditor> | |||
| </Form.Item> | |||
| <Form.Item | |||
| name={['cite']} | |||
| label={t('cite')} | |||
| initialValue={true} | |||
| valuePropName="checked" | |||
| tooltip={t('citeTip')} | |||
| > | |||
| <Switch /> | |||
| </Form.Item> | |||
| <MessageHistoryWindowSizeItem | |||
| initialValue={12} | |||
| ></MessageHistoryWindowSizeItem> | |||
| <FormField | |||
| control={form.control} | |||
| name="llm_id" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel tooltip={t('chat.modelTip')}> | |||
| {t('chat.model')} | |||
| </FormLabel> | |||
| <FormControl> | |||
| <LLMSelect {...field} /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <FormField | |||
| control={form.control} | |||
| name="prompt" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel tooltip={t('knowledgeConfiguration.promptTip')}> | |||
| {t('flow.systemPrompt')} | |||
| </FormLabel> | |||
| <FormControl> | |||
| <PromptEditor {...field} /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <FormField | |||
| control={form.control} | |||
| name="cite" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel tooltip={t('flow.citeTip')}> | |||
| {t('flow.cite')} | |||
| </FormLabel> | |||
| <FormControl> | |||
| <Switch {...field} /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <MessageHistoryWindowSizeFormField></MessageHistoryWindowSizeFormField> | |||
| </form> | |||
| </Form> | |||
| ); | |||
| }; | |||
| @@ -26,7 +26,10 @@ const RetrievalForm = ({ form, node }: INextOperatorForm) => { | |||
| }} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <SimilaritySliderFormField vectorSimilarityWeightName="keywords_similarity_weight"></SimilaritySliderFormField> | |||
| <SimilaritySliderFormField | |||
| vectorSimilarityWeightName="keywords_similarity_weight" | |||
| isTooltipShown | |||
| ></SimilaritySliderFormField> | |||
| <TopNFormField></TopNFormField> | |||
| <RerankFormFields></RerankFormFields> | |||
| <KnowledgeBaseFormField></KnowledgeBaseFormField> | |||