mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-03-13 19:36:16 +01:00
⚡ 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.
This commit is contained in:
parent
c324a8d9e4
commit
5209f1dc9e
56 changed files with 1578 additions and 1085 deletions
|
|
@ -1,31 +1,29 @@
|
|||
import { useCallback, useState } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
Constants,
|
||||
QueryKeys,
|
||||
dataService,
|
||||
EModelEndpoint,
|
||||
isAssistantsEndpoint,
|
||||
} from 'librechat-data-provider';
|
||||
import type { TConversation, TPreset, Agent } from 'librechat-data-provider';
|
||||
import useGetConversation from '~/hooks/Conversations/useGetConversation';
|
||||
import useDefaultConvo from '~/hooks/Conversations/useDefaultConvo';
|
||||
import { useAgentsMapContext } from '~/Providers/AgentsMapContext';
|
||||
import { useChatContext } from '~/Providers/ChatContext';
|
||||
import { useGetAgentByIdQuery } from '~/data-provider';
|
||||
import useNewConvo from '~/hooks/useNewConvo';
|
||||
import { logger } from '~/utils';
|
||||
|
||||
export default function useSelectAgent() {
|
||||
const queryClient = useQueryClient();
|
||||
const getDefaultConversation = useDefaultConvo();
|
||||
const { conversation, newConversation } = useChatContext();
|
||||
const agentsMap = useAgentsMapContext();
|
||||
const [selectedAgentId, setSelectedAgentId] = useState<string | null>(
|
||||
conversation?.agent_id ?? null,
|
||||
);
|
||||
|
||||
const agentQuery = useGetAgentByIdQuery(selectedAgentId);
|
||||
const getDefaultConversation = useDefaultConvo();
|
||||
const { newConversation } = useNewConvo();
|
||||
const getConversation = useGetConversation(0);
|
||||
|
||||
const updateConversation = useCallback(
|
||||
(agent: Partial<Agent>, template: Partial<TPreset | TConversation>) => {
|
||||
async (agent: Partial<Agent>, template: Partial<TPreset | TConversation>) => {
|
||||
const conversation = await getConversation();
|
||||
logger.log('conversation', 'Updating conversation with agent', agent);
|
||||
if (isAssistantsEndpoint(conversation?.endpoint)) {
|
||||
newConversation({
|
||||
|
|
@ -44,7 +42,7 @@ export default function useSelectAgent() {
|
|||
keepLatestMessage: true,
|
||||
});
|
||||
},
|
||||
[conversation, getDefaultConversation, newConversation],
|
||||
[getConversation, getDefaultConversation, newConversation],
|
||||
);
|
||||
|
||||
const onSelect = useCallback(
|
||||
|
|
@ -54,30 +52,22 @@ export default function useSelectAgent() {
|
|||
return;
|
||||
}
|
||||
|
||||
setSelectedAgentId(agent.id);
|
||||
|
||||
const template: Partial<TPreset | TConversation> = {
|
||||
endpoint: EModelEndpoint.agents,
|
||||
agent_id: agent.id,
|
||||
conversationId: Constants.NEW_CONVO as string,
|
||||
};
|
||||
|
||||
updateConversation({ id: agent.id }, template);
|
||||
await updateConversation({ id: agent.id }, template);
|
||||
|
||||
// Fetch full agent data in the background
|
||||
try {
|
||||
await queryClient.invalidateQueries(
|
||||
{
|
||||
queryKey: [QueryKeys.agent, agent.id],
|
||||
exact: true,
|
||||
refetchType: 'active',
|
||||
},
|
||||
{ throwOnError: true },
|
||||
const fullAgent = await queryClient.fetchQuery([QueryKeys.agent, agent.id], () =>
|
||||
dataService.getAgentById({
|
||||
agent_id: agent.id,
|
||||
}),
|
||||
);
|
||||
|
||||
const { data: fullAgent } = await agentQuery.refetch();
|
||||
if (fullAgent) {
|
||||
updateConversation(fullAgent, { ...template, agent_id: fullAgent.id });
|
||||
await updateConversation(fullAgent, { ...template, agent_id: fullAgent.id });
|
||||
}
|
||||
} catch (error) {
|
||||
if ((error as { silent: boolean } | undefined)?.silent) {
|
||||
|
|
@ -85,10 +75,10 @@ export default function useSelectAgent() {
|
|||
return;
|
||||
}
|
||||
console.error('Error fetching full agent data:', error);
|
||||
updateConversation({}, { ...template, agent_id: undefined });
|
||||
await updateConversation({}, { ...template, agent_id: undefined });
|
||||
}
|
||||
},
|
||||
[agentsMap, updateConversation, queryClient, agentQuery],
|
||||
[agentsMap, updateConversation, queryClient],
|
||||
);
|
||||
|
||||
return { onSelect };
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import { useCallback, useState } from 'react';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { QueryKeys, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useRecoilState, useResetRecoilState, useSetRecoilState } from 'recoil';
|
||||
import type { TMessage } from 'librechat-data-provider';
|
||||
import type { ActiveJobsResponse } from '~/data-provider';
|
||||
import { useGetMessagesByConvoId, useAbortStreamMutation } from '~/data-provider';
|
||||
import useChatFunctions from '~/hooks/Chat/useChatFunctions';
|
||||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
import { useAbortStreamMutation } from '~/data-provider';
|
||||
import useNewConvo from '~/hooks/useNewConvo';
|
||||
import store from '~/store';
|
||||
|
||||
|
|
@ -17,7 +16,6 @@ export default function useChatHelpers(index = 0, paramId?: string) {
|
|||
const [filesLoading, setFilesLoading] = useState(false);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const { isAuthenticated } = useAuthContext();
|
||||
const abortMutation = useAbortStreamMutation();
|
||||
|
||||
const { newConversation } = useNewConvo(index);
|
||||
|
|
@ -29,15 +27,15 @@ export default function useChatHelpers(index = 0, paramId?: string) {
|
|||
Falling back to conversationId (Recoil) only if paramId is not available */
|
||||
const queryParam = paramId === 'new' ? paramId : (paramId ?? conversationId ?? '');
|
||||
|
||||
/* Messages: here simply to fetch, don't export and use `getMessages()` instead */
|
||||
|
||||
const { data: _messages } = useGetMessagesByConvoId(queryParam, {
|
||||
enabled: isAuthenticated,
|
||||
});
|
||||
|
||||
const resetLatestMessage = useResetRecoilState(store.latestMessageFamily(index));
|
||||
const [isSubmitting, setIsSubmitting] = useRecoilState(store.isSubmittingFamily(index));
|
||||
const [latestMessage, setLatestMessage] = useRecoilState(store.latestMessageFamily(index));
|
||||
|
||||
const latestMessageId = latestMessage?.messageId;
|
||||
const latestMessageDepth = latestMessage?.depth;
|
||||
const latestMessageRef = useRef(latestMessage);
|
||||
latestMessageRef.current = latestMessage;
|
||||
|
||||
const setSiblingIdx = useSetRecoilState(
|
||||
store.messagesSiblingIdxFamily(latestMessage?.parentMessageId ?? null),
|
||||
);
|
||||
|
|
@ -77,7 +75,7 @@ export default function useChatHelpers(index = 0, paramId?: string) {
|
|||
|
||||
const setSubmission = useSetRecoilState(store.submissionByIndex(index));
|
||||
|
||||
const { ask, regenerate } = useChatFunctions({
|
||||
const { ask: _ask, regenerate: _regenerate } = useChatFunctions({
|
||||
index,
|
||||
files,
|
||||
setFiles,
|
||||
|
|
@ -90,8 +88,20 @@ export default function useChatHelpers(index = 0, paramId?: string) {
|
|||
setLatestMessage,
|
||||
});
|
||||
|
||||
const continueGeneration = () => {
|
||||
if (!latestMessage) {
|
||||
const askRef = useRef(_ask);
|
||||
askRef.current = _ask;
|
||||
const ask: typeof _ask = useCallback((...args) => askRef.current(...args), []);
|
||||
|
||||
const regenerateRef = useRef(_regenerate);
|
||||
regenerateRef.current = _regenerate;
|
||||
const regenerate: typeof _regenerate = useCallback(
|
||||
(...args) => regenerateRef.current(...args),
|
||||
[],
|
||||
);
|
||||
|
||||
const continueGeneration = useCallback(() => {
|
||||
const currentLatest = latestMessageRef.current;
|
||||
if (!currentLatest) {
|
||||
console.error('Failed to regenerate the message: latestMessage not found.');
|
||||
return;
|
||||
}
|
||||
|
|
@ -99,7 +109,7 @@ export default function useChatHelpers(index = 0, paramId?: string) {
|
|||
const messages = getMessages();
|
||||
|
||||
const parentMessage = messages?.find(
|
||||
(element) => element.messageId == latestMessage.parentMessageId,
|
||||
(element) => element.messageId == currentLatest.parentMessageId,
|
||||
);
|
||||
|
||||
if (parentMessage && parentMessage.isCreatedByUser) {
|
||||
|
|
@ -109,7 +119,7 @@ export default function useChatHelpers(index = 0, paramId?: string) {
|
|||
'Failed to regenerate the message: parentMessage not found, or not created by user.',
|
||||
);
|
||||
}
|
||||
};
|
||||
}, [getMessages, ask]);
|
||||
|
||||
/**
|
||||
* Stop generation - for non-assistants endpoints, calls abort endpoint first.
|
||||
|
|
@ -153,64 +163,107 @@ export default function useChatHelpers(index = 0, paramId?: string) {
|
|||
}
|
||||
}, [conversationId, endpoint, endpointType, abortMutation, clearAllSubmissions, queryClient]);
|
||||
|
||||
const handleStopGenerating = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
stopGenerating();
|
||||
};
|
||||
const handleStopGenerating = useCallback(
|
||||
(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
stopGenerating();
|
||||
},
|
||||
[stopGenerating],
|
||||
);
|
||||
|
||||
const handleRegenerate = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
const parentMessageId = latestMessage?.parentMessageId ?? '';
|
||||
if (!parentMessageId) {
|
||||
console.error('Failed to regenerate the message: parentMessageId not found.');
|
||||
return;
|
||||
}
|
||||
regenerate({ parentMessageId });
|
||||
};
|
||||
const handleRegenerate = useCallback(
|
||||
(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
const parentMessageId = latestMessageRef.current?.parentMessageId ?? '';
|
||||
if (!parentMessageId) {
|
||||
console.error('Failed to regenerate the message: parentMessageId not found.');
|
||||
return;
|
||||
}
|
||||
regenerate({ parentMessageId });
|
||||
},
|
||||
[regenerate],
|
||||
);
|
||||
|
||||
const handleContinue = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
continueGeneration();
|
||||
setSiblingIdx(0);
|
||||
};
|
||||
const handleContinue = useCallback(
|
||||
(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
continueGeneration();
|
||||
setSiblingIdx(0);
|
||||
},
|
||||
[continueGeneration, setSiblingIdx],
|
||||
);
|
||||
|
||||
const [preset, setPreset] = useRecoilState(store.presetByIndex(index));
|
||||
const [showPopover, setShowPopover] = useRecoilState(store.showPopoverFamily(index));
|
||||
const [abortScroll, setAbortScroll] = useRecoilState(store.abortScrollFamily(index));
|
||||
const [optionSettings, setOptionSettings] = useRecoilState(store.optionSettingsFamily(index));
|
||||
|
||||
return {
|
||||
newConversation,
|
||||
conversation,
|
||||
setConversation,
|
||||
// getConvos,
|
||||
// setConvos,
|
||||
isSubmitting,
|
||||
setIsSubmitting,
|
||||
getMessages,
|
||||
setMessages,
|
||||
setSiblingIdx,
|
||||
latestMessage,
|
||||
setLatestMessage,
|
||||
resetLatestMessage,
|
||||
ask,
|
||||
index,
|
||||
regenerate,
|
||||
stopGenerating,
|
||||
handleStopGenerating,
|
||||
handleRegenerate,
|
||||
handleContinue,
|
||||
showPopover,
|
||||
setShowPopover,
|
||||
abortScroll,
|
||||
setAbortScroll,
|
||||
preset,
|
||||
setPreset,
|
||||
optionSettings,
|
||||
setOptionSettings,
|
||||
files,
|
||||
setFiles,
|
||||
filesLoading,
|
||||
setFilesLoading,
|
||||
};
|
||||
return useMemo(
|
||||
() => ({
|
||||
newConversation,
|
||||
conversation,
|
||||
setConversation,
|
||||
isSubmitting,
|
||||
setIsSubmitting,
|
||||
getMessages,
|
||||
setMessages,
|
||||
setSiblingIdx,
|
||||
latestMessageId,
|
||||
latestMessageDepth,
|
||||
setLatestMessage,
|
||||
resetLatestMessage,
|
||||
ask,
|
||||
index,
|
||||
regenerate,
|
||||
stopGenerating,
|
||||
handleStopGenerating,
|
||||
handleRegenerate,
|
||||
handleContinue,
|
||||
showPopover,
|
||||
setShowPopover,
|
||||
abortScroll,
|
||||
setAbortScroll,
|
||||
preset,
|
||||
setPreset,
|
||||
optionSettings,
|
||||
setOptionSettings,
|
||||
files,
|
||||
setFiles,
|
||||
filesLoading,
|
||||
setFilesLoading,
|
||||
}),
|
||||
[
|
||||
newConversation,
|
||||
conversation,
|
||||
setConversation,
|
||||
isSubmitting,
|
||||
setIsSubmitting,
|
||||
getMessages,
|
||||
setMessages,
|
||||
setSiblingIdx,
|
||||
latestMessageId,
|
||||
latestMessageDepth,
|
||||
setLatestMessage,
|
||||
resetLatestMessage,
|
||||
ask,
|
||||
index,
|
||||
regenerate,
|
||||
stopGenerating,
|
||||
handleStopGenerating,
|
||||
handleRegenerate,
|
||||
handleContinue,
|
||||
showPopover,
|
||||
setShowPopover,
|
||||
abortScroll,
|
||||
setAbortScroll,
|
||||
preset,
|
||||
setPreset,
|
||||
optionSettings,
|
||||
setOptionSettings,
|
||||
files,
|
||||
setFiles,
|
||||
filesLoading,
|
||||
setFilesLoading,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ export { default as useDefaultConvo } from './useDefaultConvo';
|
|||
export { default as useSearchEnabled } from './useSearchEnabled';
|
||||
export { default as useGenerateConvo } from './useGenerateConvo';
|
||||
export { default as useDebouncedInput } from './useDebouncedInput';
|
||||
export { default as useGetConversation } from './useGetConversation';
|
||||
export { default as useBookmarkSuccess } from './useBookmarkSuccess';
|
||||
export { default as useNavigateToConvo } from './useNavigateToConvo';
|
||||
export { default as useSetIndexOptions } from './useSetIndexOptions';
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useGetModelsQuery } from 'librechat-data-provider/react-query';
|
||||
import { excludedKeys, getDefaultParamsEndpoint } from 'librechat-data-provider';
|
||||
import type {
|
||||
|
|
@ -22,57 +23,55 @@ const useDefaultConvo = () => {
|
|||
const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery();
|
||||
const { data: modelsConfig = {} as TModelsConfig } = useGetModelsQuery();
|
||||
|
||||
const getDefaultConversation = ({
|
||||
conversation: _convo,
|
||||
preset,
|
||||
cleanInput,
|
||||
cleanOutput,
|
||||
}: TDefaultConvo) => {
|
||||
const endpoint = getDefaultEndpoint({
|
||||
convoSetup: preset as TPreset,
|
||||
endpointsConfig,
|
||||
});
|
||||
const getDefaultConversation = useCallback(
|
||||
({ conversation: _convo, preset, cleanInput, cleanOutput }: TDefaultConvo) => {
|
||||
const endpoint = getDefaultEndpoint({
|
||||
convoSetup: preset as TPreset,
|
||||
endpointsConfig,
|
||||
});
|
||||
|
||||
const models = modelsConfig[endpoint ?? ''] || [];
|
||||
const conversation = { ..._convo };
|
||||
if (cleanInput === true) {
|
||||
for (const key in conversation) {
|
||||
const models = modelsConfig[endpoint ?? ''] || [];
|
||||
const conversation = { ..._convo };
|
||||
if (cleanInput === true) {
|
||||
for (const key in conversation) {
|
||||
if (excludedKeys.has(key) && !exceptions.has(key)) {
|
||||
continue;
|
||||
}
|
||||
if (conversation[key] == null) {
|
||||
continue;
|
||||
}
|
||||
conversation[key] = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultParamsEndpoint = getDefaultParamsEndpoint(endpointsConfig, endpoint);
|
||||
|
||||
const defaultConvo = buildDefaultConvo({
|
||||
conversation: conversation as TConversation,
|
||||
endpoint,
|
||||
lastConversationSetup: preset as TConversation,
|
||||
models,
|
||||
defaultParamsEndpoint,
|
||||
});
|
||||
|
||||
if (!cleanOutput) {
|
||||
return defaultConvo;
|
||||
}
|
||||
|
||||
for (const key in defaultConvo) {
|
||||
if (excludedKeys.has(key) && !exceptions.has(key)) {
|
||||
continue;
|
||||
}
|
||||
if (conversation[key] == null) {
|
||||
if (defaultConvo[key] == null) {
|
||||
continue;
|
||||
}
|
||||
conversation[key] = undefined;
|
||||
defaultConvo[key] = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultParamsEndpoint = getDefaultParamsEndpoint(endpointsConfig, endpoint);
|
||||
|
||||
const defaultConvo = buildDefaultConvo({
|
||||
conversation: conversation as TConversation,
|
||||
endpoint,
|
||||
lastConversationSetup: preset as TConversation,
|
||||
models,
|
||||
defaultParamsEndpoint,
|
||||
});
|
||||
|
||||
if (!cleanOutput) {
|
||||
return defaultConvo;
|
||||
}
|
||||
|
||||
for (const key in defaultConvo) {
|
||||
if (excludedKeys.has(key) && !exceptions.has(key)) {
|
||||
continue;
|
||||
}
|
||||
if (defaultConvo[key] == null) {
|
||||
continue;
|
||||
}
|
||||
defaultConvo[key] = undefined;
|
||||
}
|
||||
|
||||
return defaultConvo;
|
||||
};
|
||||
},
|
||||
[endpointsConfig, modelsConfig],
|
||||
);
|
||||
|
||||
return getDefaultConversation;
|
||||
};
|
||||
|
|
|
|||
14
client/src/hooks/Conversations/useGetConversation.ts
Normal file
14
client/src/hooks/Conversations/useGetConversation.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { useRecoilCallback } from 'recoil';
|
||||
import type { TConversation } from 'librechat-data-provider';
|
||||
import store from '~/store';
|
||||
|
||||
export default function useGetConversation(index: string | number = 0) {
|
||||
return useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
() =>
|
||||
snapshot
|
||||
.getLoadable(store.conversationByKeySelector(index))
|
||||
.getValue() as TConversation | null,
|
||||
[index],
|
||||
);
|
||||
}
|
||||
|
|
@ -13,19 +13,20 @@ import {
|
|||
useGetPresetsQuery,
|
||||
} from '~/data-provider';
|
||||
import { cleanupPreset, removeUnavailableTools, getConvoSwitchLogic } from '~/utils';
|
||||
import useGetConversation from '~/hooks/Conversations/useGetConversation';
|
||||
import useDefaultConvo from '~/hooks/Conversations/useDefaultConvo';
|
||||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import useNewConvo from '~/hooks/useNewConvo';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import store from '~/store';
|
||||
|
||||
export default function usePresets() {
|
||||
export default function usePresets(index = 0) {
|
||||
const localize = useLocalize();
|
||||
const hasLoaded = useRef(false);
|
||||
const queryClient = useQueryClient();
|
||||
const { showToast } = useToastContext();
|
||||
const getConversation = useGetConversation(index);
|
||||
const { user, isAuthenticated } = useAuthContext();
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
const [presetToDelete, setPresetToDelete] = useState<TPreset | null>(null);
|
||||
|
|
@ -35,7 +36,9 @@ export default function usePresets() {
|
|||
const setPresetModalVisible = useSetRecoilState(store.presetModalVisible);
|
||||
const [_defaultPreset, setDefaultPreset] = useRecoilState(store.defaultPreset);
|
||||
const presetsQuery = useGetPresetsQuery({ enabled: !!user && isAuthenticated });
|
||||
const { preset, conversation, index, setPreset } = useChatContext();
|
||||
const preset = useRecoilValue(store.presetByIndex(index));
|
||||
const setPreset = useSetRecoilState(store.presetByIndex(index));
|
||||
const conversationId = useRecoilValue(store.conversationIdByIndex(index));
|
||||
const { data: modelsData } = useGetModelsQuery();
|
||||
const { newConversation } = useNewConvo(index);
|
||||
|
||||
|
|
@ -60,13 +63,13 @@ export default function usePresets() {
|
|||
return;
|
||||
}
|
||||
setDefaultPreset(defaultPreset);
|
||||
if (!conversation?.conversationId || conversation.conversationId === 'new') {
|
||||
if (!conversationId || conversationId === 'new') {
|
||||
newConversation({ preset: defaultPreset, modelsData, disableParams: true });
|
||||
}
|
||||
hasLoaded.current = true;
|
||||
// dependencies are stable and only needed once
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [presetsQuery.data, user, modelsData]);
|
||||
}, [presetsQuery.data, user, modelsData, conversationId]);
|
||||
|
||||
const setPresets = useCallback(
|
||||
(presets: TPreset[]) => {
|
||||
|
|
@ -164,6 +167,7 @@ export default function usePresets() {
|
|||
return;
|
||||
}
|
||||
|
||||
const conversation = getConversation();
|
||||
const newPreset = removeUnavailableTools(_newPreset, availableTools);
|
||||
|
||||
const toastTitle = newPreset.title
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useCallback } from 'react';
|
||||
import { useState, useCallback, useMemo } from 'react';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
|
||||
export const useKeyDialog = () => {
|
||||
|
|
@ -15,24 +15,30 @@ export const useKeyDialog = () => {
|
|||
[],
|
||||
);
|
||||
|
||||
const onOpenChange = (open: boolean) => {
|
||||
if (!open && keyDialogEndpoint) {
|
||||
const button = document.getElementById(`endpoint-${keyDialogEndpoint}-settings`);
|
||||
if (button) {
|
||||
setTimeout(() => {
|
||||
button.focus();
|
||||
}, 5);
|
||||
const onOpenChange = useCallback(
|
||||
(open: boolean) => {
|
||||
if (!open && keyDialogEndpoint) {
|
||||
const button = document.getElementById(`endpoint-${keyDialogEndpoint}-settings`);
|
||||
if (button) {
|
||||
setTimeout(() => {
|
||||
button.focus();
|
||||
}, 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
setKeyDialogOpen(open);
|
||||
};
|
||||
setKeyDialogOpen(open);
|
||||
},
|
||||
[keyDialogEndpoint],
|
||||
);
|
||||
|
||||
return {
|
||||
keyDialogOpen,
|
||||
keyDialogEndpoint,
|
||||
onOpenChange,
|
||||
handleOpenKeyDialog,
|
||||
};
|
||||
return useMemo(
|
||||
() => ({
|
||||
keyDialogOpen,
|
||||
keyDialogEndpoint,
|
||||
onOpenChange,
|
||||
handleOpenKeyDialog,
|
||||
}),
|
||||
[keyDialogOpen, keyDialogEndpoint, onOpenChange, handleOpenKeyDialog],
|
||||
);
|
||||
};
|
||||
|
||||
export default useKeyDialog;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
import debounce from 'lodash/debounce';
|
||||
import type { EModelEndpoint, TEndpointsConfig, TError } from 'librechat-data-provider';
|
||||
import type { ExtendedFile, FileSetter } from '~/common';
|
||||
import type { TConversation } from 'librechat-data-provider';
|
||||
import { useGetFileConfig, useUploadFileMutation } from '~/data-provider';
|
||||
import useLocalize, { TranslationKeys } from '~/hooks/useLocalize';
|
||||
import { useDelayedUploadToast } from './useDelayedUploadToast';
|
||||
|
|
@ -33,14 +34,24 @@ type UseFileHandling = {
|
|||
endpointOverride?: EModelEndpoint;
|
||||
};
|
||||
|
||||
const useFileHandling = (params?: UseFileHandling) => {
|
||||
export type FileHandlingState = {
|
||||
files: Map<string, ExtendedFile>;
|
||||
setFiles: FileSetter;
|
||||
setFilesLoading?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
conversation?: TConversation | null;
|
||||
};
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
const useFileHandlingCore = (params: UseFileHandling | undefined, fileState: FileHandlingState) => {
|
||||
const localize = useLocalize();
|
||||
const queryClient = useQueryClient();
|
||||
const { showToast } = useToastContext();
|
||||
const [errors, setErrors] = useState<string[]>([]);
|
||||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
const { startUploadTimer, clearUploadTimer } = useDelayedUploadToast();
|
||||
const { files, setFiles, setFilesLoading, conversation } = useChatContext();
|
||||
const { files, setFiles, conversation } = fileState;
|
||||
const setFilesLoading = fileState.setFilesLoading ?? noop;
|
||||
const setEphemeralAgent = useSetRecoilState(
|
||||
ephemeralAgentByConvoId(conversation?.conversationId ?? Constants.NEW_CONVO),
|
||||
);
|
||||
|
|
@ -443,4 +454,20 @@ const useFileHandling = (params?: UseFileHandling) => {
|
|||
};
|
||||
};
|
||||
|
||||
export const useFileHandlingNoChatContext = (
|
||||
params: UseFileHandling | undefined,
|
||||
fileState: FileHandlingState,
|
||||
) => useFileHandlingCore(params, fileState);
|
||||
|
||||
const useFileHandling = (params?: UseFileHandling) => {
|
||||
const { files, setFiles, setFilesLoading, conversation } = useChatContext();
|
||||
|
||||
return useFileHandlingCore(params, {
|
||||
files,
|
||||
setFiles,
|
||||
conversation,
|
||||
setFilesLoading,
|
||||
});
|
||||
};
|
||||
|
||||
export default useFileHandling;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { useCallback } from 'react';
|
||||
import useFileHandling from './useFileHandling';
|
||||
import useSharePointDownload from './useSharePointDownload';
|
||||
import type { EModelEndpoint } from 'librechat-data-provider';
|
||||
import type { SharePointFile } from '~/data-provider/Files/sharepoint';
|
||||
import type { FileHandlingState } from './useFileHandling';
|
||||
import useFileHandling, { useFileHandlingNoChatContext } from './useFileHandling';
|
||||
import useSharePointDownload from './useSharePointDownload';
|
||||
|
||||
interface UseSharePointFileHandlingProps {
|
||||
fileSetter?: any;
|
||||
|
|
@ -23,6 +24,43 @@ export default function useSharePointFileHandling(
|
|||
props?: UseSharePointFileHandlingProps,
|
||||
): UseSharePointFileHandlingReturn {
|
||||
const { handleFiles } = useFileHandling(props);
|
||||
const { downloadSharePointFiles, isDownloading, downloadProgress, error } = useSharePointDownload(
|
||||
{
|
||||
onFilesDownloaded: async (downloadedFiles: File[]) => {
|
||||
const fileArray = Array.from(downloadedFiles);
|
||||
await handleFiles(fileArray, props?.toolResource);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('SharePoint download failed:', error);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const handleSharePointFiles = useCallback(
|
||||
async (sharePointFiles: SharePointFile[]) => {
|
||||
try {
|
||||
await downloadSharePointFiles(sharePointFiles);
|
||||
} catch (error) {
|
||||
console.error('SharePoint file handling error:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[downloadSharePointFiles],
|
||||
);
|
||||
|
||||
return {
|
||||
handleSharePointFiles,
|
||||
isProcessing: isDownloading,
|
||||
downloadProgress,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useSharePointFileHandlingNoChatContext(
|
||||
props: UseSharePointFileHandlingProps | undefined,
|
||||
fileState: FileHandlingState,
|
||||
): UseSharePointFileHandlingReturn {
|
||||
const { handleFiles } = useFileHandlingNoChatContext(props, fileState);
|
||||
|
||||
const { downloadSharePointFiles, isDownloading, downloadProgress, error } = useSharePointDownload(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -22,19 +22,19 @@ import store from '~/store';
|
|||
export default function useSelectMention({
|
||||
presets,
|
||||
modelSpecs,
|
||||
conversation,
|
||||
assistantsMap,
|
||||
returnHandlers,
|
||||
endpointsConfig,
|
||||
getConversation,
|
||||
newConversation,
|
||||
}: {
|
||||
conversation: TConversation | null;
|
||||
presets?: TPreset[];
|
||||
modelSpecs: TModelSpec[];
|
||||
returnHandlers?: boolean;
|
||||
assistantsMap?: TAssistantsMap;
|
||||
newConversation: ConvoGenerator;
|
||||
endpointsConfig: TEndpointsConfig;
|
||||
returnHandlers?: boolean;
|
||||
getConversation: () => TConversation | null;
|
||||
}) {
|
||||
const getDefaultConversation = useDefaultConvo();
|
||||
const modularChat = useRecoilValue(store.modularChat);
|
||||
|
|
@ -45,6 +45,8 @@ export default function useSelectMention({
|
|||
if (!spec) {
|
||||
return;
|
||||
}
|
||||
|
||||
const conversation = getConversation();
|
||||
const { preset } = spec;
|
||||
preset.iconURL = getModelSpecIconURL(spec);
|
||||
preset.spec = spec.name;
|
||||
|
|
@ -110,7 +112,7 @@ export default function useSelectMention({
|
|||
});
|
||||
},
|
||||
[
|
||||
conversation,
|
||||
getConversation,
|
||||
getDefaultConversation,
|
||||
modularChat,
|
||||
newConversation,
|
||||
|
|
@ -133,6 +135,8 @@ export default function useSelectMention({
|
|||
return;
|
||||
}
|
||||
|
||||
const conversation = getConversation();
|
||||
|
||||
const {
|
||||
shouldSwitch,
|
||||
isNewModular,
|
||||
|
|
@ -202,7 +206,7 @@ export default function useSelectMention({
|
|||
keepAddedConvos: isNewModular,
|
||||
});
|
||||
},
|
||||
[conversation, getDefaultConversation, modularChat, newConversation, endpointsConfig],
|
||||
[getConversation, getDefaultConversation, modularChat, newConversation, endpointsConfig],
|
||||
);
|
||||
|
||||
const onSelectPreset = useCallback(
|
||||
|
|
@ -211,6 +215,8 @@ export default function useSelectMention({
|
|||
return;
|
||||
}
|
||||
|
||||
const conversation = getConversation();
|
||||
|
||||
const newPreset = removeUnavailableTools(_newPreset, availableTools);
|
||||
const newEndpoint = newPreset.endpoint ?? '';
|
||||
|
||||
|
|
@ -266,7 +272,7 @@ export default function useSelectMention({
|
|||
},
|
||||
[
|
||||
modularChat,
|
||||
conversation,
|
||||
getConversation,
|
||||
availableTools,
|
||||
newConversation,
|
||||
endpointsConfig,
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ export default function useTextarea({
|
|||
const checkHealth = useInteractionHealthCheck();
|
||||
const enterToSend = useRecoilValue(store.enterToSend);
|
||||
|
||||
const { index, conversation, isSubmitting, filesLoading, latestMessage, setFilesLoading } =
|
||||
useChatContext();
|
||||
const { index, conversation, isSubmitting, filesLoading, setFilesLoading } = useChatContext();
|
||||
const latestMessage = useRecoilValue(store.latestMessageFamily(index));
|
||||
const [activePrompt, setActivePrompt] = useRecoilState(store.activePromptByIndex(index));
|
||||
|
||||
const { endpoint = '' } = conversation || {};
|
||||
|
|
|
|||
|
|
@ -31,8 +31,16 @@ export default function useMessageActions(props: TMessageActions) {
|
|||
const UsernameDisplay = useRecoilValue<boolean>(store.UsernameDisplay);
|
||||
const { message, currentEditId, setCurrentEditId, searchResults } = props;
|
||||
|
||||
const { ask, index, regenerate, isSubmitting, conversation, latestMessage, handleContinue } =
|
||||
useChatContext();
|
||||
const {
|
||||
ask,
|
||||
index,
|
||||
regenerate,
|
||||
isSubmitting,
|
||||
conversation,
|
||||
latestMessageId,
|
||||
latestMessageDepth,
|
||||
handleContinue,
|
||||
} = useChatContext();
|
||||
|
||||
const getAddedConvo = useGetAddedConvo();
|
||||
|
||||
|
|
@ -154,10 +162,11 @@ export default function useMessageActions(props: TMessageActions) {
|
|||
enterEdit,
|
||||
conversation,
|
||||
messageLabel,
|
||||
latestMessage,
|
||||
handleFeedback,
|
||||
handleContinue,
|
||||
copyToClipboard,
|
||||
latestMessageId,
|
||||
regenerateMessage,
|
||||
latestMessageDepth,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ export default function useMessageHelpers(props: TMessageProps) {
|
|||
regenerate,
|
||||
isSubmitting,
|
||||
conversation,
|
||||
latestMessage,
|
||||
setAbortScroll,
|
||||
handleContinue,
|
||||
latestMessageId,
|
||||
setLatestMessage,
|
||||
} = useMessagesViewContext();
|
||||
const agentsMap = useAgentsMapContext();
|
||||
|
|
@ -141,8 +141,8 @@ export default function useMessageHelpers(props: TMessageProps) {
|
|||
conversation,
|
||||
isSubmitting,
|
||||
handleScroll,
|
||||
latestMessage,
|
||||
handleContinue,
|
||||
latestMessageId,
|
||||
copyToClipboard,
|
||||
regenerateMessage,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ export default function useSubmitMessage() {
|
|||
const { user } = useAuthContext();
|
||||
const methods = useChatFormContext();
|
||||
const { conversation: addedConvo } = useAddedChatContext();
|
||||
const { ask, index, getMessages, setMessages, latestMessage } = useChatContext();
|
||||
const { ask, index, getMessages, setMessages } = useChatContext();
|
||||
const latestMessage = useRecoilValue(store.latestMessageFamily(index));
|
||||
|
||||
const autoSendPrompts = useRecoilValue(store.autoSendPrompts);
|
||||
const setActivePrompt = useSetRecoilState(store.activePromptByIndex(index));
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { TOptions } from 'i18next';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
|
@ -17,5 +17,8 @@ export default function useLocalize() {
|
|||
}
|
||||
}, [lang, i18n]);
|
||||
|
||||
return (phraseKey: TranslationKeys, options?: TOptions) => t(phraseKey, options);
|
||||
return useCallback(
|
||||
(phraseKey: TranslationKeys, options?: TOptions) => t(phraseKey, options),
|
||||
[t],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ const useNewConvo = (index = 0) => {
|
|||
const applyModelSpecEffects = useApplyModelSpecEffects();
|
||||
const clearAllConversations = store.useClearConvoState();
|
||||
const defaultPreset = useRecoilValue(store.defaultPreset);
|
||||
const { setConversation } = store.useCreateConversationAtom(index);
|
||||
const { setConversation } = store.useSetConversationAtom(index);
|
||||
const [files, setFiles] = useRecoilState(store.filesByIndex(index));
|
||||
const saveBadgesState = useRecoilValue<boolean>(store.saveBadgesState);
|
||||
const clearAllLatestMessages = store.useClearLatestMessages(`useNewConvo ${index}`);
|
||||
|
|
|
|||
67
client/src/hooks/useRenderChangeLog.ts
Normal file
67
client/src/hooks/useRenderChangeLog.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { useEffect, useRef } from 'react';
|
||||
|
||||
type DebugWindow = Window & {
|
||||
__LC_RENDER_DEBUG__?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Development-only hook that logs which tracked values changed between renders.
|
||||
*
|
||||
* Enable by setting `window.__LC_RENDER_DEBUG__ = true` in the browser console.
|
||||
* Automatically no-ops in production builds.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* useRenderChangeLog('MessageRender', { messageId, isLast, depth });
|
||||
* ```
|
||||
*/
|
||||
export default function useRenderChangeLog(
|
||||
name: string,
|
||||
values: Record<string, string | number | boolean | null | undefined>,
|
||||
) {
|
||||
const previousValuesRef = useRef<Record<
|
||||
string,
|
||||
string | number | boolean | null | undefined
|
||||
> | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof window === 'undefined' || !(window as DebugWindow).__LC_RENDER_DEBUG__) {
|
||||
previousValuesRef.current = values;
|
||||
return;
|
||||
}
|
||||
|
||||
if (previousValuesRef.current == null) {
|
||||
console.log(`[render-debug] ${name}: initial render`, values);
|
||||
previousValuesRef.current = values;
|
||||
return;
|
||||
}
|
||||
|
||||
const previousValues = previousValuesRef.current;
|
||||
const changedEntries = Object.entries(values).filter(
|
||||
([key, value]) => !Object.is(previousValues[key], value),
|
||||
);
|
||||
|
||||
if (changedEntries.length > 0) {
|
||||
console.log(
|
||||
`[render-debug] ${name}`,
|
||||
Object.fromEntries(
|
||||
changedEntries.map(([key, value]) => [
|
||||
key,
|
||||
{
|
||||
previous: previousValues[key],
|
||||
next: value,
|
||||
},
|
||||
]),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
console.log(`[render-debug] ${name}: parent-driven render`);
|
||||
}
|
||||
|
||||
previousValuesRef.current = values;
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue