4.6.8 supplement (#831)

Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer
2024-02-15 12:26:02 +08:00
committed by GitHub
parent 51bbdf26a3
commit 91bcf8c53e
200 changed files with 4387 additions and 2749 deletions

View File

@@ -248,12 +248,21 @@ const ResponseBox = React.memo(function ResponseBox({
{/* http */}
<>
{activeModule?.body && (
{activeModule?.headers && (
<Row
label={t('core.chat.response.module http body')}
value={`~~~json\n${JSON.stringify(activeModule?.body, null, 2)}`}
label={'Headers'}
value={`~~~json\n${JSON.stringify(activeModule?.headers, null, 2)}`}
/>
)}
{activeModule?.params && (
<Row
label={'Params'}
value={`~~~json\n${JSON.stringify(activeModule?.params, null, 2)}`}
/>
)}
{activeModule?.body && (
<Row label={'Body'} value={`~~~json\n${JSON.stringify(activeModule?.body, null, 2)}`} />
)}
{activeModule?.httpResult && (
<Row
label={t('core.chat.response.module http result')}

View File

@@ -26,25 +26,20 @@ const MdImage = ({ src }: { src?: string }) => {
};
return (
<Skeleton
minH="100px"
isLoaded={!isLoading}
fadeDuration={2}
display={'flex'}
justifyContent={'center'}
my={1}
>
<>
<Image
display={'inline-block'}
borderRadius={'md'}
src={src}
alt={''}
fallbackSrc={'/imgs/errImg.png'}
fallbackStrategy={'onError'}
cursor={succeed ? 'pointer' : 'default'}
loading="eager"
loading="lazy"
objectFit={'contain'}
referrerPolicy="no-referrer"
minW={'120px'}
minH={'120px'}
my={1}
onLoad={() => {
setIsLoading(false);
setSucceed(true);
@@ -74,7 +69,7 @@ const MdImage = ({ src }: { src?: string }) => {
</ModalContent>
<ModalCloseButton bg={'myWhite.500'} zIndex={999999} />
</Modal>
</Skeleton>
</>
);
};

View File

@@ -99,7 +99,7 @@ const Code = React.memo(function Code(e: any) {
{children}
</CodeLight>
);
}, [codeType, className, inline, match, strChildren]);
}, [codeType, className, inline, match, children, strChildren]);
return Component;
});

View File

@@ -0,0 +1,85 @@
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { Flex, Table, Thead, Tbody, Tr, Th, Td, TableContainer } from '@chakra-ui/react';
import {
DatasetSearchModeEnum,
DatasetSearchModeMap
} from '@fastgpt/global/core/dataset/constants';
import { useTranslation } from 'next-i18next';
import React, { useMemo } from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon';
const SearchParamsTip = ({
searchMode,
similarity = 0,
limit = 1500,
responseEmptyText,
usingReRank = false,
usingQueryExtension = false
}: {
searchMode: `${DatasetSearchModeEnum}`;
similarity?: number;
limit?: number;
responseEmptyText?: string;
usingReRank?: boolean;
usingQueryExtension?: boolean;
}) => {
const { t } = useTranslation();
const { reRankModelList } = useSystemStore();
const hasReRankModel = reRankModelList.length > 0;
const hasEmptyResponseMode = responseEmptyText !== undefined;
const hasSimilarityMode = usingReRank || searchMode === DatasetSearchModeEnum.embedding;
return (
<TableContainer
bg={'primary.50'}
borderRadius={'lg'}
borderWidth={'1px'}
borderColor={'primary.1'}
>
<Table fontSize={'xs'} overflow={'overlay'}>
<Thead>
<Tr color={'myGray.600'}>
<Th>{t('core.dataset.search.search mode')}</Th>
<Th>{t('core.dataset.search.Max Tokens')}</Th>
<Th>{t('core.dataset.search.Min Similarity')}</Th>
{hasReRankModel && <Th>{t('core.dataset.search.ReRank')}</Th>}
<Th>{t('core.module.template.Query extension')}</Th>
{hasEmptyResponseMode && <Th>{t('core.dataset.search.Empty result response')}</Th>}
</Tr>
</Thead>
<Tbody>
<Tr color={'myGray.800'}>
<Td pt={0} pb={1}>
<Flex alignItems={'center'}>
<MyIcon
name={DatasetSearchModeMap[searchMode]?.icon as any}
w={'12px'}
mr={'1px'}
/>
{t(DatasetSearchModeMap[searchMode]?.title)}
</Flex>
</Td>
<Td pt={0} pb={1}>
{limit}
</Td>
<Td pt={0} pb={1}>
{hasSimilarityMode ? similarity : t('core.dataset.search.Nonsupport')}
</Td>
{hasReRankModel && (
<Td pt={0} pb={1}>
{usingReRank ? '✅' : '❌'}
</Td>
)}
<Td pt={0} pb={1}>
{usingQueryExtension ? '✅' : '❌'}
</Td>
{hasEmptyResponseMode && <Th>{responseEmptyText !== '' ? '✅' : '❌'}</Th>}
</Tr>
</Tbody>
</Table>
</TableContainer>
);
};
export default React.memo(SearchParamsTip);

View File

@@ -37,7 +37,7 @@ export type DatasetParamsProps = {
datasetSearchExtensionModel?: string;
datasetSearchExtensionBg?: string;
maxTokens?: number;
maxTokens?: number; // limit max tokens
searchEmptyText?: string;
};
enum SearchSettingTabEnum {

View File

@@ -129,7 +129,7 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowModuleItemType>) =>
/>
{/* render dataset select */}
{RenderQuoteList}
<Flex position={'absolute'} right={4} top={'50%'} transform={'translate(0,-50%)'}>
<Flex position={'absolute'} right={4} top={'60%'}>
<Box>{t('core.module.Dataset quote.Concat result')}</Box>
<SourceHandle
handleKey={ModuleOutputKeyEnum.datasetQuoteQA}

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useCallback, useMemo, useState, useTransition } from 'react';
import { NodeProps } from 'reactflow';
import NodeCard from '../render/NodeCard';
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
@@ -6,15 +6,528 @@ import Divider from '../modules/Divider';
import Container from '../modules/Container';
import RenderInput from '../render/RenderInput';
import RenderOutput from '../render/RenderOutput';
import {
Box,
Flex,
Input,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer
} from '@chakra-ui/react';
import MySelect from '@/components/Select';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { onChangeNode, useFlowProviderStore } from '../../FlowProvider';
import { useTranslation } from 'next-i18next';
import Tabs from '@/components/Tabs';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useForm } from 'react-hook-form';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import JSONEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
import {
formatEditorVariablePickerIcon,
getGuideModule,
splitGuideModule
} from '@fastgpt/global/core/module/utils';
import { EditorVariablePickerType } from '@fastgpt/web/components/common/Textarea/PromptEditor/type';
enum TabEnum {
params = 'params',
headers = 'headers',
body = 'body'
}
type PropsArrType = {
key: string;
type: string;
value: string;
};
const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
moduleId,
inputs
}: {
moduleId: string;
inputs: FlowNodeInputItemType[];
}) {
const { t } = useTranslation();
const { toast } = useToast();
const [_, startSts] = useTransition();
const requestMethods = inputs.find((item) => item.key === ModuleInputKeyEnum.httpMethod);
const requestUrl = inputs.find((item) => item.key === ModuleInputKeyEnum.httpReqUrl);
const onChangeUrl = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
onChangeNode({
moduleId,
type: 'updateInput',
key: ModuleInputKeyEnum.httpReqUrl,
value: {
...requestUrl,
value: e.target.value
}
});
},
[moduleId, requestUrl]
);
const onBlurUrl = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const val = e.target.value;
// 拆分params和url
const url = val.split('?')[0];
const params = val.split('?')[1];
if (params) {
const paramsArr = params.split('&');
const paramsObj = paramsArr.reduce((acc, cur) => {
const [key, value] = cur.split('=');
return {
...acc,
[key]: value
};
}, {});
const inputParams = inputs.find((item) => item.key === ModuleInputKeyEnum.httpParams);
if (!inputParams || Object.keys(paramsObj).length === 0) return;
const concatParams: PropsArrType[] = inputParams?.value || [];
Object.entries(paramsObj).forEach(([key, value]) => {
if (!concatParams.find((item) => item.key === key)) {
concatParams.push({ key, value: value as string, type: 'string' });
}
});
onChangeNode({
moduleId,
type: 'updateInput',
key: ModuleInputKeyEnum.httpParams,
value: {
...inputParams,
value: concatParams
}
});
onChangeNode({
moduleId,
type: 'updateInput',
key: ModuleInputKeyEnum.httpReqUrl,
value: {
...requestUrl,
value: url
}
});
toast({
status: 'success',
title: t('core.module.http.Url and params have been split')
});
}
},
[inputs, moduleId, requestUrl, t, toast]
);
return (
<Box>
<Box mb={2}>{t('core.module.Http request settings')}</Box>
<Flex alignItems={'center'} className="nodrag">
<MySelect
h={'34px'}
w={'80px'}
bg={'myGray.50'}
width={'100%'}
value={requestMethods?.value}
list={[
{
label: 'GET',
value: 'GET'
},
{
label: 'POST',
value: 'POST'
}
]}
onchange={(e) => {
onChangeNode({
moduleId,
type: 'updateInput',
key: ModuleInputKeyEnum.httpMethod,
value: {
...requestMethods,
value: e
}
});
}}
/>
<Input
ml={2}
h={'34px'}
value={requestUrl?.value}
placeholder={t('core.module.input.label.Http Request Url')}
fontSize={'xs'}
w={'350px'}
onChange={onChangeUrl}
onBlur={onBlurUrl}
/>
</Flex>
</Box>
);
});
const defaultForm = {
key: '',
value: ''
};
function RenderHttpProps({
moduleId,
inputs
}: {
moduleId: string;
inputs: FlowNodeInputItemType[];
}) {
const { t } = useTranslation();
const [selectedTab, setSelectedTab] = useState(TabEnum.params);
const { nodes } = useFlowProviderStore();
const requestMethods = inputs.find((item) => item.key === ModuleInputKeyEnum.httpMethod)?.value;
const params = inputs.find((item) => item.key === ModuleInputKeyEnum.httpParams);
const headers = inputs.find((item) => item.key === ModuleInputKeyEnum.httpHeaders);
const jsonBody = inputs.find((item) => item.key === ModuleInputKeyEnum.httpJsonBody);
const paramsLength = params?.value?.length || 0;
const headersLength = headers?.value?.length || 0;
// get variable
const variables = useMemo(() => {
const globalVariables = formatEditorVariablePickerIcon(
splitGuideModule(getGuideModule(nodes.map((node) => node.data)))?.variableModules || []
);
const systemVariables = [
{
key: 'appId',
label: t('core.module.http.AppId')
},
{
key: 'chatId',
label: t('core.module.http.ChatId')
},
{
key: 'responseChatItemId',
label: t('core.module.http.ResponseChatItemId')
},
{
key: 'variables',
label: t('core.module.http.Variables')
},
{
key: 'histories',
label: t('core.module.http.Histories')
},
{
key: 'cTime',
label: t('core.module.http.Current time')
}
];
const moduleVariables = formatEditorVariablePickerIcon(
inputs
.filter((input) => input.edit)
.map((item) => ({
key: item.key,
label: item.label
}))
);
return [...moduleVariables, ...globalVariables, ...systemVariables];
}, [inputs, nodes, t]);
const variableText = useMemo(() => {
return variables
.map((item) => `${item.key}${item.key !== item.label ? `(${item.label})` : ''}`)
.join('\n');
}, [variables]);
return (
<Box>
<Flex alignItems={'center'} mb={2}>
{t('core.module.Http request props')}
<MyTooltip label={t('core.module.http.Props tip', { variable: variableText })}>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Flex>
<Tabs
list={[
{ label: <RenderPropsItem text="Params" num={paramsLength} />, id: TabEnum.params },
...(requestMethods === 'POST'
? [
{
label: (
<Flex alignItems={'center'}>
Body
{jsonBody?.value && <Box ml={1}></Box>}
</Flex>
),
id: TabEnum.body
}
]
: []),
{ label: <RenderPropsItem text="Headers" num={headersLength} />, id: TabEnum.headers }
]}
activeId={selectedTab}
onChange={(e) => setSelectedTab(e as any)}
/>
{params &&
headers &&
jsonBody &&
{
[TabEnum.params]: <RenderForm moduleId={moduleId} input={params} variables={variables} />,
[TabEnum.body]: <RenderJson moduleId={moduleId} variables={variables} input={jsonBody} />,
[TabEnum.headers]: (
<RenderForm moduleId={moduleId} input={headers} variables={variables} />
)
}[selectedTab]}
</Box>
);
}
const RenderForm = ({
moduleId,
input,
variables
}: {
moduleId: string;
input: FlowNodeInputItemType;
variables: EditorVariablePickerType[];
}) => {
const { t } = useTranslation();
const { toast } = useToast();
const [_, startSts] = useTransition();
const { register, reset, handleSubmit } = useForm({
defaultValues: defaultForm
});
const list = useMemo(() => (input.value || []) as PropsArrType[], [input.value]);
const addNewProps = useCallback(
({ key, value }: { key: string; value: string }) => {
const checkExist = list.find((item) => item.key === key);
if (checkExist) {
return toast({
status: 'warning',
title: t('core.module.http.Key already exists')
});
}
if (!key) return;
onChangeNode({
moduleId,
type: 'updateInput',
key: input.key,
value: {
...input,
value: [...list, { key, type: 'string', value }]
}
});
reset(defaultForm);
},
[input, list, moduleId, reset, t, toast]
);
return (
<TableContainer>
<Table>
<Thead>
<Tr>
<Th px={2}>{t('core.module.http.Props name')}</Th>
<Th px={2}>{t('core.module.http.Props value')}</Th>
</Tr>
</Thead>
<Tbody>
{list.map((item, index) => (
<Tr key={`${input.key}${index}`}>
<Td p={0} w={'150px'}>
<Input
w={'150px'}
defaultValue={item.key}
variant={'unstyled'}
paddingLeft={2}
placeholder={t('core.module.http.Props name')}
onBlur={(e) => {
const val = e.target.value;
if (!val) {
return toast({
status: 'warning',
title: t('core.module.http.Key cannot be empty')
});
}
const checkExist = list.find((item, i) => i !== index && item.key == val);
if (checkExist) {
return toast({
status: 'warning',
title: t('core.module.http.Key already exists')
});
}
startSts(() => {
onChangeNode({
moduleId,
type: 'updateInput',
key: input.key,
value: {
...input,
value: list.map((item, i) => (i === index ? { ...item, key: val } : item))
}
});
});
}}
/>
</Td>
<Td p={0} display={'flex'} alignItems={'center'}>
<Input
flex={'1 0 0'}
w={'150px'}
defaultValue={item.value}
variant={'unstyled'}
paddingLeft={2}
placeholder={t('core.module.http.Props value')}
onBlur={(e) => {
const val = e.target.value;
startSts(() => {
onChangeNode({
moduleId,
type: 'updateInput',
key: input.key,
value: {
...input,
value: list.map((item, i) =>
i === index ? { ...item, value: val } : item
)
}
});
});
}}
/>
<MyIcon
name={'delete'}
cursor={'pointer'}
_hover={{ color: 'red.600' }}
w={'14px'}
onClick={(e) => {
e.stopPropagation();
onChangeNode({
moduleId,
type: 'updateInput',
key: input.key,
value: {
...input,
value: list.filter((val) => val.key !== item.key)
}
});
}}
/>
</Td>
</Tr>
))}
<Tr>
<Td p={0} w={'150px'}>
<Input
w={'150px'}
variant={'unstyled'}
paddingLeft={2}
placeholder={t('core.module.http.Add props')}
{...register('key', {
onBlur: handleSubmit(addNewProps)
})}
/>
</Td>
<Td p={0}>
<Input variant={'unstyled'} paddingLeft={2} {...register('value')} />
</Td>
</Tr>
</Tbody>
</Table>
</TableContainer>
);
};
const RenderJson = ({
moduleId,
input,
variables
}: {
moduleId: string;
input: FlowNodeInputItemType;
variables: EditorVariablePickerType[];
}) => {
const [_, startSts] = useTransition();
return (
<Box mt={1}>
<JSONEditor
bg={'myGray.50'}
height={200}
resize
value={input.value}
onChange={(e) => {
startSts(() => {
onChangeNode({
moduleId,
type: 'updateInput',
key: input.key,
value: {
...input,
value: e
}
});
});
}}
variables={variables}
/>
</Box>
);
};
const RenderPropsItem = ({ text, num }: { text: string; num: number }) => {
return (
<Flex alignItems={'center'} fontSize={'xs'} transform={'scale(0.8)'}>
<Box>{text}</Box>
{num > 0 && (
<Box ml={1} borderRadius={'50%'} bg={'myGray.200'} px={2} py={'1px'}>
{num}
</Box>
)}
</Flex>
);
};
const NodeHttp = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
const { moduleId, inputs, outputs } = data;
const CustomComponents = useMemo(
() => ({
[ModuleInputKeyEnum.httpMethod]: () => (
<RenderHttpMethodAndUrl moduleId={moduleId} inputs={inputs} />
),
[ModuleInputKeyEnum.httpHeaders]: () => (
<>
<RenderHttpProps moduleId={moduleId} inputs={inputs} />
<Box mt={2} transform={'translateY(10px)'}>
</Box>
</>
)
}),
[inputs, moduleId]
);
return (
<NodeCard minW={'350px'} selected={selected} {...data}>
<Divider text="Input" />
<Container>
<RenderInput moduleId={moduleId} flowInputList={inputs} />
<RenderInput
moduleId={moduleId}
flowInputList={inputs}
CustomComponent={CustomComponents}
/>
</Container>
<Divider text="Output" />
<Container>

View File

@@ -135,38 +135,38 @@ const NodeCard = (props: Props) => {
boxShadow: '4'
}}
>
<Flex className="custom-drag-handle" px={4} py={3} alignItems={'center'}>
<Avatar src={avatar} borderRadius={'0'} objectFit={'contain'} w={'30px'} h={'30px'} />
<Box ml={3} fontSize={'lg'} color={'myGray.600'}>
{t(name)}
<Box className="custom-drag-handle" px={4} py={3}>
<Flex alignItems={'center'}>
<Avatar src={avatar} borderRadius={'0'} objectFit={'contain'} w={'30px'} h={'30px'} />
<Box ml={3} fontSize={'lg'}>
{t(name)}
</Box>
<Box flex={1} />
{!forbidMenu && (
<MyMenu
offset={[-60, 5]}
width={120}
Button={
<MenuButton
className={'nodrag'}
_hover={{ bg: 'myWhite.600' }}
cursor={'pointer'}
borderRadius={'md'}
onClick={(e) => {
e.stopPropagation();
}}
>
<MyIcon name={'more'} w={'14px'} p={2} />
</MenuButton>
}
menuList={menuList}
/>
)}
</Flex>
<Box fontSize={'xs'} color={'myGray.600'}>
{t(intro)}
</Box>
{intro && (
<MyTooltip label={t(intro)} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} mb={'1px'} ml={1} />
</MyTooltip>
)}
<Box flex={1} />
{!forbidMenu && (
<MyMenu
offset={[-60, 5]}
width={120}
Button={
<MenuButton
className={'nodrag'}
_hover={{ bg: 'myWhite.600' }}
cursor={'pointer'}
borderRadius={'md'}
onClick={(e) => {
e.stopPropagation();
}}
>
<MyIcon name={'more'} w={'14px'} p={2} />
</MenuButton>
}
menuList={menuList}
/>
)}
</Flex>
</Box>
{children}
<EditTitleModal />
<ConfirmModal />

View File

@@ -45,13 +45,19 @@ const JsonEditor = ({ inputs = [], item, moduleId }: RenderInputProps) => {
[item, moduleId]
);
const value = useMemo(() => {
if (typeof item.value === 'string') {
return item.value;
}
return JSON.stringify(item.value, null, 2);
}, [item.value]);
return (
<JSONEditor
title={t(item.label)}
bg={'myWhite.400'}
bg={'myGray.50'}
placeholder={t(item.placeholder || '')}
resize
value={item.value}
value={value}
onChange={(e) => {
update(e);
}}

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useMemo, useState } from 'react';
import type { RenderInputProps } from '../type';
import { onChangeNode, useFlowProviderStore } from '../../../../FlowProvider';
import { Button, useDisclosure } from '@chakra-ui/react';
import { Box, Button, Flex, useDisclosure } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
@@ -11,6 +11,7 @@ import DatasetParamsModal, {
DatasetParamsProps
} from '@/components/core/module/DatasetParamsModal';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import SearchParamsTip from '@/components/core/dataset/SearchParamsTip';
const SelectDatasetParam = ({ inputs = [], moduleId }: RenderInputProps) => {
const { nodes } = useFlowProviderStore();
@@ -61,42 +62,60 @@ const SelectDatasetParam = ({ inputs = [], moduleId }: RenderInputProps) => {
const Render = useMemo(() => {
return (
<>
<Button
variant={'whitePrimary'}
leftIcon={<MyIcon name={'common/settingLight'} w={'14px'} />}
onClick={onOpen}
>
{/* label */}
<Flex alignItems={'center'} mb={3}>
{t('core.dataset.search.Params Setting')}
</Button>
{isOpen && (
<DatasetParamsModal
{...data}
maxTokens={tokenLimit}
onClose={onClose}
onSuccess={(e) => {
setData(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]
}
});
}
<MyIcon
name={'common/settingLight'}
ml={2}
w={'16px'}
cursor={'pointer'}
_hover={{
color: 'primary.600'
}}
onClick={onOpen}
/>
)}
</Flex>
<SearchParamsTip
searchMode={data.searchMode}
similarity={data.similarity}
limit={data.limit}
usingReRank={data.usingReRank}
usingQueryExtension={data.datasetSearchUsingExtensionQuery}
/>
</>
);
}, [data, inputs, isOpen, moduleId, onClose, onOpen, t, tokenLimit]);
}, [data, onOpen, t]);
return Render;
return (
<>
{Render}
{isOpen && (
<DatasetParamsModal
{...data}
maxTokens={tokenLimit}
onClose={onClose}
onSuccess={(e) => {
setData(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]
}
});
}
}}
/>
)}
</>
);
};
export default React.memo(SelectDatasetParam);

View File

@@ -26,9 +26,15 @@ const TextareaRender = ({ inputs = [], item, moduleId }: RenderInputProps) => {
label: item.label
}))
);
const systemVariables = [
{
key: 'cTime',
label: t('core.module.http.Current time')
}
];
return [...globalVariables, ...moduleVariables];
}, [inputs, nodes]);
return [...globalVariables, ...moduleVariables, ...systemVariables];
}, [inputs, nodes, t]);
const onChange = useCallback(
(e: string) => {

View File

@@ -1,5 +1,5 @@
import React, { useMemo } from 'react';
import ReactFlow, { Background, Controls, ReactFlowProvider } from 'reactflow';
import React, { useCallback, useMemo } from 'react';
import ReactFlow, { Background, Connection, Controls, ReactFlowProvider } from 'reactflow';
import { Box, Flex, IconButton, useDisclosure } from '@chakra-ui/react';
import { SmallCloseIcon } from '@chakra-ui/icons';
import { EDGE_TYPE, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
@@ -11,6 +11,8 @@ import ModuleTemplateList, { type ModuleTemplateProps } from './ModuleTemplateLi
import { useFlowProviderStore } from './FlowProvider';
import 'reactflow/dist/style.css';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useTranslation } from 'next-i18next';
const NodeSimple = dynamic(() => import('./components/nodes/NodeSimple'));
const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = {
@@ -25,7 +27,8 @@ const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = {
[FlowNodeTypeEnum.answerNode]: dynamic(() => import('./components/nodes/NodeAnswer')),
[FlowNodeTypeEnum.classifyQuestion]: dynamic(() => import('./components/nodes/NodeCQNode')),
[FlowNodeTypeEnum.contentExtract]: dynamic(() => import('./components/nodes/NodeExtract')),
[FlowNodeTypeEnum.httpRequest]: dynamic(() => import('./components/nodes/NodeHttp')),
[FlowNodeTypeEnum.httpRequest468]: dynamic(() => import('./components/nodes/NodeHttp')),
[FlowNodeTypeEnum.httpRequest]: NodeSimple,
[FlowNodeTypeEnum.runApp]: NodeSimple,
[FlowNodeTypeEnum.pluginInput]: dynamic(() => import('./components/nodes/NodePluginInput')),
[FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./components/nodes/NodePluginOutput')),
@@ -37,6 +40,8 @@ const edgeTypes = {
};
const Container = React.memo(function Container() {
const { toast } = useToast();
const { t } = useTranslation();
const { reactFlowWrapper, nodes, onNodesChange, edges, onEdgesChange, onConnect } =
useFlowProviderStore();
@@ -50,6 +55,24 @@ const Container = React.memo(function Container() {
[]
);
const customOnConnect = useCallback(
(connect: Connection) => {
if (!connect.sourceHandle || !connect.targetHandle) {
return;
}
if (connect.source === connect.target) {
return toast({
status: 'warning',
title: t('core.module.Can not connect self')
});
}
onConnect({
connect
});
},
[onConnect, t, toast]
);
return (
<ReactFlow
ref={reactFlowWrapper}
@@ -68,13 +91,7 @@ const Container = React.memo(function Container() {
edgeTypes={edgeTypes}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={(connect) => {
connect.sourceHandle &&
connect.targetHandle &&
onConnect({
connect
});
}}
onConnect={customOnConnect}
>
{memoRenderTools}
</ReactFlow>

View File

@@ -14,7 +14,7 @@ export const flowNode2Modules = ({
const modules: ModuleItemType[] = nodes.map((item) => ({
moduleId: item.data.moduleId,
name: item.data.name,
// avatar: item.data.avatar,
avatar: item.data.avatar,
flowType: item.data.flowType,
showStatus: item.data.showStatus,
position: item.position,

View File

@@ -66,4 +66,5 @@ export type SearchTestResponse = {
searchMode: `${DatasetSearchModeEnum}`;
usingReRank: boolean;
similarity: number;
usingQueryExtension: boolean;
};

View File

@@ -71,7 +71,6 @@ const BillTable = () => {
})),
[members]
);
console.log(members);
const {
data: bills,

View File

@@ -94,6 +94,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
data: {
list: searchRes,
duration: `${((Date.now() - start) / 1000).toFixed(3)}s`,
usingQueryExtension: !!aiExtensionResult,
...result
}
});

View File

@@ -10,9 +10,7 @@ type Props = HttpBodyType<{
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const {
data: { input, rule = '' }
} = req.body as Props;
const { input, rule = '' } = req.body as Props;
await authRequestFromLocal({ req });

View File

@@ -5,6 +5,9 @@ import { addCustomFeedbacks } from '@fastgpt/service/core/chat/controller';
import { authRequestFromLocal } from '@fastgpt/service/support/permission/auth/common';
type Props = HttpBodyType<{
appId: string;
chatId?: string;
responseChatItemId?: string;
defaultFeedback: string;
customFeedback: string;
}>;
@@ -15,7 +18,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
appId,
chatId,
responseChatItemId: chatItemId,
data: { defaultFeedback, customFeedback }
defaultFeedback,
customFeedback
} = req.body as Props;
await authRequestFromLocal({ req });

View File

@@ -12,7 +12,8 @@ type Props = HttpBodyType<{
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const {
data: { text, ...obj }
text,
DYNAMIC_INPUT_KEY: { ...obj }
} = req.body as Props;
await authRequestFromLocal({ req });

View File

@@ -26,7 +26,6 @@ import { useAppStore } from '@/web/core/app/store/useAppStore';
import { postForm2Modules } from '@/web/core/app/utils';
import dynamic from 'next/dynamic';
import MySelect from '@/components/Select';
import MyTooltip from '@/components/MyTooltip';
import Avatar from '@/components/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
@@ -36,6 +35,7 @@ import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
import SelectAiModel from '@/components/Select/SelectAiModel';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/module/utils';
import SearchParamsTip from '@/components/core/dataset/SearchParamsTip';
const DatasetSelectModal = dynamic(() => import('@/components/core/module/DatasetSelectModal'));
const DatasetParamsModal = dynamic(() => import('@/components/core/module/DatasetParamsModal'));
@@ -113,11 +113,6 @@ const EditForm = ({
return llmModelList.find((item) => item.model === selectLLMModel)?.quoteMaxToken || 3000;
}, [selectLLMModel, llmModelList]);
const datasetSearchMode = useMemo(() => {
if (!searchMode) return '';
return t(DatasetSearchModeMap[searchMode]?.title);
}, [searchMode, t]);
const { mutate: onSubmitSave, isLoading: isSaving } = useRequest({
mutationFn: async (data: AppSimpleEditFormType) => {
const modules = await postForm2Modules(data);
@@ -308,23 +303,16 @@ const EditForm = ({
</Flex>
</Flex>
{getValues('dataset.datasets').length > 0 && (
<Flex mt={1} color={'myGray.600'} fontSize={'sm'} mb={2}>
{t('core.dataset.search.search mode')}: {datasetSearchMode}
{', '}
{reRankModelList.length > 0 && (
<>
{t('core.dataset.search.ReRank')}:{' '}
{getValues('dataset.usingReRank') ? '✅' : '✖'}
</>
)}
{', '}
{t('core.dataset.search.Min Similarity')}: {getValues('dataset.similarity')}
{', '}
{t('core.dataset.search.Max Tokens')}: {getValues('dataset.limit')}
{getValues('dataset.searchEmptyText') === ''
? ''
: t('core.dataset.Set Empty Result Tip')}
</Flex>
<Box my={3}>
<SearchParamsTip
searchMode={searchMode}
similarity={getValues('dataset.similarity')}
limit={getValues('dataset.limit')}
usingReRank={getValues('dataset.usingReRank')}
usingQueryExtension={getValues('dataset.datasetSearchUsingExtensionQuery')}
responseEmptyText={getValues('dataset.searchEmptyText')}
/>
</Box>
)}
<Grid
gridTemplateColumns={['repeat(2, minmax(0, 1fr))', 'repeat(3, minmax(0, 1fr))']}

View File

@@ -120,43 +120,39 @@ const Info = ({ datasetId }: { datasetId: string }) => {
</Box>
<Input flex={[1, '0 0 300px']} maxLength={30} {...register('name')} />
</Flex>
{vectorModelList.length > 1 && (
<Flex mt={8} w={'100%'} alignItems={'center'}>
<Box flex={['0 0 90px', '0 0 160px']} w={0}>
{t('core.ai.model.Vector Model')}
</Box>
<Box flex={[1, '0 0 300px']}>{getValues('vectorModel').name}</Box>
</Flex>
)}
<Flex mt={8} w={'100%'} alignItems={'center'}>
<Box flex={['0 0 90px', '0 0 160px']} w={0}>
{t('core.ai.model.Vector Model')}
</Box>
<Box flex={[1, '0 0 300px']}>{getValues('vectorModel').name}</Box>
</Flex>
<Flex mt={8} w={'100%'} alignItems={'center'}>
<Box flex={['0 0 90px', '0 0 160px']} w={0}>
{t('core.Max Token')}
</Box>
<Box flex={[1, '0 0 300px']}>{getValues('vectorModel').maxToken}</Box>
</Flex>
{datasetModelList.length > 1 && (
<Flex mt={6} alignItems={'center'}>
<Box flex={['0 0 90px', '0 0 160px']} w={0}>
{t('core.ai.model.Dataset Agent Model')}
</Box>
<Box flex={[1, '0 0 300px']}>
<MySelect
w={'100%'}
value={getValues('agentModel').model}
list={datasetModelList.map((item) => ({
label: item.name,
value: item.model
}))}
onchange={(e) => {
const agentModel = datasetModelList.find((item) => item.model === e);
if (!agentModel) return;
setValue('agentModel', agentModel);
setRefresh((state) => !state);
}}
/>
</Box>
</Flex>
)}
<Flex mt={6} alignItems={'center'}>
<Box flex={['0 0 90px', '0 0 160px']} w={0}>
{t('core.ai.model.Dataset Agent Model')}
</Box>
<Box flex={[1, '0 0 300px']}>
<MySelect
w={'100%'}
value={getValues('agentModel').model}
list={datasetModelList.map((item) => ({
label: item.name,
value: item.model
}))}
onchange={(e) => {
const agentModel = datasetModelList.find((item) => item.model === e);
if (!agentModel) return;
setValue('agentModel', agentModel);
setRefresh((state) => !state);
}}
/>
</Box>
</Flex>
<Flex mt={8} alignItems={'center'} w={'100%'}>
<Box flex={['0 0 90px', '0 0 160px']}>{t('common.Intro')}</Box>

View File

@@ -41,6 +41,7 @@ import { delay } from '@fastgpt/global/common/system/utils';
import QuoteItem from '@/components/core/dataset/QuoteItem';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import SearchParamsTip from '@/components/core/dataset/SearchParamsTip';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
@@ -121,7 +122,8 @@ const Test = ({ datasetId }: { datasetId: string }) => {
searchMode: res.searchMode,
usingReRank: res.usingReRank,
limit: res.limit,
similarity: res.similarity
similarity: res.similarity,
usingQueryExtension: res.usingQueryExtension
};
pushDatasetTestItem(testItem);
setDatasetTestItem(testItem);
@@ -477,41 +479,15 @@ const TestResults = React.memo(function TestResults({
<MyIcon name={'common/paramsLight'} w={'18px'} mr={2} />
{t('core.dataset.test.Test params')}
</Flex>
<TableContainer
mt={3}
bg={'primary.50'}
borderRadius={'lg'}
borderWidth={'1px'}
borderColor={'primary.1'}
>
<Table>
<Thead>
<Tr color={'myGray.600'}>
<Th>{t('core.dataset.search.search mode')}</Th>
<Th>{t('core.dataset.search.ReRank')}</Th>
<Th>{t('core.dataset.search.Max Tokens')}</Th>
<Th>{t('core.dataset.search.Min Similarity')}</Th>
</Tr>
</Thead>
<Tbody>
<Tr color={'myGray.800'}>
<Td pt={0}>
<Flex alignItems={'center'}>
<MyIcon
name={DatasetSearchModeMap[datasetTestItem.searchMode]?.icon as any}
w={'12px'}
mr={'1px'}
/>
{t(DatasetSearchModeMap[datasetTestItem.searchMode]?.title)}
</Flex>
</Td>
<Td pt={0}>{datasetTestItem.usingReRank ? '✅' : '❌'}</Td>
<Td pt={0}>{datasetTestItem.limit}</Td>
<Td pt={0}>{datasetTestItem.similarity}</Td>
</Tr>
</Tbody>
</Table>
</TableContainer>
<Box mt={3}>
<SearchParamsTip
searchMode={datasetTestItem.searchMode}
similarity={datasetTestItem.similarity}
limit={datasetTestItem.limit}
usingReRank={datasetTestItem.usingReRank}
usingQueryExtension={datasetTestItem.usingQueryExtension}
/>
</Box>
<Flex mt={5} mb={3} alignItems={'center'}>
<Flex fontSize={'xl'} color={'myGray.900'} alignItems={'center'}>

View File

@@ -310,6 +310,24 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
let usingSimilarityFilter = false;
/* function */
const countRecallLimit = () => {
if (searchMode === DatasetSearchModeEnum.embedding) {
return {
embeddingLimit: 100,
fullTextLimit: 0
};
}
if (searchMode === DatasetSearchModeEnum.fullTextRecall) {
return {
embeddingLimit: 0,
fullTextLimit: 100
};
}
return {
embeddingLimit: 60,
fullTextLimit: 40
};
};
const embeddingRecall = async ({ query, limit }: { query: string; limit: number }) => {
const { vectors, charsLength } = await getVectorsByText({
model: getVectorModel(model),
@@ -555,8 +573,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
/* main step */
// count limit
const embeddingLimit = 60;
const fullTextLimit = 40;
const { embeddingLimit, fullTextLimit } = countRecallLimit();
// recall
const { embeddingRecallResults, fullTextRecallResults, charsLength } = await multiQueryRecall({

View File

@@ -21,6 +21,7 @@ import { dispatchAnswer } from './tools/answer';
import { dispatchClassifyQuestion } from './agent/classifyQuestion';
import { dispatchContentExtract } from './agent/extract';
import { dispatchHttpRequest } from './tools/http';
import { dispatchHttp468Request } from './tools/http468';
import { dispatchAppRequest } from './tools/runApp';
import { dispatchCFR } from './tools/cfr';
import { dispatchRunPlugin } from './plugin/run';
@@ -38,6 +39,7 @@ const callbackMap: Record<`${FlowNodeTypeEnum}`, Function> = {
[FlowNodeTypeEnum.classifyQuestion]: dispatchClassifyQuestion,
[FlowNodeTypeEnum.contentExtract]: dispatchContentExtract,
[FlowNodeTypeEnum.httpRequest]: dispatchHttpRequest,
[FlowNodeTypeEnum.httpRequest468]: dispatchHttp468Request,
[FlowNodeTypeEnum.runApp]: dispatchAppRequest,
[FlowNodeTypeEnum.pluginModule]: dispatchRunPlugin,
[FlowNodeTypeEnum.pluginInput]: dispatchPluginInput,

View File

@@ -23,7 +23,7 @@ export const dispatchAnswer = (props: Record<string, any>): AnswerResponse => {
if (stream) {
responseWrite({
res,
event: detail ? sseResponseEventEnum.answer : undefined,
event: detail ? sseResponseEventEnum.response : undefined,
data: textAdaptGptResponse({
text: `\n${formatText}`
})
@@ -31,6 +31,6 @@ export const dispatchAnswer = (props: Record<string, any>): AnswerResponse => {
}
return {
answerText: formatText
[ModuleOutputKeyEnum.answerText]: formatText
};
};

View File

@@ -1,23 +1,37 @@
import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import {
DYNAMIC_INPUT_KEY,
ModuleInputKeyEnum,
ModuleOutputKeyEnum
} from '@fastgpt/global/core/module/constants';
import axios from 'axios';
import { flatDynamicParams, valueTypeFormat } from '../utils';
import { valueTypeFormat } from '../utils';
import { SERVICE_LOCAL_HOST } from '@fastgpt/service/common/system/tools';
export type HttpRequestProps = ModuleDispatchProps<{
type HttpRequestProps = ModuleDispatchProps<{
[ModuleInputKeyEnum.abandon_httpUrl]: string;
[ModuleInputKeyEnum.httpMethod]: string;
[ModuleInputKeyEnum.httpReqUrl]: string;
[ModuleInputKeyEnum.httpHeader]: string;
[ModuleInputKeyEnum.httpHeaders]: string;
[key: string]: any;
}>;
export type HttpResponse = {
type HttpResponse = {
[ModuleOutputKeyEnum.failed]?: boolean;
[ModuleOutputKeyEnum.responseData]: moduleDispatchResType;
[key: string]: any;
};
const flatDynamicParams = (params: Record<string, any>) => {
const dynamicParams = params[DYNAMIC_INPUT_KEY];
if (!dynamicParams) return params;
return {
...params,
...dynamicParams,
[DYNAMIC_INPUT_KEY]: undefined
};
};
export const dispatchHttpRequest = async (props: HttpRequestProps): Promise<HttpResponse> => {
let {
appId,

View File

@@ -0,0 +1,280 @@
import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
import {
DYNAMIC_INPUT_KEY,
ModuleInputKeyEnum,
ModuleOutputKeyEnum
} from '@fastgpt/global/core/module/constants';
import axios from 'axios';
import { valueTypeFormat } from '../utils';
import { SERVICE_LOCAL_HOST } from '@fastgpt/service/common/system/tools';
type PropsArrType = {
key: string;
type: string;
value: string;
};
type HttpRequestProps = ModuleDispatchProps<{
[ModuleInputKeyEnum.abandon_httpUrl]: string;
[ModuleInputKeyEnum.httpMethod]: string;
[ModuleInputKeyEnum.httpReqUrl]: string;
[ModuleInputKeyEnum.httpHeaders]: PropsArrType[];
[ModuleInputKeyEnum.httpParams]: PropsArrType[];
[ModuleInputKeyEnum.httpJsonBody]: string;
[DYNAMIC_INPUT_KEY]: Record<string, any>;
[key: string]: any;
}>;
type HttpResponse = {
[ModuleOutputKeyEnum.failed]?: boolean;
[ModuleOutputKeyEnum.responseData]: moduleDispatchResType;
[key: string]: any;
};
const UNDEFINED_SIGN = 'UNDEFINED_SIGN';
export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<HttpResponse> => {
let {
appId,
chatId,
responseChatItemId,
variables,
outputs,
histories,
params: {
system_httpMethod: httpMethod = 'POST',
system_httpReqUrl: httpReqUrl,
system_httpHeader: httpHeader,
system_httpParams: httpParams = [],
system_httpJsonBody: httpJsonBody,
[DYNAMIC_INPUT_KEY]: dynamicInput,
...body
}
} = props;
if (!httpReqUrl) {
return Promise.reject('Http url is empty');
}
const concatVariables = {
appId,
chatId,
responseChatItemId,
variables,
histories: histories.slice(0, 10),
...body
};
// parse header
const headers = await (() => {
try {
if (!httpHeader || httpHeader.length === 0) return {};
// array
return httpHeader.reduce((acc, item) => {
item.key = replaceVariable(item.key, concatVariables);
item.value = replaceVariable(item.value, concatVariables);
// @ts-ignore
acc[item.key] = valueTypeFormat(item.value, 'string');
return acc;
}, {});
} catch (error) {
return Promise.reject('Header 为非法 JSON 格式');
}
})();
const params = httpParams.reduce((acc, item) => {
item.key = replaceVariable(item.key, concatVariables);
item.value = replaceVariable(item.value, concatVariables);
// @ts-ignore
acc[item.key] = valueTypeFormat(item.value, 'string');
return acc;
}, {});
const requestBody = await (() => {
if (!httpJsonBody) return { [DYNAMIC_INPUT_KEY]: dynamicInput };
httpJsonBody = replaceVariable(httpJsonBody, concatVariables);
try {
const jsonParse = JSON.parse(httpJsonBody);
const removeSignJson = removeUndefinedSign(jsonParse);
return { [DYNAMIC_INPUT_KEY]: dynamicInput, ...removeSignJson };
} catch (error) {
console.log(error);
return Promise.reject(`Invalid JSON body: ${httpJsonBody}`);
}
})();
// console.log(params, requestBody, headers);
try {
const { formatResponse, rawResponse } = await fetchData({
method: httpMethod,
url: httpReqUrl,
headers,
body: requestBody,
params
});
// format output value type
const results: Record<string, any> = {};
for (const key in formatResponse) {
const output = outputs.find((item) => item.key === key);
if (!output) continue;
results[key] = valueTypeFormat(formatResponse[key], output.valueType);
}
return {
responseData: {
price: 0,
params: Object.keys(params).length > 0 ? params : undefined,
body: Object.keys(requestBody).length > 0 ? requestBody : undefined,
headers: Object.keys(headers).length > 0 ? headers : undefined,
httpResult: rawResponse
},
...results
};
} catch (error) {
return {
[ModuleOutputKeyEnum.failed]: true,
responseData: {
price: 0,
params: Object.keys(params).length > 0 ? params : undefined,
body: Object.keys(requestBody).length > 0 ? requestBody : undefined,
headers: Object.keys(headers).length > 0 ? headers : undefined,
httpResult: { error }
}
};
}
};
async function fetchData({
method,
url,
headers,
body,
params
}: {
method: string;
url: string;
headers: Record<string, any>;
body: Record<string, any>;
params: Record<string, any>;
}): Promise<Record<string, any>> {
const { data: response } = await axios<Record<string, any>>({
method,
baseURL: `http://${SERVICE_LOCAL_HOST}`,
url,
headers: {
'Content-Type': 'application/json',
...headers
},
params: params,
data: method === 'POST' ? body : {}
});
/*
parse the json:
{
user: {
name: 'xxx',
age: 12
},
list: [
{
name: 'xxx',
age: 50
},
[{ test: 22 }]
],
psw: 'xxx'
}
result: {
'user': { name: 'xxx', age: 12 },
'user.name': 'xxx',
'user.age': 12,
'list': [ { name: 'xxx', age: 50 }, [ [Object] ] ],
'list[0]': { name: 'xxx', age: 50 },
'list[0].name': 'xxx',
'list[0].age': 50,
'list[1]': [ { test: 22 } ],
'list[1][0]': { test: 22 },
'list[1][0].test': 22,
'psw': 'xxx'
}
*/
const parseJson = (obj: Record<string, any>, prefix = '') => {
let result: Record<string, any> = {};
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
result[`${prefix}[${i}]`] = obj[i];
if (Array.isArray(obj[i])) {
result = {
...result,
...parseJson(obj[i], `${prefix}[${i}]`)
};
} else if (typeof obj[i] === 'object') {
result = {
...result,
...parseJson(obj[i], `${prefix}[${i}].`)
};
}
}
} else if (typeof obj == 'object') {
for (const key in obj) {
result[`${prefix}${key}`] = obj[key];
if (Array.isArray(obj[key])) {
result = {
...result,
...parseJson(obj[key], `${prefix}${key}`)
};
} else if (typeof obj[key] === 'object') {
result = {
...result,
...parseJson(obj[key], `${prefix}${key}.`)
};
}
}
}
return result;
};
return {
formatResponse: parseJson(response),
rawResponse: response
};
}
function replaceVariable(text: string, obj: Record<string, any>) {
for (const [key, value] of Object.entries(obj)) {
if (value === undefined) {
text = text.replace(new RegExp(`{{${key}}}`, 'g'), UNDEFINED_SIGN);
} else {
const replacement = JSON.stringify(value);
const unquotedReplacement =
replacement.startsWith('"') && replacement.endsWith('"')
? replacement.slice(1, -1)
: replacement;
text = text.replace(new RegExp(`{{${key}}}`, 'g'), unquotedReplacement);
}
}
return text || '';
}
function removeUndefinedSign(obj: Record<string, any>) {
for (const key in obj) {
if (obj[key] === UNDEFINED_SIGN) {
obj[key] = undefined;
} else if (Array.isArray(obj[key])) {
obj[key] = obj[key].map((item: any) => {
if (item === UNDEFINED_SIGN) {
return undefined;
} else if (typeof item === 'object') {
removeUndefinedSign(item);
}
return item;
});
} else if (typeof obj[key] === 'object') {
removeUndefinedSign(obj[key]);
}
}
return obj;
}

View File

@@ -9,16 +9,6 @@ export const getHistories = (history?: ChatItemType[] | number, histories: ChatI
return [];
};
export const flatDynamicParams = (params: Record<string, any>) => {
const dynamicParams = params[DYNAMIC_INPUT_KEY];
if (!dynamicParams) return params;
return {
...params,
...dynamicParams,
[DYNAMIC_INPUT_KEY]: undefined
};
};
/* value type format */
export const valueTypeFormat = (value: any, type?: `${ModuleIOValueTypeEnum}`) => {
if (value === undefined) return;

View File

@@ -149,14 +149,14 @@ export const pushGenerateVectorBill = ({
list: [
{
moduleName: 'wallet.moduleName.index',
amount: total,
amount: totalVector,
model: vectorModelName,
charsLength
},
...(extensionModel !== undefined
? [
{
moduleName: extensionModelName,
moduleName: 'core.module.template.Query extension',
amount: extensionTotal,
model: extensionModelName,
inputTokens: extensionInputTokens,

View File

@@ -144,8 +144,12 @@ export const streamFetch = ({
})();
if (event === sseResponseEventEnum.answer) {
const answer: string = parseJson?.choices?.[0]?.delta?.content || '';
remainText += answer;
const text: string = parseJson?.choices?.[0]?.delta?.content || '';
remainText += text;
} else if (event === sseResponseEventEnum.response) {
const text: string = parseJson?.choices?.[0]?.delta?.content || '';
onMessage({ text });
responseText += text;
} else if (
event === sseResponseEventEnum.moduleStatus &&
parseJson?.name &&

View File

@@ -15,6 +15,7 @@ export type SearchTestStoreItemType = {
limit: number;
usingReRank: boolean;
similarity: number;
usingQueryExtension: boolean;
};
type State = {

View File

@@ -6,7 +6,9 @@ import { DatasetConcatModule } from '@fastgpt/global/core/module/template/system
import { AssignedAnswerModule } from '@fastgpt/global/core/module/template/system/assignedAnswer';
import { ClassifyQuestionModule } from '@fastgpt/global/core/module/template/system/classifyQuestion';
import { ContextExtractModule } from '@fastgpt/global/core/module/template/system/contextExtract';
import { HttpModule } from '@fastgpt/global/core/module/template/system/http';
import { HttpModule468 } from '@fastgpt/global/core/module/template/system/http468';
import { HttpModule } from '@fastgpt/global/core/module/template/system/abandon/http';
import { RunAppModule } from '@fastgpt/global/core/module/template/system/runApp';
import { PluginInputModule } from '@fastgpt/global/core/module/template/system/pluginInput';
import { PluginOutputModule } from '@fastgpt/global/core/module/template/system/pluginOutput';
@@ -29,7 +31,7 @@ export const appSystemModuleTemplates: FlowModuleTemplateType[] = [
RunAppModule,
ClassifyQuestionModule,
ContextExtractModule,
HttpModule
HttpModule468
];
export const pluginSystemModuleTemplates: FlowModuleTemplateType[] = [
PluginInputModule,
@@ -41,7 +43,7 @@ export const pluginSystemModuleTemplates: FlowModuleTemplateType[] = [
RunAppModule,
ClassifyQuestionModule,
ContextExtractModule,
HttpModule
HttpModule468
];
export const moduleTemplatesFlat: FlowModuleTemplateType[] = [
@@ -53,6 +55,7 @@ export const moduleTemplatesFlat: FlowModuleTemplateType[] = [
AssignedAnswerModule,
ClassifyQuestionModule,
ContextExtractModule,
HttpModule468,
HttpModule,
RunAppModule,
PluginInputModule,