This commit is contained in:
Archer
2023-11-28 19:28:46 +08:00
committed by GitHub
parent e765c3bf95
commit a74e1d7166
75 changed files with 1139 additions and 417 deletions

View File

@@ -0,0 +1,152 @@
import React, { useMemo, useState } from 'react';
import { Box, Button, ModalBody, ModalFooter, Textarea } from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import MySlider from '@/components/Slider';
import MyTooltip from '@/components/MyTooltip';
import MyModal from '@/components/MyModal';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constant';
import { useTranslation } from 'next-i18next';
import { reRankModelList } from '@/web/common/system/staticData';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constant';
import MyRadio from '@/components/Radio';
type DatasetParamsProps = {
similarity?: number;
limit?: number;
searchMode: `${DatasetSearchModeEnum}`;
searchEmptyText?: string;
};
const DatasetParamsModal = ({
searchEmptyText,
limit,
similarity,
searchMode = DatasetSearchModeEnum.embedding,
onClose,
onSuccess
}: DatasetParamsProps & { onClose: () => void; onSuccess: (e: DatasetParamsProps) => void }) => {
const { t } = useTranslation();
const [refresh, setRefresh] = useState(false);
const { register, setValue, getValues, handleSubmit } = useForm<DatasetParamsProps>({
defaultValues: {
searchEmptyText,
limit,
similarity,
searchMode
}
});
const searchModeList = useMemo(() => {
const list = Object.values(DatasetSearchModeMap);
if (reRankModelList.length > 0) {
return list;
}
return list.slice(0, 1);
}, []);
return (
<MyModal
isOpen={true}
onClose={onClose}
iconSrc="/imgs/modal/params.svg"
title={'搜索参数调整'}
minW={['90vw', '500px']}
h={['90vh', 'auto']}
overflow={'unset'}
isCentered={searchEmptyText !== undefined}
>
<ModalBody flex={['1 0 0', 'auto']} overflow={'auto'}>
<MyRadio
gridGap={2}
gridTemplateColumns={'repeat(1,1fr)'}
list={searchModeList}
value={getValues('searchMode')}
onChange={(e) => {
setValue('searchMode', e as `${DatasetSearchModeEnum}`);
setRefresh(!refresh);
}}
/>
{similarity !== undefined && (
<Box display={['block', 'flex']} py={8} mt={3}>
<Box flex={'0 0 100px'} mb={[8, 0]}>
{t('core.dataset.search.Min Similarity')}
<MyTooltip label={t('core.dataset.search.Min Similarity Tips')} forceShow>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Box>
<MySlider
markList={[
{ label: '0', value: 0 },
{ label: '1', value: 1 }
]}
min={0}
max={1}
step={0.01}
value={getValues(ModuleInputKeyEnum.datasetSimilarity) || 0.5}
onChange={(val) => {
setValue(ModuleInputKeyEnum.datasetSimilarity, val);
setRefresh(!refresh);
}}
/>
</Box>
)}
{limit !== undefined && (
<Box display={['block', 'flex']} py={8}>
<Box flex={'0 0 100px'} mb={[8, 0]}>
{t('core.dataset.search.Top K')}
</Box>
<Box flex={1}>
<MySlider
markList={[
{ label: '1', value: 1 },
{ label: '30', value: 30 }
]}
min={1}
max={30}
value={getValues(ModuleInputKeyEnum.datasetLimit) || 5}
onChange={(val) => {
setValue(ModuleInputKeyEnum.datasetLimit, val);
setRefresh(!refresh);
}}
/>
</Box>
</Box>
)}
{searchEmptyText !== undefined && (
<Box display={['block', 'flex']} pt={3}>
<Box flex={'0 0 100px'} mb={[2, 0]}>
{t('core.dataset.search.Empty result response')}
</Box>
<Box flex={1}>
<Textarea
rows={5}
maxLength={500}
placeholder={t('core.dataset.search.Empty result response Tips')}
{...register('searchEmptyText')}
></Textarea>
</Box>
</Box>
)}
</ModalBody>
<ModalFooter>
<Button variant={'base'} mr={3} onClick={onClose}>
{t('common.Close')}
</Button>
<Button
onClick={() => {
onClose();
handleSubmit(onSuccess)();
}}
>
{t('common.Done')}
</Button>
</ModalFooter>
</MyModal>
);
};
export default DatasetParamsModal;

View File

@@ -22,7 +22,7 @@ import MySlider from '@/components/Slider';
import MyTooltip from '@/components/MyTooltip';
import MyModal from '@/components/MyModal';
import MyIcon from '@/components/Icon';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant';
import { DatasetSearchModeEnum, DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant';
import { useTranslation } from 'next-i18next';
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { feConfigs } from '@/web/common/system/staticData';
@@ -30,9 +30,6 @@ import DatasetSelectContainer, { useDatasetSelect } from '@/components/core/data
import { useLoading } from '@/web/common/hooks/useLoading';
import EmptyTip from '@/components/EmptyTip';
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
type DatasetParamsProps = AppSimpleEditFormType['dataset'];
export const DatasetSelectModal = ({
isOpen,
@@ -222,127 +219,4 @@ export const DatasetSelectModal = ({
);
};
export const DatasetParamsModal = ({
searchEmptyText,
limit,
similarity,
rerank,
onClose,
onChange
}: DatasetParamsProps & { onClose: () => void; onChange: (e: DatasetParamsProps) => void }) => {
const [refresh, setRefresh] = useState(false);
const { register, setValue, getValues, handleSubmit } = useForm<DatasetParamsProps>({
defaultValues: {
searchEmptyText,
limit,
similarity,
rerank
}
});
return (
<MyModal
isOpen={true}
onClose={onClose}
iconSrc="/imgs/modal/params.svg"
title={'搜索参数调整'}
minW={['90vw', '600px']}
>
<Flex flexDirection={'column'}>
<ModalBody>
{feConfigs?.isPlus && (
<Box display={['block', 'flex']} py={5} pt={[0, 5]}>
<Box flex={'0 0 100px'} mb={[8, 0]}>
<MyTooltip label={'将召回的结果进行进一步重排,可增加召回率'} forceShow>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Box>
<Switch
size={'lg'}
isChecked={getValues(ModuleInputKeyEnum.datasetStartReRank)}
onChange={(e) => {
setValue(ModuleInputKeyEnum.datasetStartReRank, e.target.checked);
setRefresh(!refresh);
}}
/>
</Box>
)}
<Box display={['block', 'flex']} py={5} pt={[0, 5]}>
<Box flex={'0 0 100px'} mb={[8, 0]}>
<MyTooltip
label={'不同索引模型的相似度有区别,请通过搜索测试来选择合适的数值'}
forceShow
>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Box>
<MySlider
markList={[
{ label: '0', value: 0 },
{ label: '1', value: 1 }
]}
min={0}
max={1}
step={0.01}
value={getValues(ModuleInputKeyEnum.datasetSimilarity)}
onChange={(val) => {
setValue(ModuleInputKeyEnum.datasetSimilarity, val);
setRefresh(!refresh);
}}
/>
</Box>
<Box display={['block', 'flex']} py={8}>
<Box flex={'0 0 100px'} mb={[8, 0]}>
</Box>
<Box flex={1}>
<MySlider
markList={[
{ label: '1', value: 1 },
{ label: '20', value: 20 }
]}
min={1}
max={20}
value={getValues(ModuleInputKeyEnum.datasetLimit)}
onChange={(val) => {
setValue(ModuleInputKeyEnum.datasetLimit, val);
setRefresh(!refresh);
}}
/>
</Box>
</Box>
<Box display={['block', 'flex']} pt={3}>
<Box flex={'0 0 100px'} mb={[2, 0]}>
</Box>
<Box flex={1}>
<Textarea
rows={5}
maxLength={500}
placeholder={`若填写该内容,没有搜索到对应内容时,将直接回复填写的内容。\n为了连贯上下文${feConfigs?.systemTitle} 会取部分上一个聊天的搜索记录作为补充,因此在连续对话时,该功能可能会失效。`}
{...register('searchEmptyText')}
></Textarea>
</Box>
</Box>
</ModalBody>
<ModalFooter>
<Button variant={'base'} mr={3} onClick={onClose}>
</Button>
<Button
onClick={() => {
onClose();
handleSubmit(onChange)();
}}
>
</Button>
</ModalFooter>
</Flex>
</MyModal>
);
};
export default DatasetSelectModal;

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import {
Box,
Button,
@@ -54,13 +54,18 @@ const VariableEdit = ({
const VariableTypeList = [
{
label: t('core.module.variable.text type'),
icon: 'settingLight',
label: t('core.module.variable.input type'),
icon: 'core/app/variable/input',
key: VariableInputEnum.input
},
{
label: t('core.module.variable.textarea type'),
icon: 'core/app/variable/textarea',
key: VariableInputEnum.textarea
},
{
label: t('core.module.variable.select type'),
icon: 'settingLight',
icon: 'core/app/variable/select',
key: VariableInputEnum.select
}
];
@@ -94,6 +99,13 @@ const VariableEdit = ({
}
};
const formatVariables = useMemo(() => {
return variables.map((item) => ({
...item,
icon: VariableTypeList.find((type) => type.key === item.type)?.icon
}));
}, [variables]);
return (
<Box>
<Flex alignItems={'center'}>
@@ -114,12 +126,13 @@ const VariableEdit = ({
+&ensp;{t('common.Add New')}
</Flex>
</Flex>
{variables.length > 0 && (
{formatVariables.length > 0 && (
<Box mt={2} borderRadius={'lg'} overflow={'hidden'} borderWidth={'1px'} borderBottom="none">
<TableContainer>
<Table bg={'white'}>
<Thead>
<Tr>
<Th w={'18px !important'} p={0} />
<Th>{t('core.module.variable.variable name')}</Th>
<Th>{t('core.module.variable.key')}</Th>
<Th>{t('common.Require Input')}</Th>
@@ -127,9 +140,12 @@ const VariableEdit = ({
</Tr>
</Thead>
<Tbody>
{variables.map((item, index) => (
{formatVariables.map((item) => (
<Tr key={item.id}>
<Td>{item.label} </Td>
<Td textAlign={'center'} p={0} pl={3}>
<MyIcon name={item.icon as any} w={'14px'} color={'myGray.500'} />
</Td>
<Td>{item.label}</Td>
<Td>{item.key}</Td>
<Td>{item.required ? '✔' : ''}</Td>
<Td>
@@ -190,18 +206,21 @@ const VariableEdit = ({
<Box mt={5} mb={2}>
{t('core.module.Field Type')}
</Box>
<Grid gridTemplateColumns={'repeat(2,130px)'} gridGap={4}>
<Grid gridTemplateColumns={'repeat(3,1fr)'} gridGap={4}>
{VariableTypeList.map((item) => (
<Flex
key={item.key}
px={4}
py={1}
px={3}
py={3}
border={theme.borders.base}
borderRadius={'md'}
cursor={'pointer'}
{...(item.key === getValuesEdit('variable.type')
? {
bg: 'myWhite.600'
bg: 'myBlue.100',
borderColor: 'myBlue.600',
color: 'myBlue.600',
fontWeight: 'bold'
}
: {
_hover: {
@@ -214,7 +233,7 @@ const VariableEdit = ({
})}
>
<MyIcon name={item.icon as any} w={'16px'} />
<Box ml={3}>{item.label}</Box>
<Box ml={2}>{item.label}</Box>
</Flex>
))}
</Grid>
@@ -225,14 +244,14 @@ const VariableEdit = ({
{t('core.module.variable.text max length')}
</Box>
<Box>
<NumberInput max={100} min={1} step={1} position={'relative'}>
<NumberInput max={500} min={1} step={1} position={'relative'}>
<NumberInputField
{...registerEdit('variable.maxLen', {
min: 1,
max: 100,
max: 500,
valueAsNumber: true
})}
max={100}
max={500}
/>
<NumberInputStepper>
<NumberIncrementStepper />
@@ -258,16 +277,18 @@ const VariableEdit = ({
})}
/>
</FormControl>
<MyIcon
ml={3}
name={'delete'}
w={'16px'}
cursor={'pointer'}
p={2}
borderRadius={'lg'}
_hover={{ bg: 'red.100' }}
onClick={() => removeEnums(i)}
/>
{selectEnums.length > 1 && (
<MyIcon
ml={3}
name={'delete'}
w={'16px'}
cursor={'pointer'}
p={2}
borderRadius={'lg'}
_hover={{ bg: 'red.100' }}
onClick={() => removeEnums(i)}
/>
)}
</Flex>
))}
</Box>

View File

@@ -1,4 +1,4 @@
import React, { useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import type { SelectAppItemType } from '@fastgpt/global/core/module/type';
import type { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
import {
@@ -36,11 +36,13 @@ import type { SelectedDatasetType } from '@fastgpt/global/core/module/api.d';
import { useQuery } from '@tanstack/react-query';
import type { EditFieldModeType, EditFieldType } from '../modules/FieldEditModal';
import { feConfigs } from '@/web/common/system/staticData';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constant';
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
const SelectAppModal = dynamic(() => import('../../SelectAppModal'));
const AIChatSettingsModal = dynamic(() => import('../../../AIChatSettingsModal'));
const DatasetSelectModal = dynamic(() => import('../../../DatasetSelectModal'));
const DatasetParamsModal = dynamic(() => import('../../../DatasetParamsModal'));
export const Label = React.memo(function Label({
moduleId,
@@ -232,6 +234,9 @@ const RenderInput = ({
{item.type === FlowNodeInputTypeEnum.selectDataset && (
<SelectDatasetRender item={item} moduleId={moduleId} />
)}
{item.type === FlowNodeInputTypeEnum.selectDatasetParamsModal && (
<SelectDatasetParamsRender item={item} inputs={sortInputs} moduleId={moduleId} />
)}
{item.type === FlowNodeInputTypeEnum.custom && CustomComponent[item.key] && (
<>{CustomComponent[item.key]({ ...item })}</>
)}
@@ -251,7 +256,7 @@ type RenderProps = {
moduleId: string;
};
var NumberInputRender = React.memo(function NumberInputRender({ item, moduleId }: RenderProps) {
const NumberInputRender = React.memo(function NumberInputRender({ item, moduleId }: RenderProps) {
return (
<NumberInput
defaultValue={item.value}
@@ -278,7 +283,7 @@ var NumberInputRender = React.memo(function NumberInputRender({ item, moduleId }
);
});
var TextInputRender = React.memo(function TextInputRender({ item, moduleId }: RenderProps) {
const TextInputRender = React.memo(function TextInputRender({ item, moduleId }: RenderProps) {
return (
<Input
placeholder={item.placeholder}
@@ -298,7 +303,7 @@ var TextInputRender = React.memo(function TextInputRender({ item, moduleId }: Re
);
});
var SwitchRender = React.memo(function SwitchRender({ item, moduleId }: RenderProps) {
const SwitchRender = React.memo(function SwitchRender({ item, moduleId }: RenderProps) {
return (
<Switch
size={'lg'}
@@ -318,7 +323,7 @@ var SwitchRender = React.memo(function SwitchRender({ item, moduleId }: RenderPr
);
});
var TextareaRender = React.memo(function TextareaRender({ item, moduleId }: RenderProps) {
const TextareaRender = React.memo(function TextareaRender({ item, moduleId }: RenderProps) {
return (
<Textarea
rows={5}
@@ -340,7 +345,7 @@ var TextareaRender = React.memo(function TextareaRender({ item, moduleId }: Rend
);
});
var SelectRender = React.memo(function SelectRender({ item, moduleId }: RenderProps) {
const SelectRender = React.memo(function SelectRender({ item, moduleId }: RenderProps) {
return (
<MySelect
width={'100%'}
@@ -361,7 +366,7 @@ var SelectRender = React.memo(function SelectRender({ item, moduleId }: RenderPr
);
});
var SliderRender = React.memo(function SliderRender({ item, moduleId }: RenderProps) {
const SliderRender = React.memo(function SliderRender({ item, moduleId }: RenderProps) {
return (
<Box pt={5} pb={4} px={2}>
<MySlider
@@ -387,7 +392,7 @@ var SliderRender = React.memo(function SliderRender({ item, moduleId }: RenderPr
);
});
var AISetting = React.memo(function AISetting({ inputs = [], moduleId }: RenderProps) {
const AISetting = React.memo(function AISetting({ inputs = [], moduleId }: RenderProps) {
const { t } = useTranslation();
const chatModulesData = useMemo(() => {
const obj: Record<string, any> = {};
@@ -440,15 +445,15 @@ var AISetting = React.memo(function AISetting({ inputs = [], moduleId }: RenderP
);
});
var SelectChatModelRender = React.memo(function SelectChatModelRender({
const SelectChatModelRender = React.memo(function SelectChatModelRender({
inputs = [],
item,
moduleId
}: RenderProps) {
const modelList = chatModelList || [];
function onChangeModel(e: string) {
{
const onChangeModel = useCallback(
(e: string) => {
onChangeNode({
moduleId,
type: 'updateInput',
@@ -477,8 +482,9 @@ var SelectChatModelRender = React.memo(function SelectChatModelRender({
value: model.maxResponse / 2
}
});
}
}
},
[inputs, item, modelList, moduleId]
);
const list = modelList.map((item) => {
const priceStr = `(${formatPrice(item.price, 1000)}元/1k Tokens)`;
@@ -489,9 +495,11 @@ var SelectChatModelRender = React.memo(function SelectChatModelRender({
};
});
if (!item.value && list.length > 0) {
onChangeModel(list[0].value);
}
useEffect(() => {
if (!item.value && list.length > 0) {
onChangeModel(list[0].value);
}
}, [item.value, list, onChangeModel]);
return (
<MySelect
@@ -504,7 +512,10 @@ var SelectChatModelRender = React.memo(function SelectChatModelRender({
);
});
var SelectDatasetRender = React.memo(function SelectDatasetRender({ item, moduleId }: RenderProps) {
const SelectDatasetRender = React.memo(function SelectDatasetRender({
item,
moduleId
}: RenderProps) {
const theme = useTheme();
const { mode } = useFlowProviderStore();
const { allDatasets, loadAllDatasets } = useDatasetStore();
@@ -572,7 +583,7 @@ var SelectDatasetRender = React.memo(function SelectDatasetRender({ item, module
);
});
var SelectAppRender = React.memo(function SelectAppRender({ item, moduleId }: RenderProps) {
const SelectAppRender = React.memo(function SelectAppRender({ item, moduleId }: RenderProps) {
const { filterAppIds } = useFlowProviderStore();
const theme = useTheme();
@@ -622,3 +633,63 @@ var SelectAppRender = React.memo(function SelectAppRender({ item, moduleId }: Re
</>
);
});
const SelectDatasetParamsRender = React.memo(function SelectDatasetParamsRender({
inputs = [],
moduleId
}: RenderProps) {
const { t } = useTranslation();
const [data, setData] = useState({
searchMode: DatasetSearchModeEnum.embedding,
limit: 5,
similarity: 0.5
});
const { isOpen, onOpen, onClose } = useDisclosure();
useEffect(() => {
inputs.forEach((input) => {
// @ts-ignore
if (data[input.key] !== undefined) {
setData((state) => ({
...state,
[input.key]: input.value
}));
}
});
}, [inputs]);
return (
<>
<Button
variant={'base'}
leftIcon={<MyIcon name={'settingLight'} w={'14px'} />}
onClick={onOpen}
>
{t('core.dataset.search.Params Setting')}
</Button>
{isOpen && (
<DatasetParamsModal
{...data}
onClose={onClose}
onSuccess={(e) => {
for (let key in e) {
const item = inputs.find((input) => input.key === key);
if (!item) continue;
onChangeNode({
moduleId,
type: 'updateInput',
key,
value: {
...item,
//@ts-ignore
value: e[key]
}
});
}
}}
/>
)}
</>
);
});