mirror of
https://github.com/labring/FastGPT.git
synced 2025-08-01 11:58:38 +00:00
4.6.8 supplement (#831)
Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
@@ -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')}
|
||||
|
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -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;
|
||||
});
|
||||
|
85
projects/app/src/components/core/dataset/SearchParamsTip.tsx
Normal file
85
projects/app/src/components/core/dataset/SearchParamsTip.tsx
Normal 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);
|
@@ -37,7 +37,7 @@ export type DatasetParamsProps = {
|
||||
datasetSearchExtensionModel?: string;
|
||||
datasetSearchExtensionBg?: string;
|
||||
|
||||
maxTokens?: number;
|
||||
maxTokens?: number; // limit max tokens
|
||||
searchEmptyText?: string;
|
||||
};
|
||||
enum SearchSettingTabEnum {
|
||||
|
@@ -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}
|
||||
|
@@ -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>
|
||||
|
@@ -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 />
|
||||
|
@@ -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);
|
||||
}}
|
||||
|
@@ -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);
|
||||
|
@@ -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) => {
|
||||
|
@@ -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>
|
||||
|
@@ -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,
|
||||
|
Reference in New Issue
Block a user