mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 13:03:50 +00:00
perf: variable label picker scroll & focus disappear (#2135)
* fix: variable label picker scroll & focus disappear * fix * fix filter * revert
This commit is contained in:
@@ -240,7 +240,7 @@ export function replaceVariableLabel({
|
|||||||
variables: Record<string, string | number>;
|
variables: Record<string, string | number>;
|
||||||
runningNode: RuntimeNodeItemType;
|
runningNode: RuntimeNodeItemType;
|
||||||
}) {
|
}) {
|
||||||
if (!(typeof text === 'string')) return text;
|
if (typeof text !== 'string') return text;
|
||||||
|
|
||||||
const globalVariables = Object.keys(variables).map((key) => {
|
const globalVariables = Object.keys(variables).map((key) => {
|
||||||
return {
|
return {
|
||||||
|
@@ -292,14 +292,14 @@ async function fetchData({
|
|||||||
function replaceVariable(text: string, obj: Record<string, any>) {
|
function replaceVariable(text: string, obj: Record<string, any>) {
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
text = text.replace(new RegExp(`{{${key}}}`, 'g'), UNDEFINED_SIGN);
|
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), UNDEFINED_SIGN);
|
||||||
} else {
|
} else {
|
||||||
const replacement = JSON.stringify(value);
|
const replacement = JSON.stringify(value);
|
||||||
const unquotedReplacement =
|
const unquotedReplacement =
|
||||||
replacement.startsWith('"') && replacement.endsWith('"')
|
replacement.startsWith('"') && replacement.endsWith('"')
|
||||||
? replacement.slice(1, -1)
|
? replacement.slice(1, -1)
|
||||||
: replacement;
|
: replacement;
|
||||||
text = text.replace(new RegExp(`{{${key}}}`, 'g'), unquotedReplacement);
|
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), unquotedReplacement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return text || '';
|
return text || '';
|
||||||
|
@@ -129,7 +129,7 @@ export default function Editor({
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<VariableLabelPickerPlugin variables={variables} />
|
<VariableLabelPickerPlugin variables={variables} isFocus={focus} />
|
||||||
<VariablePlugin variables={variables} />
|
<VariablePlugin variables={variables} />
|
||||||
<VariableLabelPlugin variables={variables} />
|
<VariableLabelPlugin variables={variables} />
|
||||||
<OnBlurPlugin onBlur={onBlur} />
|
<OnBlurPlugin onBlur={onBlur} />
|
||||||
|
@@ -27,13 +27,18 @@ interface TransformedParent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function VariableLabelPickerPlugin({
|
export default function VariableLabelPickerPlugin({
|
||||||
variables
|
variables,
|
||||||
|
isFocus
|
||||||
}: {
|
}: {
|
||||||
variables: EditorVariablePickerType[];
|
variables: EditorVariablePickerType[];
|
||||||
|
isFocus: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [editor] = useLexicalComposerContext();
|
const [editor] = useLexicalComposerContext();
|
||||||
const [queryString, setQueryString] = useState<string | null>(null);
|
const [queryString, setQueryString] = useState<string | null>(null);
|
||||||
|
const [currentIndex, setCurrentIndex] = useState<number>(0);
|
||||||
|
const [highlightIndex, setHighlightIndex] = useState<number | null>(null);
|
||||||
|
const highlightedItemRef = React.useRef<any>(null);
|
||||||
|
|
||||||
const checkForTriggerMatch = useBasicTypeaheadTriggerMatch('/', {
|
const checkForTriggerMatch = useBasicTypeaheadTriggerMatch('/', {
|
||||||
minLength: 0
|
minLength: 0
|
||||||
@@ -58,6 +63,15 @@ export default function VariableLabelPickerPlugin({
|
|||||||
[editor]
|
[editor]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (highlightedItemRef.current) {
|
||||||
|
highlightedItemRef.current.scrollIntoView({
|
||||||
|
behavior: 'auto',
|
||||||
|
block: 'end'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [currentIndex]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LexicalTypeaheadMenuPlugin
|
<LexicalTypeaheadMenuPlugin
|
||||||
onQueryChange={setQueryString}
|
onQueryChange={setQueryString}
|
||||||
@@ -71,7 +85,11 @@ export default function VariableLabelPickerPlugin({
|
|||||||
if (anchorElementRef.current == null) {
|
if (anchorElementRef.current == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return anchorElementRef.current && variables.length
|
if (currentIndex !== selectedIndex) {
|
||||||
|
setCurrentIndex(selectedIndex || 0);
|
||||||
|
setHighlightIndex(selectedIndex || 0);
|
||||||
|
}
|
||||||
|
return anchorElementRef.current && variables.length && isFocus
|
||||||
? ReactDOM.createPortal(
|
? ReactDOM.createPortal(
|
||||||
<Box
|
<Box
|
||||||
bg={'white'}
|
bg={'white'}
|
||||||
@@ -123,7 +141,7 @@ export default function VariableLabelPickerPlugin({
|
|||||||
{item.label}
|
{item.label}
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
{item.children?.map((child, index) => (
|
{item.children?.map((child) => (
|
||||||
<Flex
|
<Flex
|
||||||
alignItems={'center'}
|
alignItems={'center'}
|
||||||
as={'li'}
|
as={'li'}
|
||||||
@@ -136,7 +154,8 @@ export default function VariableLabelPickerPlugin({
|
|||||||
_notLast={{
|
_notLast={{
|
||||||
mb: 1
|
mb: 1
|
||||||
}}
|
}}
|
||||||
{...(selectedIndex === child.index
|
ref={selectedIndex === child.index ? highlightedItemRef : null}
|
||||||
|
{...(highlightIndex === child.index
|
||||||
? {
|
? {
|
||||||
bg: '#1118240D',
|
bg: '#1118240D',
|
||||||
color: 'primary.700'
|
color: 'primary.700'
|
||||||
@@ -145,12 +164,11 @@ export default function VariableLabelPickerPlugin({
|
|||||||
bg: 'white',
|
bg: 'white',
|
||||||
color: 'myGray.600'
|
color: 'myGray.600'
|
||||||
})}
|
})}
|
||||||
onClick={() => {
|
onMouseDown={() => {
|
||||||
setHighlightedIndex(child.index);
|
|
||||||
selectOptionAndCleanUp({ ...child, parent: item });
|
selectOptionAndCleanUp({ ...child, parent: item });
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
setHighlightedIndex(child.index);
|
setHighlightIndex(child.index);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box ml={2} fontSize={'sm'} whiteSpace={'nowrap'}>
|
<Box ml={2} fontSize={'sm'} whiteSpace={'nowrap'}>
|
||||||
|
@@ -221,7 +221,7 @@ export function getHashtagRegexString(): string {
|
|||||||
const hashtag =
|
const hashtag =
|
||||||
`(${hashLeftCharList})` +
|
`(${hashLeftCharList})` +
|
||||||
`(${hashLeftCharList})` +
|
`(${hashLeftCharList})` +
|
||||||
`(${hashMiddleCharList})([a-zA-Z0-9_\\.]{0,29})(${hashMiddleCharList})` +
|
`(${hashMiddleCharList})([a-zA-Z0-9_\\.]{0,100})(${hashMiddleCharList})` +
|
||||||
`(${hashRightCharList})(${hashRightCharList})`;
|
`(${hashRightCharList})(${hashRightCharList})`;
|
||||||
|
|
||||||
return hashtag;
|
return hashtag;
|
||||||
|
@@ -35,6 +35,7 @@ type useChatStoreType = OutLinkChatAuthProps &
|
|||||||
ChatProviderProps & {
|
ChatProviderProps & {
|
||||||
welcomeText: string;
|
welcomeText: string;
|
||||||
variableList: VariableItemType[];
|
variableList: VariableItemType[];
|
||||||
|
allVariableList: VariableItemType[];
|
||||||
questionGuide: boolean;
|
questionGuide: boolean;
|
||||||
ttsConfig: AppTTSConfigType;
|
ttsConfig: AppTTSConfigType;
|
||||||
whisperConfig: AppWhisperConfigType;
|
whisperConfig: AppWhisperConfigType;
|
||||||
@@ -189,6 +190,7 @@ const Provider = ({
|
|||||||
teamToken,
|
teamToken,
|
||||||
welcomeText,
|
welcomeText,
|
||||||
variableList: variables.filter((item) => item.type !== VariableInputEnum.custom),
|
variableList: variables.filter((item) => item.type !== VariableInputEnum.custom),
|
||||||
|
allVariableList: variables,
|
||||||
questionGuide,
|
questionGuide,
|
||||||
ttsConfig,
|
ttsConfig,
|
||||||
whisperConfig,
|
whisperConfig,
|
||||||
|
@@ -21,7 +21,7 @@ const VariableInput = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { appAvatar, variableList, variablesForm } = useContextSelector(ChatBoxContext, (v) => v);
|
const { appAvatar, variableList, variablesForm } = useContextSelector(ChatBoxContext, (v) => v);
|
||||||
const { register, getValues, setValue, handleSubmit: handleSubmitChat, control } = variablesForm;
|
const { register, setValue, handleSubmit: handleSubmitChat, control } = variablesForm;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={3}>
|
<Box py={3}>
|
||||||
|
@@ -140,6 +140,7 @@ const ChatBox = (
|
|||||||
const {
|
const {
|
||||||
welcomeText,
|
welcomeText,
|
||||||
variableList,
|
variableList,
|
||||||
|
allVariableList,
|
||||||
questionGuide,
|
questionGuide,
|
||||||
startSegmentedAudio,
|
startSegmentedAudio,
|
||||||
finishSegmentedAudio,
|
finishSegmentedAudio,
|
||||||
@@ -390,7 +391,7 @@ const ChatBox = (
|
|||||||
|
|
||||||
// delete invalid variables, 只保留在 variableList 中的变量
|
// delete invalid variables, 只保留在 variableList 中的变量
|
||||||
const requestVariables: Record<string, any> = {};
|
const requestVariables: Record<string, any> = {};
|
||||||
variableList?.forEach((item) => {
|
allVariableList?.forEach((item) => {
|
||||||
requestVariables[item.key] = variables[item.key] || '';
|
requestVariables[item.key] = variables[item.key] || '';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -10,7 +10,6 @@ import {
|
|||||||
storeNodes2RuntimeNodes
|
storeNodes2RuntimeNodes
|
||||||
} from '@fastgpt/global/core/workflow/runtime/utils';
|
} from '@fastgpt/global/core/workflow/runtime/utils';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
|
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import { AppContext } from './context';
|
import { AppContext } from './context';
|
||||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||||
@@ -21,6 +20,7 @@ import dynamic from 'next/dynamic';
|
|||||||
import { useChat } from '@/components/core/chat/ChatContainer/useChat';
|
import { useChat } from '@/components/core/chat/ChatContainer/useChat';
|
||||||
import { Box } from '@chakra-ui/react';
|
import { Box } from '@chakra-ui/react';
|
||||||
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';
|
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';
|
||||||
|
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
|
||||||
|
|
||||||
const PluginRunBox = dynamic(() => import('@/components/core/chat/ChatContainer/PluginRunBox'));
|
const PluginRunBox = dynamic(() => import('@/components/core/chat/ChatContainer/PluginRunBox'));
|
||||||
|
|
||||||
|
@@ -132,8 +132,9 @@ const MyApps = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Flex gap={5} flex={'1 0 0'} h={0}>
|
<Flex gap={5} flex={'1 0 0'} h={0}>
|
||||||
<Box
|
<Flex
|
||||||
flex={'1 0 0'}
|
flex={'1 0 0'}
|
||||||
|
flexDirection={'column'}
|
||||||
h={'100%'}
|
h={'100%'}
|
||||||
pr={folderDetail ? [4, 2] : [4, 10]}
|
pr={folderDetail ? [4, 2] : [4, 10]}
|
||||||
pl={3}
|
pl={3}
|
||||||
@@ -237,7 +238,7 @@ const MyApps = () => {
|
|||||||
<MyBox flex={'1 0 0'} isLoading={myApps.length === 0 && isFetchingApps}>
|
<MyBox flex={'1 0 0'} isLoading={myApps.length === 0 && isFetchingApps}>
|
||||||
<List />
|
<List />
|
||||||
</MyBox>
|
</MyBox>
|
||||||
</Box>
|
</Flex>
|
||||||
|
|
||||||
{/* Folder slider */}
|
{/* Folder slider */}
|
||||||
{!!folderDetail && isPc && (
|
{!!folderDetail && isPc && (
|
||||||
|
Reference in New Issue
Block a user