Test version (#4792)
* plugin node version select (#4760) * plugin node version select * type * fix * fix * perf: version list * fix node version (#4787) * change my select * fix-ui * fix test * add test * fix * remove invalid version field * filter deprecated field * fix: claude tool call * fix: test --------- Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
getAppVersionDetail,
|
||||
getWorkflowVersionList,
|
||||
getAppVersionList,
|
||||
updateAppVersion
|
||||
} from '@/web/core/app/api/version';
|
||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
@@ -186,7 +186,7 @@ const TeamCloud = ({
|
||||
ScrollData,
|
||||
data: scrollDataList,
|
||||
setData
|
||||
} = useScrollPagination(getWorkflowVersionList, {
|
||||
} = useScrollPagination(getAppVersionList, {
|
||||
pageSize: 30,
|
||||
params: {
|
||||
appId: appDetail._id
|
||||
|
||||
@@ -24,7 +24,10 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { nodeTemplate2FlowNode } from '@/web/core/workflow/utils';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import {
|
||||
AppNodeFlowNodeTypeMap,
|
||||
FlowNodeTypeEnum
|
||||
} from '@fastgpt/global/core/workflow/node/constant';
|
||||
import {
|
||||
getPreviewPluginNode,
|
||||
getSystemPlugTemplates,
|
||||
@@ -475,11 +478,7 @@ const RenderList = React.memo(function RenderList({
|
||||
const templateNode = await (async () => {
|
||||
try {
|
||||
// get plugin preview module
|
||||
if (
|
||||
template.flowNodeType === FlowNodeTypeEnum.pluginModule ||
|
||||
template.flowNodeType === FlowNodeTypeEnum.appModule ||
|
||||
template.flowNodeType === FlowNodeTypeEnum.toolSet
|
||||
) {
|
||||
if (AppNodeFlowNodeTypeMap[template.flowNodeType]) {
|
||||
setLoading(true);
|
||||
const res = await getPreviewPluginNode({ appId: template.id });
|
||||
|
||||
@@ -533,21 +532,25 @@ const RenderList = React.memo(function RenderList({
|
||||
pluginId: templateNode.pluginId
|
||||
}),
|
||||
intro: t(templateNode.intro as any),
|
||||
inputs: templateNode.inputs.map((input) => ({
|
||||
...input,
|
||||
value: defaultValueMap[input.key] ?? input.value,
|
||||
valueDesc: t(input.valueDesc as any),
|
||||
label: t(input.label as any),
|
||||
description: t(input.description as any),
|
||||
debugLabel: t(input.debugLabel as any),
|
||||
toolDescription: t(input.toolDescription as any)
|
||||
})),
|
||||
outputs: templateNode.outputs.map((output) => ({
|
||||
...output,
|
||||
valueDesc: t(output.valueDesc as any),
|
||||
label: t(output.label as any),
|
||||
description: t(output.description as any)
|
||||
}))
|
||||
inputs: templateNode.inputs
|
||||
.filter((input) => input.deprecated !== true)
|
||||
.map((input) => ({
|
||||
...input,
|
||||
value: defaultValueMap[input.key] ?? input.value,
|
||||
valueDesc: t(input.valueDesc as any),
|
||||
label: t(input.label as any),
|
||||
description: t(input.description as any),
|
||||
debugLabel: t(input.debugLabel as any),
|
||||
toolDescription: t(input.toolDescription as any)
|
||||
})),
|
||||
outputs: templateNode.outputs
|
||||
.filter((output) => output.deprecated !== true)
|
||||
.map((output) => ({
|
||||
...output,
|
||||
valueDesc: t(output.valueDesc as any),
|
||||
label: t(output.label as any),
|
||||
description: t(output.description as any)
|
||||
}))
|
||||
},
|
||||
position: { x: mouseX, y: mouseY },
|
||||
selected: true,
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { Box, Button, Flex, type FlexProps } from '@chakra-ui/react';
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
import { Box, Button, Flex, HStack, useDisclosure, type FlexProps } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import type { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useEditTitle } from '@/web/common/hooks/useEditTitle';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import {
|
||||
AppNodeFlowNodeTypeMap,
|
||||
FlowNodeTypeEnum
|
||||
} from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { LOGO_ICON } from '@fastgpt/global/common/system/constants';
|
||||
import { ToolSourceHandle, ToolTargetHandle } from './Handle/ToolHandle';
|
||||
import { useEditTextarea } from '@fastgpt/web/hooks/useEditTextarea';
|
||||
@@ -28,6 +30,11 @@ import MyImage from '@fastgpt/web/components/common/Image/MyImage';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
import UseGuideModal from '@/components/common/Modal/UseGuideModal';
|
||||
import NodeDebugResponse from './RenderDebug/NodeDebugResponse';
|
||||
import { getAppVersionList } from '@/web/core/app/api/version';
|
||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { useCreation } from 'ahooks';
|
||||
|
||||
type Props = FlowNodeItemType & {
|
||||
children?: React.ReactNode | React.ReactNode[] | string;
|
||||
@@ -61,7 +68,6 @@ const NodeCard = (props: Props) => {
|
||||
w = 'full',
|
||||
h = 'full',
|
||||
nodeId,
|
||||
flowNodeType,
|
||||
selected,
|
||||
menuForbid,
|
||||
isTool = false,
|
||||
@@ -73,7 +79,6 @@ const NodeCard = (props: Props) => {
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
const onUpdateNodeError = useContextSelector(WorkflowContext, (v) => v.onUpdateNodeError);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
const onResetNode = useContextSelector(WorkflowContext, (v) => v.onResetNode);
|
||||
const setHoverNodeId = useContextSelector(WorkflowEventContext, (v) => v.setHoverNodeId);
|
||||
|
||||
// custom title edit
|
||||
@@ -96,6 +101,7 @@ const NodeCard = (props: Props) => {
|
||||
|
||||
return { node, parentNode };
|
||||
}, [nodeList, nodeId]);
|
||||
const isAppNode = node && AppNodeFlowNodeTypeMap[node?.flowNodeType];
|
||||
|
||||
const { data: nodeTemplate } = useRequest2(
|
||||
async () => {
|
||||
@@ -103,12 +109,7 @@ const NodeCard = (props: Props) => {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
node?.flowNodeType === FlowNodeTypeEnum.pluginModule ||
|
||||
node?.flowNodeType === FlowNodeTypeEnum.appModule ||
|
||||
node?.flowNodeType === FlowNodeTypeEnum.tool ||
|
||||
node?.flowNodeType === FlowNodeTypeEnum.toolSet
|
||||
) {
|
||||
if (isAppNode) {
|
||||
return { ...node, ...node.pluginData };
|
||||
} else {
|
||||
const template = moduleTemplatesFlat.find(
|
||||
@@ -132,51 +133,6 @@ const NodeCard = (props: Props) => {
|
||||
}
|
||||
);
|
||||
|
||||
const {
|
||||
openConfirm: onOpenConfirmSync,
|
||||
onClose: onCloseConfirmSync,
|
||||
ConfirmModal: ConfirmSyncModal
|
||||
} = useConfirm({
|
||||
content: t('workflow:Confirm_sync_node')
|
||||
});
|
||||
|
||||
const hasNewVersion = nodeTemplate && nodeTemplate.version !== node?.version;
|
||||
|
||||
const { runAsync: onClickSyncVersion } = useRequest2(
|
||||
async () => {
|
||||
if (!node) return;
|
||||
|
||||
if (node.pluginId) {
|
||||
const template = await getPreviewPluginNode({ appId: node.pluginId });
|
||||
|
||||
if (!!template) {
|
||||
onResetNode({
|
||||
id: nodeId,
|
||||
node: template
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const template = moduleTemplatesFlat.find(
|
||||
(item) => item.flowNodeType === node.flowNodeType
|
||||
);
|
||||
if (!template) {
|
||||
return toast({
|
||||
title: t('app:app.modules.not_found_tips'),
|
||||
status: 'warning'
|
||||
});
|
||||
}
|
||||
onResetNode({
|
||||
id: nodeId,
|
||||
node: template
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
refreshDeps: [node, nodeId, onResetNode],
|
||||
onFinally() {}
|
||||
}
|
||||
);
|
||||
|
||||
/* Node header */
|
||||
const Header = useMemo(() => {
|
||||
const showHeader = node?.flowNodeType !== FlowNodeTypeEnum.comment;
|
||||
@@ -255,28 +211,9 @@ const NodeCard = (props: Props) => {
|
||||
>
|
||||
<MyIcon name={'edit'} w={'14px'} />
|
||||
</Button>
|
||||
<Box flex={1} />
|
||||
{hasNewVersion && (
|
||||
<MyTooltip label={t('app:app.modules.click to update')}>
|
||||
<Button
|
||||
bg={'yellow.50'}
|
||||
color={'yellow.600'}
|
||||
variant={'ghost'}
|
||||
h={8}
|
||||
px={2}
|
||||
rounded={'6px'}
|
||||
fontSize={'xs'}
|
||||
fontWeight={'medium'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ bg: 'yellow.100' }}
|
||||
onClick={onOpenConfirmSync(onClickSyncVersion)}
|
||||
>
|
||||
<Box>{t('app:app.modules.has new version')}</Box>
|
||||
<MyIcon name={'help'} w={'14px'} ml={1} />
|
||||
</Button>
|
||||
</MyTooltip>
|
||||
)}
|
||||
{!!nodeTemplate?.diagram && !hasNewVersion && (
|
||||
<Box flex={1} mr={1} />
|
||||
{isAppNode && <NodeVersion node={node} />}
|
||||
{!!nodeTemplate?.diagram && (
|
||||
<MyTooltip
|
||||
label={
|
||||
<MyImage
|
||||
@@ -295,7 +232,7 @@ const NodeCard = (props: Props) => {
|
||||
{!!nodeTemplate?.diagram && node?.courseUrl && (
|
||||
<Box bg={'myGray.300'} w={'1px'} h={'12px'} ml={1} mr={0.5} />
|
||||
)}
|
||||
{!!(node?.courseUrl || nodeTemplate?.userGuide) && !hasNewVersion && (
|
||||
{!!(node?.courseUrl || nodeTemplate?.userGuide) && (
|
||||
<UseGuideModal
|
||||
title={nodeTemplate?.name}
|
||||
iconSrc={nodeTemplate?.avatar}
|
||||
@@ -310,7 +247,7 @@ const NodeCard = (props: Props) => {
|
||||
</UseGuideModal>
|
||||
)}
|
||||
{!!node?.pluginData?.error && (
|
||||
<MyTooltip label={t('app:app.modules.not_found_tips')}>
|
||||
<MyTooltip label={node?.pluginData?.error || t('app:app.modules.not_found_tips')}>
|
||||
<Flex
|
||||
bg={'red.50'}
|
||||
alignItems={'center'}
|
||||
@@ -321,7 +258,9 @@ const NodeCard = (props: Props) => {
|
||||
fontWeight={'medium'}
|
||||
>
|
||||
<MyIcon name={'common/errorFill'} w={'14px'} mr={1} />
|
||||
<Box color={'red.600'}>{t('app:app.modules.not_found')}</Box>
|
||||
<Box color={'red.600'}>
|
||||
{node?.pluginData?.error || t('app:app.modules.not_found')}
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
)}
|
||||
@@ -333,18 +272,14 @@ const NodeCard = (props: Props) => {
|
||||
</Box>
|
||||
);
|
||||
}, [
|
||||
node?.flowNodeType,
|
||||
node?.courseUrl,
|
||||
node?.pluginData?.error,
|
||||
node,
|
||||
showToolHandle,
|
||||
nodeId,
|
||||
isFolded,
|
||||
avatar,
|
||||
t,
|
||||
name,
|
||||
hasNewVersion,
|
||||
onOpenConfirmSync,
|
||||
onClickSyncVersion,
|
||||
isAppNode,
|
||||
nodeTemplate?.diagram,
|
||||
nodeTemplate?.userGuide,
|
||||
nodeTemplate?.name,
|
||||
@@ -419,7 +354,6 @@ const NodeCard = (props: Props) => {
|
||||
{RenderHandle}
|
||||
{RenderToolHandle}
|
||||
|
||||
<ConfirmSyncModal />
|
||||
<EditTitleModal maxLength={100} />
|
||||
</Flex>
|
||||
);
|
||||
@@ -663,3 +597,90 @@ const NodeIntro = React.memo(function NodeIntro({
|
||||
|
||||
return Render;
|
||||
});
|
||||
|
||||
const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeItemType }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onResetNode = useContextSelector(WorkflowContext, (v) => v.onResetNode);
|
||||
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
// Load version list
|
||||
const { ScrollData, data: versionList } = useScrollPagination(getAppVersionList, {
|
||||
pageSize: 20,
|
||||
params: {
|
||||
appId: node.pluginId,
|
||||
isPublish: true
|
||||
},
|
||||
refreshDeps: [node.pluginId, isOpen],
|
||||
disalbed: !isOpen,
|
||||
manual: false
|
||||
});
|
||||
|
||||
const { runAsync: onUpdateVersion, loading: isUpdating } = useRequest2(
|
||||
async (versionId: string) => {
|
||||
if (!node) return;
|
||||
|
||||
if (node.pluginId) {
|
||||
const template = await getPreviewPluginNode({ appId: node.pluginId, versionId });
|
||||
|
||||
if (!!template) {
|
||||
onResetNode({
|
||||
id: node.nodeId,
|
||||
node: {
|
||||
...template,
|
||||
name: node.name,
|
||||
intro: node.intro,
|
||||
avatar: node.avatar
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
refreshDeps: [node, onResetNode]
|
||||
}
|
||||
);
|
||||
|
||||
const renderList = useCreation(
|
||||
() =>
|
||||
versionList.map((item) => ({
|
||||
label: item.versionName,
|
||||
value: item._id
|
||||
})),
|
||||
[node.isLatestVersion, node.version, t, versionList]
|
||||
);
|
||||
const valueLabel = useMemo(() => {
|
||||
return (
|
||||
<Flex alignItems={'center'} gap={0.5}>
|
||||
{node?.versionLabel}
|
||||
{!node.isLatestVersion && (
|
||||
<MyTag type="fill" colorSchema={'adora'} fontSize={'mini'} borderRadius={'lg'}>
|
||||
{t('app:not_the_newest')}
|
||||
</MyTag>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}, [node.isLatestVersion, node?.versionLabel, t]);
|
||||
|
||||
return (
|
||||
<MySelect
|
||||
className="nowheel"
|
||||
value={node.version}
|
||||
onChange={onUpdateVersion}
|
||||
isLoading={isUpdating}
|
||||
customOnOpen={onOpen}
|
||||
customOnClose={onClose}
|
||||
placeholder={node?.versionLabel}
|
||||
variant={'whitePrimaryOutline'}
|
||||
size={'sm'}
|
||||
list={renderList}
|
||||
ScrollData={(props) => (
|
||||
<ScrollData minH={'100px'} maxH={'40vh'}>
|
||||
{props.children}
|
||||
</ScrollData>
|
||||
)}
|
||||
valueLabel={valueLabel}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -10,6 +10,8 @@ import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '@/pageComponents/app/detail/WorkflowComponents/context';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
|
||||
type Props = {
|
||||
nodeId: string;
|
||||
@@ -68,8 +70,38 @@ const InputLabel = ({ nodeId, input, RightComponent }: Props) => {
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{input.deprecated && (
|
||||
<>
|
||||
<Box flex={'1'} />
|
||||
<MyTooltip label={t('app:Click_to_delete_this_field')}>
|
||||
<Flex
|
||||
px={1.5}
|
||||
py={1}
|
||||
bg={'adora.50'}
|
||||
rounded={'6px'}
|
||||
fontSize={'14px'}
|
||||
cursor="pointer"
|
||||
alignItems={'center'}
|
||||
_hover={{
|
||||
bg: 'adora.100'
|
||||
}}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'delInput',
|
||||
key: input.key
|
||||
});
|
||||
}}
|
||||
>
|
||||
<MyIcon name={'common/info'} color={'adora.600'} w={4} mr={1} />
|
||||
<Box color={'adora.600'}>{t('app:Filed_is_deprecated')}</Box>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Right Component */}
|
||||
{RightComponent && (
|
||||
{!input.deprecated && RightComponent && (
|
||||
<>
|
||||
<Box flex={'1'} />
|
||||
{RightComponent}
|
||||
|
||||
@@ -8,11 +8,17 @@ import { getHandleId } from '@fastgpt/global/core/workflow/utils';
|
||||
import { Position } from 'reactflow';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import ValueTypeLabel from '../ValueTypeLabel';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../../../context';
|
||||
|
||||
const OutputLabel = ({ nodeId, output }: { nodeId: string; output: FlowNodeOutputItemType }) => {
|
||||
const { t } = useTranslation();
|
||||
const { label = '', description, valueType, valueDesc } = output;
|
||||
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
return (
|
||||
<Box position={'relative'}>
|
||||
<Flex
|
||||
@@ -36,6 +42,36 @@ const OutputLabel = ({ nodeId, output }: { nodeId: string; output: FlowNodeOutpu
|
||||
</Box>
|
||||
{description && <QuestionTip ml={1} label={t(description as any)} />}
|
||||
<ValueTypeLabel valueType={valueType} valueDesc={valueDesc} />
|
||||
|
||||
{output.deprecated && (
|
||||
<>
|
||||
<Box flex={'1'} />
|
||||
<MyTooltip label={t('app:Click_to_delete_this_field')}>
|
||||
<Flex
|
||||
px={1.5}
|
||||
py={1}
|
||||
bg={'adora.50'}
|
||||
rounded={'6px'}
|
||||
fontSize={'14px'}
|
||||
cursor="pointer"
|
||||
alignItems={'center'}
|
||||
_hover={{
|
||||
bg: 'adora.100'
|
||||
}}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'delOutput',
|
||||
key: output.key
|
||||
});
|
||||
}}
|
||||
>
|
||||
<MyIcon name={'common/info'} color={'adora.600'} w={4} mr={1} />
|
||||
<Box color={'adora.600'}>{t('app:Filed_is_deprecated')}</Box>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
{output.type === FlowNodeOutputTypeEnum.source && (
|
||||
<SourceHandle
|
||||
|
||||
Reference in New Issue
Block a user