### What problem does this PR solve? Feat: Add invoke and github operators #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.20.0
| @@ -1308,6 +1308,7 @@ This delimiter is used to split the input text into several text pieces echo of | |||
| management: 'Management', | |||
| import: 'Import', | |||
| export: 'Export', | |||
| seconds: 'Seconds', | |||
| }, | |||
| llmTools: { | |||
| bad_calculator: { | |||
| @@ -109,6 +109,9 @@ function AccordionOperators() { | |||
| Operator.Wikipedia, | |||
| Operator.GoogleScholar, | |||
| Operator.ArXiv, | |||
| Operator.PubMed, | |||
| Operator.GitHub, | |||
| Operator.Invoke, | |||
| ]} | |||
| ></OperatorItemList> | |||
| </AccordionContent> | |||
| @@ -372,9 +372,15 @@ export const initialWikipediaValues = { | |||
| }; | |||
| export const initialPubMedValues = { | |||
| top_n: 10, | |||
| top_n: 12, | |||
| email: '', | |||
| ...initialQueryBaseValues, | |||
| query: AgentGlobals.SysQuery, | |||
| outputs: { | |||
| formalized_content: { | |||
| value: '', | |||
| type: 'string', | |||
| }, | |||
| }, | |||
| }; | |||
| export const initialArXivValues = { | |||
| @@ -444,7 +450,17 @@ export const initialDeepLValues = { | |||
| export const initialGithubValues = { | |||
| top_n: 5, | |||
| ...initialQueryBaseValues, | |||
| query: AgentGlobals.SysQuery, | |||
| outputs: { | |||
| formalized_content: { | |||
| value: '', | |||
| type: 'string', | |||
| }, | |||
| json: { | |||
| value: [], | |||
| type: 'Array<Object>', | |||
| }, | |||
| }, | |||
| }; | |||
| export const initialBaiduFanyiValues = { | |||
| @@ -540,7 +556,7 @@ export const initialCrawlerValues = { | |||
| }; | |||
| export const initialInvokeValues = { | |||
| url: 'http://', | |||
| url: '', | |||
| method: 'GET', | |||
| timeout: 60, | |||
| headers: `{ | |||
| @@ -548,8 +564,9 @@ export const initialInvokeValues = { | |||
| "Cache-Control": "no-cache", | |||
| "Connection": "keep-alive" | |||
| }`, | |||
| proxy: 'http://', | |||
| proxy: '', | |||
| clean_html: false, | |||
| variables: [], | |||
| }; | |||
| export const initialTemplateValues = { | |||
| @@ -852,7 +869,7 @@ export const NodeMap = { | |||
| [Operator.TuShare]: 'ragNode', | |||
| [Operator.Note]: 'noteNode', | |||
| [Operator.Crawler]: 'ragNode', | |||
| [Operator.Invoke]: 'invokeNode', | |||
| [Operator.Invoke]: 'ragNode', | |||
| [Operator.Template]: 'templateNode', | |||
| [Operator.Email]: 'ragNode', | |||
| [Operator.Iteration]: 'group', | |||
| @@ -126,7 +126,7 @@ function AgentForm({ node }: INextOperatorForm) { | |||
| name={`sys_prompt`} | |||
| render={({ field }) => ( | |||
| <FormItem className="flex-1"> | |||
| <FormLabel>Prompt</FormLabel> | |||
| <FormLabel>System Prompt</FormLabel> | |||
| <FormControl> | |||
| <PromptEditor | |||
| {...field} | |||
| @@ -39,13 +39,7 @@ const Menus = [ | |||
| }, | |||
| { | |||
| label: 'Developer', | |||
| list: [ | |||
| Operator.GitHub, | |||
| Operator.ExeSQL, | |||
| Operator.Invoke, | |||
| Operator.Code, | |||
| Operator.Retrieval, | |||
| ], | |||
| list: [Operator.GitHub, Operator.ExeSQL, Operator.Code, Operator.Retrieval], | |||
| }, | |||
| ]; | |||
| @@ -1,21 +1,52 @@ | |||
| import TopNItem from '@/components/top-n-item'; | |||
| import { Form } from 'antd'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| import { FormContainer } from '@/components/form-container'; | |||
| import { TopNFormField } from '@/components/top-n-item'; | |||
| import { Form } from '@/components/ui/form'; | |||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||
| import { memo } from 'react'; | |||
| import { useForm } from 'react-hook-form'; | |||
| import { z } from 'zod'; | |||
| import { initialGithubValues } from '../../constant'; | |||
| import { useFormValues } from '../../hooks/use-form-values'; | |||
| import { useWatchFormChange } from '../../hooks/use-watch-form-change'; | |||
| import { INextOperatorForm } from '../../interface'; | |||
| import { buildOutputList } from '../../utils/build-output-list'; | |||
| import { FormWrapper } from '../components/form-wrapper'; | |||
| import { Output } from '../components/output'; | |||
| import { QueryVariable } from '../components/query-variable'; | |||
| export const FormSchema = z.object({ | |||
| query: z.string(), | |||
| top_n: z.number(), | |||
| }); | |||
| const outputList = buildOutputList(initialGithubValues.outputs); | |||
| function GithubForm({ node }: INextOperatorForm) { | |||
| const defaultValues = useFormValues(initialGithubValues, node); | |||
| const form = useForm<z.infer<typeof FormSchema>>({ | |||
| defaultValues, | |||
| resolver: zodResolver(FormSchema), | |||
| mode: 'onChange', | |||
| }); | |||
| useWatchFormChange(node?.id, form); | |||
| const GithubForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <TopNItem initialValue={5}></TopNItem> | |||
| <Form {...form}> | |||
| <FormWrapper> | |||
| <FormContainer> | |||
| <QueryVariable></QueryVariable> | |||
| </FormContainer> | |||
| <FormContainer> | |||
| <TopNFormField></TopNFormField> | |||
| </FormContainer> | |||
| </FormWrapper> | |||
| <div className="p-5"> | |||
| <Output list={outputList}></Output> | |||
| </div> | |||
| </Form> | |||
| ); | |||
| }; | |||
| } | |||
| export default GithubForm; | |||
| export default memo(GithubForm); | |||
| @@ -1,131 +0,0 @@ | |||
| import { EditableCell, EditableRow } from '@/components/editable-cell'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { DeleteOutlined } from '@ant-design/icons'; | |||
| import { Button, Collapse, Flex, Input, Select, Table, TableProps } from 'antd'; | |||
| import { trim } from 'lodash'; | |||
| import { useBuildVariableOptions } from '../../hooks/use-get-begin-query'; | |||
| import { IInvokeVariable } from '../../interface'; | |||
| import { useHandleOperateParameters } from './hooks'; | |||
| import { RAGFlowNodeType } from '@/interfaces/database/flow'; | |||
| import styles from './index.less'; | |||
| interface IProps { | |||
| node?: RAGFlowNodeType; | |||
| } | |||
| const components = { | |||
| body: { | |||
| row: EditableRow, | |||
| cell: EditableCell, | |||
| }, | |||
| }; | |||
| const DynamicVariablesForm = ({ node }: IProps) => { | |||
| const nodeId = node?.id; | |||
| const { t } = useTranslate('flow'); | |||
| const options = useBuildVariableOptions(nodeId, node?.parentId); | |||
| const { | |||
| dataSource, | |||
| handleAdd, | |||
| handleRemove, | |||
| handleSave, | |||
| handleComponentIdChange, | |||
| handleValueChange, | |||
| } = useHandleOperateParameters(nodeId!); | |||
| const columns: TableProps<IInvokeVariable>['columns'] = [ | |||
| { | |||
| title: t('key'), | |||
| dataIndex: 'key', | |||
| key: 'key', | |||
| onCell: (record: IInvokeVariable) => ({ | |||
| record, | |||
| editable: true, | |||
| dataIndex: 'key', | |||
| title: 'key', | |||
| handleSave, | |||
| }), | |||
| }, | |||
| { | |||
| title: t('componentId'), | |||
| dataIndex: 'component_id', | |||
| key: 'component_id', | |||
| align: 'center', | |||
| width: 140, | |||
| render(text, record) { | |||
| return ( | |||
| <Select | |||
| style={{ width: '100%' }} | |||
| allowClear | |||
| options={options} | |||
| value={text} | |||
| disabled={trim(record.value) !== ''} | |||
| onChange={handleComponentIdChange(record)} | |||
| /> | |||
| ); | |||
| }, | |||
| }, | |||
| { | |||
| title: t('value'), | |||
| dataIndex: 'value', | |||
| key: 'value', | |||
| align: 'center', | |||
| width: 140, | |||
| render(text, record) { | |||
| return ( | |||
| <Input | |||
| value={text} | |||
| disabled={!!record.component_id} | |||
| onChange={handleValueChange(record)} | |||
| /> | |||
| ); | |||
| }, | |||
| }, | |||
| { | |||
| title: t('operation'), | |||
| dataIndex: 'operation', | |||
| width: 20, | |||
| key: 'operation', | |||
| align: 'center', | |||
| fixed: 'right', | |||
| render(_, record) { | |||
| return <DeleteOutlined onClick={handleRemove(record.id)} />; | |||
| }, | |||
| }, | |||
| ]; | |||
| return ( | |||
| <Collapse | |||
| className={styles.dynamicParameterVariable} | |||
| defaultActiveKey={['1']} | |||
| items={[ | |||
| { | |||
| key: '1', | |||
| label: ( | |||
| <Flex justify={'space-between'}> | |||
| <span className={styles.title}>{t('parameter')}</span> | |||
| <Button size="small" onClick={handleAdd}> | |||
| {t('add')} | |||
| </Button> | |||
| </Flex> | |||
| ), | |||
| children: ( | |||
| <Table | |||
| dataSource={dataSource} | |||
| columns={columns} | |||
| rowKey={'id'} | |||
| components={components} | |||
| rowClassName={() => styles.editableRow} | |||
| scroll={{ x: true }} | |||
| bordered | |||
| /> | |||
| ), | |||
| }, | |||
| ]} | |||
| /> | |||
| ); | |||
| }; | |||
| export default DynamicVariablesForm; | |||
| @@ -1,8 +1,33 @@ | |||
| import { Collapse } from '@/components/collapse'; | |||
| import { FormContainer } from '@/components/form-container'; | |||
| import NumberInput from '@/components/originui/number-input'; | |||
| import { SelectWithSearch } from '@/components/originui/select-with-search'; | |||
| import { Button } from '@/components/ui/button'; | |||
| import { | |||
| Form, | |||
| FormControl, | |||
| FormField, | |||
| FormItem, | |||
| FormLabel, | |||
| FormMessage, | |||
| } from '@/components/ui/form'; | |||
| import { Input } from '@/components/ui/input'; | |||
| import { Switch } from '@/components/ui/switch'; | |||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||
| import Editor, { loader } from '@monaco-editor/react'; | |||
| import { Form, Input, InputNumber, Select, Space, Switch } from 'antd'; | |||
| import { Plus } from 'lucide-react'; | |||
| import { memo } from 'react'; | |||
| import { useForm, useWatch } from 'react-hook-form'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicVariablesForm from './dynamic-variables'; | |||
| import { initialInvokeValues } from '../../constant'; | |||
| import { useFormValues } from '../../hooks/use-form-values'; | |||
| import { useWatchFormChange } from '../../hooks/use-watch-form-change'; | |||
| import { INextOperatorForm } from '../../interface'; | |||
| import { FormWrapper } from '../components/form-wrapper'; | |||
| import { FormSchema, FormSchemaType } from './schema'; | |||
| import { useEditVariableRecord } from './use-edit-variable'; | |||
| import { VariableDialog } from './variable-dialog'; | |||
| import { VariableTable } from './variable-table'; | |||
| loader.config({ paths: { vs: '/vs' } }); | |||
| @@ -25,54 +50,170 @@ interface TimeoutInputProps { | |||
| const TimeoutInput = ({ value, onChange }: TimeoutInputProps) => { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <Space> | |||
| <InputNumber value={value} onChange={onChange} /> {t('common.s')} | |||
| </Space> | |||
| <div className="flex gap-2 items-center"> | |||
| <NumberInput value={value} onChange={onChange} /> {t('flow.seconds')} | |||
| </div> | |||
| ); | |||
| }; | |||
| const InvokeForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| function InvokeForm({ node }: INextOperatorForm) { | |||
| const { t } = useTranslation(); | |||
| const defaultValues = useFormValues(initialInvokeValues, node); | |||
| const form = useForm<FormSchemaType>({ | |||
| defaultValues, | |||
| resolver: zodResolver(FormSchema), | |||
| mode: 'onChange', | |||
| }); | |||
| const { | |||
| visible, | |||
| hideModal, | |||
| showModal, | |||
| ok, | |||
| currentRecord, | |||
| otherThanCurrentQuery, | |||
| handleDeleteRecord, | |||
| } = useEditVariableRecord({ | |||
| form, | |||
| node, | |||
| }); | |||
| const variables = useWatch({ control: form.control, name: 'variables' }); | |||
| useWatchFormChange(node?.id, form); | |||
| return ( | |||
| <> | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <Form.Item name={'url'} label={t('flow.url')}> | |||
| <Input /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| name={'method'} | |||
| label={t('flow.method')} | |||
| initialValue={Method.GET} | |||
| <Form {...form}> | |||
| <FormWrapper> | |||
| <FormContainer> | |||
| <FormField | |||
| control={form.control} | |||
| name="url" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('flow.url')}</FormLabel> | |||
| <FormControl> | |||
| <Input {...field} placeholder="http://" /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <FormField | |||
| control={form.control} | |||
| name="method" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('flow.method')}</FormLabel> | |||
| <FormControl> | |||
| <SelectWithSearch {...field} options={MethodOptions} /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <FormField | |||
| control={form.control} | |||
| name="timeout" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('flow.timeout')}</FormLabel> | |||
| <FormControl> | |||
| <TimeoutInput {...field} /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <FormField | |||
| control={form.control} | |||
| name="headers" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('flow.headers')}</FormLabel> | |||
| <FormControl> | |||
| <Editor | |||
| height={200} | |||
| defaultLanguage="json" | |||
| theme="vs-dark" | |||
| {...field} | |||
| /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <FormField | |||
| control={form.control} | |||
| name="proxy" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('flow.proxy')}</FormLabel> | |||
| <FormControl> | |||
| <Input {...field} /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <FormField | |||
| control={form.control} | |||
| name="clean_html" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel tooltip={t('flow.cleanHtmlTip')}> | |||
| {t('flow.cleanHtml')} | |||
| </FormLabel> | |||
| <FormControl> | |||
| <Switch | |||
| onCheckedChange={field.onChange} | |||
| checked={field.value} | |||
| /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| {/* Create a hidden field to make Form instance record this */} | |||
| <FormField | |||
| control={form.control} | |||
| name={'variables'} | |||
| render={() => <div></div>} | |||
| /> | |||
| </FormContainer> | |||
| <Collapse | |||
| title={<div>{t('flow.parameter')}</div>} | |||
| rightContent={ | |||
| <Button | |||
| variant={'ghost'} | |||
| onClick={(e) => { | |||
| e.preventDefault(); | |||
| showModal(); | |||
| }} | |||
| > | |||
| <Plus /> | |||
| </Button> | |||
| } | |||
| > | |||
| <Select options={MethodOptions} /> | |||
| </Form.Item> | |||
| <Form.Item name={'timeout'} label={t('flow.timeout')}> | |||
| <TimeoutInput></TimeoutInput> | |||
| </Form.Item> | |||
| <Form.Item name={'headers'} label={t('flow.headers')}> | |||
| <Editor height={200} defaultLanguage="json" theme="vs-dark" /> | |||
| </Form.Item> | |||
| <Form.Item name={'proxy'} label={t('flow.proxy')}> | |||
| <Input /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| name={'clean_html'} | |||
| label={t('flow.cleanHtml')} | |||
| tooltip={t('flow.cleanHtmlTip')} | |||
| > | |||
| <Switch /> | |||
| </Form.Item> | |||
| <DynamicVariablesForm node={node}></DynamicVariablesForm> | |||
| </Form> | |||
| </> | |||
| <VariableTable | |||
| data={variables} | |||
| showModal={showModal} | |||
| deleteRecord={handleDeleteRecord} | |||
| nodeId={node?.id} | |||
| ></VariableTable> | |||
| </Collapse> | |||
| {visible && ( | |||
| <VariableDialog | |||
| hideModal={hideModal} | |||
| initialValue={currentRecord} | |||
| otherThanCurrentQuery={otherThanCurrentQuery} | |||
| submit={ok} | |||
| ></VariableDialog> | |||
| )} | |||
| </FormWrapper> | |||
| </Form> | |||
| ); | |||
| }; | |||
| } | |||
| export default InvokeForm; | |||
| export default memo(InvokeForm); | |||
| @@ -0,0 +1,21 @@ | |||
| import { z } from 'zod'; | |||
| export const VariableFormSchema = z.object({ | |||
| key: z.string(), | |||
| ref: z.string(), | |||
| value: z.string(), | |||
| }); | |||
| export const FormSchema = z.object({ | |||
| url: z.string().url(), | |||
| method: z.string(), | |||
| timeout: z.number(), | |||
| headers: z.string(), | |||
| proxy: z.string().url(), | |||
| clean_html: z.boolean(), | |||
| variables: z.array(VariableFormSchema), | |||
| }); | |||
| export type FormSchemaType = z.infer<typeof FormSchema>; | |||
| export type VariableFormSchemaType = z.infer<typeof VariableFormSchema>; | |||
| @@ -0,0 +1,70 @@ | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { useSetSelectedRecord } from '@/hooks/logic-hooks'; | |||
| import { useCallback, useMemo, useState } from 'react'; | |||
| import { UseFormReturn, useWatch } from 'react-hook-form'; | |||
| import { INextOperatorForm } from '../../interface'; | |||
| import { FormSchemaType, VariableFormSchemaType } from './schema'; | |||
| export const useEditVariableRecord = ({ | |||
| form, | |||
| }: INextOperatorForm & { form: UseFormReturn<FormSchemaType> }) => { | |||
| const { setRecord, currentRecord } = | |||
| useSetSelectedRecord<VariableFormSchemaType>(); | |||
| const { visible, hideModal, showModal } = useSetModalState(); | |||
| const [index, setIndex] = useState(-1); | |||
| const variables = useWatch({ | |||
| control: form.control, | |||
| name: 'variables', | |||
| }); | |||
| const otherThanCurrentQuery = useMemo(() => { | |||
| return variables.filter((item, idx) => idx !== index); | |||
| }, [index, variables]); | |||
| const handleEditRecord = useCallback( | |||
| (record: VariableFormSchemaType) => { | |||
| const variables = form?.getValues('variables') || []; | |||
| const nextVaribales = | |||
| index > -1 | |||
| ? variables.toSpliced(index, 1, record) | |||
| : [...variables, record]; | |||
| form.setValue('variables', nextVaribales); | |||
| hideModal(); | |||
| }, | |||
| [form, hideModal, index], | |||
| ); | |||
| const handleShowModal = useCallback( | |||
| (idx?: number, record?: VariableFormSchemaType) => { | |||
| setIndex(idx ?? -1); | |||
| setRecord(record ?? ({} as VariableFormSchemaType)); | |||
| showModal(); | |||
| }, | |||
| [setRecord, showModal], | |||
| ); | |||
| const handleDeleteRecord = useCallback( | |||
| (idx: number) => { | |||
| const variables = form?.getValues('variables') || []; | |||
| const nextVariables = variables.filter((item, index) => index !== idx); | |||
| form.setValue('variables', nextVariables); | |||
| }, | |||
| [form], | |||
| ); | |||
| return { | |||
| ok: handleEditRecord, | |||
| currentRecord, | |||
| setRecord, | |||
| visible, | |||
| hideModal, | |||
| showModal: handleShowModal, | |||
| otherThanCurrentQuery, | |||
| handleDeleteRecord, | |||
| }; | |||
| }; | |||
| @@ -0,0 +1,143 @@ | |||
| import { Button } from '@/components/ui/button'; | |||
| import { | |||
| Dialog, | |||
| DialogContent, | |||
| DialogFooter, | |||
| DialogHeader, | |||
| DialogTitle, | |||
| } from '@/components/ui/dialog'; | |||
| import { | |||
| Form, | |||
| FormControl, | |||
| FormField, | |||
| FormItem, | |||
| FormLabel, | |||
| FormMessage, | |||
| } from '@/components/ui/form'; | |||
| import { Input } from '@/components/ui/input'; | |||
| import { IModalProps } from '@/interfaces/common'; | |||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||
| import { isEmpty } from 'lodash'; | |||
| import { useEffect } from 'react'; | |||
| import { useForm } from 'react-hook-form'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { z } from 'zod'; | |||
| import { QueryVariable } from '../components/query-variable'; | |||
| import { VariableFormSchemaType } from './schema'; | |||
| type ModalFormProps = { | |||
| initialValue: VariableFormSchemaType; | |||
| otherThanCurrentQuery: VariableFormSchemaType[]; | |||
| submit(values: any): void; | |||
| }; | |||
| const FormId = 'BeginParameterForm'; | |||
| function VariableForm({ | |||
| initialValue, | |||
| otherThanCurrentQuery, | |||
| submit, | |||
| }: ModalFormProps) { | |||
| const { t } = useTranslation(); | |||
| const FormSchema = z.object({ | |||
| key: z | |||
| .string() | |||
| .trim() | |||
| .min(1) | |||
| .refine( | |||
| (value) => | |||
| !value || !otherThanCurrentQuery.some((x) => x.key === value), | |||
| { message: 'The key cannot be repeated!' }, | |||
| ), | |||
| ref: z.string(), | |||
| value: z.string(), | |||
| }); | |||
| const form = useForm<z.infer<typeof FormSchema>>({ | |||
| resolver: zodResolver(FormSchema), | |||
| mode: 'onChange', | |||
| defaultValues: { | |||
| key: '', | |||
| value: '', | |||
| ref: '', | |||
| }, | |||
| }); | |||
| useEffect(() => { | |||
| if (!isEmpty(initialValue)) { | |||
| form.reset(initialValue); | |||
| } | |||
| }, [form, initialValue]); | |||
| function onSubmit(data: z.infer<typeof FormSchema>) { | |||
| submit(data); | |||
| } | |||
| return ( | |||
| <Form {...form}> | |||
| <form | |||
| onSubmit={form.handleSubmit(onSubmit)} | |||
| id={FormId} | |||
| className="space-y-5" | |||
| autoComplete="off" | |||
| > | |||
| <FormField | |||
| name="key" | |||
| control={form.control} | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('flow.key')}</FormLabel> | |||
| <FormControl> | |||
| <Input {...field} autoComplete="off" /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <QueryVariable name="ref" label={t('flow.ref')}></QueryVariable> | |||
| <FormField | |||
| name="value" | |||
| control={form.control} | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('flow.value')}</FormLabel> | |||
| <FormControl> | |||
| <Input {...field} /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| </form> | |||
| </Form> | |||
| ); | |||
| } | |||
| export function VariableDialog({ | |||
| initialValue, | |||
| hideModal, | |||
| otherThanCurrentQuery, | |||
| submit, | |||
| }: ModalFormProps & IModalProps<VariableFormSchemaType>) { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <Dialog open onOpenChange={hideModal}> | |||
| <DialogContent> | |||
| <DialogHeader> | |||
| <DialogTitle>{t('flow.variableSettings')}</DialogTitle> | |||
| </DialogHeader> | |||
| <VariableForm | |||
| initialValue={initialValue} | |||
| otherThanCurrentQuery={otherThanCurrentQuery} | |||
| submit={submit} | |||
| ></VariableForm> | |||
| <DialogFooter> | |||
| <Button type="submit" form={FormId}> | |||
| Confirm | |||
| </Button> | |||
| </DialogFooter> | |||
| </DialogContent> | |||
| </Dialog> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,199 @@ | |||
| 'use client'; | |||
| import { | |||
| ColumnDef, | |||
| ColumnFiltersState, | |||
| SortingState, | |||
| VisibilityState, | |||
| flexRender, | |||
| getCoreRowModel, | |||
| getFilteredRowModel, | |||
| getPaginationRowModel, | |||
| getSortedRowModel, | |||
| useReactTable, | |||
| } from '@tanstack/react-table'; | |||
| import { Pencil, Trash2 } from 'lucide-react'; | |||
| import * as React from 'react'; | |||
| import { TableEmpty } from '@/components/table-skeleton'; | |||
| import { Button } from '@/components/ui/button'; | |||
| import { | |||
| Table, | |||
| TableBody, | |||
| TableCell, | |||
| TableHead, | |||
| TableHeader, | |||
| TableRow, | |||
| } from '@/components/ui/table'; | |||
| import { | |||
| Tooltip, | |||
| TooltipContent, | |||
| TooltipTrigger, | |||
| } from '@/components/ui/tooltip'; | |||
| import { cn } from '@/lib/utils'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { useGetVariableLabelByValue } from '../../hooks/use-get-begin-query'; | |||
| import { VariableFormSchemaType } from './schema'; | |||
| interface IProps { | |||
| data: VariableFormSchemaType[]; | |||
| deleteRecord(index: number): void; | |||
| showModal(index: number, record: VariableFormSchemaType): void; | |||
| nodeId?: string; | |||
| } | |||
| export function VariableTable({ | |||
| data = [], | |||
| deleteRecord, | |||
| showModal, | |||
| nodeId, | |||
| }: IProps) { | |||
| const { t } = useTranslation(); | |||
| const getLabel = useGetVariableLabelByValue(nodeId!); | |||
| const [sorting, setSorting] = React.useState<SortingState>([]); | |||
| const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>( | |||
| [], | |||
| ); | |||
| const [columnVisibility, setColumnVisibility] = | |||
| React.useState<VisibilityState>({}); | |||
| const columns: ColumnDef<VariableFormSchemaType>[] = [ | |||
| { | |||
| accessorKey: 'key', | |||
| header: 'key', | |||
| meta: { cellClassName: 'max-w-30' }, | |||
| cell: ({ row }) => { | |||
| const key: string = row.getValue('key'); | |||
| return ( | |||
| <Tooltip> | |||
| <TooltipTrigger asChild> | |||
| <div className="truncate">{key}</div> | |||
| </TooltipTrigger> | |||
| <TooltipContent> | |||
| <p>{key}</p> | |||
| </TooltipContent> | |||
| </Tooltip> | |||
| ); | |||
| }, | |||
| }, | |||
| { | |||
| accessorKey: 'ref', | |||
| header: t('flow.ref'), | |||
| meta: { cellClassName: 'max-w-30' }, | |||
| cell: ({ row }) => { | |||
| const ref: string = row.getValue('ref'); | |||
| const label = getLabel(ref); | |||
| return ( | |||
| <Tooltip> | |||
| <TooltipTrigger asChild> | |||
| <div className="truncate">{label}</div> | |||
| </TooltipTrigger> | |||
| <TooltipContent> | |||
| <p>{label}</p> | |||
| </TooltipContent> | |||
| </Tooltip> | |||
| ); | |||
| }, | |||
| }, | |||
| { | |||
| accessorKey: 'value', | |||
| header: t('flow.value'), | |||
| cell: ({ row }) => <div>{row.getValue('value')}</div>, | |||
| }, | |||
| { | |||
| id: 'actions', | |||
| enableHiding: false, | |||
| header: t('common.action'), | |||
| cell: ({ row }) => { | |||
| const record = row.original; | |||
| const idx = row.index; | |||
| return ( | |||
| <div> | |||
| <Button | |||
| className="bg-transparent text-foreground hover:bg-muted-foreground hover:text-foreground" | |||
| onClick={() => showModal(idx, record)} | |||
| > | |||
| <Pencil /> | |||
| </Button> | |||
| <Button | |||
| className="bg-transparent text-foreground hover:bg-muted-foreground hover:text-foreground" | |||
| onClick={() => deleteRecord(idx)} | |||
| > | |||
| <Trash2 /> | |||
| </Button> | |||
| </div> | |||
| ); | |||
| }, | |||
| }, | |||
| ]; | |||
| const table = useReactTable({ | |||
| data, | |||
| columns, | |||
| onSortingChange: setSorting, | |||
| onColumnFiltersChange: setColumnFilters, | |||
| getCoreRowModel: getCoreRowModel(), | |||
| getPaginationRowModel: getPaginationRowModel(), | |||
| getSortedRowModel: getSortedRowModel(), | |||
| getFilteredRowModel: getFilteredRowModel(), | |||
| onColumnVisibilityChange: setColumnVisibility, | |||
| state: { | |||
| sorting, | |||
| columnFilters, | |||
| columnVisibility, | |||
| }, | |||
| }); | |||
| return ( | |||
| <div className="w-full"> | |||
| <div className="rounded-md border"> | |||
| <Table rootClassName="rounded-md"> | |||
| <TableHeader> | |||
| {table.getHeaderGroups().map((headerGroup) => ( | |||
| <TableRow key={headerGroup.id}> | |||
| {headerGroup.headers.map((header) => { | |||
| return ( | |||
| <TableHead key={header.id}> | |||
| {header.isPlaceholder | |||
| ? null | |||
| : flexRender( | |||
| header.column.columnDef.header, | |||
| header.getContext(), | |||
| )} | |||
| </TableHead> | |||
| ); | |||
| })} | |||
| </TableRow> | |||
| ))} | |||
| </TableHeader> | |||
| <TableBody> | |||
| {table.getRowModel().rows?.length ? ( | |||
| table.getRowModel().rows.map((row) => ( | |||
| <TableRow | |||
| key={row.id} | |||
| data-state={row.getIsSelected() && 'selected'} | |||
| > | |||
| {row.getVisibleCells().map((cell) => ( | |||
| <TableCell | |||
| key={cell.id} | |||
| className={cn(cell.column.columnDef.meta?.cellClassName)} | |||
| > | |||
| {flexRender( | |||
| cell.column.columnDef.cell, | |||
| cell.getContext(), | |||
| )} | |||
| </TableCell> | |||
| ))} | |||
| </TableRow> | |||
| )) | |||
| ) : ( | |||
| <TableEmpty columnsLength={columns.length}></TableEmpty> | |||
| )} | |||
| </TableBody> | |||
| </Table> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| @@ -1,3 +1,4 @@ | |||
| import { FormContainer } from '@/components/form-container'; | |||
| import { TopNFormField } from '@/components/top-n-item'; | |||
| import { | |||
| Form, | |||
| @@ -9,38 +10,81 @@ import { | |||
| } from '@/components/ui/form'; | |||
| import { Input } from '@/components/ui/input'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||
| import { memo } from 'react'; | |||
| import { useForm, useFormContext } from 'react-hook-form'; | |||
| import { z } from 'zod'; | |||
| import { initialPubMedValues } from '../../constant'; | |||
| import { useFormValues } from '../../hooks/use-form-values'; | |||
| import { useWatchFormChange } from '../../hooks/use-watch-form-change'; | |||
| import { INextOperatorForm } from '../../interface'; | |||
| import { DynamicInputVariable } from '../components/next-dynamic-input-variable'; | |||
| import { buildOutputList } from '../../utils/build-output-list'; | |||
| import { FormWrapper } from '../components/form-wrapper'; | |||
| import { Output } from '../components/output'; | |||
| import { QueryVariable } from '../components/query-variable'; | |||
| const PubMedForm = ({ form, node }: INextOperatorForm) => { | |||
| export const PubMedFormPartialSchema = { | |||
| top_n: z.number(), | |||
| email: z.string().email(), | |||
| }; | |||
| export const FormSchema = z.object({ | |||
| ...PubMedFormPartialSchema, | |||
| query: z.string(), | |||
| }); | |||
| export function PubMedFormWidgets() { | |||
| const form = useFormContext(); | |||
| const { t } = useTranslate('flow'); | |||
| return ( | |||
| <> | |||
| <TopNFormField></TopNFormField> | |||
| <FormField | |||
| control={form.control} | |||
| name="email" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel tooltip={t('emailTip')}>{t('email')}</FormLabel> | |||
| <FormControl> | |||
| <Input {...field} /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| </> | |||
| ); | |||
| } | |||
| const outputList = buildOutputList(initialPubMedValues.outputs); | |||
| function PubMedForm({ node }: INextOperatorForm) { | |||
| const defaultValues = useFormValues(initialPubMedValues, node); | |||
| const form = useForm<z.infer<typeof FormSchema>>({ | |||
| defaultValues, | |||
| resolver: zodResolver(FormSchema), | |||
| mode: 'onChange', | |||
| }); | |||
| useWatchFormChange(node?.id, form); | |||
| return ( | |||
| <Form {...form}> | |||
| <form | |||
| className="space-y-6" | |||
| onSubmit={(e) => { | |||
| e.preventDefault(); | |||
| }} | |||
| > | |||
| <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> | |||
| <FormWrapper> | |||
| <FormContainer> | |||
| <QueryVariable></QueryVariable> | |||
| </FormContainer> | |||
| <FormContainer> | |||
| <PubMedFormWidgets></PubMedFormWidgets> | |||
| </FormContainer> | |||
| </FormWrapper> | |||
| <div className="p-5"> | |||
| <Output list={outputList}></Output> | |||
| </div> | |||
| </Form> | |||
| ); | |||
| }; | |||
| } | |||
| export default PubMedForm; | |||
| export default memo(PubMedForm); | |||
| @@ -1,16 +1,16 @@ | |||
| import { Operator } from '../../constant'; | |||
| import AkShareForm from '../akshare-form'; | |||
| import DeepLForm from '../deepl-form'; | |||
| import GithubForm from '../github-form'; | |||
| import PubMedForm from '../pubmed-form'; | |||
| import ArXivForm from './arxiv-form'; | |||
| import BingForm from './bing-form'; | |||
| import CrawlerForm from './crawler-form'; | |||
| import DuckDuckGoForm from './duckduckgo-form'; | |||
| import EmailForm from './email-form'; | |||
| import ExeSQLForm from './exesql-form'; | |||
| import GithubForm from './github-form'; | |||
| import GoogleForm from './google-form'; | |||
| import GoogleScholarForm from './google-scholar-form'; | |||
| import PubMedForm from './pubmed-form'; | |||
| import RetrievalForm from './retrieval-form'; | |||
| import TavilyForm from './tavily-form'; | |||
| import WikipediaForm from './wikipedia-form'; | |||
| @@ -0,0 +1,36 @@ | |||
| import { FormContainer } from '@/components/form-container'; | |||
| import { TopNFormField } from '@/components/top-n-item'; | |||
| import { Form } from '@/components/ui/form'; | |||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||
| import { memo } from 'react'; | |||
| import { useForm } from 'react-hook-form'; | |||
| import { z } from 'zod'; | |||
| import { FormWrapper } from '../../components/form-wrapper'; | |||
| import { useValues } from '../use-values'; | |||
| import { useWatchFormChange } from '../use-watch-change'; | |||
| function GithubForm() { | |||
| const values = useValues(); | |||
| const FormSchema = z.object({ query: z.string() }); | |||
| const form = useForm<z.infer<typeof FormSchema>>({ | |||
| defaultValues: values, | |||
| resolver: zodResolver(FormSchema), | |||
| mode: 'onChange', | |||
| }); | |||
| useWatchFormChange(form); | |||
| return ( | |||
| <Form {...form}> | |||
| <FormWrapper> | |||
| <FormContainer> | |||
| <TopNFormField></TopNFormField> | |||
| </FormContainer> | |||
| </FormWrapper> | |||
| </Form> | |||
| ); | |||
| } | |||
| export default memo(GithubForm); | |||
| @@ -0,0 +1,36 @@ | |||
| import { FormContainer } from '@/components/form-container'; | |||
| import { Form } from '@/components/ui/form'; | |||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||
| import { memo } from 'react'; | |||
| import { useForm } from 'react-hook-form'; | |||
| import { z } from 'zod'; | |||
| import { FormWrapper } from '../../components/form-wrapper'; | |||
| import { PubMedFormPartialSchema, PubMedFormWidgets } from '../../pubmed-form'; | |||
| import { useValues } from '../use-values'; | |||
| import { useWatchFormChange } from '../use-watch-change'; | |||
| function PubMedForm() { | |||
| const values = useValues(); | |||
| const FormSchema = z.object(PubMedFormPartialSchema); | |||
| const form = useForm<z.infer<typeof FormSchema>>({ | |||
| defaultValues: values, | |||
| resolver: zodResolver(FormSchema), | |||
| mode: 'onChange', | |||
| }); | |||
| useWatchFormChange(form); | |||
| return ( | |||
| <Form {...form}> | |||
| <FormWrapper> | |||
| <FormContainer> | |||
| <PubMedFormWidgets></PubMedFormWidgets> | |||
| </FormContainer> | |||
| </FormWrapper> | |||
| </Form> | |||
| ); | |||
| } | |||
| export default memo(PubMedForm); | |||
| @@ -7,7 +7,6 @@ import { | |||
| FormLabel, | |||
| FormMessage, | |||
| } from '@/components/ui/form'; | |||
| import { Input } from '@/components/ui/input'; | |||
| import { Switch } from '@/components/ui/switch'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||
| @@ -21,6 +20,7 @@ import { INextOperatorForm } from '../../interface'; | |||
| import { buildOutputList } from '../../utils/build-output-list'; | |||
| import { FormWrapper } from '../components/form-wrapper'; | |||
| import { Output } from '../components/output'; | |||
| import { QueryVariable } from '../components/query-variable'; | |||
| export const YahooFinanceFormPartialSchema = { | |||
| info: z.boolean(), | |||
| @@ -106,19 +106,12 @@ const YahooFinanceForm = ({ node }: INextOperatorForm) => { | |||
| <Form {...form}> | |||
| <FormWrapper> | |||
| <FormContainer> | |||
| <FormField | |||
| control={form.control} | |||
| name={`stock_code`} | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('stockCode')}</FormLabel> | |||
| <FormControl> | |||
| <Input {...field}></Input> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <QueryVariable | |||
| name="stock_code" | |||
| label={t('stockCode')} | |||
| ></QueryVariable> | |||
| </FormContainer> | |||
| <FormContainer> | |||
| <YahooFinanceFormWidgets></YahooFinanceFormWidgets> | |||
| </FormContainer> | |||
| </FormWrapper> | |||
| @@ -48,6 +48,10 @@ export function useAgentToolInitialValues() { | |||
| return omit(initialValues, 'query', 'outputs'); | |||
| case Operator.ArXiv: | |||
| return pick(initialValues, 'top_n', 'sort_by'); | |||
| case Operator.PubMed: | |||
| return pick(initialValues, 'top_n', 'email'); | |||
| case Operator.GitHub: | |||
| return pick(initialValues, 'top_n'); | |||
| default: | |||
| return initialValues; | |||