mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 08:20:14 +01:00
⚡ refactor: Latest Message Tracking with Robust Text Key Generation (#10059)
* chore: enhance logging for latest message actions in message components
* fix: Extract previous convoId from latest text in message helpers and process hooks
- Updated `useMessageHelpers` and `useMessageProcess` to extract `convoId` from the previous text key for improved message handling.
- Refactored `getLengthAndLastTenChars` to `getLengthAndLastNChars` for better flexibility in character length retrieval.
- Introduced `getLatestContentForKey` function to streamline content extraction from messages.
* chore: Enhance logging for clearing latest messages in conversation hooks
* refactor: Update message key formatting for improved URL parameter handling
- Modified `getLatestContentForKey` to change the format from `${text}-${i}` to `${text}&i=${i}` for better URL parameter structure.
- Adjusted `getTextKey` to increase character length retrieval from 12 to 16 in `getLengthAndLastNChars` for enhanced text processing.
* refactor: Simplify convoId extraction and enhance message formatting
- Updated `useMessageHelpers` and `useMessageProcess` to extract `convoId` using a new format for improved clarity.
- Refactored `getLatestContentForKey` to streamline content formatting and ensure consistent use of `Constants.COMMON_DIVIDER` for better message structure.
- Removed redundant length and last character extraction logic from `getLengthAndLastNChars` for cleaner code.
* chore: linting
* chore: Simplify pre-commit hook by removing unnecessary lines
This commit is contained in:
parent
20282f32c8
commit
fbe341a171
9 changed files with 118 additions and 29 deletions
|
|
@ -1,5 +1,2 @@
|
||||||
#!/usr/bin/env sh
|
|
||||||
set -e
|
|
||||||
. "$(dirname -- "$0")/_/husky.sh"
|
|
||||||
[ -n "$CI" ] && exit 0
|
[ -n "$CI" ] && exit 0
|
||||||
npx lint-staged --config ./.husky/lint-staged.config.js
|
npx lint-staged --config ./.husky/lint-staged.config.js
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,10 @@ const MessageRender = memo(
|
||||||
() =>
|
() =>
|
||||||
showCardRender && !isLatestMessage
|
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);
|
logger.dir(msg);
|
||||||
setLatestMessage(msg!);
|
setLatestMessage(msg!);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,10 @@ const ContentRender = memo(
|
||||||
() =>
|
() =>
|
||||||
showCardRender && !isLatestMessage
|
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);
|
logger.dir(msg);
|
||||||
setLatestMessage(msg!);
|
setLatestMessage(msg!);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ const useNavigateToConvo = (index = 0) => {
|
||||||
hasSetConversation.current = true;
|
hasSetConversation.current = true;
|
||||||
setSubmission(null);
|
setSubmission(null);
|
||||||
if (resetLatestMessage) {
|
if (resetLatestMessage) {
|
||||||
|
logger.log('latest_message', 'Clearing all latest messages');
|
||||||
clearAllLatestMessages();
|
clearAllLatestMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import { useEffect, useRef, useCallback, useMemo } from 'react';
|
||||||
import { Constants, isAssistantsEndpoint, isAgentsEndpoint } from 'librechat-data-provider';
|
import { Constants, isAssistantsEndpoint, isAgentsEndpoint } from 'librechat-data-provider';
|
||||||
import type { TMessageProps } from '~/common';
|
import type { TMessageProps } from '~/common';
|
||||||
import { useMessagesViewContext, useAssistantsMapContext, useAgentsMapContext } from '~/Providers';
|
import { useMessagesViewContext, useAssistantsMapContext, useAgentsMapContext } from '~/Providers';
|
||||||
|
import { getTextKey, TEXT_KEY_DIVIDER, logger } from '~/utils';
|
||||||
import useCopyToClipboard from './useCopyToClipboard';
|
import useCopyToClipboard from './useCopyToClipboard';
|
||||||
import { getTextKey, logger } from '~/utils';
|
|
||||||
|
|
||||||
export default function useMessageHelpers(props: TMessageProps) {
|
export default function useMessageHelpers(props: TMessageProps) {
|
||||||
const latestText = useRef<string | number>('');
|
const latestText = useRef<string | number>('');
|
||||||
|
|
@ -49,15 +49,27 @@ export default function useMessageHelpers(props: TMessageProps) {
|
||||||
messageId: message.messageId,
|
messageId: message.messageId,
|
||||||
convoId,
|
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 (
|
if (
|
||||||
textKey !== latestText.current ||
|
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;
|
latestText.current = textKey;
|
||||||
setLatestMessage({ ...message });
|
setLatestMessage({ ...message });
|
||||||
} else {
|
} else {
|
||||||
logger.log('No change in latest message', logInfo);
|
logger.log('latest_message', 'No change in latest message', logInfo);
|
||||||
}
|
}
|
||||||
}, [isLast, message, setLatestMessage, conversation?.conversationId]);
|
}, [isLast, message, setLatestMessage, conversation?.conversationId]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import { useRecoilValue } from 'recoil';
|
||||||
import { Constants } from 'librechat-data-provider';
|
import { Constants } from 'librechat-data-provider';
|
||||||
import { useEffect, useRef, useCallback, useMemo, useState } from 'react';
|
import { useEffect, useRef, useCallback, useMemo, useState } from 'react';
|
||||||
import type { TMessage } from 'librechat-data-provider';
|
import type { TMessage } from 'librechat-data-provider';
|
||||||
|
import { getTextKey, TEXT_KEY_DIVIDER, logger } from '~/utils';
|
||||||
import { useMessagesViewContext } from '~/Providers';
|
import { useMessagesViewContext } from '~/Providers';
|
||||||
import { getTextKey, logger } from '~/utils';
|
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
||||||
export default function useMessageProcess({ message }: { message?: TMessage | null }) {
|
export default function useMessageProcess({ message }: { message?: TMessage | null }) {
|
||||||
|
|
@ -43,11 +43,21 @@ export default function useMessageProcess({ message }: { message?: TMessage | nu
|
||||||
messageId: message.messageId,
|
messageId: message.messageId,
|
||||||
convoId,
|
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 (
|
if (
|
||||||
textKey !== latestText.current ||
|
textKey !== latestText.current ||
|
||||||
(convoId != null &&
|
(convoId != null && previousConvoId != null && convoId !== previousConvoId)
|
||||||
latestText.current &&
|
|
||||||
convoId !== latestText.current.split(Constants.COMMON_DIVIDER)[2])
|
|
||||||
) {
|
) {
|
||||||
logger.log('latest_message', '[useMessageProcess] Setting latest message; logInfo:', logInfo);
|
logger.log('latest_message', '[useMessageProcess] Setting latest message; logInfo:', logInfo);
|
||||||
latestText.current = textKey;
|
latestText.current = textKey;
|
||||||
|
|
|
||||||
|
|
@ -339,6 +339,7 @@ export default function useEventHandlers({
|
||||||
|
|
||||||
setShowStopButton(true);
|
setShowStopButton(true);
|
||||||
if (resetLatestMessage) {
|
if (resetLatestMessage) {
|
||||||
|
logger.log('latest_message', 'syncHandler: resetting latest message');
|
||||||
resetLatestMessage();
|
resetLatestMessage();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -418,6 +419,7 @@ export default function useEventHandlers({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resetLatestMessage) {
|
if (resetLatestMessage) {
|
||||||
|
logger.log('latest_message', 'createdHandler: resetting latest message');
|
||||||
resetLatestMessage();
|
resetLatestMessage();
|
||||||
}
|
}
|
||||||
scrollToEnd(() => setAbortScroll(false));
|
scrollToEnd(() => setAbortScroll(false));
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,7 @@ const useNewConvo = (index = 0) => {
|
||||||
}
|
}
|
||||||
setSubmission({} as TSubmission);
|
setSubmission({} as TSubmission);
|
||||||
if (!(keepLatestMessage ?? false)) {
|
if (!(keepLatestMessage ?? false)) {
|
||||||
|
logger.log('latest_message', 'Clearing all latest messages');
|
||||||
clearAllLatestMessages();
|
clearAllLatestMessages();
|
||||||
}
|
}
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
|
|
|
||||||
|
|
@ -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';
|
import type { TMessage, TMessageContentParts } from 'librechat-data-provider';
|
||||||
|
|
||||||
export const getLengthAndLastTenChars = (str?: string): string => {
|
export const TEXT_KEY_DIVIDER = '|||';
|
||||||
if (typeof str !== 'string' || str.length === 0) {
|
|
||||||
return '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
const length = str.length;
|
|
||||||
const lastTenChars = str.slice(-10);
|
|
||||||
return `${length}${lastTenChars}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getLatestText = (message?: TMessage | null, includeIndex?: boolean): string => {
|
export const getLatestText = (message?: TMessage | null, includeIndex?: boolean): string => {
|
||||||
if (!message) {
|
if (!message) {
|
||||||
|
|
@ -65,16 +57,84 @@ export const getAllContentText = (message?: TMessage | null): string => {
|
||||||
return '';
|
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) => {
|
export const getTextKey = (message?: TMessage | null, convoId?: string | null) => {
|
||||||
if (!message) {
|
if (!message) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
const text = getLatestText(message, true);
|
const contentKey = getLatestContentForKey(message);
|
||||||
return `${(message.messageId as string | null) ?? ''}${
|
return `${(message.messageId as string | null) ?? ''}${TEXT_KEY_DIVIDER}${contentKey}${TEXT_KEY_DIVIDER}${message.conversationId ?? convoId}`;
|
||||||
Constants.COMMON_DIVIDER
|
|
||||||
}${getLengthAndLastTenChars(text)}${Constants.COMMON_DIVIDER}${
|
|
||||||
message.conversationId ?? convoId
|
|
||||||
}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const scrollToEnd = (callback?: () => void) => {
|
export const scrollToEnd = (callback?: () => void) => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue