mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
🛠️ fix: Conversation Navigation State (#7210)
* refactor: Enhance initial conversation query condition for better state management and prevent unused network requests * ifx: Add Prettier plugin to ESLint configuration * chore: linting and typing in convos.spec.ts * fix: add back fresh data fetching and improve error handling for conversation navigation * fix: set conversation only with conversation state change intent, to prevent double queries for messages
This commit is contained in:
parent
ddb2141eac
commit
6e663b2480
6 changed files with 52 additions and 23 deletions
|
|
@ -30,6 +30,7 @@ import type {
|
||||||
SharedLinksResponse,
|
SharedLinksResponse,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
import type { ConversationCursorData } from '~/utils/convos';
|
import type { ConversationCursorData } from '~/utils/convos';
|
||||||
|
import { findConversationInInfinite } from '~/utils';
|
||||||
|
|
||||||
export const useGetPresetsQuery = (
|
export const useGetPresetsQuery = (
|
||||||
config?: UseQueryOptions<TPreset[]>,
|
config?: UseQueryOptions<TPreset[]>,
|
||||||
|
|
@ -68,14 +69,13 @@ export const useGetConvoIdQuery = (
|
||||||
[QueryKeys.conversation, id],
|
[QueryKeys.conversation, id],
|
||||||
() => {
|
() => {
|
||||||
// Try to find in all fetched infinite pages
|
// Try to find in all fetched infinite pages
|
||||||
const convosQuery = queryClient.getQueryData<InfiniteData<ConversationCursorData>>([
|
const convosQuery = queryClient.getQueryData<InfiniteData<ConversationCursorData>>(
|
||||||
QueryKeys.allConversations,
|
[QueryKeys.allConversations],
|
||||||
]);
|
{ exact: false },
|
||||||
const found = convosQuery?.pages
|
);
|
||||||
.flatMap((page) => page.conversations)
|
const found = findConversationInInfinite(convosQuery, id);
|
||||||
.find((c) => c.conversationId === id);
|
|
||||||
|
|
||||||
if (found) {
|
if (found && found.messages != null) {
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
// Otherwise, fetch from API
|
// Otherwise, fetch from API
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { QueryKeys, Constants } from 'librechat-data-provider';
|
import { QueryKeys, Constants, dataService } from 'librechat-data-provider';
|
||||||
import type { TConversation, TEndpointsConfig, TModelsConfig } from 'librechat-data-provider';
|
import type { TConversation, TEndpointsConfig, TModelsConfig } from 'librechat-data-provider';
|
||||||
import { buildDefaultConvo, getDefaultEndpoint, getEndpointField, logger } from '~/utils';
|
import { buildDefaultConvo, getDefaultEndpoint, getEndpointField, logger } from '~/utils';
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
@ -14,6 +14,27 @@ const useNavigateToConvo = (index = 0) => {
|
||||||
const clearAllLatestMessages = store.useClearLatestMessages(`useNavigateToConvo ${index}`);
|
const clearAllLatestMessages = store.useClearLatestMessages(`useNavigateToConvo ${index}`);
|
||||||
const { hasSetConversation, setConversation } = store.useCreateConversationAtom(index);
|
const { hasSetConversation, setConversation } = store.useCreateConversationAtom(index);
|
||||||
|
|
||||||
|
const fetchFreshData = async (conversation?: Partial<TConversation>) => {
|
||||||
|
const conversationId = conversation?.conversationId;
|
||||||
|
if (!conversationId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const data = await queryClient.fetchQuery([QueryKeys.conversation, conversationId], () =>
|
||||||
|
dataService.getConversationById(conversationId),
|
||||||
|
);
|
||||||
|
logger.log('conversation', 'Fetched fresh conversation data', data);
|
||||||
|
setConversation(data);
|
||||||
|
navigate(`/c/${conversationId ?? Constants.NEW_CONVO}`, { state: { focusChat: true } });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching conversation data on navigation', error);
|
||||||
|
if (conversation) {
|
||||||
|
setConversation(conversation as TConversation);
|
||||||
|
navigate(`/c/${conversationId}`, { state: { focusChat: true } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const navigateToConvo = (
|
const navigateToConvo = (
|
||||||
conversation?: TConversation | null,
|
conversation?: TConversation | null,
|
||||||
options?: {
|
options?: {
|
||||||
|
|
@ -58,9 +79,14 @@ const useNavigateToConvo = (index = 0) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
clearAllConversations(true);
|
clearAllConversations(true);
|
||||||
setConversation(convo);
|
|
||||||
queryClient.setQueryData([QueryKeys.messages, currentConvoId], []);
|
queryClient.setQueryData([QueryKeys.messages, currentConvoId], []);
|
||||||
navigate(`/c/${convo.conversationId ?? Constants.NEW_CONVO}`, { state: { focusChat: true } });
|
if (convo.conversationId !== Constants.NEW_CONVO && convo.conversationId) {
|
||||||
|
queryClient.invalidateQueries([QueryKeys.conversation, convo.conversationId]);
|
||||||
|
fetchFreshData(convo);
|
||||||
|
} else {
|
||||||
|
setConversation(convo);
|
||||||
|
navigate(`/c/${convo.conversationId ?? Constants.NEW_CONVO}`, { state: { focusChat: true } });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,8 @@ export default function ChatRoute() {
|
||||||
refetchOnMount: 'always',
|
refetchOnMount: 'always',
|
||||||
});
|
});
|
||||||
const initialConvoQuery = useGetConvoIdQuery(conversationId, {
|
const initialConvoQuery = useGetConvoIdQuery(conversationId, {
|
||||||
enabled: isAuthenticated && conversationId !== Constants.NEW_CONVO,
|
enabled:
|
||||||
|
isAuthenticated && conversationId !== Constants.NEW_CONVO && !hasSetConversation.current,
|
||||||
});
|
});
|
||||||
const endpointsQuery = useGetEndpointsQuery({ enabled: isAuthenticated });
|
const endpointsQuery = useGetEndpointsQuery({ enabled: isAuthenticated });
|
||||||
const assistantListMap = useAssistantListMap();
|
const assistantListMap = useAssistantListMap();
|
||||||
|
|
|
||||||
|
|
@ -431,14 +431,14 @@ describe('Conversation Utilities', () => {
|
||||||
pageParams: [],
|
pageParams: [],
|
||||||
};
|
};
|
||||||
const newConvo = makeConversation('new');
|
const newConvo = makeConversation('new');
|
||||||
const updated = addConversationToInfinitePages(data, newConvo);
|
const updated = addConversationToInfinitePages(data, newConvo as TConversation);
|
||||||
expect(updated.pages[0].conversations[0].conversationId).toBe('new');
|
expect(updated.pages[0].conversations[0].conversationId).toBe('new');
|
||||||
expect(updated.pages[0].conversations[1].conversationId).toBe('1');
|
expect(updated.pages[0].conversations[1].conversationId).toBe('1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates new InfiniteData if data is undefined', () => {
|
it('creates new InfiniteData if data is undefined', () => {
|
||||||
const newConvo = makeConversation('new');
|
const newConvo = makeConversation('new');
|
||||||
const updated = addConversationToInfinitePages(undefined, newConvo);
|
const updated = addConversationToInfinitePages(undefined, newConvo as TConversation);
|
||||||
expect(updated.pages[0].conversations[0].conversationId).toBe('new');
|
expect(updated.pages[0].conversations[0].conversationId).toBe('new');
|
||||||
expect(updated.pageParams).toEqual([undefined]);
|
expect(updated.pageParams).toEqual([undefined]);
|
||||||
});
|
});
|
||||||
|
|
@ -531,12 +531,12 @@ describe('Conversation Utilities', () => {
|
||||||
it('stores model for endpoint', () => {
|
it('stores model for endpoint', () => {
|
||||||
const conversation = {
|
const conversation = {
|
||||||
conversationId: '1',
|
conversationId: '1',
|
||||||
endpoint: 'openai',
|
endpoint: 'openAI',
|
||||||
model: 'gpt-3',
|
model: 'gpt-3',
|
||||||
};
|
};
|
||||||
storeEndpointSettings(conversation as any);
|
storeEndpointSettings(conversation as any);
|
||||||
const stored = JSON.parse(localStorage.getItem('lastModel') || '{}');
|
const stored = JSON.parse(localStorage.getItem('lastModel') || '{}');
|
||||||
expect([undefined, 'gpt-3']).toContain(stored.openai);
|
expect([undefined, 'gpt-3']).toContain(stored.openAI);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('stores secondaryModel for gptPlugins endpoint', () => {
|
it('stores secondaryModel for gptPlugins endpoint', () => {
|
||||||
|
|
@ -574,14 +574,14 @@ describe('Conversation Utilities', () => {
|
||||||
conversationId: 'a',
|
conversationId: 'a',
|
||||||
updatedAt: '2024-01-01T12:00:00Z',
|
updatedAt: '2024-01-01T12:00:00Z',
|
||||||
createdAt: '2024-01-01T10:00:00Z',
|
createdAt: '2024-01-01T10:00:00Z',
|
||||||
endpoint: 'openai',
|
endpoint: 'openAI',
|
||||||
model: 'gpt-3',
|
model: 'gpt-3',
|
||||||
title: 'Conversation A',
|
title: 'Conversation A',
|
||||||
} as TConversation;
|
} as TConversation;
|
||||||
convoB = {
|
convoB = {
|
||||||
conversationId: 'b',
|
conversationId: 'b',
|
||||||
updatedAt: '2024-01-02T12:00:00Z',
|
updatedAt: '2024-01-02T12:00:00Z',
|
||||||
endpoint: 'openai',
|
endpoint: 'openAI',
|
||||||
model: 'gpt-3',
|
model: 'gpt-3',
|
||||||
} as TConversation;
|
} as TConversation;
|
||||||
queryClient.setQueryData(['allConversations'], {
|
queryClient.setQueryData(['allConversations'], {
|
||||||
|
|
|
||||||
|
|
@ -280,11 +280,11 @@ export function updateConvoFieldsInfinite(
|
||||||
pages: data.pages.map((page, pi) =>
|
pages: data.pages.map((page, pi) =>
|
||||||
pi === pageIdx
|
pi === pageIdx
|
||||||
? {
|
? {
|
||||||
...page,
|
...page,
|
||||||
conversations: page.conversations.map((c, ci) =>
|
conversations: page.conversations.map((c, ci) =>
|
||||||
ci === convoIdx ? { ...c, ...updatedConversation } : c,
|
ci === convoIdx ? { ...c, ...updatedConversation } : c,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
: page,
|
: page,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import typescriptEslintEslintPlugin from '@typescript-eslint/eslint-plugin';
|
||||||
import { fixupConfigRules, fixupPluginRules } from '@eslint/compat';
|
import { fixupConfigRules, fixupPluginRules } from '@eslint/compat';
|
||||||
// import perfectionist from 'eslint-plugin-perfectionist';
|
// import perfectionist from 'eslint-plugin-perfectionist';
|
||||||
import reactHooks from 'eslint-plugin-react-hooks';
|
import reactHooks from 'eslint-plugin-react-hooks';
|
||||||
|
import prettier from 'eslint-plugin-prettier';
|
||||||
import tsParser from '@typescript-eslint/parser';
|
import tsParser from '@typescript-eslint/parser';
|
||||||
import importPlugin from 'eslint-plugin-import';
|
import importPlugin from 'eslint-plugin-import';
|
||||||
import { FlatCompat } from '@eslint/eslintrc';
|
import { FlatCompat } from '@eslint/eslintrc';
|
||||||
|
|
@ -62,6 +63,7 @@ export default [
|
||||||
'import/parsers': tsParser,
|
'import/parsers': tsParser,
|
||||||
i18next,
|
i18next,
|
||||||
// perfectionist,
|
// perfectionist,
|
||||||
|
prettier: fixupPluginRules(prettier),
|
||||||
},
|
},
|
||||||
|
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
|
|
@ -357,4 +359,4 @@ export default [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue