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:
parent
fde1618af2
commit
56281d92f2
@ -21,5 +21,6 @@ weight: 813
|
|||||||
4. 优化 - 工作流嵌套层级限制 20 层,避免因编排不合理导致的无限死循环。
|
4. 优化 - 工作流嵌套层级限制 20 层,避免因编排不合理导致的无限死循环。
|
||||||
5. 优化 - 工作流 handler 性能优化。
|
5. 优化 - 工作流 handler 性能优化。
|
||||||
6. 优化 - 工作流快捷键,避免调试测试时也会触发。
|
6. 优化 - 工作流快捷键,避免调试测试时也会触发。
|
||||||
7. 修复 - 知识库选择权限问题。
|
7. 优化 - 流输出,切换 tab 时仍可以继续输出。
|
||||||
8. 修复 - 空 chatId 发起对话,首轮携带用户选择时会异常。
|
8. 修复 - 知识库选择权限问题。
|
||||||
|
9. 修复 - 空 chatId 发起对话,首轮携带用户选择时会异常。
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -141,7 +141,6 @@ export function useScrollPagination<
|
|||||||
// Reload data
|
// Reload data
|
||||||
useRequest(
|
useRequest(
|
||||||
async () => {
|
async () => {
|
||||||
console.log('reload', 11111);
|
|
||||||
loadData(1);
|
loadData(1);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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'}>
|
||||||
|
|||||||
@ -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)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -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();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
3
projects/app/src/types/index.d.ts
vendored
3
projects/app/src/types/index.d.ts
vendored
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user