perf: variable label picker scroll & focus disappear (#2135)

* fix: variable label picker scroll & focus disappear

* fix

* fix filter

* revert
This commit is contained in:
heheer
2024-07-23 18:03:53 +08:00
committed by GitHub
parent f37cdabb15
commit bf5145e632
10 changed files with 39 additions and 17 deletions

View File

@@ -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 {

View File

@@ -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 || '';

View File

@@ -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} />

View File

@@ -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'}>

View File

@@ -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;

View File

@@ -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,

View File

@@ -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}>

View File

@@ -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] || '';
}); });

View File

@@ -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'));

View File

@@ -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 && (