Feat: Add FormDrawer to agent page. #3221 (#5323)

### 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)
This commit is contained in:
balibabu 2025-02-25 11:32:01 +08:00 committed by GitHub
parent b3d579e2c1
commit 9c9f2dbe3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
63 changed files with 4005 additions and 70 deletions

View File

@ -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>
);

View File

@ -0,0 +1,6 @@
import { RAGFlowNodeType } from '@/interfaces/database/flow';
import { createContext } from 'react';
export const FlowFormContext = createContext<RAGFlowNodeType | undefined>(
undefined,
);

View File

@ -0,0 +1,5 @@
.formWrapper {
:global(.ant-form-item-label) {
font-weight: 600 !important;
}
}

View File

@ -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;

View File

@ -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>
);
};

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;
};

View File

@ -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;

View File

@ -0,0 +1,5 @@
const AnswerForm = () => {
return <div></div>;
};
export default AnswerForm;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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,
};
};

View File

@ -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;
}

View File

@ -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;

View File

@ -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>
);
};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 };
};

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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,
};
};

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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,
};
};

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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]);
};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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}