mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-24 22:03:54 +00:00
4.8.11 code perf (#2804)
* perf: support child layout * perf: user form ui * perf: plugin tool output code * perf: code * perf: node fold hook
This commit is contained in:
@@ -299,18 +299,12 @@ const AIResponseBox = ({ value, isLastResponseValue, isChatting }: props) => {
|
||||
);
|
||||
if (value.type === ChatItemValueTypeEnum.tool && value.tools)
|
||||
return <RenderTool showAnimation={isChatting} tools={value.tools} />;
|
||||
if (
|
||||
value.type === ChatItemValueTypeEnum.interactive &&
|
||||
value.interactive &&
|
||||
value.interactive.type === 'userSelect'
|
||||
)
|
||||
return <RenderUserSelectInteractive interactive={value.interactive} />;
|
||||
if (
|
||||
value.type === ChatItemValueTypeEnum.interactive &&
|
||||
value.interactive &&
|
||||
value.interactive?.type === 'userInput'
|
||||
)
|
||||
return <RenderUserFormInteractive interactive={value.interactive} />;
|
||||
if (value.type === ChatItemValueTypeEnum.interactive && value.interactive) {
|
||||
if (value.interactive.type === 'userSelect')
|
||||
return <RenderUserSelectInteractive interactive={value.interactive} />;
|
||||
if (value.interactive?.type === 'userInput')
|
||||
return <RenderUserFormInteractive interactive={value.interactive} />;
|
||||
}
|
||||
};
|
||||
|
||||
export default React.memo(AIResponseBox);
|
||||
|
@@ -354,10 +354,7 @@ export const WholeResponseContent = ({
|
||||
/>
|
||||
|
||||
{/* form input */}
|
||||
<Row
|
||||
label={t('common:core.chat.response.form_input_result')}
|
||||
value={activeModule?.formInputResult}
|
||||
/>
|
||||
<Row label={t('workflow:form_input_result')} value={activeModule?.formInputResult} />
|
||||
</Box>
|
||||
) : null;
|
||||
};
|
||||
|
@@ -11,9 +11,17 @@ import { useInitApp } from '@/web/context/useInitApp';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import '@/web/styles/reset.scss';
|
||||
import NextHead from '@/components/common/NextHead';
|
||||
import { useEffect } from 'react';
|
||||
import { ReactElement, useEffect } from 'react';
|
||||
import { NextPage } from 'next';
|
||||
|
||||
function App({ Component, pageProps }: AppProps) {
|
||||
type NextPageWithLayout = NextPage & {
|
||||
setLayout?: (page: ReactElement) => JSX.Element;
|
||||
};
|
||||
type AppPropsWithLayout = AppProps & {
|
||||
Component: NextPageWithLayout;
|
||||
};
|
||||
|
||||
function App({ Component, pageProps }: AppPropsWithLayout) {
|
||||
const { feConfigs, scripts, title } = useInitApp();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -30,6 +38,8 @@ function App({ Component, pageProps }: AppProps) {
|
||||
);
|
||||
}, []);
|
||||
|
||||
const setLayout = Component.setLayout || ((page) => <>{page}</>);
|
||||
|
||||
return (
|
||||
<>
|
||||
<NextHead
|
||||
@@ -46,9 +56,7 @@ function App({ Component, pageProps }: AppProps) {
|
||||
<QueryClientContext>
|
||||
<I18nContextProvider>
|
||||
<ChakraUIContext>
|
||||
<Layout>
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
<Layout>{setLayout(<Component {...pageProps} />)}</Layout>
|
||||
</ChakraUIContext>
|
||||
</I18nContextProvider>
|
||||
</QueryClientContext>
|
||||
|
@@ -1,10 +1,11 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { BezierEdge, getBezierPath, EdgeLabelRenderer, EdgeProps } from 'reactflow';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { NodeOutputKeyEnum, RuntimeEdgeStatusEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../context';
|
||||
import { useThrottleEffect } from 'ahooks';
|
||||
|
||||
const ButtonEdge = (props: EdgeProps) => {
|
||||
const { nodes, nodeList, setEdges, workflowDebugData, hoverEdgeId } = useContextSelector(
|
||||
@@ -28,13 +29,11 @@ const ButtonEdge = (props: EdgeProps) => {
|
||||
style
|
||||
} = props;
|
||||
|
||||
// If parentNode is folded, the edge will not be displayed
|
||||
const parentNode = useMemo(() => {
|
||||
for (const node of nodeList) {
|
||||
if ((node.nodeId === source || node.nodeId === target) && node.parentNodeId) {
|
||||
return nodeList.find((parent) => parent.nodeId === node.parentNodeId);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
return nodeList.find(
|
||||
(node) => (node.nodeId === source || node.nodeId === target) && node.parentNodeId
|
||||
);
|
||||
}, [nodeList, source, target]);
|
||||
|
||||
const defaultZIndex = useMemo(
|
||||
@@ -52,12 +51,20 @@ const ButtonEdge = (props: EdgeProps) => {
|
||||
[setEdges]
|
||||
);
|
||||
|
||||
const highlightEdge = useMemo(() => {
|
||||
const connectNode = nodes.find((node) => {
|
||||
return node.selected && (node.id === props.source || node.id === props.target);
|
||||
});
|
||||
return !!(connectNode || selected);
|
||||
}, [nodes, props.source, props.target, selected]);
|
||||
// Selected edge or source/target node selected
|
||||
const [highlightEdge, setHighlightEdge] = useState(false);
|
||||
useThrottleEffect(
|
||||
() => {
|
||||
const connectNode = nodes.find((node) => {
|
||||
return node.selected && (node.id === props.source || node.id === props.target);
|
||||
});
|
||||
setHighlightEdge(!!connectNode || !!selected);
|
||||
},
|
||||
[nodes, selected, props.source, props.target],
|
||||
{
|
||||
wait: 100
|
||||
}
|
||||
);
|
||||
|
||||
const [, labelX, labelY] = getBezierPath({
|
||||
sourceX,
|
||||
|
@@ -17,6 +17,7 @@ import { useTranslation } from 'next-i18next';
|
||||
import styles from './index.module.scss';
|
||||
import { maxZoom, minZoom } from '../index';
|
||||
import { useKeyPress } from 'ahooks';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
|
||||
const buttonStyle = {
|
||||
border: 'none',
|
||||
@@ -61,11 +62,16 @@ const FlowController = React.memo(function FlowController() {
|
||||
zoomOut();
|
||||
});
|
||||
|
||||
/*
|
||||
id: Render node id
|
||||
*/
|
||||
const MiniMapNode = useCallback(
|
||||
({ x, y, width, height, color, id }: MiniMapNodeProps) => {
|
||||
// If the node parentNode is folded, the child node will not be displayed
|
||||
const node = nodeList.find((node) => node.nodeId === id);
|
||||
const parentNode = nodeList.find((n) => n.nodeId === node?.parentNodeId);
|
||||
|
||||
const parentNode = node?.parentNodeId
|
||||
? nodeList.find((n) => n.nodeId === node?.parentNodeId)
|
||||
: undefined;
|
||||
if (parentNode?.isFolded) {
|
||||
return null;
|
||||
}
|
||||
|
@@ -277,6 +277,7 @@ export const useWorkflow = () => {
|
||||
const {
|
||||
setConnectingEdge,
|
||||
nodes,
|
||||
nodeList,
|
||||
onNodesChange,
|
||||
setEdges,
|
||||
onChangeNode,
|
||||
@@ -370,10 +371,12 @@ export const useWorkflow = () => {
|
||||
const checkNodeOverLoopNode = useMemoizedFn((node: Node) => {
|
||||
if (!node) return;
|
||||
|
||||
// 获取所有与当前节点相交的节点,不包含折叠的节点
|
||||
const intersections = getIntersectingNodes(node).filter((node) => !node.data.isFolded);
|
||||
// 获取所有与当前节点相交的节点中,类型为 loop 的节点
|
||||
const parentNode = intersections.find((item) => item.type === FlowNodeTypeEnum.loop);
|
||||
// 获取所有与当前节点相交的节点
|
||||
const intersections = getIntersectingNodes(node);
|
||||
// 获取所有与当前节点相交的节点中,类型为 loop 的节点且它不能是折叠状态
|
||||
const parentNode = intersections.find(
|
||||
(item) => !item.data.isFolded && item.type === FlowNodeTypeEnum.loop
|
||||
);
|
||||
|
||||
const unSupportedTypes = [
|
||||
FlowNodeTypeEnum.workflowStart,
|
||||
@@ -548,8 +551,10 @@ export const useWorkflow = () => {
|
||||
const onConnectStart = useCallback(
|
||||
(event: any, params: OnConnectStartParams) => {
|
||||
if (!params.nodeId) return;
|
||||
const sourceNode = nodes.find((node) => node.id === params.nodeId);
|
||||
if (sourceNode?.data.isFolded) {
|
||||
|
||||
// If node is folded, unfold it when connecting
|
||||
const sourceNode = nodeList.find((node) => node.nodeId === params.nodeId);
|
||||
if (sourceNode?.isFolded) {
|
||||
return onChangeNode({
|
||||
nodeId: params.nodeId,
|
||||
type: 'attr',
|
||||
@@ -559,7 +564,7 @@ export const useWorkflow = () => {
|
||||
}
|
||||
setConnectingEdge(params);
|
||||
},
|
||||
[nodes, setConnectingEdge, onChangeNode]
|
||||
[nodeList, setConnectingEdge, onChangeNode]
|
||||
);
|
||||
const onConnectEnd = useCallback(() => {
|
||||
setConnectingEdge(undefined);
|
||||
|
@@ -120,7 +120,7 @@ const NodeFormInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
return (
|
||||
<Box>
|
||||
<HStack className="nodrag" cursor={'default'} mb={3}>
|
||||
<FormLabel>{t('common:core.module.input_form')}</FormLabel>
|
||||
<FormLabel>{t('workflow:user_form_input_config')}</FormLabel>
|
||||
<Box flex={'1 0 0'} />
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
@@ -150,9 +150,9 @@ const NodeFormInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th borderBottomLeftRadius={'none !important'}>
|
||||
{t('common:core.module.input_name')}
|
||||
{t('workflow:user_form_input_name')}
|
||||
</Th>
|
||||
<Th>{t('common:core.module.input_description')}</Th>
|
||||
<Th>{t('workflow:user_form_input_description')}</Th>
|
||||
<Th>{t('common:common.Require Input')}</Th>
|
||||
<Th borderBottomRightRadius={'none !important'}>{t('user:operations')}</Th>
|
||||
</Tr>
|
||||
@@ -204,7 +204,7 @@ const NodeFormInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
);
|
||||
}
|
||||
}),
|
||||
[nodeId, editField, t, setEditField, onChangeNode]
|
||||
[t, editField, onChangeNode, nodeId, outputs]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@@ -26,6 +26,7 @@ import { useI18n } from '@/web/context/I18n';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { isWorkflowStartOutput } from '@fastgpt/global/core/workflow/template/system/workflowStart';
|
||||
import PluginOutputEditModal, { defaultOutput } from './PluginOutputEditModal';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
|
||||
const customOutputConfig = {
|
||||
selectValueTypeList: Object.values(WorkflowIOValueTypeEnum),
|
||||
@@ -197,19 +198,21 @@ function Reference({
|
||||
onClick={openConfirm(onDel)}
|
||||
/>
|
||||
</Flex>
|
||||
<MyIcon
|
||||
name={
|
||||
input.isToolOutput !== false
|
||||
? 'core/workflow/template/toolkitActive'
|
||||
: 'core/workflow/template/toolkitInactive'
|
||||
}
|
||||
w={'14px'}
|
||||
color={'myGray.500'}
|
||||
cursor={'pointer'}
|
||||
mr={2}
|
||||
_hover={{ color: 'red.600' }}
|
||||
onClick={() => setEditField(input)}
|
||||
/>
|
||||
<MyTooltip label={t('workflow:plugin_output_tool')}>
|
||||
<MyIcon
|
||||
name={
|
||||
input.isToolOutput !== false
|
||||
? 'core/workflow/template/toolkitActive'
|
||||
: 'core/workflow/template/toolkitInactive'
|
||||
}
|
||||
w={'14px'}
|
||||
color={'myGray.500'}
|
||||
cursor={'pointer'}
|
||||
mr={2}
|
||||
_hover={{ color: 'red.600' }}
|
||||
onClick={() => setEditField(input)}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<ReferSelector
|
||||
placeholder={t((input.referencePlaceholder as any) || 'select_reference_variable')}
|
||||
|
@@ -24,6 +24,7 @@ import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useMount } from 'ahooks';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
|
||||
const PluginOutputEditModal = ({
|
||||
customOutputConfig,
|
||||
@@ -167,8 +168,10 @@ const PluginOutputEditModal = ({
|
||||
<FormLabel flex={'0 0 70px'}>{t('workflow:input_description')}</FormLabel>
|
||||
<Textarea bg={'myGray.50'} {...register('description', {})} />
|
||||
</Flex>
|
||||
<Flex mt={3} alignItems={'center'} justifyContent={'space-between'}>
|
||||
<FormLabel>{t('workflow:is_tool_output')}</FormLabel>
|
||||
<Flex mt={3} alignItems={'center'}>
|
||||
<FormLabel>{t('workflow:is_tool_output_label')}</FormLabel>
|
||||
<QuestionTip label={t('workflow:plugin_output_tool')} ml={1} />
|
||||
<Box flex={1} />
|
||||
<Switch {...register('isToolOutput')} />
|
||||
</Flex>
|
||||
</Stack>
|
||||
|
@@ -13,9 +13,7 @@ export const ConnectionSourceHandle = ({
|
||||
nodeId: string;
|
||||
isFoldNode?: boolean;
|
||||
}) => {
|
||||
const connectingEdge = useContextSelector(WorkflowContext, (ctx) => ctx.connectingEdge);
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
|
||||
const { connectingEdge, nodeList, edges } = useContextSelector(WorkflowContext, (ctx) => ctx);
|
||||
|
||||
const { showSourceHandle, RightHandle, LeftHandlee, TopHandlee, BottomHandlee } = useMemo(() => {
|
||||
const node = nodeList.find((node) => node.nodeId === nodeId);
|
||||
@@ -32,6 +30,15 @@ export const ConnectionSourceHandle = ({
|
||||
const rightTargetConnected = edges.some(
|
||||
(edge) => edge.targetHandle === getHandleId(nodeId, 'target', Position.Right)
|
||||
);
|
||||
|
||||
/*
|
||||
If the node is folded, must show the handle
|
||||
hide handle when:
|
||||
- not folded
|
||||
- not node
|
||||
- not sourceHandle
|
||||
- already connected
|
||||
*/
|
||||
if (!isFoldNode && (!node || !node?.sourceHandle?.right || rightTargetConnected)) return null;
|
||||
|
||||
return (
|
||||
|
@@ -82,9 +82,13 @@ const NodeCard = (props: Props) => {
|
||||
[isTool, nodeList]
|
||||
);
|
||||
|
||||
// Current node and parent node
|
||||
const { node, parentNode } = useMemo(() => {
|
||||
const node = nodeList.find((node) => node.nodeId === nodeId);
|
||||
const parentNode = nodeList.find((n) => n.nodeId === node?.parentNodeId);
|
||||
const parentNode = node?.parentNodeId
|
||||
? nodeList.find((n) => n.nodeId === node?.parentNodeId)
|
||||
: undefined;
|
||||
|
||||
return { node, parentNode };
|
||||
}, [nodeList, nodeId]);
|
||||
|
||||
|
@@ -48,10 +48,12 @@ export const uiWorkflow2StoreWorkflow = ({
|
||||
.filter(
|
||||
// Filter out edges that do not have both sourceHandle and targetHandle
|
||||
(item) => {
|
||||
// Not in react flow page
|
||||
if (!reactFlowViewport) return true;
|
||||
const currentSourceNode = nodes.find((node) => node.data.nodeId === item.source);
|
||||
|
||||
if (currentSourceNode?.data.isFolded) return true;
|
||||
|
||||
// Not in react flow page
|
||||
return handleIdList.includes(item.sourceHandle) && handleIdList.includes(item.targetHandle);
|
||||
}
|
||||
);
|
||||
|
Reference in New Issue
Block a user