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

### What problem does this PR solve?

Feat: Add FormDrawer to agent page. #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-02-25 11:32:01 +08:00
committed by GitHub
parent b3d579e2c1
commit 9c9f2dbe3f
63 changed files with 4005 additions and 70 deletions

View File

@ -0,0 +1,21 @@
import TopNItem from '@/components/top-n-item';
import { Form } from 'antd';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const AkShareForm = ({ onValuesChange, form, node }: IOperatorForm) => {
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<TopNItem initialValue={10} max={99}></TopNItem>
</Form>
);
};
export default AkShareForm;

View File

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

View File

@ -0,0 +1,36 @@
import TopNItem from '@/components/top-n-item';
import { useTranslate } from '@/hooks/common-hooks';
import { Form, Select } from 'antd';
import { useMemo } from 'react';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const ArXivForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
const options = useMemo(() => {
return ['submittedDate', 'lastUpdatedDate', 'relevance'].map((x) => ({
value: x,
label: t(x),
}));
}, [t]);
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<TopNItem initialValue={10}></TopNItem>
<Form.Item label={t('sortBy')} name={'sort_by'}>
<Select options={options}></Select>
</Form.Item>
</Form>
);
};
export default ArXivForm;

View File

@ -0,0 +1,71 @@
import { useTranslate } from '@/hooks/common-hooks';
import { Form, Input, Select } from 'antd';
import { useMemo } from 'react';
import {
BaiduFanyiDomainOptions,
BaiduFanyiSourceLangOptions,
} from '../../constant';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const BaiduFanyiForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
const options = useMemo(() => {
return ['translate', 'fieldtranslate'].map((x) => ({
value: x,
label: t(`baiduSecretKeyOptions.${x}`),
}));
}, [t]);
const baiduFanyiOptions = useMemo(() => {
return BaiduFanyiDomainOptions.map((x) => ({
value: x,
label: t(`baiduDomainOptions.${x}`),
}));
}, [t]);
const baiduFanyiSourceLangOptions = useMemo(() => {
return BaiduFanyiSourceLangOptions.map((x) => ({
value: x,
label: t(`baiduSourceLangOptions.${x}`),
}));
}, [t]);
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<Form.Item label={t('appid')} name={'appid'}>
<Input></Input>
</Form.Item>
<Form.Item label={t('secretKey')} name={'secret_key'}>
<Input></Input>
</Form.Item>
<Form.Item label={t('transType')} name={'trans_type'}>
<Select options={options}></Select>
</Form.Item>
<Form.Item noStyle dependencies={['model_type']}>
{({ getFieldValue }) =>
getFieldValue('trans_type') === 'fieldtranslate' && (
<Form.Item label={t('domain')} name={'domain'}>
<Select options={baiduFanyiOptions}></Select>
</Form.Item>
)
}
</Form.Item>
<Form.Item label={t('sourceLang')} name={'source_lang'}>
<Select options={baiduFanyiSourceLangOptions}></Select>
</Form.Item>
<Form.Item label={t('targetLang')} name={'target_lang'}>
<Select options={baiduFanyiSourceLangOptions}></Select>
</Form.Item>
</Form>
);
};
export default BaiduFanyiForm;

View File

@ -0,0 +1,21 @@
import TopNItem from '@/components/top-n-item';
import { Form } from 'antd';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const BaiduForm = ({ onValuesChange, form, node }: IOperatorForm) => {
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<TopNItem initialValue={10}></TopNItem>
</Form>
);
};
export default BaiduForm;

View File

@ -0,0 +1,68 @@
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, Form, Input } from 'antd';
const BeginDynamicOptions = () => {
return (
<Form.List
name="options"
rules={[
{
validator: async (_, names) => {
if (!names || names.length < 1) {
return Promise.reject(new Error('At least 1 option'));
}
},
},
]}
>
{(fields, { add, remove }, { errors }) => (
<>
{fields.map((field, index) => (
<Form.Item
label={index === 0 ? 'Options' : ''}
required={false}
key={field.key}
>
<Form.Item
{...field}
validateTrigger={['onChange', 'onBlur']}
rules={[
{
required: true,
whitespace: true,
message: 'Please input option or delete this field.',
},
]}
noStyle
>
<Input
placeholder="option"
style={{ width: '90%', marginRight: 16 }}
/>
</Form.Item>
{fields.length > 1 ? (
<MinusCircleOutlined
className="dynamic-delete-button"
onClick={() => remove(field.name)}
/>
) : null}
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => add()}
icon={<PlusOutlined />}
block
>
Add option
</Button>
<Form.ErrorList errors={errors} />
</Form.Item>
</>
)}
</Form.List>
);
};
export default BeginDynamicOptions;

View File

@ -0,0 +1,50 @@
import { useSetModalState } from '@/hooks/common-hooks';
import { useSetSelectedRecord } from '@/hooks/logic-hooks';
import { useCallback, useMemo, useState } from 'react';
import { BeginQuery, IOperatorForm } from '../../interface';
export const useEditQueryRecord = ({ form, onValuesChange }: IOperatorForm) => {
const { setRecord, currentRecord } = useSetSelectedRecord<BeginQuery>();
const { visible, hideModal, showModal } = useSetModalState();
const [index, setIndex] = useState(-1);
const otherThanCurrentQuery = useMemo(() => {
const query: BeginQuery[] = form?.getFieldValue('query') || [];
return query.filter((item, idx) => idx !== index);
}, [form, index]);
const handleEditRecord = useCallback(
(record: BeginQuery) => {
const query: BeginQuery[] = form?.getFieldValue('query') || [];
const nextQuery: BeginQuery[] =
index > -1 ? query.toSpliced(index, 1, record) : [...query, record];
onValuesChange?.(
{ query: nextQuery },
{ query: nextQuery, prologue: form?.getFieldValue('prologue') },
);
hideModal();
},
[form, hideModal, index, onValuesChange],
);
const handleShowModal = useCallback(
(idx?: number, record?: BeginQuery) => {
setIndex(idx ?? -1);
setRecord(record ?? ({} as BeginQuery));
showModal();
},
[setRecord, showModal],
);
return {
ok: handleEditRecord,
currentRecord,
setRecord,
visible,
hideModal,
showModal: handleShowModal,
otherThanCurrentQuery,
};
};

View File

@ -0,0 +1,24 @@
.dynamicInputVariable {
background-color: #ebe9e950;
:global(.ant-collapse-content) {
background-color: #f6f6f657;
}
:global(.ant-collapse-content-box) {
padding: 0 !important;
}
margin-bottom: 20px;
.title {
font-weight: 600;
font-size: 16px;
}
.addButton {
color: rgb(22, 119, 255);
font-weight: 600;
}
}
.addButton {
color: rgb(22, 119, 255);
font-weight: 600;
}

View File

@ -0,0 +1,111 @@
import { PlusOutlined } from '@ant-design/icons';
import { Button, Form, Input } from 'antd';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { BeginQuery, IOperatorForm } from '../../interface';
import { useEditQueryRecord } from './hooks';
import { ModalForm } from './paramater-modal';
import QueryTable from './query-table';
import styles from './index.less';
type FieldType = {
prologue?: string;
};
const BeginForm = ({ onValuesChange, form }: IOperatorForm) => {
const { t } = useTranslation();
const {
ok,
currentRecord,
visible,
hideModal,
showModal,
otherThanCurrentQuery,
} = useEditQueryRecord({
form,
onValuesChange,
});
const handleDeleteRecord = useCallback(
(idx: number) => {
const query = form?.getFieldValue('query') || [];
const nextQuery = query.filter(
(item: BeginQuery, index: number) => index !== idx,
);
onValuesChange?.(
{ query: nextQuery },
{ query: nextQuery, prologue: form?.getFieldValue('prologue') },
);
},
[form, onValuesChange],
);
return (
<Form.Provider
onFormFinish={(name, { values }) => {
if (name === 'queryForm') {
ok(values as BeginQuery);
}
}}
>
<Form
name="basicForm"
onValuesChange={onValuesChange}
autoComplete="off"
form={form}
layout="vertical"
>
<Form.Item<FieldType>
name={'prologue'}
label={t('chat.setAnOpener')}
tooltip={t('chat.setAnOpenerTip')}
initialValue={t('chat.setAnOpenerInitial')}
>
<Input.TextArea autoSize={{ minRows: 5 }} />
</Form.Item>
{/* Create a hidden field to make Form instance record this */}
<Form.Item name="query" noStyle />
<Form.Item
shouldUpdate={(prevValues, curValues) =>
prevValues.query !== curValues.query
}
>
{({ getFieldValue }) => {
const query: BeginQuery[] = getFieldValue('query') || [];
return (
<QueryTable
data={query}
showModal={showModal}
deleteRecord={handleDeleteRecord}
></QueryTable>
);
}}
</Form.Item>
<Button
htmlType="button"
style={{ margin: '0 8px' }}
onClick={() => showModal()}
icon={<PlusOutlined />}
block
className={styles.addButton}
>
{t('flow.addItem')}
</Button>
{visible && (
<ModalForm
visible={visible}
hideModal={hideModal}
initialValue={currentRecord}
onOk={ok}
otherThanCurrentQuery={otherThanCurrentQuery}
/>
)}
</Form>
</Form.Provider>
);
};
export default BeginForm;

View File

@ -0,0 +1,124 @@
import { useResetFormOnCloseModal } from '@/hooks/logic-hooks';
import { IModalProps } from '@/interfaces/common';
import { Form, Input, Modal, Select, Switch } from 'antd';
import { DefaultOptionType } from 'antd/es/select';
import { useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { BeginQueryType, BeginQueryTypeIconMap } from '../../constant';
import { BeginQuery } from '../../interface';
import BeginDynamicOptions from './begin-dynamic-options';
export const ModalForm = ({
visible,
initialValue,
hideModal,
otherThanCurrentQuery,
}: IModalProps<BeginQuery> & {
initialValue: BeginQuery;
otherThanCurrentQuery: BeginQuery[];
}) => {
const { t } = useTranslation();
const [form] = Form.useForm();
const options = useMemo(() => {
return Object.values(BeginQueryType).reduce<DefaultOptionType[]>(
(pre, cur) => {
const Icon = BeginQueryTypeIconMap[cur];
return [
...pre,
{
label: (
<div className="flex items-center gap-2">
<Icon
className={`size-${cur === BeginQueryType.Options ? 4 : 5}`}
></Icon>
{cur}
</div>
),
value: cur,
},
];
},
[],
);
}, []);
useResetFormOnCloseModal({
form,
visible: visible,
});
useEffect(() => {
form.setFieldsValue(initialValue);
}, [form, initialValue]);
const onOk = () => {
form.submit();
};
return (
<Modal
title={t('flow.variableSettings')}
open={visible}
onOk={onOk}
onCancel={hideModal}
centered
>
<Form form={form} layout="vertical" name="queryForm" autoComplete="false">
<Form.Item
name="type"
label="Type"
rules={[{ required: true }]}
initialValue={BeginQueryType.Line}
>
<Select options={options} />
</Form.Item>
<Form.Item
name="key"
label="Key"
rules={[
{ required: true },
() => ({
validator(_, value) {
if (
!value ||
!otherThanCurrentQuery.some((x) => x.key === value)
) {
return Promise.resolve();
}
return Promise.reject(new Error('The key cannot be repeated!'));
},
}),
]}
>
<Input />
</Form.Item>
<Form.Item name="name" label="Name" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item
name="optional"
label={'Optional'}
valuePropName="checked"
initialValue={false}
>
<Switch />
</Form.Item>
<Form.Item
shouldUpdate={(prevValues, curValues) =>
prevValues.type !== curValues.type
}
>
{({ getFieldValue }) => {
const type: BeginQueryType = getFieldValue('type');
return (
type === BeginQueryType.Options && (
<BeginDynamicOptions></BeginDynamicOptions>
)
);
}}
</Form.Item>
</Form>
</Modal>
);
};

View File

@ -0,0 +1,92 @@
import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
import type { TableProps } from 'antd';
import { Collapse, Space, Table, Tooltip } from 'antd';
import { BeginQuery } from '../../interface';
import { useTranslation } from 'react-i18next';
import styles from './index.less';
interface IProps {
data: BeginQuery[];
deleteRecord(index: number): void;
showModal(index: number, record: BeginQuery): void;
}
const QueryTable = ({ data, deleteRecord, showModal }: IProps) => {
const { t } = useTranslation();
const columns: TableProps<BeginQuery>['columns'] = [
{
title: 'Key',
dataIndex: 'key',
key: 'key',
ellipsis: {
showTitle: false,
},
render: (key) => (
<Tooltip placement="topLeft" title={key}>
{key}
</Tooltip>
),
},
{
title: t('flow.name'),
dataIndex: 'name',
key: 'name',
ellipsis: {
showTitle: false,
},
render: (name) => (
<Tooltip placement="topLeft" title={name}>
{name}
</Tooltip>
),
},
{
title: t('flow.type'),
dataIndex: 'type',
key: 'type',
},
{
title: t('flow.optional'),
dataIndex: 'optional',
key: 'optional',
render: (optional) => (optional ? 'Yes' : 'No'),
},
{
title: t('common.action'),
key: 'action',
render: (_, record, idx) => (
<Space>
<EditOutlined onClick={() => showModal(idx, record)} />
<DeleteOutlined
className="cursor-pointer"
onClick={() => deleteRecord(idx)}
/>
</Space>
),
},
];
return (
<Collapse
defaultActiveKey={['1']}
className={styles.dynamicInputVariable}
items={[
{
key: '1',
label: <span className={styles.title}>{t('flow.input')}</span>,
children: (
<Table<BeginQuery>
columns={columns}
dataSource={data}
pagination={false}
/>
),
},
]}
/>
);
};
export default QueryTable;

View File

@ -0,0 +1,42 @@
import TopNItem from '@/components/top-n-item';
import { useTranslate } from '@/hooks/common-hooks';
import { Form, Input, Select } from 'antd';
import { useMemo } from 'react';
import { BingCountryOptions, BingLanguageOptions } from '../../constant';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const BingForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
const options = useMemo(() => {
return ['Webpages', 'News'].map((x) => ({ label: x, value: x }));
}, []);
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<TopNItem initialValue={10}></TopNItem>
<Form.Item label={t('channel')} name={'channel'}>
<Select options={options}></Select>
</Form.Item>
<Form.Item label={t('apiKey')} name={'api_key'}>
<Input></Input>
</Form.Item>
<Form.Item label={t('country')} name={'country'}>
<Select options={BingCountryOptions}></Select>
</Form.Item>
<Form.Item label={t('language')} name={'language'}>
<Select options={BingLanguageOptions}></Select>
</Form.Item>
</Form>
);
};
export default BingForm;

View File

@ -0,0 +1,225 @@
import { useTranslate } from '@/hooks/common-hooks';
import { CloseOutlined, PlusOutlined } from '@ant-design/icons';
import { useUpdateNodeInternals } from '@xyflow/react';
import {
Button,
Collapse,
Flex,
Form,
FormListFieldData,
Input,
Select,
} from 'antd';
import { FormInstance } from 'antd/lib';
import { humanId } from 'human-id';
import trim from 'lodash/trim';
import {
ChangeEventHandler,
FocusEventHandler,
useCallback,
useEffect,
useState,
} from 'react';
import { Operator } from '../../constant';
import { useBuildFormSelectOptions } from '../../form-hooks';
import styles from './index.less';
interface IProps {
nodeId?: string;
}
interface INameInputProps {
value?: string;
onChange?: (value: string) => void;
otherNames?: string[];
validate(errors: string[]): void;
}
const getOtherFieldValues = (
form: FormInstance,
formListName: string = 'items',
field: FormListFieldData,
latestField: string,
) =>
(form.getFieldValue([formListName]) ?? [])
.map((x: any) => x[latestField])
.filter(
(x: string) =>
x !== form.getFieldValue([formListName, field.name, latestField]),
);
const NameInput = ({
value,
onChange,
otherNames,
validate,
}: INameInputProps) => {
const [name, setName] = useState<string | undefined>();
const { t } = useTranslate('flow');
const handleNameChange: ChangeEventHandler<HTMLInputElement> = useCallback(
(e) => {
const val = e.target.value;
// trigger validation
if (otherNames?.some((x) => x === val)) {
validate([t('nameRepeatedMsg')]);
} else if (trim(val) === '') {
validate([t('nameRequiredMsg')]);
} else {
validate([]);
}
setName(val);
},
[otherNames, validate, t],
);
const handleNameBlur: FocusEventHandler<HTMLInputElement> = useCallback(
(e) => {
const val = e.target.value;
if (otherNames?.every((x) => x !== val) && trim(val) !== '') {
onChange?.(val);
}
},
[onChange, otherNames],
);
useEffect(() => {
setName(value);
}, [value]);
return (
<Input
value={name}
onChange={handleNameChange}
onBlur={handleNameBlur}
></Input>
);
};
const FormSet = ({ nodeId, field }: IProps & { field: FormListFieldData }) => {
const form = Form.useFormInstance();
const { t } = useTranslate('flow');
const buildCategorizeToOptions = useBuildFormSelectOptions(
Operator.Categorize,
nodeId,
);
return (
<section>
<Form.Item
label={t('categoryName')}
name={[field.name, 'name']}
validateTrigger={['onChange', 'onBlur']}
rules={[
{
required: true,
whitespace: true,
message: t('nameMessage'),
},
]}
>
<NameInput
otherNames={getOtherFieldValues(form, 'items', field, 'name')}
validate={(errors: string[]) =>
form.setFields([
{
name: ['items', field.name, 'name'],
errors,
},
])
}
></NameInput>
</Form.Item>
<Form.Item label={t('description')} name={[field.name, 'description']}>
<Input.TextArea rows={3} />
</Form.Item>
<Form.Item label={t('examples')} name={[field.name, 'examples']}>
<Input.TextArea rows={3} />
</Form.Item>
<Form.Item label={t('nextStep')} name={[field.name, 'to']}>
<Select
allowClear
options={buildCategorizeToOptions(
getOtherFieldValues(form, 'items', field, 'to'),
)}
/>
</Form.Item>
<Form.Item hidden name={[field.name, 'index']}>
<Input />
</Form.Item>
</section>
);
};
const DynamicCategorize = ({ nodeId }: IProps) => {
const updateNodeInternals = useUpdateNodeInternals();
const form = Form.useFormInstance();
const { t } = useTranslate('flow');
return (
<>
<Form.List name="items">
{(fields, { add, remove }) => {
const handleAdd = () => {
const idx = form.getFieldValue([
'items',
fields.at(-1)?.name,
'index',
]);
add({
name: humanId(),
index: fields.length === 0 ? 0 : idx + 1,
});
if (nodeId) updateNodeInternals(nodeId);
};
return (
<Flex gap={18} vertical>
{fields.map((field) => (
<Collapse
size="small"
key={field.key}
className={styles.caseCard}
items={[
{
key: field.key,
label: (
<div className="flex justify-between">
<span>
{form.getFieldValue(['items', field.name, 'name'])}
</span>
<CloseOutlined
onClick={() => {
remove(field.name);
}}
/>
</div>
),
children: (
<FormSet nodeId={nodeId} field={field}></FormSet>
),
},
]}
></Collapse>
))}
<Button
type="dashed"
onClick={handleAdd}
block
className={styles.addButton}
icon={<PlusOutlined />}
>
{t('addCategory')}
</Button>
</Flex>
);
}}
</Form.List>
</>
);
};
export default DynamicCategorize;

View File

@ -0,0 +1,45 @@
import {
ICategorizeItem,
ICategorizeItemResult,
} from '@/interfaces/database/flow';
import omit from 'lodash/omit';
import { useCallback } from 'react';
import { IOperatorForm } from '../../interface';
/**
* Convert the list in the following form into an object
* {
"items": [
{
"name": "Categorize 1",
"description": "111",
"examples": "ddd",
"to": "Retrieval:LazyEelsStick"
}
]
}
*/
const buildCategorizeObjectFromList = (list: Array<ICategorizeItem>) => {
return list.reduce<ICategorizeItemResult>((pre, cur) => {
if (cur?.name) {
pre[cur.name] = omit(cur, 'name');
}
return pre;
}, {});
};
export const useHandleFormValuesChange = ({
onValuesChange,
}: IOperatorForm) => {
const handleValuesChange = useCallback(
(changedValues: any, values: any) => {
onValuesChange?.(changedValues, {
...omit(values, 'items'),
category_description: buildCategorizeObjectFromList(values.items),
});
},
[onValuesChange],
);
return { handleValuesChange };
};

View File

@ -0,0 +1,13 @@
@lightBackgroundColor: rgba(150, 150, 150, 0.07);
@darkBackgroundColor: rgba(150, 150, 150, 0.12);
.caseCard {
:global(.ant-collapse-content) {
background-color: @darkBackgroundColor;
}
}
.addButton {
color: rgb(22, 119, 255);
font-weight: 600;
}

View File

@ -0,0 +1,43 @@
import LLMSelect from '@/components/llm-select';
import MessageHistoryWindowSizeItem from '@/components/message-history-window-size-item';
import { useTranslate } from '@/hooks/common-hooks';
import { Form } from 'antd';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
import DynamicCategorize from './dynamic-categorize';
import { useHandleFormValuesChange } from './hooks';
const CategorizeForm = ({ form, onValuesChange, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
const { handleValuesChange } = useHandleFormValuesChange({
form,
nodeId: node?.id,
onValuesChange,
});
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={handleValuesChange}
initialValues={{ items: [{}] }}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<Form.Item
name={'llm_id'}
label={t('model', { keyPrefix: 'chat' })}
tooltip={t('modelTip', { keyPrefix: 'chat' })}
>
<LLMSelect></LLMSelect>
</Form.Item>
<MessageHistoryWindowSizeItem
initialValue={1}
></MessageHistoryWindowSizeItem>
<DynamicCategorize nodeId={node?.id}></DynamicCategorize>
</Form>
);
};
export default CategorizeForm;

View File

@ -0,0 +1,130 @@
import { RAGFlowNodeType } from '@/interfaces/database/flow';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, Collapse, Flex, Form, Input, Select } from 'antd';
import { PropsWithChildren, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query';
import styles from './index.less';
interface IProps {
node?: RAGFlowNodeType;
}
enum VariableType {
Reference = 'reference',
Input = 'input',
}
const getVariableName = (type: string) =>
type === VariableType.Reference ? 'component_id' : 'value';
const DynamicVariableForm = ({ node }: IProps) => {
const { t } = useTranslation();
const valueOptions = useBuildComponentIdSelectOptions(
node?.id,
node?.parentId,
);
const form = Form.useFormInstance();
const options = [
{ value: VariableType.Reference, label: t('flow.reference') },
{ value: VariableType.Input, label: t('flow.text') },
];
const handleTypeChange = useCallback(
(name: number) => () => {
setTimeout(() => {
form.setFieldValue(['query', name, 'component_id'], undefined);
form.setFieldValue(['query', name, 'value'], undefined);
}, 0);
},
[form],
);
return (
<Form.List name="query">
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, ...restField }) => (
<Flex key={key} gap={10} align={'baseline'}>
<Form.Item
{...restField}
name={[name, 'type']}
className={styles.variableType}
>
<Select
options={options}
onChange={handleTypeChange(name)}
></Select>
</Form.Item>
<Form.Item noStyle dependencies={[name, 'type']}>
{({ getFieldValue }) => {
const type = getFieldValue(['query', name, 'type']);
return (
<Form.Item
{...restField}
name={[name, getVariableName(type)]}
className={styles.variableValue}
>
{type === VariableType.Reference ? (
<Select
placeholder={t('common.pleaseSelect')}
options={valueOptions}
></Select>
) : (
<Input placeholder={t('common.pleaseInput')} />
)}
</Form.Item>
);
}}
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} />
</Flex>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => add({ type: VariableType.Reference })}
block
icon={<PlusOutlined />}
className={styles.addButton}
>
{t('flow.addVariable')}
</Button>
</Form.Item>
</>
)}
</Form.List>
);
};
export function FormCollapse({
children,
title,
}: PropsWithChildren<{ title: string }>) {
return (
<Collapse
className={styles.dynamicInputVariable}
defaultActiveKey={['1']}
items={[
{
key: '1',
label: <span className={styles.title}>{title}</span>,
children,
},
]}
/>
);
}
const DynamicInputVariable = ({ node }: IProps) => {
const { t } = useTranslation();
return (
<FormCollapse title={t('flow.input')}>
<DynamicVariableForm node={node}></DynamicVariableForm>
</FormCollapse>
);
};
export default DynamicInputVariable;

View File

@ -0,0 +1,22 @@
.dynamicInputVariable {
background-color: #ebe9e950;
:global(.ant-collapse-content) {
background-color: #f6f6f657;
}
margin-bottom: 20px;
.title {
font-weight: 600;
font-size: 16px;
}
.variableType {
width: 30%;
}
.variableValue {
flex: 1;
}
.addButton {
color: rgb(22, 119, 255);
font-weight: 600;
}
}

View File

@ -0,0 +1,17 @@
import { Form } from 'antd';
import { IOperatorForm } from '../../interface';
const ConcentratorForm = ({ onValuesChange, form }: IOperatorForm) => {
return (
<Form
name="basic"
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
></Form>
);
};
export default ConcentratorForm;

View File

@ -0,0 +1,38 @@
import { useTranslate } from '@/hooks/common-hooks';
import { Form, Input, Select } from 'antd';
import { useMemo } from 'react';
import { CrawlerResultOptions } from '../../constant';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const CrawlerForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
const crawlerResultOptions = useMemo(() => {
return CrawlerResultOptions.map((x) => ({
value: x,
label: t(`crawlerResultOptions.${x}`),
}));
}, [t]);
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<Form.Item label={t('proxy')} name={'proxy'}>
<Input placeholder="like: http://127.0.0.1:8888"></Input>
</Form.Item>
<Form.Item
label={t('extractType')}
name={'extract_type'}
initialValue="markdown"
>
<Select options={crawlerResultOptions}></Select>
</Form.Item>
</Form>
);
};
export default CrawlerForm;

View File

@ -0,0 +1,36 @@
import TopNItem from '@/components/top-n-item';
import { useTranslate } from '@/hooks/common-hooks';
import { Form, Select } from 'antd';
import { DeepLSourceLangOptions, DeepLTargetLangOptions } from '../../constant';
import { useBuildSortOptions } from '../../form-hooks';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const DeepLForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
const options = useBuildSortOptions();
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<TopNItem initialValue={5}></TopNItem>
<Form.Item label={t('authKey')} name={'auth_key'}>
<Select options={options}></Select>
</Form.Item>
<Form.Item label={t('sourceLang')} name={'source_lang'}>
<Select options={DeepLSourceLangOptions}></Select>
</Form.Item>
<Form.Item label={t('targetLang')} name={'target_lang'}>
<Select options={DeepLTargetLangOptions}></Select>
</Form.Item>
</Form>
);
};
export default DeepLForm;

View File

@ -0,0 +1,38 @@
import TopNItem from '@/components/top-n-item';
import { useTranslate } from '@/hooks/common-hooks';
import { Form, Select } from 'antd';
import { useMemo } from 'react';
import { Channel } from '../../constant';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const DuckDuckGoForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
const options = useMemo(() => {
return Object.values(Channel).map((x) => ({ value: x, label: t(x) }));
}, [t]);
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<TopNItem initialValue={10}></TopNItem>
<Form.Item
label={t('channel')}
name={'channel'}
tooltip={t('channelTip')}
initialValue={'text'}
>
<Select options={options}></Select>
</Form.Item>
</Form>
);
};
export default DuckDuckGoForm;

View File

@ -0,0 +1,53 @@
import { useTranslate } from '@/hooks/common-hooks';
import { Form, Input } from 'antd';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const EmailForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
{/* SMTP服务器配置 */}
<Form.Item label={t('smtpServer')} name={'smtp_server'}>
<Input placeholder="smtp.example.com" />
</Form.Item>
<Form.Item label={t('smtpPort')} name={'smtp_port'}>
<Input type="number" placeholder="587" />
</Form.Item>
<Form.Item label={t('senderEmail')} name={'email'}>
<Input placeholder="sender@example.com" />
</Form.Item>
<Form.Item label={t('authCode')} name={'password'}>
<Input.Password placeholder="your_password" />
</Form.Item>
<Form.Item label={t('senderName')} name={'sender_name'}>
<Input placeholder="Sender Name" />
</Form.Item>
{/* 动态参数说明 */}
<div style={{ marginBottom: 24 }}>
<h4>{t('dynamicParameters')}</h4>
<div>{t('jsonFormatTip')}</div>
<pre style={{ background: '#f5f5f5', padding: 12, borderRadius: 4 }}>
{`{
"to_email": "recipient@example.com",
"cc_email": "cc@example.com",
"subject": "Email Subject",
"content": "Email Content"
}`}
</pre>
</div>
</Form>
);
};
export default EmailForm;

View File

@ -0,0 +1,88 @@
import LLMSelect from '@/components/llm-select';
import TopNItem from '@/components/top-n-item';
import { useTranslate } from '@/hooks/common-hooks';
import { useTestDbConnect } from '@/hooks/flow-hooks';
import { Button, Flex, Form, Input, InputNumber, Select } from 'antd';
import { useCallback } from 'react';
import { ExeSQLOptions } from '../../constant';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const ExeSQLForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
const { testDbConnect, loading } = useTestDbConnect();
const handleTest = useCallback(async () => {
const ret = await form?.validateFields();
testDbConnect(ret);
}, [form, testDbConnect]);
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<Form.Item
name={'llm_id'}
label={t('model', { keyPrefix: 'chat' })}
tooltip={t('modelTip', { keyPrefix: 'chat' })}
>
<LLMSelect></LLMSelect>
</Form.Item>
<Form.Item
label={t('dbType')}
name={'db_type'}
rules={[{ required: true }]}
>
<Select options={ExeSQLOptions}></Select>
</Form.Item>
<Form.Item
label={t('database')}
name={'database'}
rules={[{ required: true }]}
>
<Input></Input>
</Form.Item>
<Form.Item
label={t('username')}
name={'username'}
rules={[{ required: true }]}
>
<Input></Input>
</Form.Item>
<Form.Item label={t('host')} name={'host'} rules={[{ required: true }]}>
<Input></Input>
</Form.Item>
<Form.Item label={t('port')} name={'port'} rules={[{ required: true }]}>
<InputNumber></InputNumber>
</Form.Item>
<Form.Item
label={t('password')}
name={'password'}
rules={[{ required: true }]}
>
<Input.Password></Input.Password>
</Form.Item>
<Form.Item
label={t('loop')}
name={'loop'}
tooltip={t('loopTip')}
rules={[{ required: true }]}
>
<InputNumber></InputNumber>
</Form.Item>
<TopNItem initialValue={30} max={1000}></TopNItem>
<Flex justify={'end'}>
<Button type={'primary'} loading={loading} onClick={handleTest}>
Test
</Button>
</Flex>
</Form>
);
};
export default ExeSQLForm;

View File

@ -0,0 +1,101 @@
import { EditableCell, EditableRow } from '@/components/editable-cell';
import { useTranslate } from '@/hooks/common-hooks';
import { RAGFlowNodeType } from '@/interfaces/database/flow';
import { DeleteOutlined } from '@ant-design/icons';
import { Button, Flex, Select, Table, TableProps } from 'antd';
import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query';
import { IGenerateParameter } from '../../interface';
import { useHandleOperateParameters } from './hooks';
import styles from './index.less';
interface IProps {
node?: RAGFlowNodeType;
}
const components = {
body: {
row: EditableRow,
cell: EditableCell,
},
};
const DynamicParameters = ({ node }: IProps) => {
const nodeId = node?.id;
const { t } = useTranslate('flow');
const options = useBuildComponentIdSelectOptions(nodeId, node?.parentId);
const {
dataSource,
handleAdd,
handleRemove,
handleSave,
handleComponentIdChange,
} = useHandleOperateParameters(nodeId!);
const columns: TableProps<IGenerateParameter>['columns'] = [
{
title: t('key'),
dataIndex: 'key',
key: 'key',
width: '40%',
onCell: (record: IGenerateParameter) => ({
record,
editable: true,
dataIndex: 'key',
title: 'key',
handleSave,
}),
},
{
title: t('value'),
dataIndex: 'component_id',
key: 'component_id',
align: 'center',
width: '40%',
render(text, record) {
return (
<Select
style={{ width: '100%' }}
allowClear
options={options}
value={text}
onChange={handleComponentIdChange(record)}
/>
);
},
},
{
title: t('operation'),
dataIndex: 'operation',
width: 20,
key: 'operation',
align: 'center',
fixed: 'right',
render(_, record) {
return <DeleteOutlined onClick={handleRemove(record.id)} />;
},
},
];
return (
<section>
<Flex justify="end">
<Button size="small" onClick={handleAdd}>
{t('add')}
</Button>
</Flex>
<Table
dataSource={dataSource}
columns={columns}
rowKey={'id'}
className={styles.variableTable}
components={components}
rowClassName={() => styles.editableRow}
scroll={{ x: true }}
bordered
/>
</section>
);
};
export default DynamicParameters;

View File

@ -0,0 +1,70 @@
import get from 'lodash/get';
import { useCallback, useMemo } from 'react';
import { v4 as uuid } from 'uuid';
import { IGenerateParameter } from '../../interface';
import useGraphStore from '../../store';
export const useHandleOperateParameters = (nodeId: string) => {
const { getNode, updateNodeForm } = useGraphStore((state) => state);
const node = getNode(nodeId);
const dataSource: IGenerateParameter[] = useMemo(
() => get(node, 'data.form.parameters', []) as IGenerateParameter[],
[node],
);
const handleComponentIdChange = useCallback(
(row: IGenerateParameter) => (value: string) => {
const newData = [...dataSource];
const index = newData.findIndex((item) => row.id === item.id);
const item = newData[index];
newData.splice(index, 1, {
...item,
component_id: value,
});
updateNodeForm(nodeId, { parameters: newData });
},
[updateNodeForm, nodeId, dataSource],
);
const handleRemove = useCallback(
(id?: string) => () => {
const newData = dataSource.filter((item) => item.id !== id);
updateNodeForm(nodeId, { parameters: newData });
},
[updateNodeForm, nodeId, dataSource],
);
const handleAdd = useCallback(() => {
updateNodeForm(nodeId, {
parameters: [
...dataSource,
{
id: uuid(),
key: '',
component_id: undefined,
},
],
});
}, [dataSource, nodeId, updateNodeForm]);
const handleSave = (row: IGenerateParameter) => {
const newData = [...dataSource];
const index = newData.findIndex((item) => row.id === item.id);
const item = newData[index];
newData.splice(index, 1, {
...item,
...row,
});
updateNodeForm(nodeId, { parameters: newData });
};
return {
handleAdd,
handleRemove,
handleComponentIdChange,
handleSave,
dataSource,
};
};

View File

@ -0,0 +1,21 @@
.variableTable {
margin-top: 14px;
}
.editableRow {
:global(.editable-cell) {
position: relative;
}
:global(.editable-cell-value-wrap) {
padding: 5px 12px;
cursor: pointer;
height: 30px !important;
}
&:hover {
:global(.editable-cell-value-wrap) {
padding: 4px 11px;
border: 1px solid #d9d9d9;
border-radius: 2px;
}
}
}

View File

@ -0,0 +1,57 @@
import LLMSelect from '@/components/llm-select';
import MessageHistoryWindowSizeItem from '@/components/message-history-window-size-item';
import { PromptEditor } from '@/components/prompt-editor';
import { useTranslate } from '@/hooks/common-hooks';
import { Form, Switch } from 'antd';
import { IOperatorForm } from '../../interface';
const GenerateForm = ({ onValuesChange, form }: IOperatorForm) => {
const { t } = useTranslate('flow');
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<Form.Item
name={'llm_id'}
label={t('model', { keyPrefix: 'chat' })}
tooltip={t('modelTip', { keyPrefix: 'chat' })}
>
<LLMSelect></LLMSelect>
</Form.Item>
<Form.Item
name={['prompt']}
label={t('systemPrompt')}
initialValue={t('promptText')}
tooltip={t('promptTip', { keyPrefix: 'knowledgeConfiguration' })}
rules={[
{
required: true,
message: t('promptMessage'),
},
]}
>
{/* <Input.TextArea rows={8}></Input.TextArea> */}
<PromptEditor></PromptEditor>
</Form.Item>
<Form.Item
name={['cite']}
label={t('cite')}
initialValue={true}
valuePropName="checked"
tooltip={t('citeTip')}
>
<Switch />
</Form.Item>
<MessageHistoryWindowSizeItem
initialValue={12}
></MessageHistoryWindowSizeItem>
</Form>
);
};
export default GenerateForm;

View File

@ -0,0 +1,21 @@
import TopNItem from '@/components/top-n-item';
import { Form } from 'antd';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const GithubForm = ({ onValuesChange, form, node }: IOperatorForm) => {
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<TopNItem initialValue={5}></TopNItem>
</Form>
);
};
export default GithubForm;

View File

@ -0,0 +1,34 @@
import TopNItem from '@/components/top-n-item';
import { useTranslate } from '@/hooks/common-hooks';
import { Form, Input, Select } from 'antd';
import { GoogleCountryOptions, GoogleLanguageOptions } from '../../constant';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const GoogleForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<TopNItem initialValue={10}></TopNItem>
<Form.Item label={t('apiKey')} name={'api_key'}>
<Input></Input>
</Form.Item>
<Form.Item label={t('country')} name={'country'}>
<Select options={GoogleCountryOptions}></Select>
</Form.Item>
<Form.Item label={t('language')} name={'language'}>
<Select options={GoogleLanguageOptions}></Select>
</Form.Item>
</Form>
);
};
export default GoogleForm;

View File

@ -0,0 +1,75 @@
import TopNItem from '@/components/top-n-item';
import { useTranslate } from '@/hooks/common-hooks';
import { DatePicker, DatePickerProps, Form, Select, Switch } from 'antd';
import dayjs from 'dayjs';
import { useCallback, useMemo } from 'react';
import { useBuildSortOptions } from '../../form-hooks';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const YearPicker = ({
onChange,
value,
}: {
onChange?: (val: number | undefined) => void;
value?: number | undefined;
}) => {
const handleChange: DatePickerProps['onChange'] = useCallback(
(val: any) => {
const nextVal = val?.format('YYYY');
onChange?.(nextVal ? Number(nextVal) : undefined);
},
[onChange],
);
// The year needs to be converted into a number and saved to the backend
const nextValue = useMemo(() => {
if (value) {
return dayjs(value.toString());
}
return undefined;
}, [value]);
return <DatePicker picker="year" onChange={handleChange} value={nextValue} />;
};
const GoogleScholarForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
const options = useBuildSortOptions();
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<TopNItem initialValue={5}></TopNItem>
<Form.Item
label={t('sortBy')}
name={'sort_by'}
initialValue={'relevance'}
>
<Select options={options}></Select>
</Form.Item>
<Form.Item label={t('yearLow')} name={'year_low'}>
<YearPicker />
</Form.Item>
<Form.Item label={t('yearHigh')} name={'year_high'}>
<YearPicker />
</Form.Item>
<Form.Item
label={t('patents')}
name={'patents'}
valuePropName="checked"
initialValue={true}
>
<Switch></Switch>
</Form.Item>
</Form>
);
};
export default GoogleScholarForm;

View File

@ -0,0 +1,130 @@
import { EditableCell, EditableRow } from '@/components/editable-cell';
import { useTranslate } from '@/hooks/common-hooks';
import { DeleteOutlined } from '@ant-design/icons';
import { Button, Collapse, Flex, Input, Select, Table, TableProps } from 'antd';
import { trim } from 'lodash';
import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query';
import { IInvokeVariable, RAGFlowNodeType } from '../../interface';
import { useHandleOperateParameters } from './hooks';
import styles from './index.less';
interface IProps {
node?: RAGFlowNodeType;
}
const components = {
body: {
row: EditableRow,
cell: EditableCell,
},
};
const DynamicVariablesForm = ({ node }: IProps) => {
const nodeId = node?.id;
const { t } = useTranslate('flow');
const options = useBuildComponentIdSelectOptions(nodeId, node?.parentId);
const {
dataSource,
handleAdd,
handleRemove,
handleSave,
handleComponentIdChange,
handleValueChange,
} = useHandleOperateParameters(nodeId!);
const columns: TableProps<IInvokeVariable>['columns'] = [
{
title: t('key'),
dataIndex: 'key',
key: 'key',
onCell: (record: IInvokeVariable) => ({
record,
editable: true,
dataIndex: 'key',
title: 'key',
handleSave,
}),
},
{
title: t('componentId'),
dataIndex: 'component_id',
key: 'component_id',
align: 'center',
width: 140,
render(text, record) {
return (
<Select
style={{ width: '100%' }}
allowClear
options={options}
value={text}
disabled={trim(record.value) !== ''}
onChange={handleComponentIdChange(record)}
/>
);
},
},
{
title: t('value'),
dataIndex: 'value',
key: 'value',
align: 'center',
width: 140,
render(text, record) {
return (
<Input
value={text}
disabled={!!record.component_id}
onChange={handleValueChange(record)}
/>
);
},
},
{
title: t('operation'),
dataIndex: 'operation',
width: 20,
key: 'operation',
align: 'center',
fixed: 'right',
render(_, record) {
return <DeleteOutlined onClick={handleRemove(record.id)} />;
},
},
];
return (
<Collapse
className={styles.dynamicParameterVariable}
defaultActiveKey={['1']}
items={[
{
key: '1',
label: (
<Flex justify={'space-between'}>
<span className={styles.title}>{t('parameter')}</span>
<Button size="small" onClick={handleAdd}>
{t('add')}
</Button>
</Flex>
),
children: (
<Table
dataSource={dataSource}
columns={columns}
rowKey={'id'}
components={components}
rowClassName={() => styles.editableRow}
scroll={{ x: true }}
bordered
/>
),
},
]}
/>
);
};
export default DynamicVariablesForm;

View File

@ -0,0 +1,97 @@
import get from 'lodash/get';
import {
ChangeEventHandler,
MouseEventHandler,
useCallback,
useMemo,
} from 'react';
import { v4 as uuid } from 'uuid';
import { IGenerateParameter, IInvokeVariable } from '../../interface';
import useGraphStore from '../../store';
export const useHandleOperateParameters = (nodeId: string) => {
const { getNode, updateNodeForm } = useGraphStore((state) => state);
const node = getNode(nodeId);
const dataSource: IGenerateParameter[] = useMemo(
() => get(node, 'data.form.variables', []) as IGenerateParameter[],
[node],
);
const changeValue = useCallback(
(row: IInvokeVariable, field: string, value: string) => {
const newData = [...dataSource];
const index = newData.findIndex((item) => row.id === item.id);
const item = newData[index];
newData.splice(index, 1, {
...item,
[field]: value,
});
updateNodeForm(nodeId, { variables: newData });
},
[dataSource, nodeId, updateNodeForm],
);
const handleComponentIdChange = useCallback(
(row: IInvokeVariable) => (value: string) => {
changeValue(row, 'component_id', value);
},
[changeValue],
);
const handleValueChange = useCallback(
(row: IInvokeVariable): ChangeEventHandler<HTMLInputElement> =>
(e) => {
changeValue(row, 'value', e.target.value);
},
[changeValue],
);
const handleRemove = useCallback(
(id?: string) => () => {
const newData = dataSource.filter((item) => item.id !== id);
updateNodeForm(nodeId, { variables: newData });
},
[updateNodeForm, nodeId, dataSource],
);
const handleAdd: MouseEventHandler = useCallback(
(e) => {
e.preventDefault();
e.stopPropagation();
updateNodeForm(nodeId, {
variables: [
...dataSource,
{
id: uuid(),
key: '',
component_id: undefined,
value: '',
},
],
});
},
[dataSource, nodeId, updateNodeForm],
);
const handleSave = (row: IGenerateParameter) => {
const newData = [...dataSource];
const index = newData.findIndex((item) => row.id === item.id);
const item = newData[index];
newData.splice(index, 1, {
...item,
...row,
});
updateNodeForm(nodeId, { variables: newData });
};
return {
handleAdd,
handleRemove,
handleComponentIdChange,
handleValueChange,
handleSave,
dataSource,
};
};

View File

@ -0,0 +1,44 @@
.editableRow {
:global(.editable-cell) {
position: relative;
}
:global(.editable-cell-value-wrap) {
padding: 5px 12px;
cursor: pointer;
height: 30px !important;
}
&:hover {
:global(.editable-cell-value-wrap) {
padding: 4px 11px;
border: 1px solid #d9d9d9;
border-radius: 2px;
}
}
}
.dynamicParameterVariable {
background-color: #ebe9e950;
:global(.ant-collapse-content) {
background-color: #f6f6f634;
}
:global(.ant-collapse-content-box) {
padding: 0 !important;
}
margin-bottom: 20px;
.title {
font-weight: 600;
font-size: 16px;
}
.variableType {
width: 30%;
}
.variableValue {
flex: 1;
}
.addButton {
color: rgb(22, 119, 255);
font-weight: 600;
}
}

View File

@ -0,0 +1,78 @@
import Editor, { loader } from '@monaco-editor/react';
import { Form, Input, InputNumber, Select, Space, Switch } from 'antd';
import { useTranslation } from 'react-i18next';
import { IOperatorForm } from '../../interface';
import DynamicVariablesForm from './dynamic-variables';
loader.config({ paths: { vs: '/vs' } });
enum Method {
GET = 'GET',
POST = 'POST',
PUT = 'PUT',
}
const MethodOptions = [Method.GET, Method.POST, Method.PUT].map((x) => ({
label: x,
value: x,
}));
interface TimeoutInputProps {
value?: number;
onChange?: (value: number | null) => void;
}
const TimeoutInput = ({ value, onChange }: TimeoutInputProps) => {
const { t } = useTranslation();
return (
<Space>
<InputNumber value={value} onChange={onChange} /> {t('common.s')}
</Space>
);
};
const InvokeForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslation();
return (
<>
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<Form.Item name={'url'} label={t('flow.url')}>
<Input />
</Form.Item>
<Form.Item
name={'method'}
label={t('flow.method')}
initialValue={Method.GET}
>
<Select options={MethodOptions} />
</Form.Item>
<Form.Item name={'timeout'} label={t('flow.timeout')}>
<TimeoutInput></TimeoutInput>
</Form.Item>
<Form.Item name={'headers'} label={t('flow.headers')}>
<Editor height={200} defaultLanguage="json" theme="vs-dark" />
</Form.Item>
<Form.Item name={'proxy'} label={t('flow.proxy')}>
<Input />
</Form.Item>
<Form.Item
name={'clean_html'}
label={t('flow.cleanHtml')}
tooltip={t('flow.cleanHtmlTip')}
>
<Switch />
</Form.Item>
<DynamicVariablesForm node={node}></DynamicVariablesForm>
</Form>
</>
);
};
export default InvokeForm;

View File

@ -0,0 +1,94 @@
import { CommaIcon, SemicolonIcon } from '@/assets/icon/Icon';
import { Form, Select } from 'antd';
import {
CornerDownLeft,
IndentIncrease,
Minus,
Slash,
Underline,
} from 'lucide-react';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const optionList = [
{
value: ',',
icon: CommaIcon,
text: 'comma',
},
{
value: '\n',
icon: CornerDownLeft,
text: 'lineBreak',
},
{
value: 'tab',
icon: IndentIncrease,
text: 'tab',
},
{
value: '_',
icon: Underline,
text: 'underline',
},
{
value: '/',
icon: Slash,
text: 'diagonal',
},
{
value: '-',
icon: Minus,
text: 'minus',
},
{
value: ';',
icon: SemicolonIcon,
text: 'semicolon',
},
];
const IterationForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslation();
const options = useMemo(() => {
return optionList.map((x) => {
let Icon = x.icon;
return {
value: x.value,
label: (
<div className="flex items-center gap-2">
<Icon className={'size-4'}></Icon>
{t(`flow.delimiterOptions.${x.text}`)}
</div>
),
};
});
}, [t]);
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<Form.Item
name={['delimiter']}
label={t('knowledgeDetails.delimiter')}
initialValue={`\\n!?;。;!?`}
rules={[{ required: true }]}
tooltip={t('flow.delimiterTip')}
>
<Select options={options}></Select>
</Form.Item>
</Form>
);
};
export default IterationForm;

View File

@ -0,0 +1,145 @@
import { useTranslate } from '@/hooks/common-hooks';
import { Form, Input, Select } from 'antd';
import { useMemo } from 'react';
import {
Jin10CalendarDatashapeOptions,
Jin10CalendarTypeOptions,
Jin10FlashTypeOptions,
Jin10SymbolsDatatypeOptions,
Jin10SymbolsTypeOptions,
Jin10TypeOptions,
} from '../../constant';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const Jin10Form = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
const jin10TypeOptions = useMemo(() => {
return Jin10TypeOptions.map((x) => ({
value: x,
label: t(`jin10TypeOptions.${x}`),
}));
}, [t]);
const jin10FlashTypeOptions = useMemo(() => {
return Jin10FlashTypeOptions.map((x) => ({
value: x,
label: t(`jin10FlashTypeOptions.${x}`),
}));
}, [t]);
const jin10CalendarTypeOptions = useMemo(() => {
return Jin10CalendarTypeOptions.map((x) => ({
value: x,
label: t(`jin10CalendarTypeOptions.${x}`),
}));
}, [t]);
const jin10CalendarDatashapeOptions = useMemo(() => {
return Jin10CalendarDatashapeOptions.map((x) => ({
value: x,
label: t(`jin10CalendarDatashapeOptions.${x}`),
}));
}, [t]);
const jin10SymbolsTypeOptions = useMemo(() => {
return Jin10SymbolsTypeOptions.map((x) => ({
value: x,
label: t(`jin10SymbolsTypeOptions.${x}`),
}));
}, [t]);
const jin10SymbolsDatatypeOptions = useMemo(() => {
return Jin10SymbolsDatatypeOptions.map((x) => ({
value: x,
label: t(`jin10SymbolsDatatypeOptions.${x}`),
}));
}, [t]);
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<Form.Item label={t('type')} name={'type'} initialValue={'flash'}>
<Select options={jin10TypeOptions}></Select>
</Form.Item>
<Form.Item label={t('secretKey')} name={'secret_key'}>
<Input></Input>
</Form.Item>
<Form.Item noStyle dependencies={['type']}>
{({ getFieldValue }) => {
const type = getFieldValue('type');
switch (type) {
case 'flash':
return (
<>
<Form.Item label={t('flashType')} name={'flash_type'}>
<Select options={jin10FlashTypeOptions}></Select>
</Form.Item>
<Form.Item label={t('contain')} name={'contain'}>
<Input></Input>
</Form.Item>
<Form.Item label={t('filter')} name={'filter'}>
<Input></Input>
</Form.Item>
</>
);
case 'calendar':
return (
<>
<Form.Item label={t('calendarType')} name={'calendar_type'}>
<Select options={jin10CalendarTypeOptions}></Select>
</Form.Item>
<Form.Item
label={t('calendarDatashape')}
name={'calendar_datashape'}
>
<Select options={jin10CalendarDatashapeOptions}></Select>
</Form.Item>
</>
);
case 'symbols':
return (
<>
<Form.Item label={t('symbolsType')} name={'symbols_type'}>
<Select options={jin10SymbolsTypeOptions}></Select>
</Form.Item>
<Form.Item
label={t('symbolsDatatype')}
name={'symbols_datatype'}
>
<Select options={jin10SymbolsDatatypeOptions}></Select>
</Form.Item>
</>
);
case 'news':
return (
<>
<Form.Item label={t('contain')} name={'contain'}>
<Input></Input>
</Form.Item>
<Form.Item label={t('filter')} name={'filter'}>
<Input></Input>
</Form.Item>
</>
);
default:
return <></>;
}
}}
</Form.Item>
</Form>
);
};
export default Jin10Form;

View File

@ -0,0 +1,32 @@
import LLMSelect from '@/components/llm-select';
import TopNItem from '@/components/top-n-item';
import { useTranslate } from '@/hooks/common-hooks';
import { Form } from 'antd';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const KeywordExtractForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<Form.Item
name={'llm_id'}
label={t('model', { keyPrefix: 'chat' })}
tooltip={t('modelTip', { keyPrefix: 'chat' })}
>
<LLMSelect></LLMSelect>
</Form.Item>
<TopNItem initialValue={3}></TopNItem>
</Form>
);
};
export default KeywordExtractForm;

View File

@ -0,0 +1,16 @@
.dynamicDeleteButton {
position: relative;
top: 4px;
margin: 0 8px;
color: #999;
font-size: 24px;
cursor: pointer;
transition: all 0.3s;
&:hover {
color: #777;
}
&[disabled] {
cursor: not-allowed;
opacity: 0.5;
}
}

View File

@ -0,0 +1,87 @@
import { useTranslate } from '@/hooks/common-hooks';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, Form, Input } from 'antd';
import { IOperatorForm } from '../../interface';
import styles from './index.less';
const formItemLayout = {
labelCol: {
sm: { span: 6 },
},
wrapperCol: {
sm: { span: 18 },
},
};
const formItemLayoutWithOutLabel = {
wrapperCol: {
sm: { span: 18, offset: 6 },
},
};
const MessageForm = ({ onValuesChange, form }: IOperatorForm) => {
const { t } = useTranslate('flow');
return (
<Form
name="basic"
{...formItemLayoutWithOutLabel}
onValuesChange={onValuesChange}
autoComplete="off"
form={form}
>
<Form.List name="messages">
{(fields, { add, remove }, {}) => (
<>
{fields.map((field, index) => (
<Form.Item
{...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
label={index === 0 ? t('msg') : ''}
required={false}
key={field.key}
>
<Form.Item
{...field}
validateTrigger={['onChange', 'onBlur']}
rules={[
{
required: true,
whitespace: true,
message: t('messageMsg'),
},
]}
noStyle
>
<Input.TextArea
rows={4}
placeholder={t('messagePlaceholder')}
style={{ width: '80%' }}
/>
</Form.Item>
{fields.length > 1 ? (
<MinusCircleOutlined
className={styles.dynamicDeleteButton}
onClick={() => remove(field.name)}
/>
) : null}
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => add()}
style={{ width: '80%' }}
icon={<PlusOutlined />}
>
{t('addMessage')}
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form>
);
};
export default MessageForm;

View File

@ -0,0 +1,32 @@
import TopNItem from '@/components/top-n-item';
import { useTranslate } from '@/hooks/common-hooks';
import { Form, Input } from 'antd';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const PubMedForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<TopNItem initialValue={10}></TopNItem>
<Form.Item
label={t('email')}
name={'email'}
tooltip={t('emailTip')}
rules={[{ type: 'email' }]}
>
<Input></Input>
</Form.Item>
</Form>
);
};
export default PubMedForm;

View File

@ -0,0 +1,88 @@
import { useTranslate } from '@/hooks/common-hooks';
import { Form, Input, Select } from 'antd';
import { useCallback, useMemo } from 'react';
import {
QWeatherLangOptions,
QWeatherTimePeriodOptions,
QWeatherTypeOptions,
QWeatherUserTypeOptions,
} from '../../constant';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const QWeatherForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
const qWeatherLangOptions = useMemo(() => {
return QWeatherLangOptions.map((x) => ({
value: x,
label: t(`qWeatherLangOptions.${x}`),
}));
}, [t]);
const qWeatherTypeOptions = useMemo(() => {
return QWeatherTypeOptions.map((x) => ({
value: x,
label: t(`qWeatherTypeOptions.${x}`),
}));
}, [t]);
const qWeatherUserTypeOptions = useMemo(() => {
return QWeatherUserTypeOptions.map((x) => ({
value: x,
label: t(`qWeatherUserTypeOptions.${x}`),
}));
}, [t]);
const getQWeatherTimePeriodOptions = useCallback(
(userType: string) => {
let options = QWeatherTimePeriodOptions;
if (userType === 'free') {
options = options.slice(0, 3);
}
return options.map((x) => ({
value: x,
label: t(`qWeatherTimePeriodOptions.${x}`),
}));
},
[t],
);
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<Form.Item label={t('webApiKey')} name={'web_apikey'}>
<Input></Input>
</Form.Item>
<Form.Item label={t('lang')} name={'lang'}>
<Select options={qWeatherLangOptions}></Select>
</Form.Item>
<Form.Item label={t('type')} name={'type'}>
<Select options={qWeatherTypeOptions}></Select>
</Form.Item>
<Form.Item label={t('userType')} name={'user_type'}>
<Select options={qWeatherUserTypeOptions}></Select>
</Form.Item>
<Form.Item noStyle dependencies={['type', 'user_type']}>
{({ getFieldValue }) =>
getFieldValue('type') === 'weather' && (
<Form.Item label={t('timePeriod')} name={'time_period'}>
<Select
options={getQWeatherTimePeriodOptions(
getFieldValue('user_type'),
)}
></Select>
</Form.Item>
)
}
</Form.Item>
</Form>
);
};
export default QWeatherForm;

View File

@ -0,0 +1,53 @@
import { Edge } from '@xyflow/react';
import pick from 'lodash/pick';
import { useCallback, useEffect } from 'react';
import { IOperatorForm } from '../../interface';
import useGraphStore from '../../store';
export const useBuildRelevantOptions = () => {
const nodes = useGraphStore((state) => state.nodes);
const buildRelevantOptions = useCallback(
(toList: string[]) => {
return nodes
.filter(
(x) => !toList.some((y) => y === x.id), // filter out selected values in other to fields from the current drop-down box options
)
.map((x) => ({ label: x.data.name, value: x.id }));
},
[nodes],
);
return buildRelevantOptions;
};
const getTargetOfEdge = (edges: Edge[], sourceHandle: string) =>
edges.find((x) => x.sourceHandle === sourceHandle)?.target;
/**
* monitor changes in the connection and synchronize the target to the yes and no fields of the form
* similar to the categorize-form's useHandleFormValuesChange method
* @param param0
*/
export const useWatchConnectionChanges = ({ nodeId, form }: IOperatorForm) => {
const edges = useGraphStore((state) => state.edges);
const getNode = useGraphStore((state) => state.getNode);
const node = getNode(nodeId);
const watchFormChanges = useCallback(() => {
if (node) {
form?.setFieldsValue(pick(node, ['yes', 'no']));
}
}, [node, form]);
const watchConnectionChanges = useCallback(() => {
const edgeList = edges.filter((x) => x.source === nodeId);
const yes = getTargetOfEdge(edgeList, 'yes');
const no = getTargetOfEdge(edgeList, 'no');
form?.setFieldsValue({ yes, no });
}, [edges, nodeId, form]);
useEffect(() => {
watchFormChanges();
}, [watchFormChanges]);
};

View File

@ -0,0 +1,49 @@
import LLMSelect from '@/components/llm-select';
import { useTranslate } from '@/hooks/common-hooks';
import { Form, Select } from 'antd';
import { Operator } from '../../constant';
import { useBuildFormSelectOptions } from '../../form-hooks';
import { IOperatorForm } from '../../interface';
import { useWatchConnectionChanges } from './hooks';
const RelevantForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
const buildRelevantOptions = useBuildFormSelectOptions(
Operator.Relevant,
node?.id,
);
useWatchConnectionChanges({ nodeId: node?.id, form });
return (
<Form
name="basic"
labelCol={{ span: 4 }}
wrapperCol={{ span: 20 }}
onValuesChange={onValuesChange}
autoComplete="off"
form={form}
>
<Form.Item
name={'llm_id'}
label={t('model', { keyPrefix: 'chat' })}
tooltip={t('modelTip', { keyPrefix: 'chat' })}
>
<LLMSelect></LLMSelect>
</Form.Item>
<Form.Item label={t('yes')} name={'yes'}>
<Select
allowClear
options={buildRelevantOptions([form?.getFieldValue('no')])}
/>
</Form.Item>
<Form.Item label={t('no')} name={'no'}>
<Select
allowClear
options={buildRelevantOptions([form?.getFieldValue('yes')])}
/>
</Form.Item>
</Form>
);
};
export default RelevantForm;

View File

@ -0,0 +1,54 @@
import KnowledgeBaseItem from '@/components/knowledge-base-item';
import Rerank from '@/components/rerank';
import SimilaritySlider from '@/components/similarity-slider';
import TopNItem from '@/components/top-n-item';
import { useTranslate } from '@/hooks/common-hooks';
import type { FormProps } from 'antd';
import { Form, Input } from 'antd';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
type FieldType = {
top_n?: number;
};
const onFinish: FormProps<FieldType>['onFinish'] = (values) => {
console.log('Success:', values);
};
const onFinishFailed: FormProps<FieldType>['onFinishFailed'] = (errorInfo) => {
console.log('Failed:', errorInfo);
};
const RetrievalForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
return (
<Form
name="basic"
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
onValuesChange={onValuesChange}
form={form}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<SimilaritySlider
isTooltipShown
vectorSimilarityWeightName="keywords_similarity_weight"
></SimilaritySlider>
<TopNItem></TopNItem>
<Rerank></Rerank>
<KnowledgeBaseItem></KnowledgeBaseItem>
<Form.Item
name={'empty_response'}
label={t('emptyResponse', { keyPrefix: 'chat' })}
tooltip={t('emptyResponseTip', { keyPrefix: 'chat' })}
>
<Input.TextArea placeholder="" rows={4} />
</Form.Item>
</Form>
);
};
export default RetrievalForm;

View File

@ -0,0 +1,41 @@
import LLMSelect from '@/components/llm-select';
import MessageHistoryWindowSizeItem from '@/components/message-history-window-size-item';
import { useTranslate } from '@/hooks/common-hooks';
import { Form, Select } from 'antd';
import { GoogleLanguageOptions } from '../../constant';
import { IOperatorForm } from '../../interface';
const RewriteQuestionForm = ({ onValuesChange, form }: IOperatorForm) => {
const { t } = useTranslate('chat');
return (
<Form
name="basic"
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
onValuesChange={onValuesChange}
autoComplete="off"
form={form}
>
<Form.Item
name={'llm_id'}
label={t('model', { keyPrefix: 'chat' })}
tooltip={t('modelTip', { keyPrefix: 'chat' })}
>
<LLMSelect></LLMSelect>
</Form.Item>
<Form.Item
label={t('language')}
name={'language'}
tooltip={t('languageTip')}
>
<Select options={GoogleLanguageOptions} allowClear={true}></Select>
</Form.Item>
<MessageHistoryWindowSizeItem
initialValue={6}
></MessageHistoryWindowSizeItem>
</Form>
);
};
export default RewriteQuestionForm;

View File

@ -0,0 +1,21 @@
@lightBackgroundColor: rgba(150, 150, 150, 0.07);
@darkBackgroundColor: rgba(150, 150, 150, 0.12);
.caseCard {
background-color: @lightBackgroundColor;
}
.conditionCard {
background-color: @darkBackgroundColor;
}
.elseCase {
background-color: @lightBackgroundColor;
padding: 12px;
border-radius: 8px;
}
.addButton {
color: rgb(22, 119, 255);
font-weight: 600;
}

View File

@ -0,0 +1,204 @@
import { CloseOutlined } from '@ant-design/icons';
import { Button, Card, Divider, Form, Input, Select } from 'antd';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import {
Operator,
SwitchElseTo,
SwitchLogicOperatorOptions,
SwitchOperatorOptions,
} from '../../constant';
import { useBuildFormSelectOptions } from '../../form-hooks';
import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query';
import { IOperatorForm } from '../../interface';
import { getOtherFieldValues } from '../../utils';
import { ISwitchForm } from '@/interfaces/database/flow';
import styles from './index.less';
const SwitchForm = ({ onValuesChange, node, form }: IOperatorForm) => {
const { t } = useTranslation();
const buildCategorizeToOptions = useBuildFormSelectOptions(
Operator.Switch,
node?.id,
);
const getSelectedConditionTos = () => {
const conditions: ISwitchForm['conditions'] =
form?.getFieldValue('conditions');
return conditions?.filter((x) => !!x).map((x) => x?.to) ?? [];
};
const switchOperatorOptions = useMemo(() => {
return SwitchOperatorOptions.map((x) => ({
value: x.value,
label: t(`flow.switchOperatorOptions.${x.label}`),
}));
}, [t]);
const switchLogicOperatorOptions = useMemo(() => {
return SwitchLogicOperatorOptions.map((x) => ({
value: x,
label: t(`flow.switchLogicOperatorOptions.${x}`),
}));
}, [t]);
const componentIdOptions = useBuildComponentIdSelectOptions(
node?.id,
node?.parentId,
);
return (
<Form
form={form}
name="dynamic_form_complex"
autoComplete="off"
initialValues={{ conditions: [{}] }}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<Form.List name="conditions">
{(fields, { add, remove }) => (
<div style={{ display: 'flex', rowGap: 16, flexDirection: 'column' }}>
{fields.map((field) => {
return (
<Card
size="small"
title={`Case ${field.name + 1}`}
key={field.key}
className={styles.caseCard}
extra={
<CloseOutlined
onClick={() => {
remove(field.name);
}}
/>
}
>
<Form.Item noStyle dependencies={[field.name, 'items']}>
{({ getFieldValue }) =>
getFieldValue(['conditions', field.name, 'items'])
?.length > 1 && (
<Form.Item
label={t('flow.logicalOperator')}
name={[field.name, 'logical_operator']}
>
<Select options={switchLogicOperatorOptions} />
</Form.Item>
)
}
</Form.Item>
<Form.Item
label={t('flow.nextStep')}
name={[field.name, 'to']}
>
<Select
allowClear
options={buildCategorizeToOptions([
form?.getFieldValue(SwitchElseTo),
...getOtherFieldValues(
form!,
'conditions',
field,
'to',
),
])}
/>
</Form.Item>
<Form.Item label="Condition">
<Form.List name={[field.name, 'items']}>
{(subFields, subOpt) => (
<div
style={{
display: 'flex',
flexDirection: 'column',
rowGap: 16,
}}
>
{subFields.map((subField) => (
<Card
key={subField.key}
title={null}
size="small"
className={styles.conditionCard}
bordered
extra={
<CloseOutlined
onClick={() => {
subOpt.remove(subField.name);
}}
/>
}
>
<Form.Item
label={t('flow.componentId')}
name={[subField.name, 'cpn_id']}
>
<Select
placeholder={t('flow.componentId')}
options={componentIdOptions}
/>
</Form.Item>
<Form.Item
label={t('flow.operator')}
name={[subField.name, 'operator']}
>
<Select
placeholder={t('flow.operator')}
options={switchOperatorOptions}
/>
</Form.Item>
<Form.Item
label={t('flow.value')}
name={[subField.name, 'value']}
>
<Input placeholder={t('flow.value')} />
</Form.Item>
</Card>
))}
<Button
onClick={() => {
form?.setFieldValue(
['conditions', field.name, 'logical_operator'],
SwitchLogicOperatorOptions[0],
);
subOpt.add({
operator: SwitchOperatorOptions[0].value,
});
}}
block
className={styles.addButton}
>
+ Add Condition
</Button>
</div>
)}
</Form.List>
</Form.Item>
</Card>
);
})}
<Button onClick={() => add()} block className={styles.addButton}>
+ Add Case
</Button>
</div>
)}
</Form.List>
<Divider />
<Form.Item
label={'ELSE'}
name={[SwitchElseTo]}
className={styles.elseCase}
>
<Select
allowClear
options={buildCategorizeToOptions(getSelectedConditionTos())}
/>
</Form.Item>
</Form>
);
};
export default SwitchForm;

View File

@ -0,0 +1,24 @@
import { PromptEditor } from '@/components/prompt-editor';
import { Form } from 'antd';
import { useTranslation } from 'react-i18next';
import { IOperatorForm } from '../../interface';
const TemplateForm = ({ onValuesChange, form }: IOperatorForm) => {
const { t } = useTranslation();
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<Form.Item name={['content']} label={t('flow.content')}>
<PromptEditor></PromptEditor>
</Form.Item>
</Form>
);
};
export default TemplateForm;

View File

@ -0,0 +1,83 @@
import { useTranslate } from '@/hooks/common-hooks';
import { DatePicker, DatePickerProps, Form, Input, Select } from 'antd';
import dayjs from 'dayjs';
import { useCallback, useMemo } from 'react';
import { TuShareSrcOptions } from '../../constant';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const DateTimePicker = ({
onChange,
value,
}: {
onChange?: (val: number | undefined) => void;
value?: number | undefined;
}) => {
const handleChange: DatePickerProps['onChange'] = useCallback(
(val: any) => {
const nextVal = val?.format('YYYY-MM-DD HH:mm:ss');
onChange?.(nextVal ? nextVal : undefined);
},
[onChange],
);
// The value needs to be converted into a string and saved to the backend
const nextValue = useMemo(() => {
if (value) {
return dayjs(value);
}
return undefined;
}, [value]);
return (
<DatePicker
showTime
format="YYYY-MM-DD HH:mm:ss"
onChange={handleChange}
value={nextValue}
/>
);
};
const TuShareForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
const tuShareSrcOptions = useMemo(() => {
return TuShareSrcOptions.map((x) => ({
value: x,
label: t(`tuShareSrcOptions.${x}`),
}));
}, [t]);
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<Form.Item
label={t('token')}
name={'token'}
tooltip={'Get from https://tushare.pro/'}
>
<Input></Input>
</Form.Item>
<Form.Item label={t('src')} name={'src'}>
<Select options={tuShareSrcOptions}></Select>
</Form.Item>
<Form.Item label={t('startDate')} name={'start_date'}>
<DateTimePicker />
</Form.Item>
<Form.Item label={t('endDate')} name={'end_date'}>
<DateTimePicker />
</Form.Item>
<Form.Item label={t('keyword')} name={'keyword'}>
<Input></Input>
</Form.Item>
</Form>
);
};
export default TuShareForm;

View File

@ -0,0 +1,36 @@
import TopNItem from '@/components/top-n-item';
import { useTranslate } from '@/hooks/common-hooks';
import { Form, Select } from 'antd';
import { useMemo } from 'react';
import { WenCaiQueryTypeOptions } from '../../constant';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const WenCaiForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
const wenCaiQueryTypeOptions = useMemo(() => {
return WenCaiQueryTypeOptions.map((x) => ({
value: x,
label: t(`wenCaiQueryTypeOptions.${x}`),
}));
}, [t]);
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<TopNItem initialValue={20} max={99}></TopNItem>
<Form.Item label={t('queryType')} name={'query_type'}>
<Select options={wenCaiQueryTypeOptions}></Select>
</Form.Item>
</Form>
);
};
export default WenCaiForm;

View File

@ -0,0 +1,28 @@
import TopNItem from '@/components/top-n-item';
import { useTranslate } from '@/hooks/common-hooks';
import { Form, Select } from 'antd';
import { LanguageOptions } from '../../constant';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const WikipediaForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('common');
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<TopNItem initialValue={10}></TopNItem>
<Form.Item label={t('language')} name={'language'}>
<Select options={LanguageOptions}></Select>
</Form.Item>
</Form>
);
};
export default WikipediaForm;

View File

@ -0,0 +1,40 @@
import { useTranslate } from '@/hooks/common-hooks';
import { Form, Switch } from 'antd';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
const YahooFinanceForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<Form.Item label={t('info')} name={'info'}>
<Switch></Switch>
</Form.Item>
<Form.Item label={t('history')} name={'history'}>
<Switch></Switch>
</Form.Item>
<Form.Item label={t('financials')} name={'financials'}>
<Switch></Switch>
</Form.Item>
<Form.Item label={t('balanceSheet')} name={'balance_sheet'}>
<Switch></Switch>
</Form.Item>
<Form.Item label={t('cashFlowStatement')} name={'cash_flow_statement'}>
<Switch></Switch>
</Form.Item>
<Form.Item label={t('news')} name={'news'}>
<Switch></Switch>
</Form.Item>
</Form>
);
};
export default YahooFinanceForm;