4.7-alpha2 (#1027)

* feat: stop toolCall and rename some field. (#46)

* perf: node delete tip;pay tip

* fix: toolCall cannot save child answer

* feat: stop tool

* fix: team modal

* fix feckbackMoal  auth bug (#47)

* 简单的支持提示词运行tool。优化workflow模板 (#49)

* remove templates

* fix: request body undefined

* feat: prompt tool run

* feat: workflow tamplates modal

* perf: plugin start

* 4.7 (#50)

* fix docker-compose download url (#994)

original code is a bad url with '404 NOT FOUND' return.
fix docker-compose download url, add 'v' before docker-compose version

* Update ai_settings.md (#1000)

* Update configuration.md

* Update configuration.md

* Fix history in classifyQuestion and extract modules (#1012)

* Fix history in classifyQuestion and extract modules

* Add chatValue2RuntimePrompt import and update text formatting

* flow controller to packages

* fix: rerank select

* modal ui

* perf: modal code path

* point not sufficient

* feat: http url support variable

* fix http key

* perf: prompt

* perf: ai setting modal

* simple edit ui

---------

Co-authored-by: entorick <entorick11@qq.com>
Co-authored-by: liujianglc <liujianglc@163.com>
Co-authored-by: Fengrui Liu <liufengrui.work@bytedance.com>

* fix team share redirect to login (#51)

* feat: support openapi import plugins (#48)

* feat: support openapi import plugins

* feat: import from url

* fix: add body params parse

* fix build

* fix

* fix

* fix

* tool box ui (#52)

* fix: training queue

* feat: simple edit tool select

* perf: simple edit dataset prompt

* fix: chatbox tool ux

* feat: quote prompt module

* perf: plugin tools sign

* perf: model avatar

* tool selector ui

* feat: max histories

* perf: http plugin import (#53)

* perf: plugin http import

* chatBox ui

* perf: name

* fix: Node template card (#54)

* fix: ts

* setting modal

* package

* package

* feat: add plugins search (#57)

* feat: add plugins search

* perf: change http plugin header input

* Yjl (#56)

* perf: prompt tool call

* perf: chat box ux

* doc

* doc

* price tip

* perf: tool selector

* ui'

* fix: vector queue

* fix: empty tool and empty response

* fix: empty msg

* perf: pg index

* perf: ui tip

* doc

* tool tip

---------

Co-authored-by: yst <77910600+yu-and-liu@users.noreply.github.com>
Co-authored-by: entorick <entorick11@qq.com>
Co-authored-by: liujianglc <liujianglc@163.com>
Co-authored-by: Fengrui Liu <liufengrui.work@bytedance.com>
Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer
2024-03-21 13:32:31 +08:00
committed by GitHub
parent 6d4b331db9
commit 9d27de154b
322 changed files with 9282 additions and 6498 deletions

View File

@@ -0,0 +1,242 @@
import React, { useMemo, useState } from 'react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form';
import {
Box,
BoxProps,
Button,
Flex,
Link,
ModalBody,
ModalFooter,
Switch
} from '@chakra-ui/react';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MySlider from '@/components/Slider';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import type { SettingAIDataType } from '@fastgpt/global/core/module/node/type.d';
import { getDocPath } from '@/web/common/system/doc';
import AIModelSelector from '@/components/Select/AIModelSelector';
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
const AIChatSettingsModal = ({
onClose,
onSuccess,
defaultData,
llmModels = []
}: {
onClose: () => void;
onSuccess: (e: SettingAIDataType) => void;
defaultData: SettingAIDataType;
llmModels?: LLMModelItemType[];
}) => {
const { t } = useTranslation();
const [refresh, setRefresh] = useState(false);
const { feConfigs, llmModelList } = useSystemStore();
const { handleSubmit, getValues, setValue, watch } = useForm({
defaultValues: defaultData
});
const model = watch('model');
const showResponseAnswerText = watch(ModuleInputKeyEnum.aiChatIsResponseText) !== undefined;
const showMaxHistoriesSlider = watch('maxHistories') !== undefined;
const selectedModel = llmModelList.find((item) => item.model === model) || llmModelList[0];
const tokenLimit = useMemo(() => {
return llmModelList.find((item) => item.model === model)?.maxResponse || 4096;
}, [llmModelList, model]);
const onChangeModel = (e: string) => {
setValue('model', e);
// update max tokens
const modelData = llmModelList.find((item) => item.model === e);
if (modelData) {
setValue('maxToken', modelData.maxResponse / 2);
}
setRefresh(!refresh);
};
const LabelStyles: BoxProps = {
display: 'flex',
alignItems: 'center',
fontSize: ['sm', 'md'],
width: ['80px', '90px']
};
return (
<MyModal
isOpen
iconSrc="/imgs/module/AI.png"
onClose={onClose}
title={
<>
{t('core.ai.AI settings')}
{feConfigs?.docUrl && (
<Link
href={getDocPath('/docs/course/ai_settings/')}
target={'_blank'}
ml={1}
textDecoration={'underline'}
fontWeight={'normal'}
fontSize={'md'}
>
{t('common.Read intro')}
</Link>
)}
</>
}
w={'500px'}
>
<ModalBody overflowY={'auto'}>
<Flex alignItems={'center'}>
<Box {...LabelStyles} mr={2}>
{t('core.ai.Model')}
</Box>
<Box flex={'1 0 0'}>
<AIModelSelector
width={'100%'}
value={model}
list={llmModels.map((item) => ({
value: item.model,
label: item.name
}))}
onchange={onChangeModel}
/>
</Box>
</Flex>
{feConfigs && (
<Flex mt={8}>
<Box {...LabelStyles} mr={2}>
{t('core.ai.Ai point price')}
</Box>
<Box flex={1} ml={'10px'}>
{t('support.wallet.Ai point every thousand tokens', {
points: selectedModel?.charsPointsPrice || 0
})}
</Box>
</Flex>
)}
<Flex mt={8}>
<Box {...LabelStyles} mr={2}>
{t('core.ai.Max context')}
</Box>
<Box flex={1} ml={'10px'}>
{selectedModel?.maxContext || 4096}Tokens
</Box>
</Flex>
<Flex mt={8}>
<Box {...LabelStyles} mr={2}>
{t('core.ai.Support tool')}
<QuestionTip ml={1} label={t('core.module.template.AI support tool tip')} />
</Box>
<Box flex={1} ml={'10px'}>
{selectedModel?.usedInToolCall ? '支持' : '不支持'}
</Box>
</Flex>
<Flex mt={8}>
<Box {...LabelStyles} mr={2}>
{t('core.app.Temperature')}
</Box>
<Box flex={1} ml={'10px'}>
<MySlider
markList={[
{ label: t('core.app.deterministic'), value: 0 },
{ label: t('core.app.Random'), value: 10 }
]}
width={'95%'}
min={0}
max={10}
value={getValues(ModuleInputKeyEnum.aiChatTemperature)}
onChange={(e) => {
setValue(ModuleInputKeyEnum.aiChatTemperature, e);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
<Flex mt={8}>
<Box {...LabelStyles} mr={2}>
{t('core.app.Max tokens')}
</Box>
<Box flex={1} ml={'10px'}>
<MySlider
markList={[
{ label: '100', value: 100 },
{ label: `${tokenLimit}`, value: tokenLimit }
]}
width={'95%'}
min={100}
max={tokenLimit}
step={50}
value={getValues(ModuleInputKeyEnum.aiChatMaxToken)}
onChange={(val) => {
setValue(ModuleInputKeyEnum.aiChatMaxToken, val);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
{showMaxHistoriesSlider && (
<Flex mt={8}>
<Box {...LabelStyles} mr={2}>
{t('core.app.Max histories')}
</Box>
<Box flex={1} ml={'10px'}>
<MySlider
markList={[
{ label: 0, value: 0 },
{ label: 30, value: 30 }
]}
width={'95%'}
min={0}
max={30}
value={getValues('maxHistories') ?? 6}
onChange={(e) => {
setValue('maxHistories', e);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
)}
{showResponseAnswerText && (
<Flex mt={8} alignItems={'center'}>
<Box {...LabelStyles}>
{t('core.app.Ai response')}
<MyTooltip label={t('core.module.template.AI response switch tip')}>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Box>
<Box flex={1} ml={'10px'}>
<Switch
isChecked={getValues(ModuleInputKeyEnum.aiChatIsResponseText)}
size={'lg'}
onChange={(e) => {
const value = e.target.checked;
setValue(ModuleInputKeyEnum.aiChatIsResponseText, value);
setRefresh((state) => !state);
}}
/>
</Box>
</Flex>
)}
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} onClick={onClose}>
{t('common.Close')}
</Button>
<Button ml={4} onClick={handleSubmit(onSuccess)}>
{t('common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
);
};
export default AIChatSettingsModal;

View File

@@ -0,0 +1,83 @@
import React, { useEffect } from 'react';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { LLMModelTypeEnum, llmModelTypeFilterMap } from '@fastgpt/global/core/ai/constants';
import { Box, Button, useDisclosure } from '@chakra-ui/react';
import { SettingAIDataType } from '@fastgpt/global/core/module/node/type';
import AISettingModal from '@/components/core/ai/AISettingModal';
import Avatar from '@/components/Avatar';
import { HUGGING_FACE_ICON } from '@fastgpt/global/common/system/constants';
type Props = {
llmModelType?: `${LLMModelTypeEnum}`;
defaultData: SettingAIDataType;
onChange: (e: SettingAIDataType) => void;
};
const SettingLLMModel = ({ llmModelType = LLMModelTypeEnum.all, defaultData, onChange }: Props) => {
const { llmModelList } = useSystemStore();
const model = defaultData.model;
const modelList = llmModelList.filter((model) => {
if (!llmModelType) return true;
const filterField = llmModelTypeFilterMap[llmModelType];
if (!filterField) return true;
//@ts-ignore
return !!model[filterField];
});
const selectedModel = modelList.find((item) => item.model === model) || modelList[0];
const {
isOpen: isOpenAIChatSetting,
onOpen: onOpenAIChatSetting,
onClose: onCloseAIChatSetting
} = useDisclosure();
useEffect(() => {
if (!model && modelList.length > 0) {
onChange({
...defaultData,
model: modelList[0].model
});
}
}, [defaultData, model, modelList, onChange]);
return (
<Box position={'relative'}>
<Button
w={'100%'}
justifyContent={'flex-start'}
variant={'whitePrimary'}
_active={{
transform: 'none'
}}
leftIcon={
<Avatar
borderRadius={'0'}
src={selectedModel.avatar || HUGGING_FACE_ICON}
fallbackSrc={HUGGING_FACE_ICON}
w={'18px'}
/>
}
pl={4}
onClick={onOpenAIChatSetting}
>
{selectedModel?.name}
</Button>
{isOpenAIChatSetting && (
<AISettingModal
onClose={onCloseAIChatSetting}
onSuccess={(e) => {
onChange(e);
onCloseAIChatSetting();
}}
defaultData={defaultData}
llmModels={modelList}
/>
)}
</Box>
);
};
export default React.memo(SettingLLMModel);

View File

@@ -50,7 +50,7 @@ const SearchParamsTip = ({
</Thead>
<Tbody>
<Tr color={'myGray.800'}>
<Td pt={0} pb={1}>
<Td pt={0} pb={2}>
<Flex alignItems={'center'}>
<MyIcon
name={DatasetSearchModeMap[searchMode]?.icon as any}
@@ -60,18 +60,18 @@ const SearchParamsTip = ({
{t(DatasetSearchModeMap[searchMode]?.title)}
</Flex>
</Td>
<Td pt={0} pb={1}>
<Td pt={0} pb={2}>
{limit}
</Td>
<Td pt={0} pb={1}>
<Td pt={0} pb={2}>
{hasSimilarityMode ? similarity : t('core.dataset.search.Nonsupport')}
</Td>
{hasReRankModel && (
<Td pt={0} pb={1}>
<Td pt={0} pb={2}>
{usingReRank ? '✅' : '❌'}
</Td>
)}
<Td pt={0} pb={1}>
<Td pt={0} pb={2}>
{usingQueryExtension ? '✅' : '❌'}
</Td>
{hasEmptyResponseMode && <Th>{responseEmptyText !== '' ? '✅' : '❌'}</Th>}

View File

@@ -1,5 +1,5 @@
import { getDatasets, getDatasetPaths } from '@/web/core/dataset/api';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useQuery } from '@tanstack/react-query';
import React, { Dispatch, useMemo, useState } from 'react';
import { useTranslation } from 'next-i18next';

View File

@@ -1,293 +0,0 @@
import React, { useMemo, useState } from 'react';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form';
import {
Box,
BoxProps,
Button,
Flex,
Link,
ModalBody,
ModalFooter,
Switch
} from '@chakra-ui/react';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { Prompt_QuotePromptList, Prompt_QuoteTemplateList } from '@/global/core/prompt/AIChat';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MySlider from '@/components/Slider';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import dynamic from 'next/dynamic';
import { PromptTemplateItem } from '@fastgpt/global/core/ai/type.d';
import type { AIChatModuleProps } from '@fastgpt/global/core/module/node/type.d';
import type { AppSimpleEditConfigTemplateType } from '@fastgpt/global/core/app/type.d';
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
import { getDocPath } from '@/web/common/system/doc';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import { EditorVariablePickerType } from '@fastgpt/web/components/common/Textarea/PromptEditor/type';
const PromptTemplate = dynamic(() => import('@/components/PromptTemplate'));
const AIChatSettingsModal = ({
isAdEdit,
onClose,
onSuccess,
defaultData,
pickerMenu = []
}: {
isAdEdit?: boolean;
onClose: () => void;
onSuccess: (e: AIChatModuleProps) => void;
defaultData: AIChatModuleProps;
pickerMenu?: EditorVariablePickerType[];
}) => {
const { t } = useTranslation();
const [refresh, setRefresh] = useState(false);
const { feConfigs, llmModelList } = useSystemStore();
const { handleSubmit, getValues, setValue, watch } = useForm({
defaultValues: defaultData
});
const aiChatQuoteTemplate = watch(ModuleInputKeyEnum.aiChatQuoteTemplate);
const aiChatQuotePrompt = watch(ModuleInputKeyEnum.aiChatQuotePrompt);
const [selectTemplateData, setSelectTemplateData] = useState<{
title: string;
templates: PromptTemplateItem[];
}>();
const tokenLimit = useMemo(() => {
return (
llmModelList.find((item) => item.model === getValues(ModuleInputKeyEnum.aiModel))
?.maxResponse || 4000
);
}, [getValues, llmModelList]);
const quoteTemplateVariables = (() => [
{
key: 'q',
label: 'q',
icon: 'core/app/simpleMode/variable'
},
{
key: 'a',
label: 'a',
icon: 'core/app/simpleMode/variable'
},
{
key: 'source',
label: t('core.dataset.search.Source name'),
icon: 'core/app/simpleMode/variable'
},
{
key: 'sourceId',
label: t('core.dataset.search.Source id'),
icon: 'core/app/simpleMode/variable'
},
{
key: 'index',
label: t('core.dataset.search.Quote index'),
icon: 'core/app/simpleMode/variable'
},
...pickerMenu
])();
const quotePromptVariables = (() => [
{
key: 'quote',
label: t('core.app.Quote templates'),
icon: 'core/app/simpleMode/variable'
},
{
key: 'question',
label: t('core.module.input.label.user question'),
icon: 'core/app/simpleMode/variable'
},
...pickerMenu
])();
const LabelStyles: BoxProps = {
fontSize: ['sm', 'md']
};
const selectTemplateBtn: BoxProps = {
color: 'primary.500',
cursor: 'pointer'
};
return (
<MyModal
isOpen
iconSrc="/imgs/module/AI.png"
title={
<>
{t('common.More settings')}
{feConfigs?.docUrl && (
<Link
href={getDocPath('/docs/use-cases/ai_settings/')}
target={'_blank'}
ml={1}
textDecoration={'underline'}
fontWeight={'normal'}
fontSize={'md'}
>
{t('common.Read intro')}
</Link>
)}
</>
}
isCentered
w={'700px'}
h={['90vh', 'auto']}
>
<ModalBody flex={['1 0 0', 'auto']} overflowY={'auto'} minH={'40vh'}>
{isAdEdit && (
<Flex alignItems={'center'}>
<Box {...LabelStyles} w={'80px'}>
{t('core.app.Ai response')}
</Box>
<Box flex={1} ml={'10px'}>
<Switch
isChecked={getValues(ModuleInputKeyEnum.aiChatIsResponseText)}
size={'lg'}
onChange={(e) => {
const value = e.target.checked;
setValue(ModuleInputKeyEnum.aiChatIsResponseText, value);
setRefresh((state) => !state);
}}
/>
</Box>
</Flex>
)}
<Flex mb={10} mt={isAdEdit ? 8 : 6}>
<Box {...LabelStyles} mr={2} w={'80px'}>
{t('core.app.Temperature')}
</Box>
<Box flex={1} ml={'10px'}>
<MySlider
markList={[
{ label: t('core.app.deterministic'), value: 0 },
{ label: t('core.app.Random'), value: 10 }
]}
width={'95%'}
min={0}
max={10}
value={getValues(ModuleInputKeyEnum.aiChatTemperature)}
onChange={(e) => {
setValue(ModuleInputKeyEnum.aiChatTemperature, e);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
<Flex mt={5} mb={5}>
<Box {...LabelStyles} mr={2} w={'80px'}>
{t('core.app.Max tokens')}
</Box>
<Box flex={1} ml={'10px'}>
<MySlider
markList={[
{ label: '100', value: 100 },
{ label: `${tokenLimit}`, value: tokenLimit }
]}
width={'95%'}
min={100}
max={tokenLimit}
step={50}
value={getValues(ModuleInputKeyEnum.aiChatMaxToken)}
onChange={(val) => {
setValue(ModuleInputKeyEnum.aiChatMaxToken, val);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
<Box>
<Flex {...LabelStyles} mb={1}>
{t('core.app.Quote templates')}
<MyTooltip
label={t('template.Quote Content Tip', {
default: Prompt_QuoteTemplateList[0].value
})}
forceShow
>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
<Box flex={1} />
<Box
{...selectTemplateBtn}
onClick={() =>
setSelectTemplateData({
title: t('core.app.Select quote template'),
templates: Prompt_QuoteTemplateList
})
}
>
{t('common.Select template')}
</Box>
</Flex>
<PromptEditor
variables={quoteTemplateVariables}
h={160}
title={t('core.app.Quote templates')}
placeholder={t('template.Quote Content Tip', {
default: Prompt_QuoteTemplateList[0].value
})}
value={aiChatQuoteTemplate}
onChange={(e) => {
setValue(ModuleInputKeyEnum.aiChatQuoteTemplate, e);
// setRefresh(!refresh);
}}
/>
</Box>
<Box mt={4}>
<Flex {...LabelStyles} mb={1}>
{t('core.app.Quote prompt')}
<MyTooltip
label={t('template.Quote Prompt Tip', { default: Prompt_QuotePromptList[0].value })}
forceShow
>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Flex>
<PromptEditor
variables={quotePromptVariables}
title={t('core.app.Quote prompt')}
h={230}
placeholder={t('template.Quote Prompt Tip', {
default: Prompt_QuotePromptList[0].value
})}
value={aiChatQuotePrompt}
onChange={(e) => {
setValue(ModuleInputKeyEnum.aiChatQuotePrompt, e);
}}
/>
</Box>
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} onClick={onClose}>
{t('common.Close')}
</Button>
<Button ml={4} onClick={handleSubmit(onSuccess)}>
{t('common.Confirm')}
</Button>
</ModalFooter>
{!!selectTemplateData && (
<PromptTemplate
title={selectTemplateData.title}
templates={selectTemplateData.templates}
onClose={() => setSelectTemplateData(undefined)}
onSuccess={(e) => {
const quoteVal = e.value;
const promptVal = Prompt_QuotePromptList.find((item) => item.title === e.title)?.value;
setValue(ModuleInputKeyEnum.aiChatQuoteTemplate, quoteVal);
setValue(ModuleInputKeyEnum.aiChatQuotePrompt, promptVal);
}}
/>
)}
</MyModal>
);
};
export default AIChatSettingsModal;

View File

@@ -15,7 +15,7 @@ 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 MyModal from '@fastgpt/web/components/common/MyModal';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
import { useTranslation } from 'next-i18next';
import { useSystemStore } from '@/web/common/system/useSystemStore';
@@ -28,7 +28,7 @@ import Tabs from '@/components/Tabs';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useToast } from '@fastgpt/web/hooks/useToast';
import SelectAiModel from '@/components/Select/SelectAiModel';
import SelectAiModel from '@/components/Select/AIModelSelector';
export type DatasetParamsProps = {
searchMode: `${DatasetSearchModeEnum}`;
@@ -40,7 +40,6 @@ export type DatasetParamsProps = {
datasetSearchExtensionBg?: string;
maxTokens?: number; // limit max tokens
searchEmptyText?: string;
};
enum SearchSettingTabEnum {
searchMode = 'searchMode',
@@ -50,7 +49,6 @@ enum SearchSettingTabEnum {
const DatasetParamsModal = ({
searchMode = DatasetSearchModeEnum.embedding,
searchEmptyText,
limit,
similarity,
usingReRank,
@@ -71,11 +69,10 @@ const DatasetParamsModal = ({
const { register, setValue, getValues, handleSubmit, watch } = useForm<DatasetParamsProps>({
defaultValues: {
searchEmptyText,
limit,
similarity,
searchMode,
usingReRank: !!usingReRank && !!teamPlanStatus?.standardConstants?.permissionReRank,
usingReRank: !!usingReRank && teamPlanStatus?.standardConstants?.permissionReRank !== false,
datasetSearchUsingExtensionQuery,
datasetSearchExtensionModel: datasetSearchExtensionModel ?? llmModelList[0]?.model,
datasetSearchExtensionBg
@@ -272,21 +269,6 @@ const DatasetParamsModal = ({
</Box>
</Box>
)}
{searchEmptyText !== undefined && (
<Box display={['block', 'flex']} pt={3}>
<Box flex={'0 0 120px'} 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>
)}
</Box>
)}
{currentTabType === SearchSettingTabEnum.queryExtension && (

View File

@@ -8,10 +8,7 @@ import {
Connection,
addEdge
} from 'reactflow';
import type {
FlowModuleItemType,
FlowModuleTemplateType
} from '@fastgpt/global/core/module/type.d';
import type { FlowModuleItemType, FlowNodeTemplateType } from '@fastgpt/global/core/module/type.d';
import type {
FlowNodeChangeProps,
FlowNodeInputItemType
@@ -64,7 +61,7 @@ export type useFlowProviderStoreType = {
onDelNode: (nodeId: string) => void;
onChangeNode: (e: FlowNodeChangeProps) => void;
onCopyNode: (nodeId: string) => void;
onResetNode: (e: { id: string; module: FlowModuleTemplateType }) => void;
onResetNode: (e: { id: string; module: FlowNodeTemplateType }) => void;
onDelEdge: (e: {
moduleId: string;
sourceHandle?: string | undefined;
@@ -417,7 +414,7 @@ export const FlowProvider = ({
// reset a node data. delete edge and replace it
const onResetNode = useCallback(
({ id, module }: { id: string; module: FlowModuleTemplateType }) => {
({ id, module }: { id: string; module: FlowNodeTemplateType }) => {
setNodes((state) =>
state.map((node) => {
if (node.id === id) {
@@ -549,12 +546,6 @@ export const onResetNode = (e: Parameters<useFlowProviderStoreType['onResetNode'
data: e
});
};
export const onDelNode = (nodeId: string) => {
eventBus.emit(EventNameEnum.requestFlowEvent, {
type: 'onDelNode',
data: nodeId
});
};
export const onDelConnect = (e: Parameters<useFlowProviderStoreType['onDelConnect']>[0]) => {
eventBus.emit(EventNameEnum.requestFlowEvent, {
type: 'onDelConnect',

View File

@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { Textarea, Button, ModalBody, ModalFooter } from '@chakra-ui/react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useFlowProviderStore } from './FlowProvider';

View File

@@ -1,13 +1,13 @@
import React, { useCallback, useMemo } from 'react';
import { Box, Flex } from '@chakra-ui/react';
import React, { useCallback, useMemo, useState } from 'react';
import { Box, Flex, IconButton, Input, InputGroup, InputLeftElement } from '@chakra-ui/react';
import type {
FlowModuleTemplateType,
FlowNodeTemplateType,
moduleTemplateListType
} from '@fastgpt/global/core/module/type.d';
import { useViewport, XYPosition } from 'reactflow';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import Avatar from '@/components/Avatar';
import { getFlowStore, onSetNodes } from './FlowProvider';
import { onSetNodes, useFlowProviderStore } from './FlowProvider';
import { customAlphabet } from 'nanoid';
import { appModule2FlowNode } from '@/utils/adapt';
import { useTranslation } from 'next-i18next';
@@ -18,22 +18,83 @@ import { getPreviewPluginModule } from '@/web/core/plugin/api';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { moduleTemplatesList } from '@fastgpt/global/core/module/template/constants';
import RowTabs from '@fastgpt/web/components/common/Tabs/RowTabs';
import { useWorkflowStore } from '@/web/core/workflow/store/workflow';
import { useRequest } from '@/web/common/hooks/useRequest';
import ParentPaths from '@/components/common/ParentPaths';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useRouter } from 'next/router';
import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants';
import { useQuery } from '@tanstack/react-query';
import { debounce } from 'lodash';
export type ModuleTemplateProps = {
templates: FlowModuleTemplateType[];
};
type ModuleTemplateListProps = ModuleTemplateProps & {
type ModuleTemplateListProps = {
isOpen: boolean;
onClose: () => void;
};
type RenderListProps = {
templates: FlowModuleTemplateType[];
templates: FlowNodeTemplateType[];
onClose: () => void;
currentParent?: { parentId: string; parentName: string };
setCurrentParent: (e: { parentId: string; parentName: string }) => void;
};
const ModuleTemplateList = ({ templates, isOpen, onClose }: ModuleTemplateListProps) => {
enum TemplateTypeEnum {
'basic' = 'basic',
'systemPlugin' = 'systemPlugin',
'teamPlugin' = 'teamPlugin'
}
const sliderWidth = 380;
const ModuleTemplateList = ({ isOpen, onClose }: ModuleTemplateListProps) => {
const { t } = useTranslation();
const router = useRouter();
const [currentParent, setCurrentParent] = useState<RenderListProps['currentParent']>();
const [searchKey, setSearchKey] = useState('');
const {
basicNodeTemplates,
systemNodeTemplates,
loadSystemNodeTemplates,
teamPluginNodeTemplates,
loadTeamPluginNodeTemplates
} = useWorkflowStore();
const [templateType, setTemplateType] = useState(TemplateTypeEnum.basic);
const templates = useMemo(() => {
const map = {
[TemplateTypeEnum.basic]: basicNodeTemplates,
[TemplateTypeEnum.systemPlugin]: systemNodeTemplates,
[TemplateTypeEnum.teamPlugin]: teamPluginNodeTemplates.filter((item) =>
searchKey ? item.pluginType !== PluginTypeEnum.folder : true
)
};
return map[templateType];
}, [basicNodeTemplates, searchKey, systemNodeTemplates, teamPluginNodeTemplates, templateType]);
const { mutate: onChangeTab } = useRequest({
mutationFn: async (e: any) => {
const val = e as TemplateTypeEnum;
if (val === TemplateTypeEnum.systemPlugin) {
await loadSystemNodeTemplates();
} else if (val === TemplateTypeEnum.teamPlugin) {
await loadTeamPluginNodeTemplates({
parentId: currentParent?.parentId
});
}
setTemplateType(val);
},
errorToast: t('core.module.templates.Load plugin error')
});
useQuery(['teamNodeTemplate', currentParent?.parentId, searchKey], () =>
loadTeamPluginNodeTemplates({
parentId: currentParent?.parentId,
searchKey,
init: true
})
);
return (
<>
@@ -44,27 +105,108 @@ const ModuleTemplateList = ({ templates, isOpen, onClose }: ModuleTemplateListPr
top={0}
left={0}
bottom={0}
w={'360px'}
w={`${sliderWidth}px`}
onClick={onClose}
/>
<Flex
zIndex={3}
flexDirection={'column'}
position={'absolute'}
top={'65px'}
top={'10px'}
left={0}
pt={2}
pt={'20px'}
pb={4}
h={isOpen ? 'calc(100% - 100px)' : '0'}
w={isOpen ? ['100%', '360px'] : '0'}
h={isOpen ? 'calc(100% - 20px)' : '0'}
w={isOpen ? ['100%', `${sliderWidth}px`] : '0'}
bg={'white'}
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
borderRadius={'20px'}
overflow={'hidden'}
borderRadius={'0 20px 20px 0'}
transition={'.2s ease'}
userSelect={'none'}
overflow={isOpen ? 'none' : 'hidden'}
>
<RenderList templates={templates} onClose={onClose} />
<Box mb={2} pl={'20px'} pr={'10px'} whiteSpace={'nowrap'} overflow={'hidden'}>
<Flex flex={'1 0 0'} alignItems={'center'} gap={3}>
<RowTabs
list={[
{
icon: 'core/modules/basicNode',
label: t('core.module.template.Basic Node'),
value: TemplateTypeEnum.basic
},
{
icon: 'core/modules/systemPlugin',
label: t('core.module.template.System Plugin'),
value: TemplateTypeEnum.systemPlugin
},
{
icon: 'core/modules/teamPlugin',
label: t('core.module.template.Team Plugin'),
value: TemplateTypeEnum.teamPlugin
}
]}
py={'5px'}
value={templateType}
onChange={onChangeTab}
/>
{/* close icon */}
<IconButton
size={'sm'}
icon={<MyIcon name={'common/backFill'} w={'14px'} color={'myGray.700'} />}
w={'26px'}
h={'26px'}
borderColor={'myGray.300'}
variant={'grayBase'}
aria-label={''}
onClick={onClose}
/>
</Flex>
{templateType === TemplateTypeEnum.teamPlugin && (
<Flex mt={2} alignItems={'center'} h={10}>
<InputGroup mr={4} h={'full'}>
<InputLeftElement h={'full'} alignItems={'center'} display={'flex'}>
<MyIcon name={'common/searchLight'} w={'16px'} color={'myGray.500'} ml={3} />
</InputLeftElement>
<Input
h={'full'}
bg={'myGray.50'}
placeholder={t('plugin.Search plugin')}
onChange={debounce((e) => setSearchKey(e.target.value), 200)}
/>
</InputGroup>
<Box flex={1} />
<Flex
alignItems={'center'}
cursor={'pointer'}
_hover={{
color: 'primary.600'
}}
onClick={() => router.push('/plugin/list')}
>
<Box></Box>
<MyIcon name={'common/rightArrowLight'} w={'14px'} />
</Flex>
</Flex>
)}
{templateType === TemplateTypeEnum.teamPlugin && !searchKey && currentParent && (
<Flex alignItems={'center'} mt={2}>
<ParentPaths
paths={[currentParent]}
FirstPathDom={null}
onClick={() => {
setCurrentParent(undefined);
}}
fontSize="md"
/>
</Flex>
)}
</Box>
<RenderList
templates={templates}
onClose={onClose}
currentParent={currentParent}
setCurrentParent={setCurrentParent}
/>
</Flex>
</>
);
@@ -72,12 +214,18 @@ const ModuleTemplateList = ({ templates, isOpen, onClose }: ModuleTemplateListPr
export default React.memo(ModuleTemplateList);
const RenderList = React.memo(function RenderList({ templates, onClose }: RenderListProps) {
const RenderList = React.memo(function RenderList({
templates,
onClose,
currentParent,
setCurrentParent
}: RenderListProps) {
const { t } = useTranslation();
const { isPc } = useSystemStore();
const { x, y, zoom } = useViewport();
const { setLoading } = useSystemStore();
const { toast } = useToast();
const { reactFlowWrapper, nodes } = useFlowProviderStore();
const formatTemplates = useMemo<moduleTemplateListType>(() => {
const copy: moduleTemplateListType = JSON.parse(JSON.stringify(moduleTemplatesList));
@@ -87,11 +235,11 @@ const RenderList = React.memo(function RenderList({ templates, onClose }: Render
copy[index].list.push(item);
});
return copy.filter((item) => item.list.length > 0);
}, [templates]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [templates, currentParent]);
const onAddNode = useCallback(
async ({ template, position }: { template: FlowModuleTemplateType; position: XYPosition }) => {
const { reactFlowWrapper, nodes } = await getFlowStore();
async ({ template, position }: { template: FlowNodeTemplateType; position: XYPosition }) => {
if (!reactFlowWrapper?.current) return;
const templateModule = await (async () => {
@@ -131,34 +279,24 @@ const RenderList = React.memo(function RenderList({ templates, onClose }: Render
)
);
},
[setLoading, t, toast, x, y, zoom]
[nodes, reactFlowWrapper, setLoading, t, toast, x, y, zoom]
);
return templates.length === 0 ? (
<EmptyTip text={t('app.module.No Modules')} />
) : (
<Box flex={'1 0 0'} overflow={'overlay'}>
<Box w={['100%', '330px']} mx={'auto'}>
<Box flex={'1 0 0'} overflow={'overlay'} px={'20px'}>
<Box mx={'auto'}>
{formatTemplates.map((item, i) => (
<Box key={item.type}>
<Flex>
<Box fontWeight={'bold'} flex={1}>
{t(item.label)}
</Box>
{/* {isPlugin && item.type === ModuleTemplateTypeEnum.personalPlugin && (
<Flex
alignItems={'center'}
_hover={{ textDecoration: 'underline' }}
cursor={'pointer'}
onClick={() => router.push('/plugin/list')}
>
<Box fontSize={'sm'} transform={'translateY(-1px)'}>
{t('plugin.To Edit Plugin')}
</Box>
<MyIcon name={'common/rightArrowLight'} w={'12px'} />
</Flex>
)} */}
</Flex>
{item.label && (
<Flex>
<Box fontWeight={'bold'} flex={1}>
{t(item.label)}
</Box>
</Flex>
)}
<>
{item.list.map((template) => (
<Flex
@@ -168,21 +306,32 @@ const RenderList = React.memo(function RenderList({ templates, onClose }: Render
cursor={'pointer'}
_hover={{ bg: 'myWhite.600' }}
borderRadius={'sm'}
draggable
draggable={template.pluginType !== PluginTypeEnum.folder}
onDragEnd={(e) => {
if (e.clientX < 360) return;
if (e.clientX < sliderWidth) return;
onAddNode({
template: template,
position: { x: e.clientX, y: e.clientY }
});
}}
onClick={(e) => {
if (isPc) return;
onClose();
if (template.pluginType === PluginTypeEnum.folder) {
return setCurrentParent({
parentId: template.id,
parentName: template.name
});
}
if (isPc) {
return onAddNode({
template,
position: { x: sliderWidth * 1.5, y: 200 }
});
}
onAddNode({
template: template,
position: { x: e.clientX, y: e.clientY }
});
onClose();
}}
>
<Avatar

View File

@@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import { ModalBody, Flex, Box, useTheme, ModalFooter, Button } from '@chakra-ui/react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useQuery } from '@tanstack/react-query';
import type { SelectAppItemType } from '@fastgpt/global/core/module/type';
import Avatar from '@/components/Avatar';

View File

@@ -8,7 +8,7 @@ import { TTSTypeEnum } from '@/constants/app';
import type { AppTTSConfigType } from '@fastgpt/global/core/module/type.d';
import { useAudioPlay } from '@/web/common/utils/voice';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import MySlider from '@/components/Slider';
import MySelect from '@fastgpt/web/components/common/MySelect';
@@ -67,7 +67,7 @@ const TTSSelect = ({
});
}
},
[onChange, value]
[audioSpeechModelList, onChange, value]
);
return (
@@ -79,17 +79,16 @@ const TTSSelect = ({
</MyTooltip>
<Box flex={1} />
<MyTooltip label={t('core.app.Select TTS')}>
<Box
cursor={'pointer'}
_hover={{ bg: 'myGray.100' }}
py={2}
px={3}
borderRadius={'md'}
<Button
variant={'transparentBase'}
iconSpacing={1}
size={'sm'}
fontSize={'md'}
mr={'-5px'}
onClick={onOpen}
color={'myGray.600'}
>
{formLabel}
</Box>
</Button>
</MyTooltip>
<MyModal
title={

View File

@@ -32,7 +32,7 @@ import { useForm } from 'react-hook-form';
import { useFieldArray } from 'react-hook-form';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import MyTooltip from '@/components/MyTooltip';
import { variableTip } from '@fastgpt/global/core/module/template/tip';
import { useTranslation } from 'next-i18next';
@@ -83,16 +83,6 @@ const VariableEdit = ({
name: 'variable.enums'
});
const BoxBtnStyles: BoxProps = {
cursor: 'pointer',
px: 3,
py: 1,
borderRadius: 'md',
_hover: {
bg: 'myGray.150'
}
};
const formatVariables = useMemo(() => {
const results = formatEditorVariablePickerIcon(variables);
return results.map((item) => {
@@ -114,17 +104,20 @@ const VariableEdit = ({
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Box>
<Flex
{...BoxBtnStyles}
alignItems={'center'}
<Button
variant={'transparentBase'}
leftIcon={<SmallAddIcon />}
iconSpacing={1}
size={'sm'}
mr={'-5px'}
fontSize={'md'}
onClick={() => {
resetEdit({ variable: addVariable() });
onOpenEdit();
}}
>
<SmallAddIcon />
{t('common.Add New')}
</Flex>
</Button>
</Flex>
{formatVariables.length > 0 && (
<Box mt={2} borderRadius={'md'} overflow={'hidden'} borderWidth={'1px'} borderBottom="none">

View File

@@ -11,7 +11,7 @@ import {
} from '@chakra-ui/react';
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/module/type';
import { useForm } from 'react-hook-form';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';

View File

@@ -1,5 +1,5 @@
import React from 'react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { ModalBody, Button, ModalFooter, useDisclosure, Textarea, Box } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { onChangeNode } from '../../../FlowProvider';

View File

@@ -41,6 +41,47 @@ import MySelect from '@fastgpt/web/components/common/MySelect';
import RenderToolInput from '../../render/RenderToolInput';
const OpenApiImportModal = dynamic(() => import('./OpenApiImportModal'));
export const HttpHeaders = [
{ key: 'A-IM', label: 'A-IM' },
{ key: 'Accept', label: 'Accept' },
{ key: 'Accept-Charset', label: 'Accept-Charset' },
{ key: 'Accept-Encoding', label: 'Accept-Encoding' },
{ key: 'Accept-Language', label: 'Accept-Language' },
{ key: 'Accept-Datetime', label: 'Accept-Datetime' },
{ key: 'Access-Control-Request-Method', label: 'Access-Control-Request-Method' },
{ key: 'Access-Control-Request-Headers', label: 'Access-Control-Request-Headers' },
{ key: 'Authorization', label: 'Authorization' },
{ key: 'Cache-Control', label: 'Cache-Control' },
{ key: 'Connection', label: 'Connection' },
{ key: 'Content-Length', label: 'Content-Length' },
{ key: 'Content-Type', label: 'Content-Type' },
{ key: 'Cookie', label: 'Cookie' },
{ key: 'Date', label: 'Date' },
{ key: 'Expect', label: 'Expect' },
{ key: 'Forwarded', label: 'Forwarded' },
{ key: 'From', label: 'From' },
{ key: 'Host', label: 'Host' },
{ key: 'If-Match', label: 'If-Match' },
{ key: 'If-Modified-Since', label: 'If-Modified-Since' },
{ key: 'If-None-Match', label: 'If-None-Match' },
{ key: 'If-Range', label: 'If-Range' },
{ key: 'If-Unmodified-Since', label: 'If-Unmodified-Since' },
{ key: 'Max-Forwards', label: 'Max-Forwards' },
{ key: 'Origin', label: 'Origin' },
{ key: 'Pragma', label: 'Pragma' },
{ key: 'Proxy-Authorization', label: 'Proxy-Authorization' },
{ key: 'Range', label: 'Range' },
{ key: 'Referer', label: 'Referer' },
{ key: 'TE', label: 'TE' },
{ key: 'User-Agent', label: 'User-Agent' },
{ key: 'Upgrade', label: 'Upgrade' },
{ key: 'Via', label: 'Via' },
{ key: 'Warning', label: 'Warning' },
{ key: 'Dnt', label: 'Dnt' },
{ key: 'X-Requested-With', label: 'X-Requested-With' },
{ key: 'X-CSRF-Token', label: 'X-CSRF-Token' }
];
enum TabEnum {
params = 'params',
headers = 'headers',
@@ -342,47 +383,6 @@ const RenderForm = ({
const [shouldUpdateNode, setShouldUpdateNode] = useState(false);
const leftVariables = useMemo(() => {
const HttpHeaders = [
{ key: 'A-IM', label: 'A-IM' },
{ key: 'Accept', label: 'Accept' },
{ key: 'Accept-Charset', label: 'Accept-Charset' },
{ key: 'Accept-Encoding', label: 'Accept-Encoding' },
{ key: 'Accept-Language', label: 'Accept-Language' },
{ key: 'Accept-Datetime', label: 'Accept-Datetime' },
{ key: 'Access-Control-Request-Method', label: 'Access-Control-Request-Method' },
{ key: 'Access-Control-Request-Headers', label: 'Access-Control-Request-Headers' },
{ key: 'Authorization', label: 'Authorization' },
{ key: 'Cache-Control', label: 'Cache-Control' },
{ key: 'Connection', label: 'Connection' },
{ key: 'Content-Length', label: 'Content-Length' },
{ key: 'Content-Type', label: 'Content-Type' },
{ key: 'Cookie', label: 'Cookie' },
{ key: 'Date', label: 'Date' },
{ key: 'Expect', label: 'Expect' },
{ key: 'Forwarded', label: 'Forwarded' },
{ key: 'From', label: 'From' },
{ key: 'Host', label: 'Host' },
{ key: 'If-Match', label: 'If-Match' },
{ key: 'If-Modified-Since', label: 'If-Modified-Since' },
{ key: 'If-None-Match', label: 'If-None-Match' },
{ key: 'If-Range', label: 'If-Range' },
{ key: 'If-Unmodified-Since', label: 'If-Unmodified-Since' },
{ key: 'Max-Forwards', label: 'Max-Forwards' },
{ key: 'Origin', label: 'Origin' },
{ key: 'Pragma', label: 'Pragma' },
{ key: 'Proxy-Authorization', label: 'Proxy-Authorization' },
{ key: 'Range', label: 'Range' },
{ key: 'Referer', label: 'Referer' },
{ key: 'TE', label: 'TE' },
{ key: 'User-Agent', label: 'User-Agent' },
{ key: 'Upgrade', label: 'Upgrade' },
{ key: 'Via', label: 'Via' },
{ key: 'Warning', label: 'Warning' },
{ key: 'Dnt', label: 'Dnt' },
{ key: 'X-Requested-With', label: 'X-Requested-With' },
{ key: 'X-CSRF-Token', label: 'X-CSRF-Token' }
];
return (tabType === TabEnum.headers ? HttpHeaders : variables).filter((variable) => {
const existVariables = list.map((item) => item.key);
return !existVariables.includes(variable.key);
@@ -455,7 +455,7 @@ const RenderForm = ({
};
return (
<TableContainer>
<TableContainer overflowY={'visible'} overflowX={'unset'}>
<Table>
<Thead>
<Tr>

View File

@@ -62,44 +62,48 @@ const NodePluginInput = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
position={'relative'}
mb={7}
>
<MyIcon
name={'common/settingLight'}
w={'14px'}
cursor={'pointer'}
mr={3}
_hover={{ color: 'primary.500' }}
onClick={() =>
setEditField({
inputType: item.type,
valueType: item.valueType,
key: item.key,
label: item.label,
description: item.description,
required: item.required,
isToolInput: !!item.toolDescription
})
}
/>
<MyIcon
className="delete"
name={'delete'}
w={'14px'}
cursor={'pointer'}
mr={3}
_hover={{ color: 'red.500' }}
onClick={() => {
onChangeNode({
moduleId,
type: 'delInput',
key: item.key
});
onChangeNode({
moduleId,
type: 'delOutput',
key: item.key
});
}}
/>
{item.edit && (
<>
<MyIcon
name={'common/settingLight'}
w={'14px'}
cursor={'pointer'}
mr={3}
_hover={{ color: 'primary.500' }}
onClick={() =>
setEditField({
inputType: item.type,
valueType: item.valueType,
key: item.key,
label: item.label,
description: item.description,
required: item.required,
isToolInput: !!item.toolDescription
})
}
/>
<MyIcon
className="delete"
name={'delete'}
w={'14px'}
cursor={'pointer'}
mr={3}
_hover={{ color: 'red.500' }}
onClick={() => {
onChangeNode({
moduleId,
type: 'delInput',
key: item.key
});
onChangeNode({
moduleId,
type: 'delOutput',
key: item.key
});
}}
/>
</>
)}
{item.description && (
<MyTooltip label={t(item.description)} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} mr={1} />

View File

@@ -10,7 +10,12 @@ import RenderToolInput from '../render/RenderToolInput';
import { useTranslation } from 'next-i18next';
import { useFlowProviderStore } from '../../FlowProvider';
const NodeSimple = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
const NodeSimple = ({
data,
selected,
minW = '350px',
maxW
}: NodeProps<FlowModuleItemType> & { minW?: string | number; maxW?: string | number }) => {
const { t } = useTranslation();
const { splitToolInputs } = useFlowProviderStore();
const { moduleId, inputs, outputs } = data;
@@ -22,7 +27,7 @@ const NodeSimple = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
);
return (
<NodeCard minW={'350px'} selected={selected} {...data}>
<NodeCard minW={minW} maxW={maxW} selected={selected} {...data}>
{toolInputs.length > 0 && (
<>
<Divider text={t('core.module.tool.Tool input')} />

View File

@@ -10,10 +10,10 @@ import {
Textarea
} from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { DYNAMIC_INPUT_KEY, ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
import { useTranslation } from 'next-i18next';
import { FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import {
FlowNodeInputTypeEnum,
FlowNodeOutputTypeEnum
@@ -265,10 +265,6 @@ const FieldEditModal = ({
placeholder="appointment/sql"
{...register('key', {
required: true,
pattern: {
value: /^[a-zA-Z]+[0-9]*$/,
message: '字段key必须是纯英文字母或数字并且不能以数字开头。'
},
onChange: (e) => {
const value = e.target.value;
// auto fill label

View File

@@ -6,13 +6,7 @@ import type { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
import { useTranslation } from 'next-i18next';
import { useEditTitle } from '@/web/common/hooks/useEditTitle';
import { useToast } from '@fastgpt/web/hooks/useToast';
import {
onChangeNode,
onCopyNode,
onResetNode,
onDelNode,
useFlowProviderStore
} from '../../FlowProvider';
import { onChangeNode, onCopyNode, onResetNode, useFlowProviderStore } from '../../FlowProvider';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { useSystemStore } from '@/web/common/system/useSystemStore';
@@ -26,6 +20,7 @@ import { useEditTextarea } from '@fastgpt/web/hooks/useEditTextarea';
type Props = FlowModuleItemType & {
children?: React.ReactNode | React.ReactNode[] | string;
minW?: string | number;
maxW?: string | number;
forbidMenu?: boolean;
selected?: boolean;
};
@@ -38,6 +33,7 @@ const NodeCard = (props: Props) => {
name = t('core.module.template.UnKnow Module'),
intro,
minW = '300px',
maxW = '600px',
moduleId,
flowType,
inputs,
@@ -48,21 +44,25 @@ const NodeCard = (props: Props) => {
const { toast } = useToast();
const { setLoading } = useSystemStore();
const { nodes, splitToolInputs } = useFlowProviderStore();
const { nodes, splitToolInputs, onDelNode } = useFlowProviderStore();
// edit intro
const { onOpenModal: onOpenIntroModal, EditModal: EditIntroModal } = useEditTextarea({
title: t('core.module.Edit intro'),
tip: '调整该模块会对工具调用时机有影响。\n你可以通过精确的描述该模块功能引导模型进行工具调用。',
canEmpty: false
});
// custom title edit
const { onOpenModal, EditModal: EditTitleModal } = useEditTitle({
title: t('common.Custom Title'),
placeholder: t('app.module.Custom Title Tip') || ''
});
const { openConfirm, ConfirmModal } = useConfirm({
const { openConfirm: onOpenConfirmSync, ConfirmModal: ConfirmSyncModal } = useConfirm({
content: t('module.Confirm Sync Plugin')
});
const { openConfirm: onOpenConfirmDeleteNode, ConfirmModal: ConfirmDeleteModal } = useConfirm({
content: t('core.module.Confirm Delete Node'),
type: 'delete'
});
const showToolHandle = useMemo(
() => isTool && !!nodes.find((item) => item.data?.flowType === FlowNodeTypeEnum.tools),
@@ -86,7 +86,7 @@ const NodeCard = (props: Props) => {
(item) => item.key === ModuleInputKeyEnum.pluginId
)?.value;
if (!pluginId) return;
openConfirm(async () => {
onOpenConfirmSync(async () => {
try {
setLoading(true);
const pluginModule = await getPreviewPluginModule(pluginId);
@@ -140,7 +140,7 @@ const NodeCard = (props: Props) => {
icon: 'delete',
label: t('common.Delete'),
variant: 'whiteDanger',
onClick: () => onDelNode(moduleId)
onClick: onOpenConfirmDeleteNode(() => onDelNode(moduleId))
}
];
@@ -211,21 +211,23 @@ const NodeCard = (props: Props) => {
</Box>
);
}, [
avatar,
flowType,
forbidMenu,
inputs,
intro,
moduleId,
moduleIsTool,
name,
onOpenIntroModal,
onOpenModal,
openConfirm,
setLoading,
showToolHandle,
t,
toast
onOpenConfirmDeleteNode,
showToolHandle,
moduleId,
avatar,
name,
forbidMenu,
intro,
moduleIsTool,
inputs,
onOpenConfirmSync,
setLoading,
toast,
onOpenModal,
onDelNode,
onOpenIntroModal
]);
const RenderModal = useMemo(() => {
@@ -233,15 +235,16 @@ const NodeCard = (props: Props) => {
<>
<EditTitleModal maxLength={20} />
{moduleIsTool && <EditIntroModal maxLength={500} />}
<ConfirmModal />
<ConfirmSyncModal />
<ConfirmDeleteModal />
</>
);
}, [ConfirmModal, EditIntroModal, EditTitleModal, moduleIsTool]);
}, [ConfirmDeleteModal, ConfirmSyncModal, EditIntroModal, EditTitleModal, moduleIsTool]);
return (
<Box
minW={minW}
maxW={'600px'}
maxW={maxW}
bg={'white'}
borderWidth={'1px'}
borderColor={selected ? 'primary.600' : 'borderColor.base'}

View File

@@ -42,12 +42,12 @@ const RenderList: {
Component: dynamic(() => import('./templates/SelectApp'))
},
{
types: [FlowNodeInputTypeEnum.aiSettings],
Component: dynamic(() => import('./templates/AiSetting'))
types: [FlowNodeInputTypeEnum.selectLLMModel],
Component: dynamic(() => import('./templates/SelectLLMModel'))
},
{
types: [FlowNodeInputTypeEnum.selectLLMModel],
Component: dynamic(() => import('./templates/SelectAiModel'))
types: [FlowNodeInputTypeEnum.settingLLMModel],
Component: dynamic(() => import('./templates/SettingLLMModel'))
},
{
types: [FlowNodeInputTypeEnum.selectDataset],
@@ -64,6 +64,10 @@ const RenderList: {
{
types: [FlowNodeInputTypeEnum.JSONEditor],
Component: dynamic(() => import('./templates/JsonEditor'))
},
{
types: [FlowNodeInputTypeEnum.settingDatasetQuotePrompt],
Component: dynamic(() => import('./templates/SettingQuotePrompt'))
}
];
const UserChatInput = dynamic(() => import('./templates/UserChatInput'));

View File

@@ -1,63 +0,0 @@
import React, { useMemo } from 'react';
import type { RenderInputProps } from '../type';
import { onChangeNode } from '../../../../FlowProvider';
import { useTranslation } from 'next-i18next';
import { Button, useDisclosure } from '@chakra-ui/react';
import { AIChatModuleProps } from '@fastgpt/global/core/module/node/type';
import MyIcon from '@fastgpt/web/components/common/Icon';
import AIChatSettingsModal from '@/components/core/module/AIChatSettingsModal';
const AiSettingRender = ({ inputs = [], moduleId }: RenderInputProps) => {
const { t } = useTranslation();
const chatModulesData = useMemo(() => {
const obj: Record<string, any> = {};
inputs.forEach((item) => {
obj[item.key] = item.value;
});
return obj as AIChatModuleProps;
}, [inputs]);
const {
isOpen: isOpenAIChatSetting,
onOpen: onOpenAIChatSetting,
onClose: onCloseAIChatSetting
} = useDisclosure();
return (
<>
<Button
variant={'whitePrimary'}
leftIcon={<MyIcon name={'common/settingLight'} w={'14px'} />}
onClick={onOpenAIChatSetting}
>
{t('app.AI Settings')}
</Button>
{isOpenAIChatSetting && (
<AIChatSettingsModal
isAdEdit
onClose={onCloseAIChatSetting}
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]
}
});
}
onCloseAIChatSetting();
}}
defaultData={chatModulesData}
/>
)}
</>
);
};
export default React.memo(AiSettingRender);

View File

@@ -1,83 +0,0 @@
import React, { useCallback, useEffect } from 'react';
import type { RenderInputProps } from '../type';
import { onChangeNode } from '../../../../FlowProvider';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import SelectAiModel from '@/components/Select/SelectAiModel';
import { llmModelTypeFilterMap } from '@fastgpt/global/core/ai/constants';
const SelectAiModelRender = ({ item, inputs = [], moduleId }: RenderInputProps) => {
const { llmModelList } = useSystemStore();
const modelList = llmModelList
.filter((model) => {
if (!item.llmModelType) return true;
const filterField = llmModelTypeFilterMap[item.llmModelType];
if (!filterField) return true;
//@ts-ignore
return !!model[filterField];
})
.map((item) => ({
model: item.model,
name: item.name,
maxResponse: item.maxResponse
}));
const onChangeModel = useCallback(
(e: string) => {
onChangeNode({
moduleId,
type: 'updateInput',
key: item.key,
value: {
...item,
value: e
}
});
// update max tokens
const model = modelList.find((item) => item.model === e) || modelList[0];
if (!model) return;
onChangeNode({
moduleId,
type: 'updateInput',
key: 'maxToken',
value: {
...inputs.find((input) => input.key === 'maxToken'),
markList: [
{ label: '100', value: 100 },
{ label: `${model.maxResponse}`, value: model.maxResponse }
],
max: model.maxResponse,
value: model.maxResponse / 2
}
});
},
[inputs, item, modelList, moduleId]
);
const list = modelList.map((item) => {
return {
value: item.model,
label: item.name
};
});
useEffect(() => {
if (!item.value && list.length > 0) {
onChangeModel(list[0].value);
}
}, [item.value, list, onChangeModel]);
return (
<SelectAiModel
minW={'350px'}
width={'100%'}
value={item.value}
list={list}
onchange={onChangeModel}
/>
);
};
export default React.memo(SelectAiModelRender);

View File

@@ -0,0 +1,54 @@
import React, { useCallback, useEffect } from 'react';
import type { RenderInputProps } from '../type';
import { onChangeNode } from '../../../../FlowProvider';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { llmModelTypeFilterMap } from '@fastgpt/global/core/ai/constants';
import AIModelSelector from '@/components/Select/AIModelSelector';
const SelectAiModelRender = ({ item, moduleId }: RenderInputProps) => {
const { llmModelList } = useSystemStore();
const modelList = llmModelList.filter((model) => {
if (!item.llmModelType) return true;
const filterField = llmModelTypeFilterMap[item.llmModelType];
if (!filterField) return true;
//@ts-ignore
return !!model[filterField];
});
const onChangeModel = useCallback(
(e: string) => {
onChangeNode({
moduleId,
type: 'updateInput',
key: item.key,
value: {
...item,
value: e
}
});
},
[item, moduleId]
);
useEffect(() => {
if (!item.value && modelList.length > 0) {
onChangeModel(modelList[0].model);
}
}, [item.value, modelList, onChangeModel]);
return (
<AIModelSelector
minW={'350px'}
width={'100%'}
value={item.value}
list={modelList.map((item) => ({
value: item.model,
label: item.name
}))}
onchange={onChangeModel}
/>
);
};
export default React.memo(SelectAiModelRender);

View File

@@ -0,0 +1,49 @@
import React, { useCallback } from 'react';
import type { RenderInputProps } from '../type';
import { onChangeNode } from '../../../../FlowProvider';
import { SettingAIDataType } from '@fastgpt/global/core/module/node/type';
import SettingLLMModel from '@/components/core/ai/SettingLLMModel';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
const SelectAiModelRender = ({ item, inputs = [], moduleId }: RenderInputProps) => {
const onChangeModel = useCallback(
(e: SettingAIDataType) => {
for (const key in e) {
const input = inputs.find((input) => input.key === key);
input &&
onChangeNode({
moduleId,
type: 'updateInput',
key,
value: {
...input,
// @ts-ignore
value: e[key]
}
});
}
},
[inputs, moduleId]
);
const llmModelData: SettingAIDataType = {
model: inputs.find((input) => input.key === ModuleInputKeyEnum.aiModel)?.value ?? '',
maxToken:
inputs.find((input) => input.key === ModuleInputKeyEnum.aiChatMaxToken)?.value ?? 2048,
temperature:
inputs.find((input) => input.key === ModuleInputKeyEnum.aiChatTemperature)?.value ?? 1,
isResponseAnswerText: inputs.find(
(input) => input.key === ModuleInputKeyEnum.aiChatIsResponseText
)?.value
};
return (
<SettingLLMModel
llmModelType={item.llmModelType}
defaultData={llmModelData}
onChange={onChangeModel}
/>
);
};
export default React.memo(SelectAiModelRender);

View File

@@ -0,0 +1,243 @@
import React, { useMemo, useState } from 'react';
import type { RenderInputProps } from '../type';
import { Box, BoxProps, Button, Flex, ModalFooter, useDisclosure } from '@chakra-ui/react';
import { onChangeNode, useFlowProviderStore } from '../../../../FlowProvider';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useForm } from 'react-hook-form';
import { PromptTemplateItem } from '@fastgpt/global/core/ai/type';
import { useTranslation } from 'next-i18next';
import {
formatEditorVariablePickerIcon,
getGuideModule,
splitGuideModule
} from '@fastgpt/global/core/module/utils';
import { ModalBody } from '@chakra-ui/react';
import MyTooltip from '@/components/MyTooltip';
import {
Prompt_QuotePromptList,
Prompt_QuoteTemplateList
} from '@fastgpt/global/core/ai/prompt/AIChat';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import PromptTemplate from '@/components/PromptTemplate';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
const SettingQuotePrompt = ({ inputs = [], moduleId }: RenderInputProps) => {
const { t } = useTranslation();
const { isOpen, onOpen, onClose } = useDisclosure();
const { nodes } = useFlowProviderStore();
const { watch, setValue, handleSubmit } = useForm({
defaultValues: {
quoteTemplate: inputs.find((input) => input.key === 'quoteTemplate')?.value || '',
quotePrompt: inputs.find((input) => input.key === 'quotePrompt')?.value || ''
}
});
const aiChatQuoteTemplate = watch('quoteTemplate');
const aiChatQuotePrompt = watch('quotePrompt');
const variables = useMemo(() => {
const globalVariables = formatEditorVariablePickerIcon(
splitGuideModule(getGuideModule(nodes.map((node) => node.data)))?.variableModules || []
);
const moduleVariables = formatEditorVariablePickerIcon(
inputs
.filter((input) => input.edit)
.map((item) => ({
key: item.key,
label: item.label
}))
);
const systemVariables = [
{
key: 'cTime',
label: t('core.module.http.Current time')
}
];
return [...globalVariables, ...moduleVariables, ...systemVariables];
}, [inputs, t]);
const [selectTemplateData, setSelectTemplateData] = useState<{
title: string;
templates: PromptTemplateItem[];
}>();
const quoteTemplateVariables = (() => [
{
key: 'q',
label: 'q',
icon: 'core/app/simpleMode/variable'
},
{
key: 'a',
label: 'a',
icon: 'core/app/simpleMode/variable'
},
{
key: 'source',
label: t('core.dataset.search.Source name'),
icon: 'core/app/simpleMode/variable'
},
{
key: 'sourceId',
label: t('core.dataset.search.Source id'),
icon: 'core/app/simpleMode/variable'
},
{
key: 'index',
label: t('core.dataset.search.Quote index'),
icon: 'core/app/simpleMode/variable'
},
...variables
])();
const quotePromptVariables = (() => [
{
key: 'quote',
label: t('core.app.Quote templates'),
icon: 'core/app/simpleMode/variable'
},
{
key: 'question',
label: t('core.module.input.label.user question'),
icon: 'core/app/simpleMode/variable'
},
...variables
])();
const LabelStyles: BoxProps = {
fontSize: ['sm', 'md']
};
const selectTemplateBtn: BoxProps = {
color: 'primary.500',
cursor: 'pointer'
};
const onSubmit = (data: { quoteTemplate: string; quotePrompt: string }) => {
const quoteTemplateInput = inputs.find(
(input) => input.key === ModuleInputKeyEnum.aiChatQuoteTemplate
);
const quotePromptInput = inputs.find(
(input) => input.key === ModuleInputKeyEnum.aiChatQuotePrompt
);
if (quoteTemplateInput) {
onChangeNode({
moduleId,
type: 'updateInput',
key: quoteTemplateInput.key,
value: {
...quoteTemplateInput,
value: data.quoteTemplate
}
});
}
if (quotePromptInput) {
onChangeNode({
moduleId,
type: 'updateInput',
key: quotePromptInput.key,
value: {
...quotePromptInput,
value: data.quotePrompt
}
});
}
onClose();
};
return (
<>
<Button variant={'whitePrimary'} size={'sm'} onClick={onOpen}>
{t('core.module.Setting quote prompt')}
</Button>
<MyModal
isOpen={isOpen}
iconSrc={'modal/edit'}
title={t('core.module.Quote prompt setting')}
w={'600px'}
>
<ModalBody>
<Box>
<Flex {...LabelStyles} mb={1}>
{t('core.app.Quote templates')}
<MyTooltip
label={t('template.Quote Content Tip', {
default: Prompt_QuoteTemplateList[0].value
})}
forceShow
>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
<Box flex={1} />
<Box
{...selectTemplateBtn}
onClick={() =>
setSelectTemplateData({
title: t('core.app.Select quote template'),
templates: Prompt_QuoteTemplateList
})
}
>
{t('common.Select template')}
</Box>
</Flex>
<PromptEditor
variables={quoteTemplateVariables}
h={160}
title={t('core.app.Quote templates')}
placeholder={t('template.Quote Content Tip', {
default: Prompt_QuoteTemplateList[0].value
})}
value={aiChatQuoteTemplate}
onChange={(e) => {
setValue('quoteTemplate', e);
}}
/>
</Box>
<Box mt={4}>
<Flex {...LabelStyles} mb={1}>
{t('core.app.Quote prompt')}
<MyTooltip
label={t('template.Quote Prompt Tip', { default: Prompt_QuotePromptList[0].value })}
forceShow
>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Flex>
<PromptEditor
variables={quotePromptVariables}
title={t('core.app.Quote prompt')}
h={280}
placeholder={t('template.Quote Prompt Tip', {
default: Prompt_QuotePromptList[0].value
})}
value={aiChatQuotePrompt}
onChange={(e) => {
setValue('quotePrompt', e);
}}
/>
</Box>
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} mr={2} onClick={onClose}>
{t('common.Close')}
</Button>
<Button onClick={handleSubmit(onSubmit)}>{t('common.Confirm')}</Button>
</ModalFooter>
</MyModal>
{!!selectTemplateData && (
<PromptTemplate
title={selectTemplateData.title}
templates={selectTemplateData.templates}
onClose={() => setSelectTemplateData(undefined)}
onSuccess={(e) => {
const quoteVal = e.value;
const promptVal = Prompt_QuotePromptList.find((item) => item.title === e.title)?.value;
setValue('quoteTemplate', quoteVal);
setValue('quotePrompt', promptVal);
}}
/>
)}
</>
);
};
export default React.memo(SettingQuotePrompt);

View File

@@ -1,5 +1,5 @@
import React, { useCallback, useRef } from 'react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import type { EditFieldModalProps } from './type.d';
import { useTranslation } from 'next-i18next';
import {

View File

@@ -1,7 +1,7 @@
import React, { useMemo } from 'react';
import { Box, BoxProps } from '@chakra-ui/react';
import { Handle, Position } from 'reactflow';
import { FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import MyTooltip from '@/components/MyTooltip';
import { useTranslation } from 'next-i18next';
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';

View File

@@ -1,7 +1,7 @@
import React, { useMemo } from 'react';
import { Box, BoxProps } from '@chakra-ui/react';
import { Handle, OnConnect, Position } from 'reactflow';
import { FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import MyTooltip from '@/components/MyTooltip';
import { useTranslation } from 'next-i18next';
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';

View File

@@ -1,5 +1,5 @@
import MyTooltip from '@/components/MyTooltip';
import { FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import { Box, BoxProps } from '@chakra-ui/react';
import {
ModuleIOValueTypeEnum,

View File

@@ -1,5 +1,11 @@
import React, { useCallback, useMemo } from 'react';
import ReactFlow, { Background, Connection, Controls, ReactFlowProvider } from 'reactflow';
import ReactFlow, {
Background,
Connection,
Controls,
NodeProps,
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';
@@ -7,12 +13,13 @@ import { EDGE_TYPE, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/co
import dynamic from 'next/dynamic';
import ButtonEdge from './components/modules/ButtonEdge';
import ModuleTemplateList, { type ModuleTemplateProps } from './ModuleTemplateList';
import ModuleTemplateList from './ModuleTemplateList';
import { useFlowProviderStore } from './FlowProvider';
import 'reactflow/dist/style.css';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useTranslation } from 'next-i18next';
import { FlowModuleItemType } from '@fastgpt/global/core/module/type';
const NodeSimple = dynamic(() => import('./components/nodes/NodeSimple'));
const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = {
@@ -34,7 +41,10 @@ const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = {
[FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./components/nodes/NodePluginOutput')),
[FlowNodeTypeEnum.pluginModule]: NodeSimple,
[FlowNodeTypeEnum.queryExtension]: NodeSimple,
[FlowNodeTypeEnum.tools]: dynamic(() => import('./components/nodes/NodeTools'))
[FlowNodeTypeEnum.tools]: dynamic(() => import('./components/nodes/NodeTools')),
[FlowNodeTypeEnum.stopTool]: (data: NodeProps<FlowModuleItemType>) => (
<NodeSimple {...data} minW={'100px'} maxW={'300px'} />
)
};
const edgeTypes = {
[EDGE_TYPE]: ButtonEdge
@@ -99,11 +109,7 @@ const Container = React.memo(function Container() {
);
});
const Flow = ({
Header,
templates,
...data
}: ModuleTemplateProps & { Header: React.ReactNode }) => {
const Flow = ({ Header, ...data }: { Header: React.ReactNode }) => {
const {
isOpen: isOpenTemplate,
onOpen: onOpenTemplate,
@@ -143,14 +149,10 @@ const Flow = ({
<Container {...data} />
<ModuleTemplateList
templates={templates}
isOpen={isOpenTemplate}
onClose={onCloseTemplate}
/>
<ModuleTemplateList isOpen={isOpenTemplate} onClose={onCloseTemplate} />
</Box>
);
}, [data, isOpenTemplate, onCloseTemplate, onOpenTemplate, templates]);
}, [data, isOpenTemplate, onCloseTemplate, onOpenTemplate]);
return (
<Box h={'100%'} position={'fixed'} zIndex={999} top={0} left={0} right={0} bottom={0}>