V4.6.5-alpha (#609)

This commit is contained in:
Archer
2023-12-15 15:57:39 +08:00
committed by GitHub
parent dd7b4b98ae
commit 05bf1b2265
127 changed files with 4283 additions and 2315 deletions

View File

@@ -6,7 +6,7 @@ import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constant';
import { ModuleDataTypeEnum, ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { ModuleIOValueTypeEnum, ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { ModuleItemType } from '@fastgpt/global/core/module/type';
let success = 0;
@@ -52,7 +52,7 @@ export async function initApp(limit = 50): Promise<any> {
key: ModuleInputKeyEnum.datasetSearchMode,
type: FlowNodeInputTypeEnum.hidden,
label: 'core.dataset.search.Mode',
valueType: ModuleDataTypeEnum.string,
valueType: ModuleIOValueTypeEnum.string,
showTargetInApp: false,
showTargetInPlugin: false,
value: val

View File

@@ -7,7 +7,7 @@ import { jsonRes } from '@fastgpt/service/common/response';
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import type { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type.d';
import { FormatForm2ModulesProps } from '@fastgpt/global/core/app/api';
@@ -317,7 +317,7 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
key: ModuleInputKeyEnum.answerText,
value: formData.dataset.searchEmptyText,
type: FlowNodeInputTypeEnum.textarea,
valueType: ModuleDataTypeEnum.string,
valueType: ModuleIOValueTypeEnum.string,
label: '回复的内容',
connected: true
}

View File

@@ -6,11 +6,10 @@ import { addLog } from '@fastgpt/service/common/system/log';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { findDatasetIdTreeByTopDatasetId } from '@fastgpt/service/core/dataset/controller';
import { Readable } from 'stream';
import type { Cursor } from '@fastgpt/service/common/mongo';
import { limitCheck } from './checkExportLimit';
import { withNextCors } from '@fastgpt/service/common/middle/cors';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
let { datasetId } = req.query as {
@@ -81,7 +80,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
error: err
});
}
}
});
export const config = {
api: {

View File

@@ -33,7 +33,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
})),
...(global.communityPlugins?.map((plugin) => ({
id: plugin.id,
templateType: ModuleTemplateTypeEnum.communityPlugin,
templateType: plugin.templateType ?? ModuleTemplateTypeEnum.other,
flowType: FlowNodeTypeEnum.pluginModule,
avatar: plugin.avatar,
name: plugin.name,

View File

@@ -0,0 +1,69 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import type { HttpBodyType } from '@fastgpt/global/core/module/api.d';
import { getErrText } from '@fastgpt/global/common/error/utils';
type Props = HttpBodyType<{
input: string;
rule?: string;
}>;
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const {
data: { input, rule = '' }
} = req.body as Props;
const result = (() => {
if (typeof input === 'string') {
const defaultReg: any[] = [
undefined,
'undefined',
null,
'null',
false,
'false',
0,
'0',
'none'
];
const customReg = rule.split('\n');
defaultReg.push(...customReg);
return !defaultReg.find((item) => {
const reg = typeof item === 'string' ? stringToRegex(item) : null;
if (reg) {
return reg.test(input);
}
return input === item;
});
}
return !!input;
})();
res.json({
...(result
? {
true: true
}
: {
false: false
})
});
} catch (err) {
console.log(err);
res.status(500).send(getErrText(err));
}
}
function stringToRegex(str: string) {
const regexFormat = /^\/(.+)\/([gimuy]*)$/;
const match = str.match(regexFormat);
if (match) {
const [, pattern, flags] = match;
return new RegExp(pattern, flags);
} else {
return null;
}
}

View File

@@ -0,0 +1,26 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import type { HttpBodyType } from '@fastgpt/global/core/module/api.d';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { replaceVariable } from '@fastgpt/global/common/string/tools';
type Props = HttpBodyType<{
text: string;
[key: string]: any;
}>;
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const {
data: { text, ...obj }
} = req.body as Props;
const textResult = replaceVariable(text, obj);
res.json({
text: textResult
});
} catch (err) {
console.log(err);
res.status(500).send(getErrText(err));
}
}

View File

@@ -19,9 +19,10 @@ import {
} from '@fastgpt/global/core/ai/model';
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
import { getSimpleTemplatesFromPlus } from '@/service/core/app/utils';
import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants';
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
import { getFastGPTFeConfig } from '@fastgpt/service/common/system/config/controller';
import { connectToDatabase } from '@/service/mongo';
import { PluginTemplateType } from '@fastgpt/global/core/plugin/type';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
await getInitConfig();
@@ -251,12 +252,12 @@ function getSystemPlugin() {
const filterFiles = files.filter((item) => item.endsWith('.json'));
// read json file
const fileTemplates = filterFiles.map((item) => {
const content = readFileSync(`${basePath}/${item}`, 'utf-8');
const fileTemplates: PluginTemplateType[] = filterFiles.map((filename) => {
const content = readFileSync(`${basePath}/${filename}`, 'utf-8');
return {
id: `${PluginTypeEnum.community}-${item.replace('.json', '')}`,
type: PluginTypeEnum.community,
...JSON.parse(content)
...JSON.parse(content),
id: `${PluginSourceEnum.community}-${filename.replace('.json', '')}`,
source: PluginSourceEnum.community
};
});

View File

@@ -138,6 +138,11 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
// openapi key
if (authType === AuthUserTypeEnum.apikey) {
if (!apiKeyAppId) {
return Promise.reject(
'Key is error. You need to use the app key rather than the account key.'
);
}
const app = await MongoApp.findById(apiKeyAppId);
if (!app) {

View File

@@ -27,7 +27,8 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
const { total } = pushReRankBill({
teamId,
tmbId,
source: 'api'
source: 'api',
inputs
});
if (apikey) {

View File

@@ -12,9 +12,11 @@ import dynamic from 'next/dynamic';
import MyIcon from '@/components/Icon';
import MyTooltip from '@/components/MyTooltip';
import ChatTest, { type ChatTestComponentRef } from '@/components/core/module/Flow/ChatTest';
import { flowNode2Modules, useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider';
import { useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider';
import { flowNode2Modules } from '@/components/core/module/utils';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { useToast } from '@/web/common/hooks/useToast';
import { useConfirm } from '@/web/common/hooks/useConfirm';
const ImportSettings = dynamic(() => import('@/components/core/module/Flow/ImportSettings'));
@@ -35,42 +37,47 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
const { toast } = useToast();
const { t } = useTranslation();
const { copyData } = useCopyData();
const { openConfirm: openConfirmOut, ConfirmModal } = useConfirm({
content: t('core.app.edit.Out Ad Edit')
});
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
const { updateAppDetail } = useAppStore();
const { nodes, edges, onFixView } = useFlowProviderStore();
const { nodes, edges } = useFlowProviderStore();
const flow2ModulesAndCheck = useCallback(
(tip = false) => {
const modules = flowNode2Modules({ nodes, edges });
// check required connect
for (let i = 0; i < modules.length; i++) {
const item = modules[i];
if (
item.inputs.find((input) => {
if (!input.required || input.connected) return false;
if (!input.value || input.value === '' || input.value?.length === 0) return true;
return false;
})
) {
const msg = `${item.name}】存在未填或未连接参数`;
tip &&
toast({
status: 'warning',
title: msg
});
return Promise.reject(msg);
const flow2ModulesAndCheck = useCallback(() => {
const modules = flowNode2Modules({ nodes, edges });
// check required connect
for (let i = 0; i < modules.length; i++) {
const item = modules[i];
const unconnected = item.inputs.find((input) => {
if (!input.required || input.connected) {
return false;
}
if (input.value === undefined || input.value === '' || input.value?.length === 0) {
return true;
}
return false;
});
if (unconnected) {
const msg = `${item.name}】存在未填或未连接参数`;
toast({
status: 'warning',
title: msg
});
return false;
}
return modules;
},
[edges, nodes, toast]
);
}
return modules;
}, [edges, nodes, toast]);
const { mutate: onclickSave, isLoading } = useRequest({
mutationFn: async () => {
mutationFn: async (modules: ModuleItemType[]) => {
return updateAppDetail(app._id, {
modules: await flow2ModulesAndCheck(),
modules: modules,
type: AppTypeEnum.advanced,
permission: undefined
});
@@ -91,7 +98,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
alignItems={'center'}
userSelect={'none'}
>
<MyTooltip label={'返回'} offset={[10, 10]}>
<MyTooltip label={t('common.Back')} offset={[10, 10]}>
<IconButton
size={'sm'}
icon={<MyIcon name={'back'} w={'14px'} />}
@@ -99,10 +106,13 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
borderColor={'myGray.300'}
variant={'base'}
aria-label={''}
onClick={() => {
onClick={openConfirmOut(async () => {
const modules = flow2ModulesAndCheck();
if (modules) {
await onclickSave(modules);
}
onClose();
onFixView();
}}
}, onClose)}
/>
</MyTooltip>
<Box ml={[3, 6]} fontSize={['md', '2xl']} flex={1}>
@@ -153,8 +163,11 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
borderRadius={'lg'}
aria-label={'save'}
variant={'base'}
onClick={async () => {
setTestModules(await flow2ModulesAndCheck(true));
onClick={() => {
const modules = flow2ModulesAndCheck();
if (modules) {
setTestModules(modules);
}
}}
/>
</MyTooltip>
@@ -166,11 +179,20 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
borderRadius={'lg'}
isLoading={isLoading}
aria-label={'save'}
onClick={onclickSave}
onClick={() => {
const modules = flow2ModulesAndCheck();
if (modules) {
onclickSave(modules);
}
}}
/>
</MyTooltip>
</Flex>
{isOpenImport && <ImportSettings onClose={onCloseImport} />}
<ConfirmModal
closeText={t('core.app.edit.UnSave')}
confirmText={t('core.app.edit.Save and out')}
/>
</>
);
});

View File

@@ -15,16 +15,16 @@ const Render = ({ app, onClose }: Props) => {
const { nodes } = useFlowProviderStore();
const { pluginModuleTemplates, loadPluginTemplates } = usePluginStore();
const filterTemplates = useMemo(() => {
const copyTemplates: FlowModuleTemplateType[] = JSON.parse(
JSON.stringify(appSystemModuleTemplates)
);
const moduleTemplates = useMemo(() => {
const concatTemplates = [...appSystemModuleTemplates, ...pluginModuleTemplates];
const copyTemplates: FlowModuleTemplateType[] = JSON.parse(JSON.stringify(concatTemplates));
const filterType: Record<string, 1> = {
[FlowNodeTypeEnum.userGuide]: 1
};
// filter some template
// filter some template, There can only be one
nodes.forEach((node) => {
if (node.type && filterType[node.type]) {
copyTemplates.forEach((module, index) => {
@@ -36,14 +36,13 @@ const Render = ({ app, onClose }: Props) => {
});
return copyTemplates;
}, [nodes]);
}, [nodes, pluginModuleTemplates]);
useQuery(['getPlugTemplates'], () => loadPluginTemplates());
return (
<Flow
systemTemplates={filterTemplates}
pluginTemplates={pluginModuleTemplates}
templates={moduleTemplates}
modules={app.modules}
Header={<Header app={app} onClose={onClose} />}
/>

View File

@@ -48,15 +48,18 @@ import Avatar from '@/components/Avatar';
import MyIcon from '@/components/Icon';
import ChatBox, { type ComponentRef, type StartChatFnProps } from '@/components/ChatBox';
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
import QGSwitch from '@/components/core/module/Flow/components/modules/QGSwitch';
import TTSSelect from '@/components/core/module/Flow/components/modules/TTSSelect';
import VariableEdit from '@/components/core/module/Flow/components/modules/VariableEdit';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import PromptTextarea from '@/components/common/Textarea/PromptTextarea/index';
const InfoModal = dynamic(() => import('../InfoModal'));
const DatasetSelectModal = dynamic(() => import('@/components/core/module/DatasetSelectModal'));
const DatasetParamsModal = dynamic(() => import('@/components/core/module/DatasetParamsModal'));
const AIChatSettingsModal = dynamic(() => import('@/components/core/module/AIChatSettingsModal'));
const TTSSelect = dynamic(
() => import('@/components/core/module/Flow/components/modules/TTSSelect')
);
const QGSwitch = dynamic(() => import('@/components/core/module/Flow/components/modules/QGSwitch'));
function ConfigForm({
divRef,
@@ -100,8 +103,7 @@ function ConfigForm({
} = useDisclosure();
const { openConfirm: openConfirmSave, ConfirmModal: ConfirmSaveModal } = useConfirm({
content: t('app.Confirm Save App Tip'),
bg: appDetail.type === AppTypeEnum.simple ? '' : 'red.600'
content: t('core.app.edit.Confirm Save App Tip')
});
const chatModelSelectList = useMemo(() => {
@@ -330,13 +332,18 @@ function ConfigForm({
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Box>
<Textarea
<PromptTextarea
flex={1}
bg={'myWhite.400'}
rows={5}
minH={'60px'}
placeholder={chatNodeSystemPromptTip}
borderColor={'myGray.100'}
{...register('aiSettings.systemPrompt')}
></Textarea>
showSetModalModeIcon
value={getValues('aiSettings.systemPrompt')}
onChange={(e) => {
setValue('aiSettings.systemPrompt', e.target.value || '');
setRefresh(!refresh);
}}
/>
</Flex>
)}
</Box>
@@ -438,7 +445,7 @@ function ConfigForm({
)}
</Box>
<ConfirmSaveModal />
<ConfirmSaveModal bg={appDetail.type === AppTypeEnum.simple ? '' : 'red.600'} countDown={5} />
{isOpenAIChatSetting && (
<AIChatSettingsModal
onClose={onCloseAIChatSetting}

View File

@@ -26,7 +26,7 @@ import { POST } from '@/web/common/api/request';
import { chatContentReplaceBlock } from '@fastgpt/global/core/chat/utils';
import { useChatStore } from '@/web/core/chat/storeChat';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import { OutLinkErrEnum } from '@fastgpt/global/common/error/code/outLink';
import MyBox from '@/components/common/MyBox';
const OutLink = ({
shareId,
@@ -44,10 +44,10 @@ const OutLink = ({
const { toast } = useToast();
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
const { isPc } = useSystemStore();
const forbidRefresh = useRef(false);
const [isEmbed, setIdEmbed] = useState(true);
const ChatBoxRef = useRef<ComponentRef>(null);
const forbidRefresh = useRef(false);
const initSign = useRef(false);
const [isEmbed, setIdEmbed] = useState(true);
const {
localUId,
@@ -161,7 +161,7 @@ const OutLink = ({
const res = await getInitOutLinkChatInfo({
chatId,
shareId,
outLinkUid: authToken || localUId
outLinkUid
});
const history = res.history.map((item) => ({
...item,
@@ -176,12 +176,21 @@ const OutLink = ({
ChatBoxRef.current?.resetHistory(history);
ChatBoxRef.current?.resetVariables(res.variables);
if (res.history.length > 0) {
// send init message
if (!initSign.current) {
initSign.current = true;
if (window !== top) {
window.top?.postMessage({ type: 'shareChatReady' }, '*');
}
}
if (chatId && res.history.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
} catch (e: any) {
console.log(e);
toast({
status: 'error',
title: getErrText(e, t('core.shareChat.Init Error'))
@@ -194,16 +203,14 @@ const OutLink = ({
}
});
}
if (e?.statusText === OutLinkErrEnum.linkUnInvalid) {
router.replace('/');
}
}
return null;
},
[authToken, localUId, router, setChatData, t, toast]
[outLinkUid, router, setChatData, t, toast]
);
useQuery(['init', shareId, chatId], () => {
const { isFetching } = useQuery(['init', shareId, chatId], () => {
if (forbidRefresh.current) {
forbidRefresh.current = false;
return null;
@@ -223,11 +230,8 @@ const OutLink = ({
return null;
});
// check is embed
// window init
useEffect(() => {
if (window !== top) {
window.top?.postMessage({ type: 'shareChatReady' }, '*');
}
setIdEmbed(window !== top);
}, []);
@@ -258,7 +262,7 @@ const OutLink = ({
<Head>
<title>{chatData.app.name}</title>
</Head>
<Flex h={'100%'} flexDirection={['column', 'row']}>
<MyBox isLoading={isFetching} h={'100%'} display={'flex'} flexDirection={['column', 'row']}>
{showHistory === '1'
? ((children: React.ReactNode) => {
return isPc ? (
@@ -372,7 +376,7 @@ const OutLink = ({
/>
</Box>
</Flex>
</Flex>
</MyBox>
</PageContainer>
);
};

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useCallback } from 'react';
import { Box, Flex, IconButton, useTheme, useDisclosure } from '@chakra-ui/react';
import { PluginItemSchema } from '@fastgpt/global/core/plugin/type';
import { useRequest } from '@/web/common/hooks/useRequest';
@@ -7,10 +7,12 @@ import { useCopyData } from '@/web/common/hooks/useCopyData';
import dynamic from 'next/dynamic';
import MyIcon from '@/components/Icon';
import MyTooltip from '@/components/MyTooltip';
import { flowNode2Modules, useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider';
import { useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider';
import { flowNode2Modules } from '@/components/core/module/utils';
import { putUpdatePlugin } from '@/web/core/plugin/api';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { ModuleItemType } from '@fastgpt/global/core/module/type';
import { useToast } from '@/web/common/hooks/useToast';
const ImportSettings = dynamic(() => import('@/components/core/module/Flow/ImportSettings'));
const PreviewPlugin = dynamic(() => import('./Preview'));
@@ -20,58 +22,83 @@ type Props = { plugin: PluginItemSchema; onClose: () => void };
const Header = ({ plugin, onClose }: Props) => {
const theme = useTheme();
const { t } = useTranslation();
const { toast } = useToast();
const { copyData } = useCopyData();
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
const { nodes, edges, onFixView } = useFlowProviderStore();
const [previewModules, setPreviewModules] = React.useState<ModuleItemType[]>();
const { mutate: onclickSave, isLoading } = useRequest({
mutationFn: () => {
const modules = flowNode2Modules({ nodes, edges });
const flow2ModulesAndCheck = useCallback(() => {
const modules = flowNode2Modules({ nodes, edges });
// check required connect
for (let i = 0; i < modules.length; i++) {
const item = modules[i];
// check required connect
for (let i = 0; i < modules.length; i++) {
const item = modules[i];
// update custom input connected
if (item.flowType === FlowNodeTypeEnum.pluginInput) {
item.inputs.forEach((item) => {
item.connected = true;
// update custom input connected
if (item.flowType === FlowNodeTypeEnum.pluginInput) {
item.inputs.forEach((item) => {
item.connected = true;
});
if (item.outputs.find((output) => output.targets.length === 0)) {
toast({
status: 'warning',
title: t('module.Plugin input must connect')
});
if (item.outputs.find((output) => output.targets.length === 0)) {
return Promise.reject(t('module.Plugin input must connect'));
}
}
if (
item.flowType === FlowNodeTypeEnum.pluginOutput &&
item.inputs.find((input) => !input.connected)
) {
return Promise.reject(t('core.module.Plugin output must connect'));
}
if (
item.inputs.find((input) => {
if (!input.required || input.connected) return false;
if (!input.value || input.value === '' || input.value?.length === 0) return true;
return false;
})
) {
return Promise.reject(`${item.name}】存在未填或未连接参数`);
return false;
}
}
// plugin must have input
const pluginInputModule = modules.find(
(item) => item.flowType === FlowNodeTypeEnum.pluginInput
);
if (!pluginInputModule) {
return Promise.reject(t('module.Plugin input is required'));
}
if (pluginInputModule.inputs.length < 1) {
return Promise.reject(t('module.Plugin input is not value'));
if (
item.flowType === FlowNodeTypeEnum.pluginOutput &&
item.inputs.find((input) => !input.connected)
) {
toast({
status: 'warning',
title: t('core.module.Plugin output must connect')
});
return false;
}
if (
item.inputs.find((input) => {
if (!input.required || input.connected) return false;
if (!input.value || input.value === '' || input.value?.length === 0) return true;
return false;
})
) {
toast({
status: 'warning',
title: `${item.name}】存在未填或未连接参数`
});
return false;
}
}
// plugin must have input
const pluginInputModule = modules.find(
(item) => item.flowType === FlowNodeTypeEnum.pluginInput
);
if (!pluginInputModule) {
toast({
status: 'warning',
title: t('module.Plugin input is required')
});
return false;
}
if (pluginInputModule.inputs.length < 1) {
toast({
status: 'warning',
title: t('module.Plugin input is not value')
});
return false;
}
return modules;
}, [edges, nodes, t, toast]);
const { mutate: onclickSave, isLoading } = useRequest({
mutationFn: (modules: ModuleItemType[]) => {
return putUpdatePlugin({
id: plugin._id,
modules
@@ -90,7 +117,7 @@ const Header = ({ plugin, onClose }: Props) => {
alignItems={'center'}
userSelect={'none'}
>
<MyTooltip label={'返回'} offset={[10, 10]}>
<MyTooltip label={t('common.Back')} offset={[10, 10]}>
<IconButton
size={'sm'}
icon={<MyIcon name={'back'} w={'14px'} />}
@@ -125,12 +152,12 @@ const Header = ({ plugin, onClose }: Props) => {
borderRadius={'lg'}
variant={'base'}
aria-label={'save'}
onClick={() =>
copyData(
JSON.stringify(flowNode2Modules({ nodes, edges }), null, 2),
t('app.Export Config Successful')
)
}
onClick={() => {
const modules = flow2ModulesAndCheck();
if (modules) {
copyData(JSON.stringify(modules, null, 2), t('app.Export Config Successful'));
}
}}
/>
</MyTooltip>
<MyTooltip label={t('module.Preview Plugin')}>
@@ -141,7 +168,10 @@ const Header = ({ plugin, onClose }: Props) => {
aria-label={'save'}
variant={'base'}
onClick={() => {
setPreviewModules(flowNode2Modules({ nodes, edges }));
const modules = flow2ModulesAndCheck();
if (modules) {
setPreviewModules(modules);
}
}}
/>
</MyTooltip>
@@ -151,7 +181,12 @@ const Header = ({ plugin, onClose }: Props) => {
borderRadius={'lg'}
isLoading={isLoading}
aria-label={'save'}
onClick={onclickSave}
onClick={() => {
const modules = flow2ModulesAndCheck();
if (modules) {
onclickSave(modules);
}
}}
/>
</MyTooltip>
</Flex>

View File

@@ -3,7 +3,7 @@ import ReactFlow, { Background, ReactFlowProvider, useNodesState } from 'reactfl
import { FlowModuleItemType, ModuleItemType } from '@fastgpt/global/core/module/type';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import dynamic from 'next/dynamic';
import { formatPluginToPreviewModule } from '@fastgpt/global/core/module/utils';
import { plugin2ModuleIO } from '@fastgpt/global/core/module/utils';
import MyModal from '@/components/MyModal';
import { Box } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
@@ -37,7 +37,7 @@ const PreviewPlugin = ({
avatar: plugin.avatar,
name: plugin.name,
intro: plugin.intro,
...formatPluginToPreviewModule(plugin._id, modules)
...plugin2ModuleIO(plugin._id, modules)
}
})
]);

View File

@@ -24,10 +24,12 @@ const Render = ({ pluginId }: Props) => {
const { nodes = [] } = useFlowProviderStore();
const { pluginModuleTemplates, loadPluginTemplates } = usePluginStore();
const filterTemplates = useMemo(() => {
const copyTemplates: FlowModuleTemplateType[] = JSON.parse(
JSON.stringify(pluginSystemModuleTemplates)
);
const moduleTemplates = useMemo(() => {
const pluginTemplates = pluginModuleTemplates.filter((item) => item.id !== pluginId);
const concatTemplates = [...pluginSystemModuleTemplates, ...pluginTemplates];
const copyTemplates: FlowModuleTemplateType[] = JSON.parse(JSON.stringify(concatTemplates));
const filterType: Record<string, 1> = {
[FlowNodeTypeEnum.userGuide]: 1,
[FlowNodeTypeEnum.pluginInput]: 1,
@@ -45,8 +47,13 @@ const Render = ({ pluginId }: Props) => {
}
});
// filter hideInPlugin inputs
copyTemplates.forEach((template) => {
template.inputs = template.inputs.filter((input) => !input.hideInPlugin);
});
return copyTemplates;
}, [nodes]);
}, [nodes, pluginId, pluginModuleTemplates]);
const { data: pluginDetail } = useQuery(
['getOnePlugin', pluginId],
@@ -61,17 +68,12 @@ const Render = ({ pluginId }: Props) => {
}
}
);
console.log(pluginDetail);
useQuery(['getPlugTemplates'], () => loadPluginTemplates());
const filterPlugins = useMemo(() => {
return pluginModuleTemplates.filter((item) => item.id !== pluginId);
}, [pluginId, pluginModuleTemplates]);
return pluginDetail ? (
<Flow
systemTemplates={filterTemplates}
pluginTemplates={filterPlugins}
templates={moduleTemplates}
modules={pluginDetail?.modules || []}
Header={<Header plugin={pluginDetail} onClose={() => router.back()} />}
/>

View File

@@ -1,14 +1,5 @@
import React, { useCallback, useState } from 'react';
import {
Box,
Flex,
Button,
ModalHeader,
ModalBody,
Input,
Textarea,
IconButton
} from '@chakra-ui/react';
import { Box, Flex, Button, ModalBody, Input, Textarea, IconButton } from '@chakra-ui/react';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { useForm } from 'react-hook-form';
import { compressImgFileAndUpload } from '@/web/common/file/controller';
@@ -25,6 +16,8 @@ import { useTranslation } from 'next-i18next';
import { useConfirm } from '@/web/common/hooks/useConfirm';
import MyIcon from '@/components/Icon';
import { CreateOnePluginParams } from '@fastgpt/global/core/plugin/controller';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
export type FormType = CreateOnePluginParams & {
id?: string;
@@ -35,7 +28,7 @@ export const defaultForm: FormType = {
intro: '',
modules: [
{
moduleId: 'w90mfp',
moduleId: nanoid(),
name: '定义插件输入',
avatar: '/imgs/module/input.png',
flowType: 'pluginInput',
@@ -44,30 +37,11 @@ export const defaultForm: FormType = {
x: 616.4226348688949,
y: -165.05298493910115
},
inputs: [
{
key: 'question',
valueType: 'string',
type: 'target',
label: '用户问题',
required: true,
edit: true,
connected: false
}
],
outputs: [
{
key: 'question',
valueType: 'string',
label: '用户问题',
type: 'source',
edit: true,
targets: []
}
]
inputs: [],
outputs: []
},
{
moduleId: 'tze1ju',
moduleId: nanoid(),
name: '定义插件输出',
avatar: '/imgs/module/output.png',
flowType: 'pluginOutput',
@@ -76,27 +50,8 @@ export const defaultForm: FormType = {
x: 1607.7142331269126,
y: -151.8669210746189
},
inputs: [
{
key: 'answer',
type: 'target',
valueType: 'string',
label: '答案',
required: true,
edit: true,
connected: true
}
],
outputs: [
{
key: 'answer',
valueType: 'string',
label: '答案',
type: 'source',
edit: true,
targets: []
}
]
inputs: [],
outputs: []
}
]
};
@@ -127,7 +82,7 @@ const CreateModal = ({
});
const { File, onOpen: onOpenSelectFile } = useSelectFile({
fileType: '.jpg,.png,.svg',
fileType: 'image/*',
multiple: false
});