### What problem does this PR solve? Feat: Render MessageForm with shadcn-ui. #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.17.1
| import { LanguageList, LanguageMap } from '@/constants/common'; | import { LanguageList, LanguageMap } from '@/constants/common'; | ||||
| import { useChangeLanguage } from '@/hooks/logic-hooks'; | import { useChangeLanguage } from '@/hooks/logic-hooks'; | ||||
| import { useFetchUserInfo } from '@/hooks/user-setting-hooks'; | import { useFetchUserInfo } from '@/hooks/user-setting-hooks'; | ||||
| import { MoonIcon, SunIcon } from 'lucide-react'; | |||||
| import { CircleHelp, MoonIcon, SunIcon } from 'lucide-react'; | |||||
| import styled from './index.less'; | import styled from './index.less'; | ||||
| const Circle = ({ children, ...restProps }: React.PropsWithChildren) => { | const Circle = ({ children, ...restProps }: React.PropsWithChildren) => { | ||||
| window.open('https://github.com/infiniflow/ragflow', 'target'); | window.open('https://github.com/infiniflow/ragflow', 'target'); | ||||
| }; | }; | ||||
| const handleDocHelpCLick = () => { | |||||
| window.open('https://ragflow.io/docs/dev/category/guides', 'target'); | |||||
| }; | |||||
| const RightToolBar = () => { | const RightToolBar = () => { | ||||
| const { t } = useTranslate('common'); | const { t } = useTranslate('common'); | ||||
| const changeLanguage = useChangeLanguage(); | const changeLanguage = useChangeLanguage(); | ||||
| <Circle> | <Circle> | ||||
| <GithubOutlined onClick={handleGithubCLick} /> | <GithubOutlined onClick={handleGithubCLick} /> | ||||
| </Circle> | </Circle> | ||||
| <Circle> | |||||
| <CircleHelp className="size-4" onClick={handleDocHelpCLick} /> | |||||
| </Circle> | |||||
| <Circle> | <Circle> | ||||
| {theme === 'dark' ? ( | {theme === 'dark' ? ( | ||||
| <MoonIcon onClick={onMoonClick} size={20} /> | <MoonIcon onClick={onMoonClick} size={20} /> |
| }, | }, | ||||
| [Operator.PubMed]: { | [Operator.PubMed]: { | ||||
| component: PubMedForm, | component: PubMedForm, | ||||
| defaultValues: {}, | |||||
| schema: z.object({}), | |||||
| defaultValues: { top_n: 10 }, | |||||
| schema: z.object({ | |||||
| top_n: z.number(), | |||||
| email: z.string(), | |||||
| }), | |||||
| }, | }, | ||||
| [Operator.ArXiv]: { | [Operator.ArXiv]: { | ||||
| component: ArXivForm, | component: ArXivForm, |
| import { useTranslate } from '@/hooks/common-hooks'; | |||||
| import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; | |||||
| import { Button, Form, Input } from 'antd'; | |||||
| import { IOperatorForm } from '../../interface'; | |||||
| import { Button } from '@/components/ui/button'; | |||||
| import { | |||||
| Form, | |||||
| FormControl, | |||||
| FormField, | |||||
| FormItem, | |||||
| FormLabel, | |||||
| FormMessage, | |||||
| } from '@/components/ui/form'; | |||||
| import { Textarea } from '@/components/ui/textarea'; | |||||
| import { PlusCircle, Trash2 } from 'lucide-react'; | |||||
| import { useFieldArray } from 'react-hook-form'; | |||||
| import { useTranslation } from 'react-i18next'; | |||||
| import { INextOperatorForm } from '../../interface'; | |||||
| import styles from './index.less'; | |||||
| const formItemLayout = { | |||||
| labelCol: { | |||||
| sm: { span: 6 }, | |||||
| }, | |||||
| wrapperCol: { | |||||
| sm: { span: 18 }, | |||||
| }, | |||||
| }; | |||||
| const formItemLayoutWithOutLabel = { | |||||
| wrapperCol: { | |||||
| sm: { span: 18, offset: 6 }, | |||||
| }, | |||||
| }; | |||||
| const MessageForm = ({ onValuesChange, form }: IOperatorForm) => { | |||||
| const { t } = useTranslate('flow'); | |||||
| const MessageForm = ({ form }: INextOperatorForm) => { | |||||
| const { t } = useTranslation(); | |||||
| const { fields, append, remove } = useFieldArray({ | |||||
| name: 'messages', | |||||
| control: form.control, | |||||
| }); | |||||
| return ( | return ( | ||||
| <Form | |||||
| name="basic" | |||||
| {...formItemLayoutWithOutLabel} | |||||
| onValuesChange={onValuesChange} | |||||
| autoComplete="off" | |||||
| form={form} | |||||
| > | |||||
| <Form.List name="messages"> | |||||
| {(fields, { add, remove }, {}) => ( | |||||
| <> | |||||
| <Form {...form}> | |||||
| <form | |||||
| className="space-y-6" | |||||
| onSubmit={(e) => { | |||||
| e.preventDefault(); | |||||
| }} | |||||
| > | |||||
| <FormItem> | |||||
| <FormLabel>{t('flow.msg')}</FormLabel> | |||||
| <div className="space-y-4"> | |||||
| {fields.map((field, index) => ( | {fields.map((field, index) => ( | ||||
| <Form.Item | |||||
| {...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)} | |||||
| label={index === 0 ? t('msg') : ''} | |||||
| required={false} | |||||
| key={field.key} | |||||
| > | |||||
| <Form.Item | |||||
| {...field} | |||||
| validateTrigger={['onChange', 'onBlur']} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| whitespace: true, | |||||
| message: t('messageMsg'), | |||||
| }, | |||||
| ]} | |||||
| noStyle | |||||
| > | |||||
| <Input.TextArea | |||||
| rows={4} | |||||
| placeholder={t('messagePlaceholder')} | |||||
| style={{ width: '80%' }} | |||||
| /> | |||||
| </Form.Item> | |||||
| {fields.length > 1 ? ( | |||||
| <MinusCircleOutlined | |||||
| className={styles.dynamicDeleteButton} | |||||
| onClick={() => remove(field.name)} | |||||
| /> | |||||
| ) : null} | |||||
| </Form.Item> | |||||
| <div key={field.id} className="flex items-start gap-2"> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name={`messages.${index}`} | |||||
| render={({ field }) => ( | |||||
| <FormItem className="flex-1"> | |||||
| <FormControl> | |||||
| <Textarea | |||||
| {...field} | |||||
| placeholder={t('flow.messagePlaceholder')} | |||||
| rows={5} | |||||
| /> | |||||
| </FormControl> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| {fields.length > 1 && ( | |||||
| <Button | |||||
| variant="ghost" | |||||
| size="icon" | |||||
| type="button" | |||||
| onClick={() => remove(index)} | |||||
| className="cursor-pointer text-colors-text-functional-danger" | |||||
| > | |||||
| <Trash2 className="h-4 w-4" /> | |||||
| </Button> | |||||
| )} | |||||
| </div> | |||||
| ))} | ))} | ||||
| <Form.Item> | |||||
| <Button | |||||
| type="dashed" | |||||
| onClick={() => add()} | |||||
| style={{ width: '80%' }} | |||||
| icon={<PlusOutlined />} | |||||
| > | |||||
| {t('addMessage')} | |||||
| </Button> | |||||
| </Form.Item> | |||||
| </> | |||||
| )} | |||||
| </Form.List> | |||||
| <Button | |||||
| type="button" | |||||
| variant="outline" | |||||
| onClick={() => append(' ')} // "" will cause the inability to add, refer to: https://github.com/orgs/react-hook-form/discussions/8485#discussioncomment-2961861 | |||||
| className="w-full mt-4" | |||||
| > | |||||
| <PlusCircle className="mr-2 h-4 w-4" /> | |||||
| {t('flow.addMessage')} | |||||
| </Button> | |||||
| </div> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| </form> | |||||
| </Form> | </Form> | ||||
| ); | ); | ||||
| }; | }; |
| import TopNItem from '@/components/top-n-item'; | |||||
| import { TopNFormField } from '@/components/top-n-item'; | |||||
| import { | |||||
| Form, | |||||
| FormControl, | |||||
| FormField, | |||||
| FormItem, | |||||
| FormLabel, | |||||
| FormMessage, | |||||
| } from '@/components/ui/form'; | |||||
| import { Input } from '@/components/ui/input'; | |||||
| import { useTranslate } from '@/hooks/common-hooks'; | import { useTranslate } from '@/hooks/common-hooks'; | ||||
| import { Form, Input } from 'antd'; | |||||
| import { IOperatorForm } from '../../interface'; | |||||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||||
| import { INextOperatorForm } from '../../interface'; | |||||
| import { DynamicInputVariable } from '../components/next-dynamic-input-variable'; | |||||
| const PubMedForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||||
| const PubMedForm = ({ form, node }: INextOperatorForm) => { | |||||
| const { t } = useTranslate('flow'); | const { t } = useTranslate('flow'); | ||||
| return ( | return ( | ||||
| <Form | |||||
| name="basic" | |||||
| autoComplete="off" | |||||
| form={form} | |||||
| onValuesChange={onValuesChange} | |||||
| layout={'vertical'} | |||||
| > | |||||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||||
| <TopNItem initialValue={10}></TopNItem> | |||||
| <Form.Item | |||||
| label={t('email')} | |||||
| name={'email'} | |||||
| tooltip={t('emailTip')} | |||||
| rules={[{ type: 'email' }]} | |||||
| <Form {...form}> | |||||
| <form | |||||
| className="space-y-6" | |||||
| onSubmit={(e) => { | |||||
| e.preventDefault(); | |||||
| }} | |||||
| > | > | ||||
| <Input></Input> | |||||
| </Form.Item> | |||||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||||
| <TopNFormField></TopNFormField> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name="email" | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel tooltip={t('emailTip')}>{t('email')}</FormLabel> | |||||
| <FormControl> | |||||
| <Input {...field} /> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| </form> | |||||
| </Form> | </Form> | ||||
| ); | ); | ||||
| }; | }; |