perf: isPc check;perf: dataset max token checker (#4872)

* perf: isPc check

* perf: dataset max token checker

* perf: dataset max token checker
This commit is contained in:
Archer
2025-05-22 18:40:29 +08:00
committed by GitHub
parent 50481f4ca8
commit 6a6719e93d
8 changed files with 91 additions and 90 deletions

View File

@@ -17,6 +17,8 @@ weight: 790
1. LLM stream调用默认超时调大。 1. LLM stream调用默认超时调大。
2. 部分确认交互优化。 2. 部分确认交互优化。
3. 纠正原先知识库的“表格数据集”名称,改成“备份导入”。同时支持知识库索引的导出和导入。 3. 纠正原先知识库的“表格数据集”名称,改成“备份导入”。同时支持知识库索引的导出和导入。
4. 工作流知识库引用上限,如果工作流中没有相关 AI 节点,则交互模式改成纯手动输入,并且上限为 1000万。
5. 语音输入,移动端判断逻辑,准确判断是否为手机,而不是小屏。
## 🐛 修复 ## 🐛 修复

View File

@@ -60,5 +60,3 @@ export enum AppTemplateTypeEnum {
// special type // special type
contribute = 'contribute' contribute = 'contribute'
} }
export const defaultDatasetMaxTokens = 16000;

View File

@@ -11,40 +11,6 @@ export const beforeUpdateAppFormat = <T extends AppSchema['modules'] | undefined
nodes: T; nodes: T;
isPlugin: boolean; isPlugin: boolean;
}) => { }) => {
if (nodes) {
// Check dataset maxTokens
if (isPlugin) {
let maxTokens = 16000;
nodes.forEach((item) => {
if (
item.flowNodeType === FlowNodeTypeEnum.chatNode ||
item.flowNodeType === FlowNodeTypeEnum.tools
) {
const model =
item.inputs.find((item) => item.key === NodeInputKeyEnum.aiModel)?.value || '';
const chatModel = getLLMModel(model);
const quoteMaxToken = chatModel.quoteMaxToken || 16000;
maxTokens = Math.max(maxTokens, quoteMaxToken);
}
});
nodes.forEach((item) => {
if (item.flowNodeType === FlowNodeTypeEnum.datasetSearchNode) {
item.inputs.forEach((input) => {
if (input.key === NodeInputKeyEnum.datasetMaxTokens) {
const val = input.value as number;
if (val > maxTokens) {
input.value = maxTokens;
}
}
});
}
});
}
}
return { return {
nodes nodes
}; };

View File

@@ -18,10 +18,10 @@ export const getWebReqUrl = (url: string = '') => {
}; };
export const isMobile = () => { export const isMobile = () => {
// 服务端渲染时返回 false // SSR return false
if (typeof window === 'undefined') return false; if (typeof window === 'undefined') return false;
// 1. 检查 User-Agent // 1. Check User-Agent
const userAgent = navigator.userAgent.toLowerCase(); const userAgent = navigator.userAgent.toLowerCase();
const mobileKeywords = [ const mobileKeywords = [
'android', 'android',
@@ -36,12 +36,12 @@ export const isMobile = () => {
]; ];
const isMobileUA = mobileKeywords.some((keyword) => userAgent.includes(keyword)); const isMobileUA = mobileKeywords.some((keyword) => userAgent.includes(keyword));
// 2. 检查屏幕宽度 // 2. Check screen width
const isMobileWidth = window.innerWidth <= 900; const isMobileWidth = window.innerWidth <= 900;
// 3. 检查是否支持触摸事件排除触控屏PC // 3. Check if touch events are supported (exclude touch screen PCs)
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
// 综合判断:满足以下任一条件即视为移动端 // If any of the following conditions are met, it is considered a mobile device
return isMobileUA || (isMobileWidth && isTouchDevice); return isMobileUA || (isMobileWidth && isTouchDevice);
}; };

View File

@@ -25,11 +25,11 @@ import SelectAiModel from '@/components/Select/AIModelSelector';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import MyTextarea from '@/components/common/Textarea/MyTextarea'; import MyTextarea from '@/components/common/Textarea/MyTextarea';
import { defaultDatasetMaxTokens } from '@fastgpt/global/core/app/constants';
import InputSlider from '@fastgpt/web/components/common/MySlider/InputSlider'; import InputSlider from '@fastgpt/web/components/common/MySlider/InputSlider';
import LeftRadio from '@fastgpt/web/components/common/Radio/LeftRadio'; import LeftRadio from '@fastgpt/web/components/common/Radio/LeftRadio';
import { type AppDatasetSearchParamsType } from '@fastgpt/global/core/app/type'; import { type AppDatasetSearchParamsType } from '@fastgpt/global/core/app/type';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
enum SearchSettingTabEnum { enum SearchSettingTabEnum {
searchMode = 'searchMode', searchMode = 'searchMode',
@@ -48,7 +48,7 @@ const DatasetParamsModal = ({
datasetSearchUsingExtensionQuery, datasetSearchUsingExtensionQuery,
datasetSearchExtensionModel, datasetSearchExtensionModel,
datasetSearchExtensionBg, datasetSearchExtensionBg,
maxTokens = defaultDatasetMaxTokens, maxTokens,
onClose, onClose,
onSuccess onSuccess
}: AppDatasetSearchParamsType & { }: AppDatasetSearchParamsType & {
@@ -130,7 +130,7 @@ const DatasetParamsModal = ({
// 保证只有 80 左右个刻度。 // 保证只有 80 左右个刻度。
const maxTokenStep = useMemo(() => { const maxTokenStep = useMemo(() => {
if (maxTokens < 8000) return 80; if (!maxTokens || maxTokens < 8000) return 80;
return Math.ceil(maxTokens / 80 / 100) * 100; return Math.ceil(maxTokens / 80 / 100) * 100;
}, [maxTokens]); }, [maxTokens]);
@@ -301,16 +301,27 @@ const DatasetParamsModal = ({
<QuestionTip label={t('common:max_quote_tokens_tips')} /> <QuestionTip label={t('common:max_quote_tokens_tips')} />
</Flex> </Flex>
<Box flex={'1 0 0'}> <Box flex={'1 0 0'}>
<InputSlider {maxTokens ? (
min={100} <InputSlider
max={maxTokens} min={100}
step={maxTokenStep} max={maxTokens}
value={getValues(NodeInputKeyEnum.datasetMaxTokens) ?? 1000} step={maxTokenStep}
onChange={(val) => { value={getValues(NodeInputKeyEnum.datasetMaxTokens) ?? 1000}
setValue(NodeInputKeyEnum.datasetMaxTokens, val); onChange={(val) => {
setRefresh(!refresh); setValue(NodeInputKeyEnum.datasetMaxTokens, val);
}} setRefresh(!refresh);
/> }}
/>
) : (
<MyNumberInput
size={'sm'}
min={100}
max={1000000}
step={100}
register={register}
name={NodeInputKeyEnum.datasetMaxTokens}
/>
)}
</Box> </Box>
</Box> </Box>
)} )}

View File

@@ -214,7 +214,8 @@ const MobileVoiceInput = ({
const VoiceInput = forwardRef<VoiceInputComponentRef, VoiceInputProps>( const VoiceInput = forwardRef<VoiceInputComponentRef, VoiceInputProps>(
({ onSendMessage, resetInputVal }, ref) => { ({ onSendMessage, resetInputVal }, ref) => {
const { t } = useTranslation(); const { t } = useTranslation();
const isPc = !isMobile(); const isMobileDevice = isMobile();
const { isPc } = useSystem();
const outLinkAuthData = useContextSelector(ChatBoxContext, (v) => v.outLinkAuthData); const outLinkAuthData = useContextSelector(ChatBoxContext, (v) => v.outLinkAuthData);
const appId = useContextSelector(ChatBoxContext, (v) => v.appId); const appId = useContextSelector(ChatBoxContext, (v) => v.appId);
@@ -265,10 +266,10 @@ const VoiceInput = forwardRef<VoiceInputComponentRef, VoiceInputProps>(
return; return;
} }
if (isPc) { if (isMobileDevice) {
renderAudioGraphPc(analyser, canvas);
} else {
renderAudioGraphMobile(analyser, canvas); renderAudioGraphMobile(analyser, canvas);
} else {
renderAudioGraphPc(analyser, canvas);
} }
animationFrameId = window.requestAnimationFrame(renderCurve); animationFrameId = window.requestAnimationFrame(renderCurve);
}; };
@@ -283,7 +284,7 @@ const VoiceInput = forwardRef<VoiceInputComponentRef, VoiceInputProps>(
source.disconnect(); source.disconnect();
analyser.disconnect(); analyser.disconnect();
}; };
}, [stream, canvasRef, renderAudioGraphPc, renderAudioGraphMobile, isPc]); }, [stream, canvasRef, renderAudioGraphPc, renderAudioGraphMobile, isMobileDevice]);
const onStartSpeak = useCallback(() => { const onStartSpeak = useCallback(() => {
const finishWhisperTranscription = (text: string) => { const finishWhisperTranscription = (text: string) => {
@@ -301,12 +302,12 @@ const VoiceInput = forwardRef<VoiceInputComponentRef, VoiceInputProps>(
}, [autoTTSResponse, onSendMessage, resetInputVal, startSpeak, whisperConfig?.autoSend]); }, [autoTTSResponse, onSendMessage, resetInputVal, startSpeak, whisperConfig?.autoSend]);
const onSpeach = useCallback(() => { const onSpeach = useCallback(() => {
if (isPc) { if (isMobileDevice) {
onStartSpeak();
} else {
setMobilePreSpeak(true); setMobilePreSpeak(true);
} else {
onStartSpeak();
} }
}, [isPc, onStartSpeak]); }, [isMobileDevice, onStartSpeak]);
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
onSpeak: onSpeach onSpeak: onSpeach
})); }));
@@ -328,13 +329,7 @@ const VoiceInput = forwardRef<VoiceInputComponentRef, VoiceInputProps>(
borderRadius={isPc ? 'md' : ''} borderRadius={isPc ? 'md' : ''}
onContextMenu={(e) => e.preventDefault()} onContextMenu={(e) => e.preventDefault()}
> >
{isPc ? ( {isMobileDevice ? (
<PCVoiceInput
speakingTimeString={speakingTimeString}
stopSpeak={stopSpeak}
canvasRef={canvasRef}
/>
) : (
<MobileVoiceInput <MobileVoiceInput
isSpeaking={isSpeaking} isSpeaking={isSpeaking}
onStartSpeak={onStartSpeak} onStartSpeak={onStartSpeak}
@@ -342,6 +337,12 @@ const VoiceInput = forwardRef<VoiceInputComponentRef, VoiceInputProps>(
stopSpeak={stopSpeak} stopSpeak={stopSpeak}
canvasRef={canvasRef} canvasRef={canvasRef}
/> />
) : (
<PCVoiceInput
speakingTimeString={speakingTimeString}
stopSpeak={stopSpeak}
canvasRef={canvasRef}
/>
)} )}
{isTransCription && ( {isTransCription && (

View File

@@ -24,6 +24,8 @@ import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import ValueTypeLabel from './render/ValueTypeLabel'; import ValueTypeLabel from './render/ValueTypeLabel';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { getWebLLMModel } from '@/web/common/system/utils'; import { getWebLLMModel } from '@/web/common/system/utils';
import InputSlider from '@fastgpt/web/components/common/MySlider/InputSlider';
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => { const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -32,35 +34,58 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const CustomComponent = useMemo(() => { const CustomComponent = useMemo(() => {
const quoteList = inputs.filter((item) => item.canEdit); const quoteList = inputs.filter((item) => item.canEdit);
const tokenLimit = (() => { const maxTokens = (() => {
let maxTokens = 16000; let maxTokens = 0;
nodeList.forEach((item) => { nodeList.forEach((item) => {
if ([FlowNodeTypeEnum.chatNode, FlowNodeTypeEnum.tools].includes(item.flowNodeType)) { if ([FlowNodeTypeEnum.chatNode, FlowNodeTypeEnum.tools].includes(item.flowNodeType)) {
const model = const model =
item.inputs.find((item) => item.key === NodeInputKeyEnum.aiModel)?.value || ''; item.inputs.find((item) => item.key === NodeInputKeyEnum.aiModel)?.value || '';
const quoteMaxToken = getWebLLMModel(model)?.quoteMaxToken || 16000; const quoteMaxToken = getWebLLMModel(model)?.quoteMaxToken || 0;
maxTokens = Math.max(maxTokens, quoteMaxToken); maxTokens = Math.max(maxTokens, quoteMaxToken);
} }
}); });
return maxTokens; return maxTokens ? maxTokens : undefined;
})();
const maxTokenStep = (() => {
if (!maxTokens || maxTokens < 8000) return 80;
return Math.ceil(maxTokens / 80 / 100) * 100;
})(); })();
return { return {
[NodeInputKeyEnum.datasetMaxTokens]: (item: FlowNodeInputItemType) => ( [NodeInputKeyEnum.datasetMaxTokens]: (item: FlowNodeInputItemType) =>
<Box px={2}> maxTokens ? (
<MySlider <Box px={2} bg={'white'} py={2} border={'base'} borderRadius={'md'}>
markList={[ <InputSlider
{ label: '100', value: 100 }, min={100}
{ label: tokenLimit, value: tokenLimit } max={maxTokens}
]} step={maxTokenStep}
width={'100%'} value={item.value}
onChange={(e) => {
onChangeNode({
nodeId,
type: 'updateInput',
key: item.key,
value: {
...item,
value: e
}
});
}}
/>
</Box>
) : (
<MyNumberInput
size={'sm'}
min={100} min={100}
max={tokenLimit} max={1000000}
step={50} step={100}
value={item.value} value={item.value}
name={NodeInputKeyEnum.datasetMaxTokens}
bg={'white'}
onChange={(e) => { onChange={(e) => {
onChangeNode({ onChangeNode({
nodeId, nodeId,
@@ -73,8 +98,7 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
}); });
}} }}
/> />
</Box> ),
),
[NodeInputKeyEnum.datasetQuoteList]: (item: FlowNodeInputItemType) => { [NodeInputKeyEnum.datasetQuoteList]: (item: FlowNodeInputItemType) => {
return ( return (
<> <>

View File

@@ -12,7 +12,6 @@ import SearchParamsTip from '@/components/core/dataset/SearchParamsTip';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/pageComponents/app/detail/WorkflowComponents/context'; import { WorkflowContext } from '@/pageComponents/app/detail/WorkflowComponents/context';
import { getWebLLMModel } from '@/web/common/system/utils'; import { getWebLLMModel } from '@/web/common/system/utils';
import { defaultDatasetMaxTokens } from '@fastgpt/global/core/app/constants';
import { type AppDatasetSearchParamsType } from '@fastgpt/global/core/app/type'; import { type AppDatasetSearchParamsType } from '@fastgpt/global/core/app/type';
const SelectDatasetParam = ({ inputs = [], nodeId }: RenderInputProps) => { const SelectDatasetParam = ({ inputs = [], nodeId }: RenderInputProps) => {
@@ -36,19 +35,19 @@ const SelectDatasetParam = ({ inputs = [], nodeId }: RenderInputProps) => {
}); });
const tokenLimit = useMemo(() => { const tokenLimit = useMemo(() => {
let maxTokens = defaultDatasetMaxTokens; let maxTokens = 0;
nodeList.forEach((item) => { nodeList.forEach((item) => {
if ([FlowNodeTypeEnum.chatNode, FlowNodeTypeEnum.tools].includes(item.flowNodeType)) { if ([FlowNodeTypeEnum.chatNode, FlowNodeTypeEnum.tools].includes(item.flowNodeType)) {
const model = const model =
item.inputs.find((item) => item.key === NodeInputKeyEnum.aiModel)?.value || ''; item.inputs.find((item) => item.key === NodeInputKeyEnum.aiModel)?.value || '';
const quoteMaxToken = getWebLLMModel(model)?.quoteMaxToken || defaultDatasetMaxTokens; const quoteMaxToken = getWebLLMModel(model)?.quoteMaxToken ?? 0;
maxTokens = Math.max(maxTokens, quoteMaxToken); maxTokens = Math.max(maxTokens, quoteMaxToken);
} }
}); });
return maxTokens; return maxTokens ? maxTokens : undefined;
}, [nodeList]); }, [nodeList]);
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();