mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-19 09:50:15 +01:00
⌚ fix: Wait for Initial Message Save & Correct Latest Message (#3399)
* chore: assistants, unsupported assistant, better logging * chore: remove unnecessary logger in validateAssistant middleware * fix: resolve initial conversation save/promise before saving response * chore: Import and organize dependencies in Speech component * fix: conversation statefulness - Latest Message (at index 0) should not be reset if existing convo - add debugging context for clearAllLatestMessages - Added logging concerning latest Message updates (dev mode only) - update latest message Set logic, also checks for change in conversation Id - consolidated latest message helpers to client/src/utils/messages.ts
This commit is contained in:
parent
9e7615f832
commit
2ad097647c
25 changed files with 275 additions and 113 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { useRecoilValue } from 'recoil';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useState, useRef, useMemo } from 'react';
|
||||
import { Constants } from 'librechat-data-provider';
|
||||
import { useGetEndpointsQuery, useGetStartupConfig } from 'librechat-data-provider/react-query';
|
||||
import type { MouseEvent, FocusEvent, KeyboardEvent } from 'react';
|
||||
import { useUpdateConversationMutation } from '~/data-provider';
|
||||
|
|
@ -9,14 +10,14 @@ import { useConversations, useNavigateToConvo } from '~/hooks';
|
|||
import { NotificationSeverity } from '~/common';
|
||||
import { ArchiveIcon } from '~/components/svg';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import DropDownMenu from './DropDownMenu';
|
||||
import ArchiveButton from './ArchiveButton';
|
||||
import DropDownMenu from './DropDownMenu';
|
||||
import DeleteButton from './DeleteButton';
|
||||
import RenameButton from './RenameButton';
|
||||
import HoverToggle from './HoverToggle';
|
||||
import ShareButton from './ShareButton';
|
||||
import { cn } from '~/utils';
|
||||
import store from '~/store';
|
||||
import ShareButton from './ShareButton';
|
||||
|
||||
type KeyEvent = KeyboardEvent<HTMLInputElement>;
|
||||
|
||||
|
|
@ -51,7 +52,8 @@ export default function Conversation({ conversation, retainView, toggleNav, isLa
|
|||
|
||||
// set document title
|
||||
document.title = title;
|
||||
navigateWithLastTools(conversation);
|
||||
/* Note: Latest Message should not be reset if existing convo */
|
||||
navigateWithLastTools(conversation, !conversationId || conversationId === Constants.NEW_CONVO);
|
||||
};
|
||||
|
||||
const renameHandler = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import * as Tabs from '@radix-ui/react-tabs';
|
||||
import { Lightbulb, Cog } from 'lucide-react';
|
||||
import { SettingsTabValues } from 'librechat-data-provider';
|
||||
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { Lightbulb, Cog } from 'lucide-react';
|
||||
import { useOnClickOutside, useMediaQuery } from '~/hooks';
|
||||
import store from '~/store';
|
||||
import { cn } from '~/utils';
|
||||
import ConversationModeSwitch from './ConversationModeSwitch';
|
||||
import { useGetCustomConfigSpeechQuery } from 'librechat-data-provider/react-query';
|
||||
import {
|
||||
CloudBrowserVoicesSwitch,
|
||||
AutomaticPlaybackSwitch,
|
||||
|
|
@ -24,7 +21,10 @@ import {
|
|||
EngineSTTDropdown,
|
||||
DecibelSelector,
|
||||
} from './STT';
|
||||
import { useGetCustomConfigSpeechQuery } from 'librechat-data-provider/react-query';
|
||||
import ConversationModeSwitch from './ConversationModeSwitch';
|
||||
import { useOnClickOutside, useMediaQuery } from '~/hooks';
|
||||
import { cn, logger } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
function Speech() {
|
||||
const [confirmClear, setConfirmClear] = useState(false);
|
||||
|
|
@ -131,8 +131,7 @@ function Speech() {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data]);
|
||||
|
||||
console.log(sttExternal);
|
||||
console.log(ttsExternal);
|
||||
logger.log({ sttExternal, ttsExternal });
|
||||
|
||||
const contentRef = useRef(null);
|
||||
useOnClickOutside(contentRef, () => confirmClear && setConfirmClear(false), []);
|
||||
|
|
|
|||
|
|
@ -107,6 +107,16 @@ export default function useChatFunctions({
|
|||
const intermediateId = overrideUserMessageId ?? v4();
|
||||
parentMessageId = parentMessageId || latestMessage?.messageId || Constants.NO_PARENT;
|
||||
|
||||
logger.dir('Ask function called with:', {
|
||||
index,
|
||||
latestMessage,
|
||||
conversationId,
|
||||
intermediateId,
|
||||
parentMessageId,
|
||||
currentMessages,
|
||||
});
|
||||
logger.log('=====================================');
|
||||
|
||||
if (conversationId == Constants.NEW_CONVO) {
|
||||
parentMessageId = Constants.NO_PARENT;
|
||||
currentMessages = [];
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import type {
|
|||
TModelsConfig,
|
||||
TEndpointsConfig,
|
||||
} from 'librechat-data-provider';
|
||||
import { buildDefaultConvo, getDefaultEndpoint, getEndpointField } from '~/utils';
|
||||
import { buildDefaultConvo, getDefaultEndpoint, getEndpointField, logger } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
const useConversation = () => {
|
||||
|
|
@ -60,6 +60,10 @@ const useConversation = () => {
|
|||
setMessages(messages);
|
||||
setSubmission({} as TSubmission);
|
||||
resetLatestMessage();
|
||||
logger.log(
|
||||
'[useConversation] Switched to conversation and reset Latest Message',
|
||||
conversation,
|
||||
);
|
||||
|
||||
if (conversation.conversationId === 'new' && !modelsData) {
|
||||
queryClient.invalidateQueries([QueryKeys.messages, 'new']);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useSetRecoilState } from 'recoil';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { QueryKeys, EModelEndpoint, LocalStorageKeys } from 'librechat-data-provider';
|
||||
import { QueryKeys, EModelEndpoint, LocalStorageKeys, Constants } from 'librechat-data-provider';
|
||||
import type { TConversation, TEndpointsConfig, TModelsConfig } from 'librechat-data-provider';
|
||||
import { buildDefaultConvo, getDefaultEndpoint, getEndpointField } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
|
@ -10,7 +10,7 @@ const useNavigateToConvo = (index = 0) => {
|
|||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const clearAllConversations = store.useClearConvoState();
|
||||
const clearAllLatestMessages = store.useClearLatestMessages();
|
||||
const clearAllLatestMessages = store.useClearLatestMessages(`useNavigateToConvo ${index}`);
|
||||
const setSubmission = useSetRecoilState(store.submissionByIndex(index));
|
||||
const { setConversation } = store.useCreateConversationAtom(index);
|
||||
|
||||
|
|
@ -50,10 +50,10 @@ const useNavigateToConvo = (index = 0) => {
|
|||
}
|
||||
clearAllConversations(true);
|
||||
setConversation(convo);
|
||||
navigate(`/c/${convo.conversationId ?? 'new'}`);
|
||||
navigate(`/c/${convo.conversationId ?? Constants.NEW_CONVO}`);
|
||||
};
|
||||
|
||||
const navigateWithLastTools = (conversation: TConversation) => {
|
||||
const navigateWithLastTools = (conversation: TConversation, _resetLatestMessage?: boolean) => {
|
||||
// set conversation to the new conversation
|
||||
if (conversation?.endpoint === EModelEndpoint.gptPlugins) {
|
||||
let lastSelectedTools = [];
|
||||
|
|
@ -63,12 +63,15 @@ const useNavigateToConvo = (index = 0) => {
|
|||
} catch (e) {
|
||||
// console.error(e);
|
||||
}
|
||||
navigateToConvo({
|
||||
...conversation,
|
||||
tools: conversation?.tools?.length ? conversation?.tools : lastSelectedTools,
|
||||
});
|
||||
navigateToConvo(
|
||||
{
|
||||
...conversation,
|
||||
tools: conversation?.tools?.length ? conversation?.tools : lastSelectedTools,
|
||||
},
|
||||
_resetLatestMessage,
|
||||
);
|
||||
} else {
|
||||
navigateToConvo(conversation);
|
||||
navigateToConvo(conversation, _resetLatestMessage);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { useEffect, useRef, useCallback } from 'react';
|
||||
import { isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import { Constants, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type { TMessageProps } from '~/common';
|
||||
import { useChatContext, useAssistantsMapContext } from '~/Providers';
|
||||
import { getLatestText, getLengthAndFirstFiveChars } from '~/utils';
|
||||
import useCopyToClipboard from './useCopyToClipboard';
|
||||
import { getTextKey, logger } from '~/utils';
|
||||
|
||||
export default function useMessageHelpers(props: TMessageProps) {
|
||||
const latestText = useRef<string | number>('');
|
||||
|
|
@ -27,7 +27,8 @@ export default function useMessageHelpers(props: TMessageProps) {
|
|||
const isLast = !children?.length;
|
||||
|
||||
useEffect(() => {
|
||||
if (conversation?.conversationId === 'new') {
|
||||
const convoId = conversation?.conversationId;
|
||||
if (convoId === Constants.NEW_CONVO) {
|
||||
return;
|
||||
}
|
||||
if (!message) {
|
||||
|
|
@ -37,15 +38,25 @@ export default function useMessageHelpers(props: TMessageProps) {
|
|||
return;
|
||||
}
|
||||
|
||||
const text = getLatestText(message);
|
||||
const textKey = `${message?.messageId ?? ''}${getLengthAndFirstFiveChars(text)}`;
|
||||
const textKey = getTextKey(message, convoId);
|
||||
|
||||
if (textKey === latestText.current) {
|
||||
return;
|
||||
// Check for text/conversation change
|
||||
const logInfo = {
|
||||
textKey,
|
||||
'latestText.current': latestText.current,
|
||||
messageId: message?.messageId,
|
||||
convoId,
|
||||
};
|
||||
if (
|
||||
textKey !== latestText.current ||
|
||||
(latestText.current && convoId !== latestText.current.split(Constants.COMMON_DIVIDER)[2])
|
||||
) {
|
||||
logger.log('[useMessageHelpers] Setting latest message: ', logInfo);
|
||||
latestText.current = textKey;
|
||||
setLatestMessage({ ...message });
|
||||
} else {
|
||||
logger.log('No change in latest message', logInfo);
|
||||
}
|
||||
|
||||
latestText.current = textKey;
|
||||
setLatestMessage({ ...message });
|
||||
}, [isLast, message, setLatestMessage, conversation?.conversationId]);
|
||||
|
||||
const enterEdit = useCallback(
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
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 { useChatContext, useAddedChatContext } from '~/Providers';
|
||||
import { getLatestText, getLengthAndFirstFiveChars } from '~/utils';
|
||||
import { getTextKey, logger } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
export default function useMessageProcess({ message }: { message?: TMessage | null }) {
|
||||
|
|
@ -26,7 +27,8 @@ export default function useMessageProcess({ message }: { message?: TMessage | nu
|
|||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (conversation?.conversationId === 'new') {
|
||||
const convoId = conversation?.conversationId;
|
||||
if (convoId === Constants.NEW_CONVO) {
|
||||
return;
|
||||
}
|
||||
if (!message) {
|
||||
|
|
@ -36,15 +38,27 @@ export default function useMessageProcess({ message }: { message?: TMessage | nu
|
|||
return;
|
||||
}
|
||||
|
||||
const text = getLatestText(message);
|
||||
const textKey = `${message?.messageId ?? ''}${getLengthAndFirstFiveChars(text)}`;
|
||||
const textKey = getTextKey(message, convoId);
|
||||
|
||||
if (textKey === latestText.current) {
|
||||
return;
|
||||
// Check for text/conversation change
|
||||
const logInfo = {
|
||||
textKey,
|
||||
'latestText.current': latestText.current,
|
||||
messageId: message?.messageId,
|
||||
convoId,
|
||||
};
|
||||
if (
|
||||
textKey !== latestText.current ||
|
||||
(convoId &&
|
||||
latestText.current &&
|
||||
convoId !== latestText.current.split(Constants.COMMON_DIVIDER)[2])
|
||||
) {
|
||||
logger.log('[useMessageProcess] Setting latest message: ', logInfo);
|
||||
latestText.current = textKey;
|
||||
setLatestMessage({ ...message });
|
||||
} else {
|
||||
logger.log('No change in latest message', logInfo);
|
||||
}
|
||||
|
||||
latestText.current = textKey;
|
||||
setLatestMessage({ ...message });
|
||||
}, [hasNoChildren, message, setLatestMessage, conversation?.conversationId]);
|
||||
|
||||
const handleScroll = useCallback(() => {
|
||||
|
|
|
|||
|
|
@ -34,9 +34,9 @@ const useNewConvo = (index = 0) => {
|
|||
const { data: startupConfig } = useGetStartupConfig();
|
||||
const clearAllConversations = store.useClearConvoState();
|
||||
const defaultPreset = useRecoilValue(store.defaultPreset);
|
||||
const clearAllLatestMessages = store.useClearLatestMessages();
|
||||
const { setConversation } = store.useCreateConversationAtom(index);
|
||||
const [files, setFiles] = useRecoilState(store.filesByIndex(index));
|
||||
const clearAllLatestMessages = store.useClearLatestMessages(`useNewConvo ${index}`);
|
||||
const setSubmission = useSetRecoilState<TSubmission | null>(store.submissionByIndex(index));
|
||||
const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery();
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
useSetRecoilState,
|
||||
useRecoilCallback,
|
||||
} from 'recoil';
|
||||
import { LocalStorageKeys } from 'librechat-data-provider';
|
||||
import { LocalStorageKeys, Constants } from 'librechat-data-provider';
|
||||
import type { TMessage, TPreset, TConversation, TSubmission } from 'librechat-data-provider';
|
||||
import type { TOptionSettings, ExtendedFile } from '~/common';
|
||||
import { storeEndpointSettings, logger } from '~/utils';
|
||||
|
|
@ -27,6 +27,14 @@ const submissionKeysAtom = atom<(string | number)[]>({
|
|||
const latestMessageFamily = atomFamily<TMessage | null, string | number | null>({
|
||||
key: 'latestMessageByIndex',
|
||||
default: null,
|
||||
effects: [
|
||||
({ onSet, node }) => {
|
||||
onSet(async (newValue) => {
|
||||
const key = Number(node.key.split(Constants.COMMON_DIVIDER)[1]);
|
||||
logger.log('Recoil Effect: Setting latestMessage', { key, newValue });
|
||||
});
|
||||
},
|
||||
] as const,
|
||||
});
|
||||
|
||||
const submissionByIndex = atomFamily<TSubmission | null, string | number>({
|
||||
|
|
@ -41,7 +49,7 @@ const latestMessageKeysSelector = selector<(string | number)[]>({
|
|||
return keys.filter((key) => get(latestMessageFamily(key)) !== null);
|
||||
},
|
||||
set: ({ set }, newKeys) => {
|
||||
logger.log('setting latestMessageKeys', newKeys);
|
||||
logger.log('setting latestMessageKeys', { newKeys });
|
||||
set(latestMessageKeysAtom, newKeys);
|
||||
},
|
||||
});
|
||||
|
|
@ -279,19 +287,22 @@ function useClearSubmissionState() {
|
|||
return clearAllSubmissions;
|
||||
}
|
||||
|
||||
function useClearLatestMessages() {
|
||||
function useClearLatestMessages(context?: string) {
|
||||
const clearAllLatestMessages = useRecoilCallback(
|
||||
({ reset, set, snapshot }) =>
|
||||
async (skipFirst?: boolean) => {
|
||||
const latestMessageKeys = await snapshot.getPromise(latestMessageKeysSelector);
|
||||
logger.log('latestMessageKeys', latestMessageKeys);
|
||||
logger.log('[clearAllLatestMessages] latestMessageKeys', latestMessageKeys);
|
||||
if (context) {
|
||||
logger.log(`[clearAllLatestMessages] context: ${context}`);
|
||||
}
|
||||
|
||||
for (const key of latestMessageKeys) {
|
||||
if (skipFirst && key == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.log('resetting latest message', key);
|
||||
logger.log(`[clearAllLatestMessages] resetting latest message; key: ${key}`);
|
||||
reset(latestMessageFamily(key));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
import { ContentTypes } from 'librechat-data-provider';
|
||||
import { ContentTypes, Constants } from 'librechat-data-provider';
|
||||
import type { TMessage } from 'librechat-data-provider';
|
||||
|
||||
export const getLengthAndFirstFiveChars = (str?: string) => {
|
||||
const length = str ? str.length : 0;
|
||||
const firstFiveChars = str ? str.substring(0, 5) : '';
|
||||
return `${length}${firstFiveChars}`;
|
||||
export const getLengthAndLastTenChars = (str?: string): string => {
|
||||
if (!str) {
|
||||
return '0';
|
||||
}
|
||||
|
||||
const length = str.length;
|
||||
const lastTenChars = str.slice(-10);
|
||||
return `${length}${lastTenChars}`;
|
||||
};
|
||||
|
||||
export const getLatestText = (message?: TMessage | null) => {
|
||||
export const getLatestText = (message?: TMessage | null, includeIndex?: boolean) => {
|
||||
if (!message) {
|
||||
return '';
|
||||
}
|
||||
|
|
@ -18,9 +22,24 @@ export const getLatestText = (message?: TMessage | null) => {
|
|||
for (let i = message.content.length - 1; i >= 0; i--) {
|
||||
const part = message.content[i];
|
||||
if (part.type === ContentTypes.TEXT && part[ContentTypes.TEXT]?.value?.length > 0) {
|
||||
return part[ContentTypes.TEXT].value;
|
||||
const text = part[ContentTypes.TEXT].value;
|
||||
if (includeIndex) {
|
||||
return `${text}-${i}`;
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
export const getTextKey = (message?: TMessage | null, convoId?: string | null) => {
|
||||
if (!message) {
|
||||
return '';
|
||||
}
|
||||
const text = getLatestText(message, true);
|
||||
return `${message.messageId ?? ''}${Constants.COMMON_DIVIDER}${getLengthAndLastTenChars(text)}${
|
||||
Constants.COMMON_DIVIDER
|
||||
}${message.conversationId ?? convoId}`;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue