mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-05-28 09:09:21 +08:00
### 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:
parent
b3d579e2c1
commit
9c9f2dbe3f
@ -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>
|
||||
);
|
||||
|
6
web/src/pages/agent/context.ts
Normal file
6
web/src/pages/agent/context.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export const FlowFormContext = createContext<RAGFlowNodeType | undefined>(
|
||||
undefined,
|
||||
);
|
5
web/src/pages/agent/debug-content/index.less
Normal file
5
web/src/pages/agent/debug-content/index.less
Normal file
@ -0,0 +1,5 @@
|
||||
.formWrapper {
|
||||
:global(.ant-form-item-label) {
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
}
|
238
web/src/pages/agent/debug-content/index.tsx
Normal file
238
web/src/pages/agent/debug-content/index.tsx
Normal 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;
|
74
web/src/pages/agent/debug-content/popover-form.tsx
Normal file
74
web/src/pages/agent/debug-content/popover-form.tsx
Normal 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>
|
||||
);
|
||||
};
|
21
web/src/pages/agent/form-drawer/index.less
Normal file
21
web/src/pages/agent/form-drawer/index.less
Normal 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;
|
||||
}
|
||||
}
|
216
web/src/pages/agent/form-drawer/index.tsx
Normal file
216
web/src/pages/agent/form-drawer/index.tsx
Normal 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;
|
@ -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;
|
77
web/src/pages/agent/form-hooks.ts
Normal file
77
web/src/pages/agent/form-hooks.ts
Normal 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;
|
||||
};
|
21
web/src/pages/agent/form/akshare-form/index.tsx
Normal file
21
web/src/pages/agent/form/akshare-form/index.tsx
Normal 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;
|
5
web/src/pages/agent/form/answer-form/index.tsx
Normal file
5
web/src/pages/agent/form/answer-form/index.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
const AnswerForm = () => {
|
||||
return <div></div>;
|
||||
};
|
||||
|
||||
export default AnswerForm;
|
36
web/src/pages/agent/form/arxiv-form/index.tsx
Normal file
36
web/src/pages/agent/form/arxiv-form/index.tsx
Normal 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;
|
71
web/src/pages/agent/form/baidu-fanyi-form/index.tsx
Normal file
71
web/src/pages/agent/form/baidu-fanyi-form/index.tsx
Normal 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;
|
21
web/src/pages/agent/form/baidu-form/index.tsx
Normal file
21
web/src/pages/agent/form/baidu-form/index.tsx
Normal 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;
|
@ -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;
|
50
web/src/pages/agent/form/begin-form/hooks.ts
Normal file
50
web/src/pages/agent/form/begin-form/hooks.ts
Normal 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,
|
||||
};
|
||||
};
|
24
web/src/pages/agent/form/begin-form/index.less
Normal file
24
web/src/pages/agent/form/begin-form/index.less
Normal 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;
|
||||
}
|
111
web/src/pages/agent/form/begin-form/index.tsx
Normal file
111
web/src/pages/agent/form/begin-form/index.tsx
Normal 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;
|
124
web/src/pages/agent/form/begin-form/paramater-modal.tsx
Normal file
124
web/src/pages/agent/form/begin-form/paramater-modal.tsx
Normal 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>
|
||||
);
|
||||
};
|
92
web/src/pages/agent/form/begin-form/query-table.tsx
Normal file
92
web/src/pages/agent/form/begin-form/query-table.tsx
Normal 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;
|
42
web/src/pages/agent/form/bing-form/index.tsx
Normal file
42
web/src/pages/agent/form/bing-form/index.tsx
Normal 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;
|
225
web/src/pages/agent/form/categorize-form/dynamic-categorize.tsx
Normal file
225
web/src/pages/agent/form/categorize-form/dynamic-categorize.tsx
Normal 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;
|
45
web/src/pages/agent/form/categorize-form/hooks.ts
Normal file
45
web/src/pages/agent/form/categorize-form/hooks.ts
Normal 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 };
|
||||
};
|
13
web/src/pages/agent/form/categorize-form/index.less
Normal file
13
web/src/pages/agent/form/categorize-form/index.less
Normal 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;
|
||||
}
|
43
web/src/pages/agent/form/categorize-form/index.tsx
Normal file
43
web/src/pages/agent/form/categorize-form/index.tsx
Normal 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;
|
130
web/src/pages/agent/form/components/dynamic-input-variable.tsx
Normal file
130
web/src/pages/agent/form/components/dynamic-input-variable.tsx
Normal 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;
|
22
web/src/pages/agent/form/components/index.less
Normal file
22
web/src/pages/agent/form/components/index.less
Normal 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;
|
||||
}
|
||||
}
|
17
web/src/pages/agent/form/concentrator-form/index.tsx
Normal file
17
web/src/pages/agent/form/concentrator-form/index.tsx
Normal 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;
|
38
web/src/pages/agent/form/crawler-form/index.tsx
Normal file
38
web/src/pages/agent/form/crawler-form/index.tsx
Normal 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;
|
36
web/src/pages/agent/form/deepl-form/index.tsx
Normal file
36
web/src/pages/agent/form/deepl-form/index.tsx
Normal 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;
|
38
web/src/pages/agent/form/duckduckgo-form/index.tsx
Normal file
38
web/src/pages/agent/form/duckduckgo-form/index.tsx
Normal 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;
|
53
web/src/pages/agent/form/email-form/index.tsx
Normal file
53
web/src/pages/agent/form/email-form/index.tsx
Normal 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;
|
88
web/src/pages/agent/form/exesql-form/index.tsx
Normal file
88
web/src/pages/agent/form/exesql-form/index.tsx
Normal 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;
|
101
web/src/pages/agent/form/generate-form/dynamic-parameters.tsx
Normal file
101
web/src/pages/agent/form/generate-form/dynamic-parameters.tsx
Normal 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;
|
70
web/src/pages/agent/form/generate-form/hooks.ts
Normal file
70
web/src/pages/agent/form/generate-form/hooks.ts
Normal 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,
|
||||
};
|
||||
};
|
21
web/src/pages/agent/form/generate-form/index.less
Normal file
21
web/src/pages/agent/form/generate-form/index.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
57
web/src/pages/agent/form/generate-form/index.tsx
Normal file
57
web/src/pages/agent/form/generate-form/index.tsx
Normal 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;
|
21
web/src/pages/agent/form/github-form/index.tsx
Normal file
21
web/src/pages/agent/form/github-form/index.tsx
Normal 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;
|
34
web/src/pages/agent/form/google-form/index.tsx
Normal file
34
web/src/pages/agent/form/google-form/index.tsx
Normal 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;
|
75
web/src/pages/agent/form/google-scholar-form/index.tsx
Normal file
75
web/src/pages/agent/form/google-scholar-form/index.tsx
Normal 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;
|
130
web/src/pages/agent/form/invoke-form/dynamic-variables.tsx
Normal file
130
web/src/pages/agent/form/invoke-form/dynamic-variables.tsx
Normal 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;
|
97
web/src/pages/agent/form/invoke-form/hooks.ts
Normal file
97
web/src/pages/agent/form/invoke-form/hooks.ts
Normal 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,
|
||||
};
|
||||
};
|
44
web/src/pages/agent/form/invoke-form/index.less
Normal file
44
web/src/pages/agent/form/invoke-form/index.less
Normal 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;
|
||||
}
|
||||
}
|
78
web/src/pages/agent/form/invoke-form/index.tsx
Normal file
78
web/src/pages/agent/form/invoke-form/index.tsx
Normal 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;
|
94
web/src/pages/agent/form/iteration-from/index.tsx
Normal file
94
web/src/pages/agent/form/iteration-from/index.tsx
Normal 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;
|
145
web/src/pages/agent/form/jin10-form/index.tsx
Normal file
145
web/src/pages/agent/form/jin10-form/index.tsx
Normal 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;
|
32
web/src/pages/agent/form/keyword-extract-form/index.tsx
Normal file
32
web/src/pages/agent/form/keyword-extract-form/index.tsx
Normal 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;
|
16
web/src/pages/agent/form/message-form/index.less
Normal file
16
web/src/pages/agent/form/message-form/index.less
Normal 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;
|
||||
}
|
||||
}
|
87
web/src/pages/agent/form/message-form/index.tsx
Normal file
87
web/src/pages/agent/form/message-form/index.tsx
Normal 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;
|
32
web/src/pages/agent/form/pubmed-form/index.tsx
Normal file
32
web/src/pages/agent/form/pubmed-form/index.tsx
Normal 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;
|
88
web/src/pages/agent/form/qweather-form/index.tsx
Normal file
88
web/src/pages/agent/form/qweather-form/index.tsx
Normal 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;
|
53
web/src/pages/agent/form/relevant-form/hooks.ts
Normal file
53
web/src/pages/agent/form/relevant-form/hooks.ts
Normal 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]);
|
||||
};
|
49
web/src/pages/agent/form/relevant-form/index.tsx
Normal file
49
web/src/pages/agent/form/relevant-form/index.tsx
Normal 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;
|
54
web/src/pages/agent/form/retrieval-form/index.tsx
Normal file
54
web/src/pages/agent/form/retrieval-form/index.tsx
Normal 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;
|
41
web/src/pages/agent/form/rewrite-question-form/index.tsx
Normal file
41
web/src/pages/agent/form/rewrite-question-form/index.tsx
Normal 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;
|
21
web/src/pages/agent/form/switch-form/index.less
Normal file
21
web/src/pages/agent/form/switch-form/index.less
Normal 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;
|
||||
}
|
204
web/src/pages/agent/form/switch-form/index.tsx
Normal file
204
web/src/pages/agent/form/switch-form/index.tsx
Normal 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;
|
24
web/src/pages/agent/form/template-form/index.tsx
Normal file
24
web/src/pages/agent/form/template-form/index.tsx
Normal 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;
|
83
web/src/pages/agent/form/tushare-form/index.tsx
Normal file
83
web/src/pages/agent/form/tushare-form/index.tsx
Normal 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;
|
36
web/src/pages/agent/form/wencai-form/index.tsx
Normal file
36
web/src/pages/agent/form/wencai-form/index.tsx
Normal 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;
|
28
web/src/pages/agent/form/wikipedia-form/index.tsx
Normal file
28
web/src/pages/agent/form/wikipedia-form/index.tsx
Normal 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;
|
40
web/src/pages/agent/form/yahoo-finance-form/index.tsx
Normal file
40
web/src/pages/agent/form/yahoo-finance-form/index.tsx
Normal 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;
|
@ -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}
|
||||
|
Loading…
x
Reference in New Issue
Block a user