LibreChat/client/src/components/Chat/Input/Mention.tsx
Danny Avila 5209f1dc9e
refactor: Optimize Message Re-renders (#12097)
* 🔄 refactor: Update Artifacts and Messages Contexts to Use Latest Message ID and Depth

- Modified ArtifactsContext to retrieve latestMessage using Recoil state management.
- Updated MessagesViewContext to replace latestMessage with latestMessageId and latestMessageDepth for improved clarity and consistency.
- Adjusted various components (HoverButtons, MessageParts, MessageRender, ContentRender) to utilize latestMessageId instead of the entire message object, enhancing performance and reducing unnecessary re-renders.
- Refactored useChatHelpers to extract latestMessageId and latestMessageDepth, streamlining message handling across the application.

* refactor: Introduce PartWithContext Component for Optimized Message Rendering

- Added a new PartWithContext component to encapsulate message part rendering logic, improving context management and reducing redundancy in the ContentParts component.
- Updated MessageRender to utilize the new PartWithContext, streamlining the context provider setup and enhancing code clarity.
- Refactored related logic to ensure proper context values are passed, improving maintainability and performance in message rendering.

* refactor: Update Components to Use Function Declarations and Improve Readability

- Refactored several components (MessageContainer, Markdown, MarkdownCode, MarkdownCodeNoExecution, MarkdownAnchor, MarkdownParagraph, MarkdownImage, TextPart, PlaceholderRow) to use function declarations instead of arrow functions, enhancing readability and consistency across the codebase.
- Added display names to memoized components for better debugging and profiling in React DevTools.
- Improved overall code clarity and maintainability by standardizing component definitions.

* refactor: Standardize MessageRender and ContentRender Components for Improved Clarity

- Refactored MessageRender and ContentRender components to use function declarations, enhancing readability and consistency.
- Streamlined props handling by removing unnecessary parameters and improving the use of hooks for state management.
- Updated memoization and rendering logic to optimize performance and reduce unnecessary re-renders.
- Enhanced overall code clarity and maintainability by standardizing component definitions and structure.

* refactor: Enhance Header Component with Memoization for Performance

- Refactored the Header component to utilize React's memoization by wrapping it with the memo function, improving rendering performance by preventing unnecessary re-renders.
- Changed the export to a memoized version of the Header component, ensuring better debugging with a display name.
- Maintained overall code clarity and consistency in component structure.

* refactor: Transition Components to Use Recoil for State Management

- Updated multiple components (AddMultiConvo, TemporaryChat, HeaderNewChat, PresetsMenu, ModelSelectorChatContext) to utilize Recoil for state management, enhancing consistency and performance.
- Replaced useChatContext with Recoil selectors and atoms, improving data flow and reducing unnecessary re-renders.
- Introduced new selectors for conversation ID and endpoint retrieval, streamlining component logic and enhancing maintainability.
- Improved overall code clarity by standardizing state management practices across components.

* refactor: Integrate getConversation Callback for Enhanced State Management

- Updated multiple components (Mention, ModelSelectorChatContext, ModelSelectorContext, FavoritesList) to utilize a getConversation callback instead of directly accessing conversation state, improving encapsulation and maintainability.
- Refactored useSelectMention hook to accept getConversation, streamlining conversation retrieval and enhancing code clarity.
- Introduced new Recoil selectors for conversation properties, ensuring consistent state management across components.
- Enhanced overall code structure by standardizing the approach to conversation handling, reducing redundancy and improving performance.

* refactor: Optimize LiveAnnouncer Context Value with useMemo

- Updated the LiveAnnouncer component to utilize useMemo for context value creation, enhancing performance by preventing unnecessary recalculations of the context object.
- Improved overall code clarity and maintainability by ensuring that context values are only recomputed when their dependencies change.

* refactor: Update AgentPanelSwitch to Use Recoil for Agent ID Management

- Refactored AgentPanelSwitch component to utilize Recoil for retrieving the current agent ID, replacing the previous use of chat context.
- Improved state management by ensuring the agent ID is derived from Recoil, enhancing code clarity and maintainability.
- Adjusted useEffect dependencies to reflect the new state management approach, streamlining the component's logic.

* refactor: Enhance useLocalize Hook with useCallback for Improved Performance

- Updated the useLocalize hook to utilize useCallback for the translation function, optimizing performance by preventing unnecessary re-creations of the function on each render.
- Improved code clarity by ensuring that the translation function is memoized, enhancing maintainability and efficiency in localization handling.

* refactor: Rename useCreateConversationAtom to useSetConversationAtom for Clarity

- Updated the hook name from useCreateConversationAtom to useSetConversationAtom to better reflect its functionality in managing conversation state.
- Introduced a new implementation for setting conversation state, enhancing clarity and maintainability in the codebase.
- Adjusted related references in the useNewConvo hook to align with the new naming convention.

* refactor: Enhance useKeyDialog Hook with useMemo and useCallback for Improved Performance

- Updated the useKeyDialog hook to utilize useMemo for returning the dialog state and handlers, optimizing performance by preventing unnecessary recalculations.
- Refactored the onOpenChange function to use useCallback, ensuring it only changes when its dependencies do, enhancing maintainability and clarity in the code.
- Improved overall code structure and readability by streamlining the hook's logic and dependencies.

* feat: Add useRenderChangeLog Hook for Debugging Render Changes

- Introduced a new hook, useRenderChangeLog, that logs changes in tracked values between renders when a debug flag is enabled.
- Utilizes useEffect and useRef to track previous values and identify changes, enhancing debugging capabilities for component renders.
- Provides detailed console output for initial renders and value changes, improving developer insights during the rendering process.

* refactor: Update useSelectAgent Hook for Improved State Management and Performance

- Refactored the useSelectAgent hook to utilize useRecoilCallback for fetching conversation data, enhancing state management and performance.
- Replaced the use of useChatContext with a more efficient approach, streamlining the logic for selecting agents and updating conversations.
- Improved error handling and ensured asynchronous operations are properly awaited, enhancing reliability in agent selection and data fetching processes.

* refactor: Optimize useDefaultConvo Hook with useCallback for Improved Performance

- Refactored the getDefaultConversation function within the useDefaultConvo hook to utilize useCallback, enhancing performance by memoizing the function and preventing unnecessary re-creations on re-renders.
- Streamlined the logic for cleaning input and output in the conversation object, improving code clarity and maintainability.
- Ensured that dependencies for useCallback are correctly set, enhancing the reliability of the hook's behavior.

* refactor: Optimize Agent Components with Memoization for Improved Performance

- Refactored multiple agent-related components (AgentAvatar, AgentCategorySelector, AgentSelect, DeleteButton, FileContext, FileSearch, Files) to utilize React.memo for memoization, enhancing rendering performance by preventing unnecessary re-renders.
- Updated the FileRow component to make setFilesLoading optional, improving flexibility in file handling.
- Streamlined component logic and improved maintainability by ensuring that props are compared efficiently in memoized components.

* refactor: Enhance File Handling and Agent Components for Improved Performance

- Refactored multiple components (DeleteButton, FileContext, FileSearch, Files) to utilize new file handling hooks that separate chat context from file operations, improving performance and maintainability.
- Introduced useFileHandlingNoChatContext and useSharePointFileHandlingNoChatContext hooks to streamline file handling logic, enhancing flexibility in managing file states.
- Updated DeleteButton to improve conversation state management and ensure proper handling of agent deletions, enhancing user experience.
- Optimized imports and component structure for better clarity and organization across the affected files.

* refactor: Enhance useRenderChangeLog Hook with Improved Type Safety and Documentation

- Updated the useRenderChangeLog hook to improve type safety by specifying the value types as string, number, boolean, null, or undefined.
- Enhanced documentation to clarify usage and enablement of the debug feature, ensuring better developer insights during rendering.
- Added a production check to prevent logging in production builds, optimizing performance and maintaining clean console output.

* chore: imports

* refactor: Replace useRecoilCallback with useGetConversation Hook for Improved Clarity and Performance

- Refactored multiple components (AddMultiConvo, ModelSelectorChatContext, FavoritesList, useSelectAgent, usePresets) to utilize the new useGetConversation hook, enhancing clarity and reducing complexity by eliminating the use of useRecoilCallback.
- Streamlined conversation retrieval logic across components, improving maintainability and performance.
- Updated imports and component structure for better organization and readability.

* refactor: Enhance Memoization in DeleteButton Component for Improved Performance

- Updated the memoization logic in the DeleteButton component to include a comparison for the setCurrentAgentId prop, ensuring more efficient re-renders.
- This change improves performance by preventing unnecessary updates when the agent ID and current agent ID remain unchanged.

* chore: fix test

* refactor: Improve Memoization Logic in AgentSelect Component

- Updated the memoization comparison in the AgentSelect component to directly compare agentQuery.data objects, enhancing performance by ensuring accurate re-renders.
- Refactored the useCreateConversationAtom function to streamline the logic for updating conversation keys, improving clarity and maintainability.

* refactor: Simplify State Management in DeleteButton Component

- Removed unnecessary setConversationOption function, streamlining the logic for updating conversation state after agent deletion.
- Updated the conversation state directly within the deleteAgent mutation, improving clarity and maintainability of the component.
- Refactored conversationByKeySelector to directly reference conversationByIndex, enhancing performance and reducing complexity in state retrieval.

* refactor: Remove Unused Conversation Prop from Mention Component

- Eliminated the conversation prop from the Mention component, simplifying its interface and reducing unnecessary dependencies.
- Updated the ChatForm component to reflect this change, enhancing clarity and maintainability of the codebase.
- Introduced useGetConversation hook for improved conversation retrieval logic, streamlining the component's functionality.

* refactor: Simplify File Handling State Management Across Components

- Removed the unused setFilesLoading function from FileContext, FileSearch, and Files components, streamlining the file handling state management.
- Updated the FileHandlingState type to make setFilesLoading optional, enhancing flexibility in file operations.
- Improved memoization logic by directly referencing necessary state properties, ensuring better performance and maintainability.

* refactor: Update ArtifactsContext for Improved State Management

- Replaced the useChatContext hook with direct Recoil state retrieval for isSubmitting, latestMessage, and conversationId, simplifying the context provider's logic.
- Enhanced memoization by ensuring relevant state properties are directly referenced, improving performance and maintainability.
- Streamlined the context value creation to reflect the updated state management approach.

* refactor: Adjust Memoization Logic in ArtifactsContext for Consistency

- Updated the memoization logic in the ArtifactsProvider to ensure the messageId is consistently referenced, improving clarity and maintainability.
- This change enhances the performance of the context provider by ensuring all relevant properties are included in the memoization dependencies.
2026-03-06 00:03:32 -05:00

228 lines
7.1 KiB
TypeScript

import { useState, useRef, useEffect } from 'react';
import { useCombobox } from '@librechat/client';
import { AutoSizer, List } from 'react-virtualized';
import { EModelEndpoint } from 'librechat-data-provider';
import type { MentionOption, ConvoGenerator } from '~/common';
import type { SetterOrUpdater } from 'recoil';
import { useGetConversation, useLocalize, TranslationKeys } from '~/hooks';
import useSelectMention from '~/hooks/Input/useSelectMention';
import { useAssistantsMapContext } from '~/Providers';
import useMentions from '~/hooks/Input/useMentions';
import { removeCharIfLast } from '~/utils';
import MentionItem from './MentionItem';
const ROW_HEIGHT = 44;
export default function Mention({
setShowMentionPopover,
newConversation,
textAreaRef,
commandChar = '@',
placeholder = 'com_ui_mention',
includeAssistants = true,
}: {
setShowMentionPopover: SetterOrUpdater<boolean>;
newConversation: ConvoGenerator;
textAreaRef: React.MutableRefObject<HTMLTextAreaElement | null>;
commandChar?: string;
placeholder?: TranslationKeys;
includeAssistants?: boolean;
}) {
const localize = useLocalize();
const getConversation = useGetConversation(0);
const assistantsMap = useAssistantsMapContext();
const {
options,
presets,
modelSpecs,
agentsList,
modelsConfig,
endpointsConfig,
assistantListMap,
} = useMentions({ assistantMap: assistantsMap || {}, includeAssistants });
const { onSelectMention } = useSelectMention({
presets,
modelSpecs,
assistantsMap,
endpointsConfig,
getConversation,
newConversation,
});
const [activeIndex, setActiveIndex] = useState(0);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const inputRef = useRef<HTMLInputElement | null>(null);
const [inputOptions, setInputOptions] = useState<MentionOption[]>(options);
const { open, setOpen, searchValue, setSearchValue, matches } = useCombobox({
value: '',
options: inputOptions,
});
const handleSelect = (mention?: MentionOption) => {
if (!mention) {
return;
}
const defaultSelect = () => {
setSearchValue('');
setOpen(false);
setShowMentionPopover(false);
onSelectMention?.(mention);
if (textAreaRef.current) {
removeCharIfLast(textAreaRef.current, commandChar);
}
};
if (mention.type === 'endpoint' && mention.value === EModelEndpoint.agents) {
setSearchValue('');
setInputOptions(agentsList ?? []);
setActiveIndex(0);
inputRef.current?.focus();
} else if (mention.type === 'endpoint' && mention.value === EModelEndpoint.assistants) {
setSearchValue('');
setInputOptions(assistantListMap[EModelEndpoint.assistants] ?? []);
setActiveIndex(0);
inputRef.current?.focus();
} else if (mention.type === 'endpoint' && mention.value === EModelEndpoint.azureAssistants) {
setSearchValue('');
setInputOptions(assistantListMap[EModelEndpoint.azureAssistants] ?? []);
setActiveIndex(0);
inputRef.current?.focus();
} else if (mention.type === 'endpoint') {
const models = (modelsConfig?.[mention.value || ''] ?? []).map((model) => ({
value: mention.value,
label: model,
type: 'model',
}));
setActiveIndex(0);
setSearchValue('');
setInputOptions(models);
inputRef.current?.focus();
} else {
defaultSelect();
}
};
useEffect(() => {
if (!open) {
setInputOptions(options);
setActiveIndex(0);
}
}, [open, options]);
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
const type = commandChar !== '@' ? 'add-convo' : 'mention';
useEffect(() => {
const currentActiveItem = document.getElementById(`${type}-item-${activeIndex}`);
currentActiveItem?.scrollIntoView({ behavior: 'instant', block: 'nearest' });
}, [type, activeIndex]);
const rowRenderer = ({
index,
key,
style,
}: {
index: number;
key: string;
style: React.CSSProperties;
}) => {
const mention = matches[index] as MentionOption;
return (
<MentionItem
type={type}
index={index}
key={key}
style={style}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = null;
handleSelect(mention);
}}
name={mention.label ?? ''}
icon={mention.icon}
description={mention.description}
isActive={index === activeIndex}
/>
);
};
return (
<div className="absolute bottom-28 z-10 w-full space-y-2">
<div className="popover border-token-border-light rounded-2xl border bg-white p-2 shadow-lg dark:bg-gray-700">
<input
// The user expects focus to transition to the input field when the popover is opened
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus
ref={inputRef}
placeholder={localize(placeholder)}
className="mb-1 w-full border-0 bg-white p-2 text-sm focus:outline-none dark:bg-gray-700 dark:text-gray-200"
autoComplete="off"
value={searchValue}
onKeyDown={(e) => {
if (e.key === 'Escape') {
setOpen(false);
setShowMentionPopover(false);
textAreaRef.current?.focus();
}
if (e.key === 'ArrowDown') {
setActiveIndex((prevIndex) => (prevIndex + 1) % matches.length);
} else if (e.key === 'ArrowUp') {
setActiveIndex((prevIndex) => (prevIndex - 1 + matches.length) % matches.length);
} else if (e.key === 'Enter' || e.key === 'Tab') {
const mentionOption = matches[activeIndex] as MentionOption | undefined;
if (mentionOption?.type === 'endpoint') {
e.preventDefault();
} else if (e.key === 'Enter') {
e.preventDefault();
}
handleSelect(matches[activeIndex] as MentionOption);
} else if (e.key === 'Backspace' && searchValue === '') {
setOpen(false);
setShowMentionPopover(false);
textAreaRef.current?.focus();
}
}}
onChange={(e) => setSearchValue(e.target.value)}
onFocus={() => setOpen(true)}
onBlur={() => {
timeoutRef.current = setTimeout(() => {
setOpen(false);
setShowMentionPopover(false);
}, 150);
}}
/>
{open && (
<div className="max-h-40">
<AutoSizer disableHeight>
{({ width }) => (
<List
width={width}
overscanRowCount={5}
rowHeight={ROW_HEIGHT}
rowCount={matches.length}
rowRenderer={rowRenderer}
scrollToIndex={activeIndex}
height={Math.min(matches.length * ROW_HEIGHT, 160)}
/>
)}
</AutoSizer>
</div>
)}
</div>
</div>
);
}