add plugin unexist error tips (#3717)

* add plugin unexist error tips

* throw error when run plugin

* check workflow

* plugin data avoid request twice

* auth owner tmbId

* fix
This commit is contained in:
heheer
2025-02-10 15:20:49 +08:00
committed by GitHub
parent 4284b78707
commit 896a3f1472
24 changed files with 284 additions and 91 deletions

View File

@@ -196,7 +196,7 @@ const Header = () => {
<SaveButton
isLoading={loading}
onClickSave={onClickSave}
checkData={flowData2StoreDataAndCheck}
checkData={() => !!flowData2StoreDataAndCheck()}
/>
)}
</HStack>

View File

@@ -30,6 +30,12 @@ import PublishHistories from '../PublishHistoriesSlider';
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload';
import { isProduction } from '@fastgpt/global/common/system/constants';
import { useToast } from '@fastgpt/web/hooks/useToast';
import {
checkWorkflowNodeAndConnection,
storeEdgesRenderEdge,
storeNode2FlowNode
} from '@/web/core/workflow/utils';
const Header = ({
forbiddenSaveSnapshot,
@@ -48,6 +54,7 @@ const Header = ({
}) => {
const { t } = useTranslation();
const { isPc } = useSystem();
const { toast } = useToast();
const router = useRouter();
const appId = useContextSelector(AppContext, (v) => v.appId);
const onSaveApp = useContextSelector(AppContext, (v) => v.onSaveApp);
@@ -231,7 +238,26 @@ const Header = ({
variant={'whitePrimary'}
onClick={setIsShowHistories}
/>
<SaveButton isLoading={loading} onClickSave={onClickSave} />
<SaveButton
isLoading={loading}
onClickSave={onClickSave}
checkData={() => {
const { nodes: storeNodes, edges: storeEdges } = form2AppWorkflow(appForm, t);
const nodes = storeNodes.map((item) => storeNode2FlowNode({ item, t }));
const edges = storeEdges.map((item) => storeEdgesRenderEdge({ edge: item }));
const checkResults = checkWorkflowNodeAndConnection({ nodes, edges });
if (checkResults) {
toast({
title: t('app:app.error.publish_unExist_app'),
status: 'warning'
});
}
return !checkResults;
}}
/>
</>
)}
</Flex>

View File

@@ -14,6 +14,7 @@ import Avatar from '@fastgpt/web/components/common/Avatar';
import ConfigToolModal from './ConfigToolModal';
import { getWebLLMModel } from '@/web/common/system/utils';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import { checkAppUnExistError } from '@fastgpt/global/core/app/utils';
const ToolSelect = ({
appForm,
@@ -60,60 +61,83 @@ const ToolSelect = ({
gridTemplateColumns={'repeat(2, minmax(0, 1fr))'}
gridGap={[2, 4]}
>
{appForm.selectedTools.map((item) => (
<MyTooltip key={item.id} label={item.intro}>
<Flex
overflow={'hidden'}
alignItems={'center'}
p={2.5}
bg={'white'}
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
borderRadius={'md'}
border={theme.borders.base}
_hover={{
...hoverDeleteStyles,
borderColor: 'primary.300'
}}
cursor={'pointer'}
onClick={() => {
if (
item.inputs
.filter((input) => !childAppSystemKey.includes(input.key))
.every(
(input) =>
input.toolDescription ||
input.renderTypeList.includes(FlowNodeInputTypeEnum.selectLLMModel) ||
input.renderTypeList.includes(FlowNodeInputTypeEnum.fileSelect)
)
) {
return;
}
setConfigTool(item);
}}
>
<Avatar src={item.avatar} w={'1.5rem'} h={'1.5rem'} borderRadius={'sm'} />
<Box
ml={2}
flex={'1 0 0'}
w={0}
className={'textEllipsis'}
fontSize={'sm'}
color={'myGray.900'}
>
{item.name}
</Box>
<DeleteIcon
onClick={(e) => {
e.stopPropagation();
setAppForm((state: AppSimpleEditFormType) => ({
...state,
selectedTools: state.selectedTools.filter((tool) => tool.id !== item.id)
}));
{appForm.selectedTools.map((item) => {
const hasError = checkAppUnExistError(item.pluginData?.error);
return (
<MyTooltip key={item.id} label={item.intro}>
<Flex
overflow={'hidden'}
alignItems={'center'}
p={2.5}
bg={'white'}
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
borderRadius={'md'}
border={theme.borders.base}
borderColor={hasError ? 'red.600' : ''}
_hover={{
...hoverDeleteStyles,
borderColor: hasError ? 'red.600' : 'primary.300'
}}
/>
</Flex>
</MyTooltip>
))}
cursor={'pointer'}
onClick={() => {
if (
item.inputs
.filter((input) => !childAppSystemKey.includes(input.key))
.every(
(input) =>
input.toolDescription ||
input.renderTypeList.includes(FlowNodeInputTypeEnum.selectLLMModel) ||
input.renderTypeList.includes(FlowNodeInputTypeEnum.fileSelect)
) ||
hasError
) {
return;
}
setConfigTool(item);
}}
>
<Avatar src={item.avatar} w={'1.5rem'} h={'1.5rem'} borderRadius={'sm'} />
<Box
flex={'1 0 0'}
ml={2}
gap={2}
className={'textEllipsis'}
fontSize={'sm'}
color={'myGray.900'}
>
{item.name}
</Box>
{hasError && (
<MyTooltip label={t('app:app.modules.not_found_tips')}>
<Flex
bg={'red.50'}
alignItems={'center'}
h={6}
px={2}
rounded={'6px'}
fontSize={'xs'}
fontWeight={'medium'}
>
<MyIcon name={'common/errorFill'} w={'14px'} mr={1} />
<Box color={'red.600'}>{t('app:app.modules.not_found')}</Box>
</Flex>
</MyTooltip>
)}
<DeleteIcon
ml={2}
onClick={(e) => {
e.stopPropagation();
setAppForm((state: AppSimpleEditFormType) => ({
...state,
selectedTools: state.selectedTools.filter((tool) => tool.id !== item.id)
}));
}}
/>
</Flex>
</MyTooltip>
);
})}
</Grid>
{isOpenToolsSelect && (

View File

@@ -200,7 +200,7 @@ const Header = () => {
<SaveButton
isLoading={loading}
onClickSave={onClickSave}
checkData={flowData2StoreDataAndCheck}
checkData={() => !!flowData2StoreDataAndCheck()}
/>
)}
</HStack>

View File

@@ -6,8 +6,6 @@ import { useTranslation } from 'next-i18next';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { useToast } from '@fastgpt/web/hooks/useToast';
import SaveAndPublishModal from '../../WorkflowComponents/Flow/components/SaveAndPublish';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
const SaveButton = ({
isLoading,
@@ -16,12 +14,7 @@ const SaveButton = ({
}: {
isLoading: boolean;
onClickSave: (options: { isPublish?: boolean; versionName?: string }) => Promise<void>;
checkData?: (hideTip?: boolean) =>
| {
nodes: StoreNodeItemType[];
edges: StoreEdgeItemType[];
}
| undefined;
checkData?: () => boolean | undefined;
}) => {
const { t } = useTranslation();
const [isSave, setIsSave] = useState(false);

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useMemo, useState } from 'react';
import React, { useCallback, useMemo } from 'react';
import { Box, Button, Card, Flex, FlexProps } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import Avatar from '@fastgpt/web/components/common/Avatar';
@@ -99,14 +99,15 @@ const NodeCard = (props: Props) => {
const { data: nodeTemplate } = useRequest2(
async () => {
if (node?.pluginData?.error) {
return undefined;
}
if (
node?.flowNodeType === FlowNodeTypeEnum.pluginModule ||
node?.flowNodeType === FlowNodeTypeEnum.appModule
) {
if (!node?.pluginId) return;
const template = await getPreviewPluginNode({ appId: node.pluginId });
return template;
return { ...node, ...node.pluginData };
} else {
const template = moduleTemplatesFlat.find(
(item) => item.flowNodeType === node?.flowNodeType
@@ -141,10 +142,13 @@ const NodeCard = (props: Props) => {
const { runAsync: onClickSyncVersion } = useRequest2(
async () => {
if (!!nodeTemplate) {
if (!node?.pluginId) return;
const template = await getPreviewPluginNode({ appId: node.pluginId });
if (!!template) {
onResetNode({
id: nodeId,
node: nodeTemplate
node: template
});
}
onCloseConfirmSync();
@@ -286,6 +290,22 @@ const NodeCard = (props: Props) => {
)}
</UseGuideModal>
)}
{!!node?.pluginData?.error && (
<MyTooltip label={t('app:app.modules.not_found_tips')}>
<Flex
bg={'red.50'}
alignItems={'center'}
h={8}
px={2}
rounded={'6px'}
fontSize={'xs'}
fontWeight={'medium'}
>
<MyIcon name={'common/errorFill'} w={'14px'} mr={1} />
<Box color={'red.600'}>{t('app:app.modules.not_found')}</Box>
</Flex>
</MyTooltip>
)}
</Flex>
<NodeIntro nodeId={nodeId} intro={intro} />
</Box>
@@ -297,6 +317,7 @@ const NodeCard = (props: Props) => {
}, [
node?.flowNodeType,
node?.courseUrl,
node?.pluginData?.error,
showToolHandle,
nodeId,
isFolded,

View File

@@ -1,4 +1,12 @@
import { Dispatch, ReactNode, SetStateAction, useCallback, useMemo, useState } from 'react';
import {
Dispatch,
ReactNode,
SetStateAction,
useCallback,
useEffect,
useMemo,
useState
} from 'react';
import { createContext } from 'use-context-selector';
import { defaultApp } from '@/web/core/app/constants';
import { delAppById, getAppDetailById, putAppById } from '@/web/core/app/api';
@@ -14,6 +22,8 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
import type { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import { checkAppUnExistError } from '@fastgpt/global/core/app/utils';
import { useToast } from '@fastgpt/web/hooks/useToast';
const InfoModal = dynamic(() => import('./InfoModal'));
const TagsEditModal = dynamic(() => import('./TagsEditModal'));
@@ -84,6 +94,7 @@ export const AppContext = createContext<AppContextType>({
const AppContextProvider = ({ children }: { children: ReactNode }) => {
const { t } = useTranslation();
const { toast } = useToast();
const router = useRouter();
const { appId, currentTab = TabEnum.appEdit } = router.query as {
appId: string;
@@ -194,6 +205,16 @@ const AppContextProvider = ({ children }: { children: ReactNode }) => {
[appDetail.name, deleteApp, openConfirmDel, t]
);
// check app unExist error
useEffect(() => {
if (appDetail.modules.some((module) => checkAppUnExistError(module.pluginData?.error))) {
toast({
title: t('app:app.error.unExist_app'),
status: 'error'
});
}
}, [appDetail.modules, t, toast]);
const contextValue: AppContextType = useMemo(
() => ({
appId,