Perf: worfklow scroll cannot wheel. Adapt wrokflow skip circle. Change tab alway output stream (#2688)

* perf: teaxtarea no wheel

* remove render error

* adapt workflow skip circle

* perf: change tab can stream output
This commit is contained in:
Archer 2024-09-12 17:22:52 +08:00 committed by GitHub
parent fde1618af2
commit 56281d92f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 64 additions and 39 deletions

View File

@ -21,5 +21,6 @@ weight: 813
4. 优化 - 工作流嵌套层级限制 20 层,避免因编排不合理导致的无限死循环。 4. 优化 - 工作流嵌套层级限制 20 层,避免因编排不合理导致的无限死循环。
5. 优化 - 工作流 handler 性能优化。 5. 优化 - 工作流 handler 性能优化。
6. 优化 - 工作流快捷键,避免调试测试时也会触发。 6. 优化 - 工作流快捷键,避免调试测试时也会触发。
7. 修复 - 知识库选择权限问题。 7. 优化 - 流输出,切换 tab 时仍可以继续输出。
8. 修复 - 空 chatId 发起对话,首轮携带用户选择时会异常。 8. 修复 - 知识库选择权限问题。
9. 修复 - 空 chatId 发起对话,首轮携带用户选择时会异常。

View File

@ -374,6 +374,18 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
if (!nodeRunResult) return []; if (!nodeRunResult) return [];
/*
skipEdges
skip 线
skip
*/
const skipEdges = (nodeRunResult.result[DispatchNodeResponseKeyEnum.skipHandleId] ||
[]) as string[];
if (skipEdges && skipEdges?.length > 0) {
skippedNodeIdList.add(node.nodeId);
}
// In the current version, only one interactive node is allowed at the same time // In the current version, only one interactive node is allowed at the same time
const interactiveResponse = nodeRunResult.result?.[DispatchNodeResponseKeyEnum.interactive]; const interactiveResponse = nodeRunResult.result?.[DispatchNodeResponseKeyEnum.interactive];
if (interactiveResponse) { if (interactiveResponse) {
@ -559,7 +571,6 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
// start process width initInput // start process width initInput
const entryNodes = runtimeNodes.filter((item) => item.isEntry); const entryNodes = runtimeNodes.filter((item) => item.isEntry);
// reset entry // reset entry
runtimeNodes.forEach((item) => { runtimeNodes.forEach((item) => {
// Interactive node is not the entry node, return interactive result // Interactive node is not the entry node, return interactive result

View File

@ -141,7 +141,6 @@ export function useScrollPagination<
// Reload data // Reload data
useRequest( useRequest(
async () => { async () => {
console.log('reload', 11111);
loadData(1); loadData(1);
}, },
{ {

View File

@ -1,4 +1,4 @@
import React, { useMemo } from 'react'; import React, { useEffect, useMemo } from 'react';
import { Box, Flex } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useLoading } from '@fastgpt/web/hooks/useLoading'; import { useLoading } from '@fastgpt/web/hooks/useLoading';
@ -12,6 +12,7 @@ import { useI18nLng } from '@fastgpt/web/hooks/useI18n';
import Auth from './auth'; import Auth from './auth';
import { useSystem } from '@fastgpt/web/hooks/useSystem'; import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { useMount } from 'ahooks'; import { useMount } from 'ahooks';
import { watchWindowHidden } from '@/web/common/system/utils';
const Navbar = dynamic(() => import('./navbar')); const Navbar = dynamic(() => import('./navbar'));
const NavbarPhone = dynamic(() => import('./navbarPhone')); const NavbarPhone = dynamic(() => import('./navbarPhone'));
const UpdateInviteModal = dynamic(() => import('@/components/support/user/team/UpdateInviteModal')); const UpdateInviteModal = dynamic(() => import('@/components/support/user/team/UpdateInviteModal'));
@ -68,6 +69,14 @@ const Layout = ({ children }: { children: JSX.Element }) => {
setUserDefaultLng(); setUserDefaultLng();
}); });
// Add global listener
useEffect(() => {
document.addEventListener('visibilitychange', watchWindowHidden);
return () => {
document.removeEventListener('visibilitychange', watchWindowHidden);
};
});
return ( return (
<> <>
<Box h={'100%'} bg={'myGray.100'}> <Box h={'100%'} bg={'myGray.100'}>

View File

@ -4,17 +4,16 @@ import MyModal from '@fastgpt/web/components/common/MyModal';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../context'; import { WorkflowContext } from '../context';
import { useI18n } from '@/web/context/I18n';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile'; import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { useSystem } from '@fastgpt/web/hooks/useSystem'; import { useSystem } from '@fastgpt/web/hooks/useSystem';
type Props = { type Props = {
onClose: () => void; onClose: () => void;
}; };
const ImportSettings = ({ onClose }: Props) => { const ImportSettings = ({ onClose }: Props) => {
const { appT } = useI18n();
const { toast } = useToast(); const { toast } = useToast();
const { File, onOpen } = useSelectFile({ const { File, onOpen } = useSelectFile({
fileType: 'json', fileType: 'json',
@ -25,21 +24,6 @@ const ImportSettings = ({ onClose }: Props) => {
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);
const [value, setValue] = useState(''); const [value, setValue] = useState('');
const { t } = useTranslation(); const { t } = useTranslation();
const handleDragEnter = useCallback((e: DragEvent<HTMLDivElement>) => {
e.preventDefault();
setIsDragging(true);
}, []);
const handleDragLeave = useCallback((e: DragEvent<HTMLDivElement>) => {
e.preventDefault();
setIsDragging(false);
}, []);
const handleDrop = useCallback(async (e: DragEvent<HTMLDivElement>) => {
e.preventDefault();
const file = e.dataTransfer.files[0];
readJSONFile(file);
setIsDragging(false);
}, []);
const readJSONFile = useCallback( const readJSONFile = useCallback(
(file: File) => { (file: File) => {
@ -62,6 +46,25 @@ const ImportSettings = ({ onClose }: Props) => {
[t, toast] [t, toast]
); );
const handleDragEnter = useCallback((e: DragEvent<HTMLDivElement>) => {
e.preventDefault();
setIsDragging(true);
}, []);
const handleDragLeave = useCallback((e: DragEvent<HTMLDivElement>) => {
e.preventDefault();
setIsDragging(false);
}, []);
const handleDrop = useCallback(
async (e: DragEvent<HTMLDivElement>) => {
e.preventDefault();
const file = e.dataTransfer.files[0];
readJSONFile(file);
setIsDragging(false);
},
[readJSONFile]
);
const onSelectFile = useCallback( const onSelectFile = useCallback(
async (e: File[]) => { async (e: File[]) => {
const file = e[0]; const file = e[0];
@ -70,16 +73,14 @@ const ImportSettings = ({ onClose }: Props) => {
}, },
[readJSONFile] [readJSONFile]
); );
return ( return (
<MyModal <MyModal
isOpen isOpen
onClose={onClose} onClose={onClose}
title={ iconSrc="common/importLight"
<Flex align={'center'} ml={-3}> iconColor="primary.600"
<MyIcon name={'common/importLight'} color={'primary.600'} w={'1.25rem'} mr={'0.62rem'} /> title={t('app:import_configs')}
<Box lineHeight={'1.25rem'}>{appT('import_configs')}</Box>
</Flex>
}
size={isPc ? 'lg' : 'md'} size={isPc ? 'lg' : 'md'}
> >
<ModalBody> <ModalBody>
@ -129,14 +130,12 @@ const ImportSettings = ({ onClose }: Props) => {
border={'1px solid'} border={'1px solid'}
borderRadius={'md'} borderRadius={'md'}
borderColor={'myGray.200'} borderColor={'myGray.200'}
h={'15.125rem'}
value={value} value={value}
placeholder={ placeholder={
isPc isPc
? t('app:paste_config') + '\n' + t('app:or_drag_JSON') ? t('app:paste_config') + '\n' + t('app:or_drag_JSON')
: t('app:paste_config') : t('app:paste_config')
} }
defaultValue={value}
rows={16} rows={16}
onChange={(e) => setValue(e.target.value)} onChange={(e) => setValue(e.target.value)}
/> />

View File

@ -34,12 +34,10 @@ const FlowController = React.memo(function FlowController() {
// Controller shortcut key // Controller shortcut key
useKeyPress(['ctrl.z', 'meta.z'], (e) => { useKeyPress(['ctrl.z', 'meta.z'], (e) => {
e.preventDefault();
if (!mouseInCanvas) return; if (!mouseInCanvas) return;
undo(); undo();
}); });
useKeyPress(['ctrl.shift.z', 'meta.shift.z', 'ctrl.y', 'meta.y'], (e) => { useKeyPress(['ctrl.shift.z', 'meta.shift.z', 'ctrl.y', 'meta.y'], (e) => {
e.preventDefault();
if (!mouseInCanvas) return; if (!mouseInCanvas) return;
redo(); redo();
}); });

View File

@ -86,12 +86,10 @@ export const useKeyboard = () => {
}, [computedNewNodeName, hasInputtingElement, setNodes]); }, [computedNewNodeName, hasInputtingElement, setNodes]);
useKeyPressEffect(['ctrl.c', 'meta.c'], (e) => { useKeyPressEffect(['ctrl.c', 'meta.c'], (e) => {
e.preventDefault();
if (!mouseInCanvas) return; if (!mouseInCanvas) return;
onCopy(); onCopy();
}); });
useKeyPressEffect(['ctrl.v', 'meta.v'], (e) => { useKeyPressEffect(['ctrl.v', 'meta.v'], (e) => {
e.preventDefault();
if (!mouseInCanvas) return; if (!mouseInCanvas) return;
onParse(); onParse();
}); });

View File

@ -44,7 +44,7 @@ export const ToolTargetHandle = ({ show, nodeId }: ToolHandleProps) => {
type="target" type="target"
id={handleId} id={handleId}
position={Position.Top} position={Position.Top}
isConnectableStart={false} isConnectableEnd={showHandle}
> >
<Box <Box
className="flow-handle" className="flow-handle"

View File

@ -23,13 +23,12 @@ declare global {
var qaQueueLen: number; var qaQueueLen: number;
var vectorQueueLen: number; var vectorQueueLen: number;
var systemVersion: string;
interface Window { interface Window {
grecaptcha: any; grecaptcha: any;
QRCode: any; QRCode: any;
umami?: { umami?: {
track: (event: TrackEventName, data: any) => void; track: (event: TrackEventName, data: any) => void;
}; };
windowHidden: boolean;
} }
} }

View File

@ -84,7 +84,7 @@ export const streamFetch = ({
} }
if (responseQueue.length > 0) { if (responseQueue.length > 0) {
const fetchCount = Math.max(1, Math.round(responseQueue.length / 30)); const fetchCount = Math.max(1, Math.round(responseQueue.length / 20));
for (let i = 0; i < fetchCount; i++) { for (let i = 0; i < fetchCount; i++) {
const item = responseQueue[i]; const item = responseQueue[i];
onMessage(item); onMessage(item);
@ -100,7 +100,9 @@ export const streamFetch = ({
return finish(); return finish();
} }
requestAnimationFrame(animateResponseText); window.windowHidden
? setTimeout(animateResponseText, 16)
: requestAnimationFrame(animateResponseText);
} }
// start animation // start animation
animateResponseText(); animateResponseText();

View File

@ -13,3 +13,12 @@ export const getWebLLMModel = (model?: string) => {
const list = useSystemStore.getState().llmModelList; const list = useSystemStore.getState().llmModelList;
return list.find((item) => item.model === model || item.name === model) ?? list[0]; return list.find((item) => item.model === model || item.name === model) ?? list[0];
}; };
export const watchWindowHidden = () => {
// @ts-ignore
if (document.hidden) {
window.windowHidden = true;
} else {
window.windowHidden = false;
}
};