### What problem does this PR solve? Feat: Add FormDrawer to agent page. #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.17.0
| @@ -1,20 +1,12 @@ | |||
| import { | |||
| Tooltip, | |||
| TooltipContent, | |||
| TooltipTrigger, | |||
| } from '@/components/ui/tooltip'; | |||
| import { | |||
| Background, | |||
| ConnectionMode, | |||
| ControlButton, | |||
| Controls, | |||
| NodeTypes, | |||
| ReactFlow, | |||
| } from '@xyflow/react'; | |||
| import '@xyflow/react/dist/style.css'; | |||
| import { Book, FolderInput, FolderOutput } from 'lucide-react'; | |||
| // import ChatDrawer from '../chat/drawer'; | |||
| // import FormDrawer from '../flow-drawer'; | |||
| import FormDrawer from '../form-drawer'; | |||
| import { | |||
| useHandleDrop, | |||
| useSelectCanvasData, | |||
| @@ -22,10 +14,7 @@ import { | |||
| useWatchNodeFormDataChange, | |||
| } from '../hooks'; | |||
| import { useBeforeDelete } from '../hooks/use-before-delete'; | |||
| import { useHandleExportOrImportJsonFile } from '../hooks/use-export-json'; | |||
| import { useOpenDocument } from '../hooks/use-open-document'; | |||
| import { useShowDrawer } from '../hooks/use-show-drawer'; | |||
| // import JsonUploadModal from '../json-upload-modal'; | |||
| // import RunDrawer from '../run-drawer'; | |||
| import { ButtonEdge } from './edge'; | |||
| import styles from './index.less'; | |||
| @@ -88,16 +77,6 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) { | |||
| const { onDrop, onDragOver, setReactFlowInstance } = useHandleDrop(); | |||
| const { | |||
| handleExportJson, | |||
| handleImportJson, | |||
| fileUploadVisible, | |||
| onFileUploadOk, | |||
| hideFileUploadModal, | |||
| } = useHandleExportOrImportJsonFile(); | |||
| const openDocument = useOpenDocument(); | |||
| const { | |||
| onNodeClick, | |||
| onPaneClick, | |||
| @@ -173,34 +152,8 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) { | |||
| onBeforeDelete={handleBeforeDelete} | |||
| > | |||
| <Background /> | |||
| <Controls className="text-black !flex-col-reverse"> | |||
| <ControlButton onClick={handleImportJson}> | |||
| <Tooltip> | |||
| <TooltipTrigger asChild> | |||
| <FolderInput className="!fill-none" /> | |||
| </TooltipTrigger> | |||
| <TooltipContent>Import</TooltipContent> | |||
| </Tooltip> | |||
| </ControlButton> | |||
| <ControlButton onClick={handleExportJson}> | |||
| <Tooltip> | |||
| <TooltipTrigger asChild> | |||
| <FolderOutput className="!fill-none" /> | |||
| </TooltipTrigger> | |||
| <TooltipContent>Export</TooltipContent> | |||
| </Tooltip> | |||
| </ControlButton> | |||
| <ControlButton onClick={openDocument}> | |||
| <Tooltip> | |||
| <TooltipTrigger asChild> | |||
| <Book className="!fill-none" /> | |||
| </TooltipTrigger> | |||
| <TooltipContent>Document</TooltipContent> | |||
| </Tooltip> | |||
| </ControlButton> | |||
| </Controls> | |||
| </ReactFlow> | |||
| {/* {formDrawerVisible && ( | |||
| {formDrawerVisible && ( | |||
| <FormDrawer | |||
| node={clickedNode} | |||
| visible={formDrawerVisible} | |||
| @@ -209,7 +162,7 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) { | |||
| hideSingleDebugDrawer={hideSingleDebugDrawer} | |||
| showSingleDebugDrawer={showSingleDebugDrawer} | |||
| ></FormDrawer> | |||
| )} */} | |||
| )} | |||
| {/* {chatVisible && ( | |||
| <ChatDrawer | |||
| visible={chatVisible} | |||
| @@ -222,13 +175,6 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) { | |||
| hideModal={hideRunOrChatDrawer} | |||
| showModal={showChatModal} | |||
| ></RunDrawer> | |||
| )} | |||
| {fileUploadVisible && ( | |||
| <JsonUploadModal | |||
| onOk={onFileUploadOk} | |||
| visible={fileUploadVisible} | |||
| hideModal={hideFileUploadModal} | |||
| ></JsonUploadModal> | |||
| )} */} | |||
| </div> | |||
| ); | |||
| @@ -0,0 +1,6 @@ | |||
| import { RAGFlowNodeType } from '@/interfaces/database/flow'; | |||
| import { createContext } from 'react'; | |||
| export const FlowFormContext = createContext<RAGFlowNodeType | undefined>( | |||
| undefined, | |||
| ); | |||
| @@ -0,0 +1,5 @@ | |||
| .formWrapper { | |||
| :global(.ant-form-item-label) { | |||
| font-weight: 600 !important; | |||
| } | |||
| } | |||
| @@ -0,0 +1,238 @@ | |||
| import { Authorization } from '@/constants/authorization'; | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { useSetSelectedRecord } from '@/hooks/logic-hooks'; | |||
| import { useHandleSubmittable } from '@/hooks/login-hooks'; | |||
| import api from '@/utils/api'; | |||
| import { getAuthorization } from '@/utils/authorization-util'; | |||
| import { UploadOutlined } from '@ant-design/icons'; | |||
| import { | |||
| Button, | |||
| Form, | |||
| FormItemProps, | |||
| Input, | |||
| InputNumber, | |||
| Select, | |||
| Switch, | |||
| Upload, | |||
| } from 'antd'; | |||
| import { UploadChangeParam, UploadFile } from 'antd/es/upload'; | |||
| import { pick } from 'lodash'; | |||
| import { Link } from 'lucide-react'; | |||
| import React, { useCallback, useState } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { BeginQueryType } from '../constant'; | |||
| import { BeginQuery } from '../interface'; | |||
| import { PopoverForm } from './popover-form'; | |||
| import styles from './index.less'; | |||
| interface IProps { | |||
| parameters: BeginQuery[]; | |||
| ok(parameters: any[]): void; | |||
| isNext?: boolean; | |||
| loading?: boolean; | |||
| submitButtonDisabled?: boolean; | |||
| } | |||
| const DebugContent = ({ | |||
| parameters, | |||
| ok, | |||
| isNext = true, | |||
| loading = false, | |||
| submitButtonDisabled = false, | |||
| }: IProps) => { | |||
| const { t } = useTranslation(); | |||
| const [form] = Form.useForm(); | |||
| const { | |||
| visible, | |||
| hideModal: hidePopover, | |||
| switchVisible, | |||
| showModal: showPopover, | |||
| } = useSetModalState(); | |||
| const { setRecord, currentRecord } = useSetSelectedRecord<number>(); | |||
| const { submittable } = useHandleSubmittable(form); | |||
| const [isUploading, setIsUploading] = useState(false); | |||
| const handleShowPopover = useCallback( | |||
| (idx: number) => () => { | |||
| setRecord(idx); | |||
| showPopover(); | |||
| }, | |||
| [setRecord, showPopover], | |||
| ); | |||
| const normFile = (e: any) => { | |||
| if (Array.isArray(e)) { | |||
| return e; | |||
| } | |||
| return e?.fileList; | |||
| }; | |||
| const onChange = useCallback( | |||
| (optional: boolean) => | |||
| ({ fileList }: UploadChangeParam<UploadFile>) => { | |||
| if (!optional) { | |||
| setIsUploading(fileList.some((x) => x.status === 'uploading')); | |||
| } | |||
| }, | |||
| [], | |||
| ); | |||
| const renderWidget = useCallback( | |||
| (q: BeginQuery, idx: number) => { | |||
| const props: FormItemProps & { key: number } = { | |||
| key: idx, | |||
| label: q.name ?? q.key, | |||
| name: idx, | |||
| }; | |||
| if (q.optional === false) { | |||
| props.rules = [{ required: true }]; | |||
| } | |||
| const urlList: { url: string; result: string }[] = | |||
| form.getFieldValue(idx) || []; | |||
| const BeginQueryTypeMap = { | |||
| [BeginQueryType.Line]: ( | |||
| <Form.Item {...props}> | |||
| <Input></Input> | |||
| </Form.Item> | |||
| ), | |||
| [BeginQueryType.Paragraph]: ( | |||
| <Form.Item {...props}> | |||
| <Input.TextArea rows={1}></Input.TextArea> | |||
| </Form.Item> | |||
| ), | |||
| [BeginQueryType.Options]: ( | |||
| <Form.Item {...props}> | |||
| <Select | |||
| allowClear | |||
| options={q.options?.map((x) => ({ label: x, value: x })) ?? []} | |||
| ></Select> | |||
| </Form.Item> | |||
| ), | |||
| [BeginQueryType.File]: ( | |||
| <React.Fragment key={idx}> | |||
| <Form.Item label={q.name ?? q.key} required={!q.optional}> | |||
| <div className="relative"> | |||
| <Form.Item | |||
| {...props} | |||
| valuePropName="fileList" | |||
| getValueFromEvent={normFile} | |||
| noStyle | |||
| > | |||
| <Upload | |||
| name="file" | |||
| action={api.parse} | |||
| multiple | |||
| headers={{ [Authorization]: getAuthorization() }} | |||
| onChange={onChange(q.optional)} | |||
| > | |||
| <Button icon={<UploadOutlined />}> | |||
| {t('common.upload')} | |||
| </Button> | |||
| </Upload> | |||
| </Form.Item> | |||
| <Form.Item | |||
| {...pick(props, ['key', 'label', 'rules'])} | |||
| required={!q.optional} | |||
| className={urlList.length > 0 ? 'mb-1' : ''} | |||
| noStyle | |||
| > | |||
| <PopoverForm visible={visible} switchVisible={switchVisible}> | |||
| <Button | |||
| onClick={handleShowPopover(idx)} | |||
| className="absolute left-1/2 top-0" | |||
| icon={<Link className="size-3" />} | |||
| > | |||
| {t('flow.pasteFileLink')} | |||
| </Button> | |||
| </PopoverForm> | |||
| </Form.Item> | |||
| </div> | |||
| </Form.Item> | |||
| <Form.Item name={idx} noStyle {...pick(props, ['rules'])} /> | |||
| </React.Fragment> | |||
| ), | |||
| [BeginQueryType.Integer]: ( | |||
| <Form.Item {...props}> | |||
| <InputNumber></InputNumber> | |||
| </Form.Item> | |||
| ), | |||
| [BeginQueryType.Boolean]: ( | |||
| <Form.Item valuePropName={'checked'} {...props}> | |||
| <Switch></Switch> | |||
| </Form.Item> | |||
| ), | |||
| }; | |||
| return ( | |||
| BeginQueryTypeMap[q.type as BeginQueryType] ?? | |||
| BeginQueryTypeMap[BeginQueryType.Paragraph] | |||
| ); | |||
| }, | |||
| [form, handleShowPopover, onChange, switchVisible, t, visible], | |||
| ); | |||
| const onOk = useCallback(async () => { | |||
| const values = await form.validateFields(); | |||
| const nextValues = Object.entries(values).map(([key, value]) => { | |||
| const item = parameters[Number(key)]; | |||
| let nextValue = value; | |||
| if (Array.isArray(value)) { | |||
| nextValue = ``; | |||
| value.forEach((x) => { | |||
| nextValue += | |||
| x?.originFileObj instanceof File | |||
| ? `${x.name}\n${x.response?.data}\n----\n` | |||
| : `${x.url}\n${x.result}\n----\n`; | |||
| }); | |||
| } | |||
| return { ...item, value: nextValue }; | |||
| }); | |||
| ok(nextValues); | |||
| }, [form, ok, parameters]); | |||
| return ( | |||
| <> | |||
| <section className={styles.formWrapper}> | |||
| <Form.Provider | |||
| onFormFinish={(name, { values, forms }) => { | |||
| if (name === 'urlForm') { | |||
| const { basicForm } = forms; | |||
| const urlInfo = basicForm.getFieldValue(currentRecord) || []; | |||
| basicForm.setFieldsValue({ | |||
| [currentRecord]: [...urlInfo, { ...values, name: values.url }], | |||
| }); | |||
| hidePopover(); | |||
| } | |||
| }} | |||
| > | |||
| <Form | |||
| name="basicForm" | |||
| autoComplete="off" | |||
| layout={'vertical'} | |||
| form={form} | |||
| > | |||
| {parameters.map((x, idx) => { | |||
| return renderWidget(x, idx); | |||
| })} | |||
| </Form> | |||
| </Form.Provider> | |||
| </section> | |||
| <Button | |||
| type={'primary'} | |||
| block | |||
| onClick={onOk} | |||
| loading={loading} | |||
| disabled={!submittable || isUploading || submitButtonDisabled} | |||
| > | |||
| {t(isNext ? 'common.next' : 'flow.run')} | |||
| </Button> | |||
| </> | |||
| ); | |||
| }; | |||
| export default DebugContent; | |||
| @@ -0,0 +1,74 @@ | |||
| import { useParseDocument } from '@/hooks/document-hooks'; | |||
| import { useResetFormOnCloseModal } from '@/hooks/logic-hooks'; | |||
| import { IModalProps } from '@/interfaces/common'; | |||
| import { Button, Form, Input, Popover } from 'antd'; | |||
| import { PropsWithChildren } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| const reg = | |||
| /^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/; | |||
| export const PopoverForm = ({ | |||
| children, | |||
| visible, | |||
| switchVisible, | |||
| }: PropsWithChildren<IModalProps<any>>) => { | |||
| const [form] = Form.useForm(); | |||
| const { parseDocument, loading } = useParseDocument(); | |||
| const { t } = useTranslation(); | |||
| useResetFormOnCloseModal({ | |||
| form, | |||
| visible, | |||
| }); | |||
| const onOk = async () => { | |||
| const values = await form.validateFields(); | |||
| const val = values.url; | |||
| if (reg.test(val)) { | |||
| const ret = await parseDocument(val); | |||
| if (ret?.data?.code === 0) { | |||
| form.setFieldValue('result', ret?.data?.data); | |||
| form.submit(); | |||
| } | |||
| } | |||
| }; | |||
| const content = ( | |||
| <Form form={form} name="urlForm"> | |||
| <Form.Item | |||
| name="url" | |||
| rules={[{ required: true, type: 'url' }]} | |||
| className="m-0" | |||
| > | |||
| <Input | |||
| onPressEnter={(e) => e.preventDefault()} | |||
| placeholder={t('flow.pasteFileLink')} | |||
| suffix={ | |||
| <Button | |||
| type="primary" | |||
| onClick={onOk} | |||
| size={'small'} | |||
| loading={loading} | |||
| > | |||
| {t('common.submit')} | |||
| </Button> | |||
| } | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item name={'result'} noStyle /> | |||
| </Form> | |||
| ); | |||
| return ( | |||
| <Popover | |||
| content={content} | |||
| open={visible} | |||
| trigger={'click'} | |||
| onOpenChange={switchVisible} | |||
| > | |||
| {children} | |||
| </Popover> | |||
| ); | |||
| }; | |||
| @@ -0,0 +1,21 @@ | |||
| .title { | |||
| flex-basis: 60px; | |||
| } | |||
| .formWrapper { | |||
| :global(.ant-form-item-label) { | |||
| font-weight: 600; | |||
| } | |||
| } | |||
| .operatorDescription { | |||
| font-size: 14px; | |||
| padding-top: 16px; | |||
| font-weight: normal; | |||
| } | |||
| .formDrawer { | |||
| :global(.ant-drawer-content-wrapper) { | |||
| transform: translateX(0) !important; | |||
| } | |||
| } | |||
| @@ -0,0 +1,216 @@ | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { IModalProps } from '@/interfaces/common'; | |||
| import { CloseOutlined } from '@ant-design/icons'; | |||
| import { Drawer, Flex, Form, Input } from 'antd'; | |||
| import { get, isPlainObject, lowerFirst } from 'lodash'; | |||
| import { Play } from 'lucide-react'; | |||
| import { useEffect, useRef } from 'react'; | |||
| import { BeginId, Operator, operatorMap } from '../constant'; | |||
| import AkShareForm from '../form/akshare-form'; | |||
| import AnswerForm from '../form/answer-form'; | |||
| import ArXivForm from '../form/arxiv-form'; | |||
| import BaiduFanyiForm from '../form/baidu-fanyi-form'; | |||
| import BaiduForm from '../form/baidu-form'; | |||
| import BeginForm from '../form/begin-form'; | |||
| import BingForm from '../form/bing-form'; | |||
| import CategorizeForm from '../form/categorize-form'; | |||
| import CrawlerForm from '../form/crawler-form'; | |||
| import DeepLForm from '../form/deepl-form'; | |||
| import DuckDuckGoForm from '../form/duckduckgo-form'; | |||
| import EmailForm from '../form/email-form'; | |||
| import ExeSQLForm from '../form/exesql-form'; | |||
| import GenerateForm from '../form/generate-form'; | |||
| import GithubForm from '../form/github-form'; | |||
| import GoogleForm from '../form/google-form'; | |||
| import GoogleScholarForm from '../form/google-scholar-form'; | |||
| import InvokeForm from '../form/invoke-form'; | |||
| import Jin10Form from '../form/jin10-form'; | |||
| import KeywordExtractForm from '../form/keyword-extract-form'; | |||
| import MessageForm from '../form/message-form'; | |||
| import PubMedForm from '../form/pubmed-form'; | |||
| import QWeatherForm from '../form/qweather-form'; | |||
| import RelevantForm from '../form/relevant-form'; | |||
| import RetrievalForm from '../form/retrieval-form'; | |||
| import RewriteQuestionForm from '../form/rewrite-question-form'; | |||
| import SwitchForm from '../form/switch-form'; | |||
| import TemplateForm from '../form/template-form'; | |||
| import TuShareForm from '../form/tushare-form'; | |||
| import WenCaiForm from '../form/wencai-form'; | |||
| import WikipediaForm from '../form/wikipedia-form'; | |||
| import YahooFinanceForm from '../form/yahoo-finance-form'; | |||
| import { useHandleFormValuesChange, useHandleNodeNameChange } from '../hooks'; | |||
| import OperatorIcon from '../operator-icon'; | |||
| import { | |||
| buildCategorizeListFromObject, | |||
| getDrawerWidth, | |||
| needsSingleStepDebugging, | |||
| } from '../utils'; | |||
| import SingleDebugDrawer from './single-debug-drawer'; | |||
| import { RAGFlowNodeType } from '@/interfaces/database/flow'; | |||
| import { FlowFormContext } from '../context'; | |||
| import { RunTooltip } from '../flow-tooltip'; | |||
| import IterationForm from '../form/iteration-from'; | |||
| import styles from './index.less'; | |||
| interface IProps { | |||
| node?: RAGFlowNodeType; | |||
| singleDebugDrawerVisible: IModalProps<any>['visible']; | |||
| hideSingleDebugDrawer: IModalProps<any>['hideModal']; | |||
| showSingleDebugDrawer: IModalProps<any>['showModal']; | |||
| } | |||
| const FormMap = { | |||
| [Operator.Begin]: BeginForm, | |||
| [Operator.Retrieval]: RetrievalForm, | |||
| [Operator.Generate]: GenerateForm, | |||
| [Operator.Answer]: AnswerForm, | |||
| [Operator.Categorize]: CategorizeForm, | |||
| [Operator.Message]: MessageForm, | |||
| [Operator.Relevant]: RelevantForm, | |||
| [Operator.RewriteQuestion]: RewriteQuestionForm, | |||
| [Operator.Baidu]: BaiduForm, | |||
| [Operator.DuckDuckGo]: DuckDuckGoForm, | |||
| [Operator.KeywordExtract]: KeywordExtractForm, | |||
| [Operator.Wikipedia]: WikipediaForm, | |||
| [Operator.PubMed]: PubMedForm, | |||
| [Operator.ArXiv]: ArXivForm, | |||
| [Operator.Google]: GoogleForm, | |||
| [Operator.Bing]: BingForm, | |||
| [Operator.GoogleScholar]: GoogleScholarForm, | |||
| [Operator.DeepL]: DeepLForm, | |||
| [Operator.GitHub]: GithubForm, | |||
| [Operator.BaiduFanyi]: BaiduFanyiForm, | |||
| [Operator.QWeather]: QWeatherForm, | |||
| [Operator.ExeSQL]: ExeSQLForm, | |||
| [Operator.Switch]: SwitchForm, | |||
| [Operator.WenCai]: WenCaiForm, | |||
| [Operator.AkShare]: AkShareForm, | |||
| [Operator.YahooFinance]: YahooFinanceForm, | |||
| [Operator.Jin10]: Jin10Form, | |||
| [Operator.TuShare]: TuShareForm, | |||
| [Operator.Crawler]: CrawlerForm, | |||
| [Operator.Invoke]: InvokeForm, | |||
| [Operator.Concentrator]: () => <></>, | |||
| [Operator.Note]: () => <></>, | |||
| [Operator.Template]: TemplateForm, | |||
| [Operator.Email]: EmailForm, | |||
| [Operator.Iteration]: IterationForm, | |||
| [Operator.IterationStart]: () => <></>, | |||
| }; | |||
| const EmptyContent = () => <div></div>; | |||
| const FormDrawer = ({ | |||
| visible, | |||
| hideModal, | |||
| node, | |||
| singleDebugDrawerVisible, | |||
| hideSingleDebugDrawer, | |||
| showSingleDebugDrawer, | |||
| }: IModalProps<any> & IProps) => { | |||
| const operatorName: Operator = node?.data.label as Operator; | |||
| const OperatorForm = FormMap[operatorName] ?? EmptyContent; | |||
| const [form] = Form.useForm(); | |||
| const { name, handleNameBlur, handleNameChange } = useHandleNodeNameChange({ | |||
| id: node?.id, | |||
| data: node?.data, | |||
| }); | |||
| const previousId = useRef<string | undefined>(node?.id); | |||
| const { t } = useTranslate('flow'); | |||
| const { handleValuesChange } = useHandleFormValuesChange(node?.id); | |||
| useEffect(() => { | |||
| if (visible) { | |||
| if (node?.id !== previousId.current) { | |||
| form.resetFields(); | |||
| } | |||
| if (operatorName === Operator.Categorize) { | |||
| const items = buildCategorizeListFromObject( | |||
| get(node, 'data.form.category_description', {}), | |||
| ); | |||
| const formData = node?.data?.form; | |||
| if (isPlainObject(formData)) { | |||
| form.setFieldsValue({ ...formData, items }); | |||
| } | |||
| } else { | |||
| form.setFieldsValue(node?.data?.form); | |||
| } | |||
| previousId.current = node?.id; | |||
| } | |||
| }, [visible, form, node?.data?.form, node?.id, node, operatorName]); | |||
| return ( | |||
| <Drawer | |||
| title={ | |||
| <Flex vertical> | |||
| <Flex gap={'middle'} align="center"> | |||
| <OperatorIcon | |||
| name={operatorName} | |||
| color={operatorMap[operatorName]?.color} | |||
| ></OperatorIcon> | |||
| <Flex align="center" gap={'small'} flex={1}> | |||
| <label htmlFor="" className={styles.title}> | |||
| {t('title')} | |||
| </label> | |||
| {node?.id === BeginId ? ( | |||
| <span>{t(BeginId)}</span> | |||
| ) : ( | |||
| <Input | |||
| value={name} | |||
| onBlur={handleNameBlur} | |||
| onChange={handleNameChange} | |||
| ></Input> | |||
| )} | |||
| </Flex> | |||
| {needsSingleStepDebugging(operatorName) && ( | |||
| <RunTooltip> | |||
| <Play | |||
| className="size-5 cursor-pointer" | |||
| onClick={showSingleDebugDrawer} | |||
| /> | |||
| </RunTooltip> | |||
| )} | |||
| <CloseOutlined onClick={hideModal} /> | |||
| </Flex> | |||
| <span className={styles.operatorDescription}> | |||
| {t(`${lowerFirst(operatorName)}Description`)} | |||
| </span> | |||
| </Flex> | |||
| } | |||
| placement="right" | |||
| onClose={hideModal} | |||
| open={visible} | |||
| getContainer={false} | |||
| mask={false} | |||
| width={getDrawerWidth()} | |||
| closeIcon={null} | |||
| rootClassName={styles.formDrawer} | |||
| > | |||
| <section className={styles.formWrapper}> | |||
| {visible && ( | |||
| <FlowFormContext.Provider value={node}> | |||
| <OperatorForm | |||
| onValuesChange={handleValuesChange} | |||
| form={form} | |||
| node={node} | |||
| ></OperatorForm> | |||
| </FlowFormContext.Provider> | |||
| )} | |||
| </section> | |||
| {singleDebugDrawerVisible && ( | |||
| <SingleDebugDrawer | |||
| visible={singleDebugDrawerVisible} | |||
| hideModal={hideSingleDebugDrawer} | |||
| componentId={node?.id} | |||
| ></SingleDebugDrawer> | |||
| )} | |||
| </Drawer> | |||
| ); | |||
| }; | |||
| export default FormDrawer; | |||
| @@ -0,0 +1,81 @@ | |||
| import CopyToClipboard from '@/components/copy-to-clipboard'; | |||
| import { useDebugSingle, useFetchInputElements } from '@/hooks/flow-hooks'; | |||
| import { IModalProps } from '@/interfaces/common'; | |||
| import { CloseOutlined } from '@ant-design/icons'; | |||
| import { Drawer } from 'antd'; | |||
| import { isEmpty } from 'lodash'; | |||
| import { useCallback } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import JsonView from 'react18-json-view'; | |||
| import 'react18-json-view/src/style.css'; | |||
| import DebugContent from '../../debug-content'; | |||
| interface IProps { | |||
| componentId?: string; | |||
| } | |||
| const SingleDebugDrawer = ({ | |||
| componentId, | |||
| visible, | |||
| hideModal, | |||
| }: IModalProps<any> & IProps) => { | |||
| const { t } = useTranslation(); | |||
| const { data: list } = useFetchInputElements(componentId); | |||
| const { debugSingle, data, loading } = useDebugSingle(); | |||
| const onOk = useCallback( | |||
| (nextValues: any[]) => { | |||
| if (componentId) { | |||
| debugSingle({ component_id: componentId, params: nextValues }); | |||
| } | |||
| }, | |||
| [componentId, debugSingle], | |||
| ); | |||
| const content = JSON.stringify(data, null, 2); | |||
| return ( | |||
| <Drawer | |||
| title={ | |||
| <div className="flex justify-between"> | |||
| {t('flow.testRun')} | |||
| <CloseOutlined onClick={hideModal} /> | |||
| </div> | |||
| } | |||
| width={'100%'} | |||
| onClose={hideModal} | |||
| open={visible} | |||
| getContainer={false} | |||
| mask={false} | |||
| placement={'bottom'} | |||
| height={'95%'} | |||
| closeIcon={null} | |||
| > | |||
| <section className="overflow-y-auto"> | |||
| <DebugContent | |||
| parameters={list} | |||
| ok={onOk} | |||
| isNext={false} | |||
| loading={loading} | |||
| submitButtonDisabled={list.length === 0} | |||
| ></DebugContent> | |||
| {!isEmpty(data) ? ( | |||
| <div className="mt-4 rounded-md bg-slate-200 border border-neutral-200"> | |||
| <div className="flex justify-between p-2"> | |||
| <span>JSON</span> | |||
| <CopyToClipboard text={content}></CopyToClipboard> | |||
| </div> | |||
| <JsonView | |||
| src={data} | |||
| displaySize | |||
| collapseStringsAfterLength={100000000000} | |||
| className="w-full h-[800px] break-words overflow-auto p-2 bg-slate-100" | |||
| /> | |||
| </div> | |||
| ) : null} | |||
| </section> | |||
| </Drawer> | |||
| ); | |||
| }; | |||
| export default SingleDebugDrawer; | |||
| @@ -0,0 +1,77 @@ | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { useCallback, useMemo } from 'react'; | |||
| import { Operator, RestrictedUpstreamMap } from './constant'; | |||
| import useGraphStore from './store'; | |||
| export const useBuildFormSelectOptions = ( | |||
| operatorName: Operator, | |||
| selfId?: string, // exclude the current node | |||
| ) => { | |||
| const nodes = useGraphStore((state) => state.nodes); | |||
| const buildCategorizeToOptions = useCallback( | |||
| (toList: string[]) => { | |||
| const excludedNodes: Operator[] = [ | |||
| Operator.Note, | |||
| ...(RestrictedUpstreamMap[operatorName] ?? []), | |||
| ]; | |||
| return nodes | |||
| .filter( | |||
| (x) => | |||
| excludedNodes.every((y) => y !== x.data.label) && | |||
| x.id !== selfId && | |||
| !toList.some((y) => y === x.id), // filter out selected values in other to fields from the current drop-down box options | |||
| ) | |||
| .map((x) => ({ label: x.data.name, value: x.id })); | |||
| }, | |||
| [nodes, operatorName, selfId], | |||
| ); | |||
| return buildCategorizeToOptions; | |||
| }; | |||
| /** | |||
| * dumped | |||
| * @param nodeId | |||
| * @returns | |||
| */ | |||
| export const useHandleFormSelectChange = (nodeId?: string) => { | |||
| const { addEdge, deleteEdgeBySourceAndSourceHandle } = useGraphStore( | |||
| (state) => state, | |||
| ); | |||
| const handleSelectChange = useCallback( | |||
| (name?: string) => (value?: string) => { | |||
| if (nodeId && name) { | |||
| if (value) { | |||
| addEdge({ | |||
| source: nodeId, | |||
| target: value, | |||
| sourceHandle: name, | |||
| targetHandle: null, | |||
| }); | |||
| } else { | |||
| // clear selected value | |||
| deleteEdgeBySourceAndSourceHandle({ | |||
| source: nodeId, | |||
| sourceHandle: name, | |||
| }); | |||
| } | |||
| } | |||
| }, | |||
| [addEdge, nodeId, deleteEdgeBySourceAndSourceHandle], | |||
| ); | |||
| return { handleSelectChange }; | |||
| }; | |||
| export const useBuildSortOptions = () => { | |||
| const { t } = useTranslate('flow'); | |||
| const options = useMemo(() => { | |||
| return ['data', 'relevance'].map((x) => ({ | |||
| value: x, | |||
| label: t(x), | |||
| })); | |||
| }, [t]); | |||
| return options; | |||
| }; | |||
| @@ -0,0 +1,21 @@ | |||
| import TopNItem from '@/components/top-n-item'; | |||
| import { Form } from 'antd'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| const AkShareForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <TopNItem initialValue={10} max={99}></TopNItem> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default AkShareForm; | |||
| @@ -0,0 +1,5 @@ | |||
| const AnswerForm = () => { | |||
| return <div></div>; | |||
| }; | |||
| export default AnswerForm; | |||
| @@ -0,0 +1,36 @@ | |||
| import TopNItem from '@/components/top-n-item'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Form, Select } from 'antd'; | |||
| import { useMemo } from 'react'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| const ArXivForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| const options = useMemo(() => { | |||
| return ['submittedDate', 'lastUpdatedDate', 'relevance'].map((x) => ({ | |||
| value: x, | |||
| label: t(x), | |||
| })); | |||
| }, [t]); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <TopNItem initialValue={10}></TopNItem> | |||
| <Form.Item label={t('sortBy')} name={'sort_by'}> | |||
| <Select options={options}></Select> | |||
| </Form.Item> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default ArXivForm; | |||
| @@ -0,0 +1,71 @@ | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Form, Input, Select } from 'antd'; | |||
| import { useMemo } from 'react'; | |||
| import { | |||
| BaiduFanyiDomainOptions, | |||
| BaiduFanyiSourceLangOptions, | |||
| } from '../../constant'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| const BaiduFanyiForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| const options = useMemo(() => { | |||
| return ['translate', 'fieldtranslate'].map((x) => ({ | |||
| value: x, | |||
| label: t(`baiduSecretKeyOptions.${x}`), | |||
| })); | |||
| }, [t]); | |||
| const baiduFanyiOptions = useMemo(() => { | |||
| return BaiduFanyiDomainOptions.map((x) => ({ | |||
| value: x, | |||
| label: t(`baiduDomainOptions.${x}`), | |||
| })); | |||
| }, [t]); | |||
| const baiduFanyiSourceLangOptions = useMemo(() => { | |||
| return BaiduFanyiSourceLangOptions.map((x) => ({ | |||
| value: x, | |||
| label: t(`baiduSourceLangOptions.${x}`), | |||
| })); | |||
| }, [t]); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <Form.Item label={t('appid')} name={'appid'}> | |||
| <Input></Input> | |||
| </Form.Item> | |||
| <Form.Item label={t('secretKey')} name={'secret_key'}> | |||
| <Input></Input> | |||
| </Form.Item> | |||
| <Form.Item label={t('transType')} name={'trans_type'}> | |||
| <Select options={options}></Select> | |||
| </Form.Item> | |||
| <Form.Item noStyle dependencies={['model_type']}> | |||
| {({ getFieldValue }) => | |||
| getFieldValue('trans_type') === 'fieldtranslate' && ( | |||
| <Form.Item label={t('domain')} name={'domain'}> | |||
| <Select options={baiduFanyiOptions}></Select> | |||
| </Form.Item> | |||
| ) | |||
| } | |||
| </Form.Item> | |||
| <Form.Item label={t('sourceLang')} name={'source_lang'}> | |||
| <Select options={baiduFanyiSourceLangOptions}></Select> | |||
| </Form.Item> | |||
| <Form.Item label={t('targetLang')} name={'target_lang'}> | |||
| <Select options={baiduFanyiSourceLangOptions}></Select> | |||
| </Form.Item> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default BaiduFanyiForm; | |||
| @@ -0,0 +1,21 @@ | |||
| import TopNItem from '@/components/top-n-item'; | |||
| import { Form } from 'antd'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| const BaiduForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <TopNItem initialValue={10}></TopNItem> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default BaiduForm; | |||
| @@ -0,0 +1,68 @@ | |||
| import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; | |||
| import { Button, Form, Input } from 'antd'; | |||
| const BeginDynamicOptions = () => { | |||
| return ( | |||
| <Form.List | |||
| name="options" | |||
| rules={[ | |||
| { | |||
| validator: async (_, names) => { | |||
| if (!names || names.length < 1) { | |||
| return Promise.reject(new Error('At least 1 option')); | |||
| } | |||
| }, | |||
| }, | |||
| ]} | |||
| > | |||
| {(fields, { add, remove }, { errors }) => ( | |||
| <> | |||
| {fields.map((field, index) => ( | |||
| <Form.Item | |||
| label={index === 0 ? 'Options' : ''} | |||
| required={false} | |||
| key={field.key} | |||
| > | |||
| <Form.Item | |||
| {...field} | |||
| validateTrigger={['onChange', 'onBlur']} | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| whitespace: true, | |||
| message: 'Please input option or delete this field.', | |||
| }, | |||
| ]} | |||
| noStyle | |||
| > | |||
| <Input | |||
| placeholder="option" | |||
| style={{ width: '90%', marginRight: 16 }} | |||
| /> | |||
| </Form.Item> | |||
| {fields.length > 1 ? ( | |||
| <MinusCircleOutlined | |||
| className="dynamic-delete-button" | |||
| onClick={() => remove(field.name)} | |||
| /> | |||
| ) : null} | |||
| </Form.Item> | |||
| ))} | |||
| <Form.Item> | |||
| <Button | |||
| type="dashed" | |||
| onClick={() => add()} | |||
| icon={<PlusOutlined />} | |||
| block | |||
| > | |||
| Add option | |||
| </Button> | |||
| <Form.ErrorList errors={errors} /> | |||
| </Form.Item> | |||
| </> | |||
| )} | |||
| </Form.List> | |||
| ); | |||
| }; | |||
| export default BeginDynamicOptions; | |||
| @@ -0,0 +1,50 @@ | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { useSetSelectedRecord } from '@/hooks/logic-hooks'; | |||
| import { useCallback, useMemo, useState } from 'react'; | |||
| import { BeginQuery, IOperatorForm } from '../../interface'; | |||
| export const useEditQueryRecord = ({ form, onValuesChange }: IOperatorForm) => { | |||
| const { setRecord, currentRecord } = useSetSelectedRecord<BeginQuery>(); | |||
| const { visible, hideModal, showModal } = useSetModalState(); | |||
| const [index, setIndex] = useState(-1); | |||
| const otherThanCurrentQuery = useMemo(() => { | |||
| const query: BeginQuery[] = form?.getFieldValue('query') || []; | |||
| return query.filter((item, idx) => idx !== index); | |||
| }, [form, index]); | |||
| const handleEditRecord = useCallback( | |||
| (record: BeginQuery) => { | |||
| const query: BeginQuery[] = form?.getFieldValue('query') || []; | |||
| const nextQuery: BeginQuery[] = | |||
| index > -1 ? query.toSpliced(index, 1, record) : [...query, record]; | |||
| onValuesChange?.( | |||
| { query: nextQuery }, | |||
| { query: nextQuery, prologue: form?.getFieldValue('prologue') }, | |||
| ); | |||
| hideModal(); | |||
| }, | |||
| [form, hideModal, index, onValuesChange], | |||
| ); | |||
| const handleShowModal = useCallback( | |||
| (idx?: number, record?: BeginQuery) => { | |||
| setIndex(idx ?? -1); | |||
| setRecord(record ?? ({} as BeginQuery)); | |||
| showModal(); | |||
| }, | |||
| [setRecord, showModal], | |||
| ); | |||
| return { | |||
| ok: handleEditRecord, | |||
| currentRecord, | |||
| setRecord, | |||
| visible, | |||
| hideModal, | |||
| showModal: handleShowModal, | |||
| otherThanCurrentQuery, | |||
| }; | |||
| }; | |||
| @@ -0,0 +1,24 @@ | |||
| .dynamicInputVariable { | |||
| background-color: #ebe9e950; | |||
| :global(.ant-collapse-content) { | |||
| background-color: #f6f6f657; | |||
| } | |||
| :global(.ant-collapse-content-box) { | |||
| padding: 0 !important; | |||
| } | |||
| margin-bottom: 20px; | |||
| .title { | |||
| font-weight: 600; | |||
| font-size: 16px; | |||
| } | |||
| .addButton { | |||
| color: rgb(22, 119, 255); | |||
| font-weight: 600; | |||
| } | |||
| } | |||
| .addButton { | |||
| color: rgb(22, 119, 255); | |||
| font-weight: 600; | |||
| } | |||
| @@ -0,0 +1,111 @@ | |||
| import { PlusOutlined } from '@ant-design/icons'; | |||
| import { Button, Form, Input } from 'antd'; | |||
| import { useCallback } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { BeginQuery, IOperatorForm } from '../../interface'; | |||
| import { useEditQueryRecord } from './hooks'; | |||
| import { ModalForm } from './paramater-modal'; | |||
| import QueryTable from './query-table'; | |||
| import styles from './index.less'; | |||
| type FieldType = { | |||
| prologue?: string; | |||
| }; | |||
| const BeginForm = ({ onValuesChange, form }: IOperatorForm) => { | |||
| const { t } = useTranslation(); | |||
| const { | |||
| ok, | |||
| currentRecord, | |||
| visible, | |||
| hideModal, | |||
| showModal, | |||
| otherThanCurrentQuery, | |||
| } = useEditQueryRecord({ | |||
| form, | |||
| onValuesChange, | |||
| }); | |||
| const handleDeleteRecord = useCallback( | |||
| (idx: number) => { | |||
| const query = form?.getFieldValue('query') || []; | |||
| const nextQuery = query.filter( | |||
| (item: BeginQuery, index: number) => index !== idx, | |||
| ); | |||
| onValuesChange?.( | |||
| { query: nextQuery }, | |||
| { query: nextQuery, prologue: form?.getFieldValue('prologue') }, | |||
| ); | |||
| }, | |||
| [form, onValuesChange], | |||
| ); | |||
| return ( | |||
| <Form.Provider | |||
| onFormFinish={(name, { values }) => { | |||
| if (name === 'queryForm') { | |||
| ok(values as BeginQuery); | |||
| } | |||
| }} | |||
| > | |||
| <Form | |||
| name="basicForm" | |||
| onValuesChange={onValuesChange} | |||
| autoComplete="off" | |||
| form={form} | |||
| layout="vertical" | |||
| > | |||
| <Form.Item<FieldType> | |||
| name={'prologue'} | |||
| label={t('chat.setAnOpener')} | |||
| tooltip={t('chat.setAnOpenerTip')} | |||
| initialValue={t('chat.setAnOpenerInitial')} | |||
| > | |||
| <Input.TextArea autoSize={{ minRows: 5 }} /> | |||
| </Form.Item> | |||
| {/* Create a hidden field to make Form instance record this */} | |||
| <Form.Item name="query" noStyle /> | |||
| <Form.Item | |||
| shouldUpdate={(prevValues, curValues) => | |||
| prevValues.query !== curValues.query | |||
| } | |||
| > | |||
| {({ getFieldValue }) => { | |||
| const query: BeginQuery[] = getFieldValue('query') || []; | |||
| return ( | |||
| <QueryTable | |||
| data={query} | |||
| showModal={showModal} | |||
| deleteRecord={handleDeleteRecord} | |||
| ></QueryTable> | |||
| ); | |||
| }} | |||
| </Form.Item> | |||
| <Button | |||
| htmlType="button" | |||
| style={{ margin: '0 8px' }} | |||
| onClick={() => showModal()} | |||
| icon={<PlusOutlined />} | |||
| block | |||
| className={styles.addButton} | |||
| > | |||
| {t('flow.addItem')} | |||
| </Button> | |||
| {visible && ( | |||
| <ModalForm | |||
| visible={visible} | |||
| hideModal={hideModal} | |||
| initialValue={currentRecord} | |||
| onOk={ok} | |||
| otherThanCurrentQuery={otherThanCurrentQuery} | |||
| /> | |||
| )} | |||
| </Form> | |||
| </Form.Provider> | |||
| ); | |||
| }; | |||
| export default BeginForm; | |||
| @@ -0,0 +1,124 @@ | |||
| import { useResetFormOnCloseModal } from '@/hooks/logic-hooks'; | |||
| import { IModalProps } from '@/interfaces/common'; | |||
| import { Form, Input, Modal, Select, Switch } from 'antd'; | |||
| import { DefaultOptionType } from 'antd/es/select'; | |||
| import { useEffect, useMemo } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { BeginQueryType, BeginQueryTypeIconMap } from '../../constant'; | |||
| import { BeginQuery } from '../../interface'; | |||
| import BeginDynamicOptions from './begin-dynamic-options'; | |||
| export const ModalForm = ({ | |||
| visible, | |||
| initialValue, | |||
| hideModal, | |||
| otherThanCurrentQuery, | |||
| }: IModalProps<BeginQuery> & { | |||
| initialValue: BeginQuery; | |||
| otherThanCurrentQuery: BeginQuery[]; | |||
| }) => { | |||
| const { t } = useTranslation(); | |||
| const [form] = Form.useForm(); | |||
| const options = useMemo(() => { | |||
| return Object.values(BeginQueryType).reduce<DefaultOptionType[]>( | |||
| (pre, cur) => { | |||
| const Icon = BeginQueryTypeIconMap[cur]; | |||
| return [ | |||
| ...pre, | |||
| { | |||
| label: ( | |||
| <div className="flex items-center gap-2"> | |||
| <Icon | |||
| className={`size-${cur === BeginQueryType.Options ? 4 : 5}`} | |||
| ></Icon> | |||
| {cur} | |||
| </div> | |||
| ), | |||
| value: cur, | |||
| }, | |||
| ]; | |||
| }, | |||
| [], | |||
| ); | |||
| }, []); | |||
| useResetFormOnCloseModal({ | |||
| form, | |||
| visible: visible, | |||
| }); | |||
| useEffect(() => { | |||
| form.setFieldsValue(initialValue); | |||
| }, [form, initialValue]); | |||
| const onOk = () => { | |||
| form.submit(); | |||
| }; | |||
| return ( | |||
| <Modal | |||
| title={t('flow.variableSettings')} | |||
| open={visible} | |||
| onOk={onOk} | |||
| onCancel={hideModal} | |||
| centered | |||
| > | |||
| <Form form={form} layout="vertical" name="queryForm" autoComplete="false"> | |||
| <Form.Item | |||
| name="type" | |||
| label="Type" | |||
| rules={[{ required: true }]} | |||
| initialValue={BeginQueryType.Line} | |||
| > | |||
| <Select options={options} /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| name="key" | |||
| label="Key" | |||
| rules={[ | |||
| { required: true }, | |||
| () => ({ | |||
| validator(_, value) { | |||
| if ( | |||
| !value || | |||
| !otherThanCurrentQuery.some((x) => x.key === value) | |||
| ) { | |||
| return Promise.resolve(); | |||
| } | |||
| return Promise.reject(new Error('The key cannot be repeated!')); | |||
| }, | |||
| }), | |||
| ]} | |||
| > | |||
| <Input /> | |||
| </Form.Item> | |||
| <Form.Item name="name" label="Name" rules={[{ required: true }]}> | |||
| <Input /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| name="optional" | |||
| label={'Optional'} | |||
| valuePropName="checked" | |||
| initialValue={false} | |||
| > | |||
| <Switch /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| shouldUpdate={(prevValues, curValues) => | |||
| prevValues.type !== curValues.type | |||
| } | |||
| > | |||
| {({ getFieldValue }) => { | |||
| const type: BeginQueryType = getFieldValue('type'); | |||
| return ( | |||
| type === BeginQueryType.Options && ( | |||
| <BeginDynamicOptions></BeginDynamicOptions> | |||
| ) | |||
| ); | |||
| }} | |||
| </Form.Item> | |||
| </Form> | |||
| </Modal> | |||
| ); | |||
| }; | |||
| @@ -0,0 +1,92 @@ | |||
| import { DeleteOutlined, EditOutlined } from '@ant-design/icons'; | |||
| import type { TableProps } from 'antd'; | |||
| import { Collapse, Space, Table, Tooltip } from 'antd'; | |||
| import { BeginQuery } from '../../interface'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import styles from './index.less'; | |||
| interface IProps { | |||
| data: BeginQuery[]; | |||
| deleteRecord(index: number): void; | |||
| showModal(index: number, record: BeginQuery): void; | |||
| } | |||
| const QueryTable = ({ data, deleteRecord, showModal }: IProps) => { | |||
| const { t } = useTranslation(); | |||
| const columns: TableProps<BeginQuery>['columns'] = [ | |||
| { | |||
| title: 'Key', | |||
| dataIndex: 'key', | |||
| key: 'key', | |||
| ellipsis: { | |||
| showTitle: false, | |||
| }, | |||
| render: (key) => ( | |||
| <Tooltip placement="topLeft" title={key}> | |||
| {key} | |||
| </Tooltip> | |||
| ), | |||
| }, | |||
| { | |||
| title: t('flow.name'), | |||
| dataIndex: 'name', | |||
| key: 'name', | |||
| ellipsis: { | |||
| showTitle: false, | |||
| }, | |||
| render: (name) => ( | |||
| <Tooltip placement="topLeft" title={name}> | |||
| {name} | |||
| </Tooltip> | |||
| ), | |||
| }, | |||
| { | |||
| title: t('flow.type'), | |||
| dataIndex: 'type', | |||
| key: 'type', | |||
| }, | |||
| { | |||
| title: t('flow.optional'), | |||
| dataIndex: 'optional', | |||
| key: 'optional', | |||
| render: (optional) => (optional ? 'Yes' : 'No'), | |||
| }, | |||
| { | |||
| title: t('common.action'), | |||
| key: 'action', | |||
| render: (_, record, idx) => ( | |||
| <Space> | |||
| <EditOutlined onClick={() => showModal(idx, record)} /> | |||
| <DeleteOutlined | |||
| className="cursor-pointer" | |||
| onClick={() => deleteRecord(idx)} | |||
| /> | |||
| </Space> | |||
| ), | |||
| }, | |||
| ]; | |||
| return ( | |||
| <Collapse | |||
| defaultActiveKey={['1']} | |||
| className={styles.dynamicInputVariable} | |||
| items={[ | |||
| { | |||
| key: '1', | |||
| label: <span className={styles.title}>{t('flow.input')}</span>, | |||
| children: ( | |||
| <Table<BeginQuery> | |||
| columns={columns} | |||
| dataSource={data} | |||
| pagination={false} | |||
| /> | |||
| ), | |||
| }, | |||
| ]} | |||
| /> | |||
| ); | |||
| }; | |||
| export default QueryTable; | |||
| @@ -0,0 +1,42 @@ | |||
| import TopNItem from '@/components/top-n-item'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Form, Input, Select } from 'antd'; | |||
| import { useMemo } from 'react'; | |||
| import { BingCountryOptions, BingLanguageOptions } from '../../constant'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| const BingForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| const options = useMemo(() => { | |||
| return ['Webpages', 'News'].map((x) => ({ label: x, value: x })); | |||
| }, []); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <TopNItem initialValue={10}></TopNItem> | |||
| <Form.Item label={t('channel')} name={'channel'}> | |||
| <Select options={options}></Select> | |||
| </Form.Item> | |||
| <Form.Item label={t('apiKey')} name={'api_key'}> | |||
| <Input></Input> | |||
| </Form.Item> | |||
| <Form.Item label={t('country')} name={'country'}> | |||
| <Select options={BingCountryOptions}></Select> | |||
| </Form.Item> | |||
| <Form.Item label={t('language')} name={'language'}> | |||
| <Select options={BingLanguageOptions}></Select> | |||
| </Form.Item> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default BingForm; | |||
| @@ -0,0 +1,225 @@ | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { CloseOutlined, PlusOutlined } from '@ant-design/icons'; | |||
| import { useUpdateNodeInternals } from '@xyflow/react'; | |||
| import { | |||
| Button, | |||
| Collapse, | |||
| Flex, | |||
| Form, | |||
| FormListFieldData, | |||
| Input, | |||
| Select, | |||
| } from 'antd'; | |||
| import { FormInstance } from 'antd/lib'; | |||
| import { humanId } from 'human-id'; | |||
| import trim from 'lodash/trim'; | |||
| import { | |||
| ChangeEventHandler, | |||
| FocusEventHandler, | |||
| useCallback, | |||
| useEffect, | |||
| useState, | |||
| } from 'react'; | |||
| import { Operator } from '../../constant'; | |||
| import { useBuildFormSelectOptions } from '../../form-hooks'; | |||
| import styles from './index.less'; | |||
| interface IProps { | |||
| nodeId?: string; | |||
| } | |||
| interface INameInputProps { | |||
| value?: string; | |||
| onChange?: (value: string) => void; | |||
| otherNames?: string[]; | |||
| validate(errors: string[]): void; | |||
| } | |||
| const getOtherFieldValues = ( | |||
| form: FormInstance, | |||
| formListName: string = 'items', | |||
| field: FormListFieldData, | |||
| latestField: string, | |||
| ) => | |||
| (form.getFieldValue([formListName]) ?? []) | |||
| .map((x: any) => x[latestField]) | |||
| .filter( | |||
| (x: string) => | |||
| x !== form.getFieldValue([formListName, field.name, latestField]), | |||
| ); | |||
| const NameInput = ({ | |||
| value, | |||
| onChange, | |||
| otherNames, | |||
| validate, | |||
| }: INameInputProps) => { | |||
| const [name, setName] = useState<string | undefined>(); | |||
| const { t } = useTranslate('flow'); | |||
| const handleNameChange: ChangeEventHandler<HTMLInputElement> = useCallback( | |||
| (e) => { | |||
| const val = e.target.value; | |||
| // trigger validation | |||
| if (otherNames?.some((x) => x === val)) { | |||
| validate([t('nameRepeatedMsg')]); | |||
| } else if (trim(val) === '') { | |||
| validate([t('nameRequiredMsg')]); | |||
| } else { | |||
| validate([]); | |||
| } | |||
| setName(val); | |||
| }, | |||
| [otherNames, validate, t], | |||
| ); | |||
| const handleNameBlur: FocusEventHandler<HTMLInputElement> = useCallback( | |||
| (e) => { | |||
| const val = e.target.value; | |||
| if (otherNames?.every((x) => x !== val) && trim(val) !== '') { | |||
| onChange?.(val); | |||
| } | |||
| }, | |||
| [onChange, otherNames], | |||
| ); | |||
| useEffect(() => { | |||
| setName(value); | |||
| }, [value]); | |||
| return ( | |||
| <Input | |||
| value={name} | |||
| onChange={handleNameChange} | |||
| onBlur={handleNameBlur} | |||
| ></Input> | |||
| ); | |||
| }; | |||
| const FormSet = ({ nodeId, field }: IProps & { field: FormListFieldData }) => { | |||
| const form = Form.useFormInstance(); | |||
| const { t } = useTranslate('flow'); | |||
| const buildCategorizeToOptions = useBuildFormSelectOptions( | |||
| Operator.Categorize, | |||
| nodeId, | |||
| ); | |||
| return ( | |||
| <section> | |||
| <Form.Item | |||
| label={t('categoryName')} | |||
| name={[field.name, 'name']} | |||
| validateTrigger={['onChange', 'onBlur']} | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| whitespace: true, | |||
| message: t('nameMessage'), | |||
| }, | |||
| ]} | |||
| > | |||
| <NameInput | |||
| otherNames={getOtherFieldValues(form, 'items', field, 'name')} | |||
| validate={(errors: string[]) => | |||
| form.setFields([ | |||
| { | |||
| name: ['items', field.name, 'name'], | |||
| errors, | |||
| }, | |||
| ]) | |||
| } | |||
| ></NameInput> | |||
| </Form.Item> | |||
| <Form.Item label={t('description')} name={[field.name, 'description']}> | |||
| <Input.TextArea rows={3} /> | |||
| </Form.Item> | |||
| <Form.Item label={t('examples')} name={[field.name, 'examples']}> | |||
| <Input.TextArea rows={3} /> | |||
| </Form.Item> | |||
| <Form.Item label={t('nextStep')} name={[field.name, 'to']}> | |||
| <Select | |||
| allowClear | |||
| options={buildCategorizeToOptions( | |||
| getOtherFieldValues(form, 'items', field, 'to'), | |||
| )} | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item hidden name={[field.name, 'index']}> | |||
| <Input /> | |||
| </Form.Item> | |||
| </section> | |||
| ); | |||
| }; | |||
| const DynamicCategorize = ({ nodeId }: IProps) => { | |||
| const updateNodeInternals = useUpdateNodeInternals(); | |||
| const form = Form.useFormInstance(); | |||
| const { t } = useTranslate('flow'); | |||
| return ( | |||
| <> | |||
| <Form.List name="items"> | |||
| {(fields, { add, remove }) => { | |||
| const handleAdd = () => { | |||
| const idx = form.getFieldValue([ | |||
| 'items', | |||
| fields.at(-1)?.name, | |||
| 'index', | |||
| ]); | |||
| add({ | |||
| name: humanId(), | |||
| index: fields.length === 0 ? 0 : idx + 1, | |||
| }); | |||
| if (nodeId) updateNodeInternals(nodeId); | |||
| }; | |||
| return ( | |||
| <Flex gap={18} vertical> | |||
| {fields.map((field) => ( | |||
| <Collapse | |||
| size="small" | |||
| key={field.key} | |||
| className={styles.caseCard} | |||
| items={[ | |||
| { | |||
| key: field.key, | |||
| label: ( | |||
| <div className="flex justify-between"> | |||
| <span> | |||
| {form.getFieldValue(['items', field.name, 'name'])} | |||
| </span> | |||
| <CloseOutlined | |||
| onClick={() => { | |||
| remove(field.name); | |||
| }} | |||
| /> | |||
| </div> | |||
| ), | |||
| children: ( | |||
| <FormSet nodeId={nodeId} field={field}></FormSet> | |||
| ), | |||
| }, | |||
| ]} | |||
| ></Collapse> | |||
| ))} | |||
| <Button | |||
| type="dashed" | |||
| onClick={handleAdd} | |||
| block | |||
| className={styles.addButton} | |||
| icon={<PlusOutlined />} | |||
| > | |||
| {t('addCategory')} | |||
| </Button> | |||
| </Flex> | |||
| ); | |||
| }} | |||
| </Form.List> | |||
| </> | |||
| ); | |||
| }; | |||
| export default DynamicCategorize; | |||
| @@ -0,0 +1,45 @@ | |||
| import { | |||
| ICategorizeItem, | |||
| ICategorizeItemResult, | |||
| } from '@/interfaces/database/flow'; | |||
| import omit from 'lodash/omit'; | |||
| import { useCallback } from 'react'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| /** | |||
| * Convert the list in the following form into an object | |||
| * { | |||
| "items": [ | |||
| { | |||
| "name": "Categorize 1", | |||
| "description": "111", | |||
| "examples": "ddd", | |||
| "to": "Retrieval:LazyEelsStick" | |||
| } | |||
| ] | |||
| } | |||
| */ | |||
| const buildCategorizeObjectFromList = (list: Array<ICategorizeItem>) => { | |||
| return list.reduce<ICategorizeItemResult>((pre, cur) => { | |||
| if (cur?.name) { | |||
| pre[cur.name] = omit(cur, 'name'); | |||
| } | |||
| return pre; | |||
| }, {}); | |||
| }; | |||
| export const useHandleFormValuesChange = ({ | |||
| onValuesChange, | |||
| }: IOperatorForm) => { | |||
| const handleValuesChange = useCallback( | |||
| (changedValues: any, values: any) => { | |||
| onValuesChange?.(changedValues, { | |||
| ...omit(values, 'items'), | |||
| category_description: buildCategorizeObjectFromList(values.items), | |||
| }); | |||
| }, | |||
| [onValuesChange], | |||
| ); | |||
| return { handleValuesChange }; | |||
| }; | |||
| @@ -0,0 +1,13 @@ | |||
| @lightBackgroundColor: rgba(150, 150, 150, 0.07); | |||
| @darkBackgroundColor: rgba(150, 150, 150, 0.12); | |||
| .caseCard { | |||
| :global(.ant-collapse-content) { | |||
| background-color: @darkBackgroundColor; | |||
| } | |||
| } | |||
| .addButton { | |||
| color: rgb(22, 119, 255); | |||
| font-weight: 600; | |||
| } | |||
| @@ -0,0 +1,43 @@ | |||
| import LLMSelect from '@/components/llm-select'; | |||
| import MessageHistoryWindowSizeItem from '@/components/message-history-window-size-item'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Form } from 'antd'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| import DynamicCategorize from './dynamic-categorize'; | |||
| import { useHandleFormValuesChange } from './hooks'; | |||
| const CategorizeForm = ({ form, onValuesChange, node }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| const { handleValuesChange } = useHandleFormValuesChange({ | |||
| form, | |||
| nodeId: node?.id, | |||
| onValuesChange, | |||
| }); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={handleValuesChange} | |||
| initialValues={{ items: [{}] }} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <Form.Item | |||
| name={'llm_id'} | |||
| label={t('model', { keyPrefix: 'chat' })} | |||
| tooltip={t('modelTip', { keyPrefix: 'chat' })} | |||
| > | |||
| <LLMSelect></LLMSelect> | |||
| </Form.Item> | |||
| <MessageHistoryWindowSizeItem | |||
| initialValue={1} | |||
| ></MessageHistoryWindowSizeItem> | |||
| <DynamicCategorize nodeId={node?.id}></DynamicCategorize> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default CategorizeForm; | |||
| @@ -0,0 +1,130 @@ | |||
| import { RAGFlowNodeType } from '@/interfaces/database/flow'; | |||
| import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; | |||
| import { Button, Collapse, Flex, Form, Input, Select } from 'antd'; | |||
| import { PropsWithChildren, useCallback } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query'; | |||
| import styles from './index.less'; | |||
| interface IProps { | |||
| node?: RAGFlowNodeType; | |||
| } | |||
| enum VariableType { | |||
| Reference = 'reference', | |||
| Input = 'input', | |||
| } | |||
| const getVariableName = (type: string) => | |||
| type === VariableType.Reference ? 'component_id' : 'value'; | |||
| const DynamicVariableForm = ({ node }: IProps) => { | |||
| const { t } = useTranslation(); | |||
| const valueOptions = useBuildComponentIdSelectOptions( | |||
| node?.id, | |||
| node?.parentId, | |||
| ); | |||
| const form = Form.useFormInstance(); | |||
| const options = [ | |||
| { value: VariableType.Reference, label: t('flow.reference') }, | |||
| { value: VariableType.Input, label: t('flow.text') }, | |||
| ]; | |||
| const handleTypeChange = useCallback( | |||
| (name: number) => () => { | |||
| setTimeout(() => { | |||
| form.setFieldValue(['query', name, 'component_id'], undefined); | |||
| form.setFieldValue(['query', name, 'value'], undefined); | |||
| }, 0); | |||
| }, | |||
| [form], | |||
| ); | |||
| return ( | |||
| <Form.List name="query"> | |||
| {(fields, { add, remove }) => ( | |||
| <> | |||
| {fields.map(({ key, name, ...restField }) => ( | |||
| <Flex key={key} gap={10} align={'baseline'}> | |||
| <Form.Item | |||
| {...restField} | |||
| name={[name, 'type']} | |||
| className={styles.variableType} | |||
| > | |||
| <Select | |||
| options={options} | |||
| onChange={handleTypeChange(name)} | |||
| ></Select> | |||
| </Form.Item> | |||
| <Form.Item noStyle dependencies={[name, 'type']}> | |||
| {({ getFieldValue }) => { | |||
| const type = getFieldValue(['query', name, 'type']); | |||
| return ( | |||
| <Form.Item | |||
| {...restField} | |||
| name={[name, getVariableName(type)]} | |||
| className={styles.variableValue} | |||
| > | |||
| {type === VariableType.Reference ? ( | |||
| <Select | |||
| placeholder={t('common.pleaseSelect')} | |||
| options={valueOptions} | |||
| ></Select> | |||
| ) : ( | |||
| <Input placeholder={t('common.pleaseInput')} /> | |||
| )} | |||
| </Form.Item> | |||
| ); | |||
| }} | |||
| </Form.Item> | |||
| <MinusCircleOutlined onClick={() => remove(name)} /> | |||
| </Flex> | |||
| ))} | |||
| <Form.Item> | |||
| <Button | |||
| type="dashed" | |||
| onClick={() => add({ type: VariableType.Reference })} | |||
| block | |||
| icon={<PlusOutlined />} | |||
| className={styles.addButton} | |||
| > | |||
| {t('flow.addVariable')} | |||
| </Button> | |||
| </Form.Item> | |||
| </> | |||
| )} | |||
| </Form.List> | |||
| ); | |||
| }; | |||
| export function FormCollapse({ | |||
| children, | |||
| title, | |||
| }: PropsWithChildren<{ title: string }>) { | |||
| return ( | |||
| <Collapse | |||
| className={styles.dynamicInputVariable} | |||
| defaultActiveKey={['1']} | |||
| items={[ | |||
| { | |||
| key: '1', | |||
| label: <span className={styles.title}>{title}</span>, | |||
| children, | |||
| }, | |||
| ]} | |||
| /> | |||
| ); | |||
| } | |||
| const DynamicInputVariable = ({ node }: IProps) => { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <FormCollapse title={t('flow.input')}> | |||
| <DynamicVariableForm node={node}></DynamicVariableForm> | |||
| </FormCollapse> | |||
| ); | |||
| }; | |||
| export default DynamicInputVariable; | |||
| @@ -0,0 +1,22 @@ | |||
| .dynamicInputVariable { | |||
| background-color: #ebe9e950; | |||
| :global(.ant-collapse-content) { | |||
| background-color: #f6f6f657; | |||
| } | |||
| margin-bottom: 20px; | |||
| .title { | |||
| font-weight: 600; | |||
| font-size: 16px; | |||
| } | |||
| .variableType { | |||
| width: 30%; | |||
| } | |||
| .variableValue { | |||
| flex: 1; | |||
| } | |||
| .addButton { | |||
| color: rgb(22, 119, 255); | |||
| font-weight: 600; | |||
| } | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| import { Form } from 'antd'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| const ConcentratorForm = ({ onValuesChange, form }: IOperatorForm) => { | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| labelCol={{ span: 8 }} | |||
| wrapperCol={{ span: 16 }} | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| ></Form> | |||
| ); | |||
| }; | |||
| export default ConcentratorForm; | |||
| @@ -0,0 +1,38 @@ | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Form, Input, Select } from 'antd'; | |||
| import { useMemo } from 'react'; | |||
| import { CrawlerResultOptions } from '../../constant'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| const CrawlerForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| const crawlerResultOptions = useMemo(() => { | |||
| return CrawlerResultOptions.map((x) => ({ | |||
| value: x, | |||
| label: t(`crawlerResultOptions.${x}`), | |||
| })); | |||
| }, [t]); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <Form.Item label={t('proxy')} name={'proxy'}> | |||
| <Input placeholder="like: http://127.0.0.1:8888"></Input> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label={t('extractType')} | |||
| name={'extract_type'} | |||
| initialValue="markdown" | |||
| > | |||
| <Select options={crawlerResultOptions}></Select> | |||
| </Form.Item> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default CrawlerForm; | |||
| @@ -0,0 +1,36 @@ | |||
| import TopNItem from '@/components/top-n-item'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Form, Select } from 'antd'; | |||
| import { DeepLSourceLangOptions, DeepLTargetLangOptions } from '../../constant'; | |||
| import { useBuildSortOptions } from '../../form-hooks'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| const DeepLForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| const options = useBuildSortOptions(); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <TopNItem initialValue={5}></TopNItem> | |||
| <Form.Item label={t('authKey')} name={'auth_key'}> | |||
| <Select options={options}></Select> | |||
| </Form.Item> | |||
| <Form.Item label={t('sourceLang')} name={'source_lang'}> | |||
| <Select options={DeepLSourceLangOptions}></Select> | |||
| </Form.Item> | |||
| <Form.Item label={t('targetLang')} name={'target_lang'}> | |||
| <Select options={DeepLTargetLangOptions}></Select> | |||
| </Form.Item> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default DeepLForm; | |||
| @@ -0,0 +1,38 @@ | |||
| import TopNItem from '@/components/top-n-item'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Form, Select } from 'antd'; | |||
| import { useMemo } from 'react'; | |||
| import { Channel } from '../../constant'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| const DuckDuckGoForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| const options = useMemo(() => { | |||
| return Object.values(Channel).map((x) => ({ value: x, label: t(x) })); | |||
| }, [t]); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <TopNItem initialValue={10}></TopNItem> | |||
| <Form.Item | |||
| label={t('channel')} | |||
| name={'channel'} | |||
| tooltip={t('channelTip')} | |||
| initialValue={'text'} | |||
| > | |||
| <Select options={options}></Select> | |||
| </Form.Item> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default DuckDuckGoForm; | |||
| @@ -0,0 +1,53 @@ | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Form, Input } from 'antd'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| const EmailForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| {/* SMTP服务器配置 */} | |||
| <Form.Item label={t('smtpServer')} name={'smtp_server'}> | |||
| <Input placeholder="smtp.example.com" /> | |||
| </Form.Item> | |||
| <Form.Item label={t('smtpPort')} name={'smtp_port'}> | |||
| <Input type="number" placeholder="587" /> | |||
| </Form.Item> | |||
| <Form.Item label={t('senderEmail')} name={'email'}> | |||
| <Input placeholder="sender@example.com" /> | |||
| </Form.Item> | |||
| <Form.Item label={t('authCode')} name={'password'}> | |||
| <Input.Password placeholder="your_password" /> | |||
| </Form.Item> | |||
| <Form.Item label={t('senderName')} name={'sender_name'}> | |||
| <Input placeholder="Sender Name" /> | |||
| </Form.Item> | |||
| {/* 动态参数说明 */} | |||
| <div style={{ marginBottom: 24 }}> | |||
| <h4>{t('dynamicParameters')}</h4> | |||
| <div>{t('jsonFormatTip')}</div> | |||
| <pre style={{ background: '#f5f5f5', padding: 12, borderRadius: 4 }}> | |||
| {`{ | |||
| "to_email": "recipient@example.com", | |||
| "cc_email": "cc@example.com", | |||
| "subject": "Email Subject", | |||
| "content": "Email Content" | |||
| }`} | |||
| </pre> | |||
| </div> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default EmailForm; | |||
| @@ -0,0 +1,88 @@ | |||
| import LLMSelect from '@/components/llm-select'; | |||
| import TopNItem from '@/components/top-n-item'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { useTestDbConnect } from '@/hooks/flow-hooks'; | |||
| import { Button, Flex, Form, Input, InputNumber, Select } from 'antd'; | |||
| import { useCallback } from 'react'; | |||
| import { ExeSQLOptions } from '../../constant'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| const ExeSQLForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| const { testDbConnect, loading } = useTestDbConnect(); | |||
| const handleTest = useCallback(async () => { | |||
| const ret = await form?.validateFields(); | |||
| testDbConnect(ret); | |||
| }, [form, testDbConnect]); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <Form.Item | |||
| name={'llm_id'} | |||
| label={t('model', { keyPrefix: 'chat' })} | |||
| tooltip={t('modelTip', { keyPrefix: 'chat' })} | |||
| > | |||
| <LLMSelect></LLMSelect> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label={t('dbType')} | |||
| name={'db_type'} | |||
| rules={[{ required: true }]} | |||
| > | |||
| <Select options={ExeSQLOptions}></Select> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label={t('database')} | |||
| name={'database'} | |||
| rules={[{ required: true }]} | |||
| > | |||
| <Input></Input> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label={t('username')} | |||
| name={'username'} | |||
| rules={[{ required: true }]} | |||
| > | |||
| <Input></Input> | |||
| </Form.Item> | |||
| <Form.Item label={t('host')} name={'host'} rules={[{ required: true }]}> | |||
| <Input></Input> | |||
| </Form.Item> | |||
| <Form.Item label={t('port')} name={'port'} rules={[{ required: true }]}> | |||
| <InputNumber></InputNumber> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label={t('password')} | |||
| name={'password'} | |||
| rules={[{ required: true }]} | |||
| > | |||
| <Input.Password></Input.Password> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label={t('loop')} | |||
| name={'loop'} | |||
| tooltip={t('loopTip')} | |||
| rules={[{ required: true }]} | |||
| > | |||
| <InputNumber></InputNumber> | |||
| </Form.Item> | |||
| <TopNItem initialValue={30} max={1000}></TopNItem> | |||
| <Flex justify={'end'}> | |||
| <Button type={'primary'} loading={loading} onClick={handleTest}> | |||
| Test | |||
| </Button> | |||
| </Flex> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default ExeSQLForm; | |||
| @@ -0,0 +1,101 @@ | |||
| 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; | |||
| @@ -0,0 +1,70 @@ | |||
| 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, | |||
| }; | |||
| }; | |||
| @@ -0,0 +1,21 @@ | |||
| .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; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,57 @@ | |||
| import LLMSelect from '@/components/llm-select'; | |||
| import MessageHistoryWindowSizeItem 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'; | |||
| const GenerateForm = ({ onValuesChange, form }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| 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' })} | |||
| > | |||
| <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> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default GenerateForm; | |||
| @@ -0,0 +1,21 @@ | |||
| import TopNItem from '@/components/top-n-item'; | |||
| import { Form } from 'antd'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| 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> | |||
| ); | |||
| }; | |||
| export default GithubForm; | |||
| @@ -0,0 +1,34 @@ | |||
| import TopNItem from '@/components/top-n-item'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Form, Input, Select } from 'antd'; | |||
| import { GoogleCountryOptions, GoogleLanguageOptions } from '../../constant'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| const GoogleForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <TopNItem initialValue={10}></TopNItem> | |||
| <Form.Item label={t('apiKey')} name={'api_key'}> | |||
| <Input></Input> | |||
| </Form.Item> | |||
| <Form.Item label={t('country')} name={'country'}> | |||
| <Select options={GoogleCountryOptions}></Select> | |||
| </Form.Item> | |||
| <Form.Item label={t('language')} name={'language'}> | |||
| <Select options={GoogleLanguageOptions}></Select> | |||
| </Form.Item> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default GoogleForm; | |||
| @@ -0,0 +1,75 @@ | |||
| import TopNItem from '@/components/top-n-item'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { DatePicker, DatePickerProps, Form, Select, Switch } from 'antd'; | |||
| import dayjs from 'dayjs'; | |||
| import { useCallback, useMemo } from 'react'; | |||
| import { useBuildSortOptions } from '../../form-hooks'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| const YearPicker = ({ | |||
| onChange, | |||
| value, | |||
| }: { | |||
| onChange?: (val: number | undefined) => void; | |||
| value?: number | undefined; | |||
| }) => { | |||
| const handleChange: DatePickerProps['onChange'] = useCallback( | |||
| (val: any) => { | |||
| const nextVal = val?.format('YYYY'); | |||
| onChange?.(nextVal ? Number(nextVal) : undefined); | |||
| }, | |||
| [onChange], | |||
| ); | |||
| // The year needs to be converted into a number and saved to the backend | |||
| const nextValue = useMemo(() => { | |||
| if (value) { | |||
| return dayjs(value.toString()); | |||
| } | |||
| return undefined; | |||
| }, [value]); | |||
| return <DatePicker picker="year" onChange={handleChange} value={nextValue} />; | |||
| }; | |||
| const GoogleScholarForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| const options = useBuildSortOptions(); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <TopNItem initialValue={5}></TopNItem> | |||
| <Form.Item | |||
| label={t('sortBy')} | |||
| name={'sort_by'} | |||
| initialValue={'relevance'} | |||
| > | |||
| <Select options={options}></Select> | |||
| </Form.Item> | |||
| <Form.Item label={t('yearLow')} name={'year_low'}> | |||
| <YearPicker /> | |||
| </Form.Item> | |||
| <Form.Item label={t('yearHigh')} name={'year_high'}> | |||
| <YearPicker /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label={t('patents')} | |||
| name={'patents'} | |||
| valuePropName="checked" | |||
| initialValue={true} | |||
| > | |||
| <Switch></Switch> | |||
| </Form.Item> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default GoogleScholarForm; | |||
| @@ -0,0 +1,130 @@ | |||
| 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 { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query'; | |||
| import { IInvokeVariable, RAGFlowNodeType } from '../../interface'; | |||
| import { useHandleOperateParameters } from './hooks'; | |||
| 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 = useBuildComponentIdSelectOptions(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; | |||
| @@ -0,0 +1,97 @@ | |||
| import get from 'lodash/get'; | |||
| import { | |||
| ChangeEventHandler, | |||
| MouseEventHandler, | |||
| useCallback, | |||
| useMemo, | |||
| } from 'react'; | |||
| import { v4 as uuid } from 'uuid'; | |||
| import { IGenerateParameter, IInvokeVariable } 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.variables', []) as IGenerateParameter[], | |||
| [node], | |||
| ); | |||
| const changeValue = useCallback( | |||
| (row: IInvokeVariable, field: string, value: string) => { | |||
| const newData = [...dataSource]; | |||
| const index = newData.findIndex((item) => row.id === item.id); | |||
| const item = newData[index]; | |||
| newData.splice(index, 1, { | |||
| ...item, | |||
| [field]: value, | |||
| }); | |||
| updateNodeForm(nodeId, { variables: newData }); | |||
| }, | |||
| [dataSource, nodeId, updateNodeForm], | |||
| ); | |||
| const handleComponentIdChange = useCallback( | |||
| (row: IInvokeVariable) => (value: string) => { | |||
| changeValue(row, 'component_id', value); | |||
| }, | |||
| [changeValue], | |||
| ); | |||
| const handleValueChange = useCallback( | |||
| (row: IInvokeVariable): ChangeEventHandler<HTMLInputElement> => | |||
| (e) => { | |||
| changeValue(row, 'value', e.target.value); | |||
| }, | |||
| [changeValue], | |||
| ); | |||
| const handleRemove = useCallback( | |||
| (id?: string) => () => { | |||
| const newData = dataSource.filter((item) => item.id !== id); | |||
| updateNodeForm(nodeId, { variables: newData }); | |||
| }, | |||
| [updateNodeForm, nodeId, dataSource], | |||
| ); | |||
| const handleAdd: MouseEventHandler = useCallback( | |||
| (e) => { | |||
| e.preventDefault(); | |||
| e.stopPropagation(); | |||
| updateNodeForm(nodeId, { | |||
| variables: [ | |||
| ...dataSource, | |||
| { | |||
| id: uuid(), | |||
| key: '', | |||
| component_id: undefined, | |||
| value: '', | |||
| }, | |||
| ], | |||
| }); | |||
| }, | |||
| [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, { variables: newData }); | |||
| }; | |||
| return { | |||
| handleAdd, | |||
| handleRemove, | |||
| handleComponentIdChange, | |||
| handleValueChange, | |||
| handleSave, | |||
| dataSource, | |||
| }; | |||
| }; | |||
| @@ -0,0 +1,44 @@ | |||
| .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; | |||
| } | |||
| } | |||
| } | |||
| .dynamicParameterVariable { | |||
| background-color: #ebe9e950; | |||
| :global(.ant-collapse-content) { | |||
| background-color: #f6f6f634; | |||
| } | |||
| :global(.ant-collapse-content-box) { | |||
| padding: 0 !important; | |||
| } | |||
| margin-bottom: 20px; | |||
| .title { | |||
| font-weight: 600; | |||
| font-size: 16px; | |||
| } | |||
| .variableType { | |||
| width: 30%; | |||
| } | |||
| .variableValue { | |||
| flex: 1; | |||
| } | |||
| .addButton { | |||
| color: rgb(22, 119, 255); | |||
| font-weight: 600; | |||
| } | |||
| } | |||
| @@ -0,0 +1,78 @@ | |||
| import Editor, { loader } from '@monaco-editor/react'; | |||
| import { Form, Input, InputNumber, Select, Space, Switch } from 'antd'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicVariablesForm from './dynamic-variables'; | |||
| loader.config({ paths: { vs: '/vs' } }); | |||
| enum Method { | |||
| GET = 'GET', | |||
| POST = 'POST', | |||
| PUT = 'PUT', | |||
| } | |||
| const MethodOptions = [Method.GET, Method.POST, Method.PUT].map((x) => ({ | |||
| label: x, | |||
| value: x, | |||
| })); | |||
| interface TimeoutInputProps { | |||
| value?: number; | |||
| onChange?: (value: number | null) => void; | |||
| } | |||
| const TimeoutInput = ({ value, onChange }: TimeoutInputProps) => { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <Space> | |||
| <InputNumber value={value} onChange={onChange} /> {t('common.s')} | |||
| </Space> | |||
| ); | |||
| }; | |||
| const InvokeForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslation(); | |||
| 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} | |||
| > | |||
| <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> | |||
| </> | |||
| ); | |||
| }; | |||
| export default InvokeForm; | |||
| @@ -0,0 +1,94 @@ | |||
| import { CommaIcon, SemicolonIcon } from '@/assets/icon/Icon'; | |||
| import { Form, Select } from 'antd'; | |||
| import { | |||
| CornerDownLeft, | |||
| IndentIncrease, | |||
| Minus, | |||
| Slash, | |||
| Underline, | |||
| } from 'lucide-react'; | |||
| import { useMemo } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| const optionList = [ | |||
| { | |||
| value: ',', | |||
| icon: CommaIcon, | |||
| text: 'comma', | |||
| }, | |||
| { | |||
| value: '\n', | |||
| icon: CornerDownLeft, | |||
| text: 'lineBreak', | |||
| }, | |||
| { | |||
| value: 'tab', | |||
| icon: IndentIncrease, | |||
| text: 'tab', | |||
| }, | |||
| { | |||
| value: '_', | |||
| icon: Underline, | |||
| text: 'underline', | |||
| }, | |||
| { | |||
| value: '/', | |||
| icon: Slash, | |||
| text: 'diagonal', | |||
| }, | |||
| { | |||
| value: '-', | |||
| icon: Minus, | |||
| text: 'minus', | |||
| }, | |||
| { | |||
| value: ';', | |||
| icon: SemicolonIcon, | |||
| text: 'semicolon', | |||
| }, | |||
| ]; | |||
| const IterationForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslation(); | |||
| const options = useMemo(() => { | |||
| return optionList.map((x) => { | |||
| let Icon = x.icon; | |||
| return { | |||
| value: x.value, | |||
| label: ( | |||
| <div className="flex items-center gap-2"> | |||
| <Icon className={'size-4'}></Icon> | |||
| {t(`flow.delimiterOptions.${x.text}`)} | |||
| </div> | |||
| ), | |||
| }; | |||
| }); | |||
| }, [t]); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <Form.Item | |||
| name={['delimiter']} | |||
| label={t('knowledgeDetails.delimiter')} | |||
| initialValue={`\\n!?;。;!?`} | |||
| rules={[{ required: true }]} | |||
| tooltip={t('flow.delimiterTip')} | |||
| > | |||
| <Select options={options}></Select> | |||
| </Form.Item> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default IterationForm; | |||
| @@ -0,0 +1,145 @@ | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Form, Input, Select } from 'antd'; | |||
| import { useMemo } from 'react'; | |||
| import { | |||
| Jin10CalendarDatashapeOptions, | |||
| Jin10CalendarTypeOptions, | |||
| Jin10FlashTypeOptions, | |||
| Jin10SymbolsDatatypeOptions, | |||
| Jin10SymbolsTypeOptions, | |||
| Jin10TypeOptions, | |||
| } from '../../constant'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| const Jin10Form = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| const jin10TypeOptions = useMemo(() => { | |||
| return Jin10TypeOptions.map((x) => ({ | |||
| value: x, | |||
| label: t(`jin10TypeOptions.${x}`), | |||
| })); | |||
| }, [t]); | |||
| const jin10FlashTypeOptions = useMemo(() => { | |||
| return Jin10FlashTypeOptions.map((x) => ({ | |||
| value: x, | |||
| label: t(`jin10FlashTypeOptions.${x}`), | |||
| })); | |||
| }, [t]); | |||
| const jin10CalendarTypeOptions = useMemo(() => { | |||
| return Jin10CalendarTypeOptions.map((x) => ({ | |||
| value: x, | |||
| label: t(`jin10CalendarTypeOptions.${x}`), | |||
| })); | |||
| }, [t]); | |||
| const jin10CalendarDatashapeOptions = useMemo(() => { | |||
| return Jin10CalendarDatashapeOptions.map((x) => ({ | |||
| value: x, | |||
| label: t(`jin10CalendarDatashapeOptions.${x}`), | |||
| })); | |||
| }, [t]); | |||
| const jin10SymbolsTypeOptions = useMemo(() => { | |||
| return Jin10SymbolsTypeOptions.map((x) => ({ | |||
| value: x, | |||
| label: t(`jin10SymbolsTypeOptions.${x}`), | |||
| })); | |||
| }, [t]); | |||
| const jin10SymbolsDatatypeOptions = useMemo(() => { | |||
| return Jin10SymbolsDatatypeOptions.map((x) => ({ | |||
| value: x, | |||
| label: t(`jin10SymbolsDatatypeOptions.${x}`), | |||
| })); | |||
| }, [t]); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <Form.Item label={t('type')} name={'type'} initialValue={'flash'}> | |||
| <Select options={jin10TypeOptions}></Select> | |||
| </Form.Item> | |||
| <Form.Item label={t('secretKey')} name={'secret_key'}> | |||
| <Input></Input> | |||
| </Form.Item> | |||
| <Form.Item noStyle dependencies={['type']}> | |||
| {({ getFieldValue }) => { | |||
| const type = getFieldValue('type'); | |||
| switch (type) { | |||
| case 'flash': | |||
| return ( | |||
| <> | |||
| <Form.Item label={t('flashType')} name={'flash_type'}> | |||
| <Select options={jin10FlashTypeOptions}></Select> | |||
| </Form.Item> | |||
| <Form.Item label={t('contain')} name={'contain'}> | |||
| <Input></Input> | |||
| </Form.Item> | |||
| <Form.Item label={t('filter')} name={'filter'}> | |||
| <Input></Input> | |||
| </Form.Item> | |||
| </> | |||
| ); | |||
| case 'calendar': | |||
| return ( | |||
| <> | |||
| <Form.Item label={t('calendarType')} name={'calendar_type'}> | |||
| <Select options={jin10CalendarTypeOptions}></Select> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label={t('calendarDatashape')} | |||
| name={'calendar_datashape'} | |||
| > | |||
| <Select options={jin10CalendarDatashapeOptions}></Select> | |||
| </Form.Item> | |||
| </> | |||
| ); | |||
| case 'symbols': | |||
| return ( | |||
| <> | |||
| <Form.Item label={t('symbolsType')} name={'symbols_type'}> | |||
| <Select options={jin10SymbolsTypeOptions}></Select> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label={t('symbolsDatatype')} | |||
| name={'symbols_datatype'} | |||
| > | |||
| <Select options={jin10SymbolsDatatypeOptions}></Select> | |||
| </Form.Item> | |||
| </> | |||
| ); | |||
| case 'news': | |||
| return ( | |||
| <> | |||
| <Form.Item label={t('contain')} name={'contain'}> | |||
| <Input></Input> | |||
| </Form.Item> | |||
| <Form.Item label={t('filter')} name={'filter'}> | |||
| <Input></Input> | |||
| </Form.Item> | |||
| </> | |||
| ); | |||
| default: | |||
| return <></>; | |||
| } | |||
| }} | |||
| </Form.Item> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default Jin10Form; | |||
| @@ -0,0 +1,32 @@ | |||
| import LLMSelect from '@/components/llm-select'; | |||
| import TopNItem from '@/components/top-n-item'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Form } from 'antd'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| const KeywordExtractForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <Form.Item | |||
| name={'llm_id'} | |||
| label={t('model', { keyPrefix: 'chat' })} | |||
| tooltip={t('modelTip', { keyPrefix: 'chat' })} | |||
| > | |||
| <LLMSelect></LLMSelect> | |||
| </Form.Item> | |||
| <TopNItem initialValue={3}></TopNItem> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default KeywordExtractForm; | |||
| @@ -0,0 +1,16 @@ | |||
| .dynamicDeleteButton { | |||
| position: relative; | |||
| top: 4px; | |||
| margin: 0 8px; | |||
| color: #999; | |||
| font-size: 24px; | |||
| cursor: pointer; | |||
| transition: all 0.3s; | |||
| &:hover { | |||
| color: #777; | |||
| } | |||
| &[disabled] { | |||
| cursor: not-allowed; | |||
| opacity: 0.5; | |||
| } | |||
| } | |||
| @@ -0,0 +1,87 @@ | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; | |||
| import { Button, Form, Input } from 'antd'; | |||
| import { IOperatorForm } 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'); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| {...formItemLayoutWithOutLabel} | |||
| onValuesChange={onValuesChange} | |||
| autoComplete="off" | |||
| form={form} | |||
| > | |||
| <Form.List name="messages"> | |||
| {(fields, { add, remove }, {}) => ( | |||
| <> | |||
| {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> | |||
| ))} | |||
| <Form.Item> | |||
| <Button | |||
| type="dashed" | |||
| onClick={() => add()} | |||
| style={{ width: '80%' }} | |||
| icon={<PlusOutlined />} | |||
| > | |||
| {t('addMessage')} | |||
| </Button> | |||
| </Form.Item> | |||
| </> | |||
| )} | |||
| </Form.List> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default MessageForm; | |||
| @@ -0,0 +1,32 @@ | |||
| import TopNItem from '@/components/top-n-item'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Form, Input } from 'antd'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| const PubMedForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| 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' }]} | |||
| > | |||
| <Input></Input> | |||
| </Form.Item> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default PubMedForm; | |||
| @@ -0,0 +1,88 @@ | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Form, Input, Select } from 'antd'; | |||
| import { useCallback, useMemo } from 'react'; | |||
| import { | |||
| QWeatherLangOptions, | |||
| QWeatherTimePeriodOptions, | |||
| QWeatherTypeOptions, | |||
| QWeatherUserTypeOptions, | |||
| } from '../../constant'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| const QWeatherForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| const qWeatherLangOptions = useMemo(() => { | |||
| return QWeatherLangOptions.map((x) => ({ | |||
| value: x, | |||
| label: t(`qWeatherLangOptions.${x}`), | |||
| })); | |||
| }, [t]); | |||
| const qWeatherTypeOptions = useMemo(() => { | |||
| return QWeatherTypeOptions.map((x) => ({ | |||
| value: x, | |||
| label: t(`qWeatherTypeOptions.${x}`), | |||
| })); | |||
| }, [t]); | |||
| const qWeatherUserTypeOptions = useMemo(() => { | |||
| return QWeatherUserTypeOptions.map((x) => ({ | |||
| value: x, | |||
| label: t(`qWeatherUserTypeOptions.${x}`), | |||
| })); | |||
| }, [t]); | |||
| const getQWeatherTimePeriodOptions = useCallback( | |||
| (userType: string) => { | |||
| let options = QWeatherTimePeriodOptions; | |||
| if (userType === 'free') { | |||
| options = options.slice(0, 3); | |||
| } | |||
| return options.map((x) => ({ | |||
| value: x, | |||
| label: t(`qWeatherTimePeriodOptions.${x}`), | |||
| })); | |||
| }, | |||
| [t], | |||
| ); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <Form.Item label={t('webApiKey')} name={'web_apikey'}> | |||
| <Input></Input> | |||
| </Form.Item> | |||
| <Form.Item label={t('lang')} name={'lang'}> | |||
| <Select options={qWeatherLangOptions}></Select> | |||
| </Form.Item> | |||
| <Form.Item label={t('type')} name={'type'}> | |||
| <Select options={qWeatherTypeOptions}></Select> | |||
| </Form.Item> | |||
| <Form.Item label={t('userType')} name={'user_type'}> | |||
| <Select options={qWeatherUserTypeOptions}></Select> | |||
| </Form.Item> | |||
| <Form.Item noStyle dependencies={['type', 'user_type']}> | |||
| {({ getFieldValue }) => | |||
| getFieldValue('type') === 'weather' && ( | |||
| <Form.Item label={t('timePeriod')} name={'time_period'}> | |||
| <Select | |||
| options={getQWeatherTimePeriodOptions( | |||
| getFieldValue('user_type'), | |||
| )} | |||
| ></Select> | |||
| </Form.Item> | |||
| ) | |||
| } | |||
| </Form.Item> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default QWeatherForm; | |||
| @@ -0,0 +1,53 @@ | |||
| import { Edge } from '@xyflow/react'; | |||
| import pick from 'lodash/pick'; | |||
| import { useCallback, useEffect } from 'react'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import useGraphStore from '../../store'; | |||
| export const useBuildRelevantOptions = () => { | |||
| const nodes = useGraphStore((state) => state.nodes); | |||
| const buildRelevantOptions = useCallback( | |||
| (toList: string[]) => { | |||
| return nodes | |||
| .filter( | |||
| (x) => !toList.some((y) => y === x.id), // filter out selected values in other to fields from the current drop-down box options | |||
| ) | |||
| .map((x) => ({ label: x.data.name, value: x.id })); | |||
| }, | |||
| [nodes], | |||
| ); | |||
| return buildRelevantOptions; | |||
| }; | |||
| const getTargetOfEdge = (edges: Edge[], sourceHandle: string) => | |||
| edges.find((x) => x.sourceHandle === sourceHandle)?.target; | |||
| /** | |||
| * monitor changes in the connection and synchronize the target to the yes and no fields of the form | |||
| * similar to the categorize-form's useHandleFormValuesChange method | |||
| * @param param0 | |||
| */ | |||
| export const useWatchConnectionChanges = ({ nodeId, form }: IOperatorForm) => { | |||
| const edges = useGraphStore((state) => state.edges); | |||
| const getNode = useGraphStore((state) => state.getNode); | |||
| const node = getNode(nodeId); | |||
| const watchFormChanges = useCallback(() => { | |||
| if (node) { | |||
| form?.setFieldsValue(pick(node, ['yes', 'no'])); | |||
| } | |||
| }, [node, form]); | |||
| const watchConnectionChanges = useCallback(() => { | |||
| const edgeList = edges.filter((x) => x.source === nodeId); | |||
| const yes = getTargetOfEdge(edgeList, 'yes'); | |||
| const no = getTargetOfEdge(edgeList, 'no'); | |||
| form?.setFieldsValue({ yes, no }); | |||
| }, [edges, nodeId, form]); | |||
| useEffect(() => { | |||
| watchFormChanges(); | |||
| }, [watchFormChanges]); | |||
| }; | |||
| @@ -0,0 +1,49 @@ | |||
| import LLMSelect from '@/components/llm-select'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Form, Select } from 'antd'; | |||
| import { Operator } from '../../constant'; | |||
| import { useBuildFormSelectOptions } from '../../form-hooks'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import { useWatchConnectionChanges } from './hooks'; | |||
| const RelevantForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| const buildRelevantOptions = useBuildFormSelectOptions( | |||
| Operator.Relevant, | |||
| node?.id, | |||
| ); | |||
| useWatchConnectionChanges({ nodeId: node?.id, form }); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| labelCol={{ span: 4 }} | |||
| wrapperCol={{ span: 20 }} | |||
| onValuesChange={onValuesChange} | |||
| autoComplete="off" | |||
| form={form} | |||
| > | |||
| <Form.Item | |||
| name={'llm_id'} | |||
| label={t('model', { keyPrefix: 'chat' })} | |||
| tooltip={t('modelTip', { keyPrefix: 'chat' })} | |||
| > | |||
| <LLMSelect></LLMSelect> | |||
| </Form.Item> | |||
| <Form.Item label={t('yes')} name={'yes'}> | |||
| <Select | |||
| allowClear | |||
| options={buildRelevantOptions([form?.getFieldValue('no')])} | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item label={t('no')} name={'no'}> | |||
| <Select | |||
| allowClear | |||
| options={buildRelevantOptions([form?.getFieldValue('yes')])} | |||
| /> | |||
| </Form.Item> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default RelevantForm; | |||
| @@ -0,0 +1,54 @@ | |||
| import KnowledgeBaseItem from '@/components/knowledge-base-item'; | |||
| import Rerank from '@/components/rerank'; | |||
| import SimilaritySlider from '@/components/similarity-slider'; | |||
| import TopNItem from '@/components/top-n-item'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import type { FormProps } from 'antd'; | |||
| import { Form, Input } from 'antd'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| type FieldType = { | |||
| top_n?: number; | |||
| }; | |||
| const onFinish: FormProps<FieldType>['onFinish'] = (values) => { | |||
| console.log('Success:', values); | |||
| }; | |||
| const onFinishFailed: FormProps<FieldType>['onFinishFailed'] = (errorInfo) => { | |||
| console.log('Failed:', errorInfo); | |||
| }; | |||
| const RetrievalForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| onFinish={onFinish} | |||
| onFinishFailed={onFinishFailed} | |||
| autoComplete="off" | |||
| onValuesChange={onValuesChange} | |||
| form={form} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <SimilaritySlider | |||
| isTooltipShown | |||
| vectorSimilarityWeightName="keywords_similarity_weight" | |||
| ></SimilaritySlider> | |||
| <TopNItem></TopNItem> | |||
| <Rerank></Rerank> | |||
| <KnowledgeBaseItem></KnowledgeBaseItem> | |||
| <Form.Item | |||
| name={'empty_response'} | |||
| label={t('emptyResponse', { keyPrefix: 'chat' })} | |||
| tooltip={t('emptyResponseTip', { keyPrefix: 'chat' })} | |||
| > | |||
| <Input.TextArea placeholder="" rows={4} /> | |||
| </Form.Item> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default RetrievalForm; | |||
| @@ -0,0 +1,41 @@ | |||
| import LLMSelect from '@/components/llm-select'; | |||
| import MessageHistoryWindowSizeItem from '@/components/message-history-window-size-item'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Form, Select } from 'antd'; | |||
| import { GoogleLanguageOptions } from '../../constant'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| const RewriteQuestionForm = ({ onValuesChange, form }: IOperatorForm) => { | |||
| const { t } = useTranslate('chat'); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| labelCol={{ span: 8 }} | |||
| wrapperCol={{ span: 16 }} | |||
| onValuesChange={onValuesChange} | |||
| autoComplete="off" | |||
| form={form} | |||
| > | |||
| <Form.Item | |||
| name={'llm_id'} | |||
| label={t('model', { keyPrefix: 'chat' })} | |||
| tooltip={t('modelTip', { keyPrefix: 'chat' })} | |||
| > | |||
| <LLMSelect></LLMSelect> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label={t('language')} | |||
| name={'language'} | |||
| tooltip={t('languageTip')} | |||
| > | |||
| <Select options={GoogleLanguageOptions} allowClear={true}></Select> | |||
| </Form.Item> | |||
| <MessageHistoryWindowSizeItem | |||
| initialValue={6} | |||
| ></MessageHistoryWindowSizeItem> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default RewriteQuestionForm; | |||
| @@ -0,0 +1,21 @@ | |||
| @lightBackgroundColor: rgba(150, 150, 150, 0.07); | |||
| @darkBackgroundColor: rgba(150, 150, 150, 0.12); | |||
| .caseCard { | |||
| background-color: @lightBackgroundColor; | |||
| } | |||
| .conditionCard { | |||
| background-color: @darkBackgroundColor; | |||
| } | |||
| .elseCase { | |||
| background-color: @lightBackgroundColor; | |||
| padding: 12px; | |||
| border-radius: 8px; | |||
| } | |||
| .addButton { | |||
| color: rgb(22, 119, 255); | |||
| font-weight: 600; | |||
| } | |||
| @@ -0,0 +1,204 @@ | |||
| import { CloseOutlined } from '@ant-design/icons'; | |||
| import { Button, Card, Divider, Form, Input, Select } from 'antd'; | |||
| import { useMemo } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { | |||
| Operator, | |||
| SwitchElseTo, | |||
| SwitchLogicOperatorOptions, | |||
| SwitchOperatorOptions, | |||
| } from '../../constant'; | |||
| import { useBuildFormSelectOptions } from '../../form-hooks'; | |||
| import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import { getOtherFieldValues } from '../../utils'; | |||
| import { ISwitchForm } from '@/interfaces/database/flow'; | |||
| import styles from './index.less'; | |||
| const SwitchForm = ({ onValuesChange, node, form }: IOperatorForm) => { | |||
| const { t } = useTranslation(); | |||
| const buildCategorizeToOptions = useBuildFormSelectOptions( | |||
| Operator.Switch, | |||
| node?.id, | |||
| ); | |||
| const getSelectedConditionTos = () => { | |||
| const conditions: ISwitchForm['conditions'] = | |||
| form?.getFieldValue('conditions'); | |||
| return conditions?.filter((x) => !!x).map((x) => x?.to) ?? []; | |||
| }; | |||
| const switchOperatorOptions = useMemo(() => { | |||
| return SwitchOperatorOptions.map((x) => ({ | |||
| value: x.value, | |||
| label: t(`flow.switchOperatorOptions.${x.label}`), | |||
| })); | |||
| }, [t]); | |||
| const switchLogicOperatorOptions = useMemo(() => { | |||
| return SwitchLogicOperatorOptions.map((x) => ({ | |||
| value: x, | |||
| label: t(`flow.switchLogicOperatorOptions.${x}`), | |||
| })); | |||
| }, [t]); | |||
| const componentIdOptions = useBuildComponentIdSelectOptions( | |||
| node?.id, | |||
| node?.parentId, | |||
| ); | |||
| return ( | |||
| <Form | |||
| form={form} | |||
| name="dynamic_form_complex" | |||
| autoComplete="off" | |||
| initialValues={{ conditions: [{}] }} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <Form.List name="conditions"> | |||
| {(fields, { add, remove }) => ( | |||
| <div style={{ display: 'flex', rowGap: 16, flexDirection: 'column' }}> | |||
| {fields.map((field) => { | |||
| return ( | |||
| <Card | |||
| size="small" | |||
| title={`Case ${field.name + 1}`} | |||
| key={field.key} | |||
| className={styles.caseCard} | |||
| extra={ | |||
| <CloseOutlined | |||
| onClick={() => { | |||
| remove(field.name); | |||
| }} | |||
| /> | |||
| } | |||
| > | |||
| <Form.Item noStyle dependencies={[field.name, 'items']}> | |||
| {({ getFieldValue }) => | |||
| getFieldValue(['conditions', field.name, 'items']) | |||
| ?.length > 1 && ( | |||
| <Form.Item | |||
| label={t('flow.logicalOperator')} | |||
| name={[field.name, 'logical_operator']} | |||
| > | |||
| <Select options={switchLogicOperatorOptions} /> | |||
| </Form.Item> | |||
| ) | |||
| } | |||
| </Form.Item> | |||
| <Form.Item | |||
| label={t('flow.nextStep')} | |||
| name={[field.name, 'to']} | |||
| > | |||
| <Select | |||
| allowClear | |||
| options={buildCategorizeToOptions([ | |||
| form?.getFieldValue(SwitchElseTo), | |||
| ...getOtherFieldValues( | |||
| form!, | |||
| 'conditions', | |||
| field, | |||
| 'to', | |||
| ), | |||
| ])} | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item label="Condition"> | |||
| <Form.List name={[field.name, 'items']}> | |||
| {(subFields, subOpt) => ( | |||
| <div | |||
| style={{ | |||
| display: 'flex', | |||
| flexDirection: 'column', | |||
| rowGap: 16, | |||
| }} | |||
| > | |||
| {subFields.map((subField) => ( | |||
| <Card | |||
| key={subField.key} | |||
| title={null} | |||
| size="small" | |||
| className={styles.conditionCard} | |||
| bordered | |||
| extra={ | |||
| <CloseOutlined | |||
| onClick={() => { | |||
| subOpt.remove(subField.name); | |||
| }} | |||
| /> | |||
| } | |||
| > | |||
| <Form.Item | |||
| label={t('flow.componentId')} | |||
| name={[subField.name, 'cpn_id']} | |||
| > | |||
| <Select | |||
| placeholder={t('flow.componentId')} | |||
| options={componentIdOptions} | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label={t('flow.operator')} | |||
| name={[subField.name, 'operator']} | |||
| > | |||
| <Select | |||
| placeholder={t('flow.operator')} | |||
| options={switchOperatorOptions} | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label={t('flow.value')} | |||
| name={[subField.name, 'value']} | |||
| > | |||
| <Input placeholder={t('flow.value')} /> | |||
| </Form.Item> | |||
| </Card> | |||
| ))} | |||
| <Button | |||
| onClick={() => { | |||
| form?.setFieldValue( | |||
| ['conditions', field.name, 'logical_operator'], | |||
| SwitchLogicOperatorOptions[0], | |||
| ); | |||
| subOpt.add({ | |||
| operator: SwitchOperatorOptions[0].value, | |||
| }); | |||
| }} | |||
| block | |||
| className={styles.addButton} | |||
| > | |||
| + Add Condition | |||
| </Button> | |||
| </div> | |||
| )} | |||
| </Form.List> | |||
| </Form.Item> | |||
| </Card> | |||
| ); | |||
| })} | |||
| <Button onClick={() => add()} block className={styles.addButton}> | |||
| + Add Case | |||
| </Button> | |||
| </div> | |||
| )} | |||
| </Form.List> | |||
| <Divider /> | |||
| <Form.Item | |||
| label={'ELSE'} | |||
| name={[SwitchElseTo]} | |||
| className={styles.elseCase} | |||
| > | |||
| <Select | |||
| allowClear | |||
| options={buildCategorizeToOptions(getSelectedConditionTos())} | |||
| /> | |||
| </Form.Item> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default SwitchForm; | |||
| @@ -0,0 +1,24 @@ | |||
| import { PromptEditor } from '@/components/prompt-editor'; | |||
| import { Form } from 'antd'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| const TemplateForm = ({ onValuesChange, form }: IOperatorForm) => { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <Form.Item name={['content']} label={t('flow.content')}> | |||
| <PromptEditor></PromptEditor> | |||
| </Form.Item> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default TemplateForm; | |||
| @@ -0,0 +1,83 @@ | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { DatePicker, DatePickerProps, Form, Input, Select } from 'antd'; | |||
| import dayjs from 'dayjs'; | |||
| import { useCallback, useMemo } from 'react'; | |||
| import { TuShareSrcOptions } from '../../constant'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| const DateTimePicker = ({ | |||
| onChange, | |||
| value, | |||
| }: { | |||
| onChange?: (val: number | undefined) => void; | |||
| value?: number | undefined; | |||
| }) => { | |||
| const handleChange: DatePickerProps['onChange'] = useCallback( | |||
| (val: any) => { | |||
| const nextVal = val?.format('YYYY-MM-DD HH:mm:ss'); | |||
| onChange?.(nextVal ? nextVal : undefined); | |||
| }, | |||
| [onChange], | |||
| ); | |||
| // The value needs to be converted into a string and saved to the backend | |||
| const nextValue = useMemo(() => { | |||
| if (value) { | |||
| return dayjs(value); | |||
| } | |||
| return undefined; | |||
| }, [value]); | |||
| return ( | |||
| <DatePicker | |||
| showTime | |||
| format="YYYY-MM-DD HH:mm:ss" | |||
| onChange={handleChange} | |||
| value={nextValue} | |||
| /> | |||
| ); | |||
| }; | |||
| const TuShareForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| const tuShareSrcOptions = useMemo(() => { | |||
| return TuShareSrcOptions.map((x) => ({ | |||
| value: x, | |||
| label: t(`tuShareSrcOptions.${x}`), | |||
| })); | |||
| }, [t]); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <Form.Item | |||
| label={t('token')} | |||
| name={'token'} | |||
| tooltip={'Get from https://tushare.pro/'} | |||
| > | |||
| <Input></Input> | |||
| </Form.Item> | |||
| <Form.Item label={t('src')} name={'src'}> | |||
| <Select options={tuShareSrcOptions}></Select> | |||
| </Form.Item> | |||
| <Form.Item label={t('startDate')} name={'start_date'}> | |||
| <DateTimePicker /> | |||
| </Form.Item> | |||
| <Form.Item label={t('endDate')} name={'end_date'}> | |||
| <DateTimePicker /> | |||
| </Form.Item> | |||
| <Form.Item label={t('keyword')} name={'keyword'}> | |||
| <Input></Input> | |||
| </Form.Item> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default TuShareForm; | |||
| @@ -0,0 +1,36 @@ | |||
| import TopNItem from '@/components/top-n-item'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Form, Select } from 'antd'; | |||
| import { useMemo } from 'react'; | |||
| import { WenCaiQueryTypeOptions } from '../../constant'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| const WenCaiForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| const wenCaiQueryTypeOptions = useMemo(() => { | |||
| return WenCaiQueryTypeOptions.map((x) => ({ | |||
| value: x, | |||
| label: t(`wenCaiQueryTypeOptions.${x}`), | |||
| })); | |||
| }, [t]); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <TopNItem initialValue={20} max={99}></TopNItem> | |||
| <Form.Item label={t('queryType')} name={'query_type'}> | |||
| <Select options={wenCaiQueryTypeOptions}></Select> | |||
| </Form.Item> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default WenCaiForm; | |||
| @@ -0,0 +1,28 @@ | |||
| import TopNItem from '@/components/top-n-item'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Form, Select } from 'antd'; | |||
| import { LanguageOptions } from '../../constant'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| const WikipediaForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslate('common'); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <TopNItem initialValue={10}></TopNItem> | |||
| <Form.Item label={t('language')} name={'language'}> | |||
| <Select options={LanguageOptions}></Select> | |||
| </Form.Item> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default WikipediaForm; | |||
| @@ -0,0 +1,40 @@ | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Form, Switch } from 'antd'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | |||
| const YahooFinanceForm = ({ onValuesChange, form, node }: IOperatorForm) => { | |||
| const { t } = useTranslate('flow'); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| autoComplete="off" | |||
| form={form} | |||
| onValuesChange={onValuesChange} | |||
| layout={'vertical'} | |||
| > | |||
| <DynamicInputVariable node={node}></DynamicInputVariable> | |||
| <Form.Item label={t('info')} name={'info'}> | |||
| <Switch></Switch> | |||
| </Form.Item> | |||
| <Form.Item label={t('history')} name={'history'}> | |||
| <Switch></Switch> | |||
| </Form.Item> | |||
| <Form.Item label={t('financials')} name={'financials'}> | |||
| <Switch></Switch> | |||
| </Form.Item> | |||
| <Form.Item label={t('balanceSheet')} name={'balance_sheet'}> | |||
| <Switch></Switch> | |||
| </Form.Item> | |||
| <Form.Item label={t('cashFlowStatement')} name={'cash_flow_statement'}> | |||
| <Switch></Switch> | |||
| </Form.Item> | |||
| <Form.Item label={t('news')} name={'news'}> | |||
| <Switch></Switch> | |||
| </Form.Item> | |||
| </Form> | |||
| ); | |||
| }; | |||
| export default YahooFinanceForm; | |||
| @@ -10,6 +10,7 @@ import { | |||
| import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar'; | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | |||
| import { ReactFlowProvider } from '@xyflow/react'; | |||
| import { CodeXml, EllipsisVertical, Forward, Import, Key } from 'lucide-react'; | |||
| import { ComponentPropsWithoutRef } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| @@ -95,20 +96,22 @@ export default function Agent() { | |||
| </Button> | |||
| </div> | |||
| </PageHeader> | |||
| <div> | |||
| <SidebarProvider> | |||
| <AgentSidebar /> | |||
| <div className="w-full"> | |||
| <SidebarTrigger /> | |||
| <div className="w-full h-full"> | |||
| <FlowCanvas | |||
| drawerVisible={chatDrawerVisible} | |||
| hideDrawer={hideChatDrawer} | |||
| ></FlowCanvas> | |||
| <ReactFlowProvider> | |||
| <div> | |||
| <SidebarProvider> | |||
| <AgentSidebar /> | |||
| <div className="w-full"> | |||
| <SidebarTrigger /> | |||
| <div className="w-full h-full"> | |||
| <FlowCanvas | |||
| drawerVisible={chatDrawerVisible} | |||
| hideDrawer={hideChatDrawer} | |||
| ></FlowCanvas> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </SidebarProvider> | |||
| </div> | |||
| </SidebarProvider> | |||
| </div> | |||
| </ReactFlowProvider> | |||
| {fileUploadVisible && ( | |||
| <UploadAgentDialog | |||
| hideModal={hideFileUploadModal} | |||