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:
Danny Avila 2025-10-10 11:22:16 +03:00 committed by GitHub
parent 20282f32c8
commit fbe341a171
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 118 additions and 29 deletions

View file

@ -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) => {