### 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
| management: 'Management', | management: 'Management', | ||||
| import: 'Import', | import: 'Import', | ||||
| export: 'Export', | export: 'Export', | ||||
| seconds: 'Seconds', | |||||
| }, | }, | ||||
| llmTools: { | llmTools: { | ||||
| bad_calculator: { | bad_calculator: { |
| Operator.Wikipedia, | Operator.Wikipedia, | ||||
| Operator.GoogleScholar, | Operator.GoogleScholar, | ||||
| Operator.ArXiv, | Operator.ArXiv, | ||||
| Operator.PubMed, | |||||
| Operator.GitHub, | |||||
| Operator.Invoke, | |||||
| ]} | ]} | ||||
| ></OperatorItemList> | ></OperatorItemList> | ||||
| </AccordionContent> | </AccordionContent> |
| }; | }; | ||||
| export const initialPubMedValues = { | export const initialPubMedValues = { | ||||
| top_n: 10, | |||||
| top_n: 12, | |||||
| email: '', | email: '', | ||||
| ...initialQueryBaseValues, | |||||
| query: AgentGlobals.SysQuery, | |||||
| outputs: { | |||||
| formalized_content: { | |||||
| value: '', | |||||
| type: 'string', | |||||
| }, | |||||
| }, | |||||
| }; | }; | ||||
| export const initialArXivValues = { | export const initialArXivValues = { | ||||
| export const initialGithubValues = { | export const initialGithubValues = { | ||||
| top_n: 5, | top_n: 5, | ||||
| ...initialQueryBaseValues, | |||||
| query: AgentGlobals.SysQuery, | |||||
| outputs: { | |||||
| formalized_content: { | |||||
| value: '', | |||||
| type: 'string', | |||||
| }, | |||||
| json: { | |||||
| value: [], | |||||
| type: 'Array<Object>', | |||||
| }, | |||||
| }, | |||||
| }; | }; | ||||
| export const initialBaiduFanyiValues = { | export const initialBaiduFanyiValues = { | ||||
| }; | }; | ||||
| export const initialInvokeValues = { | export const initialInvokeValues = { | ||||
| url: 'http://', | |||||
| url: '', | |||||
| method: 'GET', | method: 'GET', | ||||
| timeout: 60, | timeout: 60, | ||||
| headers: `{ | headers: `{ | ||||
| "Cache-Control": "no-cache", | "Cache-Control": "no-cache", | ||||
| "Connection": "keep-alive" | "Connection": "keep-alive" | ||||
| }`, | }`, | ||||
| proxy: 'http://', | |||||
| proxy: '', | |||||
| clean_html: false, | clean_html: false, | ||||
| variables: [], | |||||
| }; | }; | ||||
| export const initialTemplateValues = { | export const initialTemplateValues = { | ||||
| [Operator.TuShare]: 'ragNode', | [Operator.TuShare]: 'ragNode', | ||||
| [Operator.Note]: 'noteNode', | [Operator.Note]: 'noteNode', | ||||
| [Operator.Crawler]: 'ragNode', | [Operator.Crawler]: 'ragNode', | ||||
| [Operator.Invoke]: 'invokeNode', | |||||
| [Operator.Invoke]: 'ragNode', | |||||
| [Operator.Template]: 'templateNode', | [Operator.Template]: 'templateNode', | ||||
| [Operator.Email]: 'ragNode', | [Operator.Email]: 'ragNode', | ||||
| [Operator.Iteration]: 'group', | [Operator.Iteration]: 'group', |
| name={`sys_prompt`} | name={`sys_prompt`} | ||||
| render={({ field }) => ( | render={({ field }) => ( | ||||
| <FormItem className="flex-1"> | <FormItem className="flex-1"> | ||||
| <FormLabel>Prompt</FormLabel> | |||||
| <FormLabel>System Prompt</FormLabel> | |||||
| <FormControl> | <FormControl> | ||||
| <PromptEditor | <PromptEditor | ||||
| {...field} | {...field} |
| }, | }, | ||||
| { | { | ||||
| label: 'Developer', | label: 'Developer', | ||||
| list: [ | |||||
| Operator.GitHub, | |||||
| Operator.ExeSQL, | |||||
| Operator.Invoke, | |||||
| Operator.Code, | |||||
| Operator.Retrieval, | |||||
| ], | |||||
| list: [Operator.GitHub, Operator.ExeSQL, Operator.Code, Operator.Retrieval], | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| 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 ( | 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> | </Form> | ||||
| ); | ); | ||||
| }; | |||||
| } | |||||
| export default GithubForm; | |||||
| export default memo(GithubForm); |
| 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; |
| 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 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 { 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' } }); | loader.config({ paths: { vs: '/vs' } }); | ||||
| const TimeoutInput = ({ value, onChange }: TimeoutInputProps) => { | const TimeoutInput = ({ value, onChange }: TimeoutInputProps) => { | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| return ( | 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 { 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 ( | 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); |
| 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>; |
| 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, | |||||
| }; | |||||
| }; |
| 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> | |||||
| ); | |||||
| } |
| '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> | |||||
| ); | |||||
| } |
| import { FormContainer } from '@/components/form-container'; | |||||
| import { TopNFormField } from '@/components/top-n-item'; | import { TopNFormField } from '@/components/top-n-item'; | ||||
| import { | import { | ||||
| Form, | Form, | ||||
| } from '@/components/ui/form'; | } from '@/components/ui/form'; | ||||
| import { Input } from '@/components/ui/input'; | import { Input } from '@/components/ui/input'; | ||||
| import { useTranslate } from '@/hooks/common-hooks'; | 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 { 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'); | 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 ( | return ( | ||||
| <Form {...form}> | <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> | </Form> | ||||
| ); | ); | ||||
| }; | |||||
| } | |||||
| export default PubMedForm; | |||||
| export default memo(PubMedForm); |
| import { Operator } from '../../constant'; | import { Operator } from '../../constant'; | ||||
| import AkShareForm from '../akshare-form'; | import AkShareForm from '../akshare-form'; | ||||
| import DeepLForm from '../deepl-form'; | import DeepLForm from '../deepl-form'; | ||||
| import GithubForm from '../github-form'; | |||||
| import PubMedForm from '../pubmed-form'; | |||||
| import ArXivForm from './arxiv-form'; | import ArXivForm from './arxiv-form'; | ||||
| import BingForm from './bing-form'; | import BingForm from './bing-form'; | ||||
| import CrawlerForm from './crawler-form'; | import CrawlerForm from './crawler-form'; | ||||
| import DuckDuckGoForm from './duckduckgo-form'; | import DuckDuckGoForm from './duckduckgo-form'; | ||||
| import EmailForm from './email-form'; | import EmailForm from './email-form'; | ||||
| import ExeSQLForm from './exesql-form'; | import ExeSQLForm from './exesql-form'; | ||||
| import GithubForm from './github-form'; | |||||
| import GoogleForm from './google-form'; | import GoogleForm from './google-form'; | ||||
| import GoogleScholarForm from './google-scholar-form'; | import GoogleScholarForm from './google-scholar-form'; | ||||
| import PubMedForm from './pubmed-form'; | |||||
| import RetrievalForm from './retrieval-form'; | import RetrievalForm from './retrieval-form'; | ||||
| import TavilyForm from './tavily-form'; | import TavilyForm from './tavily-form'; | ||||
| import WikipediaForm from './wikipedia-form'; | import WikipediaForm from './wikipedia-form'; |
| 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); |
| 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); |
| FormLabel, | FormLabel, | ||||
| FormMessage, | FormMessage, | ||||
| } from '@/components/ui/form'; | } from '@/components/ui/form'; | ||||
| import { Input } from '@/components/ui/input'; | |||||
| import { Switch } from '@/components/ui/switch'; | import { Switch } from '@/components/ui/switch'; | ||||
| import { useTranslate } from '@/hooks/common-hooks'; | import { useTranslate } from '@/hooks/common-hooks'; | ||||
| import { zodResolver } from '@hookform/resolvers/zod'; | import { zodResolver } from '@hookform/resolvers/zod'; | ||||
| import { buildOutputList } from '../../utils/build-output-list'; | import { buildOutputList } from '../../utils/build-output-list'; | ||||
| import { FormWrapper } from '../components/form-wrapper'; | import { FormWrapper } from '../components/form-wrapper'; | ||||
| import { Output } from '../components/output'; | import { Output } from '../components/output'; | ||||
| import { QueryVariable } from '../components/query-variable'; | |||||
| export const YahooFinanceFormPartialSchema = { | export const YahooFinanceFormPartialSchema = { | ||||
| info: z.boolean(), | info: z.boolean(), | ||||
| <Form {...form}> | <Form {...form}> | ||||
| <FormWrapper> | <FormWrapper> | ||||
| <FormContainer> | <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> | <YahooFinanceFormWidgets></YahooFinanceFormWidgets> | ||||
| </FormContainer> | </FormContainer> | ||||
| </FormWrapper> | </FormWrapper> |
| return omit(initialValues, 'query', 'outputs'); | return omit(initialValues, 'query', 'outputs'); | ||||
| case Operator.ArXiv: | case Operator.ArXiv: | ||||
| return pick(initialValues, 'top_n', 'sort_by'); | 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: | default: | ||||
| return initialValues; | return initialValues; |