mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-04-03 14:27:20 +02:00
3 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7e2b51697e
|
🪢 refactor: Eliminate Unnecessary Re-renders During Message Streaming (#12454)
Some checks failed
Publish `librechat-data-provider` to NPM / build (push) Has been cancelled
Publish `@librechat/data-schemas` to NPM / build-and-publish (push) Has been cancelled
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Has been cancelled
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Has been cancelled
Publish `librechat-data-provider` to NPM / publish-npm (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Has been cancelled
* refactor: add TMessageChatContext type for stable context passing Defines a type for a stable context object that wrapper components pass to memo'd message components, avoiding direct ChatContext subscriptions that bypass React.memo during streaming. * perf: remove ChatContext subscription from useMessageActions useMessageActions previously called useChatContext() inside memo'd components (MessageRender, ContentRender), bypassing React.memo when isSubmitting changed during streaming. Now accepts a stable chatContext param instead, using a ref for the isSubmitting guard in regenerateMessage. Also stabilizes handleScroll in useMessageProcess by using a ref for isSubmitting instead of including it in useCallback deps. * perf: pass stable chatContext to memo'd message components Wrapper components (Message, MessageContent) now create a stable chatContext object via useMemo with a getter-backed isSubmitting, and compute effectiveIsSubmitting (false for non-latest messages). This ensures MessageRender and ContentRender (both React.memo'd) only re-render for the latest message during streaming, preventing unnecessary re-renders of all prior messages and their SubRow, HoverButtons, and SiblingSwitch children. * perf: add custom memo comparators to prevent message reference re-renders buildTree creates new message objects on every streaming update for ALL messages, not just the changed one. This defeats React.memo's default shallow comparison since the message prop has a new reference even when the content hasn't changed. Custom areEqual comparators now compare message by key fields (messageId, text, error, depth, children length, etc.) instead of reference equality, preventing unnecessary re-renders of SubRow, Files, HoverButtons and other children for non-latest messages. * perf: memoize ChatForm children to prevent streaming re-renders - Wrap StopButton in React.memo - Wrap AudioRecorder in React.memo, use ref for isSubmitting in onTranscriptionComplete callback to stabilize it - Remove useChatContext() from FileFormChat (bypassed its memo during streaming), accept files/setFiles/setFilesLoading as props from ChatForm instead * perf: stabilize ChatForm child props to prevent cascading re-renders ChatForm re-renders frequently during streaming (ChatContext changes). This caused StopButton and AttachFileChat/AttachFileMenu to re-render despite being memo'd, because their props were new references each time. - Wrap handleStopGenerating in a ref-based stable callback so StopButton always receives the same function reference - Create stableConversation via useMemo keyed on rendering-relevant fields only (conversationId, endpoint, agent_id, etc.), so AttachFileChat and FileFormChat don't re-render from unrelated conversation metadata updates (e.g., title generation) * perf: remove ChatContext subscription from AttachFileMenu and FileFormChat Both components used useFileHandling() which internally calls useChatContext(), bypassing their React.memo wrappers and causing re-renders on every streaming chunk. Switch to useFileHandlingNoChatContext() which accepts file state as parameters. The state (files, setFiles, setFilesLoading, conversation) is passed down from ChatForm → AttachFileChat → AttachFileMenu as props, keeping the memo chain intact. * fix: update imports and test mocks for useFileHandlingNoChatContext - Re-export useFileHandlingNoChatContext from hooks barrel - Import from ~/hooks instead of direct path for test compatibility - Add useToastContext mock to @librechat/client in AttachFileMenu tests since useFileHandlingNoChatContext runs the core hook which needs it - Add useFileHandlingNoChatContext to ~/hooks test mock * perf: fix remaining ChatForm streaming re-renders - Switch AttachFileMenu from useSharePointFileHandling (subscribes to ChatContext) to useSharePointFileHandlingNoChatContext with explicit file state props - Memoize ChatForm textarea onFocus/onBlur handlers with useCallback to prevent TextareaAutosize re-renders (inline arrow functions and .bind() created new references on every ChatForm render) - Update AttachFileMenu test mocks for new hook variants * refactor: add displayName to ChatForm for React DevTools * perf: prevent ChatForm re-renders during streaming via wrapper pattern ChatForm was re-rendering on every streaming chunk because it subscribed to useChatContext() internally, and the ChatContext value changed frequently during streaming. Extract context subscription into a ChatFormWrapper that: - Subscribes to useChatContext() (re-renders on every chunk, cheap) - Stabilizes conversation via selective useMemo - Stabilizes handleStopGenerating via ref-based callback - Passes individual stable values as props to ChatForm ChatForm (memo'd) now receives context values as props instead of subscribing directly. Since individual values (files, setFiles, isSubmitting, etc.) are stable references during streaming, ChatForm's memo prevents re-renders entirely — it only re-renders when isSubmitting actually toggles (2x per stream: start/end). * perf: stabilize newConversation prop and memoize CollapseChat - Wrap newConversation in ref-based stable callback in ChatFormWrapper (was the remaining unstable prop causing ChatForm to re-render) - Wrap CollapseChat in React.memo to prevent re-renders from parent * perf: memoize useAddedResponse return value useAddedResponse returned a new object literal on every render, causing AddedChatContext.Provider to trigger re-renders of all consumers (including ChatForm) on every streaming chunk. Wrap in useMemo so the context value stays referentially stable. * perf: memoize TextareaHeader to prevent re-renders from ChatForm * perf: address review findings for streaming render optimization Finding 1: Switch AttachFile.tsx from useFileHandling to useFileHandlingNoChatContext, closing the optimization hole for standard (non-agent) chat endpoints. Finding 2: Replace content reference equality with length comparison in both memo comparators — safer against buildTree array reconstruction. Finding 3: Add conversation?.model to stableConversation deps in ChatFormWrapper so file uploads use the correct model after switches. Finding 4/14: Fix stableNewConversation to explicitly return the underlying call's result instead of discarding it via `as` cast. Finding 5/6: Extract useMemoizedChatContext hook shared by Message.tsx and MessageContent.tsx — eliminates ~70 lines of duplication and stabilizes chatContext.conversation via selective useMemo to prevent post-stream metadata updates from re-rendering all messages. Finding 8: Use TMessage type for regenerate param instead of Record<string, unknown>. Finding 9: Use FileSetter alias in FileFormChat instead of inline type. Finding 11: Fix pre-existing broken throttle in useMessageProcess — was creating a new throttle instance per call, providing zero deduplication. Now retains the instance via useMemo. Finding 12: Initialize isSubmittingRef with chatContext.isSubmitting instead of false for consistency. Finding 13: Add ChatFormWrapper displayName. * fix: revert content comparison to reference equality in memo comparators The length-based comparison (content?.length) missed updates within existing content parts during streaming — text chunks update a part's content without changing the array length, so the comparator returned true and skipped re-renders for the latest message. Reference equality (===) is correct here: buildTree preserves content array references for unchanged messages via shallow spread, while React Query gives the latest message a new reference when its content updates during streaming. * fix: cancel throttled handleScroll on unmount and remove unused import * fix: use chatContext getter directly in regenerateMessage callback The local isSubmittingRef was stale for non-latest messages (which don't re-render during streaming by design). chatContext.isSubmitting is a getter backed by the wrapper's ref, so reading it at call-time always returns the current value regardless of whether the component has re-rendered. * fix: remove unused useCallback import from useMemoizedChatContext * fix: pass global isSubmitting to HoverButtons for action gating HoverButtons uses isSubmitting via useGenerationsByLatest to disable regenerate and hide edit buttons during streaming. Passing the effective value (false for non-latest messages) re-enabled those actions mid-stream, risking overlapping edits/regenerations. Use chatContext.isSubmitting (getter, always returns current value) for HoverButtons while keeping the effective value for rendering-only UI (cursor, placeholder, streaming indicator). * fix: address second review — stale HoverButtons, messages dep, cleanup - Add isSubmitting to chatContext useMemo deps in useMemoizedChatContext so HoverButtons correctly updates when streaming starts/ends (2 extra re-renders per session, belt-and-suspenders for post-stream state) - Change conversation?.messages?.length dep to boolean in ChatFormWrapper stableConversation — only need 0↔1+ transition for landing page check, not exact count on every message addition - Add defensive comment at chatContext destructuring point in useMessageActions explaining why isSubmitting must not be destructured - Remove dead mockUseFileHandling.mockReturnValue from AttachFileMenu tests * chore: remove dead useFileHandling mock artifacts from AttachFileMenu tests * fix: resolve eslint warnings for useMemo dependencies - Extract complex expression (conversation?.messages?.length ?? 0) > 0 to hasMessages variable for static analysis in ChatFormWrapper - Add eslint-disable for intentional isSubmitting dep in useMemoizedChatContext (forces new chatContext reference on streaming start/end so HoverButtons re-renders) |
||
|
|
a26597a696
|
📇 refactor: Improve State mgmt. for File uploads and Tool Auth (#9359)
* 🔧 fix: Ensure loading state is correctly set when files are empty or in progress * 🔧 fix: Update ephemeral agent state on file upload error for execute code tool resource * 🔧 fix: Reset ephemeral agent state for tool when authentication fails * refactor: Pass conversation prop to FileFormChat and AttachFileChat components |
||
|
|
7f29f2f676
|
🎨 feat: UI Refresh for Enhanced UX (#6346)
* ✨ feat: Add Expand Chat functionality and improve UI components * ✨ feat: Introduce Chat Badges feature with editing capabilities and UI enhancements * ✨ feat: re-implement file attachment functionality with new components and improved UI * ✨ feat: Enhance BadgeRow component with drag-and-drop functionality and add animations for better user experience * ✨ feat: Add useChatBadges hook and enhance Badge component with animations and toggle functionality * feat: Improve Add/Delete Badges + style and bug fixes * ✨ feat: Refactor EditBadges component and optimize useChatBadges hook for improved performance and readability * ✨ feat: Add type definition for LucideIcon in EditBadges component * refactor: Clean up BadgeRow component by removing outdated comment and improving code readability * refactor: Rename app-icon class to badge-icon for consistency and improve badge styling * feat: Add Center Chat Input toggle and update related components for improved UI/UX * refactor: Simplify ChatView and MessagesView components for improved readability and performance * refactor: Improve layout and positioning of scroll button in MessagesView component * refactor: Adjust scroll button position in MessagesView component for better visibility * refactor: Remove redundant background class from Badge component for cleaner styling * feat: disable chat badges * refactor: adjust positioning of scroll button and popover for improved layout * refactor: simplify class names in ChatForm and RemoveFile components for cleaner code * refactor: move Switcher to HeaderOptions from SidePanel * fix(Landing): duplicate description * feat: add SplitText component for animated text display and update Landing component to use it * feat(Chat): add ConversationStarters component and integrate it into ChatView; remove ConvoStarter component * feat(Chat): enhance Message component layout and styling for improved readability * feat(ControlCombobox, Select): enhance styling and add animation for improved UI experience * feat(Chat): update Header and HeaderNewChat components for improved layout and styling * feat(Chat): add ModelDropdown (now includes both endpoint and model) and refactor Menu components for improved UI * feat(ModelDropdown): add Agent Select; removed old AgentSwitcher components * feat(ModelDropdown): add settings button for user key configuration * fix(ModelDropdown): the model dropdown wasn't opening automatically when opening the endpoint one * refactor(Chat): remove unused EndpointsMenu and related components to streamline codebase * feat: enhance greeting message and improve accessibility fro ModelDropdown * refactor(Endpoints): add new hooks and components for endpoint management * feat(Endpoint): add support for modelSpecs * feat(Endpoints): add mobile support * fix: type issues * fix(modelSpec): type issue * fix(EndpointMenuDropdown): double overflow scroller in mobile model list * fix: search model on mobile * refactor: Endpoint/Model/modelSpec dropdown * refactor: reorganize imports in Endpoint components * refactor: remove unused translation keys from English locale * BREAKING: moving to ariakit with new CustomMenu * refactor: remove unnecessary comments * refactor: remove EndpointItem, ModelDropdownButton, SpecIcon, and SpecItem components * 🔧 fix: AI Icon bump when regenerating message * wip: chat UI refactoring, fix issues * chore: add recent update to useAutoSave * feat: add access control for agent permissions in useMentions hook * refactor: streamline ModelSelector by removing unused endpoints logic * refactor: enhance ModelSelector and context by integrating endpointsConfig and improving type usage * feat: update ModelSelectorContext to utilize conversation data for initial state * feat: add selector effects for synced endpoint handling * feat: add guard clause for conversation endpoint in useSelectorEffects hook * fix: safely call onSelectMention and add autofocus to mention input * chore: typing * refactor: ModelSelector to streamline key dialog handling and improve endpoint rendering * refactor: extract SettingsButton component for cleaner endpoint item rendering * wip: first pass, expand set api key * wip: first pass, expanding set key * refactor: update EndpointItem styles for improved layout and hover effects * refactor: adjust padding in EndpointItem for improved layout consistency * refactor: update preset structure in useSelectMention to include spec as null * refactor: rename setKeyDialogOpen to onOpenChange for clarity and consistency, bring focus back to button that opened dialog * feat: add SpecIcon component for dynamic model spec icons in menu, adjust icon styling * refactor: update getSelectedIcon to accept additional parameters and improve icon rendering logic * fix: adjust padding in MessageRender for improved layout * refactor: remove inline style for menu width in CustomMenu component * refactor: enhance layout and styling in ModelSpecItem component for better responsiveness * refactor: update getDefaultModelSpec to accept startupConfig and improve model spec retrieval logic * refactor: improve key management and default values in ModelSelector and related components * refactor: adjust menu width and improve responsiveness in CustomMenu and EndpointItem components * refactor: enhance focus styles and responsiveness in EndpointItem component * refactor: improve layout and spacing in Header and ModelSelector components for better responsiveness * refactor: adjust button styles for consistency and improved layout in AddMultiConvo and PresetsMenu components * fix: initial fix of assistant names * fix: assistants handling * chore: update version of librechat-data-provider to 0.7.75 and add 'spec' to excludedKeys * fix: improve endpoint filtering logic based on interface configuration and access rights * fix: remove unused HeaderOptions import and set spec to null in presets and mentions * fix: ensure currentExample is always an object when updating examples * fix: update interfaceConfig checks to ensure modelSelect is considered for rendering components * fix: update model selection logic to consider interface configuration when prioritizing model specs * fix: add missing localizations * fix: remove unused agent and assistant selection translations * fix: implement debounced state updates for selected values in useSelectorEffects * style: minor style changes related to the ModelSelector * fix: adjust maximum height for popover and set fixed height for model item * fix: update placeholders for model and endpoint search inputs * fix: refactor MessageRender and ContentRender components to better match each other * fix: remove convo fallback for iconURL in MessageRender and ContentRender components * fix: update handling of spec, iconURL, and modelLabel in conversation presets, to allow better interchangeability * fix: replace chatGptLabel with modelLabel in OpenAI settings configuration (fully deprecate chatGptLabel) * fix: remove console log for assistantNames in useEndpoints hook * refactor: add cleanInput and cleanOutput options to default conversation handling * chore: update bun.lockb * fix: set default value for showIconInHeader in getSelectedIcon function * refactor: enhance error handling in message processing when latest message has existing content blocks * chore: allow import/no-cycle for messages * fix: adjust flex properties in BookmarkMenu for better layout * feat: support both 'prompt' and 'q' as query parameters in useQueryParams hook * feat: re-enable Badges components * refactor: disable edit badge component * chore: rename assistantMap to assistantsMap for consistency * chore: rename assistantMap to assistantsMap for consistency in Mention component * feat: set staleTime for various queries to improve data freshness * feat: add spec field to tQueryParamsSchema for model specification * feat: enhance useQueryParams to handle model specs --------- Co-authored-by: Danny Avila <danny@librechat.ai> |