diff --git a/.husky/pre-commit b/.husky/pre-commit index 67f5b00272..23c736d1de 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,5 +1,2 @@ -#!/usr/bin/env sh -set -e -. "$(dirname -- "$0")/_/husky.sh" [ -n "$CI" ] && exit 0 npx lint-staged --config ./.husky/lint-staged.config.js diff --git a/client/src/components/Chat/Messages/ui/MessageRender.tsx b/client/src/components/Chat/Messages/ui/MessageRender.tsx index ad38f1ee40..f056fccc98 100644 --- a/client/src/components/Chat/Messages/ui/MessageRender.tsx +++ b/client/src/components/Chat/Messages/ui/MessageRender.tsx @@ -97,7 +97,10 @@ const MessageRender = memo( () => showCardRender && !isLatestMessage ? () => { - logger.log(`Message Card click: Setting ${msg?.messageId} as latest message`); + logger.log( + 'latest_message', + `Message Card click: Setting ${msg?.messageId} as latest message`, + ); logger.dir(msg); setLatestMessage(msg!); } diff --git a/client/src/components/Messages/ContentRender.tsx b/client/src/components/Messages/ContentRender.tsx index bdf051453e..ce88687d23 100644 --- a/client/src/components/Messages/ContentRender.tsx +++ b/client/src/components/Messages/ContentRender.tsx @@ -96,7 +96,10 @@ const ContentRender = memo( () => showCardRender && !isLatestMessage ? () => { - logger.log(`Message Card click: Setting ${msg?.messageId} as latest message`); + logger.log( + 'latest_message', + `Message Card click: Setting ${msg?.messageId} as latest message`, + ); logger.dir(msg); setLatestMessage(msg!); } diff --git a/client/src/hooks/Conversations/useNavigateToConvo.tsx b/client/src/hooks/Conversations/useNavigateToConvo.tsx index dd8ce11aa5..55f43fa820 100644 --- a/client/src/hooks/Conversations/useNavigateToConvo.tsx +++ b/client/src/hooks/Conversations/useNavigateToConvo.tsx @@ -51,6 +51,7 @@ const useNavigateToConvo = (index = 0) => { hasSetConversation.current = true; setSubmission(null); if (resetLatestMessage) { + logger.log('latest_message', 'Clearing all latest messages'); clearAllLatestMessages(); } diff --git a/client/src/hooks/Messages/useMessageHelpers.tsx b/client/src/hooks/Messages/useMessageHelpers.tsx index 264fe666d6..8343e97756 100644 --- a/client/src/hooks/Messages/useMessageHelpers.tsx +++ b/client/src/hooks/Messages/useMessageHelpers.tsx @@ -3,8 +3,8 @@ import { useEffect, useRef, useCallback, useMemo } from 'react'; import { Constants, isAssistantsEndpoint, isAgentsEndpoint } from 'librechat-data-provider'; import type { TMessageProps } from '~/common'; import { useMessagesViewContext, useAssistantsMapContext, useAgentsMapContext } from '~/Providers'; +import { getTextKey, TEXT_KEY_DIVIDER, logger } from '~/utils'; import useCopyToClipboard from './useCopyToClipboard'; -import { getTextKey, logger } from '~/utils'; export default function useMessageHelpers(props: TMessageProps) { const latestText = useRef(''); @@ -49,15 +49,27 @@ export default function useMessageHelpers(props: TMessageProps) { messageId: message.messageId, convoId, }; + + /* Extracted convoId from previous textKey (format: messageId|||length|||lastChars|||convoId) */ + let previousConvoId: string | null = null; + if ( + latestText.current && + typeof latestText.current === 'string' && + latestText.current.length > 0 + ) { + const parts = latestText.current.split(TEXT_KEY_DIVIDER); + previousConvoId = parts[parts.length - 1] || null; + } + if ( textKey !== latestText.current || - (latestText.current && convoId !== latestText.current.split(Constants.COMMON_DIVIDER)[2]) + (convoId != null && previousConvoId != null && convoId !== previousConvoId) ) { - logger.log('[useMessageHelpers] Setting latest message: ', logInfo); + logger.log('latest_message', '[useMessageHelpers] Setting latest message: ', logInfo); latestText.current = textKey; setLatestMessage({ ...message }); } else { - logger.log('No change in latest message', logInfo); + logger.log('latest_message', 'No change in latest message', logInfo); } }, [isLast, message, setLatestMessage, conversation?.conversationId]); diff --git a/client/src/hooks/Messages/useMessageProcess.tsx b/client/src/hooks/Messages/useMessageProcess.tsx index ea5779a69f..30bec90d17 100644 --- a/client/src/hooks/Messages/useMessageProcess.tsx +++ b/client/src/hooks/Messages/useMessageProcess.tsx @@ -3,8 +3,8 @@ import { useRecoilValue } from 'recoil'; import { Constants } from 'librechat-data-provider'; import { useEffect, useRef, useCallback, useMemo, useState } from 'react'; import type { TMessage } from 'librechat-data-provider'; +import { getTextKey, TEXT_KEY_DIVIDER, logger } from '~/utils'; import { useMessagesViewContext } from '~/Providers'; -import { getTextKey, logger } from '~/utils'; import store from '~/store'; export default function useMessageProcess({ message }: { message?: TMessage | null }) { @@ -43,11 +43,21 @@ export default function useMessageProcess({ message }: { message?: TMessage | nu messageId: message.messageId, convoId, }; + + /* Extracted convoId from previous textKey (format: messageId|||length|||lastChars|||convoId) */ + let previousConvoId: string | null = null; + if ( + latestText.current && + typeof latestText.current === 'string' && + latestText.current.length > 0 + ) { + const parts = latestText.current.split(TEXT_KEY_DIVIDER); + previousConvoId = parts[parts.length - 1] || null; + } + if ( textKey !== latestText.current || - (convoId != null && - latestText.current && - convoId !== latestText.current.split(Constants.COMMON_DIVIDER)[2]) + (convoId != null && previousConvoId != null && convoId !== previousConvoId) ) { logger.log('latest_message', '[useMessageProcess] Setting latest message; logInfo:', logInfo); latestText.current = textKey; diff --git a/client/src/hooks/SSE/useEventHandlers.ts b/client/src/hooks/SSE/useEventHandlers.ts index 1d860fbb7a..83c1ff1ad9 100644 --- a/client/src/hooks/SSE/useEventHandlers.ts +++ b/client/src/hooks/SSE/useEventHandlers.ts @@ -339,6 +339,7 @@ export default function useEventHandlers({ setShowStopButton(true); if (resetLatestMessage) { + logger.log('latest_message', 'syncHandler: resetting latest message'); resetLatestMessage(); } }, @@ -418,6 +419,7 @@ export default function useEventHandlers({ } if (resetLatestMessage) { + logger.log('latest_message', 'createdHandler: resetting latest message'); resetLatestMessage(); } scrollToEnd(() => setAbortScroll(false)); diff --git a/client/src/hooks/useNewConvo.ts b/client/src/hooks/useNewConvo.ts index ab2d177428..22ea5f327c 100644 --- a/client/src/hooks/useNewConvo.ts +++ b/client/src/hooks/useNewConvo.ts @@ -179,6 +179,7 @@ const useNewConvo = (index = 0) => { } setSubmission({} as TSubmission); if (!(keepLatestMessage ?? false)) { + logger.log('latest_message', 'Clearing all latest messages'); clearAllLatestMessages(); } if (isCancelled) { diff --git a/client/src/utils/messages.ts b/client/src/utils/messages.ts index caae46d923..fe8ec36499 100644 --- a/client/src/utils/messages.ts +++ b/client/src/utils/messages.ts @@ -1,15 +1,7 @@ -import { ContentTypes, Constants } from 'librechat-data-provider'; +import { ContentTypes } from 'librechat-data-provider'; import type { TMessage, TMessageContentParts } from 'librechat-data-provider'; -export const getLengthAndLastTenChars = (str?: string): string => { - if (typeof str !== 'string' || str.length === 0) { - return '0'; - } - - const length = str.length; - const lastTenChars = str.slice(-10); - return `${length}${lastTenChars}`; -}; +export const TEXT_KEY_DIVIDER = '|||'; export const getLatestText = (message?: TMessage | null, includeIndex?: boolean): string => { if (!message) { @@ -65,16 +57,84 @@ export const getAllContentText = (message?: TMessage | null): string => { return ''; }; +const getLatestContentForKey = (message: TMessage): string => { + const formatText = (str: string, index: number): string => { + if (str.length === 0) { + return '0'; + } + const length = str.length; + const lastChars = str.slice(-16); + return `${length}${TEXT_KEY_DIVIDER}${lastChars}${TEXT_KEY_DIVIDER}${index}`; + }; + + if (message.text) { + return formatText(message.text, -1); + } + + if (!message.content || message.content.length === 0) { + return ''; + } + + for (let i = message.content.length - 1; i >= 0; i--) { + const part = message.content[i] as TMessageContentParts | undefined; + if (!part?.type) { + continue; + } + + const type = part.type; + let text = ''; + + // Handle THINK type - extract think content + if (type === ContentTypes.THINK && 'think' in part) { + text = typeof part.think === 'string' ? part.think : (part.think?.value ?? ''); + } + // Handle TEXT type + else if (type === ContentTypes.TEXT && 'text' in part) { + text = typeof part.text === 'string' ? part.text : (part.text?.value ?? ''); + } + // Handle ERROR type + else if (type === ContentTypes.ERROR && 'error' in part) { + text = String(part.error || 'err').slice(0, 30); + } + // Handle TOOL_CALL - use simple marker with type + else if (type === ContentTypes.TOOL_CALL && 'tool_call' in part) { + const tcType = part.tool_call?.type || 'x'; + const tcName = String(part.tool_call?.['name'] || 'unknown').slice(0, 20); + const tcArgs = String(part.tool_call?.['args'] || 'none').slice(0, 20); + const tcOutput = String(part.tool_call?.['output'] || 'none').slice(0, 20); + text = `tc_${tcType}_${tcName}_${tcArgs}_${tcOutput}`; + } + // Handle IMAGE_FILE - use simple marker with file_id suffix + else if (type === ContentTypes.IMAGE_FILE && 'image_file' in part) { + const fileId = part.image_file?.file_id || 'x'; + text = `if_${fileId.slice(-8)}`; + } + // Handle IMAGE_URL - use simple marker + else if (type === ContentTypes.IMAGE_URL) { + text = 'iu'; + } + // Handle AGENT_UPDATE - use simple marker with agentId suffix + else if (type === ContentTypes.AGENT_UPDATE && 'agent_update' in part) { + const agentId = String(part.agent_update?.agentId || 'x').slice(0, 30); + text = `au_${agentId}`; + } else { + text = type; + } + + if (text.length > 0) { + return formatText(text, i); + } + } + + return ''; +}; + export const getTextKey = (message?: TMessage | null, convoId?: string | null) => { if (!message) { return ''; } - const text = getLatestText(message, true); - return `${(message.messageId as string | null) ?? ''}${ - Constants.COMMON_DIVIDER - }${getLengthAndLastTenChars(text)}${Constants.COMMON_DIVIDER}${ - message.conversationId ?? convoId - }`; + const contentKey = getLatestContentForKey(message); + return `${(message.messageId as string | null) ?? ''}${TEXT_KEY_DIVIDER}${contentKey}${TEXT_KEY_DIVIDER}${message.conversationId ?? convoId}`; }; export const scrollToEnd = (callback?: () => void) => {